From 556cbd1d8502c2a76040e695ea7d721f4932bbd6 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Sat, 7 Mar 2026 22:26:49 +0100 Subject: [PATCH] Fix auto-recovery for CRC-corrupt archives with correct file sizes - Trust extractor CRC verdict over file size checks - Re-queue incomplete downloads instead of just warning Co-Authored-By: Claude Opus 4.6 --- src/main/download-manager.ts | 33 ++++++++++++++++++++++++++++----- tests/download-manager.test.ts | 15 ++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 60a12d9..ccd0b04 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -4226,12 +4226,21 @@ export class DownloadManager extends EventEmitter { return 0; } - const corruptArchiveItems = archiveItems - .map((item) => ({ item, state: inspectPackageItemDiskState(pkg, item) })) + const inspectedArchiveItems = archiveItems + .map((item) => ({ item, state: inspectPackageItemDiskState(pkg, item) })); + const corruptArchiveItems = inspectedArchiveItems .filter(({ state }) => state.reason !== "ok"); + if (corruptArchiveItems.length === 0) { - logger.warn(`Auto-Recovery (${scope}): ${failure.archiveName} uebersprungen - kein lokaler Dateifehler nachweisbar`); - return 0; + // The extractor confirmed corruption (CRC error) but all files look + // correct by size. This happens when content is corrupt despite having + // the right byte count (e.g. network corruption during download). + // Trust the extractor verdict and force re-download of ALL archive parts. + logger.warn( + `Auto-Recovery (${scope}): ${failure.archiveName} - Dateien korrekte Groesse aber Extractor meldet CRC-Fehler, ` + + `erzwinge Re-Download aller ${archiveItems.length} Parts` + ); + corruptArchiveItems.push(...inspectedArchiveItems); } const queuedAt = nowMs(); @@ -7814,7 +7823,21 @@ export class DownloadManager extends EventEmitter { item.updatedAt = nowMs(); this.recordRunOutcome(item.id, "completed"); } else if (stat.size > 0) { - logger.warn(`Item-Recovery: ${item.fileName} übersprungen – Datei zu klein (${humanSize(stat.size)}, erwartet mind. ${humanSize(minSize)})`); + // File exists but is clearly incomplete — delete and re-queue for download. + logger.warn(`Item-Recovery: ${item.fileName} unvollstaendig (${humanSize(stat.size)}, erwartet mind. ${humanSize(minSize)}), loesche und re-queue`); + try { + fs.rmSync(item.targetPath, { force: true }); + } catch { /* ignore */ } + this.releaseTargetPath(item.id); + this.dropItemContribution(item.id); + item.targetPath = ""; + item.status = "queued"; + item.attempts = 0; + item.downloadedBytes = 0; + item.progressPercent = 0; + item.speedBps = 0; + item.fullStatus = "Wartet (unvollständiger Download)"; + item.updatedAt = nowMs(); } } catch { // file doesn't exist, nothing to recover diff --git a/tests/download-manager.test.ts b/tests/download-manager.test.ts index d5cc427..fdee9ff 100644 --- a/tests/download-manager.test.ts +++ b/tests/download-manager.test.ts @@ -2042,7 +2042,7 @@ describe("download manager", () => { expect(session.packages[packageId]?.status).toBe("queued"); }); - it("does not requeue completed archive parts without local file evidence", () => { + it("requeues completed archive parts on CRC error even when file size matches", () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-")); tempDirs.push(root); @@ -2121,13 +2121,18 @@ describe("download manager", () => { "hybrid" ); - expect(changed).toBe(0); + // CRC error from extractor IS evidence of corruption — even when files + // have the right size, content may be corrupt. Must force re-download. + expect(changed).toBe(2); for (const itemId of itemIds) { const item = session.items[itemId]!; - expect(item.status).toBe("completed"); - expect(item.targetPath).toContain(".rar"); - expect(item.downloadedBytes).toBe(archiveSize); + expect(item.status).toBe("queued"); + expect(item.targetPath).toBe(""); + expect(item.downloadedBytes).toBe(0); + expect(item.fullStatus).toContain("Auto-Recovery"); } + expect(fs.existsSync(path.join(outputDir, archiveNames[0]!))).toBe(false); + expect(fs.existsSync(path.join(outputDir, archiveNames[1]!))).toBe(false); }); it("does not treat rev files as ready archive parts during disk fallback", async () => {