Reduce cancel lag with non-blocking cleanup in v1.1.25
Some checks are pending
Build and Release / build (push) Waiting to run

This commit is contained in:
Sucukdeluxe 2026-02-27 11:53:14 +01:00
parent 4548d809f9
commit 0f61b0be08
6 changed files with 68 additions and 8 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.1.24", "version": "1.1.25",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.1.24", "version": "1.1.25",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.1.24", "version": "1.1.25",
"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

@ -2,6 +2,12 @@ import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import { ARCHIVE_TEMP_EXTENSIONS, LINK_ARTIFACT_EXTENSIONS, RAR_SPLIT_RE, SAMPLE_DIR_NAMES, SAMPLE_TOKEN_RE, SAMPLE_VIDEO_EXTENSIONS } from "./constants"; import { ARCHIVE_TEMP_EXTENSIONS, LINK_ARTIFACT_EXTENSIONS, RAR_SPLIT_RE, SAMPLE_DIR_NAMES, SAMPLE_TOKEN_RE, SAMPLE_VIDEO_EXTENSIONS } from "./constants";
async function yieldToLoop(): Promise<void> {
await new Promise<void>((resolve) => {
setTimeout(resolve, 0);
});
}
export function isArchiveOrTempFile(filePath: string): boolean { export function isArchiveOrTempFile(filePath: string): boolean {
const lower = filePath.toLowerCase(); const lower = filePath.toLowerCase();
const ext = path.extname(lower); const ext = path.extname(lower);
@ -39,6 +45,47 @@ export function cleanupCancelledPackageArtifacts(packageDir: string): number {
return removed; return removed;
} }
export async function cleanupCancelledPackageArtifactsAsync(packageDir: string): Promise<number> {
try {
await fs.promises.access(packageDir, fs.constants.F_OK);
} catch {
return 0;
}
let removed = 0;
let touched = 0;
const stack = [packageDir];
while (stack.length > 0) {
const current = stack.pop() as string;
let entries: fs.Dirent[] = [];
try {
entries = await fs.promises.readdir(current, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
const full = path.join(current, entry.name);
if (entry.isDirectory()) {
stack.push(full);
} else if (entry.isFile() && isArchiveOrTempFile(full)) {
try {
await fs.promises.rm(full, { force: true });
removed += 1;
} catch {
// ignore
}
}
touched += 1;
if (touched % 80 === 0) {
await yieldToLoop();
}
}
}
return removed;
}
export function removeDownloadLinkArtifacts(extractDir: string): number { export function removeDownloadLinkArtifacts(extractDir: string): number {
if (!fs.existsSync(extractDir)) { if (!fs.existsSync(extractDir)) {
return 0; return 0;

View File

@ -3,7 +3,7 @@ import os from "node:os";
import { AppSettings } from "../shared/types"; import { AppSettings } from "../shared/types";
export const APP_NAME = "Debrid Download Manager"; export const APP_NAME = "Debrid Download Manager";
export const APP_VERSION = "1.1.24"; export const APP_VERSION = "1.1.25";
export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0"; export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0";
export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload"; export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload";

View File

@ -5,7 +5,7 @@ import { EventEmitter } from "node:events";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { AppSettings, DownloadItem, DownloadSummary, DownloadStatus, PackageEntry, ParsedPackageInput, SessionState, UiSnapshot } from "../shared/types"; import { AppSettings, DownloadItem, DownloadSummary, DownloadStatus, PackageEntry, ParsedPackageInput, SessionState, UiSnapshot } from "../shared/types";
import { CHUNK_SIZE, REQUEST_RETRIES } from "./constants"; import { CHUNK_SIZE, REQUEST_RETRIES } from "./constants";
import { cleanupCancelledPackageArtifacts, removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup"; import { cleanupCancelledPackageArtifactsAsync } from "./cleanup";
import { DebridService, MegaWebUnrestrictor } from "./debrid"; import { DebridService, MegaWebUnrestrictor } from "./debrid";
import { extractPackageArchives } from "./extractor"; import { extractPackageArchives } from "./extractor";
import { validateFileAgainstManifest } from "./integrity"; import { validateFileAgainstManifest } from "./integrity";
@ -112,6 +112,8 @@ export class DownloadManager extends EventEmitter {
private speedBytesLastWindow = 0; private speedBytesLastWindow = 0;
private cleanupQueue: Promise<void> = Promise.resolve();
private reservedTargetPaths = new Map<string, string>(); private reservedTargetPaths = new Map<string, string>();
private claimedTargetPathByItem = new Map<string, string>(); private claimedTargetPathByItem = new Map<string, string>();
@ -332,6 +334,8 @@ export class DownloadManager extends EventEmitter {
if (!pkg) { if (!pkg) {
return; return;
} }
const packageName = pkg.name;
const outputDir = pkg.outputDir;
const itemIds = [...pkg.itemIds]; const itemIds = [...pkg.itemIds];
for (const itemId of itemIds) { for (const itemId of itemIds) {
@ -347,11 +351,18 @@ export class DownloadManager extends EventEmitter {
} }
} }
const removed = cleanupCancelledPackageArtifacts(pkg.outputDir);
this.removePackageFromSession(packageId, itemIds); this.removePackageFromSession(packageId, itemIds);
logger.info(`Paket ${pkg.name} abgebrochen, ${removed} Artefakte gelöscht`);
this.persistSoon(); this.persistSoon();
this.emitState(true); this.emitState(true);
this.cleanupQueue = this.cleanupQueue
.then(async () => {
const removed = await cleanupCancelledPackageArtifactsAsync(outputDir);
logger.info(`Paket ${packageName} abgebrochen, ${removed} Artefakte gelöscht`);
})
.catch((error) => {
logger.warn(`Cleanup für Paket ${packageName} fehlgeschlagen: ${compactErrorText(error)}`);
});
} }
public start(): void { public start(): void {

View File

@ -193,7 +193,9 @@ async function main(): Promise<void> {
assert(cancelItem?.status === "cancelled" || cancelItem?.status === "queued", "Paketabbruch nicht wirksam"); assert(cancelItem?.status === "cancelled" || cancelItem?.status === "queued", "Paketabbruch nicht wirksam");
} }
const packageDir = path.join(path.join(tempRoot, "downloads-cancel"), "cancel"); const packageDir = path.join(path.join(tempRoot, "downloads-cancel"), "cancel");
assert(!fs.existsSync(path.join(packageDir, "release.part1.rar")), "RAR-Artefakt wurde nicht gelöscht"); const cancelArtifact = path.join(packageDir, "release.part1.rar");
await waitFor(() => !fs.existsSync(cancelArtifact), 10000);
assert(!fs.existsSync(cancelArtifact), "RAR-Artefakt wurde nicht gelöscht");
console.log("Node self-check erfolgreich"); console.log("Node self-check erfolgreich");
} finally { } finally {