feat(contracts,server,web): files G6–G8 fields and identify S-7 contract
- G6: FileRoot.pendingCount/pendingPartial for download roots (same rule as the unidentified filter, recursive, cached 5 min, invalidated by files.changed) - G7: FileEntry.parsedHint from parseReleasePath (file + parent dirs); the web subline uses it and no longer parses names itself - G8: FileIdentifyCandidate.posterUrl (TMDB poster_path via adapter and catalog.lookup, local entities from metadata.posterPath); thumbnails in the identify panel - S-7 contract: FilesIdentifyResult.remoteTaskId/remotePending, MediaServerInventory.partial, EventType identify.completed with IdentifyCompletedPayload; identify panel (useIdentifyLive) and organize wizard merge remote results by root + path, buffering events that beat the response Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
fd2f7bc33d
commit
abc69886ea
26 files changed
+510
-134
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveFilesMock } from "~/api/files.ts";
|
||||
import {
|
||||
applyIdentifyCompleted,
|
||||
clickSelect,
|
||||
collapseCrumbs,
|
||||
deleteBlockText,
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
opApplies,
|
||||
opDisabledReason,
|
||||
parseEpisodes,
|
||||
parseHint,
|
||||
pruneSelection,
|
||||
selectAll,
|
||||
shiftEpisodes,
|
||||
@@ -84,6 +84,7 @@ const base = {
|
||||
download: null,
|
||||
media: null,
|
||||
dirSummary: null,
|
||||
parsedHint: null,
|
||||
};
|
||||
|
||||
describe("状态与禁用原因", () => {
|
||||
@@ -173,6 +174,7 @@ describe("v2 菜单与副行", () => {
|
||||
name: "The.Last.of.Us.S01E02.1080p.WEB-DL.mkv",
|
||||
fileKind: "video" as const,
|
||||
status: "unidentified" as const,
|
||||
parsedHint: "The Last of Us · S01E02 · 1080p",
|
||||
};
|
||||
const asset = {
|
||||
assetId: "a",
|
||||
@@ -227,8 +229,8 @@ describe("v2 菜单与副行", () => {
|
||||
dirSummary: { videoCount: 13, registeredCount: 0, partial: false },
|
||||
})?.text,
|
||||
).toBe("13 视频 · 0 已登记");
|
||||
expect(parseHint("Oppenheimer.2023.2160p.BluRay.x265.mkv")).toBe("Oppenheimer (2023) · 2160p");
|
||||
expect(parseHint("readme.txt")).toBeNull();
|
||||
// 没有 parsedHint(服务端解析不出标题)时不显示副行,前端不自己解析
|
||||
expect(entrySubline({ ...video, parsedHint: null })).toBeNull();
|
||||
});
|
||||
|
||||
it("初始位置:上次位置 > 下载目录 > 库根;离线根跳过", () => {
|
||||
@@ -275,3 +277,55 @@ describe("transferTargetProblem(U13)", () => {
|
||||
expect(transferTargetProblem(items, { root: "dl", path: "packs" })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("识别远端回填(S-7)", () => {
|
||||
const item = (path: string, over: Record<string, unknown> = {}) => ({
|
||||
root: "dl:x:0",
|
||||
path,
|
||||
parsed: {
|
||||
title: "T",
|
||||
year: null,
|
||||
season: null,
|
||||
episodes: [],
|
||||
resolution: null,
|
||||
source: null,
|
||||
releaseGroup: null,
|
||||
mediaKind: null,
|
||||
},
|
||||
candidates: [],
|
||||
suggestion: { mediaEntityId: null, contentUnitId: null, contentUnit: null },
|
||||
existing: { assetId: null },
|
||||
warnings: ["远端候选在后台查询,查完自动补上"],
|
||||
...over,
|
||||
});
|
||||
const base = { items: [item("a.mkv"), item("b.mkv")], truncated: false, remoteTaskId: "run-1", remotePending: 1 };
|
||||
|
||||
it("同一 taskId 按 root + path 替换条目并清掉待查标记", () => {
|
||||
const done = item("b.mkv", { warnings: [], candidates: [{ title: "B" }] });
|
||||
const r = applyIdentifyCompleted(
|
||||
base as never,
|
||||
{
|
||||
taskId: "run-1",
|
||||
status: "succeeded",
|
||||
items: [done],
|
||||
truncated: false,
|
||||
excludedBySize: 0,
|
||||
error: null,
|
||||
} as never,
|
||||
);
|
||||
expect(r.remoteTaskId).toBeNull();
|
||||
expect(r.remotePending).toBe(0);
|
||||
expect(r.items[0]).toBe(base.items[0]);
|
||||
expect(r.items[1]).toBe(done);
|
||||
});
|
||||
it("别的 taskId 原样返回;失败时保留本地结果并写明原因", () => {
|
||||
const other = { taskId: "run-2", status: "succeeded", items: [], truncated: false, excludedBySize: 0, error: null };
|
||||
expect(applyIdentifyCompleted(base as never, other as never)).toBe(base);
|
||||
const f = applyIdentifyCompleted(
|
||||
base as never,
|
||||
{ ...other, taskId: "run-1", status: "failed", error: "超时" } as never,
|
||||
);
|
||||
expect(f.remoteTaskId).toBeNull();
|
||||
expect(f.items[0]?.warnings).toEqual(["远端查询失败,只有本地结果:超时"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user