mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2024-12-20 17:01:47 +03:00
Merge branch 'empathize' into 'main'
Moved launcher on empathize See merge request KRypt0n_/an-anime-game-launcher!24
This commit is contained in:
commit
236113bd15
50 changed files with 246 additions and 2097 deletions
|
@ -11,6 +11,7 @@
|
|||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@empathize/framework": "^1.3.5",
|
||||
"js-md5": "^0.7.3",
|
||||
"semver": "^7.3.5",
|
||||
"svelte-i18n": "^3.3.13",
|
||||
|
@ -18,12 +19,12 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@neutralinojs/neu": "^9.1.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.35",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
|
||||
"@tsconfig/svelte": "^3.0.0",
|
||||
"@types/js-md5": "^0.4.3",
|
||||
"neutralino-appimage-bundler": "^1.3.2",
|
||||
"sass": "^1.49.0",
|
||||
"svelte": "^3.46.2",
|
||||
"svelte": "^3.46.3",
|
||||
"svelte-check": "^2.3.0",
|
||||
"svelte-preprocess": "^4.10.2",
|
||||
"tslib": "^2.3.1",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
import { Configs } from '../empathize';
|
||||
|
||||
export let active: boolean = false;
|
||||
export let disabled: boolean = false;
|
||||
|
||||
|
@ -12,8 +14,6 @@
|
|||
|
||||
import Checkmark from '../assets/svgs/checkmark.svg';
|
||||
|
||||
import Configs from '../ts/Configs';
|
||||
|
||||
Configs.get(prop).then((value) => active = value as boolean);
|
||||
|
||||
function updateCheckbox()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
import Configs from '../ts/Configs';
|
||||
import { Configs } from '../empathize';
|
||||
|
||||
export let visible: boolean = false;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
import Configs from '../ts/Configs';
|
||||
import { Configs } from '../empathize';
|
||||
|
||||
export let prop: string = '';
|
||||
export let lang: string = '';
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
import Configs from '../ts/Configs';
|
||||
import { Configs } from '../empathize';
|
||||
|
||||
import Button from './Button.svelte';
|
||||
|
||||
let last_id = 0, variables = {}, selected;
|
||||
|
||||
Configs.get('env').then((env) => {
|
||||
for (const key of Object.keys(env as object))
|
||||
if (env)
|
||||
{
|
||||
variables[last_id++] = {
|
||||
key: key,
|
||||
value: env![key]
|
||||
};
|
||||
for (const key of Object.keys(env as object))
|
||||
{
|
||||
variables[last_id++] = {
|
||||
key: key,
|
||||
value: env![key]
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -29,26 +32,30 @@
|
|||
</script>
|
||||
|
||||
<div>
|
||||
<table class="table properties-table" style="margin-top: 16px">
|
||||
<tr>
|
||||
<th>{$_('settings.environment.items.table.name')}</th>
|
||||
<th>{$_('settings.environment.items.table.value')}</th>
|
||||
</tr>
|
||||
|
||||
{#each Object.keys(variables) as key}
|
||||
<tr on:click={() => selected = key} class:selected={selected === key}>
|
||||
<td>
|
||||
<span>{variables[key].key}</span>
|
||||
<input bind:value={variables[key].key} on:change={updateEnv} />
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span>{variables[key].value}</span>
|
||||
<input bind:value={variables[key].value} on:change={updateEnv} />
|
||||
</td>
|
||||
{#if Object.keys(variables).length > 0}
|
||||
<table class="table properties-table" style="margin-top: 16px">
|
||||
<tr>
|
||||
<th>{$_('settings.environment.items.table.name')}</th>
|
||||
<th>{$_('settings.environment.items.table.value')}</th>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
|
||||
{#each Object.keys(variables) as key}
|
||||
<tr on:click={() => selected = key} class:selected={selected === key}>
|
||||
<td>
|
||||
<span>{variables[key].key}</span>
|
||||
<input bind:value={variables[key].key} on:change={updateEnv} />
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span>{variables[key].value}</span>
|
||||
<input bind:value={variables[key].value} on:change={updateEnv} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{:else}
|
||||
<p>There're no variables here</p>
|
||||
{/if}
|
||||
|
||||
<div style="margin-top: 16px">
|
||||
<Button lang="settings.environment.items.buttons.add" click={() => variables[last_id++] = { key: '', value: '' }} />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
|
||||
import Configs from '../ts/Configs';
|
||||
import { Configs } from '../empathize';
|
||||
|
||||
export let prop: string = '';
|
||||
export let lang: string = '';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Configs from './ts/Configs';
|
||||
import { Configs, promisify } from './empathize';
|
||||
|
||||
import constants from './ts/Constants';
|
||||
import promisify from './ts/core/promisify';
|
||||
|
||||
promisify(async () => {
|
||||
Configs.defaults({
|
||||
|
|
55
src/empathize.ts
Normal file
55
src/empathize.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import {
|
||||
// Paths API
|
||||
path, dir,
|
||||
|
||||
// Filesystem API
|
||||
fs,
|
||||
|
||||
// Windows API
|
||||
Windows,
|
||||
|
||||
// OS API
|
||||
Process, Tray, IPC, Notification, Archive,
|
||||
|
||||
// Network API
|
||||
fetch, Domain, Downloader,
|
||||
|
||||
// Async API
|
||||
promisify,
|
||||
|
||||
// Meta classes
|
||||
Cache, Configs, Debug
|
||||
} from '@empathize/framework';
|
||||
|
||||
import YAML from 'yaml';
|
||||
|
||||
import constants from './ts/Constants';
|
||||
|
||||
Configs.file = constants.paths.config;
|
||||
Cache.file = constants.paths.cache;
|
||||
|
||||
Configs.serialize = YAML.stringify;
|
||||
Configs.unserialize = YAML.parse;
|
||||
|
||||
export {
|
||||
// Paths API
|
||||
path, dir,
|
||||
|
||||
// Filesystem API
|
||||
fs,
|
||||
|
||||
// Windows API
|
||||
Windows,
|
||||
|
||||
// OS API
|
||||
Process, Tray, IPC, Notification, Archive,
|
||||
|
||||
// Network API
|
||||
fetch, Domain, Downloader,
|
||||
|
||||
// Async API
|
||||
promisify,
|
||||
|
||||
// Meta classes
|
||||
Cache, Configs, Debug
|
||||
};
|
|
@ -6,18 +6,12 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { _, locale } from 'svelte-i18n';
|
||||
|
||||
import Window from './ts/neutralino/Window';
|
||||
import Process from './ts/neutralino/Process';
|
||||
import { Windows, path, Archive, Debug, Downloader, IPC, Configs } from './empathize';
|
||||
|
||||
import Launcher from './ts/Launcher';
|
||||
import constants from './ts/Constants';
|
||||
import Game from './ts/Game';
|
||||
import Background from './ts/launcher/Background';
|
||||
import Archive from './ts/core/Archive';
|
||||
import Debug from './ts/core/Debug';
|
||||
import Downloader from './ts/core/Downloader';
|
||||
import IPC from './ts/core/IPC';
|
||||
import Configs from './ts/Configs';
|
||||
|
||||
import Gear from './assets/images/gear.png';
|
||||
import GearActive from './assets/images/gear-active.png';
|
||||
|
@ -36,7 +30,7 @@
|
|||
const launcher = new Launcher(onMount);
|
||||
|
||||
Neutralino.events.on('ready', () => {
|
||||
Window.open('splash', {
|
||||
Windows.open('splash', {
|
||||
title: 'Splash',
|
||||
width: 300,
|
||||
height: 400,
|
||||
|
@ -60,13 +54,13 @@
|
|||
await launcher.rpc.stop(true);
|
||||
|
||||
// Remove .tmp files from the temp folder
|
||||
await Neutralino.os.execCommand(`find "${Process.addSlashes(tempDir)}" -maxdepth 1 -type f -name "*.tmp" -delete`);
|
||||
await Neutralino.os.execCommand(`find "${path.addSlashes(tempDir)}" -maxdepth 1 -type f -name "*.tmp" -delete`);
|
||||
|
||||
// Remove old launcher's log files
|
||||
const purge_logs = await Configs.get('purge_logs.launcher') as string|null;
|
||||
|
||||
if (purge_logs !== null && purge_logs[purge_logs.length - 1] == 'd')
|
||||
await Neutralino.os.execCommand(`find "${Process.addSlashes(launcherDir)}/logs" -maxdepth 1 -mtime ${purge_logs.substring(0, purge_logs.length - 1)} -delete`);
|
||||
await Neutralino.os.execCommand(`find "${path.addSlashes(launcherDir)}/logs" -maxdepth 1 -mtime ${purge_logs.substring(0, purge_logs.length - 1)} -delete`);
|
||||
|
||||
// Save logs
|
||||
const log = Debug.get().join('\r\n');
|
||||
|
@ -103,7 +97,7 @@
|
|||
* Update launcher's title
|
||||
*/
|
||||
Game.latest.then((game) => {
|
||||
Window.current.setTitle(`${constants.placeholders.uppercase.full} Linux Launcher - ${game.version}`);
|
||||
Windows.current.setTitle(`${constants.placeholders.uppercase.full} Linux Launcher - ${game.version}`);
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,16 +6,11 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { _, locale, locales } from 'svelte-i18n';
|
||||
|
||||
import Window from './ts/neutralino/Window';
|
||||
import Process from './ts/neutralino/Process';
|
||||
import { Windows, Configs, Debug, IPC, Process, path } from './empathize';
|
||||
|
||||
import constants from './ts/Constants';
|
||||
import Configs from './ts/Configs';
|
||||
import Launcher from './ts/Launcher';
|
||||
import FPSUnlock from './ts/FPSUnlock';
|
||||
|
||||
import Debug from './ts/core/Debug';
|
||||
import IPC from './ts/core/IPC';
|
||||
import Runners from './ts/core/Runners';
|
||||
|
||||
import Button from './components/Button.svelte';
|
||||
|
@ -41,72 +36,6 @@
|
|||
|
||||
launcherLocales = launcherLocales;
|
||||
|
||||
/**
|
||||
* Game voice packs languages
|
||||
*/
|
||||
|
||||
const voiceLocales = {
|
||||
'en-us': 'settings.general.items.lang.voice.items.en-us',
|
||||
'ja-jp': 'settings.general.items.lang.voice.items.ja-jp',
|
||||
'ko-kr': 'settings.general.items.lang.voice.items.ko-kr',
|
||||
'zn-cn': 'settings.general.items.lang.voice.items.zn-cn'
|
||||
};
|
||||
|
||||
/**
|
||||
* Themes
|
||||
*/
|
||||
|
||||
const themes = {
|
||||
'system': 'settings.general.items.theme.items.system',
|
||||
'light': 'settings.general.items.theme.items.light',
|
||||
'dark': 'settings.general.items.theme.items.dark'
|
||||
};
|
||||
|
||||
/**
|
||||
* HUD options
|
||||
*/
|
||||
|
||||
const huds = {
|
||||
'none': 'settings.enhancements.items.hud.items.none',
|
||||
'dxvk': 'settings.enhancements.items.hud.items.dxvk',
|
||||
'mangohud': 'settings.enhancements.items.hud.items.mangohud'
|
||||
};
|
||||
|
||||
/**
|
||||
* Wine synchronizations
|
||||
*/
|
||||
|
||||
const winesyncs = {
|
||||
'none': 'settings.enhancements.items.winesync.items.none',
|
||||
'esync': 'settings.enhancements.items.winesync.items.esync',
|
||||
'fsync': 'settings.enhancements.items.winesync.items.fsync'
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete launcher logs options
|
||||
*/
|
||||
|
||||
const purgeLauncherLogs = {
|
||||
'1d': 'settings.enhancements.items.purge_logs.launcher.items.1d',
|
||||
'3d': 'settings.enhancements.items.purge_logs.launcher.items.3d',
|
||||
'5d': 'settings.enhancements.items.purge_logs.launcher.items.5d',
|
||||
'7d': 'settings.enhancements.items.purge_logs.launcher.items.7d',
|
||||
'14d': 'settings.enhancements.items.purge_logs.launcher.items.14d',
|
||||
'never': 'settings.enhancements.items.purge_logs.launcher.items.never'
|
||||
};
|
||||
|
||||
/**
|
||||
* Menu items
|
||||
*/
|
||||
const menuItems = [
|
||||
'general',
|
||||
'enhancements',
|
||||
'runners',
|
||||
'dxvks',
|
||||
'shaders',
|
||||
'environment'
|
||||
];
|
||||
|
||||
/**
|
||||
* Some components stuff
|
||||
*/
|
||||
|
@ -203,8 +132,8 @@
|
|||
|
||||
// Do some stuff when all the content will be loaded
|
||||
onMount(async () => {
|
||||
await Window.current.show();
|
||||
await Window.current.center(900, 600);
|
||||
await Windows.current.show();
|
||||
// FIXME: await Windows.current.center(900, 600);
|
||||
|
||||
// This thing will fix window resizing
|
||||
// in several cases (wayland + gnome + custom theme)
|
||||
|
@ -214,7 +143,7 @@
|
|||
|
||||
else
|
||||
{
|
||||
Window.current.setSize({
|
||||
Windows.current.setSize({
|
||||
width: 900 + (900 - window.innerWidth),
|
||||
height: 600 + (600 - window.innerHeight),
|
||||
resizable: false
|
||||
|
@ -241,8 +170,13 @@
|
|||
{#if typeof $locale === 'string'}
|
||||
<main>
|
||||
<div class="menu">
|
||||
{#each menuItems as item}
|
||||
<div class="menu-item" on:click={changeItem} class:menu-item-active={selectedItem === item} data-anchor={item}>{ $_(`settings.${item}.title`) }</div>
|
||||
{#each ['general', 'enhancements', 'runners', 'dxvks', 'shaders', 'environment'] as item}
|
||||
<div
|
||||
class="menu-item"
|
||||
class:menu-item-active={selectedItem === item}
|
||||
data-anchor={item}
|
||||
on:click={changeItem}
|
||||
>{$_(`settings.${item}.title`)}</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
@ -269,14 +203,23 @@
|
|||
tooltip="settings.general.items.lang.voice.tooltip"
|
||||
prop="lang.voice"
|
||||
selected={undefined}
|
||||
items={voiceLocales}
|
||||
items={{
|
||||
'en-us': 'settings.general.items.lang.voice.items.en-us',
|
||||
'ja-jp': 'settings.general.items.lang.voice.items.ja-jp',
|
||||
'ko-kr': 'settings.general.items.lang.voice.items.ko-kr',
|
||||
'zn-cn': 'settings.general.items.lang.voice.items.zn-cn'
|
||||
}}
|
||||
selectionUpdated={() => voiceUpdateRequired = true}
|
||||
/>
|
||||
|
||||
<SelectionBox
|
||||
lang="settings.general.items.theme.title"
|
||||
prop="theme"
|
||||
items={themes}
|
||||
items={{
|
||||
'system': 'settings.general.items.theme.items.system',
|
||||
'light': 'settings.general.items.theme.items.light',
|
||||
'dark': 'settings.general.items.theme.items.dark'
|
||||
}}
|
||||
valueChanged={switchTheme}
|
||||
/>
|
||||
|
||||
|
@ -294,7 +237,7 @@
|
|||
|
||||
const runnersDir = await constants.paths.runnersDir;
|
||||
|
||||
Process.run(`"${Process.addSlashes(await constants.paths.launcherDir)}/winetricks.sh"`, {
|
||||
Process.run(`"${path.addSlashes(await constants.paths.launcherDir)}/winetricks.sh"`, {
|
||||
env: {
|
||||
WINE: runner ? `${runnersDir}/${runner.name}/${runner.files.wine}` : 'wine',
|
||||
WINESERVER: runner ? `${runnersDir}/${runner.name}/${runner.files.wineserver}` : 'wineserver',
|
||||
|
@ -308,7 +251,7 @@
|
|||
|
||||
const runnerDir = runner ? `${await constants.paths.runnersDir}/${runner.name}` : '';
|
||||
|
||||
Process.run(runner ? `"${Process.addSlashes(`${runnerDir}/${runner.files.wine}`)}" "${Process.addSlashes(`${runnerDir}/${runner.files.winecfg}`)}"` : 'winecfg', {
|
||||
Process.run(runner ? `"${path.addSlashes(`${runnerDir}/${runner.files.wine}`)}" "${path.addSlashes(`${runnerDir}/${runner.files.winecfg}`)}"` : 'winecfg', {
|
||||
env: {
|
||||
WINE: runner ? `${runnerDir}/${runner.files.wine}` : 'wine',
|
||||
WINESERVER: runner ? `${runnerDir}/${runner.files.wineserver}` : 'wineserver',
|
||||
|
@ -319,14 +262,14 @@
|
|||
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<Button lang="settings.general.items.buttons.launcher" click={async () => {
|
||||
Neutralino.os.execCommand(`xdg-open "${Process.addSlashes(await constants.paths.launcherDir)}"`, {
|
||||
Neutralino.os.execCommand(`xdg-open "${path.addSlashes(await constants.paths.launcherDir)}"`, {
|
||||
background: true
|
||||
});
|
||||
}} />
|
||||
|
||||
<!-- svelte-ignore missing-declaration -->
|
||||
<Button lang="settings.general.items.buttons.game" click={async () => {
|
||||
Neutralino.os.execCommand(`xdg-open "${Process.addSlashes(await constants.paths.gameDir)}"`, {
|
||||
Neutralino.os.execCommand(`xdg-open "${path.addSlashes(await constants.paths.gameDir)}"`, {
|
||||
background: true
|
||||
});
|
||||
}} />
|
||||
|
@ -339,7 +282,11 @@
|
|||
<SelectionBox
|
||||
lang="settings.enhancements.items.hud.title"
|
||||
prop="hud"
|
||||
items={huds}
|
||||
items={{
|
||||
'none': 'settings.enhancements.items.hud.items.none',
|
||||
'dxvk': 'settings.enhancements.items.hud.items.dxvk',
|
||||
'mangohud': 'settings.enhancements.items.hud.items.mangohud'
|
||||
}}
|
||||
/>
|
||||
|
||||
<SelectionBox
|
||||
|
@ -347,7 +294,11 @@
|
|||
prop="winesync"
|
||||
tooltip="settings.enhancements.items.winesync.tooltip"
|
||||
tooltip_size="large"
|
||||
items={winesyncs}
|
||||
items={{
|
||||
'none': 'settings.enhancements.items.winesync.items.none',
|
||||
'esync': 'settings.enhancements.items.winesync.items.esync',
|
||||
'fsync': 'settings.enhancements.items.winesync.items.fsync'
|
||||
}}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
|
@ -388,7 +339,14 @@
|
|||
lang="settings.enhancements.items.purge_logs.launcher.title"
|
||||
tooltip="settings.enhancements.items.purge_logs.launcher.tooltip"
|
||||
prop="purge_logs.launcher"
|
||||
items={purgeLauncherLogs}
|
||||
items={{
|
||||
'1d': 'settings.enhancements.items.purge_logs.launcher.items.1d',
|
||||
'3d': 'settings.enhancements.items.purge_logs.launcher.items.3d',
|
||||
'5d': 'settings.enhancements.items.purge_logs.launcher.items.5d',
|
||||
'7d': 'settings.enhancements.items.purge_logs.launcher.items.7d',
|
||||
'14d': 'settings.enhancements.items.purge_logs.launcher.items.14d',
|
||||
'never': 'settings.enhancements.items.purge_logs.launcher.items.never'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -6,10 +6,7 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { _, locale } from 'svelte-i18n';
|
||||
|
||||
import Configs from './ts/Configs';
|
||||
import IPC from './ts/core/IPC';
|
||||
|
||||
import Window from './ts/neutralino/Window';
|
||||
import { Configs, IPC, Windows } from './empathize';
|
||||
|
||||
import Splash from './assets/gifs/running-qiqi.gif';
|
||||
import SplashSecret from './assets/gifs/loading-marie-please.gif';
|
||||
|
@ -19,8 +16,8 @@
|
|||
let phrase = Math.round(Math.random() * 8);
|
||||
|
||||
onMount(() => {
|
||||
Window.current.show();
|
||||
Window.current.center(300, 400);
|
||||
Windows.current.show();
|
||||
// FIXME: Windows.current.center(300, 400);
|
||||
});
|
||||
|
||||
const isLauncherLoaded = () => {
|
||||
|
@ -32,7 +29,7 @@
|
|||
for (const record of launcherLoaded)
|
||||
await record.pop();
|
||||
|
||||
Window.current.hide();
|
||||
Windows.current.hide();
|
||||
Neutralino.app.exit();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
import YAML from 'yaml';
|
||||
|
||||
import constants from './Constants';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
// Ok yea, null, object and boolean aren't scalars
|
||||
// but I don't care
|
||||
type scalar = null | string | number | boolean | object;
|
||||
|
||||
export default class Configs
|
||||
{
|
||||
/**
|
||||
* Get config value
|
||||
*
|
||||
* @param name config name, e.g. "lang.launcher"
|
||||
*
|
||||
* @returns undefined if config doesn't exist. Otherwise - config value
|
||||
*/
|
||||
public static get(name: string = ''): Promise<undefined|scalar|scalar[]>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
Neutralino.filesystem.readFile(await constants.paths.config).then((config) => {
|
||||
config = YAML.parse(config);
|
||||
|
||||
if (name !== '')
|
||||
{
|
||||
name.split('.').forEach((value) => {
|
||||
config = config[value];
|
||||
});
|
||||
}
|
||||
|
||||
resolve(config);
|
||||
}).catch(() => {
|
||||
setTimeout(() => resolve(this.get(name)), 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set config value
|
||||
*
|
||||
* @param name config name, e.g. "lang.launcher"
|
||||
* @param value config value, e.g. "en-us"
|
||||
*
|
||||
* @returns Promise<void> indicates if the settings were updated
|
||||
*/
|
||||
public static set(name: string, value: scalar|scalar[]|Promise<scalar|scalar[]>): Promise<void>
|
||||
{
|
||||
const getUpdatedArray = (path: string[], array: scalar|scalar[], value: scalar|scalar[]): scalar|scalar[] => {
|
||||
array![path[0]] = path.length > 1 ?
|
||||
getUpdatedArray(path.slice(1), array![path[0]] ?? {}, value) : value;
|
||||
|
||||
return array;
|
||||
};
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
value = await Promise.resolve(value);
|
||||
|
||||
Neutralino.filesystem.readFile(await constants.paths.config).then(async (config) => {
|
||||
config = YAML.stringify(getUpdatedArray(name.split('.'), YAML.parse(config), value));
|
||||
|
||||
Neutralino.filesystem.writeFile(await constants.paths.config, config)
|
||||
.then(() => resolve());
|
||||
}).catch(async () => {
|
||||
let config = YAML.stringify(getUpdatedArray(name.split('.'), {}, value));
|
||||
|
||||
Neutralino.filesystem.writeFile(await constants.paths.config, config)
|
||||
.then(() => resolve());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default values
|
||||
*
|
||||
* @param configs object of default values
|
||||
*
|
||||
* @returns Promise<void> indicates if the default settings were applied
|
||||
*/
|
||||
public static defaults(configs: object): Promise<void>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
const setDefaults = async (current: object) => {
|
||||
const updateDefaults = (current: object, defaults: object) => {
|
||||
Object.keys(defaults).forEach((key) => {
|
||||
// If the field exists in defaults and doesn't exist in current
|
||||
if (current[key] === undefined)
|
||||
current[key] = defaults[key];
|
||||
|
||||
// If both default and current are objects
|
||||
// and we also should check if they're not nulls
|
||||
// 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;
|
||||
};
|
||||
|
||||
Neutralino.filesystem.writeFile(await constants.paths.config, YAML.stringify(updateDefaults(current, configs)))
|
||||
.then(() => resolve());
|
||||
};
|
||||
|
||||
Neutralino.filesystem.readFile(await constants.paths.config)
|
||||
.then((config) => setDefaults(YAML.parse(config)))
|
||||
.catch(() => setDefaults({}));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Configs from './Configs';
|
||||
import { Configs } from '../empathize';
|
||||
|
||||
declare const Neutralino;
|
||||
declare const NL_CWD;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Downloader, path } from '../empathize';
|
||||
|
||||
import constants from './Constants';
|
||||
import Downloader from './core/Downloader';
|
||||
import Process from './neutralino/Process';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
@ -35,7 +35,7 @@ export default class FPSUnlock
|
|||
Downloader.download(constants.uri.fpsunlock.bat, fpsunlockBat).then((stream) => {
|
||||
stream.finish(async () => {
|
||||
// sed -i 's/start ..\/GI_FPSUnlocker\/unlockfps.exe \%\*/start ..\/fpsunlock\/unlockfps.exe \%\*/g' unlockfps.bat
|
||||
Neutralino.os.execCommand(`sed -i 's/start ..\\/GI_FPSUnlocker\\/unlockfps.exe \\%\\*/start ..\\/fpsunlock\\/unlockfps.exe \\%\\*/g' "${Process.addSlashes(fpsunlockBat)}"`)
|
||||
Neutralino.os.execCommand(`sed -i 's/start ..\\/GI_FPSUnlocker\\/unlockfps.exe \\%\\*/start ..\\/fpsunlock\\/unlockfps.exe \\%\\*/g' "${path.addSlashes(fpsunlockBat)}"`)
|
||||
.then(() => resolve());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,14 +5,13 @@ import type {
|
|||
Diff
|
||||
} from './types/GameData';
|
||||
|
||||
import type { Stream as DownloadingStream } from '@empathize/framework/dist/network/Downloader';
|
||||
|
||||
import { fetch, Domain, promisify, Downloader, Cache, Debug } from '../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import constants from './Constants';
|
||||
import fetch from './core/Fetch';
|
||||
import AbstractInstaller from './core/AbstractInstaller';
|
||||
import Domain from './core/Domain';
|
||||
import promisify from './core/promisify';
|
||||
import Debug, { DebugThread } from './core/Debug';
|
||||
import Downloader, { Stream as DownloadingStream } from './core/Downloader';
|
||||
import Cache from './core/Cache';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { locale } from 'svelte-i18n';
|
||||
|
||||
import Window from './neutralino/Window';
|
||||
import Process from './neutralino/Process';
|
||||
import Tray from './neutralino/Tray';
|
||||
import {
|
||||
Windows, Process, Tray,
|
||||
Configs, Debug, IPC
|
||||
} from '../empathize';
|
||||
|
||||
import constants from './Constants';
|
||||
import Configs from './Configs';
|
||||
import Debug from './core/Debug';
|
||||
import IPC from './core/IPC';
|
||||
import DiscordRPC from './core/DiscordRPC';
|
||||
import Locales from './launcher/Locales';
|
||||
|
||||
|
@ -56,7 +54,7 @@ export default class Launcher
|
|||
{
|
||||
this.settingsMenu = undefined;
|
||||
|
||||
const window = await Window.open('settings', {
|
||||
const window = await Windows.open('settings', {
|
||||
title: 'Settings',
|
||||
width: 900,
|
||||
height: 600,
|
||||
|
@ -96,11 +94,11 @@ export default class Launcher
|
|||
});
|
||||
});
|
||||
|
||||
Window.current.show();
|
||||
Window.current.center(1280, 700);
|
||||
Windows.current.show();
|
||||
// TODO: Windows.current.center(1280, 700);
|
||||
})
|
||||
|
||||
Window.current.hide();
|
||||
Windows.current.hide();
|
||||
}
|
||||
|
||||
resolve(window.status);
|
||||
|
|
|
@ -2,14 +2,12 @@ import type { PatchInfo } from './types/Patch';
|
|||
|
||||
import md5 from 'js-md5';
|
||||
|
||||
import { fetch, promisify, Debug, Cache, path } from '../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import constants from './Constants';
|
||||
import Game from './Game';
|
||||
import fetch from './core/Fetch';
|
||||
import AbstractInstaller from './core/AbstractInstaller';
|
||||
import promisify from './core/promisify';
|
||||
import Process from './neutralino/Process';
|
||||
import Debug, { DebugThread } from './core/Debug';
|
||||
import Cache from './core/Cache';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
@ -50,37 +48,37 @@ class Stream extends AbstractInstaller
|
|||
/**
|
||||
* Remove test version restrictions from the main patch
|
||||
*/
|
||||
() => Neutralino.os.execCommand(`cd "${Process.addSlashes(patchDir)}" && sed -i '/^echo "If you would like to test this patch, modify this script and remove the line below this one."/,+5d' patch.sh`),
|
||||
() => Neutralino.os.execCommand(`cd "${path.addSlashes(patchDir)}" && sed -i '/^echo "If you would like to test this patch, modify this script and remove the line below this one."/,+5d' patch.sh`),
|
||||
|
||||
/**
|
||||
* Remove /etc/hosts editing due to sudo permissions
|
||||
*/
|
||||
() => Neutralino.os.execCommand(`cd "${Process.addSlashes(patchDir)}" && sed -i '/^# ===========================================================/,+68d' patch.sh`),
|
||||
() => Neutralino.os.execCommand(`cd "${path.addSlashes(patchDir)}" && sed -i '/^# ===========================================================/,+68d' patch.sh`),
|
||||
|
||||
/**
|
||||
* Remove test version restrictions from the anti-login crash patch
|
||||
*/
|
||||
() => Neutralino.os.execCommand(`cd "${Process.addSlashes(patchDir)}" && sed -i '/^echo " necessary afterwards (Friday?). If that's the case, comment the line below."/,+2d' patch_anti_logincrash.sh`),
|
||||
() => Neutralino.os.execCommand(`cd "${path.addSlashes(patchDir)}" && sed -i '/^echo " necessary afterwards (Friday?). If that's the case, comment the line below."/,+2d' patch_anti_logincrash.sh`),
|
||||
|
||||
/**
|
||||
* Make the main patch executable
|
||||
*/
|
||||
() => Neutralino.os.execCommand(`chmod +x "${Process.addSlashes(patchDir)}/patch.sh"`),
|
||||
() => Neutralino.os.execCommand(`chmod +x "${path.addSlashes(patchDir)}/patch.sh"`),
|
||||
|
||||
/**
|
||||
* Make the anti-login crash patch executable
|
||||
*/
|
||||
() => Neutralino.os.execCommand(`chmod +x "${Process.addSlashes(patchDir)}/patch_anti_logincrash.sh"`),
|
||||
() => Neutralino.os.execCommand(`chmod +x "${path.addSlashes(patchDir)}/patch_anti_logincrash.sh"`),
|
||||
|
||||
/**
|
||||
* Execute the main patch installation script
|
||||
*/
|
||||
() => Neutralino.os.execCommand(`cd "${Process.addSlashes(gameDir)}" && yes yes | bash "${Process.addSlashes(patchDir)}/patch.sh"`),
|
||||
() => Neutralino.os.execCommand(`cd "${path.addSlashes(gameDir)}" && yes yes | bash "${path.addSlashes(patchDir)}/patch.sh"`),
|
||||
|
||||
/**
|
||||
* Execute the anti-login crash patch installation script
|
||||
*/
|
||||
() => Neutralino.os.execCommand(`cd "${Process.addSlashes(gameDir)}" && yes | bash "${Process.addSlashes(patchDir)}/patch_anti_logincrash.sh"`)
|
||||
() => Neutralino.os.execCommand(`cd "${path.addSlashes(gameDir)}" && yes | bash "${path.addSlashes(patchDir)}/patch_anti_logincrash.sh"`)
|
||||
]
|
||||
});
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import type { VoicePack } from './types/GameData';
|
||||
import type { VoiceLang, InstalledVoice } from './types/Voice';
|
||||
|
||||
import type { Stream as DownloadingStream } from '@empathize/framework/dist/network/Downloader';
|
||||
|
||||
import { Configs, Debug, Downloader, promisify, path } from '../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import constants from './Constants';
|
||||
import Game from './Game';
|
||||
import AbstractInstaller from './core/AbstractInstaller';
|
||||
import Configs from './Configs';
|
||||
import Debug, { DebugThread } from './core/Debug';
|
||||
import Downloader, { Stream as DownloadingStream } from './core/Downloader';
|
||||
import Process from './neutralino/Process';
|
||||
import promisify from './core/promisify';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
@ -170,15 +170,15 @@ export default class Voice
|
|||
|
||||
const pipeline = promisify({
|
||||
callbacks: [
|
||||
() => Neutralino.os.execCommand(`rm -rf "${Process.addSlashes(`${voiceDir}/${this.langs[lang]}`)}"`),
|
||||
() => Neutralino.os.execCommand(`rm -rf "${path.addSlashes(`${voiceDir}/${this.langs[lang]}`)}"`),
|
||||
|
||||
(): Promise<void> => new Promise(async (resolve) => {
|
||||
Neutralino.os.execCommand(`rm -f "${Process.addSlashes(`${await constants.paths.gameDir}/Audio_${this.langs[lang]}_pkg_version`)}"`)
|
||||
Neutralino.os.execCommand(`rm -f "${path.addSlashes(`${await constants.paths.gameDir}/Audio_${this.langs[lang]}_pkg_version`)}"`)
|
||||
.then(() => resolve());
|
||||
}),
|
||||
|
||||
(): Promise<void> => new Promise(async (resolve) => {
|
||||
Neutralino.os.execCommand(`sed -i '/${this.langs[lang]}/d' "${Process.addSlashes(`${await constants.paths.gameDataDir}/Persistent/audio_lang_14`)}"`)
|
||||
Neutralino.os.execCommand(`sed -i '/${this.langs[lang]}/d' "${path.addSlashes(`${await constants.paths.gameDataDir}/Persistent/audio_lang_14`)}"`)
|
||||
.then(() => resolve());
|
||||
})
|
||||
],
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import type { Stream as DownloadStream } from '@empathize/framework/dist/network/Downloader';
|
||||
|
||||
import { Downloader, Archive } from '../../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import constants from '../Constants';
|
||||
import Downloader, { Stream as DownloadStream } from './Downloader';
|
||||
import Archive from './Archive';
|
||||
import { DebugThread } from './Debug';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
@ -68,7 +70,7 @@ export default abstract class Installer
|
|||
if (shouldResolve)
|
||||
debugThread.log(`Resolved unpack dir: ${unpackDir}`);
|
||||
|
||||
Archive.unpack(archivePath, unpackDir).then((stream) => {
|
||||
Archive.extract(archivePath, unpackDir).then((stream) => {
|
||||
stream.progressInterval = this.unpackProgressInterval;
|
||||
|
||||
stream.start(() => {
|
||||
|
|
|
@ -1,364 +0,0 @@
|
|||
import type {
|
||||
ArchiveType,
|
||||
Size,
|
||||
File,
|
||||
ArchiveInfo
|
||||
} from '../types/Archive';
|
||||
|
||||
import { DebugThread } from './Debug';
|
||||
import promisify from './promisify';
|
||||
import Process from '../neutralino/Process';
|
||||
|
||||
declare const Neutralino;
|
||||
declare const NL_CWD;
|
||||
|
||||
class Stream
|
||||
{
|
||||
protected _id: number = -1;
|
||||
|
||||
/**
|
||||
* ID of the archive unpacker process
|
||||
*/
|
||||
public get id(): number
|
||||
{
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interval in ms between progress event calls
|
||||
*/
|
||||
public progressInterval: number = 500;
|
||||
|
||||
protected path: string;
|
||||
protected unpackDir: string|null;
|
||||
protected unpacked: number = 0;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @param path path to archive
|
||||
* @param unpackDir directory to extract the files to
|
||||
*/
|
||||
public constructor(path: string, unpackDir: string|null = null)
|
||||
{
|
||||
this.path = path;
|
||||
this.unpackDir = unpackDir;
|
||||
this.started = true;
|
||||
|
||||
const debugThread = new DebugThread('Archive/Stream', {
|
||||
message: {
|
||||
'path': path,
|
||||
'unpack dir': unpackDir
|
||||
}
|
||||
});
|
||||
|
||||
if (this.onStart)
|
||||
this.onStart();
|
||||
|
||||
Archive.getInfo(path).then((info) => {
|
||||
if (info === null)
|
||||
{
|
||||
this.throwedError = true;
|
||||
|
||||
if (this.onError)
|
||||
this.onError();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
this.archive = info;
|
||||
|
||||
let command = {
|
||||
tar: `tar -xvf "${Process.addSlashes(path)}"${unpackDir ? ` -C "${Process.addSlashes(unpackDir)}"` : ''}`,
|
||||
zip: `unzip -o "${Process.addSlashes(path)}"${unpackDir ? ` -d "${Process.addSlashes(unpackDir)}"` : ''}`
|
||||
}[this.archive.type!];
|
||||
|
||||
if (unpackDir)
|
||||
command = `mkdir -p "${Process.addSlashes(unpackDir)}" && ${command}`;
|
||||
|
||||
let remainedFiles = this.archive.files;
|
||||
|
||||
const baseDir = unpackDir ?? NL_CWD;
|
||||
|
||||
Neutralino.os.execCommand(command, {
|
||||
background: true
|
||||
}).then((result) => {
|
||||
this._id = result.pid;
|
||||
});
|
||||
|
||||
debugThread.log(`Unpacking started with command: ${command}`);
|
||||
|
||||
const updateProgress = async () => {
|
||||
let difference: number = 0;
|
||||
let pool: any[] = [];
|
||||
|
||||
remainedFiles.forEach((file) => {
|
||||
if (file.path != '#unpacked#')
|
||||
{
|
||||
pool.push((): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
Neutralino.filesystem.getStats(`${baseDir}/${file.path}`)
|
||||
.then(() => {
|
||||
this.unpacked += file.size.uncompressed!;
|
||||
difference += file.size.uncompressed!;
|
||||
|
||||
file.path = '#unpacked#';
|
||||
|
||||
resolve();
|
||||
})
|
||||
.catch(() => resolve())
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await promisify({
|
||||
callbacks: pool,
|
||||
callAtOnce: true,
|
||||
interval: 200
|
||||
});
|
||||
|
||||
remainedFiles = remainedFiles.filter((file) => file.path != '#unpacked#');
|
||||
|
||||
if (this.onProgress)
|
||||
this.onProgress(this.unpacked, this.archive!.size.uncompressed!, difference);
|
||||
|
||||
if (this.unpacked >= this.archive!.size.uncompressed!)
|
||||
{
|
||||
this.finished = true;
|
||||
|
||||
debugThread.log('Unpacking finished');
|
||||
|
||||
if (this.onFinish)
|
||||
this.onFinish();
|
||||
}
|
||||
|
||||
if (!this.finished)
|
||||
setTimeout(updateProgress, this.progressInterval);
|
||||
};
|
||||
|
||||
setTimeout(updateProgress, this.progressInterval);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify event that will be called when the extraction has started
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public start(callback: () => void)
|
||||
{
|
||||
this.onStart = callback;
|
||||
|
||||
if (this.started)
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify event that will be called every [this.progressInterval] ms while extracting the archive
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public progress(callback: (current: number, total: number, difference: number) => void)
|
||||
{
|
||||
this.onProgress = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify event that will be called after the archive has been extracted
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public finish(callback: () => void)
|
||||
{
|
||||
this.onFinish = callback;
|
||||
|
||||
if (this.finished)
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify event that will be called if archive can't be extracted
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public error(callback: () => void)
|
||||
{
|
||||
this.onError = callback;
|
||||
|
||||
if (this.throwedError)
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close unpacking stream
|
||||
*/
|
||||
public close(forced: boolean = false)
|
||||
{
|
||||
Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${this._id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default class Archive
|
||||
{
|
||||
protected static streams: Stream[] = [];
|
||||
|
||||
/**
|
||||
* Get type of archive
|
||||
*
|
||||
* @param path path to archive
|
||||
* @returns supported archive type or null
|
||||
*/
|
||||
public static getType(path: string): ArchiveType|null
|
||||
{
|
||||
if (path.substring(path.length - 4) == '.zip')
|
||||
return 'zip';
|
||||
|
||||
else if (path.substring(path.length - 7, path.length - 2) == '.tar.')
|
||||
return 'tar';
|
||||
|
||||
else return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get archive info
|
||||
*
|
||||
* @param path path to archive
|
||||
* @returns null if the archive has unsupported type. Otherwise - archive info
|
||||
*/
|
||||
public static getInfo(path: string): Promise<ArchiveInfo|null>
|
||||
{
|
||||
const debugThread = new DebugThread('Archive.getInfo', `Getting info about archive: ${path}`);
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
let archive: ArchiveInfo = {
|
||||
size: {
|
||||
compressed: null,
|
||||
uncompressed: null
|
||||
},
|
||||
type: this.getType(path),
|
||||
files: []
|
||||
};
|
||||
|
||||
switch (archive.type)
|
||||
{
|
||||
case 'tar':
|
||||
const tarOutput = await Neutralino.os.execCommand(`tar -tvf "${path}"`);
|
||||
|
||||
for (const match of tarOutput.stdOut.matchAll(/^[dwxr\-]+ [\w/]+[ ]+(\d+) [0-9\-]+ [0-9\:]+ (.+)/gm))
|
||||
{
|
||||
let fileSize = parseInt(match[1]);
|
||||
|
||||
archive.size.uncompressed! += fileSize;
|
||||
|
||||
archive.files.push({
|
||||
path: match[2],
|
||||
size: {
|
||||
compressed: null,
|
||||
uncompressed: fileSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
debugThread.log({
|
||||
message: {
|
||||
'type': archive.type,
|
||||
'compressed size': archive.size.compressed,
|
||||
'uncompressed size': archive.size.uncompressed,
|
||||
'files amount': archive.files.length
|
||||
}
|
||||
});
|
||||
|
||||
resolve(archive);
|
||||
|
||||
break;
|
||||
|
||||
case 'zip':
|
||||
const zipOutput = await Neutralino.os.execCommand(`unzip -v "${path}"`);
|
||||
|
||||
for (const match of zipOutput.stdOut.matchAll(/^(\d+) [a-zA-Z\:]+[ ]+(\d+)[ ]+[0-9\-]+% [0-9\-]+ [0-9\:]+ [a-f0-9]{8} (.+)/gm))
|
||||
{
|
||||
let uncompressedSize = parseInt(match[1]),
|
||||
compressedSize = parseInt(match[2]);
|
||||
|
||||
archive.size.compressed! += compressedSize;
|
||||
archive.size.uncompressed! += uncompressedSize;
|
||||
|
||||
archive.files.push({
|
||||
path: match[3],
|
||||
size: {
|
||||
compressed: compressedSize,
|
||||
uncompressed: uncompressedSize
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
debugThread.log({
|
||||
message: {
|
||||
'type': archive.type,
|
||||
'compressed size': archive.size.compressed,
|
||||
'uncompressed size': archive.size.uncompressed,
|
||||
'files amount': archive.files.length
|
||||
}
|
||||
});
|
||||
|
||||
resolve(archive);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
debugThread.log(`Unsupported archive type: ${archive.type}`);
|
||||
|
||||
resolve(null);
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Archive
|
||||
*
|
||||
* @param path path to archive
|
||||
* @param unpackDir directory to extract the files to
|
||||
*/
|
||||
public static unpack(path: string, unpackDir: string|null = null): Promise<Stream>
|
||||
{
|
||||
return new Promise((resolve) => {
|
||||
const stream = new Stream(path, unpackDir);
|
||||
|
||||
this.streams.push(stream);
|
||||
|
||||
resolve(stream);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close every open archive extracting stream
|
||||
*/
|
||||
public static closeStreams(forced: boolean = false)
|
||||
{
|
||||
this.streams.forEach((stream) => {
|
||||
stream.close(forced);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export { Stream };
|
||||
|
||||
export type {
|
||||
ArchiveType,
|
||||
File,
|
||||
Size,
|
||||
ArchiveInfo
|
||||
};
|
|
@ -1,128 +0,0 @@
|
|||
import constants from '../Constants';
|
||||
import Debug from './Debug';
|
||||
|
||||
type Record = {
|
||||
expired: boolean;
|
||||
value: object|object[];
|
||||
};
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
export default class Cache
|
||||
{
|
||||
// Locally stored cache to not to access
|
||||
// cache.json file every time we want to find something
|
||||
protected static cache: object|null = null;
|
||||
|
||||
/**
|
||||
* Get cached value
|
||||
*
|
||||
* @returns null if this value is not cached
|
||||
*/
|
||||
public static get(name: string): Promise<Record|null>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
if (this.cache !== null && this.cache[name] !== undefined)
|
||||
{
|
||||
const expired = this.cache[name].ttl !== null ? Date.now() > this.cache[name].ttl * 1000 : false;
|
||||
|
||||
Debug.log({
|
||||
function: 'Cache.get',
|
||||
message: [
|
||||
`Resolved ${expired ? 'expired' : 'unexpired'} hot cache record`,
|
||||
`[name] ${name}`,
|
||||
`[value]: ${JSON.stringify(this.cache[name].value)}`
|
||||
]
|
||||
});
|
||||
|
||||
resolve({
|
||||
expired: expired,
|
||||
value: this.cache[name].value
|
||||
});
|
||||
}
|
||||
|
||||
else Neutralino.filesystem.readFile(await constants.paths.cache)
|
||||
.then((cache) => {
|
||||
this.cache = JSON.parse(cache);
|
||||
|
||||
if (this.cache![name] === undefined)
|
||||
resolve(null);
|
||||
|
||||
else
|
||||
{
|
||||
const output = {
|
||||
expired: this.cache![name].ttl !== null ? Date.now() > this.cache![name].ttl * 1000 : false,
|
||||
value: this.cache![name].value
|
||||
};
|
||||
|
||||
Debug.log({
|
||||
function: 'Cache.get',
|
||||
message: [
|
||||
`Resolved ${output.expired ? 'expired' : 'unexpired'} cache`,
|
||||
`[name] ${name}`,
|
||||
`[value]: ${JSON.stringify(output.value)}`
|
||||
]
|
||||
});
|
||||
|
||||
resolve(output);
|
||||
}
|
||||
})
|
||||
.catch(() => resolve(null));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache value
|
||||
*
|
||||
* @param name name of the value to cache
|
||||
* @param value value to cache
|
||||
* @param ttl number of seconds to cache
|
||||
*
|
||||
* @returns promise that indicates when the value will be cached
|
||||
*/
|
||||
public static set(name: string, value: object|object[], ttl: number|null = null): Promise<void>
|
||||
{
|
||||
return new Promise((resolve) => {
|
||||
constants.paths.cache.then((cacheFile) => {
|
||||
const writeCache = () => {
|
||||
Debug.log({
|
||||
function: 'Cache.set',
|
||||
message: [
|
||||
'Caching data:',
|
||||
`[ttl] ${ttl}`,
|
||||
`[value] ${JSON.stringify(value)}`
|
||||
]
|
||||
});
|
||||
|
||||
this.cache![name] = {
|
||||
ttl: ttl !== null ? Math.round(Date.now() / 1000) + ttl : null,
|
||||
value: value
|
||||
};
|
||||
|
||||
Neutralino.filesystem.writeFile(cacheFile, JSON.stringify(this.cache))
|
||||
.then(() => resolve());
|
||||
};
|
||||
|
||||
if (this.cache === null)
|
||||
{
|
||||
Neutralino.filesystem.readFile(cacheFile)
|
||||
.then((cacheRaw) =>
|
||||
{
|
||||
this.cache = JSON.parse(cacheRaw);
|
||||
|
||||
writeCache();
|
||||
})
|
||||
.catch(() => {
|
||||
this.cache = {};
|
||||
|
||||
writeCache();
|
||||
});
|
||||
}
|
||||
|
||||
else writeCache();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export type { Record };
|
|
@ -1,12 +1,11 @@
|
|||
import type { DXVK as TDXVK } from '../types/DXVK';
|
||||
|
||||
import { Configs, Process, promisify, path } from '../../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import constants from '../Constants';
|
||||
import Configs from '../Configs';
|
||||
import AbstractInstaller from './AbstractInstaller';
|
||||
import Process from '../neutralino/Process';
|
||||
import promisify from './promisify';
|
||||
import Runners from './Runners';
|
||||
import { DebugThread } from './Debug';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
@ -143,7 +142,7 @@ export default class DXVK
|
|||
const version = typeof dxvk !== 'string' ?
|
||||
dxvk.version : dxvk;
|
||||
|
||||
await Neutralino.os.execCommand(`rm -rf "${Process.addSlashes(await constants.paths.dxvksDir)}/dxvk-${version}"`);
|
||||
await Neutralino.os.execCommand(`rm -rf "${path.addSlashes(await constants.paths.dxvksDir)}/dxvk-${version}"`);
|
||||
|
||||
debugThread.log('Deletion completed');
|
||||
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
import type { DebugOptions, LogRecord } from '../types/Debug';
|
||||
|
||||
class DebugThread
|
||||
{
|
||||
protected thread: number;
|
||||
protected funcName: string|null;
|
||||
|
||||
public constructor(funcName: string|null = null, options: DebugOptions|string|null = null)
|
||||
{
|
||||
// Generate some random thread id
|
||||
this.thread = 1000 + Math.round(Math.random() * 8999);
|
||||
|
||||
this.funcName = funcName;
|
||||
|
||||
if (options !== null)
|
||||
this.log(options);
|
||||
}
|
||||
|
||||
public log(options: DebugOptions|string)
|
||||
{
|
||||
Debug.log({
|
||||
thread: this.thread,
|
||||
function: this.funcName ?? '',
|
||||
|
||||
...(typeof options === 'string' ? { message: options } : options)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Debug
|
||||
{
|
||||
public static readonly startedAt = new Date;
|
||||
|
||||
protected static logOutput: LogRecord[] = [];
|
||||
|
||||
protected static onLogHandler?: (record: LogRecord) => void;
|
||||
|
||||
protected static formatTime(time: number): string
|
||||
{
|
||||
const prefixTime = (time: number): string => {
|
||||
return time < 10 ? `0${time}` : time.toString();
|
||||
};
|
||||
|
||||
const date = new Date(time);
|
||||
|
||||
return `${prefixTime(date.getHours())}:${prefixTime(date.getMinutes())}:${prefixTime(date.getSeconds())}.${date.getMilliseconds()}`;
|
||||
}
|
||||
|
||||
public static log(options: DebugOptions|string)
|
||||
{
|
||||
const time = Date.now();
|
||||
|
||||
let output: LogRecord = {
|
||||
time: time,
|
||||
log: [
|
||||
`[${this.formatTime(time)}]`
|
||||
]
|
||||
};
|
||||
|
||||
if (typeof options === 'string')
|
||||
output.log[0] += ` ${options}`;
|
||||
|
||||
else
|
||||
{
|
||||
// Add thread id
|
||||
if (options.thread)
|
||||
output.log[0] += `[thread: ${options.thread}]`;
|
||||
|
||||
// Add function name
|
||||
if (options.function)
|
||||
output.log[0] += `[${options.function}]`;
|
||||
|
||||
// Add log message if it is a single line
|
||||
if (typeof options.message === 'string')
|
||||
output.log[0] += ` ${options.message}`;
|
||||
|
||||
// message: [a, b, c, d]
|
||||
else if (Array.isArray(options.message))
|
||||
options.message.forEach((line) => {
|
||||
if (line !== '')
|
||||
output.log.push(` - ${line}`);
|
||||
});
|
||||
|
||||
// message: { a: b, c: d }
|
||||
else Object.keys(options.message).forEach((key) => {
|
||||
output.log.push(` - [${key}] ${options.message[key]}`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(output.log.join('\r\n'));
|
||||
|
||||
this.logOutput.push(output);
|
||||
|
||||
if (this.onLogHandler)
|
||||
this.onLogHandler(output);
|
||||
}
|
||||
|
||||
public static merge(records: LogRecord[])
|
||||
{
|
||||
this.logOutput.unshift(...records);
|
||||
this.logOutput.sort((a, b) => a.time - b.time);
|
||||
}
|
||||
|
||||
public static getRecords(): LogRecord[]
|
||||
{
|
||||
return this.logOutput;
|
||||
}
|
||||
|
||||
public static get(): string[]
|
||||
{
|
||||
let output: string[] = [];
|
||||
|
||||
this.logOutput.forEach((record) => {
|
||||
record.log.forEach((line) => output.push(line));
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static handler(handler: (record: LogRecord) => void)
|
||||
{
|
||||
this.onLogHandler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
export default Debug;
|
||||
|
||||
export { DebugThread };
|
||||
|
||||
export type {
|
||||
DebugOptions,
|
||||
LogRecord
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import type { Params } from '../types/DiscordRPC';
|
||||
|
||||
import Process from '../neutralino/Process';
|
||||
import { Process, path } from '../../empathize';
|
||||
|
||||
declare const NL_CWD;
|
||||
|
||||
|
@ -19,10 +19,10 @@ export default class DiscordRPC
|
|||
];
|
||||
|
||||
if (params.details)
|
||||
exec = [...exec, `-d "${Process.addSlashes(params.details)}"`];
|
||||
exec = [...exec, `-d "${path.addSlashes(params.details)}"`];
|
||||
|
||||
if (params.state)
|
||||
exec = [...exec, `-s "${Process.addSlashes(params.state)}"`];
|
||||
exec = [...exec, `-s "${path.addSlashes(params.state)}"`];
|
||||
|
||||
if (params.icon)
|
||||
{
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
import type { DomainInfo } from '../types/Domain';
|
||||
|
||||
import Process from '../neutralino/Process';
|
||||
import { DebugThread } from './Debug';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
export default class Domain
|
||||
{
|
||||
public static getInfo(uri: string): Promise<DomainInfo>
|
||||
{
|
||||
const debugThread = new DebugThread('Domain.getInfo', `Getting info about uri: ${uri}`);
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
const process = await Neutralino.os.execCommand(`ping -n -4 -w 1 -B "${Process.addSlashes(uri)}"`);
|
||||
const output = process.stdOut || process.stdErr;
|
||||
|
||||
const resolveInfo = (info: DomainInfo) => {
|
||||
debugThread.log({ message: info });
|
||||
|
||||
resolve(info);
|
||||
};
|
||||
|
||||
if (output.includes('Name or service not known'))
|
||||
{
|
||||
resolveInfo({
|
||||
uri: uri,
|
||||
available: false
|
||||
});
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
const regex = /PING (.*) \(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\) .* ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}) : [\d]+\([\d]+\)/gm.exec(output);
|
||||
|
||||
if (regex !== null)
|
||||
{
|
||||
resolveInfo({
|
||||
uri: regex[1],
|
||||
remoteIp: regex[2],
|
||||
localIp: regex[3],
|
||||
available: regex[2] !== regex[3]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,230 +0,0 @@
|
|||
import Process from '../neutralino/Process';
|
||||
import { DebugThread } from './Debug';
|
||||
import fetch from './Fetch';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
class Stream
|
||||
{
|
||||
protected _id: number = -1;
|
||||
|
||||
/**
|
||||
* ID of the curl process
|
||||
*/
|
||||
public get id(): number
|
||||
{
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interval in ms between progress event calls
|
||||
*/
|
||||
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;
|
||||
|
||||
protected onStart?: () => void;
|
||||
protected onProgress?: (current: number, total: number, difference: number) => void;
|
||||
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;
|
||||
|
||||
this.debugThread = new DebugThread('Downloader/Stream', {
|
||||
message: {
|
||||
'uri': uri,
|
||||
'output file': output,
|
||||
'total size': total
|
||||
}
|
||||
});
|
||||
|
||||
if (this.onStart)
|
||||
this.onStart();
|
||||
|
||||
this.resume();
|
||||
|
||||
const updateProgress = () => {
|
||||
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;
|
||||
|
||||
if (stats.size >= this.total)
|
||||
{
|
||||
this.finished = true;
|
||||
|
||||
this.debugThread.log('Downloading finished');
|
||||
|
||||
if (this.onFinish)
|
||||
this.onFinish();
|
||||
}
|
||||
|
||||
if (!this.finished)
|
||||
setTimeout(updateProgress, this.progressInterval);
|
||||
}).catch(() => {
|
||||
if (!this.finished)
|
||||
setTimeout(updateProgress, this.progressInterval);
|
||||
});
|
||||
}
|
||||
|
||||
else setTimeout(updateProgress, this.pauseInterval);
|
||||
};
|
||||
|
||||
setTimeout(updateProgress, this.progressInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify event that will be called when the download gets started
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public start(callback: () => void)
|
||||
{
|
||||
this.onStart = callback;
|
||||
|
||||
if (this.started)
|
||||
callback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify event that will be called every [this.progressInterval] ms while the file is downloading
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public progress(callback: (current: number, total: number, difference: number) => void)
|
||||
{
|
||||
this.onProgress = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify event that will be called after the file is downloaded
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public finish(callback: () => void)
|
||||
{
|
||||
this.onFinish = callback;
|
||||
|
||||
if (this.finished)
|
||||
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
|
||||
*/
|
||||
public close(forced: boolean = false)
|
||||
{
|
||||
Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${this._id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default class Downloader
|
||||
{
|
||||
protected static streams: Stream[] = [];
|
||||
|
||||
/**
|
||||
* Download file
|
||||
*
|
||||
* @param uri file's uri to download
|
||||
* @param output relative or absolute path to the file to save it as
|
||||
*
|
||||
* @returns downloading stream
|
||||
*/
|
||||
public static async download(uri: string, output: string|null = null): Promise<Stream>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
fetch(uri).then((response) => {
|
||||
const stream = new Stream(uri, output ?? this.fileFromUri(uri), response.length!);
|
||||
|
||||
this.streams.push(stream);
|
||||
|
||||
resolve(stream);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close every open downloading stream
|
||||
*/
|
||||
public static closeStreams(forced: boolean = false)
|
||||
{
|
||||
this.streams.forEach((stream) => {
|
||||
stream.close(forced);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file name from the URI
|
||||
*/
|
||||
public static fileFromUri(uri: string): string
|
||||
{
|
||||
const file = uri.split('/').pop()!.split('#')[0].split('?')[0];
|
||||
|
||||
if (file === '')
|
||||
return 'index.html';
|
||||
|
||||
else if (`https://${file}` != uri && `http://${file}` != uri)
|
||||
return file;
|
||||
|
||||
else return 'index.html';
|
||||
}
|
||||
};
|
||||
|
||||
export { Stream };
|
|
@ -1,79 +0,0 @@
|
|||
declare const Neutralino;
|
||||
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* Requested url
|
||||
*/
|
||||
public readonly url: string;
|
||||
|
||||
/**
|
||||
* HTTP status code
|
||||
*/
|
||||
public readonly status: number|null;
|
||||
|
||||
/**
|
||||
* Content length
|
||||
*/
|
||||
public readonly length: number|null;
|
||||
|
||||
/**
|
||||
* Represents whether the response was successful (status in the range 200-299) or not
|
||||
*/
|
||||
public readonly ok: boolean;
|
||||
|
||||
public constructor(url: string, status: number|null, length: number|null)
|
||||
{
|
||||
this.url = url;
|
||||
this.status = status;
|
||||
this.length = length;
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
|
||||
this.ok = status! >= 200 && status! <= 299;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get request's body
|
||||
*
|
||||
* @param delay maximal request delay in milliseconds
|
||||
*/
|
||||
public body(delay: number|null = null): Promise<string>
|
||||
{
|
||||
return new Promise((resolve) => {
|
||||
Neutralino.os.execCommand(`curl -s -L ${delay !== null ? `-m ${delay / 1000}` : ''} "${this.url}"`)
|
||||
.then((output) => resolve(output.stdOut));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch data from the URL
|
||||
*
|
||||
* @param delay maximal request delay in milliseconds
|
||||
*/
|
||||
export default function fetch(url: string, delay: number|null = null): Promise<Response>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
let header = await Neutralino.os.execCommand(`curl -s -I -L ${delay !== null ? `-m ${delay / 1000}` : ''} "${url}"`);
|
||||
|
||||
if (header.stdOut == '')
|
||||
header = header.stdErr;
|
||||
|
||||
else header = header.stdOut;
|
||||
|
||||
header = header.split(/^HTTP\/[\d]+ /mi).pop();
|
||||
|
||||
let status: any = /^([\d]+)[\s]+$/m.exec(header);
|
||||
let length: any = /^content-length: ([\d]+)/mi.exec(header);
|
||||
|
||||
if (status !== null)
|
||||
status = parseInt(status[1]);
|
||||
|
||||
if (length !== null)
|
||||
length = parseInt(length[1]);
|
||||
|
||||
resolve(new Response(url, status, length));
|
||||
});
|
||||
};
|
||||
|
||||
export { Response };
|
|
@ -1,4 +1,4 @@
|
|||
import Process from '../neutralino/Process';
|
||||
import { path } from '../../empathize';
|
||||
|
||||
type Tag = {
|
||||
tag: string,
|
||||
|
@ -17,7 +17,7 @@ export default class Git
|
|||
public static getTags(repository: string): Promise<Tag[]>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
const output = await Neutralino.os.execCommand(`git ls-remote --tags "${Process.addSlashes(repository)}"`);
|
||||
const output = await Neutralino.os.execCommand(`git ls-remote --tags "${path.addSlashes(repository)}"`);
|
||||
|
||||
let tags: Tag[] = [];
|
||||
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
import constants from '../Constants';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
class IPCRecord
|
||||
{
|
||||
public readonly id: number;
|
||||
public readonly time: number;
|
||||
public readonly data: any;
|
||||
|
||||
public constructor(id: number, time: number, data: any)
|
||||
{
|
||||
this.id = id;
|
||||
this.time = time;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the record from the storage
|
||||
*/
|
||||
public pop(): IPCRecord
|
||||
{
|
||||
IPC.remove(this);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public get(): { id: number; time: number; data: any}
|
||||
{
|
||||
return {
|
||||
id: this.id,
|
||||
time: this.time,
|
||||
data: this.data
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default class IPC
|
||||
{
|
||||
/**
|
||||
* Read records from the "shared inter-process storage"
|
||||
*/
|
||||
public static read(): Promise<IPCRecord[]>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
Neutralino.filesystem.readFile(`${await constants.paths.launcherDir}/.ipc.json`)
|
||||
.then((data) => resolve(JSON.parse(data).map((record) => new IPCRecord(record.id, record.time, record.data))))
|
||||
.catch(() => resolve([]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write some data to the "shared inter-process storage"
|
||||
*/
|
||||
public static write(data: any): Promise<void>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
const records = await this.read();
|
||||
|
||||
records.push({
|
||||
id: Math.round(Math.random() * 100000),
|
||||
time: Date.now(),
|
||||
data: data
|
||||
} as IPCRecord);
|
||||
|
||||
await Neutralino.filesystem.writeFile(`${await constants.paths.launcherDir}/.ipc.json`, JSON.stringify(records));
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove record from the "shared inter-process storage"
|
||||
*/
|
||||
public static remove(record: IPCRecord): Promise<void>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
let records = await this.read();
|
||||
|
||||
records = records.filter((item) => item.id !== record.id || item.time !== record.time);
|
||||
|
||||
await Neutralino.filesystem.writeFile(`${await constants.paths.launcherDir}/.ipc.json`, JSON.stringify(records));
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all the record from the "shared inter-process storage"
|
||||
*/
|
||||
public static purge(): Promise<void>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
Neutralino.filesystem.removeFile(`${await constants.paths.launcherDir}/.ipc.json`)
|
||||
.then(() => resolve())
|
||||
.catch(() => resolve());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export { IPCRecord };
|
|
@ -1,32 +0,0 @@
|
|||
import type { NotificationsOptions } from '../types/Notifications';
|
||||
|
||||
import Process from '../neutralino/Process';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
export default class Notifications
|
||||
{
|
||||
/**
|
||||
* Show notification
|
||||
*/
|
||||
public static show(options: NotificationsOptions)
|
||||
{
|
||||
let command = `notify-send "${Process.addSlashes(options.title)}" "${Process.addSlashes(options.body)}"`;
|
||||
|
||||
// Specify notification icon
|
||||
if (options.icon)
|
||||
command += ` -i "${Process.addSlashes(options.icon)}"`;
|
||||
|
||||
// Specify notification duration
|
||||
if (options.duration)
|
||||
command += ` -d ${options.duration}`;
|
||||
|
||||
// Specify notification importance
|
||||
if (options.importance)
|
||||
command += ` -u ${options.importance}`;
|
||||
|
||||
Neutralino.os.execCommand(command, {
|
||||
background: true
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import { Process, Downloader, Debug, path } from '../../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import constants from '../Constants';
|
||||
import Process from '../neutralino/Process';
|
||||
import Debug, { DebugThread } from './Debug';
|
||||
import Downloader from './Downloader';
|
||||
import Runners from './Runners';
|
||||
|
||||
declare const Neutralino;
|
||||
|
@ -52,7 +52,7 @@ export default class Prefix
|
|||
.catch(() => {
|
||||
Downloader.download(constants.uri.winetricks, winetricksPath).then((stream) => {
|
||||
stream.finish(async () => {
|
||||
await Neutralino.os.execCommand(`chmod +x "${Process.addSlashes(winetricksPath)}"`);
|
||||
await Neutralino.os.execCommand(`chmod +x "${path.addSlashes(winetricksPath)}"`);
|
||||
|
||||
resolve(winetricksPath);
|
||||
});
|
||||
|
@ -64,12 +64,12 @@ export default class Prefix
|
|||
/**
|
||||
* Create wine prefix using the current selected wine
|
||||
*
|
||||
* @param path folder to create prefix in
|
||||
* @param folder folder to create prefix in
|
||||
* @param progress function that will be called with every creation step
|
||||
*
|
||||
* @returns false if there's no selected wine version. Otherwise true
|
||||
*/
|
||||
public static create(path: string, progress?: (output: string, current: number, total: number) => void): Promise<boolean>
|
||||
public static create(folder: string, progress?: (output: string, current: number, total: number) => void): Promise<boolean>
|
||||
{
|
||||
const debugThread = new DebugThread('Prefix.create', 'Creating wine prefix');
|
||||
|
||||
|
@ -108,11 +108,11 @@ export default class Prefix
|
|||
this.getWinetricks().then(async (winetricks) => {
|
||||
let installationProgress = 0;
|
||||
|
||||
const process = await Process.run(`"${Process.addSlashes(winetricks)}" corefonts usetakefocus=n`, {
|
||||
const process = await Process.run(`"${path.addSlashes(winetricks)}" corefonts usetakefocus=n`, {
|
||||
env: {
|
||||
WINE: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`,
|
||||
WINESERVER: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wineserver}`,
|
||||
WINEPREFIX: path
|
||||
WINEPREFIX: folder
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@ import type {
|
|||
RunnerFamily
|
||||
} from '../types/Runners';
|
||||
|
||||
import { Configs, Process, path } from '../../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import constants from '../Constants';
|
||||
import Configs from '../Configs';
|
||||
import AbstractInstaller from './AbstractInstaller';
|
||||
import Process from '../neutralino/Process';
|
||||
import { DebugThread } from './Debug';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
@ -149,7 +149,7 @@ class Runners
|
|||
const name = typeof runner !== 'string' ?
|
||||
runner.name : runner;
|
||||
|
||||
Process.run(`rm -rf "${Process.addSlashes(await constants.paths.runnersDir + '/' + name)}"`)
|
||||
Process.run(`rm -rf "${path.addSlashes(await constants.paths.runnersDir + '/' + name)}"`)
|
||||
.then((process) => {
|
||||
process.finish(() => {
|
||||
debugThread.log('Runner deleted');
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
type callback = () => any;
|
||||
|
||||
type PromiseOptions = {
|
||||
callbacks: callback[]|Promise<any>[];
|
||||
|
||||
/**
|
||||
* If true, then all the callbacks will be called
|
||||
* at the same time and promisify will be resolved
|
||||
* when all of them have finished
|
||||
*
|
||||
* Otherwise, callbacks will be called one after the other
|
||||
* and promisify will be resolved with the last one
|
||||
*/
|
||||
callAtOnce?: boolean;
|
||||
|
||||
/**
|
||||
* [callAtOnce: true] updates interval in ms
|
||||
*
|
||||
* @default 100
|
||||
*/
|
||||
interval?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a promise from the provided function(s) and run it(them)
|
||||
*/
|
||||
export default function promisify(callback: callback|Promise<any>|PromiseOptions): Promise<any>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
// promisify(() => { ... })
|
||||
if (typeof callback === 'function')
|
||||
resolve(await Promise.resolve(callback()));
|
||||
|
||||
// promisify(new Promise(...))
|
||||
else if (typeof callback['then'] === 'function')
|
||||
resolve(await callback);
|
||||
|
||||
// promisify({ callbacks: [ ... ] })
|
||||
else
|
||||
{
|
||||
let outputs = {};
|
||||
|
||||
// @ts-expect-error
|
||||
if (callback.callAtOnce)
|
||||
{
|
||||
// @ts-expect-error
|
||||
let remained = callback.callbacks.length;
|
||||
|
||||
// @ts-expect-error
|
||||
for (let i = 0; i < callback.callbacks.length; ++i) // @ts-expect-error
|
||||
promisify(callback.callbacks[i]).then((output) => {
|
||||
outputs[i] = output;
|
||||
|
||||
--remained;
|
||||
});
|
||||
|
||||
const updater = () => {
|
||||
if (remained > 0) // @ts-expect-error
|
||||
setTimeout(updater, callback.interval ?? 100);
|
||||
|
||||
else resolve(outputs);
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
setTimeout(updater, callback.interval ?? 100);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// @ts-expect-error
|
||||
for (let i = 0; i < callback.callbacks.length; ++i) // @ts-expect-error
|
||||
outputs[i] = await promisify(callback.callbacks[i]);
|
||||
|
||||
resolve(outputs);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export type {
|
||||
PromiseOptions,
|
||||
callback
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
import { fetch } from '../../empathize';
|
||||
|
||||
import constants from '../Constants';
|
||||
import fetch from '../core/Fetch';
|
||||
import Locales from './Locales';
|
||||
|
||||
export default class Background
|
||||
|
|
|
@ -2,9 +2,9 @@ import { dictionary, locale } from 'svelte-i18n';
|
|||
|
||||
import YAML from 'yaml';
|
||||
|
||||
import { promisify, Configs } from '../../empathize';
|
||||
|
||||
import constants from '../Constants';
|
||||
import promisify from '../core/promisify';
|
||||
import Configs from '../Configs';
|
||||
|
||||
type AvailableLocales =
|
||||
| 'en-us'
|
||||
|
|
|
@ -2,9 +2,9 @@ import YAML from 'yaml';
|
|||
|
||||
import type { Shader } from '../types/Shaders';
|
||||
|
||||
import Configs from '../Configs';
|
||||
import { Configs, promisify } from '../../empathize';
|
||||
|
||||
import constants from '../Constants';
|
||||
import promisify from '../core/promisify';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
|
|
@ -3,22 +3,20 @@ import { dictionary, locale } from 'svelte-i18n';
|
|||
|
||||
import semver from 'semver';
|
||||
|
||||
import type { LauncherState } from '../types/Launcher';
|
||||
import { Windows, Debug, IPC, Notification } from '../../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import Window from '../neutralino/Window';
|
||||
import type { LauncherState } from '../types/Launcher';
|
||||
|
||||
import Launcher from '../Launcher';
|
||||
import Game from '../Game';
|
||||
import Patch from '../Patch';
|
||||
import Voice from '../Voice';
|
||||
import Runners from '../core/Runners';
|
||||
import Debug, { DebugThread } from '../core/Debug';
|
||||
import DXVK from '../core/DXVK';
|
||||
import IPC from '../core/IPC';
|
||||
import Locales from './Locales';
|
||||
import Git from '../core/Git';
|
||||
import constants from '../Constants';
|
||||
import Notifications from '../core/Notifications';
|
||||
|
||||
export default class State
|
||||
{
|
||||
|
@ -97,8 +95,8 @@ export default class State
|
|||
this.update().then(async () => {
|
||||
IPC.write('launcher-loaded');
|
||||
|
||||
await Window.current.show();
|
||||
await Window.current.center(1280, 700);
|
||||
await Windows.current.show();
|
||||
// FIXME: await Windows.current.center(1280, 700);
|
||||
|
||||
// Check for new versions of the launcher
|
||||
Git.getTags(constants.uri.launcher).then((tags) => {
|
||||
|
@ -110,7 +108,7 @@ export default class State
|
|||
|
||||
const locales = (currentDictionary[currentLocale ?? 'en-us'] ?? currentDictionary['en-us'])['launcher']!['update'] as object;
|
||||
|
||||
Notifications.show({
|
||||
Notification.show({
|
||||
title: locales['title'].replace('{from}', Launcher.version).replace('{to}', tag.tag),
|
||||
body: locales['body'].replace('{repository}', constants.uri.launcher),
|
||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`
|
||||
|
@ -128,7 +126,7 @@ export default class State
|
|||
|
||||
else
|
||||
{
|
||||
Window.current.setSize({
|
||||
Windows.current.setSize({
|
||||
width: 1280 + (1280 - window.innerWidth),
|
||||
height: 700 + (700 - window.innerHeight),
|
||||
resizable: false
|
||||
|
@ -400,7 +398,7 @@ export default class State
|
|||
{
|
||||
state = 'game-launch-available';
|
||||
|
||||
Notifications.show({
|
||||
Notification.show({
|
||||
title: 'An Anime Game Launcher',
|
||||
body: 'All the patch repositories are not available. You\'ll be able to run the game, but launcher can\'t be sure is it patched properly',
|
||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Notification } from '../../../empathize';
|
||||
|
||||
import Launcher from '../../Launcher';
|
||||
import Patch from '../../Patch';
|
||||
import Notifications from '../../core/Notifications';
|
||||
import constants from '../../Constants';
|
||||
|
||||
export default (launcher: Launcher): Promise<void> => {
|
||||
|
@ -8,7 +9,7 @@ export default (launcher: Launcher): Promise<void> => {
|
|||
// Show an error notification if xdelta3 package is not installed
|
||||
if (!await Launcher.isPackageAvailable('xdelta3'))
|
||||
{
|
||||
Notifications.show({
|
||||
Notification.show({
|
||||
title: 'An Anime Game Launcher',
|
||||
body: 'You must download xdelta3 package to apply the patch',
|
||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
||||
|
@ -76,7 +77,7 @@ export default (launcher: Launcher): Promise<void> => {
|
|||
// If for some reasons patch wasn't applied successfully
|
||||
if (!result)
|
||||
{
|
||||
Notifications.show({
|
||||
Notification.show({
|
||||
title: 'An Anime Game Launcher',
|
||||
body: 'Patch wasn\'t applied successfully. Please, check your log file to find a reason of it, or ask someone in our discord server',
|
||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`
|
||||
|
@ -89,7 +90,7 @@ export default (launcher: Launcher): Promise<void> => {
|
|||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
Notifications.show({
|
||||
Notification.show({
|
||||
title: 'An Anime Game Launcher',
|
||||
body: 'All the patch repositories are not available. You\'ll be able to run the game, but launcher can\'t be sure is it patched properly',
|
||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
||||
|
|
|
@ -3,10 +3,11 @@ import { _ } from 'svelte-i18n';
|
|||
|
||||
import type Launcher from '../../Launcher';
|
||||
|
||||
import { Debug } from '../../../empathize';
|
||||
|
||||
import Game from '../../Game';
|
||||
import Prefix from '../../core/Prefix';
|
||||
import constants from '../../Constants';
|
||||
import Debug from '../../core/Debug';
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@ import { _ } from 'svelte-i18n';
|
|||
import type Launcher from '../../Launcher';
|
||||
import type { VoiceLang } from '../../types/Voice';
|
||||
|
||||
import { promisify } from '../../../empathize';
|
||||
|
||||
import Voice from '../../Voice';
|
||||
import promisify from '../../core/promisify';
|
||||
import Game from '../../Game';
|
||||
|
||||
export default (launcher: Launcher): Promise<void> => {
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import Process from '../../neutralino/Process';
|
||||
import Window from '../../neutralino/Window';
|
||||
import { Process, Windows, Configs, Notification, path } from '../../../empathize';
|
||||
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||
|
||||
import Launcher from '../../Launcher';
|
||||
import Configs from '../../Configs';
|
||||
import constants from '../../Constants';
|
||||
import { DebugThread } from '../../core/Debug';
|
||||
import Notifications from '../../core/Notifications';
|
||||
import Runners from '../../core/Runners';
|
||||
import Game from '../../Game';
|
||||
|
||||
|
@ -20,7 +17,7 @@ export default (launcher: Launcher): Promise<void> => {
|
|||
// If telemetry servers are not disabled
|
||||
if (!telemetry)
|
||||
{
|
||||
Notifications.show({
|
||||
Notification.show({
|
||||
title: 'An Anime Game Launcher',
|
||||
body: 'Telemetry servers are not disabled',
|
||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
||||
|
@ -33,7 +30,7 @@ export default (launcher: Launcher): Promise<void> => {
|
|||
// Otherwise run the game
|
||||
else
|
||||
{
|
||||
Window.current.hide();
|
||||
Windows.current.hide();
|
||||
|
||||
launcher.updateDiscordRPC('in-game');
|
||||
|
||||
|
@ -139,7 +136,7 @@ export default (launcher: Launcher): Promise<void> => {
|
|||
else console.warn(`GPU ${LauncherLib.getConfig('gpu')} not found. Launching on the default GPU`);
|
||||
}*/
|
||||
|
||||
let command = `"${Process.addSlashes(wineExeutable)}" ${await Configs.get('fps_unlocker') ? 'unlockfps.bat' : 'launcher.bat'}`;
|
||||
let command = `"${path.addSlashes(wineExeutable)}" ${await Configs.get('fps_unlocker') ? 'unlockfps.bat' : 'launcher.bat'}`;
|
||||
|
||||
/**
|
||||
* Gamemode integration
|
||||
|
@ -208,8 +205,8 @@ export default (launcher: Launcher): Promise<void> => {
|
|||
{
|
||||
const stopTime = Date.now();
|
||||
|
||||
Window.current.show();
|
||||
Window.current.center(1280, 700);
|
||||
Windows.current.show();
|
||||
// FIXME: Windows.current.center(1280, 700);
|
||||
|
||||
launcher.updateDiscordRPC('in-launcher');
|
||||
launcher.tray.hide();
|
||||
|
@ -218,7 +215,7 @@ export default (launcher: Launcher): Promise<void> => {
|
|||
Configs.get('purge_logs.game').then(async (purge_logs) => {
|
||||
if (purge_logs)
|
||||
{
|
||||
const gameDir = Process.addSlashes(await constants.paths.gameDir);
|
||||
const gameDir = path.addSlashes(await constants.paths.gameDir);
|
||||
|
||||
// Delete .log files (e.g. "ZFGameBrowser_xxxx.log")
|
||||
Neutralino.os.execCommand(`find "${gameDir}" -maxdepth 1 -type f -name "*.log" -delete`);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import type Launcher from '../../Launcher';
|
||||
import type { VoiceLang } from '../../types/Voice';
|
||||
|
||||
import { promisify } from '../../../empathize';
|
||||
|
||||
import Voice from '../../Voice';
|
||||
import promisify from '../../core/promisify';
|
||||
|
||||
export default (launcher: Launcher): Promise<void> => {
|
||||
return new Promise(async (resolve) => {
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
import constants from '../Constants';
|
||||
import Debug, { DebugThread } from "../core/Debug";
|
||||
|
||||
declare const Neutralino;
|
||||
declare const NL_CWD;
|
||||
|
||||
type ProcessOptions = {
|
||||
/**
|
||||
* Environment variables
|
||||
*/
|
||||
env?: object;
|
||||
|
||||
/**
|
||||
* Current working directory for the running process
|
||||
*/
|
||||
cwd?: string;
|
||||
|
||||
/**
|
||||
* Interval between tries to find started process id
|
||||
*
|
||||
* @default 50
|
||||
*/
|
||||
childInterval?: number;
|
||||
};
|
||||
|
||||
class Process
|
||||
{
|
||||
/**
|
||||
* Process ID
|
||||
*/
|
||||
public readonly id: number;
|
||||
|
||||
/**
|
||||
* Interval in ms between process status update
|
||||
*
|
||||
* null if you don't want to update process status
|
||||
*
|
||||
* @default 200
|
||||
*/
|
||||
public runningInterval: number|null = 200;
|
||||
|
||||
/**
|
||||
* Interval in ms between process output update
|
||||
*
|
||||
* null if you don't want to update process output
|
||||
*
|
||||
* @default 500
|
||||
*/
|
||||
public outputInterval: number|null = 500;
|
||||
|
||||
protected outputFile: string|null;
|
||||
protected outputOffset: number = 0;
|
||||
|
||||
protected _finished: boolean = false;
|
||||
|
||||
/**
|
||||
* Whether the process was finished
|
||||
*/
|
||||
public get finished(): boolean
|
||||
{
|
||||
return this._finished;
|
||||
};
|
||||
|
||||
protected onOutput?: (output: string, process: Process) => void;
|
||||
protected onFinish?: (process: Process) => void;
|
||||
|
||||
public constructor(pid: number, outputFile: string|null = null)
|
||||
{
|
||||
const debugThread = new DebugThread('Process/Stream', `Opened process ${pid} stream`);
|
||||
|
||||
this.id = pid;
|
||||
this.outputFile = outputFile;
|
||||
|
||||
const updateStatus = () => {
|
||||
this.running().then((running) => {
|
||||
// The process is still running
|
||||
if (running)
|
||||
{
|
||||
if (this.runningInterval)
|
||||
setTimeout(updateStatus, this.runningInterval);
|
||||
}
|
||||
|
||||
// Otherwise the process was stopped
|
||||
else
|
||||
{
|
||||
this._finished = true;
|
||||
|
||||
debugThread.log('Process stopped');
|
||||
|
||||
if (this.onFinish)
|
||||
this.onFinish(this);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (this.runningInterval)
|
||||
setTimeout(updateStatus, this.runningInterval);
|
||||
|
||||
if (this.outputFile)
|
||||
{
|
||||
const updateOutput = () => {
|
||||
Neutralino.filesystem.readFile(this.outputFile)
|
||||
.then((output: string) => {
|
||||
if (this.onOutput)
|
||||
this.onOutput(output.substring(this.outputOffset), this);
|
||||
|
||||
this.outputOffset = output.length;
|
||||
|
||||
if (this._finished)
|
||||
{
|
||||
if (output !== '')
|
||||
{
|
||||
debugThread.log({
|
||||
message: [
|
||||
'Process output:',
|
||||
...output.split(/\r\n|\r|\n/)
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
Neutralino.filesystem.removeFile(this.outputFile);
|
||||
}
|
||||
|
||||
else if (this.outputInterval)
|
||||
setTimeout(updateOutput, this.outputInterval);
|
||||
})
|
||||
.catch(() => {
|
||||
if (this.outputInterval && !this._finished)
|
||||
setTimeout(updateOutput, this.outputInterval);
|
||||
});
|
||||
};
|
||||
|
||||
if (this.outputInterval)
|
||||
setTimeout(updateOutput, this.outputInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify callback to run when the process will be finished
|
||||
*/
|
||||
public finish(callback: (process: Process) => void)
|
||||
{
|
||||
this.onFinish = callback;
|
||||
|
||||
if (this._finished)
|
||||
callback(this);
|
||||
|
||||
// If user stopped process status auto-checking
|
||||
// then we should check it manually when this method was called
|
||||
else if (this.runningInterval === null)
|
||||
{
|
||||
this.running().then((running) => {
|
||||
if (!running)
|
||||
{
|
||||
this._finished = true;
|
||||
|
||||
callback(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public output(callback: (output: string, process: Process) => void)
|
||||
{
|
||||
this.onOutput = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill process
|
||||
*/
|
||||
public kill(forced: boolean = false): Promise<void>
|
||||
{
|
||||
Neutralino.filesystem.removeFile(this.outputFile);
|
||||
|
||||
return Process.kill(this.id, forced);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the process is running
|
||||
*
|
||||
* This method doesn't call onFinish event
|
||||
*/
|
||||
public running(): Promise<boolean>
|
||||
{
|
||||
return new Promise((resolve) => {
|
||||
Neutralino.os.execCommand(`ps -p ${this.id} -S`).then((output) => {
|
||||
resolve(output.stdOut.includes(this.id) && !output.stdOut.includes('Z '));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run shell command
|
||||
*/
|
||||
public static run(command: string, options: ProcessOptions = {}): Promise<Process>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
const tmpFile = `${await constants.paths.tempDir}/${10000 + Math.round(Math.random() * 89999)}.tmp`;
|
||||
|
||||
// Set env variables
|
||||
if (options.env)
|
||||
for (const key of Object.keys(options.env))
|
||||
command = `${key}="${this.addSlashes(options.env![key].toString())}" ${command}`;
|
||||
|
||||
// Set output redirection to the temp file
|
||||
command = `${command} > "${this.addSlashes(tmpFile)}" 2>&1`;
|
||||
|
||||
// Set current working directory
|
||||
if (options.cwd)
|
||||
command = `cd "${this.addSlashes(options.cwd)}" && ${command}`;
|
||||
|
||||
// And run the command
|
||||
const process = await Neutralino.os.execCommand(command, {
|
||||
background: true
|
||||
});
|
||||
|
||||
const childFinder = async () => {
|
||||
const childProcess = await Neutralino.os.execCommand(`pgrep -P ${process.pid}`);
|
||||
|
||||
// Child wasn't found
|
||||
if (childProcess.stdOut == '')
|
||||
setTimeout(childFinder, options.childInterval ?? 50);
|
||||
|
||||
// Otherwise return its id
|
||||
else
|
||||
{
|
||||
const processId = parseInt(childProcess.stdOut.substring(0, childProcess.stdOut.length - 1));
|
||||
|
||||
Debug.log({
|
||||
function: 'Process.run',
|
||||
message: {
|
||||
'running command': command,
|
||||
'cwd': options.cwd,
|
||||
'initial process id': process.pid,
|
||||
'real process id': processId,
|
||||
...options.env
|
||||
}
|
||||
});
|
||||
|
||||
resolve(new Process(processId, tmpFile));
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(childFinder, options.childInterval ?? 50);
|
||||
});
|
||||
}
|
||||
|
||||
public static kill(id: number, forced: boolean = false): Promise<void>
|
||||
{
|
||||
return new Promise((resolve) => {
|
||||
Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${id}`).then(() => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace '\a\b' to '\\a\\b'
|
||||
* And replace ''' to '\''
|
||||
*/
|
||||
public static addSlashes(str: string): string
|
||||
{
|
||||
return str.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
||||
}
|
||||
}
|
||||
|
||||
export type { ProcessOptions };
|
||||
|
||||
export default Process;
|
|
@ -1,120 +0,0 @@
|
|||
declare const Neutralino;
|
||||
|
||||
type Item = {
|
||||
/**
|
||||
* Item text
|
||||
*/
|
||||
text: string;
|
||||
|
||||
/**
|
||||
* Item id
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* Whether the item disabled or not
|
||||
*
|
||||
* If yes, then it will be a string
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Is this item a checkbox or not
|
||||
*/
|
||||
checked?: boolean;
|
||||
|
||||
/**
|
||||
* Event on click
|
||||
*
|
||||
* If specified, then will generate random
|
||||
* item id if it is not specified
|
||||
*/
|
||||
click?: (item: Item) => void;
|
||||
};
|
||||
|
||||
Neutralino.events.on('trayMenuItemClicked', (item) => {
|
||||
for (const tray of Tray.trays)
|
||||
for (const trayItem of tray.items)
|
||||
if (trayItem.id === item.detail.id)
|
||||
{
|
||||
if (trayItem.click)
|
||||
{
|
||||
trayItem.click({
|
||||
id: item.detail.id,
|
||||
text: item.detail.text,
|
||||
disabled: item.detail['isDisabled'],
|
||||
checked: item.detail['isChecked'],
|
||||
click: trayItem.click
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
export default class Tray
|
||||
{
|
||||
public static trays: Tray[] = [];
|
||||
|
||||
public icon: string;
|
||||
|
||||
protected _items: Item[] = [];
|
||||
|
||||
public get items(): Item[]
|
||||
{
|
||||
return this._items.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
text: item.text,
|
||||
disabled: item['isDisabled'],
|
||||
checked: item['isChecked'],
|
||||
click: item.click
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public set items(items: Item[])
|
||||
{
|
||||
this._items = items.map((item) => {
|
||||
if (item.id === undefined && item.click !== undefined)
|
||||
item.id = 'click:' + Math.random().toString().substring(2);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
text: item.text,
|
||||
isDisabled: item.disabled,
|
||||
isChecked: item.checked,
|
||||
click: item.click
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public constructor(icon: string, items: Item[] = [])
|
||||
{
|
||||
this.icon = icon;
|
||||
this.items = items;
|
||||
|
||||
Tray.trays.push(this);
|
||||
}
|
||||
|
||||
public update(items: Item[]|null = null): Promise<void>
|
||||
{
|
||||
if (items !== null)
|
||||
this.items = items;
|
||||
|
||||
return Neutralino.os.setTray({
|
||||
icon: this.icon,
|
||||
menuItems: this._items
|
||||
});
|
||||
}
|
||||
|
||||
public hide(): Promise<void>
|
||||
{
|
||||
return Neutralino.os.setTray({
|
||||
icon: this.icon,
|
||||
menuItems: []
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export type { Item };
|
|
@ -1,82 +0,0 @@
|
|||
type WindowSize = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
minWidth?: number;
|
||||
minHeight?: number;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
resizable?: boolean;
|
||||
};
|
||||
|
||||
type WindowOptions = WindowSize & {
|
||||
title?: string;
|
||||
icon?: string;
|
||||
fullScreen?: boolean;
|
||||
alwaysOnTop?: boolean;
|
||||
enableInspector?: boolean;
|
||||
borderless?: boolean;
|
||||
maximize?: boolean;
|
||||
hidden?: boolean;
|
||||
maximizable?: boolean;
|
||||
exitProcessOnClose?: boolean;
|
||||
processArgs?: string;
|
||||
};
|
||||
|
||||
type WindowOpenResult = {
|
||||
status: boolean;
|
||||
data?: {
|
||||
pid: number;
|
||||
stdOut: string;
|
||||
stdErr: string;
|
||||
exitCode: number;
|
||||
};
|
||||
};
|
||||
|
||||
declare const Neutralino;
|
||||
|
||||
class Window
|
||||
{
|
||||
public static get current(): any
|
||||
{
|
||||
return {
|
||||
...Neutralino.window,
|
||||
|
||||
center(windowWidth: number, windowHeight: number)
|
||||
{
|
||||
Neutralino.window.move(Math.round((window.screen.width - windowWidth) / 2), Math.round((window.screen.height - windowHeight) / 2));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static open(name: string, options: WindowOptions = {}): Promise<WindowOpenResult>
|
||||
{
|
||||
return new Promise(async (resolve) => {
|
||||
const status = await Neutralino.window.create(`/${name}.html`, {
|
||||
width: 600,
|
||||
height: 400,
|
||||
enableInspector: false,
|
||||
exitProcessOnClose: true,
|
||||
|
||||
...options,
|
||||
|
||||
// So basically you should display the window manually
|
||||
// with Window.current.show() when your app will load
|
||||
// all its content there
|
||||
hidden: true
|
||||
});
|
||||
|
||||
resolve({
|
||||
status: status !== undefined,
|
||||
data: status
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type {
|
||||
WindowSize,
|
||||
WindowOptions,
|
||||
WindowOpenResult
|
||||
};
|
||||
|
||||
export default Window;
|
27
src/ts/types/Archive.d.ts
vendored
27
src/ts/types/Archive.d.ts
vendored
|
@ -1,27 +0,0 @@
|
|||
type ArchiveType =
|
||||
| 'tar'
|
||||
| 'zip'
|
||||
| null;
|
||||
|
||||
type Size = {
|
||||
compressed?: number | null;
|
||||
uncompressed?: number | null;
|
||||
};
|
||||
|
||||
type File = {
|
||||
path: string;
|
||||
size: Size;
|
||||
};
|
||||
|
||||
type ArchiveInfo = {
|
||||
size: Size;
|
||||
type: ArchiveType;
|
||||
files: File[];
|
||||
};
|
||||
|
||||
export type {
|
||||
ArchiveType,
|
||||
Size,
|
||||
File,
|
||||
ArchiveInfo
|
||||
};
|
23
src/ts/types/Debug.d.ts
vendored
23
src/ts/types/Debug.d.ts
vendored
|
@ -1,23 +0,0 @@
|
|||
type DebugOptions = {
|
||||
/**
|
||||
* Some random-generated thread id
|
||||
*/
|
||||
thread?: number;
|
||||
|
||||
/**
|
||||
* Some function name
|
||||
*/
|
||||
function?: string;
|
||||
|
||||
/**
|
||||
* Some log message
|
||||
*/
|
||||
message: string|string[]|object;
|
||||
};
|
||||
|
||||
type LogRecord = {
|
||||
time: number;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export type { DebugOptions, LogRecord };
|
8
src/ts/types/Domain.d.ts
vendored
8
src/ts/types/Domain.d.ts
vendored
|
@ -1,8 +0,0 @@
|
|||
type DomainInfo = {
|
||||
uri: string;
|
||||
remoteIp?: string;
|
||||
localIp?: string;
|
||||
available: boolean;
|
||||
};
|
||||
|
||||
export type { DomainInfo };
|
24
src/ts/types/Notifications.d.ts
vendored
24
src/ts/types/Notifications.d.ts
vendored
|
@ -1,24 +0,0 @@
|
|||
type NotificationsOptions = {
|
||||
title: string;
|
||||
body: string;
|
||||
|
||||
/**
|
||||
* Icon name or path
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
/**
|
||||
* Number of seconds this notification
|
||||
* will be visible
|
||||
*/
|
||||
duration?: number;
|
||||
|
||||
/**
|
||||
* Importance of the notification
|
||||
*
|
||||
* @default "normal"
|
||||
*/
|
||||
importance?: 'low' | 'normal' | 'critical';
|
||||
};
|
||||
|
||||
export type { NotificationsOptions };
|
|
@ -2,12 +2,12 @@
|
|||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"resolveJsonModule": true,
|
||||
"useDefineForClassFields": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
|
|
Loading…
Reference in a new issue