Compare commits
No commits in common. "342b4180a165312430cb76170f14087808c773fb" and "8ec5d17e0916d1a36feb061111daaaeee6ec7ac1" have entirely different histories.
342b4180a1
...
8ec5d17e09
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.7.154",
|
"version": "1.7.153",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -4600,57 +4600,27 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SOURCE DIRECTORIES:
|
const sourceDir = this.settings.autoExtract ? pkg.extractDir : pkg.outputDir;
|
||||||
// - Wenn autoExtract aktiv: extractDir ist primäre Quelle (entpackte Videos).
|
|
||||||
// - IMMER zusätzlich outputDir: Provider wie Mega-Debrid liefern direkte
|
|
||||||
// .mkv (kein Archiv), die sonst im outputDir liegen bleiben und nie in
|
|
||||||
// der Library landen würden.
|
|
||||||
const sourceDirCandidates: string[] = [];
|
|
||||||
if (this.settings.autoExtract && pkg.extractDir) {
|
|
||||||
sourceDirCandidates.push(pkg.extractDir);
|
|
||||||
}
|
|
||||||
if (pkg.outputDir) {
|
|
||||||
sourceDirCandidates.push(pkg.outputDir);
|
|
||||||
}
|
|
||||||
// Dedupe nach resolved Pfad (extractDir kann == outputDir sein).
|
|
||||||
const sourceDirSeen = new Set<string>();
|
|
||||||
const sourceDirsAll: string[] = [];
|
|
||||||
for (const dir of sourceDirCandidates) {
|
|
||||||
const resolved = path.resolve(dir).toLowerCase();
|
|
||||||
if (sourceDirSeen.has(resolved)) continue;
|
|
||||||
sourceDirSeen.add(resolved);
|
|
||||||
sourceDirsAll.push(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetDirRaw = String(this.settings.mkvLibraryDir || "").trim();
|
const targetDirRaw = String(this.settings.mkvLibraryDir || "").trim();
|
||||||
if (sourceDirsAll.length === 0 || !targetDirRaw) {
|
if (!sourceDir || !targetDirRaw) {
|
||||||
logger.warn(`MKV-Sammelordner übersprungen: pkg=${pkg.name}, ungültiger Pfad`);
|
logger.warn(`MKV-Sammelordner übersprungen: pkg=${pkg.name}, ungültiger Pfad`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const targetDir = path.resolve(targetDirRaw);
|
const targetDir = path.resolve(targetDirRaw);
|
||||||
|
|
||||||
// SAFETY: never move files WITHIN the library tree, and never treat the
|
// SAFETY: never move files WITHIN the library tree, and never treat the
|
||||||
// library itself as a source. sourceDir == targetDir would scan the
|
// library itself as a source. sourceDir == targetDir would scan the
|
||||||
// library, match files collected from OTHER packages via the same rename
|
// library, match files collected from OTHER packages via the same rename
|
||||||
// heuristics, and move them around — a cross-package corruption vector.
|
// heuristics, and move them around — a cross-package corruption vector.
|
||||||
// Pro Source-Dir prüfen — einer kann safe sein, der andere nicht.
|
if (isPathInsideDir(sourceDir, targetDir) || isPathInsideDir(targetDir, sourceDir)) {
|
||||||
const sourceDirs: string[] = [];
|
logger.warn(`MKV-Sammelordner ABGEBROCHEN: pkg=${pkg.name}, sourceDir=${sourceDir} ueberlappt mit mkvLibraryDir=${targetDir}`);
|
||||||
for (const dir of sourceDirsAll) {
|
this.logPackageForPackage(pkg, "ERROR", "MKV-Sammelordner abgebrochen: sourceDir ueberlappt mit MKV-Bibliothek", {
|
||||||
if (isPathInsideDir(dir, targetDir) || isPathInsideDir(targetDir, dir)) {
|
sourceDir,
|
||||||
logger.warn(`MKV-Sammelordner: Source uebersprungen (ueberlappt mit mkvLibraryDir): pkg=${pkg.name}, dir=${dir}, target=${targetDir}`);
|
|
||||||
this.logPackageForPackage(pkg, "WARN", "MKV-Sammelordner: Source uebersprungen (ueberlappt mit MKV-Bibliothek)", {
|
|
||||||
sourceDir: dir,
|
|
||||||
targetDir
|
targetDir
|
||||||
});
|
});
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
if (!await this.existsAsync(dir)) {
|
if (!await this.existsAsync(sourceDir)) {
|
||||||
continue;
|
logger.info(`MKV-Sammelordner: pkg=${pkg.name}, Quelle fehlt (${sourceDir})`);
|
||||||
}
|
|
||||||
sourceDirs.push(dir);
|
|
||||||
}
|
|
||||||
if (sourceDirs.length === 0) {
|
|
||||||
logger.info(`MKV-Sammelordner: pkg=${pkg.name}, keine nutzbare Quelle (alle Source-Dirs fehlen oder ueberlappen mit Library)`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4661,21 +4631,8 @@ export class DownloadManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sammle aus ALLEN safe source dirs. Dedupe nach basename (lowercase) —
|
const allMkvFiles = await this.collectFilesByExtensions(sourceDir, SAMPLE_VIDEO_EXTENSIONS);
|
||||||
// extractDir wird zuerst gescannt und gewinnt bei Kollision (entpackte
|
if (allMkvFiles.length === 0) {
|
||||||
// Datei hat Vorrang vor evtl. noch liegengebliebenem Quell-File).
|
|
||||||
const seenBasenames = new Set<string>();
|
|
||||||
const collected: { filePath: string; sourceRoot: string }[] = [];
|
|
||||||
for (const dir of sourceDirs) {
|
|
||||||
const filesInDir = await this.collectFilesByExtensions(dir, SAMPLE_VIDEO_EXTENSIONS);
|
|
||||||
for (const filePath of filesInDir) {
|
|
||||||
const baseLower = path.basename(filePath).toLowerCase();
|
|
||||||
if (seenBasenames.has(baseLower)) continue;
|
|
||||||
seenBasenames.add(baseLower);
|
|
||||||
collected.push({ filePath, sourceRoot: dir });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (collected.length === 0) {
|
|
||||||
logger.info(`MKV-Sammelordner: pkg=${pkg.name}, keine MKV gefunden`);
|
logger.info(`MKV-Sammelordner: pkg=${pkg.name}, keine MKV gefunden`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -4689,7 +4646,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
const mkvFiles: string[] = [];
|
const mkvFiles: string[] = [];
|
||||||
let sampleSkipped = 0;
|
let sampleSkipped = 0;
|
||||||
let bonusSkipped = 0;
|
let bonusSkipped = 0;
|
||||||
for (const { filePath, sourceRoot } of collected) {
|
for (const filePath of allMkvFiles) {
|
||||||
if (shouldAbort?.()) {
|
if (shouldAbort?.()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -4699,9 +4656,9 @@ export class DownloadManager extends EventEmitter {
|
|||||||
sampleSkipped += 1;
|
sampleSkipped += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isInsideBonusDir(filePath, sourceRoot) || BONUS_FILENAME_RE.test(stem)) {
|
if (isInsideBonusDir(filePath, sourceDir) || BONUS_FILENAME_RE.test(stem)) {
|
||||||
bonusSkipped += 1;
|
bonusSkipped += 1;
|
||||||
logger.info(`MKV-Sammelordner: Bonus-Datei uebersprungen: ${path.basename(filePath)} (Pfad: ${path.relative(sourceRoot, filePath)})`);
|
logger.info(`MKV-Sammelordner: Bonus-Datei uebersprungen: ${path.basename(filePath)} (Pfad: ${path.relative(sourceDir, filePath)})`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
mkvFiles.push(filePath);
|
mkvFiles.push(filePath);
|
||||||
@ -4718,7 +4675,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logRenameProcess(pkg, "INFO", "mkv-move", "MKV-Sammelordner Scan gestartet", {
|
this.logRenameProcess(pkg, "INFO", "mkv-move", "MKV-Sammelordner Scan gestartet", {
|
||||||
sourceDirs: sourceDirs.join(" | "),
|
sourceDir,
|
||||||
targetDir,
|
targetDir,
|
||||||
mkvFiles: mkvFiles.length
|
mkvFiles: mkvFiles.length
|
||||||
});
|
});
|
||||||
@ -4830,25 +4787,20 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceArtifactsChanged || sourceCleanupRelevant) {
|
if ((sourceArtifactsChanged || sourceCleanupRelevant) && await this.existsAsync(sourceDir)) {
|
||||||
// Cleanup pro Source-Dir — beide können Restdateien hinterlassen haben
|
const removedResidual = await this.cleanupNonMkvResidualFiles(sourceDir, targetDir);
|
||||||
// (Mega-Direct .mkv weg aus outputDir, oder extracted .mkv weg aus extractDir).
|
|
||||||
for (const dir of sourceDirs) {
|
|
||||||
if (!await this.existsAsync(dir)) continue;
|
|
||||||
const removedResidual = await this.cleanupNonMkvResidualFiles(dir, targetDir);
|
|
||||||
if (removedResidual > 0) {
|
if (removedResidual > 0) {
|
||||||
logger.info(`MKV-Sammelordner entfernte Restdateien: pkg=${pkg.name}, dir=${dir}, entfernt=${removedResidual}`);
|
logger.info(`MKV-Sammelordner entfernte Restdateien: pkg=${pkg.name}, entfernt=${removedResidual}`);
|
||||||
}
|
}
|
||||||
const removedDirs = await this.removeEmptyDirectoryTree(dir);
|
const removedDirs = await this.removeEmptyDirectoryTree(sourceDir);
|
||||||
if (removedDirs > 0) {
|
if (removedDirs > 0) {
|
||||||
logger.info(`MKV-Sammelordner entfernte leere Ordner: pkg=${pkg.name}, dir=${dir}, entfernt=${removedDirs}`);
|
logger.info(`MKV-Sammelordner entfernte leere Ordner: pkg=${pkg.name}, entfernt=${removedDirs}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`MKV-Sammelordner: pkg=${pkg.name}, packageId=${packageId}, moved=${moved}, skipped=${skipped}, failed=${failed}, target=${targetDir}`);
|
logger.info(`MKV-Sammelordner: pkg=${pkg.name}, packageId=${packageId}, moved=${moved}, skipped=${skipped}, failed=${failed}, target=${targetDir}`);
|
||||||
this.logRenameProcess(pkg, "INFO", "mkv-move", "MKV-Sammelordner abgeschlossen", {
|
this.logRenameProcess(pkg, "INFO", "mkv-move", "MKV-Sammelordner abgeschlossen", {
|
||||||
sourceDirs: sourceDirs.join(" | "),
|
sourceDir,
|
||||||
targetDir,
|
targetDir,
|
||||||
moved,
|
moved,
|
||||||
skipped,
|
skipped,
|
||||||
|
|||||||
@ -9358,89 +9358,6 @@ describe("download manager", () => {
|
|||||||
expect(fs.existsSync(originalExtractedPath)).toBe(false);
|
expect(fs.existsSync(originalExtractedPath)).toBe(false);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("moves direct MKV download from outputDir to library when no archive present (Mega-Debrid flow)", async () => {
|
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
|
||||||
tempDirs.push(root);
|
|
||||||
|
|
||||||
const packageName = "Mega-Direct-Pack";
|
|
||||||
const outputDir = path.join(root, "downloads", packageName);
|
|
||||||
const extractDir = path.join(root, "extract", packageName);
|
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
|
||||||
|
|
||||||
// Direct .mkv download (no archive) — wie es Mega-Debrid bei mega.nz liefert.
|
|
||||||
const directMkvName = "Direct.Show.S01E01.German.1080p.WEB.x264-DIRECT.mkv";
|
|
||||||
const directMkvPath = path.join(outputDir, directMkvName);
|
|
||||||
fs.writeFileSync(directMkvPath, Buffer.alloc(2048, 1));
|
|
||||||
const directMkvSize = fs.statSync(directMkvPath).size;
|
|
||||||
|
|
||||||
const session = emptySession();
|
|
||||||
const packageId = `${packageName}-pkg`;
|
|
||||||
const itemId = `${packageName}-item`;
|
|
||||||
const createdAt = Date.now() - 20_000;
|
|
||||||
session.packageOrder = [packageId];
|
|
||||||
session.packages[packageId] = {
|
|
||||||
id: packageId,
|
|
||||||
name: packageName,
|
|
||||||
outputDir,
|
|
||||||
extractDir,
|
|
||||||
status: "completed",
|
|
||||||
itemIds: [itemId],
|
|
||||||
cancelled: false,
|
|
||||||
enabled: true,
|
|
||||||
createdAt,
|
|
||||||
updatedAt: createdAt
|
|
||||||
};
|
|
||||||
session.items[itemId] = {
|
|
||||||
id: itemId,
|
|
||||||
packageId,
|
|
||||||
url: `https://mega.nz/file/${packageName}`,
|
|
||||||
provider: "megadebrid-api",
|
|
||||||
status: "completed",
|
|
||||||
retries: 0,
|
|
||||||
speedBps: 0,
|
|
||||||
downloadedBytes: directMkvSize,
|
|
||||||
totalBytes: directMkvSize,
|
|
||||||
progressPercent: 100,
|
|
||||||
fileName: directMkvName,
|
|
||||||
targetPath: directMkvPath,
|
|
||||||
resumable: true,
|
|
||||||
attempts: 1,
|
|
||||||
lastError: "",
|
|
||||||
fullStatus: "Fertig",
|
|
||||||
createdAt,
|
|
||||||
updatedAt: createdAt
|
|
||||||
};
|
|
||||||
|
|
||||||
const mkvLibraryDir = path.join(root, "mkv-library");
|
|
||||||
|
|
||||||
const manager = new DownloadManager(
|
|
||||||
{
|
|
||||||
...defaultSettings(),
|
|
||||||
outputDir: path.join(root, "downloads"),
|
|
||||||
extractDir: path.join(root, "extract"),
|
|
||||||
autoExtract: true,
|
|
||||||
autoRename4sf4sj: false,
|
|
||||||
collectMkvToLibrary: true,
|
|
||||||
mkvLibraryDir,
|
|
||||||
enableIntegrityCheck: false,
|
|
||||||
cleanupMode: "none"
|
|
||||||
},
|
|
||||||
session,
|
|
||||||
createStoragePaths(path.join(root, "state"))
|
|
||||||
);
|
|
||||||
|
|
||||||
const libraryPath = path.join(mkvLibraryDir, directMkvName);
|
|
||||||
await waitFor(() => fs.existsSync(libraryPath), 12000);
|
|
||||||
|
|
||||||
expect(fs.existsSync(libraryPath)).toBe(true);
|
|
||||||
// Filename darf NICHT umbenannt werden (Mega-Files sind oft schon korrekt benannt).
|
|
||||||
expect(fs.readFileSync(libraryPath).length).toBe(directMkvSize);
|
|
||||||
// Quelle ist weg (verschoben).
|
|
||||||
expect(fs.existsSync(directMkvPath)).toBe(false);
|
|
||||||
|
|
||||||
void manager;
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("does NOT move bonus files from Extras subdirectory to flat library", async () => {
|
it("does NOT move bonus files from Extras subdirectory to flat library", 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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user