Files
UHD_Media_Tool/packages/mapping/src/download.ts
T
rootandClaude Opus 5.5 2159765949 fix(download): 已入库任务不被 qB 中间态改回(H2)
importing / imported 只由整理侧推进:checkingResumeData / queuedDL / checkingUP / moving 等中间态只更新原始字段,不改回 queued / downloading / completed,completed_at 不重写;这期间也不做无进展判定。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-03 21:20:08 +08:00

90 lines
3.6 KiB
TypeScript

import type { DownloadStatus, PauseReason } from "@umt/contracts";
import type { DownloadTaskStatus as DomainDownloadStatus } from "@umt/domain";
// 下载状态:domain 只描述"下载器里看到的状态"(mapQbState 的结果),
// contracts / download_task.status 还叠加业务阶段(submitting / stalled / importing / imported)和 pauseReason。
export type { DomainDownloadStatus };
export interface ContractDownloadState {
status: DownloadStatus;
pauseReason: PauseReason | null;
}
const TO_CONTRACT: Record<DomainDownloadStatus, ContractDownloadState> = {
queued: { status: "queued", pauseReason: null },
downloading: { status: "downloading", pauseReason: null },
paused_by_threshold: { status: "paused", pauseReason: "threshold" },
paused_by_user: { status: "paused", pauseReason: "user" },
completed: { status: "completed", pauseReason: null },
error: { status: "failed", pauseReason: null },
missing_files: { status: "missing", pauseReason: null },
removed: { status: "removed", pauseReason: null },
};
/** contracts 状态 → 下载器层面的 domain 状态(业务叠加态折回其底层下载状态) */
const TO_DOMAIN: Record<DownloadStatus, DomainDownloadStatus | "by_pause_reason"> = {
queued: "queued",
// 提交中:下载器里还没有/刚出现,按排队处理
submitting: "queued",
downloading: "downloading",
paused: "by_pause_reason",
// 无进展是业务判定(detectNoProgress),下载器里仍是 downloading
stalled: "downloading",
completed: "completed",
// 入库阶段:下载本身已完成
importing: "completed",
imported: "completed",
failed: "error",
removed: "removed",
missing: "missing_files",
};
/** 暂停原因 → domain 状态:只有 threshold 会被阈值恢复自动恢复,其余都按用户暂停对待(REQ §8.4.1) */
const PAUSE_TO_DOMAIN: Record<PauseReason, DomainDownloadStatus> = {
threshold: "paused_by_threshold",
user: "paused_by_user",
policy: "paused_by_user",
agent: "paused_by_user",
};
export const DOMAIN_DOWNLOAD_STATUSES = Object.keys(TO_CONTRACT) as DomainDownloadStatus[];
export function toContractDownloadState(s: DomainDownloadStatus): ContractDownloadState {
return { ...TO_CONTRACT[s] };
}
export function toDomainDownloadStatus(status: DownloadStatus, pauseReason: PauseReason | null): DomainDownloadStatus {
const d = TO_DOMAIN[status];
if (d !== "by_pause_reason") return d;
return PAUSE_TO_DOMAIN[pauseReason ?? "user"];
}
/**
* 同步轮询时合并"下载器观察"与"当前业务状态":
* - importing / imported 只由整理侧推进:下载器的任何中间态(checkingResumeData / queuedDL / checkingUP / moving,
* 乃至出错 / 缺文件)都不把它改回下载阶段,同步只更新原始字段;只有 removed(调用方确认移除)例外;
* - 其余业务叠加态在其底层下载状态不变时保留(stalled 遇 downloading、submitting 遇 queued);
* - 暂停原因为 policy/agent 且下载器仍是暂停时保留原因,不被改写成 user;
* - 其余以观察为准。
*/
export function mergeObservedDownloadState(
current: ContractDownloadState,
observed: DomainDownloadStatus,
): ContractDownloadState {
const next = TO_CONTRACT[observed];
const keep =
((current.status === "importing" || current.status === "imported") && observed !== "removed") ||
(current.status === "stalled" && observed === "downloading") ||
(current.status === "submitting" && observed === "queued");
if (keep) return { ...current };
if (
current.status === "paused" &&
observed === "paused_by_user" &&
(current.pauseReason === "policy" || current.pauseReason === "agent")
) {
return { ...current };
}
return { ...next };
}