feat(downloads): 下载器单独的水位与保种天数
- contracts DownloaderPolicy(存 downloader_instance.settings.policy):硬阈值 / 恢复水位 / 清理目标 / 保种天数, 不填用全局 cache / retention - volumeThresholds:卷的生效水位 = 各实例「自己的值 ?? 全局」取最小;decideVolume、阈值清理、手动恢复、 首页卷信息、定时清理检查都改用它;cleanupCandidates 保种天数按实例 - 保存时校验合并全局后 恢复 < 硬阈值、清理目标 ≤ 恢复 - 下载器设置表单新增四项(留空用全局) Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ff79554a09
commit
ee128e0b3f
9 files changed
+158
-14
No files matched your search
@@ -7,6 +7,7 @@ import {
|
||||
DownloaderInstance,
|
||||
DownloaderInstanceCreate,
|
||||
DownloaderInstanceUpdate,
|
||||
DownloaderPolicy,
|
||||
IdParam,
|
||||
InstanceTestResult,
|
||||
LibraryRoot,
|
||||
@@ -262,6 +263,18 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
throw new ApiException("validation_failed", "API Key 认证需要填写 API Key");
|
||||
return { ...curSettings, ...(next.settings ?? {}), authKind };
|
||||
};
|
||||
/** 下载器自己的水位 / 保种:格式合法,且与全局值合并后仍满足 恢复 < 硬阈值、清理目标 ≤ 恢复 */
|
||||
const checkPolicy = async (settings: Record<string, unknown> | undefined) => {
|
||||
if (settings?.policy === undefined) return;
|
||||
const r = DownloaderPolicy.safeParse(settings.policy);
|
||||
if (!r.success) throw new ApiException("validation_failed", "下载器水位 / 保种设置格式不对");
|
||||
const g = await ctx.settings.get("cache");
|
||||
const hard = r.data.hardThresholdPercent ?? g.hardThresholdPercent;
|
||||
const resume = r.data.resumePercent ?? g.resumePercent;
|
||||
const target = r.data.cleanupTargetPercent ?? g.cleanupTargetPercent;
|
||||
if (resume >= hard) throw new ApiException("validation_failed", "恢复水位必须低于硬阈值");
|
||||
if (target > resume) throw new ApiException("validation_failed", "清理目标不得高于恢复水位");
|
||||
};
|
||||
/** 默认下载器至多一个:设新的之前先取消旧的 */
|
||||
const clearDefault = (exceptId?: string) => {
|
||||
let q = ctx.db.updateTable("downloader_instance").set({ is_default: false }).where("is_default", "=", true);
|
||||
@@ -273,6 +286,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
if (rest.isDefault) await clearDefault();
|
||||
const credential = secretInput(rawCredential) ?? undefined;
|
||||
const settings = dlAuth(authKind, { ...rest, ...(credential ? { credential } : {}) });
|
||||
await checkPolicy(settings ?? (rest.settings as Record<string, unknown> | undefined));
|
||||
const keep = authKind !== "none" && credential;
|
||||
const ref = keep ? await ctx.secrets.create({ purpose: "downloader_password", plaintext: credential }) : null;
|
||||
try {
|
||||
@@ -303,6 +317,7 @@ export function registerInstanceRoutes(app: ModuleApp, ctx: ServerContext) {
|
||||
if (credential === null && (authKind ?? curAuth) === "api_key")
|
||||
throw new ApiException("validation_failed", "API Key 认证不能清除 API Key;不用认证时把认证方式改为无认证");
|
||||
const settings = dlAuth(authKind, { ...rest, ...(credential ? { credential } : {}) }, cur);
|
||||
await checkPolicy(settings);
|
||||
const values = toDb({ ...rest, ...(settings ? { settings } : {}) }, JSON_COLS);
|
||||
let dropRef: string | null = null;
|
||||
if (authKind === "none" || credential === null) {
|
||||
|
||||
Reference in new issue
Block a user