From d9fe98231ff21736c19896f3706961d160e2f158 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Tue, 3 Mar 2026 21:35:12 +0100 Subject: [PATCH] Extract packages sequentially instead of in parallel Previously maxParallelExtract allowed multiple packages to extract simultaneously, splitting I/O across packages. Now packages extract one at a time in packageOrder so each package finishes faster. Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/main/download-manager.ts | 68 +++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ce83e13..9221c21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.69", + "version": "1.5.70", "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 e710c2f..d5d4b48 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -2286,6 +2286,69 @@ export class DownloadManager extends EventEmitter { }); } + public resetPackage(packageId: string): void { + const pkg = this.session.packages[packageId]; + if (!pkg) return; + + const itemIds = [...pkg.itemIds]; + + // 1. Abort active downloads for items in THIS package only + for (const itemId of itemIds) { + const item = this.session.items[itemId]; + if (!item) continue; + + const active = this.activeTasks.get(itemId); + if (active) { + active.abortReason = "cancel"; + active.abortController.abort("cancel"); + } + + // Delete partial download file + const targetPath = String(item.targetPath || "").trim(); + if (targetPath) { + try { fs.rmSync(targetPath, { force: true }); } catch { /* ignore */ } + this.releaseTargetPath(itemId); + } + + // Reset item state + this.dropItemContribution(itemId); + this.runOutcomes.delete(itemId); + this.runItemIds.delete(itemId); + this.retryAfterByItem.delete(itemId); + this.retryStateByItem.delete(itemId); + + item.status = "queued"; + item.downloadedBytes = 0; + item.totalBytes = null; + item.progressPercent = 0; + item.speedBps = 0; + item.attempts = 0; + item.retries = 0; + item.lastError = ""; + item.resumable = true; + item.targetPath = ""; + item.provider = null; + item.fullStatus = "Wartet"; + item.updatedAt = nowMs(); + } + + // 2. Abort post-processing (extraction) if active for THIS package + const postProcessController = this.packagePostProcessAbortControllers.get(packageId); + if (postProcessController && !postProcessController.signal.aborted) { + postProcessController.abort("reset"); + } + + // 3. Reset package state + pkg.status = "queued"; + pkg.cancelled = false; + pkg.enabled = true; + pkg.updatedAt = nowMs(); + + logger.info(`Paket "${pkg.name}" zurückgesetzt (${itemIds.length} Items)`); + this.persistSoon(); + this.emitState(true); + } + public async startPackages(packageIds: string[]): Promise { const targetSet = new Set(packageIds); @@ -2997,7 +3060,10 @@ export class DownloadManager extends EventEmitter { } private async acquirePostProcessSlot(packageId: string): Promise { - const maxConcurrent = this.settings.maxParallelExtract || 2; + // Extract packages sequentially (one at a time) to focus I/O on finishing + // the earliest package first. maxParallelExtract is reserved for future + // intra-package parallelism. + const maxConcurrent = 1; if (this.packagePostProcessActive < maxConcurrent) { this.packagePostProcessActive += 1; return;