From fbae8a1496693e6b1d741e7ef8736c08957c3ff0 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Tue, 10 Mar 2026 19:57:26 +0100 Subject: [PATCH] Fix cleanup after partial extraction failures --- src/main/download-manager.ts | 7 ++- tests/download-manager.test.ts | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index d0e73cb..d12436c 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -3694,6 +3694,7 @@ export class DownloadManager extends EventEmitter { let skipped = 0; let failed = 0; let sourceArtifactsChanged = false; + let sourceCleanupRelevant = false; for (const sourcePath of mkvFiles) { if (shouldAbort?.()) { @@ -3744,6 +3745,7 @@ export class DownloadManager extends EventEmitter { } catch { /* ignore */ } + sourceCleanupRelevant = true; skipped += 1; continue; } @@ -3761,6 +3763,7 @@ export class DownloadManager extends EventEmitter { await this.moveFileWithExdevFallback(sourcePath, targetPath); moved += 1; sourceArtifactsChanged = true; + sourceCleanupRelevant = true; this.logPackageForPackage(pkg, "INFO", "MKV verschoben", { sourcePath, targetPath, @@ -3790,7 +3793,7 @@ export class DownloadManager extends EventEmitter { } } - if (sourceArtifactsChanged && await this.existsAsync(sourceDir)) { + if ((sourceArtifactsChanged || sourceCleanupRelevant) && await this.existsAsync(sourceDir)) { const removedResidual = await this.cleanupNonMkvResidualFiles(sourceDir, targetDir); if (removedResidual > 0) { logger.info(`MKV-Sammelordner entfernte Restdateien: pkg=${pkg.name}, entfernt=${removedResidual}`); @@ -10585,7 +10588,7 @@ export class DownloadManager extends EventEmitter { } // ── Link/Sample artifact removal ── - if ((extractedCount > 0 || alreadyMarkedExtracted) && failed === 0) { + if (extractedCount > 0 || alreadyMarkedExtracted) { throwIfAborted(); if (this.settings.removeLinkFilesAfterExtract) { const removedLinks = await removeDownloadLinkArtifacts(pkg.extractDir, { shouldAbort }); diff --git a/tests/download-manager.test.ts b/tests/download-manager.test.ts index 0a39c6b..c4e68d4 100644 --- a/tests/download-manager.test.ts +++ b/tests/download-manager.test.ts @@ -7827,6 +7827,87 @@ describe("download manager", () => { expect(remainingPackage?.outputDir).toBe(outputDir); }); + it("removes link and sample artifacts from extracted output even when deferred post-processing has failures", async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-")); + tempDirs.push(root); + + const packageName = "Failed Cleanup"; + const outputDir = path.join(root, "downloads", packageName); + const extractDir = path.join(root, "extract", packageName); + fs.mkdirSync(outputDir, { recursive: true }); + fs.mkdirSync(path.join(extractDir, "sample"), { recursive: true }); + fs.writeFileSync(path.join(extractDir, "episode.links.txt"), "https://example.com/file", "utf8"); + fs.writeFileSync(path.join(extractDir, "sample", "sample.mkv"), "sample-video", "utf8"); + + const session = emptySession(); + const packageId = "failed-cleanup-pkg"; + const itemId = "failed-cleanup-item"; + const createdAt = Date.now() - 20_000; + session.packageOrder = [packageId]; + session.packages[packageId] = { + id: packageId, + name: packageName, + outputDir, + extractDir, + status: "failed", + itemIds: [itemId], + cancelled: false, + enabled: true, + createdAt, + updatedAt: createdAt + }; + session.items[itemId] = { + id: itemId, + packageId, + url: "https://dummy/failed-cleanup", + provider: "realdebrid", + status: "completed", + retries: 0, + speedBps: 0, + downloadedBytes: 100, + totalBytes: 100, + progressPercent: 100, + fileName: "episode.zip", + targetPath: path.join(outputDir, "episode.zip"), + resumable: true, + attempts: 1, + lastError: "", + fullStatus: "Entpackt - Done (1s)", + createdAt, + updatedAt: createdAt + }; + + const manager = new DownloadManager( + { + ...defaultSettings(), + token: "rd-token", + outputDir: path.join(root, "downloads"), + extractDir: path.join(root, "extract"), + autoExtract: true, + autoRename4sf4sj: false, + collectMkvToLibrary: false, + removeLinkFilesAfterExtract: true, + removeSamplesAfterExtract: true, + enableIntegrityCheck: false, + cleanupMode: "delete" + }, + session, + createStoragePaths(path.join(root, "state")) + ); + + await (manager as any).runDeferredPostExtraction( + packageId, + (manager as any).session.packages[packageId], + 1, + 1, + true, + 1 + ); + + expect(fs.existsSync(path.join(extractDir, "episode.links.txt"))).toBe(false); + expect(fs.existsSync(path.join(extractDir, "sample", "sample.mkv"))).toBe(false); + }); + it("does not delete startup archives when any completed item has an extract error", async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-")); tempDirs.push(root);