feat(adapters): qBittorrent, filesystem, TMDB and Emby adapters with testkit mocks (W1-3a)
- qbittorrent: WebUI API v2 client (cookie/API-key auth, add/info/files/filePrio, pause/resume with 5.x stop/start fallback, tags, delete with explicit deleteFiles, maindata), error classes, idempotent submit with local infohash (own bencode) and post-timeout hash/probe-tag lookup; state mapping per MoviePilot - fs: copyWithVerify (tmp + fsync + size/sha256/sample + no-clobber commit), moveSafe (EXDEV surfaced), resumable crossDeviceMove, statSafe/checkRoot with root sentinel, budgeted incremental scan with cursor, ffprobe, statfs usage - tmdb: multi-language search/details/seasons, LRU+TTL cache with coalescing, rate-limit backoff, MoviePilot match chain (name normalization, year +-1) - emby: Emby/Jellyfin items/seasons/episodes/MediaSources, failures -> unknown - testkit: mock qB / TMDB / Emby servers, torrent builder, temp-tree fixtures - docs: W1-3a porting table appended to downloader-and-sites ledger Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
9c881e737f
commit
ef2e504a94
45 files changed
+5766
-1
No files matched your search
@@ -0,0 +1,44 @@
|
||||
export type FsErrorCode =
|
||||
| "EXDEV" // 跨设备,不会自动降级
|
||||
| "EEXIST" // 目标已存在(no-clobber)
|
||||
| "ENOENT"
|
||||
| "EACCES"
|
||||
| "ENOSPC"
|
||||
| "VERIFY_FAILED"
|
||||
| "SOURCE_CHANGED"
|
||||
| "ROOT_OFFLINE"
|
||||
| "ABORTED"
|
||||
| "IO";
|
||||
|
||||
export class FsError extends Error {
|
||||
override readonly name = "FsError";
|
||||
constructor(
|
||||
readonly code: FsErrorCode,
|
||||
message: string,
|
||||
readonly detail: { path?: string; syscall?: string; cause?: unknown } = {},
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function errnoOf(e: unknown): string | undefined {
|
||||
return (e as NodeJS.ErrnoException | undefined)?.code;
|
||||
}
|
||||
|
||||
export function toFsError(e: unknown, path?: string): FsError {
|
||||
if (e instanceof FsError) return e;
|
||||
const code = errnoOf(e);
|
||||
const mapped: FsErrorCode =
|
||||
code === "EXDEV" || code === "EEXIST" || code === "ENOENT" || code === "ENOSPC"
|
||||
? code
|
||||
: code === "EACCES" || code === "EPERM"
|
||||
? "EACCES"
|
||||
: (e as Error)?.name === "AbortError"
|
||||
? "ABORTED"
|
||||
: "IO";
|
||||
return new FsError(mapped, (e as Error)?.message ?? String(e), {
|
||||
...(path !== undefined ? { path } : {}),
|
||||
...((e as NodeJS.ErrnoException)?.syscall ? { syscall: (e as NodeJS.ErrnoException).syscall! } : {}),
|
||||
cause: e,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user