nyaKazuha and Claude Fable 5
0ab3f3b953
feat(download): 多线程下载 + 重试指数退避(issue #17)
...
下载主循环由串行改为「并发下载 + 串行簿记」三段式:
- Phase A:无网络前置校验(官方性/目标路径/建目录)+ 判定跳过/需下载,
非官方 URL 在任何下载前 fail-fast
- Phase B:need-download 项经 scoped 线程池并发下载(默认并发 4,
可配 1..=256)。worker 只做只读 &self 的 pull_one(各 URL 独立
.part/目标文件),经 mpsc 把结果送回主线程;manifest/quarantine
簿记与进度回调全在主线程串行执行,无需加锁。首个失败或 should_cancel
置 cancel 标志,其余 worker 在任务边界停止
- Phase C:按 plan 顺序串行收尾——seed .hash 校验(顺序相关、可
fail-fast)+ 构建有序结果
fail-fast 与「不发布不完整资源」不变量保留;进度事件按 URL 配对但
顺序不再单调(并发下天然如此)。
curl 重试加指数退避(网络类 200ms→400ms→800ms…上限 5s;ETXTBSY 仍走
极短退避),并发下对官方 CDN 更礼貌;退避基值 cfg(test) 下为 0 不拖慢
单测。
并发度经 OfficialUpdateConfig.download_concurrency 贯通,CLI
--download-concurrency 与 BAT_DOWNLOAD_CONCURRENCY 可配,.env 模板
与 USERGUIDE/CURRENT_STATUS/CHANGELOG 同步。
验证:新增并发正确性测试(并发 8:每 URL 恰一次 started+finished、
全部落盘)、并发度钳制、退避时长计算、CLI/env 解析单测;progress
排序测试改为顺序无关不变量;workspace 全测试(20 套件) + fmt +
clippy --all-targets -D warnings 全绿。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-18 03:25:04 -07:00
nyaKazuha and Claude Fable 5
3edbe7ccee
feat(daemon): 任务历史文件态持久化与 .env 无参启动(issue #1)
...
任务持久化:
- 任务历史落 <state-dir>/bat-tasks.json(版本化、0600 原子写、不跟随
symlink),生命周期转换时 write-through;seq 持久化避免 pid 复用撞 ID
- daemon 重启恢复历史,中断时仍 queued/running 的任务标记 failed,
新增错误码 TASK_INTERRUPTED(BAT-ERR-700005)
- 损坏文件改名 .corrupt 留证后从空历史开始;无法识别的记录计数跳过
- core 新增 ErrorCode::ALL 公开码表与 from_id 反查(持久化错误码往返)
.env 配置:
- 首次启动在二进制所在目录释放 .env 模板(0600、create_new 防竞态),
之后每次启动加载为进程环境变量(不覆盖已存在变量)
- 优先级:CLI > 进程环境变量 > .env > 内置默认;BAT_* 键映射到
CliOptions 默认值,BAT_WATCH/BAT_DAEMON 仅对无子命令 Run 生效且
CLI 显式模式/dry-run 时让位;Redis 键预留(未接入)
- 工具/代理"非默认"判断改按 env 应用后基线,status/stop/logs 在
.env 存在时不误判;BAT_SKIP_ENV_FILE=1 整体禁用
验证:新增 10 个单测(env 解析/优先级/守卫回归/持久化往返/中断标记/
损坏恢复/未知类型跳过);真机 e2e:.env 释放加载、daemon 纯 .env 启动、
catalog.refresh 任务带类型化错误码落盘并跨 daemon 重启恢复(含日志);
fmt / clippy --workspace --all-targets -D warnings / test --workspace 全绿
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-17 21:15:47 -07:00
nyaKazuha and Claude Fable 5
11b19683cf
feat(daemon): 实现 catalog.* 命名空间与 resource.manifest(issue #1)
...
补齐 issue #1 方法分层中可立即实现的剩余命名空间:
- catalog.status:当前已发布版本的 catalog 概览(读取其 snapshot:
app/bundle 版本、connection group、addressables 根、端点与 marker
计数、launcher 元数据、GameMainConfig 摘要)。
- catalog.versions:版本历史(current / in_progress / previous /
failed,来自 official-version-state.json)。
- catalog.diff:当前 snapshot 相对上一个可用版本的差异(复用
YostarJpSyncSnapshot::diff 与 diff_extended_snapshot;含
changed_endpoint_urls 与 previous_snapshot_missing 标志;无上一版本
时按首次观察处理 is_initial=true)。
- catalog.refresh:经任务执行器的 catalog 更新检查任务(dry-run +
plan,不下载不审计;支持 force),返回 task_id 可轮询。
- resource.manifest:当前版本下载 manifest 的分页只读查询(offset
默认 0、limit 默认 100/上限 1000,BTreeMap 按 URL 有序保证分页稳定;
非法 limit → 700002)。infrastructure 公开
OfficialDownloadManifest(Entry) 与 read_download_manifest_at。
约定:只读查询在尚无已发布版本或文件缺失时返回 ok=true 且
data.available=false(正常状态而非错误,便于 Go 层直接分支)。
is_pending_rpc_method 缩减为 task.create / resource.repair /
patch.* / unityfs.*(后两者待引擎;repair 待引擎独立修复模式)。
新增 read_daemon_resource_state 供 resource/catalog 只读查询共用。
测试:新增 10 个 dispatch 单测(含 fixture:状态文件 + 版本状态 +
snapshot + manifest)与 read_download_manifest_at 单测;调整 pending
断言。真实 daemon 端到端验证(fixture 资源根 + 死代理快速失败):
raw socket 依次断言 catalog.status/versions/diff、resource.manifest
分页与参数错误、catalog.refresh 入队与 task.status 轮询、
resource.repair→700003、未知方法→700001,全部通过,daemon.stop 干净
退出。全量 fmt / clippy --workspace --all-targets -D warnings /
test --workspace 全绿。
已知后续(另行推进):任务配置校验失败(如缺少 app-version)尚未接入
输入域错误码(应为 100001,现落 900001)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-17 10:10:03 -07:00
nyaKazuha and Claude Fable 5
ab11cc899d
feat(official-sync): 下载失败携带类型化错误码
...
用类型化错误(不做 message 匹配)把资源下载失败归到准确的 BAT-ERR 码:
- curl_transfer:CurlFailureKind::error_code() 映射到网络域码(403→300001、
404→300002、5xx→300005、dns/connect/timeout/tls/interrupted→30001x、
proxy→310001、其余→300015);CurlRetryError::final_error_code() 取末次失败的码。
- official_download:新增 pub DownloadError { code, message },From<String>→internal,
Display/Error;pull_* 的返回类型由 String 改为 DownloadError,内部 String 错误经
From 归 internal,下载失败边界带 pull_one 的 error_code(),非官方 URL 带
non_official_url(300030)。
- official_update:pull 错误改用 anyhow::Error::new 保留 DownloadError 类型。
- 任务 worker:downcast DownloadError 取 code 写入任务记录的 error(失败不再一律
900001),非下载失败仍归 internal。
验证:新增 kind→code 映射单测;pull 404 测试断言 DownloadError.code()==HTTP_NOT_FOUND;
lib/core 全量测试通过,clippy --all-targets 干净。
范围说明:本次覆盖资源下载路径;launcher/metadata 拉取(official_launcher)的网络
失败仍归 internal,可按同一模式后续接入。
对应 issue #1(错误码细分映射,第一批)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-17 07:01:31 -07:00
nyaKazuha and Claude Fable 5
9a5fef23af
feat(daemon): 补全 task.cancel 与 task.logs
...
完成 task.* 命名空间:
- task.cancel:每个任务带共享的取消标志(Arc<AtomicBool>),worker 的 should_cancel
同时检查它和 daemon 停止标志;请求取消后置标志,worker 在下一个同步检查点中止,
任务转为 cancelled(协作式,不硬杀正在执行的 curl)。已结束任务返回 already-finished,
不存在返回 700004。
- task.logs:worker 把每条 progress 追加到任务的有界日志(上限 200 行),task.logs
返回该历史;不存在返回 700004。TaskRecord 的 cancel/log 字段 serde(skip),不进
task.status 输出。
- 新增 cancelled 任务状态;prune 与已结束判定纳入 cancelled。
- USERGUIDE RPC 方法表更新(cancel/logs 标为已实现)。
验证:新增取消/日志注册表单测(63 bin 测试全过);真机端到端确认触发 resource.sync
后 task.cancel 使任务转为 cancelled、task.logs 返回进度历史。
对应 issue #1(task.* 命名空间补全)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-17 06:00:26 -07:00
nyaKazuha and Claude Fable 5
01ea5d8fa5
feat(daemon): 任务执行器 + 异步 resource.sync/verify(阶段 1b)
...
在 daemon 里加入任务模型,让 resource.sync/verify 经 RPC 触发即返回 task_id、
可轮询进度与最终结果:
- 专用任务 worker 线程 + FIFO 队列(mpsc);TaskRegistry 句柄包住内存态注册表
(HashMap,有界保留 64 条已结束任务,运行中/排队中不裁剪),方法访问便于将来
换 Redis 等持久化后端。
- 进程内 sync 锁:watch 循环与 worker 在跑同步前都获取它,互相等待而非撞
.official-sync.lock 文件锁失败;只在实际跑同步的那段持锁,空闲睡眠不持锁。
- resource.sync/resource.verify 入队返回 { task_id, kind }(envelope status=accepted);
worker 复用 run_with_progress_and_cancellation(sync 用 daemon config + force;
verify 用 dry-run+plan+audit),进度写入任务记录,should_cancel 接 daemon 停止标志
(停机中止在途任务)。task.status/task.list 从注册表读真实数据。
验证:新增任务分发/入队/注册表单测(62 bin 测试全过);真机端到端确认经裸 socket
触发 resource.sync 返回 task_id、轮询 task.status 走到最终态(fake curl 失败→
failed 且记录 error)、task.list 可见、resource.verify 亦入队。满足 issue #1 验收
「至少一个同步任务可经 RPC 触发并经 task.status 查询进度和最终结果」。
任务仅存内存(随 daemon 生死);task.cancel/logs、错误码细分映射留待后续。
对应 issue #1(任务模型 + 最小方法集打通)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-17 05:23:24 -07:00
nyaKazuha and Claude Fable 5
b95a3ac417
feat(daemon): RPC 统一 envelope、命名空间分发与只读方法
...
阶段 1a 的协议地基(承接错误码模型):
- 统一 envelope:所有响应装入 JSON-RPC 2.0 result 的
{ ok, status, data, error, request_id };error 用 bat_core::ApiError
(含 BAT-ERR 码/kind/domain/location/retryable)。传输层解析失败仍走
JSON-RPC 顶层 error。request_id 为 req-<pid>-<seq>。
- 命名空间分发:方法改用国际惯例 <namespace>.<action>;daemon.* 为规范名,
bat.* 保留为兼容别名(canonical_rpc_method 解析)。未知方法 → 700001,
已规划未实现(resource.sync/verify、catalog.*/patch.*/unityfs.* 等)→ 700003。
- 只读方法:daemon.status/logs/stop/reload/refresh(迁移)、resource.state(新增,
返回资源发布根 + 版本状态 + 上次同步结果)、task.status/task.list(任务库暂空,
status → 700004,list → 空列表;执行器留待阶段 1b)。
- 客户端 daemon_rpc_call 解包 envelope(检查 ok、返回 data、把 error 转 Err),
CLI 调用方零改动。
验证:新增分发/别名/envelope 单测(60 bin 测试全过);真机起停 daemon 端到端确认
daemon.status/logs/resource.state/task.list 经 CLI 与裸 socket 均正确返回 envelope,
bat.* 别名生效,未知/未实现返回对应 BAT-ERR 码。
对应 issue #1(协议基础设施 + 最小只读方法集)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-17 04:19:22 -07:00
nyaKazuha and Claude Fable 5
232d74b617
fix(daemon): 串行化 bat-status.json 的读-改-写
...
watch 循环线程(update_daemon_status/update_daemon_progress)与 RPC 处理线程
(update_daemon_state_only)都会对状态文件做“读→改字段→写回”。单次写虽原子,
但整段读-改-写非原子,无互斥时并发线程会 last-writer-wins 丢失对方的字段更新。
新增进程级 DAEMON_STATUS_FILE_LOCK,三处读-改-写函数入口统一获取该锁串行化
(锁中毒时取 into_inner 继续,不 panic)。
对应 issue #18 维护清单 3-4。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 09:38:46 -07:00
nyaKazuha and Claude Fable 5
0656ad05c7
refactor(official-sync): CLI 工程卫生清理
...
- doctor daemon_status 检查(3-3):ok 与 message 原分别取自解析结果和文件存在性,
文件存在但解析失败时会“ok=false 却提示可解析”。改为从同一次解析结果派生两者。
- localize_error_message(3-1a):空壳直通占位函数,删除并在 4 处调用点直接用消息。
- daemon 路径 helper 形参(3-1b):output_root 实际语义是 state_dir,统一改名消除误导。
- stop_daemon 非 RPC 路径(3-1c):删除无意义的 let-else(_pid 读后丢弃、两分支
逐字重复 stop_daemon_inner+print_report),stop_daemon_inner 内已做 PID 校验。
- parse_http_status(3-1d):改为锚定 curl stderr 中 "error" 之后再取三位状态码,
避免误取端口/字节数等无关三位数字;新增锚定测试。
- 非 Unix process_exists(3-1e):补注释说明保守返回 true 的理由(无法探测存活,
避免误回收可能仍在运行的 daemon)。
对应 issue #18 维护清单 3-1、3-3。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 09:35:31 -07:00
nyaKazuha and Claude Fable 5
b570f37359
fix(official-sync): 区分代理故障并前置校验代理配置
...
- 代理故障分类:curl 因代理 407 认证失败(CONNECT 隧道失败)、无法解析/连接
代理等,原被归为普通可重试网络错误,永不成功也会耗尽 per-URL 重试且无诊断。
新增 CurlFailureKind::Proxy,从 curl stderr 识别代理故障,per-URL 层 fail-fast
不重试(daemon error-retry 循环仍整体重试以覆盖代理短暂抖动),并在失败摘要里
以 kind=proxy 明确标注。
- 前置校验:parse_proxy_config 新增 validate_proxy_url,拒绝拼错的代理 scheme
(如 htp://),只接受 http/https/socks4/socks4a/socks5/socks5h 或无 scheme
的 host:port。
- doctor:proxy 检查不再恒 ok,对解析出的代理 URL(含 auto 从环境变量取得的)
做 scheme 校验,前置暴露配置错误。
新增单测:代理 407/解析失败归为 Proxy 且不重试、源站连接失败仍为 Connect 可重试、
拒绝不支持的 scheme、无 scheme host:port 接受。
对应 issue #18 维护清单 2-5。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 08:18:37 -07:00
nyaKazuha and Claude Fable 5
3cb3637749
fix(official-sync): 清理未被引用的孤儿 staging 目录
...
同版本失败 staging 复用已由 060b7d7 处理,但跨版本失败、或被 trim/去重挤出
failed_versions 记录后残留的 .staging/<id> 目录永不被复用也无人清理,长期 daemon
场景下多 GB 孤儿目录会累积耗尽磁盘。
新增 gc_orphan_staging:枚举 .staging 下子目录,只保留 in_progress_version 与
failed_versions 引用的 <id>,删除其余(跳过普通文件与 symlink,staging 根不存在
时无操作)。在同步成功发布后(读取最新版本状态)和 clean-stable(后台已停止)两处
调用;GC 失败仅告警,不影响发布结果。
对应 issue #18 维护清单 1-4。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 07:29:37 -07:00
nyaKazuha and Claude Fable 5
55b9babb66
fix(official-sync): 代理凭据改由环境变量下传子进程
...
彻底收敛 1-2 的两处残留凭据暴露:
- 子进程 argv:daemon_child_args 不再把 --proxy <url> 写入子进程参数,改为
只放不含凭据的内部 flag --proxy-from-env,真实 URL 经 BAT_OFFICIAL_SYNC_PROXY_URL
环境变量下传。凭据从世界可读的 /proc/<pid>/cmdline(0444)转移到仅属主可读的
/proc/<pid>/environ(0400);子进程解析后立即 env::remove_var,避免被 curl 等
孙进程继承。
- 状态文件 at-rest:0600 状态文件的 command 字段不再含凭据。restart/reload 复用
所需的 URL 改存专用 0600 文件 bat-proxy.secret,该文件永不进入状态输出或 --json;
clean-stable 会在后台停止后清除它。
复用型 restart/reload 从 bat-proxy.secret 还原代理;凭据文件缺失时(如 clean-stable
后)明确报错要求重新传入 --proxy,而非静默丢弃代理。
验证:新增 curl_proxy_url、secret 文件 0600 往返、--proxy-from-env 读取并清除
环境变量的单测;真机起停 daemon 确认凭据不在 cmdline/状态文件/status --json,
仅在 0600 secret 文件与 owner-only environ;复用 restart 成功还原、clean-stable
后复用 restart 明确报错。
对应 issue #18 维护清单 1-2 残留项。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 05:20:22 -07:00
nyaKazuha and Claude Fable 5
810b39c2cd
fix(official-sync): 修复阻塞 clippy 门禁的两处告警
...
cargo clippy --workspace -- -D warnings 在 HEAD 上失败,阻塞项目验证门禁:
- resolve_bootstrap 因 c123f6c 新增 curl_proxy 参数达到 8 个,触发
too_many_arguments。将 curl_command/curl_proxy/unzip_command 收拢为
BootstrapTools 结构体,降到 6 个参数。
- visible_historical_failed_versions(169822a 引入)的显式生命周期可省略,
触发 needless_lifetimes。按 clippy 建议省略。
对应 issue #18 维护清单 1-7。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 04:40:35 -07:00
nyaKazuha and Claude Fable 5
eef8c52497
fix(official-sync): 脱敏代理凭据避免泄漏到日志与状态输出
...
两处代理凭据泄漏:
- redact_proxy_url 对无 scheme 的代理串(user:secret@host:port,curl 默认按
http 处理)完全不脱敏,明文凭据会进入 progress 事件、daemon 日志和
bat-events.jsonl。现对无 scheme 但含 userinfo 的情况一并脱敏。
- daemon 保存的 command 含原始代理 URL,经 RPC status 和 bat status --json
回显到 stdout(会被监控采集)。新增 redact_command_proxy_credentials,在
build_daemon_status_report 的对外拷贝里脱敏 --proxy 值;磁盘状态文件
(0600)仍保留原始 command 供 restart/reload 复用。
残留:子进程 argv 仍可经 /proc/<pid>/cmdline 读到代理凭据,以及 0600 状态
文件内的 at-rest 凭据,二者受进程属主与文件权限约束,后续可改由环境变量下
传子进程进一步收敛。
对应 issue #18 维护清单 1-2。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-16 04:31:10 -07:00
nyaKazuha
c123f6c0dc
feat(official-sync): 支持本地代理下载官方资源
2026-07-16 00:12:27 +08:00
nyaKazuha
169822abaf
fix(daemon): 区分当前下载与历史失败状态
2026-07-15 23:22:30 +08:00
nyaKazuha
5b31112e91
fix(daemon): 使用人类格式输出 RPC 下载进度
2026-07-15 23:05:28 +08:00
nyaKazuha
7890b667d7
fix(daemon): 去重失败版本并优化状态输出
2026-07-15 22:32:27 +08:00
nyaKazuha
90307a3243
feat: track official versions and import resources
...
Persist official version-state, extend CAS/ResourceRepository import classification, and add offline catalog and failure regression fixtures.
Fixes #13
Fixes #12
Fixes #8
2026-07-15 19:53:10 +08:00
nyaKazuha
4192d7ee4c
fix: classify official download failures
...
Distinguish terminal HTTP errors from retryable CDN/network failures, record quarantined resources, and report failed download progress.
Fixes #7
2026-07-14 01:30:12 +08:00
nyaKazuha
2ff99ae9e3
feat: publish official resources atomically
...
Fixes #9
Fixes #11
2026-07-14 00:10:39 +08:00
nyaKazuha
45aba2fb2b
fix: harden official sync filesystem boundaries
...
Fixes #14
2026-07-13 23:17:52 +08:00
nyaKazuha
599ead8391
fix: serialize daemon control commands
...
Fixes #10
2026-07-13 22:02:11 +08:00
nyaKazuha
1bcb9c828a
feat: summarize official validation results
...
Fixes #5
2026-07-13 01:06:37 +08:00
nyaKazuha
e5fcf26e19
feat: validate official zip structure
2026-07-12 23:37:47 +08:00
nyaKazuha
f7255f7c04
feat: improve bat daemon cli output
2026-07-12 23:04:13 +08:00
nyaKazuha
264e78c440
feat: harden official sync scheduling and integrity
2026-07-11 01:06:02 +08:00
nyaKazuha
f704ec18c5
feat: add official sync startup banner
2026-07-07 23:00:02 +08:00
nyaKazuha
ea0920e4e3
feat: add official sync progress logging
2026-07-07 22:13:07 +08:00
nyaKazuha
b2d6871926
fix: harden official resource sync retries
2026-07-07 01:14:54 +08:00
nyaKazuha
789402c887
feat: add official resource sync pipeline
...
Add the production-facing official update service and bat-official-sync watch CLI for unattended resource synchronization.
Support launcher-resource discovery without installing the launcher, remote marker snapshots, local manifest audit and repair, official seed hash validation, bootstrap caching, richer Addressables coverage, SQLite resource persistence, and FFI JSON helpers.
2026-07-05 23:49:56 +08:00