Merge pull request #190 from moonlight-mod/notnite/linux-updater
Cynthia Foxwell 1 month ago 2 files (+136, -1)
@@ -1,5 +1,10 @@import { app, nativeTheme } from "electron";+import * as path from "node:path";+import * as fs from "node:fs/promises";+import * as fsSync from "node:fs";+import { parseTarGzip } from "nanotar";+const logger = moonlightHost.getLogger("nativeFixes/host");const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");moonlightHost.events.on("window-created", function (browserWindow) {@@ -44,3 +49,126 @@ enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder", "VaapiVideoDecodeLinuxGL");}app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].join(","));++if (process.platform === "linux" && moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxUpdater")) {+ const exePath = app.getPath("exe");+ const appName = path.basename(exePath);+ const targetDir = path.dirname(exePath);+ const { releaseChannel }: { releaseChannel: string } = JSON.parse(+ fsSync.readFileSync(path.join(targetDir, "resources", "build_info.json"), "utf8")+ );++ const updaterModule = require(path.join(moonlightHost.asarPath, "app_bootstrap", "hostUpdater.js"));+ const updater = updaterModule.constructor;++ async function doUpdate(cb: (percent: number) => void) {+ logger.debug("Extracting to", targetDir);++ const exists = (path: string) =>+ fs+ .stat(path)+ .then(() => true)+ .catch(() => false);++ const url = `https://discord.com/api/download/${releaseChannel}?platform=linux&format=tar.gz`;+ const resp = await fetch(url, {+ cache: "no-store"+ });++ const reader = resp.body!.getReader();+ const contentLength = parseInt(resp.headers.get("Content-Length") ?? "0");+ logger.info(`Expecting ${contentLength} bytes for the update`);+ const bytes = new Uint8Array(contentLength);+ let pos = 0;+ let lastPercent = 0;++ while (true) {+ const { done, value } = await reader.read();+ if (done) {+ break;+ } else {+ bytes.set(value, pos);+ pos += value.length;++ const newPercent = Math.floor((pos / contentLength) * 100);+ if (lastPercent !== newPercent) {+ lastPercent = newPercent;+ cb(newPercent);+ }+ }+ }++ const files = await parseTarGzip(bytes);++ for (const file of files) {+ if (!file.data) continue;+ // @ts-expect-error What do you mean their own types are wrong+ if (file.type !== "file") continue;++ // Discord update files are inside of a main "Discord(PTB|Canary)" folder+ const filePath = file.name.replace(`${appName}/`, "");+ logger.info("Extracting", filePath);++ let targetFilePath = path.join(targetDir, filePath);+ if (filePath === "resources/app.asar") {+ // You tried+ targetFilePath = path.join(targetDir, "resources", "_app.asar");+ } else if (filePath === appName) {+ // Can't write over the executable? Just move it! 4head+ if (await exists(targetFilePath)) {+ await fs.rename(targetFilePath, targetFilePath + ".bak");+ await fs.unlink(targetFilePath + ".bak");+ }+ }+ const targetFileDir = path.dirname(targetFilePath);++ if (!(await exists(targetFileDir))) await fs.mkdir(targetFileDir, { recursive: true });+ await fs.writeFile(targetFilePath, file.data);++ const mode = file.attrs?.mode;+ if (mode != null) {+ // Not sure why this slice is needed+ await fs.chmod(targetFilePath, mode.slice(-3));+ }+ }++ logger.debug("Done updating");+ }++ const realEmit = updater.prototype.emit;+ updater.prototype.emit = function (event: string, ...args: any[]) {+ // Arrow functions don't bind `this` :D+ const call = (event: string, ...args: any[]) => realEmit.call(this, event, ...args);++ if (event === "update-manually") {+ const latestVerStr: string = args[0];+ logger.debug("update-manually called, intercepting", latestVerStr);+ call("update-available");++ (async () => {+ try {+ await doUpdate((progress) => {+ call("update-progress", progress);+ });+ // Copied from the win32 updater+ this.updateVersion = latestVerStr;+ call(+ "update-downloaded",+ {},+ releaseChannel,+ latestVerStr,+ new Date(),+ this.updateUrl,+ this.quitAndInstall.bind(this)+ );+ } catch (e) {+ logger.error("Error updating", e);+ }+ })();++ return this;+ } else {+ return realEmit.call(this, event, ...args);+ }+ };+}
@@ -4,7 +4,7 @@ "id": "nativeFixes","meta": {"name": "Native Fixes","tagline": "Various configurable fixes for Discord and Electron",- "authors": ["Cynosphere", "adryd"],+ "authors": ["Cynosphere", "adryd", "NotNite"],"tags": ["fixes"]},"environment": "desktop",@@ -43,6 +43,13 @@ "displayName": "Enable VAAPI features on Linux","description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems","type": "boolean","default": true+ },+ "linuxUpdater": {+ "advice": "restart",+ "displayName": "Linux Updater",+ "description": "Actually implements updating Discord on Linux. Has no effect on other operating systems",+ "type": "boolean",+ "default": false}},"apiLevel": 2