diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 4715bc5b..32df3c4d 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -323,6 +323,8 @@ "msg": { "401": "Steam doesn't seem to want to let us download Beat Saber 😢", "404": "Unable to contact Steam servers.", + "ExeNotFoundWindows": "\"DepotDownloader.exe\" is missing. Please check if the executable is quarantined by your anti-virus.", + "ExeNotFoundLinux": "\"DepotDownloader\" binary is missing.", "Password": "Password is invalid.", "InvalidCredentials": "Invalid login credentials, unapproved connection, or too many login attempts.", "NoManifest": "No manifest was found", diff --git a/src/main/models/depot-downloader.class.ts b/src/main/models/depot-downloader.class.ts index dcaa101a..5c15fd18 100644 --- a/src/main/models/depot-downloader.class.ts +++ b/src/main/models/depot-downloader.class.ts @@ -1,30 +1,48 @@ +import path from "path"; +import fs from "fs"; import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process"; import { Observable, ReplaySubject, Subscriber, filter, map, share } from "rxjs"; import { DepotDownloaderArgsOptions, DepotDownloaderErrorEvent, DepotDownloaderEvent, DepotDownloaderEventType, DepotDownloaderEventTypes, DepotDownloaderInfoEvent, DepotDownloaderWarningEvent } from "../../shared/models/bs-version-download/depot-downloader.model"; +import { UtilsService } from 'main/services/utils.service'; +import { CustomError } from "shared/models/exceptions/custom-error.class"; export class DepotDownloader { + private static readonly EXE_PATH = path.join( + UtilsService.getInstance().getAssetsScriptsPath(), + process.platform === "win32" ? "DepotDownloader.exe" : "DepotDownloader" + ); + private process: ChildProcessWithoutNullStreams; private processOut$: Observable; private subscriber: Subscriber; + public constructor( options: { - command: string, args?: string[], options?: SpawnOptionsWithoutStdio, echoStartData?: unknown - }, + args?: string[], options?: SpawnOptionsWithoutStdio, echoStartData?: unknown + }, logger?: Logger - ){ - this.processOut$ = new Observable(subscriber => { + ) { + if (!fs.existsSync(DepotDownloader.EXE_PATH)) { + throw new CustomError( + "DepotDownloader executable not found", + process.platform === "win32" + ? DepotDownloaderErrorEvent.ExeNotFoundWindows + : DepotDownloaderErrorEvent.ExeNotFoundLinux + ); + } + this.processOut$ = new Observable(subscriber => { this.subscriber = subscriber; - this.process = spawn(options.command, options.args ?? [], options.options); + this.process = spawn(DepotDownloader.EXE_PATH, options.args ?? [], options.options); subscriber.next(`[Info]|[Start]|${JSON.stringify(options.echoStartData) ?? ""}`); - + this.process.stdout.on("data", data => { const stringData: string = data.toString(); - + if(!stringData.includes(DepotDownloaderInfoEvent.Progress) && !stringData.includes(DepotDownloaderInfoEvent.Validated)){ logger?.info("DepotDownloader stdout:", stringData); } @@ -32,7 +50,7 @@ export class DepotDownloader { const lines: string[] = stringData.split("\n"); lines.forEach(line => subscriber.next(line)); }); - + this.process.on("error", error => subscriber.error(error)); this.process.stderr.on("error", error => subscriber.error(error)); this.process.on("exit", () => subscriber.complete()); @@ -74,7 +92,7 @@ export class DepotDownloader { data: splitedLine[2], } - }), + }), filter(Boolean)); } @@ -93,7 +111,7 @@ export class DepotDownloader { const args: string[] = []; for(const [key, value] of Object.entries(depotDownloaderArgs)){ - + if(value === true){ args.push(`-${key}`); } @@ -102,7 +120,7 @@ export class DepotDownloader { args.push(`${value}`); } } - + return args; } @@ -112,4 +130,4 @@ interface Logger { info: (...args: unknown[]) => void, warn: (...args: unknown[]) => void, error: (...args: unknown[]) => void, -} \ No newline at end of file +} diff --git a/src/main/services/bs-version-download/bs-steam-downloader.service.ts b/src/main/services/bs-version-download/bs-steam-downloader.service.ts index fccb1b7b..8484f839 100644 --- a/src/main/services/bs-version-download/bs-steam-downloader.service.ts +++ b/src/main/services/bs-version-download/bs-steam-downloader.service.ts @@ -1,7 +1,6 @@ import { BS_APP_ID, BS_DEPOT } from "../../constants"; import path from "path"; import { BSVersion } from "shared/bs-version.interface"; -import { UtilsService } from "../utils.service"; import log from "electron-log"; import { InstallationLocationService } from "../installation-location.service"; import { BSLocalVersionService } from "../bs-local-version.service"; @@ -17,14 +16,12 @@ import { CustomError } from "shared/models/exceptions/custom-error.class"; export class BsSteamDownloaderService { private static instance: BsSteamDownloaderService; - private readonly utils: UtilsService; private readonly installLocationService: InstallationLocationService; private readonly localVersionService: BSLocalVersionService; private depotDownloader: DepotDownloader; private constructor() { - this.utils = UtilsService.getInstance(); this.installLocationService = InstallationLocationService.getInstance(); this.localVersionService = BSLocalVersionService.getInstance(); @@ -40,10 +37,6 @@ export class BsSteamDownloaderService { return BsSteamDownloaderService.instance; } - private getDepotDownloaderExePath(): string { - return path.join(this.utils.getAssetsScriptsPath(), process.platform === 'linux' ? "DepotDownloader" : "DepotDownloader.exe"); - } - private async buildDepotDownloaderInstance(downloadInfos: DownloadSteamInfo, qr?: boolean): Promise<{depotDownloader: DepotDownloader, depotDownloaderOptions: DepotDownloaderArgsOptions, version: BSVersion}> { const versionPath = await this.localVersionService.getVersionPath(downloadInfos.bsVersion); @@ -68,11 +61,9 @@ export class BsSteamDownloaderService { await ensureDir(this.installLocationService.versionsDirectory()); - const exePath = this.getDepotDownloaderExePath(); const args = DepotDownloader.buildArgs(depotDownloaderOptions); const depotDownloader = new DepotDownloader({ - command: exePath, args, options: { cwd: this.installLocationService.versionsDirectory() }, echoStartData: downloadVersion @@ -104,11 +95,21 @@ export class BsSteamDownloaderService { finalize(() => this.localVersionService.initVersionMetadata(version, { store: BsStore.STEAM })) ).subscribe(sub); - }).catch(err => sub.error({ - type: DepotDownloaderEventType.Error, - subType: DepotDownloaderErrorEvent.Unknown, - data: err - } as DepotDownloaderEvent)); + }).catch(err => { + if (err instanceof CustomError + && Object.values(DepotDownloaderErrorEvent).includes( + err.code as DepotDownloaderErrorEvent + ) + ) { + return sub.error(err); + } + + return sub.error({ + type: DepotDownloaderEventType.Error, + subType: DepotDownloaderErrorEvent.Unknown, + data: err + } as DepotDownloaderEvent) + }); return () => { depotDownloaderBuildPromise.then(({ depotDownloader }) => depotDownloader.stop()); diff --git a/src/shared/models/bs-version-download/depot-downloader.model.ts b/src/shared/models/bs-version-download/depot-downloader.model.ts index ae701b79..0e46a80e 100644 --- a/src/shared/models/bs-version-download/depot-downloader.model.ts +++ b/src/shared/models/bs-version-download/depot-downloader.model.ts @@ -2,7 +2,6 @@ export enum DepotDownloaderEventType { Error = "Error", Warning = "Warning", Info = "Info", - } export interface DepotDownloaderEvent { @@ -27,6 +26,8 @@ export enum DepotDownloaderInfoEvent { } export enum DepotDownloaderErrorEvent { + ExeNotFoundWindows = "ExeNotFoundWindows", + ExeNotFoundLinux = "ExeNotFoundLinux", Password = "Password", InvalidCredentials = "InvalidCredentials", NoManifest = "NoManifest", @@ -66,4 +67,4 @@ export interface DepotDownloaderArgsOptions { dir: string, validate?: boolean, qr?: boolean, -} \ No newline at end of file +}