From 74920e2e2f754c4522b10d305559a8bfa90695b8 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Wed, 4 Mar 2026 22:14:03 +0100 Subject: [PATCH] Round 8 bug fixes (20 fixes) Co-Authored-By: Claude Opus 4.6 --- src/main/debrid.ts | 2 +- src/main/download-manager.ts | 20 ++++++++++++-------- src/main/extractor.ts | 8 +++++++- src/main/storage.ts | 2 ++ src/renderer/App.tsx | 15 ++++++++------- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/main/debrid.ts b/src/main/debrid.ts index 04e40da..4141d94 100644 --- a/src/main/debrid.ts +++ b/src/main/debrid.ts @@ -640,7 +640,7 @@ class MegaDebridClient { throw new Error("Mega-Web Antwort ohne Download-Link"); } if (!lastError) { - lastError = web ? "Mega-Web Antwort ohne Download-Link" : "Mega-Web Antwort leer"; + lastError = "Mega-Web Antwort leer"; } // Don't retry permanent hoster errors (dead link, file removed, etc.) if (/permanent ungültig|hosternotavailable|file.?not.?found|file.?unavailable|link.?is.?dead/i.test(lastError)) { diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 5955b50..227bf61 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -1284,8 +1284,6 @@ export class DownloadManager extends EventEmitter { this.packagePostProcessWaiters = []; this.summary = null; this.nonResumableActive = 0; - this.retryAfterByItem.clear(); - this.retryStateByItem.clear(); this.resetSessionTotalsIfQueueEmpty(); this.persistNow(); this.emitState(true); @@ -1540,10 +1538,11 @@ export class DownloadManager extends EventEmitter { item.attempts = 0; item.lastError = ""; item.fullStatus = "Wartet"; + item.provider = null; item.updatedAt = nowMs(); this.assignItemTargetPath(item, path.join(pkg.outputDir, sanitizeFilename(item.fileName || filenameFromUrl(item.url)))); this.runOutcomes.delete(itemId); - this.itemContributedBytes.delete(itemId); + this.dropItemContribution(itemId); this.retryAfterByItem.delete(itemId); this.retryStateByItem.delete(itemId); if (this.session.running) { @@ -2823,6 +2822,7 @@ export class DownloadManager extends EventEmitter { } // Not running: start with only items from specified packages + this.triggerPendingExtractions(); const runItems = Object.values(this.session.items) .filter((item) => { if (!targetSet.has(item.packageId)) return false; @@ -2928,6 +2928,7 @@ export class DownloadManager extends EventEmitter { } // Not running: start with only specified items + this.triggerPendingExtractions(); const runItems = [...targetSet] .map((id) => this.session.items[id]) .filter((item) => { @@ -3049,6 +3050,7 @@ export class DownloadManager extends EventEmitter { this.runOutcomes.clear(); this.runCompletedPackages.clear(); this.retryAfterByItem.clear(); + this.retryStateByItem.clear(); this.reservedTargetPaths.clear(); this.claimedTargetPathByItem.clear(); this.session.running = false; @@ -3323,7 +3325,8 @@ export class DownloadManager extends EventEmitter { || item.status === "paused" || item.status === "reconnect_wait") { item.status = "queued"; - item.fullStatus = "Wartet"; + const itemPkg = this.session.packages[item.packageId]; + item.fullStatus = (itemPkg && itemPkg.enabled === false) ? "Paket gestoppt" : "Wartet"; item.speedBps = 0; item.updatedAt = nowMs(); } @@ -3837,7 +3840,7 @@ export class DownloadManager extends EventEmitter { let changed = false; for (const packageId of packageIds) { const pkg = this.session.packages[packageId]; - if (!pkg || pkg.cancelled) { + if (!pkg || pkg.cancelled || !pkg.enabled) { continue; } @@ -5714,6 +5717,7 @@ export class DownloadManager extends EventEmitter { try { await fs.promises.rm(effectiveTargetPath, { force: true }); } catch { /* ignore */ } + this.releaseTargetPath(active.itemId); this.dropItemContribution(active.itemId); item.downloadedBytes = 0; item.progressPercent = 0; @@ -6396,7 +6400,7 @@ export class DownloadManager extends EventEmitter { } const updatedAt = nowMs(); for (const entry of archItems) { - if (!isExtractedLabel(entry.fullStatus)) { + if (!isExtractedLabel(entry.fullStatus) && entry.fullStatus !== label) { entry.fullStatus = label; entry.updatedAt = updatedAt; } @@ -6536,7 +6540,7 @@ export class DownloadManager extends EventEmitter { if (!allDone && this.settings.hybridExtract && this.settings.autoExtract && failed === 0 && success > 0) { await this.runHybridExtraction(packageId, pkg, items, signal); if (signal?.aborted) { - pkg.status = (pkg.enabled && !this.session.paused) ? "queued" : "paused"; + pkg.status = (pkg.enabled && this.session.running && !this.session.paused) ? "queued" : "paused"; pkg.updatedAt = nowMs(); return; } @@ -6764,8 +6768,8 @@ export class DownloadManager extends EventEmitter { for (const entry of completedItems) { if (!isExtractedLabel(entry.fullStatus)) { entry.fullStatus = `Entpack-Fehler: ${timeoutReason}`; + entry.updatedAt = nowMs(); } - entry.updatedAt = nowMs(); } pkg.status = "failed"; pkg.updatedAt = nowMs(); diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 45ab299..4c2da2e 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -2122,7 +2122,13 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{ if (passwordCandidates.length > 1 && pendingCandidates.length > 1) { logger.info(`Passwort-Discovery: Extrahiere erstes Archiv seriell (${passwordCandidates.length} Passwort-Kandidaten)...`); const first = pendingCandidates[0]; - await extractSingleArchive(first); + try { + await extractSingleArchive(first); + } catch (err) { + const errText = String(err); + if (/aborted:extract/i.test(errText)) throw err; + // noextractor:skipped — handled by noExtractorEncountered flag below + } parallelQueue = pendingCandidates.slice(1); if (parallelQueue.length > 0) { logger.info(`Passwort-Discovery abgeschlossen, starte parallele Extraktion für ${parallelQueue.length} verbleibende Archive`); diff --git a/src/main/storage.ts b/src/main/storage.ts index 7116780..1bf238a 100644 --- a/src/main/storage.ts +++ b/src/main/storage.ts @@ -485,6 +485,7 @@ async function writeSettingsPayload(paths: StoragePaths, payload: string): Promi await fsp.copyFile(tempPath, paths.configFile); await fsp.rm(tempPath, { force: true }).catch(() => {}); } else { + await fsp.rm(tempPath, { force: true }).catch(() => {}); throw renameError; } } @@ -605,6 +606,7 @@ async function writeSessionPayload(paths: StoragePaths, payload: string, generat await fsp.copyFile(tempPath, paths.sessionFile); await fsp.rm(tempPath, { force: true }).catch(() => {}); } else { + await fsp.rm(tempPath, { force: true }).catch(() => {}); throw renameError; } } diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 30d5619..6a3125c 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -316,6 +316,7 @@ const BandwidthChart = memo(function BandwidthChart({ items, running, paused, sp useEffect(() => { const handleResize = () => { + if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current); animationFrameRef.current = requestAnimationFrame(drawChart); }; @@ -907,7 +908,7 @@ export function App(): ReactElement { return next; }); } - }, [packages, snapshot.session.items]); + }, [packages, snapshot.session.items, collapsedPackages]); const allPackagesCollapsed = useMemo(() => ( packages.length > 0 && packages.every((pkg) => collapsedPackages[pkg.id]) @@ -1855,7 +1856,7 @@ export function App(): ReactElement { const target = e.target as HTMLElement; if (target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") { // Don't clear selection if an overlay is open — let the overlay close first - if (document.querySelector(".ctx-menu") || document.querySelector(".modal-backdrop") || document.querySelector(".link-popup-overlay")) return; + if (document.querySelector(".ctx-menu") || document.querySelector(".modal-backdrop")) return; if (tabRef.current === "downloads") setSelectedIds(new Set()); else if (tabRef.current === "history") setSelectedHistoryIds(new Set()); } @@ -2869,7 +2870,7 @@ export function App(): ReactElement { const pkg = snapshot.session.packages[id]; if (pkg) { for (const iid of pkg.itemIds) removedItemIds.add(iid); } } - const totalRemaining = Object.keys(snapshot.session.items).length - removedItemIds.size; + const totalRemaining = Math.max(0, Object.keys(snapshot.session.items).length - removedItemIds.size); const parts: string[] = []; if (pkgCount > 0) parts.push(`${pkgCount} Paket(e)`); if (itemCount > 0) parts.push(`${itemCount} Link(s)`); @@ -3257,7 +3258,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs } return sum; }, 0); - const dlProgress = Math.floor(((done + activeProgress) / total) * (useExtractSplit ? 50 : 100)); + const dlProgress = Math.min(useExtractSplit ? 50 : 100, Math.floor(((done + activeProgress) / total) * (useExtractSplit ? 50 : 100))); // Include fractional progress from items currently being extracted const extractingProgress = items.reduce((sum, item) => { const fs = item.fullStatus || ""; @@ -3266,8 +3267,8 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs if (m) return sum + Number(m[1]) / 100; return sum; }, 0); - const exProgress = Math.floor(((extracted + extractingProgress) / total) * 50); - const combinedProgress = useExtractSplit ? dlProgress + exProgress : dlProgress; + const exProgress = Math.min(50, Math.floor(((extracted + extractingProgress) / total) * 50)); + const combinedProgress = Math.min(100, useExtractSplit ? dlProgress + exProgress : dlProgress); const onKeyDown = (e: KeyboardEvent): void => { if (e.key === "Enter") { onFinishEdit(pkg.id, pkg.name, editingName); } @@ -3396,7 +3397,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs ) : ""} ); - case "hoster": return {extractHoster(item.url) || ""}; + case "hoster": { const h = extractHoster(item.url) || ""; return {h}; } case "account": return {item.provider ? providerLabels[item.provider] : ""}; case "prio": return ; case "status": return (