mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2024-12-30 05:28:15 +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"
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@empathize/framework": "^1.3.5",
|
||||||
"js-md5": "^0.7.3",
|
"js-md5": "^0.7.3",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
"svelte-i18n": "^3.3.13",
|
"svelte-i18n": "^3.3.13",
|
||||||
|
@ -18,12 +19,12 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutralinojs/neu": "^9.1.1",
|
"@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",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"@types/js-md5": "^0.4.3",
|
"@types/js-md5": "^0.4.3",
|
||||||
"neutralino-appimage-bundler": "^1.3.2",
|
"neutralino-appimage-bundler": "^1.3.2",
|
||||||
"sass": "^1.49.0",
|
"sass": "^1.49.0",
|
||||||
"svelte": "^3.46.2",
|
"svelte": "^3.46.3",
|
||||||
"svelte-check": "^2.3.0",
|
"svelte-check": "^2.3.0",
|
||||||
"svelte-preprocess": "^4.10.2",
|
"svelte-preprocess": "^4.10.2",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
|
import { Configs } from '../empathize';
|
||||||
|
|
||||||
export let active: boolean = false;
|
export let active: boolean = false;
|
||||||
export let disabled: boolean = false;
|
export let disabled: boolean = false;
|
||||||
|
|
||||||
|
@ -12,8 +14,6 @@
|
||||||
|
|
||||||
import Checkmark from '../assets/svgs/checkmark.svg';
|
import Checkmark from '../assets/svgs/checkmark.svg';
|
||||||
|
|
||||||
import Configs from '../ts/Configs';
|
|
||||||
|
|
||||||
Configs.get(prop).then((value) => active = value as boolean);
|
Configs.get(prop).then((value) => active = value as boolean);
|
||||||
|
|
||||||
function updateCheckbox()
|
function updateCheckbox()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
import Configs from '../ts/Configs';
|
import { Configs } from '../empathize';
|
||||||
|
|
||||||
export let visible: boolean = false;
|
export let visible: boolean = false;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
import Configs from '../ts/Configs';
|
import { Configs } from '../empathize';
|
||||||
|
|
||||||
export let prop: string = '';
|
export let prop: string = '';
|
||||||
export let lang: string = '';
|
export let lang: string = '';
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
import Configs from '../ts/Configs';
|
import { Configs } from '../empathize';
|
||||||
|
|
||||||
import Button from './Button.svelte';
|
import Button from './Button.svelte';
|
||||||
|
|
||||||
let last_id = 0, variables = {}, selected;
|
let last_id = 0, variables = {}, selected;
|
||||||
|
|
||||||
Configs.get('env').then((env) => {
|
Configs.get('env').then((env) => {
|
||||||
|
if (env)
|
||||||
|
{
|
||||||
for (const key of Object.keys(env as object))
|
for (const key of Object.keys(env as object))
|
||||||
{
|
{
|
||||||
variables[last_id++] = {
|
variables[last_id++] = {
|
||||||
|
@ -15,6 +17,7 @@
|
||||||
value: env![key]
|
value: env![key]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateEnv = () => {
|
const updateEnv = () => {
|
||||||
|
@ -29,6 +32,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
{#if Object.keys(variables).length > 0}
|
||||||
<table class="table properties-table" style="margin-top: 16px">
|
<table class="table properties-table" style="margin-top: 16px">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{$_('settings.environment.items.table.name')}</th>
|
<th>{$_('settings.environment.items.table.name')}</th>
|
||||||
|
@ -49,6 +53,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</table>
|
</table>
|
||||||
|
{:else}
|
||||||
|
<p>There're no variables here</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div style="margin-top: 16px">
|
<div style="margin-top: 16px">
|
||||||
<Button lang="settings.environment.items.buttons.add" click={() => variables[last_id++] = { key: '', value: '' }} />
|
<Button lang="settings.environment.items.buttons.add" click={() => variables[last_id++] = { key: '', value: '' }} />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { _ } from 'svelte-i18n';
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
import Configs from '../ts/Configs';
|
import { Configs } from '../empathize';
|
||||||
|
|
||||||
export let prop: string = '';
|
export let prop: string = '';
|
||||||
export let lang: 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 constants from './ts/Constants';
|
||||||
import promisify from './ts/core/promisify';
|
|
||||||
|
|
||||||
promisify(async () => {
|
promisify(async () => {
|
||||||
Configs.defaults({
|
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 { onMount } from 'svelte';
|
||||||
import { _, locale } from 'svelte-i18n';
|
import { _, locale } from 'svelte-i18n';
|
||||||
|
|
||||||
import Window from './ts/neutralino/Window';
|
import { Windows, path, Archive, Debug, Downloader, IPC, Configs } from './empathize';
|
||||||
import Process from './ts/neutralino/Process';
|
|
||||||
|
|
||||||
import Launcher from './ts/Launcher';
|
import Launcher from './ts/Launcher';
|
||||||
import constants from './ts/Constants';
|
import constants from './ts/Constants';
|
||||||
import Game from './ts/Game';
|
import Game from './ts/Game';
|
||||||
import Background from './ts/launcher/Background';
|
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 Gear from './assets/images/gear.png';
|
||||||
import GearActive from './assets/images/gear-active.png';
|
import GearActive from './assets/images/gear-active.png';
|
||||||
|
@ -36,7 +30,7 @@
|
||||||
const launcher = new Launcher(onMount);
|
const launcher = new Launcher(onMount);
|
||||||
|
|
||||||
Neutralino.events.on('ready', () => {
|
Neutralino.events.on('ready', () => {
|
||||||
Window.open('splash', {
|
Windows.open('splash', {
|
||||||
title: 'Splash',
|
title: 'Splash',
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 400,
|
height: 400,
|
||||||
|
@ -60,13 +54,13 @@
|
||||||
await launcher.rpc.stop(true);
|
await launcher.rpc.stop(true);
|
||||||
|
|
||||||
// Remove .tmp files from the temp folder
|
// 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
|
// Remove old launcher's log files
|
||||||
const purge_logs = await Configs.get('purge_logs.launcher') as string|null;
|
const purge_logs = await Configs.get('purge_logs.launcher') as string|null;
|
||||||
|
|
||||||
if (purge_logs !== null && purge_logs[purge_logs.length - 1] == 'd')
|
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
|
// Save logs
|
||||||
const log = Debug.get().join('\r\n');
|
const log = Debug.get().join('\r\n');
|
||||||
|
@ -103,7 +97,7 @@
|
||||||
* Update launcher's title
|
* Update launcher's title
|
||||||
*/
|
*/
|
||||||
Game.latest.then((game) => {
|
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 { onMount } from 'svelte';
|
||||||
import { _, locale, locales } from 'svelte-i18n';
|
import { _, locale, locales } from 'svelte-i18n';
|
||||||
|
|
||||||
import Window from './ts/neutralino/Window';
|
import { Windows, Configs, Debug, IPC, Process, path } from './empathize';
|
||||||
import Process from './ts/neutralino/Process';
|
|
||||||
|
|
||||||
import constants from './ts/Constants';
|
import constants from './ts/Constants';
|
||||||
import Configs from './ts/Configs';
|
|
||||||
import Launcher from './ts/Launcher';
|
import Launcher from './ts/Launcher';
|
||||||
import FPSUnlock from './ts/FPSUnlock';
|
import FPSUnlock from './ts/FPSUnlock';
|
||||||
|
|
||||||
import Debug from './ts/core/Debug';
|
|
||||||
import IPC from './ts/core/IPC';
|
|
||||||
import Runners from './ts/core/Runners';
|
import Runners from './ts/core/Runners';
|
||||||
|
|
||||||
import Button from './components/Button.svelte';
|
import Button from './components/Button.svelte';
|
||||||
|
@ -41,72 +36,6 @@
|
||||||
|
|
||||||
launcherLocales = launcherLocales;
|
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
|
* Some components stuff
|
||||||
*/
|
*/
|
||||||
|
@ -203,8 +132,8 @@
|
||||||
|
|
||||||
// Do some stuff when all the content will be loaded
|
// Do some stuff when all the content will be loaded
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await Window.current.show();
|
await Windows.current.show();
|
||||||
await Window.current.center(900, 600);
|
// FIXME: await Windows.current.center(900, 600);
|
||||||
|
|
||||||
// This thing will fix window resizing
|
// This thing will fix window resizing
|
||||||
// in several cases (wayland + gnome + custom theme)
|
// in several cases (wayland + gnome + custom theme)
|
||||||
|
@ -214,7 +143,7 @@
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Window.current.setSize({
|
Windows.current.setSize({
|
||||||
width: 900 + (900 - window.innerWidth),
|
width: 900 + (900 - window.innerWidth),
|
||||||
height: 600 + (600 - window.innerHeight),
|
height: 600 + (600 - window.innerHeight),
|
||||||
resizable: false
|
resizable: false
|
||||||
|
@ -241,8 +170,13 @@
|
||||||
{#if typeof $locale === 'string'}
|
{#if typeof $locale === 'string'}
|
||||||
<main>
|
<main>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
{#each menuItems as item}
|
{#each ['general', 'enhancements', 'runners', 'dxvks', 'shaders', 'environment'] as item}
|
||||||
<div class="menu-item" on:click={changeItem} class:menu-item-active={selectedItem === item} data-anchor={item}>{ $_(`settings.${item}.title`) }</div>
|
<div
|
||||||
|
class="menu-item"
|
||||||
|
class:menu-item-active={selectedItem === item}
|
||||||
|
data-anchor={item}
|
||||||
|
on:click={changeItem}
|
||||||
|
>{$_(`settings.${item}.title`)}</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -269,14 +203,23 @@
|
||||||
tooltip="settings.general.items.lang.voice.tooltip"
|
tooltip="settings.general.items.lang.voice.tooltip"
|
||||||
prop="lang.voice"
|
prop="lang.voice"
|
||||||
selected={undefined}
|
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}
|
selectionUpdated={() => voiceUpdateRequired = true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectionBox
|
<SelectionBox
|
||||||
lang="settings.general.items.theme.title"
|
lang="settings.general.items.theme.title"
|
||||||
prop="theme"
|
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}
|
valueChanged={switchTheme}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -294,7 +237,7 @@
|
||||||
|
|
||||||
const runnersDir = await constants.paths.runnersDir;
|
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: {
|
env: {
|
||||||
WINE: runner ? `${runnersDir}/${runner.name}/${runner.files.wine}` : 'wine',
|
WINE: runner ? `${runnersDir}/${runner.name}/${runner.files.wine}` : 'wine',
|
||||||
WINESERVER: runner ? `${runnersDir}/${runner.name}/${runner.files.wineserver}` : 'wineserver',
|
WINESERVER: runner ? `${runnersDir}/${runner.name}/${runner.files.wineserver}` : 'wineserver',
|
||||||
|
@ -308,7 +251,7 @@
|
||||||
|
|
||||||
const runnerDir = runner ? `${await constants.paths.runnersDir}/${runner.name}` : '';
|
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: {
|
env: {
|
||||||
WINE: runner ? `${runnerDir}/${runner.files.wine}` : 'wine',
|
WINE: runner ? `${runnerDir}/${runner.files.wine}` : 'wine',
|
||||||
WINESERVER: runner ? `${runnerDir}/${runner.files.wineserver}` : 'wineserver',
|
WINESERVER: runner ? `${runnerDir}/${runner.files.wineserver}` : 'wineserver',
|
||||||
|
@ -319,14 +262,14 @@
|
||||||
|
|
||||||
<!-- svelte-ignore missing-declaration -->
|
<!-- svelte-ignore missing-declaration -->
|
||||||
<Button lang="settings.general.items.buttons.launcher" click={async () => {
|
<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
|
background: true
|
||||||
});
|
});
|
||||||
}} />
|
}} />
|
||||||
|
|
||||||
<!-- svelte-ignore missing-declaration -->
|
<!-- svelte-ignore missing-declaration -->
|
||||||
<Button lang="settings.general.items.buttons.game" click={async () => {
|
<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
|
background: true
|
||||||
});
|
});
|
||||||
}} />
|
}} />
|
||||||
|
@ -339,7 +282,11 @@
|
||||||
<SelectionBox
|
<SelectionBox
|
||||||
lang="settings.enhancements.items.hud.title"
|
lang="settings.enhancements.items.hud.title"
|
||||||
prop="hud"
|
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
|
<SelectionBox
|
||||||
|
@ -347,7 +294,11 @@
|
||||||
prop="winesync"
|
prop="winesync"
|
||||||
tooltip="settings.enhancements.items.winesync.tooltip"
|
tooltip="settings.enhancements.items.winesync.tooltip"
|
||||||
tooltip_size="large"
|
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
|
<Checkbox
|
||||||
|
@ -388,7 +339,14 @@
|
||||||
lang="settings.enhancements.items.purge_logs.launcher.title"
|
lang="settings.enhancements.items.purge_logs.launcher.title"
|
||||||
tooltip="settings.enhancements.items.purge_logs.launcher.tooltip"
|
tooltip="settings.enhancements.items.purge_logs.launcher.tooltip"
|
||||||
prop="purge_logs.launcher"
|
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>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,7 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { _, locale } from 'svelte-i18n';
|
import { _, locale } from 'svelte-i18n';
|
||||||
|
|
||||||
import Configs from './ts/Configs';
|
import { Configs, IPC, Windows } from './empathize';
|
||||||
import IPC from './ts/core/IPC';
|
|
||||||
|
|
||||||
import Window from './ts/neutralino/Window';
|
|
||||||
|
|
||||||
import Splash from './assets/gifs/running-qiqi.gif';
|
import Splash from './assets/gifs/running-qiqi.gif';
|
||||||
import SplashSecret from './assets/gifs/loading-marie-please.gif';
|
import SplashSecret from './assets/gifs/loading-marie-please.gif';
|
||||||
|
@ -19,8 +16,8 @@
|
||||||
let phrase = Math.round(Math.random() * 8);
|
let phrase = Math.round(Math.random() * 8);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
Window.current.show();
|
Windows.current.show();
|
||||||
Window.current.center(300, 400);
|
// FIXME: Windows.current.center(300, 400);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLauncherLoaded = () => {
|
const isLauncherLoaded = () => {
|
||||||
|
@ -32,7 +29,7 @@
|
||||||
for (const record of launcherLoaded)
|
for (const record of launcherLoaded)
|
||||||
await record.pop();
|
await record.pop();
|
||||||
|
|
||||||
Window.current.hide();
|
Windows.current.hide();
|
||||||
Neutralino.app.exit();
|
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 Neutralino;
|
||||||
declare const NL_CWD;
|
declare const NL_CWD;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { Downloader, path } from '../empathize';
|
||||||
|
|
||||||
import constants from './Constants';
|
import constants from './Constants';
|
||||||
import Downloader from './core/Downloader';
|
|
||||||
import Process from './neutralino/Process';
|
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export default class FPSUnlock
|
||||||
Downloader.download(constants.uri.fpsunlock.bat, fpsunlockBat).then((stream) => {
|
Downloader.download(constants.uri.fpsunlock.bat, fpsunlockBat).then((stream) => {
|
||||||
stream.finish(async () => {
|
stream.finish(async () => {
|
||||||
// sed -i 's/start ..\/GI_FPSUnlocker\/unlockfps.exe \%\*/start ..\/fpsunlock\/unlockfps.exe \%\*/g' unlockfps.bat
|
// 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());
|
.then(() => resolve());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,14 +5,13 @@ import type {
|
||||||
Diff
|
Diff
|
||||||
} from './types/GameData';
|
} 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 constants from './Constants';
|
||||||
import fetch from './core/Fetch';
|
|
||||||
import AbstractInstaller from './core/AbstractInstaller';
|
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;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { locale } from 'svelte-i18n';
|
import { locale } from 'svelte-i18n';
|
||||||
|
|
||||||
import Window from './neutralino/Window';
|
import {
|
||||||
import Process from './neutralino/Process';
|
Windows, Process, Tray,
|
||||||
import Tray from './neutralino/Tray';
|
Configs, Debug, IPC
|
||||||
|
} from '../empathize';
|
||||||
|
|
||||||
import constants from './Constants';
|
import constants from './Constants';
|
||||||
import Configs from './Configs';
|
|
||||||
import Debug from './core/Debug';
|
|
||||||
import IPC from './core/IPC';
|
|
||||||
import DiscordRPC from './core/DiscordRPC';
|
import DiscordRPC from './core/DiscordRPC';
|
||||||
import Locales from './launcher/Locales';
|
import Locales from './launcher/Locales';
|
||||||
|
|
||||||
|
@ -56,7 +54,7 @@ export default class Launcher
|
||||||
{
|
{
|
||||||
this.settingsMenu = undefined;
|
this.settingsMenu = undefined;
|
||||||
|
|
||||||
const window = await Window.open('settings', {
|
const window = await Windows.open('settings', {
|
||||||
title: 'Settings',
|
title: 'Settings',
|
||||||
width: 900,
|
width: 900,
|
||||||
height: 600,
|
height: 600,
|
||||||
|
@ -96,11 +94,11 @@ export default class Launcher
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Window.current.show();
|
Windows.current.show();
|
||||||
Window.current.center(1280, 700);
|
// TODO: Windows.current.center(1280, 700);
|
||||||
})
|
})
|
||||||
|
|
||||||
Window.current.hide();
|
Windows.current.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(window.status);
|
resolve(window.status);
|
||||||
|
|
|
@ -2,14 +2,12 @@ import type { PatchInfo } from './types/Patch';
|
||||||
|
|
||||||
import md5 from 'js-md5';
|
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 constants from './Constants';
|
||||||
import Game from './Game';
|
import Game from './Game';
|
||||||
import fetch from './core/Fetch';
|
|
||||||
import AbstractInstaller from './core/AbstractInstaller';
|
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;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -50,37 +48,37 @@ class Stream extends AbstractInstaller
|
||||||
/**
|
/**
|
||||||
* Remove test version restrictions from the main patch
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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 { VoicePack } from './types/GameData';
|
||||||
import type { VoiceLang, InstalledVoice } from './types/Voice';
|
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 constants from './Constants';
|
||||||
import Game from './Game';
|
import Game from './Game';
|
||||||
import AbstractInstaller from './core/AbstractInstaller';
|
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;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -170,15 +170,15 @@ export default class Voice
|
||||||
|
|
||||||
const pipeline = promisify({
|
const pipeline = promisify({
|
||||||
callbacks: [
|
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) => {
|
(): 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());
|
.then(() => resolve());
|
||||||
}),
|
}),
|
||||||
|
|
||||||
(): Promise<void> => new Promise(async (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());
|
.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 constants from '../Constants';
|
||||||
import Downloader, { Stream as DownloadStream } from './Downloader';
|
|
||||||
import Archive from './Archive';
|
|
||||||
import { DebugThread } from './Debug';
|
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -68,7 +70,7 @@ export default abstract class Installer
|
||||||
if (shouldResolve)
|
if (shouldResolve)
|
||||||
debugThread.log(`Resolved unpack dir: ${unpackDir}`);
|
debugThread.log(`Resolved unpack dir: ${unpackDir}`);
|
||||||
|
|
||||||
Archive.unpack(archivePath, unpackDir).then((stream) => {
|
Archive.extract(archivePath, unpackDir).then((stream) => {
|
||||||
stream.progressInterval = this.unpackProgressInterval;
|
stream.progressInterval = this.unpackProgressInterval;
|
||||||
|
|
||||||
stream.start(() => {
|
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 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 constants from '../Constants';
|
||||||
import Configs from '../Configs';
|
|
||||||
import AbstractInstaller from './AbstractInstaller';
|
import AbstractInstaller from './AbstractInstaller';
|
||||||
import Process from '../neutralino/Process';
|
|
||||||
import promisify from './promisify';
|
|
||||||
import Runners from './Runners';
|
import Runners from './Runners';
|
||||||
import { DebugThread } from './Debug';
|
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -143,7 +142,7 @@ export default class DXVK
|
||||||
const version = typeof dxvk !== 'string' ?
|
const version = typeof dxvk !== 'string' ?
|
||||||
dxvk.version : dxvk;
|
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');
|
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 type { Params } from '../types/DiscordRPC';
|
||||||
|
|
||||||
import Process from '../neutralino/Process';
|
import { Process, path } from '../../empathize';
|
||||||
|
|
||||||
declare const NL_CWD;
|
declare const NL_CWD;
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ export default class DiscordRPC
|
||||||
];
|
];
|
||||||
|
|
||||||
if (params.details)
|
if (params.details)
|
||||||
exec = [...exec, `-d "${Process.addSlashes(params.details)}"`];
|
exec = [...exec, `-d "${path.addSlashes(params.details)}"`];
|
||||||
|
|
||||||
if (params.state)
|
if (params.state)
|
||||||
exec = [...exec, `-s "${Process.addSlashes(params.state)}"`];
|
exec = [...exec, `-s "${path.addSlashes(params.state)}"`];
|
||||||
|
|
||||||
if (params.icon)
|
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 = {
|
type Tag = {
|
||||||
tag: string,
|
tag: string,
|
||||||
|
@ -17,7 +17,7 @@ export default class Git
|
||||||
public static getTags(repository: string): Promise<Tag[]>
|
public static getTags(repository: string): Promise<Tag[]>
|
||||||
{
|
{
|
||||||
return new Promise(async (resolve) => {
|
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[] = [];
|
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 constants from '../Constants';
|
||||||
import Process from '../neutralino/Process';
|
|
||||||
import Debug, { DebugThread } from './Debug';
|
|
||||||
import Downloader from './Downloader';
|
|
||||||
import Runners from './Runners';
|
import Runners from './Runners';
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
@ -52,7 +52,7 @@ export default class Prefix
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
Downloader.download(constants.uri.winetricks, winetricksPath).then((stream) => {
|
Downloader.download(constants.uri.winetricks, winetricksPath).then((stream) => {
|
||||||
stream.finish(async () => {
|
stream.finish(async () => {
|
||||||
await Neutralino.os.execCommand(`chmod +x "${Process.addSlashes(winetricksPath)}"`);
|
await Neutralino.os.execCommand(`chmod +x "${path.addSlashes(winetricksPath)}"`);
|
||||||
|
|
||||||
resolve(winetricksPath);
|
resolve(winetricksPath);
|
||||||
});
|
});
|
||||||
|
@ -64,12 +64,12 @@ export default class Prefix
|
||||||
/**
|
/**
|
||||||
* Create wine prefix using the current selected wine
|
* 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
|
* @param progress function that will be called with every creation step
|
||||||
*
|
*
|
||||||
* @returns false if there's no selected wine version. Otherwise true
|
* @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');
|
const debugThread = new DebugThread('Prefix.create', 'Creating wine prefix');
|
||||||
|
|
||||||
|
@ -108,11 +108,11 @@ export default class Prefix
|
||||||
this.getWinetricks().then(async (winetricks) => {
|
this.getWinetricks().then(async (winetricks) => {
|
||||||
let installationProgress = 0;
|
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: {
|
env: {
|
||||||
WINE: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`,
|
WINE: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wine}`,
|
||||||
WINESERVER: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wineserver}`,
|
WINESERVER: `${await constants.paths.runnersDir}/${runner.name}/${runner.files.wineserver}`,
|
||||||
WINEPREFIX: path
|
WINEPREFIX: folder
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,11 @@ import type {
|
||||||
RunnerFamily
|
RunnerFamily
|
||||||
} from '../types/Runners';
|
} from '../types/Runners';
|
||||||
|
|
||||||
|
import { Configs, Process, path } from '../../empathize';
|
||||||
|
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||||
|
|
||||||
import constants from '../Constants';
|
import constants from '../Constants';
|
||||||
import Configs from '../Configs';
|
|
||||||
import AbstractInstaller from './AbstractInstaller';
|
import AbstractInstaller from './AbstractInstaller';
|
||||||
import Process from '../neutralino/Process';
|
|
||||||
import { DebugThread } from './Debug';
|
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ class Runners
|
||||||
const name = typeof runner !== 'string' ?
|
const name = typeof runner !== 'string' ?
|
||||||
runner.name : runner;
|
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) => {
|
.then((process) => {
|
||||||
process.finish(() => {
|
process.finish(() => {
|
||||||
debugThread.log('Runner deleted');
|
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 constants from '../Constants';
|
||||||
import fetch from '../core/Fetch';
|
|
||||||
import Locales from './Locales';
|
import Locales from './Locales';
|
||||||
|
|
||||||
export default class Background
|
export default class Background
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { dictionary, locale } from 'svelte-i18n';
|
||||||
|
|
||||||
import YAML from 'yaml';
|
import YAML from 'yaml';
|
||||||
|
|
||||||
|
import { promisify, Configs } from '../../empathize';
|
||||||
|
|
||||||
import constants from '../Constants';
|
import constants from '../Constants';
|
||||||
import promisify from '../core/promisify';
|
|
||||||
import Configs from '../Configs';
|
|
||||||
|
|
||||||
type AvailableLocales =
|
type AvailableLocales =
|
||||||
| 'en-us'
|
| 'en-us'
|
||||||
|
|
|
@ -2,9 +2,9 @@ import YAML from 'yaml';
|
||||||
|
|
||||||
import type { Shader } from '../types/Shaders';
|
import type { Shader } from '../types/Shaders';
|
||||||
|
|
||||||
import Configs from '../Configs';
|
import { Configs, promisify } from '../../empathize';
|
||||||
|
|
||||||
import constants from '../Constants';
|
import constants from '../Constants';
|
||||||
import promisify from '../core/promisify';
|
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
|
|
@ -3,22 +3,20 @@ import { dictionary, locale } from 'svelte-i18n';
|
||||||
|
|
||||||
import semver from 'semver';
|
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 Launcher from '../Launcher';
|
||||||
import Game from '../Game';
|
import Game from '../Game';
|
||||||
import Patch from '../Patch';
|
import Patch from '../Patch';
|
||||||
import Voice from '../Voice';
|
import Voice from '../Voice';
|
||||||
import Runners from '../core/Runners';
|
import Runners from '../core/Runners';
|
||||||
import Debug, { DebugThread } from '../core/Debug';
|
|
||||||
import DXVK from '../core/DXVK';
|
import DXVK from '../core/DXVK';
|
||||||
import IPC from '../core/IPC';
|
|
||||||
import Locales from './Locales';
|
import Locales from './Locales';
|
||||||
import Git from '../core/Git';
|
import Git from '../core/Git';
|
||||||
import constants from '../Constants';
|
import constants from '../Constants';
|
||||||
import Notifications from '../core/Notifications';
|
|
||||||
|
|
||||||
export default class State
|
export default class State
|
||||||
{
|
{
|
||||||
|
@ -97,8 +95,8 @@ export default class State
|
||||||
this.update().then(async () => {
|
this.update().then(async () => {
|
||||||
IPC.write('launcher-loaded');
|
IPC.write('launcher-loaded');
|
||||||
|
|
||||||
await Window.current.show();
|
await Windows.current.show();
|
||||||
await Window.current.center(1280, 700);
|
// FIXME: await Windows.current.center(1280, 700);
|
||||||
|
|
||||||
// Check for new versions of the launcher
|
// Check for new versions of the launcher
|
||||||
Git.getTags(constants.uri.launcher).then((tags) => {
|
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;
|
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),
|
title: locales['title'].replace('{from}', Launcher.version).replace('{to}', tag.tag),
|
||||||
body: locales['body'].replace('{repository}', constants.uri.launcher),
|
body: locales['body'].replace('{repository}', constants.uri.launcher),
|
||||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`
|
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`
|
||||||
|
@ -128,7 +126,7 @@ export default class State
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Window.current.setSize({
|
Windows.current.setSize({
|
||||||
width: 1280 + (1280 - window.innerWidth),
|
width: 1280 + (1280 - window.innerWidth),
|
||||||
height: 700 + (700 - window.innerHeight),
|
height: 700 + (700 - window.innerHeight),
|
||||||
resizable: false
|
resizable: false
|
||||||
|
@ -400,7 +398,7 @@ export default class State
|
||||||
{
|
{
|
||||||
state = 'game-launch-available';
|
state = 'game-launch-available';
|
||||||
|
|
||||||
Notifications.show({
|
Notification.show({
|
||||||
title: 'An Anime Game Launcher',
|
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',
|
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`,
|
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { Notification } from '../../../empathize';
|
||||||
|
|
||||||
import Launcher from '../../Launcher';
|
import Launcher from '../../Launcher';
|
||||||
import Patch from '../../Patch';
|
import Patch from '../../Patch';
|
||||||
import Notifications from '../../core/Notifications';
|
|
||||||
import constants from '../../Constants';
|
import constants from '../../Constants';
|
||||||
|
|
||||||
export default (launcher: Launcher): Promise<void> => {
|
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
|
// Show an error notification if xdelta3 package is not installed
|
||||||
if (!await Launcher.isPackageAvailable('xdelta3'))
|
if (!await Launcher.isPackageAvailable('xdelta3'))
|
||||||
{
|
{
|
||||||
Notifications.show({
|
Notification.show({
|
||||||
title: 'An Anime Game Launcher',
|
title: 'An Anime Game Launcher',
|
||||||
body: 'You must download xdelta3 package to apply the patch',
|
body: 'You must download xdelta3 package to apply the patch',
|
||||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
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 for some reasons patch wasn't applied successfully
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
Notifications.show({
|
Notification.show({
|
||||||
title: 'An Anime Game Launcher',
|
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',
|
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`
|
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`
|
||||||
|
@ -89,7 +90,7 @@ export default (launcher: Launcher): Promise<void> => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
Notifications.show({
|
Notification.show({
|
||||||
title: 'An Anime Game Launcher',
|
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',
|
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`,
|
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
||||||
|
|
|
@ -3,10 +3,11 @@ import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
import type Launcher from '../../Launcher';
|
import type Launcher from '../../Launcher';
|
||||||
|
|
||||||
|
import { Debug } from '../../../empathize';
|
||||||
|
|
||||||
import Game from '../../Game';
|
import Game from '../../Game';
|
||||||
import Prefix from '../../core/Prefix';
|
import Prefix from '../../core/Prefix';
|
||||||
import constants from '../../Constants';
|
import constants from '../../Constants';
|
||||||
import Debug from '../../core/Debug';
|
|
||||||
|
|
||||||
declare const Neutralino;
|
declare const Neutralino;
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,9 @@ import { _ } from 'svelte-i18n';
|
||||||
import type Launcher from '../../Launcher';
|
import type Launcher from '../../Launcher';
|
||||||
import type { VoiceLang } from '../../types/Voice';
|
import type { VoiceLang } from '../../types/Voice';
|
||||||
|
|
||||||
|
import { promisify } from '../../../empathize';
|
||||||
|
|
||||||
import Voice from '../../Voice';
|
import Voice from '../../Voice';
|
||||||
import promisify from '../../core/promisify';
|
|
||||||
import Game from '../../Game';
|
import Game from '../../Game';
|
||||||
|
|
||||||
export default (launcher: Launcher): Promise<void> => {
|
export default (launcher: Launcher): Promise<void> => {
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import Process from '../../neutralino/Process';
|
import { Process, Windows, Configs, Notification, path } from '../../../empathize';
|
||||||
import Window from '../../neutralino/Window';
|
import { DebugThread } from '@empathize/framework/dist/meta/Debug';
|
||||||
|
|
||||||
import Launcher from '../../Launcher';
|
import Launcher from '../../Launcher';
|
||||||
import Configs from '../../Configs';
|
|
||||||
import constants from '../../Constants';
|
import constants from '../../Constants';
|
||||||
import { DebugThread } from '../../core/Debug';
|
|
||||||
import Notifications from '../../core/Notifications';
|
|
||||||
import Runners from '../../core/Runners';
|
import Runners from '../../core/Runners';
|
||||||
import Game from '../../Game';
|
import Game from '../../Game';
|
||||||
|
|
||||||
|
@ -20,7 +17,7 @@ export default (launcher: Launcher): Promise<void> => {
|
||||||
// If telemetry servers are not disabled
|
// If telemetry servers are not disabled
|
||||||
if (!telemetry)
|
if (!telemetry)
|
||||||
{
|
{
|
||||||
Notifications.show({
|
Notification.show({
|
||||||
title: 'An Anime Game Launcher',
|
title: 'An Anime Game Launcher',
|
||||||
body: 'Telemetry servers are not disabled',
|
body: 'Telemetry servers are not disabled',
|
||||||
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
icon: `${constants.paths.appDir}/public/images/baal64-transparent.png`,
|
||||||
|
@ -33,7 +30,7 @@ export default (launcher: Launcher): Promise<void> => {
|
||||||
// Otherwise run the game
|
// Otherwise run the game
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Window.current.hide();
|
Windows.current.hide();
|
||||||
|
|
||||||
launcher.updateDiscordRPC('in-game');
|
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`);
|
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
|
* Gamemode integration
|
||||||
|
@ -208,8 +205,8 @@ export default (launcher: Launcher): Promise<void> => {
|
||||||
{
|
{
|
||||||
const stopTime = Date.now();
|
const stopTime = Date.now();
|
||||||
|
|
||||||
Window.current.show();
|
Windows.current.show();
|
||||||
Window.current.center(1280, 700);
|
// FIXME: Windows.current.center(1280, 700);
|
||||||
|
|
||||||
launcher.updateDiscordRPC('in-launcher');
|
launcher.updateDiscordRPC('in-launcher');
|
||||||
launcher.tray.hide();
|
launcher.tray.hide();
|
||||||
|
@ -218,7 +215,7 @@ export default (launcher: Launcher): Promise<void> => {
|
||||||
Configs.get('purge_logs.game').then(async (purge_logs) => {
|
Configs.get('purge_logs.game').then(async (purge_logs) => {
|
||||||
if (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")
|
// Delete .log files (e.g. "ZFGameBrowser_xxxx.log")
|
||||||
Neutralino.os.execCommand(`find "${gameDir}" -maxdepth 1 -type f -name "*.log" -delete`);
|
Neutralino.os.execCommand(`find "${gameDir}" -maxdepth 1 -type f -name "*.log" -delete`);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import type Launcher from '../../Launcher';
|
import type Launcher from '../../Launcher';
|
||||||
import type { VoiceLang } from '../../types/Voice';
|
import type { VoiceLang } from '../../types/Voice';
|
||||||
|
|
||||||
|
import { promisify } from '../../../empathize';
|
||||||
|
|
||||||
import Voice from '../../Voice';
|
import Voice from '../../Voice';
|
||||||
import promisify from '../../core/promisify';
|
|
||||||
|
|
||||||
export default (launcher: Launcher): Promise<void> => {
|
export default (launcher: Launcher): Promise<void> => {
|
||||||
return new Promise(async (resolve) => {
|
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",
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
/**
|
/**
|
||||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||||
|
|
Loading…
Reference in a new issue