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 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-07 22:26:49 +01:00
parent 2a429b49c0
commit 556cbd1d85
2 changed files with 38 additions and 10 deletions

View File

@ -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

View File

@ -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 () => {