fix(organize): stop pending sidecars when replaying failed main files
This commit is contained in:
1 parent
b306d27d08
commit
09a4c9fc1e
2 files changed
+48
-1
No files matched your search
@@ -166,7 +166,10 @@ export async function executeRecord(
|
|||||||
.orderBy("ordinal")
|
.orderBy("ordinal")
|
||||||
.execute();
|
.execute();
|
||||||
const planned = new Map(frozen.organize.files.map((f) => [f.src, f]));
|
const planned = new Map(frozen.organize.files.map((f) => [f.src, f]));
|
||||||
const failedMains = new Set<string>();
|
// 主文件的失败可能已在上次执行提交;重放也要阻止随行文件脱离主文件继续搬运。
|
||||||
|
const failedMains = new Set(
|
||||||
|
files.filter((f) => f.role === "main" && f.status === "failed").map((f) => f.source_path),
|
||||||
|
);
|
||||||
/** 本轮 TIMEOUT/IO 类失败、留待任务重试的文件(结果不入账本,文件结果保持 running) */
|
/** 本轮 TIMEOUT/IO 类失败、留待任务重试的文件(结果不入账本,文件结果保持 running) */
|
||||||
const deferred: {
|
const deferred: {
|
||||||
src: string;
|
src: string;
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ describe.skipIf(!hasPg)("整理卡死回收(真实 PG + mock qB + 临时目录)"
|
|||||||
"stall/Stall.Show.S01E01.1080p.WEB-DL.x264-GRP.mkv": SIZE,
|
"stall/Stall.Show.S01E01.1080p.WEB-DL.x264-GRP.mkv": SIZE,
|
||||||
"cap/Cap.Show.S01E01.1080p.WEB-DL.x264-GRP.mkv": SIZE,
|
"cap/Cap.Show.S01E01.1080p.WEB-DL.x264-GRP.mkv": SIZE,
|
||||||
"offline/Offline.Show.S01E01.1080p.WEB-DL.x264-GRP.mkv": SIZE,
|
"offline/Offline.Show.S01E01.1080p.WEB-DL.x264-GRP.mkv": SIZE,
|
||||||
|
"sidecar/Sidecar.Show.S01E01.1080p.WEB-DL.x264-GRP.mkv": SIZE,
|
||||||
|
"sidecar/Sidecar.Show.S01E01.1080p.WEB-DL.x264-GRP.srt": 100,
|
||||||
},
|
},
|
||||||
{ prefix: "umt-reccache-" },
|
{ prefix: "umt-reccache-" },
|
||||||
);
|
);
|
||||||
@@ -208,6 +210,48 @@ describe.skipIf(!hasPg)("整理卡死回收(真实 PG + mock qB + 临时目录)"
|
|||||||
expect((await recoverStalledRecords(h.d)).failed).toBe(0);
|
expect((await recoverStalledRecords(h.d)).failed).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each(["copy", "move"] as const)("%s 重放已失败的主文件:未完成字幕留在源目录,不再搬运", async (action) => {
|
||||||
|
const entityId = await seedEntity(h.db, { kind: "series", title: `字幕恢复 ${action}`, year: 2024 });
|
||||||
|
const plan = await planImport(h.d, {
|
||||||
|
sourcePaths: [cache.path("sidecar")],
|
||||||
|
mediaEntityId: entityId,
|
||||||
|
action,
|
||||||
|
trigger: "manual",
|
||||||
|
actorType: "user",
|
||||||
|
});
|
||||||
|
expect(plan.status, JSON.stringify(plan)).toBe("running");
|
||||||
|
const recordId = plan.recordId!;
|
||||||
|
// 主文件失败已经提交,进程在处理随行字幕前退出;恢复时主文件不会再次执行。
|
||||||
|
await h.db
|
||||||
|
.updateTable("organization_file_result")
|
||||||
|
.set({ status: "failed", error: JSON.stringify({ code: "EEXIST", message: "目标冲突" }) })
|
||||||
|
.where("organization_record_id", "=", recordId)
|
||||||
|
.where("role", "=", "main")
|
||||||
|
.execute();
|
||||||
|
const sidecar = await h.db
|
||||||
|
.selectFrom("organization_file_result")
|
||||||
|
.selectAll()
|
||||||
|
.where("organization_record_id", "=", recordId)
|
||||||
|
.where("role", "=", "subtitle")
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
expect(sidecar.status).toBe("queued");
|
||||||
|
expect(sidecar.target_path).not.toBeNull();
|
||||||
|
|
||||||
|
expect((await executeRecord(h.d, recordId)).status).toBe("failed");
|
||||||
|
const result = await h.db
|
||||||
|
.selectFrom("organization_file_result")
|
||||||
|
.selectAll()
|
||||||
|
.where("id", "=", sidecar.id)
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
expect(result).toMatchObject({ status: "skipped", skip_reason: "main_failed", asset_operation_id: null });
|
||||||
|
expect(result.finished_at).not.toBeNull();
|
||||||
|
expect(existsSync(sidecar.source_path)).toBe(true);
|
||||||
|
expect(existsSync(join(lib.root, sidecar.target_path!))).toBe(false);
|
||||||
|
expect(
|
||||||
|
await h.db.selectFrom("asset_operation").select("id").where("organization_record_id", "=", recordId).execute(),
|
||||||
|
).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it("库根离线:不抛错耗尽重试,记录等待并开待处理;恢复后巡检重投(不计上限)并完成", async () => {
|
it("库根离线:不抛错耗尽重试,记录等待并开待处理;恢复后巡检重投(不计上限)并完成", async () => {
|
||||||
const id = await manualRecord("offline", "离线的剧");
|
const id = await manualRecord("offline", "离线的剧");
|
||||||
await rm(join(lib.root, ".uhd-root"));
|
await rm(join(lib.root, ".uhd-root"));
|
||||||
|
|||||||
Reference in new issue
Block a user