diff --git a/package.json b/package.json index 5695cfc..cee0a51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.93", + "version": "1.5.94", "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 1b755a4..3adaadc 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -3728,7 +3728,9 @@ export class DownloadManager extends EventEmitter { } delete this.session.packages[packageId]; this.session.packageOrder = this.session.packageOrder.filter((id) => id !== packageId); - this.runPackageIds.delete(packageId); + // Keep packageId in runPackageIds so the "size > 0" guard still filters + // other packages. The deleted package has no items left, so the scheduler + // simply won't find anything for it. finishRun() clears runPackageIds. this.runCompletedPackages.delete(packageId); this.hybridExtractRequeue.delete(packageId); this.resetSessionTotalsIfQueueEmpty(); @@ -5716,9 +5718,11 @@ export class DownloadManager extends EventEmitter { if (!allMissingExistOnDisk) { continue; } + // Any non-completed item blocks extraction — cancelled/stopped items may + // have partial files on disk that would corrupt the extraction. const anyActivelyProcessing = missingParts.some((part) => { const status = pendingItemStatus.get(pathKey(part)); - return status !== undefined && status !== "failed" && status !== "cancelled"; + return status !== undefined && status !== "failed"; }); if (anyActivelyProcessing) { continue; @@ -6008,6 +6012,11 @@ export class DownloadManager extends EventEmitter { pkg.updatedAt = nowMs(); return; } + // Immediately clean up extracted items if "Sofort" policy is active + this.applyPackageDoneCleanup(packageId); + if (!this.session.packages[packageId]) { + return; // Package was fully cleaned up + } pkg.status = (pkg.enabled && !this.session.paused) ? "downloading" : "paused"; pkg.updatedAt = nowMs(); this.emitState(); diff --git a/src/main/storage.ts b/src/main/storage.ts index 5436e23..e47499c 100644 --- a/src/main/storage.ts +++ b/src/main/storage.ts @@ -18,6 +18,7 @@ const VALID_DOWNLOAD_STATUSES = new Set([ "queued", "validating", "downloading", "paused", "reconnect_wait", "extracting", "integrity_check", "completed", "failed", "cancelled" ]); const VALID_ITEM_PROVIDERS = new Set(["realdebrid", "megadebrid", "bestdebrid", "alldebrid"]); +const VALID_ONLINE_STATUSES = new Set(["online", "offline", "checking"]); function asText(value: unknown): string { return String(value ?? "").trim(); @@ -258,7 +259,6 @@ function normalizeLoadedSession(raw: unknown): SessionState { const providerRaw = asText(item.provider) as DebridProvider; const onlineStatusRaw = asText(item.onlineStatus); - const validOnlineStatuses = new Set(["online", "offline", "checking"]); itemsById[id] = { id, @@ -277,7 +277,7 @@ function normalizeLoadedSession(raw: unknown): SessionState { attempts: clampNumber(item.attempts, 0, 0, 10_000), lastError: asText(item.lastError), fullStatus: asText(item.fullStatus), - onlineStatus: validOnlineStatuses.has(onlineStatusRaw) ? onlineStatusRaw as "online" | "offline" | "checking" : undefined, + onlineStatus: VALID_ONLINE_STATUSES.has(onlineStatusRaw) ? onlineStatusRaw as "online" | "offline" | "checking" : undefined, createdAt: clampNumber(item.createdAt, now, 0, Number.MAX_SAFE_INTEGER), updatedAt: clampNumber(item.updatedAt, now, 0, Number.MAX_SAFE_INTEGER) }; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index ae23fc0..9abc9de 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -3078,6 +3078,11 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs const extracted = items.filter((item) => item.fullStatus?.startsWith("Entpackt")).length; const extracting = items.some((item) => item.fullStatus?.startsWith("Entpack")); const total = Math.max(1, items.length); + // Use 50/50 split when extraction is active OR package is in extracting state + // (prevents bar jumping from 100% to 50% when extraction starts) + const allDownloaded = done + failed + cancelled >= total; + const allExtracted = extracted >= total; + const useExtractSplit = extracting || pkg.status === "extracting" || (allDownloaded && !allExtracted && done > 0 && extracted > 0); // Include fractional progress from active downloads so the bar moves continuously const activeProgress = items.reduce((sum, item) => { if (item.status === "downloading" || (item.status === "queued" && (item.progressPercent || 0) > 0)) { @@ -3085,7 +3090,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs } return sum; }, 0); - const dlProgress = Math.floor(((done + activeProgress) / total) * (extracting ? 50 : 100)); + const dlProgress = 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 || ""; @@ -3095,7 +3100,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs return sum; }, 0); const exProgress = Math.floor(((extracted + extractingProgress) / total) * 50); - const combinedProgress = extracting ? dlProgress + exProgress : dlProgress; + const combinedProgress = useExtractSplit ? dlProgress + exProgress : dlProgress; const onKeyDown = (e: KeyboardEvent): void => { if (e.key === "Enter") { onFinishEdit(pkg.id, pkg.name, editingName); }