From 8f6b87ae8cd71b55c9f4691c1cb1577d2c2e71c1 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Sat, 7 Mar 2026 14:18:09 +0100 Subject: [PATCH] Fix parallel extraction wrong_password false positives, preserve session download counter across stop/resume - Retry failed wrong_password archives serially after parallel extraction to recover from CRC mismatches caused by concurrent UnRAR I/O contention - Stop resetting sessionDownloadedBytes on start/resume so the session total accurately reflects all bytes downloaded since app launch Co-Authored-By: Claude Opus 4.6 --- src/main/download-manager.ts | 7 ++----- src/main/extractor.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 45b921d..8762a6c 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -3066,7 +3066,6 @@ export class DownloadManager extends EventEmitter { this.session.paused = false; this.session.runStartedAt = nowMs(); this.session.totalDownloadedBytes = 0; - this.sessionDownloadedBytes = 0; this.session.summaryText = ""; this.session.reconnectUntil = 0; this.session.reconnectReason = ""; @@ -3174,7 +3173,6 @@ export class DownloadManager extends EventEmitter { this.session.paused = false; this.session.runStartedAt = nowMs(); this.session.totalDownloadedBytes = 0; - this.sessionDownloadedBytes = 0; this.session.summaryText = ""; this.session.reconnectUntil = 0; this.session.reconnectReason = ""; @@ -3304,11 +3302,10 @@ export class DownloadManager extends EventEmitter { this.session.running = true; this.session.paused = false; - // By design: runStartedAt and totalDownloadedBytes reset on each start/resume so that - // duration, average speed, and ETA are calculated relative to the current run, not cumulative. + // Keep cumulative session bytes across stop/resume so the session total stays accurate. + // Only runStartedAt resets (for ETA/speed calculations relative to current run). this.session.runStartedAt = nowMs(); this.session.totalDownloadedBytes = 0; - this.sessionDownloadedBytes = 0; this.session.summaryText = ""; this.session.reconnectUntil = 0; this.session.reconnectReason = ""; diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 0fa9ba5..a794423 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -2742,6 +2742,35 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{ passwordCandidates = frozenPasswords; if (abortError) throw new Error("aborted:extract"); + + // ── Retry failed wrong_password archives serially ── + // Parallel UnRAR processes writing to the same target directory can cause + // CRC mismatches that are misreported as "Incorrect password". + // If any archive succeeded (i.e. the password is known), retry the failed + // ones one-at-a-time to eliminate false positives from I/O contention. + if (failed > 0 && extracted > 0) { + const failedArchives = parallelQueue.filter((ap) => !extractedArchives.has(ap) && !resumeCompleted.has(archiveNameKey(path.basename(ap)))); + if (failedArchives.length > 0) { + logger.info(`Serielle Wiederholung: ${failedArchives.length} fehlgeschlagene Archive werden einzeln wiederholt (mögliche Parallelitäts-Kollision)`); + let retryRecovered = 0; + for (const archivePath of failedArchives) { + if (options.signal?.aborted || noExtractorEncountered) break; + try { + // Reset failed count for this archive before retry + failed -= 1; + await extractSingleArchive(archivePath); + retryRecovered += 1; + } catch (retryError) { + const errText = String(retryError); + if (isExtractAbortError(errText)) throw retryError; + // extractSingleArchive already incremented failed and logged the error + } + } + if (retryRecovered > 0) { + logger.info(`Serielle Wiederholung: ${retryRecovered}/${failedArchives.length} Archive erfolgreich entpackt`); + } + } + } } if (noExtractorEncountered) {