From 729aa302532c63707ece86dd980550d35c036464 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Wed, 4 Mar 2026 18:09:13 +0100 Subject: [PATCH] Release v1.6.20 Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/main/download-manager.ts | 14 +++++++++----- src/main/extractor.ts | 19 +++++++++++++++---- src/renderer/App.tsx | 10 ++++++---- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 6b9a3f5..592ee55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.6.19", + "version": "1.6.20", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 3225d4f..9fcfc22 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -4191,7 +4191,11 @@ export class DownloadManager extends EventEmitter { return; } - this.consecutiveReconnects += 1; + // Only increment when not already inside an active reconnect window to avoid + // inflating the backoff counter when multiple parallel downloads hit 429/503. + if (this.session.reconnectUntil <= nowMs()) { + this.consecutiveReconnects += 1; + } const backoffMultiplier = Math.min(this.consecutiveReconnects, 5); const waitMs = this.settings.reconnectWaitSeconds * 1000 * backoffMultiplier; const maxWaitMs = this.settings.reconnectWaitSeconds * 2 * 1000; @@ -5037,10 +5041,6 @@ export class DownloadManager extends EventEmitter { if (responseText && responseText !== "Unbekannter Fehler" && !/(^|\b)http\s*\d{3}\b/i.test(responseText)) { lastError = `HTTP ${response.status}: ${responseText}`; } - if (this.settings.autoReconnect && [429, 503].includes(response.status)) { - this.requestReconnect(`HTTP ${response.status}`); - throw new Error(lastError); - } if (attempt < maxAttempts) { item.retries += 1; item.fullStatus = `Serverfehler ${response.status}, retry ${attempt}/${retryDisplayLimit}`; @@ -5048,6 +5048,10 @@ export class DownloadManager extends EventEmitter { await sleep(retryDelayWithJitter(attempt, 250)); continue; } + if (this.settings.autoReconnect && [429, 503].includes(response.status)) { + this.requestReconnect(`HTTP ${response.status}`); + throw new Error(lastError); + } throw new Error(lastError); } diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 6d78890..5d7b889 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -466,7 +466,7 @@ export function classifyExtractionError(errorText: string): ExtractErrorCategory function isExtractAbortError(errorText: string): boolean { const text = String(errorText || "").toLowerCase(); - return text.includes("aborted:extract") || text.includes("extract_aborted"); + return text.includes("aborted:extract") || text.includes("extract_aborted") || text.includes("noextractor:skipped"); } export function archiveFilenamePasswords(archiveName: string): string[] { @@ -872,10 +872,17 @@ function resolveJvmExtractorRootCandidates(): string[] { } let cachedJvmLayout: JvmExtractorLayout | null | undefined; +let cachedJvmLayoutNullSince = 0; +const JVM_LAYOUT_NULL_TTL_MS = 5 * 60 * 1000; function resolveJvmExtractorLayout(): JvmExtractorLayout | null { if (cachedJvmLayout !== undefined) { - return cachedJvmLayout; + // Don't cache null permanently — retry after TTL in case Java was installed + if (cachedJvmLayout === null && Date.now() - cachedJvmLayoutNullSince > JVM_LAYOUT_NULL_TTL_MS) { + cachedJvmLayout = undefined; + } else { + return cachedJvmLayout; + } } const javaCandidates = resolveJavaCommandCandidates(); const javaCommand = javaCandidates.find((candidate) => { @@ -908,6 +915,7 @@ function resolveJvmExtractorLayout(): JvmExtractorLayout | null { } cachedJvmLayout = null; + cachedJvmLayoutNullSince = Date.now(); return null; } @@ -1958,9 +1966,12 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{ let noExtractorEncountered = false; const extractSingleArchive = async (archivePath: string): Promise => { - if (options.signal?.aborted || noExtractorEncountered) { + if (options.signal?.aborted) { throw new Error("aborted:extract"); } + if (noExtractorEncountered) { + throw new Error("noextractor:skipped"); + } const archiveName = path.basename(archivePath); const archiveResumeKey = archiveNameKey(archiveName); const archiveStartedAt = Date.now(); @@ -2057,11 +2068,11 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{ emitProgress(extracted + failed, archiveName, "extracting", archivePercent, Date.now() - archiveStartedAt); } } catch (error) { - failed += 1; const errorText = String(error); if (isExtractAbortError(errorText)) { throw error; } + failed += 1; lastError = errorText; const errorCategory = classifyExtractionError(errorText); logger.error(`Entpack-Fehler ${path.basename(archivePath)} [${errorCategory}]: ${errorText}`); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 124f331..682ed58 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -97,6 +97,7 @@ const providerLabels: Record = { }; function formatDateTime(ts: number): string { + if (!ts) return ""; const d = new Date(ts); const dd = String(d.getDate()).padStart(2, "0"); const mm = String(d.getMonth() + 1).padStart(2, "0"); @@ -1163,7 +1164,7 @@ export function App(): ReactElement { const active = collectorTabsRef.current.find((t) => t.id === activeId) ?? collectorTabsRef.current[0]; const rawText = active?.text ?? ""; const persisted = await persistDraftSettings(); - const existingIds = new Set(Object.keys(snapshot.session.packages)); + const existingIds = new Set(Object.keys(snapshotRef.current.session.packages)); const result = await window.rd.addLinks({ rawText, packageName: persisted.packageName }); if (result.addedLinks > 0) { showToast(`${result.addedPackages} Paket(e), ${result.addedLinks} Link(s) hinzugefügt`); @@ -1182,7 +1183,7 @@ export function App(): ReactElement { const files = await window.rd.pickContainers(); if (files.length === 0) { return; } await persistDraftSettings(); - const existingIds = new Set(Object.keys(snapshot.session.packages)); + const existingIds = new Set(Object.keys(snapshotRef.current.session.packages)); const result = await window.rd.addContainers(files); if (result.addedLinks > 0) { showToast(`DLC importiert: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`); @@ -1209,7 +1210,7 @@ export function App(): ReactElement { if (dlc.length > 0) { await performQuickAction(async () => { await persistDraftSettings(); - const existingIds = new Set(Object.keys(snapshot.session.packages)); + const existingIds = new Set(Object.keys(snapshotRef.current.session.packages)); const result = await window.rd.addContainers(dlc); if (result.addedLinks > 0) { showToast(`Drag-and-Drop: ${result.addedPackages} Paket(e), ${result.addedLinks} Link(s)`); @@ -3408,7 +3409,8 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs || a.speedBps !== b.speedBps || a.retries !== b.retries || a.provider !== b.provider - || a.fullStatus !== b.fullStatus) { + || a.fullStatus !== b.fullStatus + || a.onlineStatus !== b.onlineStatus) { return false; } }