Compare commits
No commits in common. "84d02c5f9886f9af5e1344797c7190a00e47065b" and "7ab508617a1196963403177f0b1232539c11c26d" have entirely different histories.
84d02c5f98
...
7ab508617a
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.7.149",
|
"version": "1.7.148",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -1685,18 +1685,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
|
|
||||||
private itemContributedBytes = new Map<string, number>();
|
private itemContributedBytes = new Map<string, number>();
|
||||||
|
|
||||||
/** Per-package serialization for autoRenameExtractedVideoFiles. The hybrid-
|
|
||||||
* extract path fires a fire-and-forget rename after every successful
|
|
||||||
* archive iteration, and the deferred post-process path runs another
|
|
||||||
* rename when post-processing finishes. For a multi-archive package
|
|
||||||
* (e.g. 25 episodes) these can overlap, with two concurrent scans seeing
|
|
||||||
* the same files, racing to rename, and producing "Ziel existiert" /
|
|
||||||
* ENOENT noise plus occasionally missed renames. We chain subsequent
|
|
||||||
* invocations onto the running promise so the second scan re-scans
|
|
||||||
* AFTER the first finishes — picking up any newly-arrived files while
|
|
||||||
* guaranteeing no two scans operate on the same fileset simultaneously. */
|
|
||||||
private autoRenameInFlight = new Map<string, Promise<number>>();
|
|
||||||
|
|
||||||
private runItemIds = new Set<string>();
|
private runItemIds = new Set<string>();
|
||||||
|
|
||||||
private runPackageIds = new Set<string>();
|
private runPackageIds = new Set<string>();
|
||||||
@ -3673,36 +3661,6 @@ export class DownloadManager extends EventEmitter {
|
|||||||
extractDir: string,
|
extractDir: string,
|
||||||
pkg?: PackageEntry,
|
pkg?: PackageEntry,
|
||||||
shouldAbort?: () => boolean
|
shouldAbort?: () => boolean
|
||||||
): Promise<number> {
|
|
||||||
// Serialize per-package: chain onto any in-flight scan for the same
|
|
||||||
// package so two scans never read the same fileset in parallel. Without
|
|
||||||
// this, hybrid-extract's per-archive trigger + the deferred post-process
|
|
||||||
// trigger frequently overlap and cause "Ziel existiert" / ENOENT
|
|
||||||
// log noise (and occasionally a missed rename when the second scan's
|
|
||||||
// chosen target file disappears between scan and rename).
|
|
||||||
if (!pkg) {
|
|
||||||
return this.autoRenameExtractedVideoFilesImpl(extractDir, undefined, shouldAbort);
|
|
||||||
}
|
|
||||||
const previous = this.autoRenameInFlight.get(pkg.id);
|
|
||||||
const next = (previous ?? Promise.resolve(0)).catch(() => 0).then(() =>
|
|
||||||
this.autoRenameExtractedVideoFilesImpl(extractDir, pkg, shouldAbort)
|
|
||||||
);
|
|
||||||
this.autoRenameInFlight.set(pkg.id, next);
|
|
||||||
try {
|
|
||||||
return await next;
|
|
||||||
} finally {
|
|
||||||
// Only clear the slot if no newer chained call took our place. This
|
|
||||||
// keeps the chain intact when several callers queue up at once.
|
|
||||||
if (this.autoRenameInFlight.get(pkg.id) === next) {
|
|
||||||
this.autoRenameInFlight.delete(pkg.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async autoRenameExtractedVideoFilesImpl(
|
|
||||||
extractDir: string,
|
|
||||||
pkg?: PackageEntry,
|
|
||||||
shouldAbort?: () => boolean
|
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
if (!this.settings.autoRename4sf4sj) {
|
if (!this.settings.autoRename4sf4sj) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@ -10375,84 +10375,4 @@ describe("download manager", () => {
|
|||||||
const pkgLogFiles = fs.readdirSync(packageLogsDir).filter((f) => f.startsWith("package_") && f.endsWith(".txt"));
|
const pkgLogFiles = fs.readdirSync(packageLogsDir).filter((f) => f.startsWith("package_") && f.endsWith(".txt"));
|
||||||
expect(pkgLogFiles.length).toBe(60);
|
expect(pkgLogFiles.length).toBe(60);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("serializes parallel auto-rename invocations for the same package (no Ziel existiert / ENOENT race)", async () => {
|
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-rename-race-"));
|
|
||||||
tempDirs.push(root);
|
|
||||||
const stateDir = path.join(root, "state");
|
|
||||||
fs.mkdirSync(stateDir, { recursive: true });
|
|
||||||
initPackageLogs(stateDir);
|
|
||||||
initItemLogs(stateDir);
|
|
||||||
initRenameLog(stateDir);
|
|
||||||
|
|
||||||
// Build extract tree with 3 episode-folders, each containing 1 obfuscated MKV
|
|
||||||
// mirroring the scene release pattern from the production log.
|
|
||||||
const extractDir = path.join(root, "extracted");
|
|
||||||
const episodes = [
|
|
||||||
{ folder: "Test.Show.S02E01.Pilot.GERMAN.WS.720p.HDTV.x264-aWake", file: "awa-testshow02e01hd.mkv" },
|
|
||||||
{ folder: "Test.Show.S02E02.Second.GERMAN.WS.720p.HDTV.x264-aWake", file: "awa-testshow02e02hd.mkv" },
|
|
||||||
{ folder: "Test.Show.S02E03.Third.GERMAN.WS.720p.HDTV.x264-aWake", file: "awa-testshow02e03hd.mkv" }
|
|
||||||
];
|
|
||||||
for (const ep of episodes) {
|
|
||||||
const dir = path.join(extractDir, ep.folder);
|
|
||||||
fs.mkdirSync(dir, { recursive: true });
|
|
||||||
fs.writeFileSync(path.join(dir, ep.file), Buffer.alloc(1024, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
const manager = new DownloadManager(
|
|
||||||
{
|
|
||||||
...defaultSettings(),
|
|
||||||
token: "rd-token",
|
|
||||||
outputDir: path.join(root, "downloads"),
|
|
||||||
extractDir,
|
|
||||||
autoExtract: false,
|
|
||||||
autoReconnect: false,
|
|
||||||
autoRename4sf4sj: true
|
|
||||||
},
|
|
||||||
emptySession(),
|
|
||||||
createStoragePaths(stateDir)
|
|
||||||
);
|
|
||||||
|
|
||||||
const pkg: any = {
|
|
||||||
id: "race-pkg-1",
|
|
||||||
name: "Test.Show.S02.GERMAN.WS.720p.HDTV.x264-aWake",
|
|
||||||
outputDir: path.join(root, "downloads", "Test.Show.S02.GERMAN.WS.720p.HDTV.x264-aWake"),
|
|
||||||
extractDir,
|
|
||||||
status: "completed",
|
|
||||||
itemIds: [],
|
|
||||||
cancelled: false,
|
|
||||||
enabled: true,
|
|
||||||
priority: "normal",
|
|
||||||
createdAt: 0,
|
|
||||||
updatedAt: 0,
|
|
||||||
downloadStartedAt: 0,
|
|
||||||
downloadCompletedAt: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fire two scans simultaneously for the SAME package — without
|
|
||||||
// serialization, both would race on the same fileset.
|
|
||||||
const [n1, n2] = await Promise.all([
|
|
||||||
(manager as any).autoRenameExtractedVideoFiles(extractDir, pkg),
|
|
||||||
(manager as any).autoRenameExtractedVideoFiles(extractDir, pkg)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// First scan should rename all 3 files. Second scan, having waited for
|
|
||||||
// the first via the in-flight promise, should find them already
|
|
||||||
// renamed (== 0 fresh renames). What matters is that BOTH calls
|
|
||||||
// resolved cleanly (no thrown ENOENT) and the disk state is correct.
|
|
||||||
expect(typeof n1).toBe("number");
|
|
||||||
expect(typeof n2).toBe("number");
|
|
||||||
expect(n1 + n2).toBe(3);
|
|
||||||
|
|
||||||
// All three episodes should now have the folder-derived name (the
|
|
||||||
// obfuscated source name was overridden via the v1.7.148 logic AND
|
|
||||||
// the rename actually succeeded for ALL of them, not just some).
|
|
||||||
for (const ep of episodes) {
|
|
||||||
const dir = path.join(extractDir, ep.folder);
|
|
||||||
const files = fs.readdirSync(dir);
|
|
||||||
const renamedFile = `${ep.folder}.mkv`;
|
|
||||||
expect(files).toContain(renamedFile);
|
|
||||||
expect(files).not.toContain(ep.file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user