diff --git a/package.json b/package.json index 616b6cc..5513889 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.6.16", + "version": "1.6.17", "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 8bf3408..8ea951a 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -2919,6 +2919,7 @@ export class DownloadManager extends EventEmitter { this.runOutcomes.clear(); this.runCompletedPackages.clear(); this.retryAfterByItem.clear(); + this.retryStateByItem.clear(); this.session.running = true; this.session.paused = false; diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 8c5a93e..6d78890 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -22,7 +22,7 @@ const JVM_EXTRACTOR_REQUIRED_LIBS = [ ]; // ── subst drive mapping for long paths on Windows ── -const SUBST_THRESHOLD = 100; +const SUBST_THRESHOLD = 200; const activeSubstDrives = new Set(); function findFreeSubstDrive(): string | null { @@ -2118,7 +2118,12 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{ const workerCount = Math.min(maxParallel, parallelQueue.length); logger.info(`Parallele Extraktion: ${workerCount} gleichzeitige Worker für ${parallelQueue.length} Archive`); + // Snapshot passwordCandidates before parallel extraction to avoid concurrent mutation. + // Each worker reads the same promoted order from the serial password-discovery pass. + const frozenPasswords = [...passwordCandidates]; await Promise.all(Array.from({ length: workerCount }, () => worker())); + // Restore passwordCandidates from frozen snapshot (parallel mutations are discarded). + passwordCandidates = frozenPasswords; if (abortError) throw new Error("aborted:extract"); } diff --git a/src/main/storage.ts b/src/main/storage.ts index f12354e..b261eff 100644 --- a/src/main/storage.ts +++ b/src/main/storage.ts @@ -140,7 +140,8 @@ export function normalizeSettings(settings: AppSettings): AppSettings { theme: VALID_THEMES.has(settings.theme) ? settings.theme : defaults.theme, bandwidthSchedules: normalizeBandwidthSchedules(settings.bandwidthSchedules), columnOrder: normalizeColumnOrder(settings.columnOrder), - extractCpuPriority: settings.extractCpuPriority + extractCpuPriority: settings.extractCpuPriority, + autoExtractWhenStopped: settings.autoExtractWhenStopped !== undefined ? Boolean(settings.autoExtractWhenStopped) : defaults.autoExtractWhenStopped }; if (!VALID_PRIMARY_PROVIDERS.has(normalized.providerPrimary)) { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 4a8fdfb..f39d6e6 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -539,7 +539,6 @@ export function App(): ReactElement { const loadHistory = async (): Promise => { try { const entries = await window.rd.getHistory(); - console.log("History loaded:", entries); if (mountedRef.current && entries) { setHistoryEntries(entries); } @@ -1396,24 +1395,17 @@ export function App(): ReactElement { }; const removeCollectorTab = (id: string): void => { - let fallbackId = ""; setCollectorTabs((prev) => { - if (prev.length <= 1) { - return prev; - } + if (prev.length <= 1) return prev; const index = prev.findIndex((tabEntry) => tabEntry.id === id); - if (index < 0) { - return prev; - } + if (index < 0) return prev; const next = prev.filter((tabEntry) => tabEntry.id !== id); if (activeCollectorTabRef.current === id) { - fallbackId = next[Math.max(0, index - 1)]?.id ?? next[0]?.id ?? ""; + const fallbackId = next[Math.max(0, index - 1)]?.id ?? next[0]?.id ?? ""; + if (fallbackId) setTimeout(() => setActiveCollectorTab(fallbackId), 0); } return next; }); - if (fallbackId) { - setActiveCollectorTab(fallbackId); - } }; const onPackageDragStart = useCallback((packageId: string) => { @@ -1817,7 +1809,10 @@ export function App(): ReactElement { useEffect(() => { const onKey = (e: KeyboardEvent): void => { - if (e.key === "Escape") setSelectedIds(new Set()); + if (e.key === "Escape") { + const target = e.target as HTMLElement; + if (target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") setSelectedIds(new Set()); + } if (e.key === "Delete" && selectedIds.size > 0) { const target = e.target as HTMLElement; if (target.tagName === "INPUT" || target.tagName === "TEXTAREA") return; @@ -2067,22 +2062,25 @@ export function App(): ReactElement { onChange={(e) => { const val = Math.max(1, Math.min(50, Number(e.target.value) || 1)); settingsDirtyRef.current = true; + const rev = ++settingsDraftRevisionRef.current; setSettingsDraft((prev) => ({ ...prev, maxParallel: val })); - void window.rd.updateSettings({ maxParallel: val }).finally(() => { settingsDirtyRef.current = false; }); + void window.rd.updateSettings({ maxParallel: val }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; }); }} />
@@ -2095,8 +2093,9 @@ export function App(): ReactElement { onChange={(e) => { const next = e.target.checked; settingsDirtyRef.current = true; + const rev = ++settingsDraftRevisionRef.current; setSettingsDraft((prev) => ({ ...prev, speedLimitEnabled: next })); - void window.rd.updateSettings({ speedLimitEnabled: next }).finally(() => { settingsDirtyRef.current = false; }); + void window.rd.updateSettings({ speedLimitEnabled: next }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; }); }} />
@@ -2115,8 +2114,9 @@ export function App(): ReactElement { } const kbps = Math.floor(parsed * 1024); settingsDirtyRef.current = true; + const rev = ++settingsDraftRevisionRef.current; setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: kbps })); - void window.rd.updateSettings({ speedLimitKbps: kbps }).finally(() => { settingsDirtyRef.current = false; }); + void window.rd.updateSettings({ speedLimitKbps: kbps }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; }); setSpeedLimitInput(formatMbpsInputFromKbps(kbps)); }} /> @@ -2125,16 +2125,18 @@ export function App(): ReactElement { const cur = (settingsDraft.speedLimitKbps || 0) / 1024; const next = Math.floor((cur + 1) * 1024); settingsDirtyRef.current = true; + const rev = ++settingsDraftRevisionRef.current; setSettingsDraft((prev) => ({ ...prev, speedLimitKbps: next })); - void window.rd.updateSettings({ speedLimitKbps: next }).finally(() => { settingsDirtyRef.current = false; }); + void window.rd.updateSettings({ speedLimitKbps: next }).finally(() => { if (settingsDraftRevisionRef.current === rev) settingsDirtyRef.current = false; }); setSpeedLimitInput(formatMbpsInputFromKbps(next)); }}>▲
@@ -2613,7 +2615,7 @@ export function App(): ReactElement { setText("packageName", e.target.value)} />
-
setNum("maxParallel", Number(e.target.value) || 1)} />
+
setNum("maxParallel", Math.max(1, Math.min(50, Number(e.target.value) || 1)))} />
setNum("retryLimit", Math.max(0, Math.min(99, Number(e.target.value) || 0)))} />