fix(organize): 跨卷 Move 删源前全量校验、计划期 sha256 缓存、补全元数据带超时
- 跨卷移动删源前一律全量 sha256(不再抽样); - 计划期同尺寸目标的全量 sha256 按 (路径, 大小, 修改时间, inode) 缓存,向导预览 / 复核 / 执行不重复算; - metadata.resolve_organize 传 3 分钟超时的 signal; - 季号推断已由解析器统一推定(seasonInferred),补回归用例。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
4b49be7228
commit
d129a96110
5 files changed
+130
-11
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import { createTempTree, type TempTree } from "@umt/testkit";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { planImport } from "../../src/modules/acquisition-organize/services/organize.ts";
|
||||
import { createHarness, type Harness, pgAvailable, seedBase, seedEntity } from "./harness.ts";
|
||||
|
||||
// 季号推断(审视 ④ 风险 9):解析器对有集号没季号的文件已推定第 1 季(seasonInferred),服务端与域层用同一个值;
|
||||
// 带季号与不带季号的文件混在一起时,不带季号的不会因为「季目录不唯一」被静默排除(回归用例)。
|
||||
|
||||
const hasPg = await pgAvailable();
|
||||
const SIZE = 200_000;
|
||||
|
||||
describe.skipIf(!hasPg)("整理:季号推断收敛到一处", () => {
|
||||
let h: Harness;
|
||||
let lib: TempTree;
|
||||
let src: TempTree;
|
||||
|
||||
beforeAll(async () => {
|
||||
lib = await createTempTree({}, { sentinel: true, prefix: "umt-silib-" });
|
||||
src = await createTempTree(
|
||||
{
|
||||
"Pack/Show.S02E01.1080p.WEB-DL.mkv": SIZE,
|
||||
"Pack/Show.E05.1080p.WEB-DL.mkv": SIZE,
|
||||
},
|
||||
{ prefix: "umt-sisrc-" },
|
||||
);
|
||||
h = await createHarness("season_inference");
|
||||
await seedBase(h.db, { libraryPath: lib.root, cachePath: src.root, qbUrl: "http://127.0.0.1:9" });
|
||||
}, 60_000);
|
||||
afterAll(async () => {
|
||||
await h?.close();
|
||||
for (const t of [lib, src]) await t?.cleanup();
|
||||
});
|
||||
|
||||
it("没季号的集按第 1 季入库,不以 no_target_dir 排除", async () => {
|
||||
const entity = await seedEntity(h.db, { kind: "series", title: "剧", year: 2020 });
|
||||
const plan = await planImport(h.d, {
|
||||
sourcePaths: [src.path("Pack")],
|
||||
mediaEntityId: entity,
|
||||
action: "copy",
|
||||
trigger: "manual",
|
||||
actorType: "user",
|
||||
dryRun: true,
|
||||
});
|
||||
const o = plan.plan!.organize;
|
||||
expect(o.excluded.filter((x) => x.reason === "no_target_dir")).toEqual([]);
|
||||
const e05 = o.files.find((f) => f.src.endsWith("Show.E05.1080p.WEB-DL.mkv"))!;
|
||||
expect(e05).toMatchObject({ season: 1, episodes: { start: 5, end: 5 } });
|
||||
expect(e05.dst).toContain("Season 1");
|
||||
expect(o.files.find((f) => f.src.endsWith("S02E01.1080p.WEB-DL.mkv"))!.dst).toContain("Season 2");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, rm, stat, utimes, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterAll, describe, expect, it } from "vitest";
|
||||
import { cachedSha256 } from "../../src/modules/acquisition-organize/services/organize-plan.ts";
|
||||
|
||||
// 计划期全量 sha256 按 (路径, 大小, 修改时间, inode) 缓存:同一文件没变时不重算,变了就重算。
|
||||
|
||||
const sha = (s: string) => createHash("sha256").update(s).digest("hex");
|
||||
|
||||
describe("计划期 sha256 缓存", () => {
|
||||
let dir = "";
|
||||
afterAll(async () => {
|
||||
if (dir) await rm(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("事实不变命中缓存;修改时间变了重算", async () => {
|
||||
dir = await mkdtemp(join(tmpdir(), "umt-shacache-"));
|
||||
const p = join(dir, "a.mkv");
|
||||
await writeFile(p, "AAAA");
|
||||
const t = new Date("2024-01-01T00:00:00Z");
|
||||
await utimes(p, t, t);
|
||||
const facts = async () => {
|
||||
const s = await stat(p);
|
||||
return { size: s.size, mtimeMs: s.mtimeMs, ino: s.ino };
|
||||
};
|
||||
expect(await cachedSha256(p, await facts())).toBe(sha("AAAA"));
|
||||
// 同大小、同修改时间、同 inode 的改写:按缓存(这正是缓存键的语义)
|
||||
await writeFile(p, "BBBB");
|
||||
await utimes(p, t, t);
|
||||
expect(await cachedSha256(p, await facts())).toBe(sha("AAAA"));
|
||||
// 修改时间变了:重算
|
||||
const t2 = new Date("2024-01-02T00:00:00Z");
|
||||
await utimes(p, t2, t2);
|
||||
expect(await cachedSha256(p, await facts())).toBe(sha("BBBB"));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user