Compare commits

..

No commits in common. "e1f9b4b6d379f0875d8b7332eb2fd57ab3a765e1" and "9ddc7d31bb07d1d09f8818c2edcfc031c6dfb70f" have entirely different histories.

3 changed files with 31 additions and 144 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.6.46", "version": "1.6.45",
"description": "Desktop downloader", "description": "Desktop downloader",
"main": "build/main/main/main.js", "main": "build/main/main/main.js",
"author": "Sucukdeluxe", "author": "Sucukdeluxe",

View File

@ -37,9 +37,9 @@ function releaseTlsSkip(): void {
delete process.env.NODE_TLS_REJECT_UNAUTHORIZED; delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
} }
} }
import { cleanupCancelledPackageArtifactsAsync, removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup"; import { cleanupCancelledPackageArtifactsAsync } from "./cleanup";
import { DebridService, MegaWebUnrestrictor, checkRapidgatorOnline } from "./debrid"; import { DebridService, MegaWebUnrestrictor, checkRapidgatorOnline } from "./debrid";
import { cleanupArchives, clearExtractResumeState, collectArchiveCleanupTargets, extractPackageArchives, findArchiveCandidates, hasAnyFilesRecursive, removeEmptyDirectoryTree } from "./extractor"; import { clearExtractResumeState, collectArchiveCleanupTargets, extractPackageArchives, findArchiveCandidates } from "./extractor";
import { validateFileAgainstManifest } from "./integrity"; import { validateFileAgainstManifest } from "./integrity";
import { logger } from "./logger"; import { logger } from "./logger";
import { StoragePaths, saveSession, saveSessionAsync, saveSettings, saveSettingsAsync } from "./storage"; import { StoragePaths, saveSession, saveSessionAsync, saveSettings, saveSettingsAsync } from "./storage";
@ -6485,9 +6485,9 @@ export class DownloadManager extends EventEmitter {
logger.info(`Hybrid-Extract Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}`); logger.info(`Hybrid-Extract Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}`);
if (result.extracted > 0) { if (result.extracted > 0) {
void this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg).catch((err) => pkg.postProcessLabel = "Renaming...";
logger.warn(`Hybrid Auto-Rename Fehler: pkg=${pkg.name}, reason=${compactErrorText(err)}`) this.emitState();
); await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
} }
if (result.failed > 0) { if (result.failed > 0) {
logger.warn(`Hybrid-Extract: ${result.failed} Archive fehlgeschlagen, wird beim finalen Durchlauf erneut versucht`); logger.warn(`Hybrid-Extract: ${result.failed} Archive fehlgeschlagen, wird beim finalen Durchlauf erneut versucht`);
@ -6633,7 +6633,6 @@ export class DownloadManager extends EventEmitter {
const completedItems = items.filter((item) => item.status === "completed"); const completedItems = items.filter((item) => item.status === "completed");
const alreadyMarkedExtracted = completedItems.length > 0 && completedItems.every((item) => isExtractedLabel(item.fullStatus)); const alreadyMarkedExtracted = completedItems.length > 0 && completedItems.every((item) => isExtractedLabel(item.fullStatus));
let extractedCount = 0;
if (this.settings.autoExtract && failed === 0 && success > 0 && !alreadyMarkedExtracted) { if (this.settings.autoExtract && failed === 0 && success > 0 && !alreadyMarkedExtracted) {
pkg.postProcessLabel = "Entpacken vorbereiten..."; pkg.postProcessLabel = "Entpacken vorbereiten...";
@ -6705,7 +6704,6 @@ export class DownloadManager extends EventEmitter {
passwordList: this.settings.archivePasswordList, passwordList: this.settings.archivePasswordList,
signal: extractAbortController.signal, signal: extractAbortController.signal,
packageId, packageId,
skipPostCleanup: true,
maxParallel: this.settings.maxParallelExtract || 2, maxParallel: this.settings.maxParallelExtract || 2,
extractCpuPriority: this.settings.extractCpuPriority, extractCpuPriority: this.settings.extractCpuPriority,
onProgress: (progress) => { onProgress: (progress) => {
@ -6796,10 +6794,13 @@ export class DownloadManager extends EventEmitter {
} }
}); });
logger.info(`Post-Processing Entpacken Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}, lastError=${result.lastError || ""}`); logger.info(`Post-Processing Entpacken Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}, lastError=${result.lastError || ""}`);
extractedCount = result.extracted;
// Auto-rename wird in runDeferredPostExtraction ausgeführt (im Hintergrund), // Auto-rename even when some archives failed — successfully extracted files still need renaming
// damit der Slot sofort freigegeben wird. if (result.extracted > 0) {
pkg.postProcessLabel = "Renaming...";
this.emitState();
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
}
if (result.failed > 0) { if (result.failed > 0) {
const reason = compactErrorText(result.lastError || "Entpacken fehlgeschlagen"); const reason = compactErrorText(result.lastError || "Entpacken fehlgeschlagen");
@ -6900,6 +6901,20 @@ export class DownloadManager extends EventEmitter {
this.recordPackageHistory(packageId, pkg, items); this.recordPackageHistory(packageId, pkg, items);
} }
if (this.settings.autoExtract && alreadyMarkedExtracted && failed === 0 && success > 0 && this.settings.cleanupMode !== "none") {
pkg.postProcessLabel = "Aufräumen...";
this.emitState();
const removedArchives = await this.cleanupRemainingArchiveArtifacts(pkg.outputDir);
if (removedArchives > 0) {
logger.info(`Hybrid-Post-Cleanup entfernte Archive: pkg=${pkg.name}, entfernt=${removedArchives}`);
}
}
if (success > 0 && (pkg.status === "completed" || pkg.status === "failed")) {
pkg.postProcessLabel = "Verschiebe MKVs...";
this.emitState();
await this.collectMkvFilesToLibrary(packageId, pkg);
}
if (this.runPackageIds.has(packageId)) { if (this.runPackageIds.has(packageId)) {
if (pkg.status === "completed" || pkg.status === "failed") { if (pkg.status === "completed" || pkg.status === "failed") {
this.runCompletedPackages.add(packageId); this.runCompletedPackages.add(packageId);
@ -6909,137 +6924,9 @@ export class DownloadManager extends EventEmitter {
} }
pkg.postProcessLabel = undefined; pkg.postProcessLabel = undefined;
pkg.updatedAt = nowMs(); pkg.updatedAt = nowMs();
logger.info(`Post-Processing Ende: pkg=${pkg.name}, status=${pkg.status} (deferred work wird im Hintergrund ausgeführt)`); logger.info(`Post-Processing Ende: pkg=${pkg.name}, status=${pkg.status}`);
// Deferred post-extraction: Rename, MKV-Sammlung, Cleanup laufen im Hintergrund,
// damit der Post-Process-Slot sofort freigegeben wird und das nächste Pack
// ohne 1015 Sekunden Pause entpacken kann.
void this.runDeferredPostExtraction(packageId, pkg, success, failed, alreadyMarkedExtracted, extractedCount);
}
/**
* Runs slow post-extraction work (rename, MKV collection, cleanup) in the background
* so the post-process slot is released immediately and the next pack can start unpacking.
*/
private async runDeferredPostExtraction(
packageId: string,
pkg: PackageEntry,
success: number,
failed: number,
alreadyMarkedExtracted: boolean,
extractedCount: number
): Promise<void> {
try {
// ── Nested extraction: extract archives found inside the extracted output ──
if (extractedCount > 0 && failed === 0 && this.settings.autoExtract) {
const nestedBlacklist = /\.(iso|img|bin|dmg|vhd|vhdx|vmdk|wim)$/i;
const nestedCandidates = (await findArchiveCandidates(pkg.extractDir))
.filter((p) => !nestedBlacklist.test(p));
if (nestedCandidates.length > 0) {
pkg.postProcessLabel = "Nested Entpacken...";
this.emitState();
logger.info(`Deferred Nested-Extraction: ${nestedCandidates.length} Archive in ${pkg.extractDir}`);
const nestedResult = await extractPackageArchives({
packageDir: pkg.extractDir,
targetDir: pkg.extractDir,
cleanupMode: this.settings.cleanupMode,
conflictMode: this.settings.extractConflictMode,
removeLinks: false,
removeSamples: false,
passwordList: this.settings.archivePasswordList,
packageId,
onlyArchives: new Set(nestedCandidates.map((p) => process.platform === "win32" ? path.resolve(p).toLowerCase() : path.resolve(p))),
maxParallel: this.settings.maxParallelExtract || 2,
extractCpuPriority: this.settings.extractCpuPriority,
});
extractedCount += nestedResult.extracted;
logger.info(`Deferred Nested-Extraction Ende: extracted=${nestedResult.extracted}, failed=${nestedResult.failed}`);
}
}
// ── Auto-Rename ──
if (extractedCount > 0) {
pkg.postProcessLabel = "Renaming...";
this.emitState();
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg);
}
// ── Archive cleanup (source archives in outputDir) ──
if (extractedCount > 0 && failed === 0 && this.settings.cleanupMode !== "none") {
pkg.postProcessLabel = "Aufräumen...";
this.emitState();
const sourceAndTargetEqual = path.resolve(pkg.outputDir).toLowerCase() === path.resolve(pkg.extractDir).toLowerCase();
if (!sourceAndTargetEqual) {
const candidates = await findArchiveCandidates(pkg.outputDir);
if (candidates.length > 0) {
const removed = await cleanupArchives(candidates, this.settings.cleanupMode);
if (removed > 0) {
logger.info(`Deferred Archive-Cleanup: pkg=${pkg.name}, entfernt=${removed}`);
}
}
}
}
// ── Hybrid archive cleanup (wenn bereits als extracted markiert) ──
if (this.settings.autoExtract && alreadyMarkedExtracted && failed === 0 && success > 0 && this.settings.cleanupMode !== "none") {
const removedArchives = await this.cleanupRemainingArchiveArtifacts(pkg.outputDir);
if (removedArchives > 0) {
logger.info(`Hybrid-Post-Cleanup entfernte Archive: pkg=${pkg.name}, entfernt=${removedArchives}`);
}
}
// ── Link/Sample artifact removal ──
if (extractedCount > 0 && failed === 0) {
if (this.settings.removeLinkFilesAfterExtract) {
const removedLinks = await removeDownloadLinkArtifacts(pkg.extractDir);
if (removedLinks > 0) {
logger.info(`Deferred Link-Cleanup: pkg=${pkg.name}, entfernt=${removedLinks}`);
}
}
if (this.settings.removeSamplesAfterExtract) {
const removedSamples = await removeSampleArtifacts(pkg.extractDir);
if (removedSamples.files > 0 || removedSamples.dirs > 0) {
logger.info(`Deferred Sample-Cleanup: pkg=${pkg.name}, files=${removedSamples.files}, dirs=${removedSamples.dirs}`);
}
}
}
// ── Empty directory tree removal ──
if (extractedCount > 0 && failed === 0 && this.settings.cleanupMode === "delete") {
if (!(await hasAnyFilesRecursive(pkg.outputDir))) {
const removedDirs = await removeEmptyDirectoryTree(pkg.outputDir);
if (removedDirs > 0) {
logger.info(`Deferred leere Download-Ordner entfernt: pkg=${pkg.name}, dirs=${removedDirs}`);
}
}
}
// ── Resume state cleanup ──
if (extractedCount > 0 && failed === 0) {
await clearExtractResumeState(pkg.outputDir, packageId);
}
// ── MKV collection ──
if (success > 0 && (pkg.status === "completed" || pkg.status === "failed")) {
pkg.postProcessLabel = "Verschiebe MKVs...";
this.emitState();
await this.collectMkvFilesToLibrary(packageId, pkg);
}
pkg.postProcessLabel = undefined;
pkg.updatedAt = nowMs();
this.persistSoon();
this.emitState();
this.applyPackageDoneCleanup(packageId); this.applyPackageDoneCleanup(packageId);
} catch (error) {
logger.warn(`Deferred Post-Extraction Fehler: pkg=${pkg.name}, reason=${compactErrorText(error)}`);
} finally {
pkg.postProcessLabel = undefined;
pkg.updatedAt = nowMs();
this.persistSoon();
this.emitState();
}
} }
private applyPackageDoneCleanup(packageId: string): void { private applyPackageDoneCleanup(packageId: string): void {

View File

@ -1718,7 +1718,7 @@ export function collectArchiveCleanupTargets(sourceArchivePath: string, director
return Array.from(targets); return Array.from(targets);
} }
export async function cleanupArchives(sourceFiles: string[], cleanupMode: CleanupMode): Promise<number> { async function cleanupArchives(sourceFiles: string[], cleanupMode: CleanupMode): Promise<number> {
if (cleanupMode === "none") { if (cleanupMode === "none") {
return 0; return 0;
} }
@ -1789,7 +1789,7 @@ export async function cleanupArchives(sourceFiles: string[], cleanupMode: Cleanu
return removed; return removed;
} }
export async function hasAnyFilesRecursive(rootDir: string): Promise<boolean> { async function hasAnyFilesRecursive(rootDir: string): Promise<boolean> {
const rootExists = await fs.promises.access(rootDir).then(() => true, () => false); const rootExists = await fs.promises.access(rootDir).then(() => true, () => false);
if (!rootExists) { if (!rootExists) {
return false; return false;
@ -1837,7 +1837,7 @@ async function hasAnyEntries(rootDir: string): Promise<boolean> {
} }
} }
export async function removeEmptyDirectoryTree(rootDir: string): Promise<number> { async function removeEmptyDirectoryTree(rootDir: string): Promise<number> {
const rootExists = await fs.promises.access(rootDir).then(() => true, () => false); const rootExists = await fs.promises.access(rootDir).then(() => true, () => false);
if (!rootExists) { if (!rootExists) {
return 0; return 0;