From 7f7bcf8ab2d2b2e69e9bc57ba97fd522a40ba22e Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Wed, 22 Apr 2026 02:25:10 +0200 Subject: [PATCH] v1.7.151 Review-Findings nachgepflegt: 3 Edge-Cases entschaerft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Independent code review fand drei echte Probleme an v1.7.151: (a) File-stability check bei Clock-Skew rueckwaerts: negative ageMs (mtime in der Zukunft, z.B. NTP-Korrektur, VM-Resume) wurde von "ageMs < 2000" als "frisch" interpretiert → Datei stuck bis Clock aufschliesst. Fix: ageMs >= 0 zusaetzlich pruefen — negativ = "definitiv stabil". (c) Suffix-Loop koennte Source-File als Resolved-Target waehlen: wenn Source schon ".2.mkv" heisst und das Original ".mkv" anderswo existiert, koennte die .2/.3-Loop sich selbst auswaehlen. Fix: pathKey-Vergleich gegen sourcePath im Loop, springt weiter. (f) xX-Format matched x264/x265/x266 Codec-Tokens: "5x265.x265.mkv" wurde als S05E265 interpretiert. "Movie.x264-GROUP.mkv" konnte phantome Episode triggern. Fix: zweite Number-Group auf \d{1,2} (max 99) gecapped + negativer Lookahead [\dx] dahinter. 3-stellige xX-Episoden (sehr selten) gehen verloren — moderne SxxEnnn deckt das ab. Schutz gegen alle gaengigen Codecs (x264/265/266, h264/265) und Aspect-Ratios (1920x1080). Tests: neue assertions fuer x264/x265/aspect-ratio + 10x99 vs 10x100. 591/591 gruen. --- src/main/download-manager.ts | 20 +++++++++++++++++--- tests/auto-rename.test.ts | 23 +++++++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index cb7cb1d..34482eb 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -1017,8 +1017,10 @@ function hasSceneGroupSuffix(fileName: string): boolean { return isValidSceneGroupSuffix(suffix); } -/** Older scene releases used "1x01" / "01x100" instead of "S01E01". */ -const SCENE_EPISODE_X_RE = /(?:^|[._\-\s])(\d{1,2})x(\d{1,3})(?:x(\d{1,3}))?(?!\d)/i; +/** Older scene releases used "1x01" instead of "S01E01". The episode group + * is capped at 2 digits so the regex does NOT falsely match codec tokens + * like "x264" / "x265" / "x266" or aspect ratios like "1920x1080". */ +const SCENE_EPISODE_X_RE = /(?:^|[._\-\s])(\d{1,2})x(\d{1,2})(?:x(\d{1,2}))?(?![\dx])/i; export function extractEpisodeToken(fileName: string): string | null { const text = String(fileName || ""); @@ -3979,7 +3981,10 @@ export class DownloadManager extends EventEmitter { continue; } const ageMs = now - sourceStat.mtimeMs; - if (ageMs < FILE_STABILIZE_MIN_AGE_MS) { + // Negative age = mtime in the future (clock skew, NTP correction, + // VM resume after suspension). Treat as "definitely stable" so the + // file doesn't get stuck waiting for the wall clock to catch up. + if (ageMs >= 0 && ageMs < FILE_STABILIZE_MIN_AGE_MS) { logger.info(`Auto-Rename: ${sourceName} uebersprungen — Datei noch frisch (${Math.floor(ageMs)}ms), wird beim naechsten Scan behandelt`); continue; } @@ -4290,6 +4295,15 @@ export class DownloadManager extends EventEmitter { let resolvedTarget: string | null = null; for (let suffixN = 2; suffixN <= 99; suffixN += 1) { const candidate = path.join(targetDir, `${targetBase}.${suffixN}${targetExt}`); + // Defensive: never pick the source file as our resolved target. + // If sourceName is already e.g. ".2.mkv", existsAsync would + // see it as "existing" and the loop would otherwise pick ".3" + // — but if pathKey matches (case-insensitive), bail to next idx + // so we don't accidentally rename source-onto-itself with a + // surprising suffix. + if (pathKey(candidate) === pathKey(sourcePath)) { + continue; + } if (!(await this.existsAsync(candidate))) { resolvedTarget = candidate; break; diff --git a/tests/auto-rename.test.ts b/tests/auto-rename.test.ts index 5026dbb..7200fd7 100644 --- a/tests/auto-rename.test.ts +++ b/tests/auto-rename.test.ts @@ -59,10 +59,15 @@ describe("looksLikeObfuscatedSceneFileName", () => { }); describe("extractEpisodeToken (extended formats)", () => { - it("recognizes the older xX format", () => { + it("recognizes the older xX format (capped at 2 episode digits)", () => { expect(extractEpisodeToken("show.1x01.720p.mkv")).toBe("S01E01"); - expect(extractEpisodeToken("Show.Name.10x100.mkv")).toBe("S10E100"); expect(extractEpisodeToken("show-2x05-hdtv.mkv")).toBe("S02E05"); + expect(extractEpisodeToken("Show.Name.10x99.mkv")).toBe("S10E99"); + // 3-digit episode in xX format is intentionally NOT supported — would + // collide with codec tokens (x264/x265/x266). 3-digit episodes still + // work in the modern SxxEnnn format which has explicit S/E delimiters. + expect(extractEpisodeToken("Show.Name.10x100.mkv")).toBeNull(); + expect(extractEpisodeToken("Show.Name.S10E100.mkv")).toBe("S10E100"); }); it("does not falsely match resolution tokens like 1080x720", () => { @@ -71,6 +76,20 @@ describe("extractEpisodeToken (extended formats)", () => { expect(extractEpisodeToken("show.1080p.mkv")).toBeNull(); expect(extractEpisodeToken("show.S01E01.1080p.mkv")).toBe("S01E01"); }); + + it("does not falsely match codec tokens like x264 / x265 (caps episode digits)", () => { + // First number 5, second number capped to 2 digits → "5x265" CANNOT + // match because 265 has 3 digits. Same for x264, x266, h264, h265. + expect(extractEpisodeToken("Movie.x264-GROUP.mkv")).toBeNull(); + expect(extractEpisodeToken("Movie.5x265.x265.mkv")).toBeNull(); + // SxxExx still wins ahead of phantom xX matches. + expect(extractEpisodeToken("Show.S01E01.x265-GROUP.mkv")).toBe("S01E01"); + }); + + it("does not falsely match common aspect ratios like 1920x1080", () => { + // 1920 has 4 digits, first group capped at 2 → no match. + expect(extractEpisodeToken("Movie.1920x1080.mkv")).toBeNull(); + }); }); describe("extractEpisodeToken", () => {