feat(organize): identify takeover downloads by file name and close organize decisions
Takeover identify (item 2): - Adopted tasks without source release / plan: on completion planImport parses main video path (file + parent + grandparent dirs, like MP MetaInfoPath) merged with the torrent name, then runs ID-2 matching via identifyParsed (local recall + TMDB; trigger "takeover" may query TMDB). auto -> bind/create entity, backfill download_task.media_entity_id and organize right away; no pool entry, no identity decision. - Failure / ambiguity: organize_identity decision with evidence and options: candidates, select entity (local / TMDB), provisional entity, dismiss. Decision closure (item 3): - decision-hooks registry: the decisions resolve route runs the owning module's executor before closing; errors keep the decision open. - organize_identity: backfill entity, freeze manual plan, enqueue run. - organize_blocked with target_exists: replace (verify + trash old) / keep both (rename "<name> (n)" with sidecars) / skip. - Successful organize closes both organize_identity and organize_blocked of the same download task (resolution manual_organize), also for manual path organizes matched to download tasks via path mappings. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
05d1f76b9f
commit
e7de7e2e69
13 files changed
+1127
-17
No files matched your search
@@ -0,0 +1,38 @@
|
||||
import type { DecisionRow } from "@umt/db";
|
||||
import type { ServerContext } from "./types.ts";
|
||||
|
||||
// 待处理"处理"端点(sources-identity routes/decisions.ts)的执行钩子:其它模块按 dedupe_key 认领自己的事项,
|
||||
// 在事项关闭前同步执行所选处理(失败抛错 → 事项保持打开,错误回给用户)。按 ctx 隔离(测试里多个装配互不影响)。
|
||||
|
||||
export interface DecisionResolveBody {
|
||||
optionKey?: string | undefined;
|
||||
dismiss?: boolean | undefined;
|
||||
note?: string | undefined;
|
||||
identity?:
|
||||
| { mediaEntityId?: string | undefined; tmdb?: { type: "movie" | "tv"; id: string } | undefined }
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export interface DecisionHookActor {
|
||||
type: string;
|
||||
id: string | null;
|
||||
}
|
||||
|
||||
export interface DecisionHook {
|
||||
/** 认领该事项(通常按 dedupe_key 前缀) */
|
||||
match(d: DecisionRow): boolean;
|
||||
/** 执行所选处理;返回写进 resolution.execution 的摘要 */
|
||||
execute(d: DecisionRow, body: DecisionResolveBody, actor: DecisionHookActor): Promise<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
const hooks = new WeakMap<ServerContext, DecisionHook[]>();
|
||||
|
||||
export function registerDecisionHook(ctx: ServerContext, hook: DecisionHook): void {
|
||||
const list = hooks.get(ctx) ?? [];
|
||||
list.push(hook);
|
||||
hooks.set(ctx, list);
|
||||
}
|
||||
|
||||
export function decisionHookFor(ctx: ServerContext, d: DecisionRow): DecisionHook | undefined {
|
||||
return hooks.get(ctx)?.find((h) => h.match(d));
|
||||
}
|
||||
Reference in new issue
Block a user