mirror of
https://github.com/phantomptr/ps5upload.git
synced 2026-10-21 19:33:08 -05:00
Engine: POST /api/folder/build unpacks a game image (or, once the pkg reader lands, a debug-key .pkg) into <out>/<name>. It runs as a job with Write and Verify stages; every path is checked before anything is written (.., absolute, empty components, names this OS cannot hold, file/folder clashes, case clashes on a case-insensitive drive); data streams in 8 MiB chunks with cancel per chunk; the folder is written as <name>.partial/ (marked so the output-folder sweep can tell it from a user's own) and renamed only after the read-back: every size and empty folder, and the SHA-256 of a seeded sample (or every file) re-read from a freshly opened source. An existing target, a plain folder source and a shortfall of free space (files + 64 MiB) are refused before the job starts. Client: a third "What to make" tile, A game folder. A folder source disables Make with "This is already a folder"; .pkg is accepted as a source; then is keep only; the finished card shows the folder and Show in folder. The queue, prefs, pipeline and stage list know the folder kind. Strings in all 21 locales. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
89 lines
3.8 KiB
TypeScript
89 lines
3.8 KiB
TypeScript
// The Convert queue's real runner: each build is the Convert screen's own pipeline (so the
|
|
// running item shows its stages there), and a finished package is handed to the console queue.
|
|
|
|
import { fpkg } from "../api/fpkg";
|
|
import { setConvertRunner, type ConvertItem } from "../state/convertQueue";
|
|
import { useFpkgConversion } from "../state/fpkgConversion";
|
|
import { pkgLibraryStore } from "../state/pkgLibrary";
|
|
|
|
/** Resolves once the pipeline is not running (a manual run started first finishes first). */
|
|
function whenIdle(): Promise<void> {
|
|
if (useFpkgConversion.getState().pipeline.phase !== "running") return Promise.resolve();
|
|
return new Promise((resolve) => {
|
|
const off = useFpkgConversion.subscribe((s) => {
|
|
if (s.pipeline.phase !== "running") {
|
|
off();
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async function build(item: ConvertItem) {
|
|
await whenIdle();
|
|
const conv = useFpkgConversion.getState();
|
|
if (item.build?.kind === "folder") {
|
|
// A plain folder from an image or package: kept here, nothing to hand on.
|
|
await conv.buildFolder(item.source, item.outputDir);
|
|
await whenIdle();
|
|
const p = useFpkgConversion.getState().pipeline;
|
|
if (p.phase === "done") return { ok: true, packagePath: p.packagePath, installed: true };
|
|
if (p.phase === "failed") return { ok: false, message: p.message };
|
|
return { ok: false, message: "The build did not start." };
|
|
}
|
|
if (item.build?.kind === "image") {
|
|
// A game image, in the format picked; sent on to the Upload queue when asked.
|
|
const send = item.then !== "keep" && !!item.host && !!item.imageDest;
|
|
await conv.buildImage(
|
|
item.source,
|
|
item.outputDir,
|
|
item.build.compress,
|
|
item.build.format,
|
|
send
|
|
? {
|
|
host: item.host!,
|
|
volume: item.imageDest!.volume,
|
|
subpath: item.imageDest!.subpath,
|
|
deleteAfter: !!item.deleteAfterInstall,
|
|
}
|
|
: undefined,
|
|
item.build.lz4,
|
|
);
|
|
await whenIdle();
|
|
const p = useFpkgConversion.getState().pipeline;
|
|
// Handed to the Upload queue (or kept): nothing more for this queue to do.
|
|
if (p.phase === "done") return { ok: true, packagePath: p.packagePath, installed: true };
|
|
if (p.phase === "failed") return { ok: false, message: p.message };
|
|
return { ok: false, message: "The build did not start." };
|
|
}
|
|
// A console dump is swapped for its package inside the build (never installed beside it).
|
|
const swap = item.source.startsWith("ps5://") && item.then !== "keep" && !!item.host;
|
|
await conv.start(
|
|
{ source: item.source, outputDir: item.outputDir, compression: item.compression, firmware: item.firmware, language: item.language },
|
|
{ install: swap, host: swap ? item.host : null, method: item.then === "upload" ? "upload" : "stream" },
|
|
);
|
|
await whenIdle();
|
|
const p = useFpkgConversion.getState().pipeline;
|
|
if (p.phase === "done") return { ok: true, packagePath: p.packagePath, installed: swap };
|
|
if (p.phase === "failed") return { ok: false, message: p.message };
|
|
return { ok: false, message: "The build did not start." };
|
|
}
|
|
|
|
async function handOff(item: ConvertItem, packagePath: string) {
|
|
const host = item.host!;
|
|
const store = pkgLibraryStore(host).getState();
|
|
const r =
|
|
item.then === "upload" ? await store.uploadInstall(packagePath, host) : await store.installStream(packagePath, host);
|
|
// Only a package this engine built, and only after its install verified.
|
|
if (r.ok && item.deleteAfterInstall) await fpkg.deletePackage(packagePath).catch(() => {});
|
|
return { ok: !!r.ok, message: r.message };
|
|
}
|
|
|
|
let installed = false;
|
|
/** Wire the queue to the real pipeline and console queue (once, from the app shell). */
|
|
export function installConvertRunner() {
|
|
if (installed) return;
|
|
installed = true;
|
|
setConvertRunner({ build, handOff });
|
|
}
|