fix: logins now refresh on plugins load
feat: Added tar archive support fix: Downloaded games and emulator execute permission now updated fix: Fixed rclone for linux fix: on screen keyaboard only now shows up when using a gamepad or touch
This commit is contained in:
parent
6aacec2c0d
commit
7bd0ebdcca
39 changed files with 523 additions and 275 deletions
|
|
@ -11,6 +11,7 @@ import { ensureDir, move } from "fs-extra";
|
|||
import { simulateProgress } from "@/bun/utils";
|
||||
import { path7za } from "7zip-bin";
|
||||
import { getEmulatorDownload, getEmulatorPath } from "../store/services/emulatorsService";
|
||||
import { $ } from "bun";
|
||||
|
||||
type EmulatorDownloadStates = "download" | "extract";
|
||||
|
||||
|
|
@ -61,7 +62,7 @@ export class EmulatorDownloadJob implements IJob<z.infer<typeof EmulatorDownload
|
|||
const destinationPaths = await downloader.start();
|
||||
if (destinationPaths)
|
||||
{
|
||||
const isArchive = destinationPaths[0].endsWith('.7z') || destinationPaths[0].endsWith('.zip');
|
||||
const isArchive = destinationPaths[0].endsWith('.7z') || destinationPaths[0].endsWith('.zip') || destinationPaths[0].endsWith('.tar');
|
||||
const isAppImage = destinationPaths[0].endsWith(".AppImage");
|
||||
|
||||
if (!isArchive && !isAppImage)
|
||||
|
|
@ -74,14 +75,23 @@ export class EmulatorDownloadJob implements IJob<z.infer<typeof EmulatorDownload
|
|||
if (destinationPaths[0])
|
||||
{
|
||||
let destinationPath = destinationPaths[0];
|
||||
await new Promise((resolve, reject) =>
|
||||
if (destinationPath.endsWith('.tar'))
|
||||
{
|
||||
const seven = Seven.extractFull(destinationPath, emulatorsFolder, { $bin: process.env.ZIP7_PATH ?? path7za, $progress: true, noRootDuplication: true });
|
||||
seven.on('progress', p => context.setProgress(p.percent, "extract"));
|
||||
seven.on('error', e => reject(e));
|
||||
seven.on('end', () => resolve(true));
|
||||
});
|
||||
await fs.rm(destinationPath, { recursive: true });
|
||||
context.setProgress(0, "extract");
|
||||
await ensureDir(emulatorsFolder);
|
||||
await $`tar -xf ${destinationPath} -C ${emulatorsFolder}`;
|
||||
await fs.rm(destinationPath, { recursive: true });
|
||||
} else
|
||||
{
|
||||
await new Promise((resolve, reject) =>
|
||||
{
|
||||
const seven = Seven.extractFull(destinationPath, emulatorsFolder, { $bin: process.env.ZIP7_PATH ?? path7za, $progress: true, noRootDuplication: true });
|
||||
seven.on('progress', p => context.setProgress(p.percent, "extract"));
|
||||
seven.on('error', e => reject(e));
|
||||
seven.on('end', () => resolve(true));
|
||||
});
|
||||
await fs.rm(destinationPath, { recursive: true });
|
||||
}
|
||||
|
||||
// check if 1 root folder we need to get rid of
|
||||
const contents = await fs.readdir(emulatorsFolder);
|
||||
|
|
@ -106,15 +116,18 @@ export class EmulatorDownloadJob implements IJob<z.infer<typeof EmulatorDownload
|
|||
}
|
||||
}
|
||||
|
||||
await Bun.write(`${emulatorsFolder}.json`, JSON.stringify(info, null, 3));
|
||||
|
||||
const execs: EmulatorSourceEntryType[] = [];
|
||||
await plugins.hooks.emulators.findEmulatorSource.promise({ emulator: this.emulator, sources: execs });
|
||||
|
||||
await plugins.hooks.emulators.emulatorPostInstall.promise({
|
||||
emulator: this.emulator,
|
||||
emulatorPackage: this.emulatorPackage,
|
||||
path: emulatorsFolder,
|
||||
path: execs.find(e => e.type === 'store')?.binPath ?? emulatorsFolder,
|
||||
info,
|
||||
update: this.isUpdate
|
||||
});
|
||||
|
||||
await Bun.write(`${emulatorsFolder}.json`, JSON.stringify(info, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ export class InstallJob implements IJob<never, InstallJobStates>
|
|||
if (!info) throw new Error(`Could not find downloader for source ${this.source}`);
|
||||
|
||||
const files = await checkFiles(info.files, !!info.extract_path);
|
||||
const finalFiles: string[] = [];
|
||||
|
||||
if (this.config?.dryRun !== true)
|
||||
{
|
||||
|
|
@ -84,6 +85,7 @@ export class InstallJob implements IJob<never, InstallJobStates>
|
|||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.extract_path && downloadedFiles)
|
||||
{
|
||||
let progress = 0;
|
||||
|
|
@ -139,6 +141,7 @@ export class InstallJob implements IJob<never, InstallJobStates>
|
|||
if (filePath.endsWith('.zip'))
|
||||
{
|
||||
cx.setProgress(0, "extract");
|
||||
console.error(e);
|
||||
console.warn("Could not extract", filePath, "with 7zip trying zip extractor");
|
||||
await ensureDir(extractPath);
|
||||
const zip = new StreamZip.async({ file: filePath });
|
||||
|
|
@ -175,6 +178,12 @@ export class InstallJob implements IJob<never, InstallJobStates>
|
|||
await move(tmpGameFolder, extractPath, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
finalFiles.push(extractPath);
|
||||
|
||||
} else
|
||||
{
|
||||
finalFiles.push(...downloadedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -323,6 +332,7 @@ export class InstallJob implements IJob<never, InstallJobStates>
|
|||
await simulateProgress(p => cx.setProgress(p, "download"), cx.abortSignal);
|
||||
}
|
||||
|
||||
await plugins.hooks.games.postInstall.promise({ source: this.source, id: this.gameId, files: finalFiles, info });
|
||||
|
||||
events.emit('notification', { message: `${info.name}: Installed`, type: 'success', duration: 8000 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import { db, events, plugins } from "../app";
|
|||
import * as appSchema from "@schema/app";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { spawn } from 'node:child_process';
|
||||
import { watch } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import { updateLocalLastPlayed } from "../games/services/statusService";
|
||||
import { getErrorMessage } from "@/bun/utils";
|
||||
|
||||
export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSchema>, string>
|
||||
{
|
||||
|
|
@ -42,15 +42,24 @@ export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSche
|
|||
const source = this.gameSource ?? this.gameId.source;
|
||||
const id = this.gameSourceId ?? this.gameId.id;
|
||||
|
||||
await plugins.hooks.games.postPlay.promise(
|
||||
{
|
||||
source,
|
||||
id,
|
||||
command: this.validCommand,
|
||||
changedSaveFiles: Array.from(this.changedSaveFiles.values()),
|
||||
validChangedSaveFiles: {},
|
||||
gameInfo
|
||||
}).catch(e => console.error(e));
|
||||
await new Promise(async (resolve) =>
|
||||
{
|
||||
await plugins.hooks.games.postPlay.promise(
|
||||
{
|
||||
source,
|
||||
id,
|
||||
command: this.validCommand,
|
||||
changedSaveFiles: Array.from(this.changedSaveFiles.values()),
|
||||
validChangedSaveFiles: {},
|
||||
gameInfo
|
||||
}).catch(e =>
|
||||
{
|
||||
console.error(e);
|
||||
events.emit('notification', { message: getErrorMessage(e), type: 'error' });
|
||||
}).then(() => resolve(false));
|
||||
const timeoutHandler = () => resolve(false);
|
||||
setTimeout(timeoutHandler, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
prePlay (setProgress: (progress: number, state: string) => void, gameInfo: { platformSlug?: string; })
|
||||
|
|
@ -118,31 +127,58 @@ export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSche
|
|||
{
|
||||
await this.prePlay(context.setProgress.bind(context), { platformSlug: gameInfo?.platformSlug }).catch(e => reject(e));
|
||||
|
||||
// ES-DE commands require shell execution. Some emulators fail otherwise.
|
||||
const spawnGame = spawn(this.validCommand.command, {
|
||||
shell: this.validCommand.shell ?? true,
|
||||
cwd: this.validCommand.startDir,
|
||||
signal: context.abortSignal,
|
||||
env: {
|
||||
...process.env,
|
||||
...this.validCommand.env
|
||||
},
|
||||
});
|
||||
|
||||
context.setProgress(0, "playing");
|
||||
|
||||
spawnGame.stdout.on('data', data => console.log(data));
|
||||
spawnGame.on('close', (code) =>
|
||||
if (Array.isArray(this.validCommand.command))
|
||||
{
|
||||
resolve(code);
|
||||
});
|
||||
spawnGame.on('error', e =>
|
||||
{
|
||||
console.error(e);
|
||||
resolve(1);
|
||||
});
|
||||
const bunGame = Bun.spawn(this.validCommand.command, {
|
||||
cwd: this.validCommand.startDir,
|
||||
signal: context.abortSignal,
|
||||
env: {
|
||||
...process.env,
|
||||
...this.validCommand.env
|
||||
}
|
||||
});
|
||||
|
||||
context.setProgress(0, "playing");
|
||||
|
||||
bunGame.exited.then(e =>
|
||||
{
|
||||
resolve(true);
|
||||
}).catch(e =>
|
||||
{
|
||||
console.error(e);
|
||||
reject(e);
|
||||
});
|
||||
|
||||
game = bunGame;
|
||||
} else
|
||||
{
|
||||
// ES-DE commands require shell execution. Some emulators fail otherwise.
|
||||
const spawnGame = spawn(this.validCommand.command, {
|
||||
shell: this.validCommand.shell ?? true,
|
||||
cwd: this.validCommand.startDir,
|
||||
signal: context.abortSignal,
|
||||
env: {
|
||||
...process.env,
|
||||
...this.validCommand.env
|
||||
},
|
||||
});
|
||||
|
||||
context.setProgress(0, "playing");
|
||||
|
||||
spawnGame.stdout.on('data', data => console.log(data));
|
||||
spawnGame.on('close', (code) =>
|
||||
{
|
||||
resolve(code);
|
||||
});
|
||||
spawnGame.on('error', e =>
|
||||
{
|
||||
console.error(e);
|
||||
resolve(1);
|
||||
});
|
||||
|
||||
game = spawnGame;
|
||||
}
|
||||
|
||||
game = spawnGame;
|
||||
}
|
||||
else if (this.validCommand.metadata.emulatorBin)
|
||||
{
|
||||
|
|
@ -151,7 +187,6 @@ export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSche
|
|||
await this.prePlay(context.setProgress.bind(context), { platformSlug: gameInfo?.platformSlug });
|
||||
|
||||
// We have full control over launching integrated emulators better to use bun spawn
|
||||
await fs.chmod(this.validCommand.metadata.emulatorBin, 0o755);
|
||||
const bunGame = Bun.spawn([this.validCommand.metadata.emulatorBin, ...commandArgs.args], {
|
||||
cwd: this.validCommand.startDir,
|
||||
signal: context.abortSignal,
|
||||
|
|
@ -212,7 +247,7 @@ export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSche
|
|||
} catch (e)
|
||||
{
|
||||
context.abort(e);
|
||||
reject(e);
|
||||
resolve(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue