API improvements

- made `Downloader.pause()` and `resume()` methods to pause/resume downloading
- made `AbstractInstaller.pauseDownload()` and `resumeDownload()` methods
- (probably) fixed some special cases of `Domain.getInfo()` method work
- added translations for progress bar
- added pause/resume functionality for game or voice downloading
This commit is contained in:
Observer KRypt0n_ 2022-01-11 21:49:09 +02:00
parent fcd90315e6
commit 8497c9d13b
No known key found for this signature in database
GPG key ID: DC5D4EC1303465DA
14 changed files with 296 additions and 49 deletions

View file

@ -14,6 +14,24 @@ splash:
# Launcher window
launcher:
# Progress bar
progress:
pause: Pausieren
resume: Fortsetzen
# Game installation
game:
downloading: Spiel ist am herunterladen...
unpacking: Spiel wird entpackt...
deleting_outdated: Lösche veraltete dateien...
# Voice packages installation
voice:
deleting: Lösche Sprachpakete...
downloading: Sprachpaket {voice} wird heruntergeladen...
unpacking: Sprachpaket {voice} wird entpackt..
# Launcher states
states:
# When the game should be installed or updated
installation:

View file

@ -14,6 +14,24 @@ splash:
# Launcher window
launcher:
# Progress bar
progress:
pause: Pause
resume: Resume
# Game installation
game:
downloading: Downloading game...
unpacking: Unpacking game...
deleting_outdated: Deleting outdated files...
# Voice packages installation
voice:
deleting: Deleting voice packages...
downloading: Downloading {voice} voice package...
unpacking: Unpacking {voice} voice package...
# Launcher states
states:
# When the game should be installed or updated
installation:

View file

@ -14,6 +14,24 @@ splash:
# Launcher window
launcher:
# Progress bar
progress:
pause: Pause
resume: Continuer
# Game installation
game:
downloading: Téléchargement du jeu...
unpacking: Décompression du jeu...
deleting_outdated: Suppression des fichiers non à jour...
# Voice packages installation
voice:
deleting: Suppression des pack de voix...
downloading: 'Téléchargement du pack de voix : {voice}...'
unpacking: 'Décompression du pack de voix : {voice}...'
# Launcher states
states:
# When the game should be installed or updated
installation:

View file

@ -14,6 +14,24 @@ splash:
# Launcher window
launcher:
# Progress bar
progress:
pause: Pause
resume: Resume
# Game installation
game:
downloading: Downloading game...
unpacking: Unpacking game...
deleting_outdated: Deleting outdated files...
# Voice packages installation
voice:
deleting: Deleting voice packages...
downloading: Downloading {voice} voice package...
unpacking: Unpacking {voice} voice package...
# Launcher states
states:
# When the game should be installed or updated
installation:

View file

@ -14,6 +14,24 @@ splash:
# Окно лаунчера
launcher:
# Прогресс бар
progress:
pause: Пауза
resume: Продолжить
# Установка игры
game:
downloading: Скачивание игры...
unpacking: Распаковка игры...
deleting_outdated: Удаление устаревших файлов...
# Установка звуковых пакетов
voice:
deleting: Удаление звуковых пакетов...
downloading: Скачивание звукового пакета {voice}...
unpacking: Распаковка звукового пакета {voice}...
# Launcher states
states:
# Когда игра должна быть установлена или обновлена
installation:

View file

@ -14,6 +14,24 @@ splash:
# Launcher window
launcher:
# Progress bar
progress:
pause: pause
resume: wesume
# Game installation
game:
downloading: downwoading game... -.-
unpacking: unpacking g-game (o^▽^o)
deleting_outdated: deweting outdated fiwes...
# Voice packages installation
voice:
deleting: deweting voice packages...
downloading: downwoading {voice} voice package...
unpacking: u-unpacking {voice} voice package...
# Launcher states
states:
# When the game should be installed or updated
installation:

View file

@ -157,4 +157,5 @@
</button>
<button class="button hint--top hint--large" aria-label="" id="launch">Launch</button>
<button class="button" id="pause">Pause</button>
</main>

View file

@ -40,7 +40,7 @@ img.background
.button-blue:hover:not([disabled])
background-color: #7284b6
#launch
#launch, #pause
position: absolute
width: 238px
@ -51,6 +51,9 @@ img.background
font-size: 22px
#pause
display: none
#predownload
position: absolute
display: none

View file

@ -1,5 +1,5 @@
import constants from '../Constants';
import Downloader from './Downloader';
import Downloader, { Stream as DownloadStream } from './Downloader';
import Archive from './Archive';
import { DebugThread } from './Debug';
@ -12,6 +12,11 @@ export default abstract class Installer
*/
public downloadProgressInterval: number = 200;
/**
* The interval in ms between checking was downloading resumed after pausing
*/
public downloadPauseInterval: number = 500;
/**
* The interval in ms between progress event calls
*/
@ -32,6 +37,8 @@ export default abstract class Installer
protected downloadFinished: boolean = false;
protected unpackFinished: boolean = false;
protected downloadStream?: DownloadStream;
/**
* @param uri URI to the archive we need to download
* @param unpackDir path to unpack this archive to
@ -94,7 +101,10 @@ export default abstract class Installer
if (!alreadyDownloaded)
{
Downloader.download(uri, archivePath).then((stream) => {
this.downloadStream = stream;
stream.progressInterval = this.downloadProgressInterval;
stream.pauseInterval = this.downloadPauseInterval;
stream.start(() => {
this.downloadStarted = true;
@ -194,4 +204,20 @@ export default abstract class Installer
if (this.unpackFinished)
callback();
}
/**
* Pause downloading
*/
public pauseDownload()
{
this.downloadStream?.pause();
}
/**
* Resume downloading
*/
public resumeDownload()
{
this.downloadStream?.resume();
}
};

View file

@ -17,22 +17,20 @@ export default class Domain
process.runningInterval = 500;
process.outputInterval = 500;
const resolveInfo = (info: DomainInfo) => {
process.outputInterval = null;
process.runningInterval = null;
process.kill();
debugThread.log({ message: info });
resolve(info);
};
let output = '';
process.output((outputPart) => {
output += outputPart;
const resolveInfo = (info: DomainInfo) => {
process.outputInterval = null;
process.runningInterval = null;
process.kill();
debugThread.log({ message: info });
resolve(info);
};
const processOutput = () => {
if (output.includes('Name or service not known'))
{
resolveInfo({
@ -55,7 +53,15 @@ export default class Domain
});
}
}
};
process.output((outputPart) => {
output += outputPart;
processOutput();
});
process.finish(() => processOutput());
});
}
};

View file

@ -21,7 +21,13 @@ class Stream
*/
public progressInterval: number = 200;
/**
* The interval in ms between checking was downloading resumed after pausing
*/
public pauseInterval: number = 500;
protected uri: string;
protected output: string;
protected total: number;
protected previous: number = 0;
@ -30,15 +36,19 @@ class Stream
protected onFinish?: () => void;
protected started: boolean = false;
protected paused: boolean = true; // true because we call .resume() method at start
protected finished: boolean = false;
protected debugThread: DebugThread;
public constructor(uri: string, output: string, total: number)
{
this.uri = uri;
this.output = output;
this.total = total;
this.started = true;
const debugThread = new DebugThread('Downloader/Stream', {
this.debugThread = new DebugThread('Downloader/Stream', {
message: {
'uri': uri,
'output file': output,
@ -49,39 +59,36 @@ class Stream
if (this.onStart)
this.onStart();
const command = `curl -s -L -N -o "${Process.addSlashes(output)}" "${uri}"`;
Neutralino.os.execCommand(command, {
background: true
}).then((result) => {
this._id = result.pid;
});
debugThread.log(`Downloading started with command: ${command}`);
this.resume();
const updateProgress = () => {
Neutralino.filesystem.getStats(output).then((stats) => {
if (this.onProgress)
this.onProgress(stats.size, this.total, stats.size - this.previous);
if (!this.paused)
{
Neutralino.filesystem.getStats(output).then((stats) => {
if (this.onProgress)
this.onProgress(stats.size, this.total, stats.size - this.previous);
this.previous = stats.size;
this.previous = stats.size;
if (stats.size >= this.total)
{
this.finished = true;
if (stats.size >= this.total)
{
this.finished = true;
debugThread.log('Downloading finished');
this.debugThread.log('Downloading finished');
if (this.onFinish)
this.onFinish();
}
if (this.onFinish)
this.onFinish();
}
if (!this.finished)
setTimeout(updateProgress, this.progressInterval);
}).catch(() => {
if (!this.finished)
setTimeout(updateProgress, this.progressInterval);
});
if (!this.finished)
setTimeout(updateProgress, this.progressInterval);
}).catch(() => {
if (!this.finished)
setTimeout(updateProgress, this.progressInterval);
});
}
else setTimeout(updateProgress, this.pauseInterval);
};
setTimeout(updateProgress, this.progressInterval);
@ -123,6 +130,42 @@ class Stream
callback();
}
/**
* Pause downloading
*/
public pause()
{
if (!this.paused)
{
this.debugThread.log('Downloading paused');
this.close(true);
this.paused = true;
}
}
/**
* Resume downloading
*/
public resume()
{
if (this.paused)
{
const command = `curl -s -L -N -C - -o "${Process.addSlashes(this.output)}" "${this.uri}"`;
this.debugThread.log(`Downloading started with command: ${command}`);
Neutralino.os.execCommand(command, {
background: true
}).then((result) => {
this._id = result.pid;
});
this.paused = false;
}
}
/**
* Close downloading stream
*/

View file

@ -25,6 +25,7 @@ export default class State
public launcher: Launcher;
public launchButton: HTMLElement;
public pauseButton: HTMLElement;
public predownloadButton: HTMLElement;
public settingsButton: HTMLElement;
@ -51,6 +52,7 @@ export default class State
this.launcher = launcher;
this.launchButton = <HTMLElement>document.getElementById('launch');
this.pauseButton = <HTMLElement>document.getElementById('pause');
this.predownloadButton = <HTMLElement>document.getElementById('predownload');
this.settingsButton = <HTMLElement>document.getElementById('settings');

View file

@ -1,3 +1,6 @@
import { get as svelteget } from 'svelte/store';
import { _ } from 'svelte-i18n';
import type Launcher from '../../Launcher';
import Game from '../../Game';
@ -27,12 +30,35 @@ export default (launcher: Launcher): Promise<void> => {
Game.update(prevGameVersion).then((stream) => {
launcher.progressBar?.init({
label: 'Downloading game...',
label: svelteget(_)('launcher.progress.game.downloading'),
showSpeed: true,
showEta: true,
showPercents: true,
showTotals: true
});
// Show pause/resume button
launcher.state!.pauseButton.style['display'] = 'block';
let paused = false;
launcher.state!.pauseButton.onclick = () => {
if (!paused)
{
stream?.pauseDownload();
launcher.state!.pauseButton.textContent = svelteget(_)('launcher.progress.resume');
}
else
{
stream?.resumeDownload();
launcher.state!.pauseButton.textContent = svelteget(_)('launcher.progress.pause');
}
paused = !paused;
};
stream?.downloadStart(() => launcher.progressBar?.show());
@ -42,7 +68,7 @@ export default (launcher: Launcher): Promise<void> => {
stream?.unpackStart(() => {
launcher.progressBar?.init({
label: 'Unpacking game...',
label: svelteget(_)('launcher.progress.game.unpacking'),
showSpeed: true,
showEta: true,
showPercents: true,
@ -70,7 +96,7 @@ export default (launcher: Launcher): Promise<void> => {
if (files.length > 0)
{
launcher.progressBar?.init({
label: 'Deleting outdated files...',
label: svelteget(_)('launcher.progress.game.deleting_outdated'),
showSpeed: false,
showEta: true,
showPercents: true,
@ -103,6 +129,9 @@ export default (launcher: Launcher): Promise<void> => {
// Download voice package when the game itself has been installed
const installVoice = () => {
// Hide pause/resume button
launcher.state!.pauseButton.style['display'] = 'none';
import('./InstallVoice').then((module) => {
module.default(launcher).then(() => resolve());
});

View file

@ -1,3 +1,6 @@
import { get as svelteget } from 'svelte/store';
import { _ } from 'svelte-i18n';
import type Launcher from '../../Launcher';
import type { VoiceLang } from '../../types/Voice';
@ -25,7 +28,7 @@ export default (launcher: Launcher): Promise<void> => {
if (packagesToDelete.length > 0)
{
launcher.progressBar?.init({
label: `Deleting voice packages...`,
label: svelteget(_)('launcher.progress.voice.deleting'),
showSpeed: false,
showEta: false,
showPercents: true,
@ -50,12 +53,35 @@ export default (launcher: Launcher): Promise<void> => {
else Voice.update(selectedVoice, packagesVersions[selectedVoice] ?? null).then((stream) => {
launcher.progressBar?.init({
label: `Downloading ${selectedVoice} voice package...`,
label: svelteget(_)('launcher.progress.voice.downloading', { values: { voice: selectedVoice } }),
showSpeed: true,
showEta: true,
showPercents: true,
showTotals: true
});
// Show pause/resume button
launcher.state!.pauseButton.style['display'] = 'block';
let paused = false;
launcher.state!.pauseButton.onclick = () => {
if (!paused)
{
stream?.pauseDownload();
launcher.state!.pauseButton.textContent = svelteget(_)('launcher.progress.resume');
}
else
{
stream?.resumeDownload();
launcher.state!.pauseButton.textContent = svelteget(_)('launcher.progress.pause');
}
paused = !paused;
};
stream?.downloadStart(() => launcher.progressBar?.show());
@ -65,7 +91,7 @@ export default (launcher: Launcher): Promise<void> => {
stream?.unpackStart(() => {
launcher.progressBar?.init({
label: `Unpacking ${selectedVoice} voice package...`,
label: svelteget(_)('launcher.progress.voice.unpacking', { values: { voice: selectedVoice } }),
showSpeed: true,
showEta: true,
showPercents: true,
@ -84,6 +110,9 @@ export default (launcher: Launcher): Promise<void> => {
stream?.unpackFinish(() => {
launcher.progressBar?.hide();
// Hide pause/resume button
launcher.state!.pauseButton.style['display'] = 'none';
resolve();
});