From a22a90adf3dd1e3b7350bf1d610c72fd2560629a Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Mon, 2 Mar 2026 20:15:00 +0100 Subject: [PATCH] Add retry extraction context menu, increase error text limit - Right-click packages with extraction errors shows "Extraktion wiederholen" option to manually retry - Increase WinRAR error text from 240 to 500 chars for better diagnostics in logs Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/main/app-controller.ts | 4 ++++ src/main/download-manager.ts | 21 +++++++++++++++++++++ src/main/extractor.ts | 2 +- src/main/main.ts | 4 ++++ src/preload/preload.ts | 1 + src/renderer/App.tsx | 8 ++++++++ src/shared/ipc.ts | 3 ++- src/shared/preload-api.ts | 1 + 9 files changed, 43 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e96a7d8..05cd038 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.14", + "version": "1.5.15", "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 f68ec48..82d12ab 100644 --- a/src/main/app-controller.ts +++ b/src/main/app-controller.ts @@ -208,6 +208,10 @@ export class AppController { return this.manager.togglePause(); } + public retryExtraction(packageId: string): void { + this.manager.retryExtraction(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 3d35bbf..a816c13 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -2955,6 +2955,27 @@ export class DownloadManager extends EventEmitter { } } + public retryExtraction(packageId: string): void { + const pkg = this.session.packages[packageId]; + if (!pkg) 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) { + if (!isExtractedLabel(item.fullStatus)) { + item.fullStatus = "Entpacken - Ausstehend"; + item.updatedAt = nowMs(); + } + } + logger.info(`Extraktion manuell wiederholt: pkg=${pkg.name}`); + this.persistSoon(); + this.emitState(true); + void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (retryExtraction): ${compactErrorText(err)}`)); + } + private removePackageFromSession(packageId: string, itemIds: string[]): void { const postProcessController = this.packagePostProcessAbortControllers.get(packageId); if (postProcessController && !postProcessController.signal.aborted) { diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 917aaec..1ef5f7f 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -178,7 +178,7 @@ function effectiveConflictMode(conflictMode: ConflictMode): "overwrite" | "skip" } function cleanErrorText(text: string): string { - return String(text || "").replace(/\s+/g, " ").trim().slice(0, 240); + return String(text || "").replace(/\s+/g, " ").trim().slice(0, 500); } function appendLimited(base: string, chunk: string, maxLen = MAX_EXTRACT_OUTPUT_BUFFER): string { diff --git a/src/main/main.ts b/src/main/main.ts index 867ef51..71542f8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -314,6 +314,10 @@ function registerIpcHandlers(): void { validateString(packageId, "packageId"); return controller.togglePackage(packageId); }); + ipcMain.handle(IPC_CHANNELS.RETRY_EXTRACTION, (_event: IpcMainInvokeEvent, packageId: string) => { + validateString(packageId, "packageId"); + return controller.retryExtraction(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 9493f7b..c18b42e 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -47,6 +47,7 @@ const api: ElectronApi = { exportBackup: (): Promise<{ saved: boolean }> => ipcRenderer.invoke(IPC_CHANNELS.EXPORT_BACKUP), 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), 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 e664daa..4be06d8 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -2565,6 +2565,14 @@ export function App(): ReactElement { else { executeDeleteSelection(ids); } }}>Ausgewählte entfernen ({[...selectedIds].filter((id) => snapshot.session.items[id]).length}) )} + {hasPackages && !multi && (() => { + 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; + })()} {hasPackages && (