refactor: enhance download management by prioritizing interrupted downloads and improving error logging

This commit is contained in:
Moyasee
2026-01-06 19:59:52 +02:00
parent 027761a1b5
commit a7c82de4a7
2 changed files with 77 additions and 6 deletions

View File

@@ -18,6 +18,7 @@ import {
DeckyPlugin, DeckyPlugin,
DownloadSourcesChecker, DownloadSourcesChecker,
WSClient, WSClient,
logger,
} from "@main/services"; } from "@main/services";
import { migrateDownloadSources } from "./helpers/migrate-download-sources"; import { migrateDownloadSources } from "./helpers/migrate-download-sources";
@@ -73,18 +74,47 @@ export const loadState = async () => {
return orderBy(games, "timestamp", "desc"); return orderBy(games, "timestamp", "desc");
}); });
downloads.forEach((download) => { let interruptedDownload = null;
for (const download of downloads) {
const downloadKey = levelKeys.game(download.shop, download.objectId);
// Reset extracting state
if (download.extracting) { if (download.extracting) {
downloadsSublevel.put(levelKeys.game(download.shop, download.objectId), { await downloadsSublevel.put(downloadKey, {
...download, ...download,
extracting: false, extracting: false,
}); });
} }
});
const [nextItemOnQueue] = downloads.filter((game) => game.queued); // Find interrupted active download (download that was running when app closed)
// Mark it as paused but remember it for auto-resume
if (download.status === "active" && !interruptedDownload) {
interruptedDownload = download;
await downloadsSublevel.put(downloadKey, {
...download,
status: "paused",
});
} else if (download.status === "active") {
// Mark other active downloads as paused
await downloadsSublevel.put(downloadKey, {
...download,
status: "paused",
});
}
}
const downloadsToSeed = downloads.filter( // Re-fetch downloads after status updates
const updatedDownloads = await downloadsSublevel
.values()
.all()
.then((games) => orderBy(games, "timestamp", "desc"));
// Prioritize interrupted download, then queued downloads
const downloadToResume =
interruptedDownload ?? updatedDownloads.find((game) => game.queued);
const downloadsToSeed = updatedDownloads.filter(
(game) => (game) =>
game.shouldSeed && game.shouldSeed &&
game.downloader === Downloader.Torrent && game.downloader === Downloader.Torrent &&
@@ -92,7 +122,22 @@ export const loadState = async () => {
game.uri !== null game.uri !== null
); );
await DownloadManager.startRPC(nextItemOnQueue, downloadsToSeed); // For torrents or if JS downloader is disabled, use Python RPC
const isTorrent = downloadToResume?.downloader === Downloader.Torrent;
const useJsDownloader =
userPreferences?.useNativeHttpDownloader && !isTorrent;
if (useJsDownloader && downloadToResume) {
// Start Python RPC for seeding only, then resume HTTP download with JS
await DownloadManager.startRPC(undefined, downloadsToSeed);
await DownloadManager.startDownload(downloadToResume).catch((err) => {
// If resume fails, just log it - user can manually retry
logger.error("Failed to auto-resume download:", err);
});
} else {
// Use Python RPC for everything (torrent or fallback)
await DownloadManager.startRPC(downloadToResume, downloadsToSeed);
}
startMainLoop(); startMainLoop();

View File

@@ -147,6 +147,32 @@ export class JsHttpDownloader {
signal: this.abortController?.signal, signal: this.abortController?.signal,
}); });
// Handle 416 Range Not Satisfiable - existing file is larger than server file
// This happens when downloading same game from different source
if (response.status === 416 && startByte > 0) {
logger.log(
"[JsHttpDownloader] Range not satisfiable, deleting existing file and restarting"
);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
this.bytesDownloaded = 0;
this.resetSpeedTracking();
// Retry without Range header
const headersWithoutRange = { ...requestHeaders };
delete headersWithoutRange["Range"];
return this.executeDownload(
url,
headersWithoutRange,
filePath,
0,
savePath,
usedFallback
);
}
if (!response.ok && response.status !== 206) { if (!response.ok && response.status !== 206) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }