Files
UHD_Media_Tool/apps/server/src/modules/decision-hooks.ts
T
rootandClaude Opus 5.5 e7de7e2e69 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]>
2026-09-29 20:54:19 +08:00

39 lines
1.4 KiB
TypeScript

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));
}