Compare commits
No commits in common. "8870a3aeca8f1e27e3190b667e88b6e1ce7eb3e7" and "e061997ed2da50111d3ba567cb8fc7b9b906f3e4" have entirely different histories.
8870a3aeca
...
e061997ed2
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.7.161",
|
"version": "1.7.160",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -3995,6 +3995,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
// they've stabilized (hybrid-extract fires a new rename scan after every
|
// they've stabilized (hybrid-extract fires a new rename scan after every
|
||||||
// archive completes, so nothing gets missed).
|
// archive completes, so nothing gets missed).
|
||||||
const FILE_STABILIZE_MIN_AGE_MS = this.fileStabilizeMinAgeMs;
|
const FILE_STABILIZE_MIN_AGE_MS = this.fileStabilizeMinAgeMs;
|
||||||
|
const now = Date.now();
|
||||||
for (const sourcePath of videoFiles) {
|
for (const sourcePath of videoFiles) {
|
||||||
if (shouldAbort?.()) {
|
if (shouldAbort?.()) {
|
||||||
return renamed;
|
return renamed;
|
||||||
@ -4012,13 +4013,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// now PER FILE erfassen (nicht einmal am Scan-Start): bei Hybrid-Extraktion
|
|
||||||
// werden weitere Dateien WÄHREND dieses Scans geschrieben. Ein am Scan-Start
|
|
||||||
// erfasstes now waere fuer solche Dateien aelter als ihre mtime → negatives
|
|
||||||
// ageMs → der Clock-Skew-Zweig unten wuerde sie faelschlich als "stabil"
|
|
||||||
// werten und einen Rename mitten im Extractor-Write ausloesen (EBUSY →
|
|
||||||
// deferred → der Collect moved die Datei mit Original-Namen, statt umbenannt).
|
|
||||||
const now = Date.now();
|
|
||||||
const ageMs = now - sourceStat.mtimeMs;
|
const ageMs = now - sourceStat.mtimeMs;
|
||||||
// Negative age = mtime in the future (clock skew, NTP correction,
|
// Negative age = mtime in the future (clock skew, NTP correction,
|
||||||
// VM resume after suspension). Treat as "definitely stable" so the
|
// VM resume after suspension). Treat as "definitely stable" so the
|
||||||
@ -4661,8 +4655,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
private async collectMkvFilesToLibrary(
|
private async collectMkvFilesToLibrary(
|
||||||
packageId: string,
|
packageId: string,
|
||||||
pkg: PackageEntry,
|
pkg: PackageEntry,
|
||||||
shouldAbort?: () => boolean,
|
shouldAbort?: () => boolean
|
||||||
deferFreshFiles = false
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this.settings.collectMkvToLibrary) {
|
if (!this.settings.collectMkvToLibrary) {
|
||||||
return;
|
return;
|
||||||
@ -4824,29 +4817,13 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
// Skip 0-byte files from failed/partial extractions
|
// Skip 0-byte files from failed/partial extractions
|
||||||
let sourceSize = 0;
|
let sourceSize = 0;
|
||||||
let sourceMtimeMs = 0;
|
|
||||||
try {
|
try {
|
||||||
const stat = await fs.promises.stat(sourcePath);
|
const stat = await fs.promises.stat(sourcePath);
|
||||||
sourceSize = stat.size;
|
sourceSize = stat.size;
|
||||||
sourceMtimeMs = stat.mtimeMs;
|
|
||||||
} catch {
|
} catch {
|
||||||
skipped += 1;
|
skipped += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Frische-Skip (nur Hybrid-Pfad: deferFreshFiles=true): eine gerade extrahierte
|
|
||||||
// Datei wird vom Auto-Rename absichtlich deferred (noch nicht stabil / EBUSY).
|
|
||||||
// Wuerde der Collect sie JETZT moven, landet sie mit Original-Namen in der
|
|
||||||
// Library statt umbenannt (genau der gemeldete "1-2 pro Staffel nicht
|
|
||||||
// umbenannt"-Bug). Wir defern sie ebenfalls → eine spaetere Hybrid-Runde oder
|
|
||||||
// der finale Deferred-Pass (deferFreshFiles=false) benennt sie um + sammelt sie.
|
|
||||||
if (deferFreshFiles && this.fileStabilizeMinAgeMs > 0) {
|
|
||||||
const ageMs = Date.now() - sourceMtimeMs;
|
|
||||||
if (ageMs >= 0 && ageMs < this.fileStabilizeMinAgeMs) {
|
|
||||||
logger.info(`MKV-Sammelordner: ${path.basename(sourcePath)} uebersprungen — Datei noch frisch (${Math.floor(ageMs)}ms), wird nach Stabilisierung gesammelt`);
|
|
||||||
skipped += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sourceSize === 0) {
|
if (sourceSize === 0) {
|
||||||
logger.warn(`MKV-Sammelordner: überspringe 0-Byte-Datei ${path.basename(sourcePath)}`);
|
logger.warn(`MKV-Sammelordner: überspringe 0-Byte-Datei ${path.basename(sourcePath)}`);
|
||||||
const resolved = this.inferItemForMediaLog(pkg, sourcePath, path.basename(sourcePath), targetDir);
|
const resolved = this.inferItemForMediaLog(pkg, sourcePath, path.basename(sourcePath), targetDir);
|
||||||
@ -11384,21 +11361,15 @@ export class DownloadManager extends EventEmitter {
|
|||||||
hybridSet.add(hybridController);
|
hybridSet.add(hybridController);
|
||||||
const hybridShouldAbort = (): boolean => hybridController.signal.aborted || this.session.packages[packageId] !== pkg;
|
const hybridShouldAbort = (): boolean => hybridController.signal.aborted || this.session.packages[packageId] !== pkg;
|
||||||
void (async () => {
|
void (async () => {
|
||||||
// Atomare Kopplung von Rename + Collect in EINER chainPackageFileOp-Kette,
|
|
||||||
// damit zwischen ihnen keine andere (ueberlappende) Hybrid-Runde ihren
|
|
||||||
// Collect einschieben kann (das war der Rename-Race: ein Collect moved
|
|
||||||
// eine Datei bevor der zugehoerige Rename lief). Wichtig: die IMPL-Variante
|
|
||||||
// des Renames verwenden — die Public-Variante ruft selbst chainPackageFileOp
|
|
||||||
// auf, was hier zu verschachteltem Chaining (Deadlock) fuehren wuerde.
|
|
||||||
// deferFreshFiles=true: Dateien die der Rename als "noch frisch" auslaesst
|
|
||||||
// werden vom Collect ebenfalls deferred (statt mit Original-Namen gemoved).
|
|
||||||
try {
|
try {
|
||||||
await this.chainPackageFileOp(pkg.id, async () => {
|
await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg, hybridShouldAbort);
|
||||||
await this.autoRenameExtractedVideoFilesImpl(pkg.extractDir, pkg, hybridShouldAbort);
|
|
||||||
await this.collectMkvFilesToLibrary(packageId, pkg, hybridShouldAbort, true);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(`Hybrid Post-Extract (Rename+Collect) Fehler: pkg=${pkg.name}, reason=${compactErrorText(err)}`);
|
logger.warn(`Hybrid Auto-Rename Fehler: pkg=${pkg.name}, reason=${compactErrorText(err)}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.chainPackageFileOp(pkg.id, () => this.collectMkvFilesToLibrary(packageId, pkg, hybridShouldAbort));
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`Hybrid MKV-Collection Fehler: pkg=${pkg.name}, reason=${compactErrorText(err)}`);
|
||||||
} finally {
|
} finally {
|
||||||
const set = this.packageHybridPostProcessControllers.get(packageId);
|
const set = this.packageHybridPostProcessControllers.get(packageId);
|
||||||
if (set) {
|
if (set) {
|
||||||
|
|||||||
@ -9358,77 +9358,6 @@ describe("download manager", () => {
|
|||||||
expect(fs.existsSync(originalExtractedPath)).toBe(false);
|
expect(fs.existsSync(originalExtractedPath)).toBe(false);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("hybrid collect defers fresh files instead of moving them unrenamed; final pass collects them", async () => {
|
|
||||||
// Regression: User-Report — bei Hybrid-Extraktion blieben 1-2 Dateien pro
|
|
||||||
// Staffel unbenannt (mit Original-Scene-Namen in der Library). Ursache: eine
|
|
||||||
// frisch extrahierte Datei wird vom Auto-Rename absichtlich deferred (noch nicht
|
|
||||||
// stabil), aber der Collect moved sie vorher mit Original-Namen. Fix: der
|
|
||||||
// Hybrid-Collect (deferFreshFiles=true) ueberspringt frische Dateien; der finale
|
|
||||||
// Deferred-Pass (deferFreshFiles=false) sammelt sie nach Stabilisierung ein.
|
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
|
||||||
tempDirs.push(root);
|
|
||||||
|
|
||||||
const packageName = "Fresh.Defer.Test.S01.German.720p.BluRay.x264-GRP";
|
|
||||||
const outputDir = path.join(root, "downloads", packageName);
|
|
||||||
const extractDir = path.join(root, "extract", packageName);
|
|
||||||
fs.mkdirSync(extractDir, { recursive: true });
|
|
||||||
|
|
||||||
const mkvName = "grp-freshshow.s01e07-720p.mkv";
|
|
||||||
const mkvPath = path.join(extractDir, mkvName);
|
|
||||||
fs.writeFileSync(mkvPath, Buffer.alloc(4096, 7)); // mtime = jetzt → "frisch"
|
|
||||||
|
|
||||||
const session = emptySession();
|
|
||||||
const packageId = `${packageName}-pkg`;
|
|
||||||
const createdAt = Date.now() - 20_000;
|
|
||||||
session.packageOrder = [packageId];
|
|
||||||
session.packages[packageId] = {
|
|
||||||
id: packageId,
|
|
||||||
name: packageName,
|
|
||||||
outputDir,
|
|
||||||
extractDir,
|
|
||||||
status: "downloading",
|
|
||||||
itemIds: [],
|
|
||||||
cancelled: false,
|
|
||||||
enabled: true,
|
|
||||||
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"))
|
|
||||||
);
|
|
||||||
// In Tests ist fileStabilizeMinAgeMs=0 (Frische-Erkennung aus) — fuer diesen
|
|
||||||
// Test aktivieren, damit die gerade erstellte Datei als "frisch" gilt.
|
|
||||||
(manager as any).fileStabilizeMinAgeMs = 30_000;
|
|
||||||
|
|
||||||
const libPath = path.join(mkvLibraryDir, mkvName);
|
|
||||||
|
|
||||||
// Hybrid-Collect (deferFreshFiles=true): frische Datei darf NICHT gemoved werden.
|
|
||||||
await (manager as any).collectMkvFilesToLibrary(packageId, session.packages[packageId], undefined, true);
|
|
||||||
expect(fs.existsSync(libPath)).toBe(false);
|
|
||||||
expect(fs.existsSync(mkvPath)).toBe(true);
|
|
||||||
|
|
||||||
// Finaler Deferred-Pass (deferFreshFiles=false): sammelt die Datei trotzdem ein.
|
|
||||||
await (manager as any).collectMkvFilesToLibrary(packageId, session.packages[packageId], undefined, false);
|
|
||||||
expect(fs.existsSync(libPath)).toBe(true);
|
|
||||||
expect(fs.existsSync(mkvPath)).toBe(false);
|
|
||||||
|
|
||||||
void manager;
|
|
||||||
}, 20000);
|
|
||||||
|
|
||||||
it("moves direct MKV download from outputDir to library when no archive present (Mega-Debrid flow)", async () => {
|
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-"));
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-dm-"));
|
||||||
tempDirs.push(root);
|
tempDirs.push(root);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user