Skip to content

Commit

Permalink
Fix case
Browse files Browse the repository at this point in the history
  • Loading branch information
Nadwey committed Feb 3, 2024
1 parent 91499eb commit 4097d97
Show file tree
Hide file tree
Showing 2 changed files with 344 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/main/installer/GameInstall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { GetLauncherDirectory, GetVersionsDirectory } from "../utils";
import path from "path";
import { DownloadAction, ExtractAction } from "./Installer";
import { GetVersionZipFile } from "../SE3Api";

/**
* @param {string} version
* @returns {import("./Installer").InstallerArgs}
*/
const GameInstall = async(version) => {
const url = (await GetVersionZipFile(version)).url;
const filename = path.basename(new URL(url).pathname);
const downloadedFile = path.join(GetLauncherDirectory(), filename);

return {
actions: [
new DownloadAction(url, downloadedFile),
new ExtractAction(downloadedFile, path.join(GetVersionsDirectory(), version))
],
type: "version",
version,
};
};

export default GameInstall;
319 changes: 319 additions & 0 deletions src/main/installer/Installer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
import fs from "fs";
import axios from "axios";
import EventEmitter from "node:events";
import compressing from "compressing";
import path from "path";

class Action extends EventEmitter {
constructor() {
super();

if (this.constructor == Action) {
throw new Error("Action class can't be instantiated.");
}
}

/**
* Executes the action
*/
async execute() {
throw new Error("execute() not implemented");
}

/**
* Cancels the action
*/
cancel() {
throw new Error("cancel() not implemented");
}

/**
* Text that gets displayed in the UI
*
* @type {string}
*/
displayText;

/**
* Details that get displayed in the UI
*
* @type {string}
*/
detailsText;

/**
* Progress of the action
*
* @type {number}
*/
progress;

updateData() {
this.emit("data", {
displayText: this.displayText,
detailsText: this.detailsText,
progress: this.progress,
});
}
}

export class DownloadAction extends Action {
/**
* Action for downloading a file
*
* @param {String} url
* @param {String} savePath
*/
constructor(url, savePath) {
super();

this.url = url;
this.savePath = savePath;

this.displayText = "Downloading...";
}

async execute() {
this.updateData();

try {
if (fs.existsSync(this.savePath)) fs.rmSync(this.savePath);

this.abortController = new AbortController();

const res = await axios.get(this.url, {
responseType: "stream",
signal: this.abortController.signal,
});

const totalLength = parseInt(res.headers["content-length"], 10);

this.writeStream = fs.createWriteStream(this.savePath);
this.writeStream.on("error", (err) => {
this.emit("error", err);
});
this.writeStream.on("close", async () => {
this.emit("finished");
});

let downloadedBytes = 0;
res.data.on("data", (chunk) => {
downloadedBytes += chunk.length;
this.progress = (downloadedBytes / totalLength) * 100;
this.detailsText = `${(downloadedBytes / 1024 / 1024).toFixed(2)}MiB / ${(totalLength / 1024 / 1024).toFixed(2)}MiB bytes`;
this.updateData();
});

res.data.pipe(this.writeStream);
} catch (error) {
this.emit("error", `Failed to install version, ${error.toString()}`);
}
}

/**
* Cancels the action
*/
cancel() {
this.abortController.abort();
this.writeStream.close();
}

/**
* @type {string}
*/
url;

/**
* @type {string}
*/
savePath;

/**
* @private
* @type {fs.WriteStream}
*/
writeStream;

/**
* @private
* @type {AbortController}
*/
abortController;
}

export class ExtractAction extends Action {
/**
* Extracts an archive
*
* @param {String} archivePath
* @param {String} extractPath
*/
constructor(archivePath, extractPath) {
super();

this.archivePath = archivePath;
this.extractPath = extractPath;

this.displayText = "Extracting...";
}

async execute() {
this.progress = null;
this.updateData();

if (!fs.existsSync(this.archivePath)) {
this.emit("error", "Archive does not exist");
return;
}

if (fs.existsSync(this.extractPath)) fs.rmSync(this.extractPath, { recursive: true, force: true });

const filename = path.basename(this.archivePath);

try {
if (filename.endsWith(".zip")) {
await compressing.zip.uncompress(this.archivePath, this.extractPath);
} else if (filename.endsWith(".tar.gz")) {
await compressing.tgz.uncompress(this.archivePath, this.extractPath);
} else {
this.emit("error", "Unknown archive type");
return;
}
} catch (ex) {
this.emit("error", ex);
return;
}
this.emit("finished");
}

/**
* @type {string}
*/
archivePath;

/**
* @type {string}
*/
extractPath;
}

export class RemoveAction extends Action {
/**
* Removes something
*
* @param {String} archivePath
* @param {String} extractPath
*/
constructor(removePath) {
super();

this.removePath = removePath;

this.displayText = "Removing...";
}

async execute() {
this.progress = null;
this.updateData();

fs.rmSync(this.removePath, { recursive: true, force: true });

this.emit("finished");
}

/**
* @type {string}
*/
removePath;
}

/**
* @typedef {Object} InstallerArgs
* @property {Action[]} actions
* @property {string} type
* @property {string} version
*/

export class Installer extends EventEmitter {
/**
* @param {InstallerArgs} args
*/
constructor(args) {
super();

this.actions = args.actions;
this.type = args.type;
this.version = args.version;
}

async Start() {
for await (const action of this.actions) {
if (this.failed) break;
await new Promise((resolve) => {
action.on("data", (data) => {
this.emit("data", data);
});

action.on("error", (err) => {
this.emit("error", err);
this.failed = true;
resolve();
});

action.on("finished", () => {
resolve();
});

action.execute();
});
this.currentAction++;
}
if (!this.failed) this.emit("finished");
}

Stop() {
try {
this.failed = true;
this.actions[this.currentAction]?.cancel?.();
return true;
} catch (ex) {
return false; // cancel is not implemented
}
}

/**
* @private
* List of actions to execute
*
* @type {Action[]}
*/
actions;

/**
* @private
* Current action
*
* @type {Number}
*/
currentAction = 0;

/**
* @private
* @type {Boolean}
*/
failed = false;

/**
* The thing that gets installed
*
* @type {string}
*/
type;

/**
* Version of the thing that gets installed
*
* @type {string}
*/
version;
}

0 comments on commit 4097d97

Please sign in to comment.