Backfill extracted archive cleanup on startup in v1.3.8

This commit is contained in:
Sucukdeluxe 2026-02-27 15:15:16 +01:00
parent 75fc582299
commit da51e03cef
3 changed files with 145 additions and 2 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.3.7", "version": "1.3.8",
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
"main": "build/main/main/main.js", "main": "build/main/main/main.js",
"author": "Sucukdeluxe", "author": "Sucukdeluxe",

View File

@ -7,7 +7,7 @@ import { AppSettings, DownloadItem, DownloadStats, DownloadSummary, DownloadStat
import { REQUEST_RETRIES } from "./constants"; import { REQUEST_RETRIES } from "./constants";
import { cleanupCancelledPackageArtifactsAsync } from "./cleanup"; import { cleanupCancelledPackageArtifactsAsync } from "./cleanup";
import { DebridService, MegaWebUnrestrictor } from "./debrid"; import { DebridService, MegaWebUnrestrictor } from "./debrid";
import { extractPackageArchives } from "./extractor"; import { collectArchiveCleanupTargets, extractPackageArchives } from "./extractor";
import { validateFileAgainstManifest } from "./integrity"; import { validateFileAgainstManifest } from "./integrity";
import { logger } from "./logger"; import { logger } from "./logger";
import { StoragePaths, saveSession } from "./storage"; import { StoragePaths, saveSession } from "./storage";
@ -179,12 +179,14 @@ export class DownloadManager extends EventEmitter {
this.normalizeSessionStatuses(); this.normalizeSessionStatuses();
this.recoverPostProcessingOnStartup(); this.recoverPostProcessingOnStartup();
this.resolveExistingQueuedOpaqueFilenames(); this.resolveExistingQueuedOpaqueFilenames();
this.cleanupExistingExtractedArchives();
} }
public setSettings(next: AppSettings): void { public setSettings(next: AppSettings): void {
this.settings = next; this.settings = next;
this.debridService.setSettings(next); this.debridService.setSettings(next);
this.resolveExistingQueuedOpaqueFilenames(); this.resolveExistingQueuedOpaqueFilenames();
this.cleanupExistingExtractedArchives();
this.emitState(); this.emitState();
} }
@ -580,6 +582,77 @@ export class DownloadManager extends EventEmitter {
} }
} }
private cleanupExistingExtractedArchives(): void {
if (this.settings.cleanupMode === "none") {
return;
}
const cleanupTargetsByPackage = new Map<string, Set<string>>();
for (const packageId of this.session.packageOrder) {
const pkg = this.session.packages[packageId];
if (!pkg || pkg.cancelled || pkg.status !== "completed") {
continue;
}
const items = pkg.itemIds
.map((itemId) => this.session.items[itemId])
.filter(Boolean) as DownloadItem[];
if (items.length === 0 || !items.every((item) => item.status === "completed")) {
continue;
}
const extractedItems = items.filter((item) => item.fullStatus === "Entpackt");
if (extractedItems.length === 0) {
continue;
}
const packageTargets = cleanupTargetsByPackage.get(packageId) ?? new Set<string>();
for (const item of extractedItems) {
const targetPath = String(item.targetPath || "").trim();
if (!targetPath) {
continue;
}
for (const cleanupTarget of collectArchiveCleanupTargets(targetPath)) {
packageTargets.add(cleanupTarget);
}
}
if (packageTargets.size > 0) {
cleanupTargetsByPackage.set(packageId, packageTargets);
}
}
if (cleanupTargetsByPackage.size === 0) {
return;
}
this.cleanupQueue = this.cleanupQueue
.then(async () => {
for (const [packageId, targets] of cleanupTargetsByPackage.entries()) {
const pkg = this.session.packages[packageId];
if (!pkg) {
continue;
}
let removed = 0;
for (const targetPath of targets) {
try {
await fs.promises.rm(targetPath, { force: true });
removed += 1;
} catch {
// ignore
}
}
if (removed > 0) {
logger.info(`Nachtraegliches Archive-Cleanup fuer ${pkg.name}: ${removed} Datei(en) geloescht`);
}
}
})
.catch((error) => {
logger.warn(`Nachtraegliches Archive-Cleanup fehlgeschlagen: ${compactErrorText(error)}`);
});
}
public cancelPackage(packageId: string): void { public cancelPackage(packageId: string): void {
const pkg = this.session.packages[packageId]; const pkg = this.session.packages[packageId];
if (!pkg) { if (!pkg) {

View File

@ -821,6 +821,76 @@ describe("download manager", () => {
expect(snapshot.canStart).toBe(true); expect(snapshot.canStart).toBe(true);
}); });
it("cleans leftover split archives on startup for already extracted packages", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
tempDirs.push(root);
const packageDir = path.join(root, "downloads", "legacy");
fs.mkdirSync(packageDir, { recursive: true });
const part1 = path.join(packageDir, "legacy.release.part01.rar");
const part2 = path.join(packageDir, "legacy.release.part02.rar");
const part3 = path.join(packageDir, "legacy.release.part03.rar");
const keep = path.join(packageDir, "keep.txt");
fs.writeFileSync(part2, "part2", "utf8");
fs.writeFileSync(part3, "part3", "utf8");
fs.writeFileSync(keep, "keep", "utf8");
const session = emptySession();
const packageId = "legacy-pkg";
const itemId = "legacy-item";
const createdAt = Date.now() - 20_000;
session.packageOrder = [packageId];
session.packages[packageId] = {
id: packageId,
name: "legacy",
outputDir: packageDir,
extractDir: path.join(root, "extract", "legacy"),
status: "completed",
itemIds: [itemId],
cancelled: false,
enabled: true,
createdAt,
updatedAt: createdAt
};
session.items[itemId] = {
id: itemId,
packageId,
url: "https://dummy/legacy",
provider: "realdebrid",
status: "completed",
retries: 0,
speedBps: 0,
downloadedBytes: 123,
totalBytes: 123,
progressPercent: 100,
fileName: path.basename(part1),
targetPath: part1,
resumable: true,
attempts: 1,
lastError: "",
fullStatus: "Entpackt",
createdAt,
updatedAt: createdAt
};
new DownloadManager(
{
...defaultSettings(),
token: "rd-token",
outputDir: path.join(root, "downloads"),
extractDir: path.join(root, "extract"),
autoExtract: true,
cleanupMode: "delete"
},
session,
createStoragePaths(path.join(root, "state"))
);
await waitFor(() => !fs.existsSync(part2) && !fs.existsSync(part3), 5000);
expect(fs.existsSync(keep)).toBe(true);
});
it("resets run counters and reconnect state on start", async () => { it("resets run counters and reconnect state on start", async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-")); const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
tempDirs.push(root); tempDirs.push(root);