diff --git a/src/pages/index.ts b/src/pages/index.ts index 8389c22..84e73b5 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -5,6 +5,7 @@ import Window from '../ts/neutralino/Window'; import Downloader from '../ts/Downloader'; import Archive from '../ts/Archive'; import Configs from '../ts/Configs'; +import Runners from '../ts/Runners'; const app = Vue.createApp({ data: () => ({ @@ -18,6 +19,21 @@ const app = Vue.createApp({ mounted: () => { Window.current.show(); + /*Downloader.download('https://github.com/GloriousEggroll/wine-ge-custom/releases/download/6.20-GE-1/wine-lutris-ge-6.20-1-x86_64.tar.xz', '123.tar.xz').then((stream) => { + stream.start(() => console.log('Downloading started')); + stream.finish(() => console.log('Downloading finished')); + + stream.progress((current, total) => console.log(`${Math.round(current / total * 100)}%`)); + });*/ + + /*Runners.download('wine-lutris-ge-6.20-1-x86_64').then((stream) => { + stream.downloadStart(() => console.log('Downloading started')); + stream.downloadFinish(() => console.log('Downloading finished')); + + stream.unpackStart(() => console.log('Unpacking started')); + stream.unpackFinish(() => console.log('Unpacking finished')); + });*/ + /*Archive.unpack('Audio_English(US)_2.3.0.zip', 'tmp').then((stream) => { stream.progress((current, total) => { console.log(`${Math.round(current / total * 100)}%`); diff --git a/src/ts/Archive.ts b/src/ts/Archive.ts index da7bfd3..3790679 100644 --- a/src/ts/Archive.ts +++ b/src/ts/Archive.ts @@ -18,10 +18,12 @@ class Stream protected archive: ArchiveInfo; + protected onStart?: () => void; protected onProgress?: (current: number, total: number, difference: number) => void; protected onFinish?: () => void; protected onError?: () => void; + protected started: boolean = false; protected finished: boolean = false; protected throwedError: boolean = false; @@ -33,6 +35,10 @@ class Stream { this.path = path; this.unpackDir = unpackDir; + this.started = true; + + if (this.onStart) + this.onStart(); Archive.getInfo(path).then((info) => { if (info === null) @@ -102,6 +108,19 @@ class Stream }); } + /** + * Specify event that will be called when the unpacking will be started + * + * @param callback + */ + public start(callback: () => void) + { + this.onStart = callback; + + if (this.started) + callback(); + } + /** * Specify event that will be called every [this.progressInterval] ms during archive unpacking * diff --git a/src/ts/Downloader.ts b/src/ts/Downloader.ts index 440df77..10f36de 100644 --- a/src/ts/Downloader.ts +++ b/src/ts/Downloader.ts @@ -9,15 +9,21 @@ class Stream protected total: number; protected previous: number = 0; + protected onStart?: () => void; protected onProgress?: (current: number, total: number, difference: number) => void; protected onFinish?: () => void; + protected started: boolean = false; protected finished: boolean = false; public constructor(uri: string, output: string, total: number) { this.uri = uri; this.total = total; + this.started = true; + + if (this.onStart) + this.onStart(); // @ts-expect-error Neutralino.os.execCommand(`curl -s -L -N -o "${output}" "${uri}"`, { @@ -51,6 +57,19 @@ class Stream setTimeout(updateProgress, this.progressInterval); } + /** + * Specify event that will be called when the downloading will be started + * + * @param callback + */ + public start(callback: () => void) + { + this.onStart = callback; + + if (this.started) + callback(); + } + /** * Specify event that will be called every [this.progressInterval] ms during the file downloading * @@ -96,13 +115,16 @@ export default class Downloader else statsRaw = statsRaw.stdOut; - const length = parseInt(/content-length: ([\d]+)/i.exec(statsRaw)[1]); + let length = 0; + + for (const match of statsRaw.matchAll(/content-length: ([\d]+)/gi)) + length = match[1]; resolve(new Stream(uri, output ?? this.fileFromUri(uri), length)); }); } - protected static fileFromUri(uri: string): string + public static fileFromUri(uri: string): string { const file = uri.split('/').pop().split('#')[0].split('?')[0]; diff --git a/src/ts/Runners.ts b/src/ts/Runners.ts index 7cd94ec..88b0b22 100644 --- a/src/ts/Runners.ts +++ b/src/ts/Runners.ts @@ -1,9 +1,168 @@ -import type { +import { Runner, RunnerFamily } from './types/Runners'; -import constants from './Constants'; +import Constants from './Constants'; +import Downloader from './Downloader'; +import Archive from './Archive'; + +class Stream +{ + /** + * The interval in ms between progress event calls + */ + public downloadProgressInterval: number = 200; + + /** + * The interval in ms between progress event calls + */ + public unpackProgressInterval: number = 500; + + protected onDownloadStart?: () => void; + protected onUnpackStart?: () => void; + + protected onDownloadProgress?: (current: number, total: number, difference: number) => void; + protected onUnpackProgress?: (current: number, total: number, difference: number) => void; + + protected onDownloadFinish?: () => void; + protected onUnpackFinish?: () => void; + + protected downloadStarted: boolean = false; + protected unpackStarted: boolean = false; + + protected downloadFinished: boolean = false; + protected unpackFinished: boolean = false; + + public constructor(runner: Runner) + { + Constants.paths.launcher.then((launcherDir) => { + const archivePath = `${launcherDir}/${Downloader.fileFromUri(runner.uri)}`; + + // Download archive + Downloader.download(runner.uri, archivePath).then((stream) => { + stream.progressInterval = this.downloadProgressInterval; + + stream.start(() => { + this.downloadStarted = true; + + if (this.onDownloadStart) + this.onDownloadStart(); + }); + + stream.progress((current, total, difference) => { + if (this.onDownloadProgress) + this.onDownloadProgress(current, total, difference); + }); + + stream.finish(() => { + this.downloadFinished = true; + + if (this.onDownloadFinish) + this.onDownloadFinish(); + + // And then unpack it + Constants.paths.runners.then((runners) => { + Archive.unpack(archivePath, runners).then((stream) => { + stream.progressInterval = this.unpackProgressInterval; + + stream.start(() => { + this.unpackStarted = true; + + if (this.onUnpackStart) + this.onUnpackStart(); + }); + + stream.progress((current, total, difference) => { + if (this.onUnpackProgress) + this.onUnpackProgress(current, total, difference); + }); + + stream.finish(() => { + this.unpackFinished = true; + + if (this.onUnpackFinish) + this.onUnpackFinish(); + }); + }); + }); + }); + }); + }); + } + + /** + * Specify event that will be called after the runner will begin downloading + * + * @param callback + */ + public downloadStart(callback: () => void) + { + this.onDownloadStart = callback; + + if (this.downloadStarted) + callback(); + } + + /** + * Specify event that will be called after the runner will begin unpacking + * + * @param callback + */ + public unpackStart(callback: () => void) + { + this.onUnpackStart = callback; + + if (this.unpackStarted) + callback(); + } + + /** + * Specify event that will be called every [this.downloadProgressInterval] ms during the runner downloading + * + * @param callback + */ + public downloadProgress(callback: (current: number, total: number, difference: number) => void) + { + this.onDownloadProgress = callback; + } + + /** + * Specify event that will be called every [this.unpackProgressInterval] ms during the runner unpacking + * + * @param callback + */ + public unpackProgress(callback: (current: number, total: number, difference: number) => void) + { + this.onUnpackProgress = callback; + } + + /** + * Specify event that will be called after the runner will be downloaded + * + * @param callback + */ + public downloadFinish(callback: () => void) + { + this.onDownloadFinish = callback; + + if (this.downloadFinished) + callback(); + } + + /** + * Specify event that will be called after the runner will be unpacked + * + * @param callback + */ + public unpackFinish(callback: () => void) + { + this.onUnpackFinish = callback; + + if (this.unpackFinished) + callback(); + } +} class Runners { @@ -12,17 +171,17 @@ class Runners * * @returns Promise<Runner[]> */ - public static get(): Promise<Runner[]> + public static get(): Promise<RunnerFamily[]> { return new Promise((resolve) => { - constants.paths.runners.then(async (runnersDir: string) => { + Constants.paths.runners.then(async (runnersDir: string) => { // @ts-expect-error - let list: RunnerFamily[] = JSON.parse(await Neutralino.filesystem.readFile(`${constants.dirs.app}/public/runners.json`)); + let list: RunnerFamily[] = JSON.parse(await Neutralino.filesystem.readFile(`${Constants.paths.app}/public/runners.json`)); // @ts-expect-error const installed: { entry: string, type: string }[] = await Neutralino.filesystem.readDirectory(runnersDir); - let runners = []; + let runners: RunnerFamily[] = []; list.forEach((family) => { let newFamily: RunnerFamily = { @@ -51,16 +210,35 @@ class Runners }); } - public static download(runner: Runner|Runner['name']): Promise<boolean> + public static download(runner: Runner|Runner['name']): Promise<null|Stream> { - return new Promise((resolve) => { - + return new Promise(async (resolve) => { + // If we provided runner property as a name of the runner + // then we should find this runner and call this method from it + if (typeof runner == 'string') + { + let foundRunner = null; + + (await this.get()).forEach((family) => { + family.runners.forEach((familyRunner) => { + if (familyRunner.name == runner) + foundRunner = familyRunner; + }); + }); + + resolve(foundRunner === null ? null : new Stream(foundRunner)); + } + + // Otherwise we can use runner.uri and so on to download runner + else resolve(new Stream(runner)); }); } } export default Runners; +export { Stream }; + export type { Runner, RunnerFamily