From 24e457d84d5ca4daa7369eb52dd6b96467d5d9a2 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Sat, 7 Mar 2026 20:14:11 +0100 Subject: [PATCH] Revalidate completed items on startup, fix stale session data Items incorrectly marked as "completed" by the old 50% recovery threshold persist in the session file across updates. On startup, check all completed items: if the file on disk is smaller than expected totalBytes, reset to "queued" so it gets re-downloaded. Also reset items whose files are missing. Co-Authored-By: Claude Opus 4.6 --- src/main/download-manager.ts | 37 ++++++++++++++++++++++++++++++++++++ src/renderer/App.tsx | 6 +++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index e56e565..9a48e2b 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -1074,6 +1074,7 @@ export class DownloadManager extends EventEmitter { this.recoverPostProcessingOnStartup(); this.resolveExistingQueuedOpaqueFilenames(); this.restoreTargetPathReservations(); + this.revalidateCompletedItems(); this.checkExistingRapidgatorLinks(); void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (constructor): ${compactErrorText(err)}`)); } @@ -3992,6 +3993,42 @@ export class DownloadManager extends EventEmitter { this.fixDuplicateSuffixFiles(); } + /** Re-validate "completed" items on startup: if the file on disk is significantly + * smaller than expected, the item was incorrectly marked completed (e.g. by the + * old 50% recovery threshold). Reset to "queued" so it gets re-downloaded. */ + private revalidateCompletedItems(): void { + let fixed = 0; + for (const item of Object.values(this.session.items)) { + if (item.status !== "completed") continue; + if (!item.targetPath || !item.totalBytes || item.totalBytes <= 0) continue; + try { + const stat = fs.statSync(item.targetPath); + if (stat.size < item.totalBytes - ALLOCATION_UNIT_SIZE) { + logger.warn(`revalidateCompleted: ${item.fileName} ist nur ${humanSize(stat.size)} statt ${humanSize(item.totalBytes)}, setze auf queued`); + item.status = "queued"; + item.fullStatus = "Wartet"; + item.downloadedBytes = stat.size; + item.progressPercent = Math.floor((stat.size / item.totalBytes) * 100); + item.speedBps = 0; + fixed += 1; + } + } catch { + // file doesn't exist — reset to queued so it gets re-downloaded + logger.warn(`revalidateCompleted: ${item.fileName} Datei nicht gefunden, setze auf queued`); + item.status = "queued"; + item.fullStatus = "Wartet"; + item.downloadedBytes = 0; + item.progressPercent = 0; + item.speedBps = 0; + fixed += 1; + } + } + if (fixed > 0) { + logger.info(`revalidateCompletedItems: ${fixed} Items korrigiert`); + this.persistSoon(); + } + } + /** Detect items whose targetPath has a " (N)" suffix from a previous bug and rename * them back to the original filename if the original path is not claimed by another item. */ private fixDuplicateSuffixFiles(): void { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 2c62d28..231f349 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -5363,7 +5363,7 @@ interface PackageCardProps { selectedIds: Set; columnOrder: string[]; gridTemplate: string; - onSelect: (id: string, ctrlKey: boolean) => void; + onSelect: (id: string, ctrlKey: boolean, shiftKey: boolean) => void; onSelectMouseDown: (id: string, e: React.MouseEvent) => void; onSelectMouseEnter: (id: string) => void; onStartEdit: (packageId: string, packageName: string) => void; @@ -5422,7 +5422,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs className={`package-card queue-package-card${pkg.enabled ? "" : " disabled-pkg"}${selectedIds.has(pkg.id) ? " pkg-selected" : ""}`} draggable onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu(pkg.id, undefined, e.clientX, e.clientY); }} - onClick={(e) => { if (e.ctrlKey) onSelect(pkg.id, true); }} + onClick={(e) => { if (e.ctrlKey || e.shiftKey) onSelect(pkg.id, e.ctrlKey, e.shiftKey); }} onMouseDown={(e) => onSelectMouseDown(pkg.id, e)} onMouseEnter={() => onSelectMouseEnter(pkg.id)} onDragStart={(event) => { event.stopPropagation(); onDragStart(pkg.id); }} @@ -5504,7 +5504,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs {useExtractSplit &&
}
{!collapsed && items.filter((item) => !hideExtractedItems || !item.fullStatus?.startsWith("Entpackt")).map((item) => ( -
{ e.stopPropagation(); onSelect(item.id, e.ctrlKey); }} onMouseDown={(e) => { e.stopPropagation(); onSelectMouseDown(item.id, e); }} onMouseEnter={() => onSelectMouseEnter(item.id)} onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu(pkg.id, item.id, e.clientX, e.clientY); }}> +
{ e.stopPropagation(); onSelect(item.id, e.ctrlKey, e.shiftKey); }} onMouseDown={(e) => { e.stopPropagation(); onSelectMouseDown(item.id, e); }} onMouseEnter={() => onSelectMouseEnter(item.id)} onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu(pkg.id, item.id, e.clientX, e.clientY); }}> {columnOrder.map((col) => { switch (col) { case "name": return (