mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2024-12-18 07:51:47 +03:00
API improvements
- made `promisify()` function to run some code asynchronously - added some default configs setting at start - now settings are stored in yaml format - fixed `Configs.defaults()` null values overwriting - added launcher state system - added `Runners.current` field to get the current selected runner according to the config file - `Configs.get()` method was renamed to `Configs.list()` - added `Configs.get()` method to get a `Runner` object by the specified runner name - added `Process` class to have a better experience of working with processes - updated readme
This commit is contained in:
parent
6e38611900
commit
d1474f643a
13 changed files with 402 additions and 35 deletions
|
@ -142,6 +142,11 @@ This is our current roadmap goals. You can find older ones [here](ROADMAP.md)
|
||||||
* Make `Launcher` class to manage launcher-related features
|
* Make `Launcher` class to manage launcher-related features
|
||||||
* <s>Downloading progress</s>
|
* <s>Downloading progress</s>
|
||||||
* Launcher state functionality
|
* Launcher state functionality
|
||||||
|
* <s>Game launch available</s>
|
||||||
|
* Game update (installation) required
|
||||||
|
* Voice data update (installation) required
|
||||||
|
* Patch unavailable
|
||||||
|
* Test patch available
|
||||||
* Make Vue components
|
* Make Vue components
|
||||||
* Checkbox
|
* Checkbox
|
||||||
* Selection
|
* Selection
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="launcher-content">
|
<div id="launcher-content">
|
||||||
<iframe id="launcher-content-frame" :src="uri.social" scrolling="no" style="position: absolute; border: 0; top: 0; left: 0;" width="100%" height="100%"></iframe>
|
<iframe :src="uri.social" scrolling="no" style="position: absolute; border: 0; top: 0; left: 0;" width="100%" height="100%"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="settings">
|
<div id="settings">
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-md5": "^0.7.3",
|
"js-md5": "^0.7.3",
|
||||||
"vue": "^3.2.25"
|
"vue": "^3.2.25",
|
||||||
|
"yaml": "^1.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutralinojs/neu": "^8.0.0",
|
"@neutralinojs/neu": "^8.0.0",
|
||||||
|
|
|
@ -5,16 +5,39 @@ import Window from '../ts/neutralino/Window';
|
||||||
import Launcher from '../ts/Launcher';
|
import Launcher from '../ts/Launcher';
|
||||||
import Configs from '../ts/Configs';
|
import Configs from '../ts/Configs';
|
||||||
import constants from '../ts/Constants';
|
import constants from '../ts/Constants';
|
||||||
|
import promisify from '../ts/core/promisify';
|
||||||
|
import Process from '../ts/neutralino/Process';
|
||||||
|
|
||||||
(async () => {
|
promisify(async () => {
|
||||||
Configs.defaults({
|
Configs.defaults({
|
||||||
lang: {
|
lang: {
|
||||||
launcher: 'en-us',
|
launcher: 'en-us',
|
||||||
voice: 'en-us'
|
voice: 'en-us'
|
||||||
},
|
},
|
||||||
prefix: await constants.paths.prefix.default
|
|
||||||
|
// Path to wine prefix
|
||||||
|
prefix: await constants.paths.prefix.default,
|
||||||
|
|
||||||
|
// runner name to use, or null if runner is not specified
|
||||||
|
runner: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HUD
|
||||||
|
*
|
||||||
|
* null if don't use
|
||||||
|
* otherwise should be "dxvk" or "mangohud"
|
||||||
|
*/
|
||||||
|
hud: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vkBasalt preset to use
|
||||||
|
*
|
||||||
|
* null if don't use
|
||||||
|
* otherwise should be some folder name from the "shaders" folder
|
||||||
|
*/
|
||||||
|
shaders: null
|
||||||
});
|
});
|
||||||
})();
|
});
|
||||||
|
|
||||||
let app = createApp({
|
let app = createApp({
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import YAML from 'yaml';
|
||||||
|
|
||||||
import constants from './Constants';
|
import constants from './Constants';
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
@ -19,7 +21,7 @@ export default class Configs
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
Neutralino.filesystem.readFile(await constants.paths.config).then((config) => {
|
Neutralino.filesystem.readFile(await constants.paths.config).then((config) => {
|
||||||
config = JSON.parse(config);
|
config = YAML.parse(config);
|
||||||
|
|
||||||
if (name !== '')
|
if (name !== '')
|
||||||
{
|
{
|
||||||
|
@ -54,12 +56,12 @@ export default class Configs
|
||||||
value = await Promise.resolve(value);
|
value = await Promise.resolve(value);
|
||||||
|
|
||||||
Neutralino.filesystem.readFile(await constants.paths.config).then(async (config) => {
|
Neutralino.filesystem.readFile(await constants.paths.config).then(async (config) => {
|
||||||
config = JSON.stringify(getUpdatedArray(name.split('.'), JSON.parse(config), value), null, 4);
|
config = YAML.stringify(getUpdatedArray(name.split('.'), YAML.parse(config), value));
|
||||||
|
|
||||||
Neutralino.filesystem.writeFile(await constants.paths.config, config)
|
Neutralino.filesystem.writeFile(await constants.paths.config, config)
|
||||||
.then(() => resolve());
|
.then(() => resolve());
|
||||||
}).catch(async () => {
|
}).catch(async () => {
|
||||||
let config = JSON.stringify(getUpdatedArray(name.split('.'), {}, value), null, 4);
|
let config = YAML.stringify(getUpdatedArray(name.split('.'), {}, value));
|
||||||
|
|
||||||
Neutralino.filesystem.writeFile(await constants.paths.config, config)
|
Neutralino.filesystem.writeFile(await constants.paths.config, config)
|
||||||
.then(() => resolve());
|
.then(() => resolve());
|
||||||
|
@ -74,32 +76,32 @@ export default class Configs
|
||||||
*
|
*
|
||||||
* @returns Promise<void> indicates if the default settings were applied
|
* @returns Promise<void> indicates if the default settings were applied
|
||||||
*/
|
*/
|
||||||
public static defaults(configs: scalar): Promise<void>
|
public static defaults(configs: object): Promise<void>
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const setDefaults = async (current: scalar) => {
|
const setDefaults = async (current: object) => {
|
||||||
const updateDefaults = (current: scalar, defaults: scalar) => {
|
const updateDefaults = (current: object, defaults: object) => {
|
||||||
Object.keys(defaults!).forEach((key) => {
|
Object.keys(defaults).forEach((key) => {
|
||||||
// If the field exists in defaults and doesn't exist in current
|
// If the field exists in defaults and doesn't exist in current
|
||||||
if (current![key] === undefined)
|
if (current[key] === undefined)
|
||||||
current![key] = defaults![key];
|
current[key] = defaults[key];
|
||||||
|
|
||||||
// If both of default and current are objects
|
// If both of default and current are objects
|
||||||
else if (typeof current![key] == 'object' && typeof defaults![key] == 'object')
|
// and we also should check if they're not nulls
|
||||||
current![key] = updateDefaults(current![key], defaults![key]);
|
// because JS thinks that [typeof null === 'object']
|
||||||
|
else if (typeof current[key] == 'object' && typeof defaults[key] == 'object' && current[key] !== null && defaults[key] !== null)
|
||||||
|
current[key] = updateDefaults(current[key], defaults![key]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return current;
|
return current;
|
||||||
};
|
};
|
||||||
|
|
||||||
current = JSON.stringify(updateDefaults(current, configs), null, 4);
|
Neutralino.filesystem.writeFile(await constants.paths.config, YAML.stringify(updateDefaults(current, configs)))
|
||||||
|
|
||||||
Neutralino.filesystem.writeFile(await constants.paths.config, current)
|
|
||||||
.then(() => resolve());
|
.then(() => resolve());
|
||||||
};
|
};
|
||||||
|
|
||||||
Neutralino.filesystem.readFile(await constants.paths.config)
|
Neutralino.filesystem.readFile(await constants.paths.config)
|
||||||
.then((config) => setDefaults(JSON.parse(config)))
|
.then((config) => setDefaults(YAML.parse(config)))
|
||||||
.catch(() => setDefaults({}));
|
.catch(() => setDefaults({}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,11 +83,11 @@ class Paths
|
||||||
/**
|
/**
|
||||||
* Config file
|
* Config file
|
||||||
*
|
*
|
||||||
* @default "~/.local/share/anime-game-launcher/config.json"
|
* @default "~/.local/share/anime-game-launcher/config.yaml"
|
||||||
*/
|
*/
|
||||||
public static get config(): Promise<string>
|
public static get config(): Promise<string>
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve) => resolve(`${await this.launcherDir}/config.json`));
|
return new Promise(async (resolve) => resolve(`${await this.launcherDir}/config.yaml`));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
import constants from './Constants';
|
import constants from './Constants';
|
||||||
import Configs from './Configs';
|
import Configs from './Configs';
|
||||||
|
|
||||||
import Background from './launcher/Background';
|
import Background from './launcher/Background';
|
||||||
import ProgressBar from './launcher/ProgressBar';
|
import ProgressBar from './launcher/ProgressBar';
|
||||||
|
import State from './launcher/State';
|
||||||
declare const Neutralino;
|
|
||||||
|
|
||||||
export default class Launcher
|
export default class Launcher
|
||||||
{
|
{
|
||||||
public app;
|
public app;
|
||||||
|
|
||||||
|
public state: State;
|
||||||
public progressBar: ProgressBar;
|
public progressBar: ProgressBar;
|
||||||
|
|
||||||
public constructor(app)
|
public constructor(app)
|
||||||
{
|
{
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
|
||||||
|
this.state = new State(this);
|
||||||
this.progressBar = new ProgressBar(this);
|
this.progressBar = new ProgressBar(this);
|
||||||
|
|
||||||
// Progress bar test
|
// Progress bar test
|
||||||
|
@ -40,6 +44,9 @@ export default class Launcher
|
||||||
t(0);
|
t(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update launcher background picture
|
||||||
|
*/
|
||||||
public updateBackground(): Promise<void>
|
public updateBackground(): Promise<void>
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
} from '../types/Runners';
|
} from '../types/Runners';
|
||||||
|
|
||||||
import constants from '../Constants';
|
import constants from '../Constants';
|
||||||
|
import Configs from '../Configs';
|
||||||
import AbstractInstaller from './AbstractInstaller';
|
import AbstractInstaller from './AbstractInstaller';
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
@ -18,10 +19,25 @@ class Stream extends AbstractInstaller
|
||||||
|
|
||||||
class Runners
|
class Runners
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Get the current using runner according to the config file
|
||||||
|
*/
|
||||||
|
public static get current(): Promise<Runner|null>
|
||||||
|
{
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
Configs.get('runner').then((runner) => {
|
||||||
|
if (typeof runner === 'string')
|
||||||
|
Runners.get(runner).then((runner) => resolve(runner));
|
||||||
|
|
||||||
|
else resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get runners list
|
* Get runners list
|
||||||
*/
|
*/
|
||||||
public static get(): Promise<RunnerFamily[]>
|
public static list(): Promise<RunnerFamily[]>
|
||||||
{
|
{
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
constants.paths.runnersDir.then(async (runnersDir: string) => {
|
constants.paths.runnersDir.then(async (runnersDir: string) => {
|
||||||
|
@ -58,6 +74,29 @@ class Runners
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the runner with a specified name
|
||||||
|
*
|
||||||
|
* @returns null if the runner with this name is not found
|
||||||
|
*/
|
||||||
|
public static get(name: string): Promise<Runner|null>
|
||||||
|
{
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.list().then((list) => {
|
||||||
|
for (const family of list)
|
||||||
|
for (const runner of family.runners)
|
||||||
|
if (runner.name == name)
|
||||||
|
{
|
||||||
|
resolve(runner);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download runner to the [constants.paths.runners] directory
|
* Download runner to the [constants.paths.runners] directory
|
||||||
*
|
*
|
||||||
|
@ -66,21 +105,14 @@ class Runners
|
||||||
*/
|
*/
|
||||||
public static download(runner: Runner|Runner['name']): Promise<null|Stream>
|
public static download(runner: Runner|Runner['name']): Promise<null|Stream>
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve) => {
|
return new Promise((resolve) => {
|
||||||
// If we provided runner parameter with a name of a runner
|
// If we provided runner parameter with a name of a runner
|
||||||
// then we should find this runner and call this method for it
|
// then we should find this runner and call this method for it
|
||||||
if (typeof runner == 'string')
|
if (typeof runner == 'string')
|
||||||
{
|
{
|
||||||
let foundRunner;
|
this.get(runner).then((foundRunner) => {
|
||||||
|
resolve(foundRunner === null ? null : new Stream(foundRunner));
|
||||||
(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
|
// Otherwise we can use runner.uri and so on to download runner
|
||||||
|
|
9
src/ts/core/promisify.ts
Normal file
9
src/ts/core/promisify.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Make a promise from a synchronous function and run it
|
||||||
|
*/
|
||||||
|
export default function promisify(callback: () => any): Promise<any>
|
||||||
|
{
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolve(callback());
|
||||||
|
});
|
||||||
|
};
|
54
src/ts/launcher/State.ts
Normal file
54
src/ts/launcher/State.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import Launcher from '../Launcher';
|
||||||
|
|
||||||
|
import type { LauncherState } from '../types/Launcher';
|
||||||
|
|
||||||
|
export default class State
|
||||||
|
{
|
||||||
|
public launcher: Launcher;
|
||||||
|
|
||||||
|
public launchButton: HTMLElement;
|
||||||
|
|
||||||
|
protected _state: LauncherState = 'game-launch-available';
|
||||||
|
|
||||||
|
protected events = {
|
||||||
|
'game-launch-available': import('./states/Launch')
|
||||||
|
};
|
||||||
|
|
||||||
|
public constructor(launcher: Launcher)
|
||||||
|
{
|
||||||
|
this.launcher = launcher;
|
||||||
|
|
||||||
|
this.launchButton = <HTMLElement>document.getElementById('launch');
|
||||||
|
|
||||||
|
this.launchButton.onclick = () => {
|
||||||
|
if (this.events[this._state])
|
||||||
|
this.events[this._state].then((event) => event.default());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current launcher state
|
||||||
|
*/
|
||||||
|
public get(): LauncherState
|
||||||
|
{
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set launcher state
|
||||||
|
*/
|
||||||
|
public set(state: LauncherState): void
|
||||||
|
{
|
||||||
|
this._state = state;
|
||||||
|
|
||||||
|
switch(state)
|
||||||
|
{
|
||||||
|
case 'game-launch-available':
|
||||||
|
this.launcher.progressBar.hide();
|
||||||
|
|
||||||
|
this.launchButton.textContent = 'Launch';
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
123
src/ts/launcher/states/Launch.ts
Normal file
123
src/ts/launcher/states/Launch.ts
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import Configs from '../../Configs';
|
||||||
|
import constants from '../../Constants';
|
||||||
|
import Runners from '../../core/Runners';
|
||||||
|
import Launcher from '../../Launcher';
|
||||||
|
import Process from '../../neutralino/Process';
|
||||||
|
|
||||||
|
declare const Neutralino;
|
||||||
|
|
||||||
|
export default (launcher: Launcher): Promise<void> => {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
/**
|
||||||
|
* Selecting wine executable
|
||||||
|
*/
|
||||||
|
let wineExeutable = 'wine';
|
||||||
|
|
||||||
|
const runner = await Runners.current;
|
||||||
|
|
||||||
|
console.log(runner);
|
||||||
|
|
||||||
|
if (runner !== null)
|
||||||
|
{
|
||||||
|
wineExeutable = `${constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Neutralino.filesystem.getStats(wineExeutable);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
wineExeutable = 'wine';
|
||||||
|
|
||||||
|
await Configs.set('runner', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Wine executable: ${wineExeutable}`);
|
||||||
|
|
||||||
|
// Some special variables
|
||||||
|
let env: any = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HUD
|
||||||
|
*/
|
||||||
|
switch (await Configs.get('hud'))
|
||||||
|
{
|
||||||
|
case 'dxvk':
|
||||||
|
env['DXVK_HUD'] = 'fps,frametimes';
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mangohud':
|
||||||
|
env['MANGOHUD'] = 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shaders
|
||||||
|
*/
|
||||||
|
const shaders = await Configs.get('shaders');
|
||||||
|
|
||||||
|
if (shaders !== null)
|
||||||
|
{
|
||||||
|
const userShadersFile = `${constants.paths.shadersDir}/${shaders}/vkBasalt.conf`;
|
||||||
|
const launcherShadersFile = `${await constants.paths.launcherDir}/vkBasalt.conf`;
|
||||||
|
|
||||||
|
env['ENABLE_VKBASALT'] = 1;
|
||||||
|
env['VKBASALT_CONFIG_FILE'] = launcherShadersFile;
|
||||||
|
|
||||||
|
await Neutralino.filesystem.writeFile(launcherShadersFile, await Neutralino.filesystem.readFile(userShadersFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GPU selection
|
||||||
|
*/
|
||||||
|
/*if (LauncherLib.getConfig('gpu') != 'default')
|
||||||
|
{
|
||||||
|
const gpu = await SwitcherooControl.getGpuByName(LauncherLib.getConfig('gpu'));
|
||||||
|
|
||||||
|
if (gpu)
|
||||||
|
{
|
||||||
|
env = {
|
||||||
|
...env,
|
||||||
|
...SwitcherooControl.getEnvAsObject(gpu)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
else console.warn(`GPU ${LauncherLib.getConfig('gpu')} not found. Launching on the default GPU`);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// let command = `${wineExeutable} ${LauncherLib.getConfig('fpsunlock') ? 'fpsunlock.bat' : 'launcher.bat'}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gamemode integration
|
||||||
|
*/
|
||||||
|
/*if (LauncherLib.getConfig('gamemode'))
|
||||||
|
command = `gamemoderun ${command}`;*/
|
||||||
|
|
||||||
|
const command = `${wineExeutable} launcher.bat`;
|
||||||
|
|
||||||
|
console.log(`Execution command: ${command}`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starting the game
|
||||||
|
*/
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
const process = await Process.run(command, {
|
||||||
|
env: env,
|
||||||
|
cwd: await constants.paths.gameDir
|
||||||
|
});
|
||||||
|
|
||||||
|
// Game closed event
|
||||||
|
process.finish(() => {
|
||||||
|
const stopTime = Date.now();
|
||||||
|
|
||||||
|
// todo
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
100
src/ts/neutralino/Process.ts
Normal file
100
src/ts/neutralino/Process.ts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
declare const Neutralino;
|
||||||
|
|
||||||
|
type ProcessOptions = {
|
||||||
|
input?: string;
|
||||||
|
env?: object;
|
||||||
|
cwd?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Process
|
||||||
|
{
|
||||||
|
public readonly id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interval between process status update
|
||||||
|
*/
|
||||||
|
public interval: number = 200;
|
||||||
|
|
||||||
|
protected _finished: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the process was finished
|
||||||
|
*/
|
||||||
|
public get finished(): boolean
|
||||||
|
{
|
||||||
|
return this._finished;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected onFinish?: (process: Process) => void;
|
||||||
|
|
||||||
|
public constructor(pid: number)
|
||||||
|
{
|
||||||
|
this.id = pid;
|
||||||
|
|
||||||
|
const updateStatus = async () => {
|
||||||
|
Neutralino.os.execCommand(`ps -p ${this.id}`).then((output) => {
|
||||||
|
// The process is still running
|
||||||
|
if (output.stdOut.includes(this.id))
|
||||||
|
setTimeout(updateStatus, this.interval);
|
||||||
|
|
||||||
|
// Otherwise the process was stopped
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._finished = true;
|
||||||
|
|
||||||
|
if (this.onFinish)
|
||||||
|
this.onFinish(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(updateStatus, this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify callback to run when the process will be finished
|
||||||
|
*/
|
||||||
|
public finish(callback: (process: Process) => void)
|
||||||
|
{
|
||||||
|
this.onFinish = callback;
|
||||||
|
|
||||||
|
if (this._finished)
|
||||||
|
callback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run shell command
|
||||||
|
*/
|
||||||
|
public static run(command: string, options: ProcessOptions = {}): Promise<Process>
|
||||||
|
{
|
||||||
|
// Replace '\a\b' to '\\a\\b'
|
||||||
|
// And replace ''' to '\''
|
||||||
|
const addSlashes = (str: string) => str.replaceAll('\\', '\\\\').replaceAll('\'', '\\\'');
|
||||||
|
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
// Set env variables
|
||||||
|
if (options.env)
|
||||||
|
{
|
||||||
|
Object.keys(options.env).forEach((key) => {
|
||||||
|
command = `${key}='${addSlashes(options.env![key])}' ${command}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set current working directory
|
||||||
|
if (options.cwd)
|
||||||
|
command = `cd '${addSlashes(options.cwd)}' && ${command} && cd -`;
|
||||||
|
|
||||||
|
// And run the command
|
||||||
|
const process = await Neutralino.os.execCommand(command, {
|
||||||
|
background: true,
|
||||||
|
stdin: options.input ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(new Process(process.pid));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { ProcessOptions };
|
||||||
|
|
||||||
|
export default Process;
|
11
src/ts/types/Launcher.ts
Normal file
11
src/ts/types/Launcher.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
type LauncherState =
|
||||||
|
| 'patch-unavailable'
|
||||||
|
| 'test-patch-available'
|
||||||
|
| 'patch-applying'
|
||||||
|
| 'game-update-available'
|
||||||
|
| 'game-installation-available'
|
||||||
|
| 'game-voice-update-required'
|
||||||
|
| 'resume-download-available'
|
||||||
|
| 'game-launch-available';
|
||||||
|
|
||||||
|
export type { LauncherState };
|
Loading…
Reference in a new issue