fix: wait for JS downloader completion (#2229)

Co-authored-by: Chubby Granny Chaser <hydralauncher@proton.me>
This commit is contained in:
João Lucas 2026-06-10 22:21:02 -03:00 committed by GitHub
parent 8b1f19bed2
commit decbf4013d
9 changed files with 142 additions and 7 deletions

View file

@ -25,6 +25,9 @@ jobs:
- name: Validate current commit (last commit) with commitlint
run: npx commitlint --last --verbose
- name: Test
run: yarn test
- name: Typecheck
run: yarn typecheck

View file

@ -17,7 +17,8 @@ files:
- "!electron.vite.config.{js,ts,mjs,cjs}"
- "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}"
- "!{.env,.env.*,.npmrc,pnpm-lock.yaml}"
- "!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}"
- "!{tsconfig.json,tsconfig.node.json,tsconfig.web.json,tsconfig.test.json}"
- "!scripts/register-ts-node.mjs"
asarUnpack:
- resources/**
- node_modules/sharp/**

View file

@ -16,6 +16,7 @@
"scripts": {
"format": "prettier --write .",
"format-check": "prettier --check .",
"test": "node --import ./scripts/register-ts-node.mjs --test \"src/**/*.test.ts\"",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",

View file

@ -0,0 +1,6 @@
import { register } from "node:module";
import { pathToFileURL } from "node:url";
process.env.TS_NODE_PROJECT ??= "tsconfig.test.json";
register("ts-node/esm", pathToFileURL("./"));

View file

@ -0,0 +1,69 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { shouldFinalizeDownload } from "./download-completion.ts";
describe("shouldFinalizeDownload", () => {
it("waits for JS downloads to report complete before finalizing", () => {
assert.equal(
shouldFinalizeDownload({
usingJsDownloader: true,
isCheckingFiles: false,
isDownloadingMetadata: false,
progress: 1,
downloadStatus: "active",
}),
false
);
});
it("finalizes JS downloads once the persisted status is complete", () => {
assert.equal(
shouldFinalizeDownload({
usingJsDownloader: true,
isCheckingFiles: false,
isDownloadingMetadata: false,
progress: 1,
downloadStatus: "complete",
}),
true
);
});
it("preserves progress-based completion for torrent downloads", () => {
assert.equal(
shouldFinalizeDownload({
usingJsDownloader: false,
isCheckingFiles: false,
isDownloadingMetadata: false,
progress: 1,
downloadStatus: "active",
}),
true
);
});
it("does not finalize while metadata or file checks are in progress", () => {
assert.equal(
shouldFinalizeDownload({
usingJsDownloader: false,
isCheckingFiles: true,
isDownloadingMetadata: false,
progress: 1,
downloadStatus: "active",
}),
false
);
assert.equal(
shouldFinalizeDownload({
usingJsDownloader: false,
isCheckingFiles: false,
isDownloadingMetadata: true,
progress: 1,
downloadStatus: "active",
}),
false
);
});
});

View file

@ -0,0 +1,27 @@
import type { Download } from "@types";
interface DownloadCompletionInput {
usingJsDownloader: boolean;
isCheckingFiles: boolean;
isDownloadingMetadata: boolean;
progress: number;
downloadStatus: Download["status"];
}
export function shouldFinalizeDownload({
usingJsDownloader,
isCheckingFiles,
isDownloadingMetadata,
progress,
downloadStatus,
}: DownloadCompletionInput) {
if (isCheckingFiles || isDownloadingMetadata) {
return false;
}
if (usingJsDownloader) {
return downloadStatus === "complete";
}
return progress === 1 || downloadStatus === "complete";
}

View file

@ -34,6 +34,7 @@ import {
getDownloadLayoutStateRecord,
getNextQueuedDownloadFromLayout,
} from "../download-layout-state";
import { shouldFinalizeDownload } from "./download-completion";
interface AllDebridBatchEntry {
url: string;
@ -509,11 +510,15 @@ export class DownloadManager {
this.sendProgressUpdate(progress, status, game);
const isComplete =
!status.isCheckingFiles &&
!status.isDownloadingMetadata &&
(progress === 1 || download.status === "complete");
if (isComplete) {
if (
shouldFinalizeDownload({
usingJsDownloader: this.usingJsDownloader,
isCheckingFiles: status.isCheckingFiles,
isDownloadingMetadata: status.isDownloadingMetadata,
progress,
downloadStatus: download.status,
})
) {
await this.handleDownloadCompletion(download, game, gameId);
}
}

View file

@ -1,6 +1,15 @@
{
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/locales/index.ts", "src/shared/**/*", "src/types/**/*", "src/big-picture/vite-scope-big-picture-css.ts"],
"include": [
"electron.vite.config.*",
"src/main/**/*",
"src/preload/**/*",
"src/locales/index.ts",
"src/shared/**/*",
"src/types/**/*",
"src/big-picture/vite-scope-big-picture-css.ts"
],
"exclude": ["src/**/*.test.ts"],
"compilerOptions": {
"module": "ESNext",
"composite": true,

14
tsconfig.test.json Normal file
View file

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.node.json",
"include": [
"src/main/**/*.test.ts",
"src/main/**/*",
"src/shared/**/*",
"src/types/**/*"
],
"compilerOptions": {
"allowImportingTsExtensions": true,
"noEmit": true,
"types": ["node", "electron-vite/node"]
}
}