diff --git a/package.json b/package.json index d22b5df..0939b16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.17", + "version": "1.5.18", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/app-controller.ts b/src/main/app-controller.ts index 82d12ab..7362365 100644 --- a/src/main/app-controller.ts +++ b/src/main/app-controller.ts @@ -212,6 +212,10 @@ export class AppController { this.manager.retryExtraction(packageId); } + public extractNow(packageId: string): void { + this.manager.extractNow(packageId); + } + public cancelPackage(packageId: string): void { this.manager.cancelPackage(packageId); } diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index a816c13..194ce2c 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -2976,6 +2976,25 @@ export class DownloadManager extends EventEmitter { void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (retryExtraction): ${compactErrorText(err)}`)); } + public extractNow(packageId: string): void { + const pkg = this.session.packages[packageId]; + if (!pkg || pkg.cancelled) return; + if (this.packagePostProcessTasks.has(packageId)) return; + const items = pkg.itemIds.map((id) => this.session.items[id]).filter(Boolean) as DownloadItem[]; + const completedItems = items.filter((item) => item.status === "completed"); + if (completedItems.length === 0) return; + pkg.status = "queued"; + pkg.updatedAt = nowMs(); + for (const item of completedItems) { + item.fullStatus = "Entpacken - Ausstehend"; + item.updatedAt = nowMs(); + } + logger.info(`Jetzt entpacken: pkg=${pkg.name}, completed=${completedItems.length}`); + this.persistSoon(); + this.emitState(true); + void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (extractNow): ${compactErrorText(err)}`)); + } + private removePackageFromSession(packageId: string, itemIds: string[]): void { const postProcessController = this.packagePostProcessAbortControllers.get(packageId); if (postProcessController && !postProcessController.signal.aborted) { @@ -4422,11 +4441,16 @@ export class DownloadManager extends EventEmitter { for (const itemId of pkg.itemIds) { const item = this.session.items[itemId]; - if (!item || item.status === "cancelled" || this.activeTasks.has(itemId)) { + if (!item || this.activeTasks.has(itemId)) { + continue; + } + // Only check failed or completed items — skip queued/cancelled to avoid + // expensive fs.stat calls on hundreds of items (caused 5-10s freeze on start). + if (item.status !== "failed" && item.status !== "completed") { continue; } - const is416Failure = this.isHttp416Failure(item); + const is416Failure = item.status === "failed" && this.isHttp416Failure(item); const hasZeroByteArchive = await this.hasZeroByteArchiveArtifact(item); if (item.status === "failed") { diff --git a/src/main/main.ts b/src/main/main.ts index 71542f8..87b1e6b 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -318,6 +318,10 @@ function registerIpcHandlers(): void { validateString(packageId, "packageId"); return controller.retryExtraction(packageId); }); + ipcMain.handle(IPC_CHANNELS.EXTRACT_NOW, (_event: IpcMainInvokeEvent, packageId: string) => { + validateString(packageId, "packageId"); + return controller.extractNow(packageId); + }); ipcMain.handle(IPC_CHANNELS.EXPORT_QUEUE, () => controller.exportQueue()); ipcMain.handle(IPC_CHANNELS.IMPORT_QUEUE, (_event: IpcMainInvokeEvent, json: string) => { validateString(json, "json"); diff --git a/src/preload/preload.ts b/src/preload/preload.ts index c18b42e..496a84a 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -48,6 +48,7 @@ const api: ElectronApi = { importBackup: (): Promise<{ restored: boolean; message: string }> => ipcRenderer.invoke(IPC_CHANNELS.IMPORT_BACKUP), openLog: (): Promise => ipcRenderer.invoke(IPC_CHANNELS.OPEN_LOG), retryExtraction: (packageId: string): Promise => ipcRenderer.invoke(IPC_CHANNELS.RETRY_EXTRACTION, packageId), + extractNow: (packageId: string): Promise => ipcRenderer.invoke(IPC_CHANNELS.EXTRACT_NOW, packageId), onStateUpdate: (callback: (snapshot: UiSnapshot) => void): (() => void) => { const listener = (_event: unknown, snapshot: UiSnapshot): void => callback(snapshot); ipcRenderer.on(IPC_CHANNELS.STATE_UPDATE, listener); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 4be06d8..9cf1444 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -2569,9 +2569,15 @@ export function App(): ReactElement { const pkg = snapshot.session.packages[contextMenu.packageId]; const items = pkg?.itemIds.map((id) => snapshot.session.items[id]).filter(Boolean) || []; const hasExtractError = items.some((item) => item && /^Entpack-Fehler/i.test(item.fullStatus)); - return hasExtractError ? ( - - ) : null; + const allCompleted = items.length > 0 && items.every((item) => item && item.status === "completed"); + return (<> + {allCompleted && ( + + )} + {hasExtractError && ( + + )} + ); })()} {hasPackages && (