mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 06:34:54 +08:00
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
# BlueArchiveToolkit 当前工作区状态
|
# BlueArchiveToolkit 当前工作区状态
|
||||||
|
|
||||||
- **更新时间**:2026-08-31
|
- **更新时间**:2026-09-02
|
||||||
- **状态来源**:本地工作区盘点、代码验证和最新提交
|
- **状态来源**:本地工作区盘点、代码验证和最新提交
|
||||||
- **状态分支**:`experiment`
|
- **状态分支**:`experiment`
|
||||||
- **最新已推送功能提交**:以当前 `git log --oneline -1` 为准
|
- **最新已推送功能提交**:以当前 `git log --oneline -1` 为准
|
||||||
@@ -20,15 +20,15 @@ Rust 侧官方日服资源链路已经从实验验证推进到正式入口:
|
|||||||
3. 默认平台为 `Windows + Android`。
|
3. 默认平台为 `Windows + Android`。
|
||||||
4. 能生成官方全量 pull plan,执行真实下载,维护 release 内的 `official-download-manifest.json`。
|
4. 能生成官方全量 pull plan,执行真实下载,维护 release 内的 `official-download-manifest.json`。
|
||||||
5. 下载后使用本地 manifest 的 size + BLAKE3 校验复用文件;所有 `.zip` 在下载验收、复用、本地 audit/verify 时做 ZIP 结构校验;官方 seed `.hash` 使用标准 `xxHash32(seed=0)` 强校验(早期实现的非标准 avalanche 常量已修正)。
|
5. 下载后使用本地 manifest 的 size + BLAKE3 校验复用文件;所有 `.zip` 在下载验收、复用、本地 audit/verify 时做 ZIP 结构校验;官方 seed `.hash` 使用标准 `xxHash32(seed=0)` 强校验(早期实现的非标准 avalanche 常量已修正)。
|
||||||
6. 支持 `.part` 断点续传、失败后 clean retry、本地 manifest audit/repair、失败 staging 恢复复用、403/404/5xx 分类重试(重试带指数退避)、下载 quarantine 诊断,以及旧 launcher 包官方 primary/backup CDN 切换。启动器/server-info 先行更新但 client-patch seed marker 或必需 seed catalog 尚未开放时,会进入 `waiting_for_official_resources`,保留现有 `current`,不创建失败 staging,也不写入失败版本循环;启用 `--auto-discover` 的非 dry-run 会写入 `<output>/official-launcher-bootstrap.pending.json` 作为维护期证据。下载默认使用 8 个独立 worker,范围为 `1..=256`;每个 worker 完成当前 URL 后立即从共享计划队列领取下一个任务,进度按实际完成顺序即时上报,最终 report 资源列表仍按计划顺序输出。manifest/quarantine 簿记与 seed `.hash` 校验仍逐项执行,`fail-fast` 与「不发布不完整资源」不变量不变。下载进度按已完成数量单调上报,不再使用 plan 序号计算百分比。
|
6. 支持 `.part` 断点续传、失败后 clean retry、本地 manifest audit/repair、失败 staging 恢复复用、403/404/5xx 分类重试(重试带指数退避)、下载 quarantine 诊断,以及旧 launcher 包官方 primary/backup CDN 切换。启动器/server-info 先行更新但 client-patch seed marker 或必需 seed catalog 尚未开放时,会进入 `waiting_for_official_resources`,保留现有 `current`,不创建失败 staging,也不写入失败版本循环;启用 `--auto-discover` 的非 dry-run 会写入 `<output>/official-launcher-bootstrap.pending.json` 作为维护期证据。下载默认使用 8 个独立 worker,范围为 `1..=256`;每个 worker 完成当前 URL 后立即从共享计划队列领取下一个任务,进度按实际完成顺序即时上报,最终 report 资源列表仍按计划顺序输出。manifest/quarantine 簿记与 seed `.hash` 校验仍逐项执行,`fail-fast` 与「不发布不完整资源」不变量不变。下载进度按已完成数量单调上报,不再使用 plan 序号计算百分比。新 staging 还会按规范化 destination 查找已发布历史 release,重新校验 size、BLAKE3 和 ZIP 结构后用硬链接或跨文件系统复制复用;历史文件不满足条件时再验证配置的 CAS 对象,最后才回退网络,并把 `release_reused`、`cas_reused`、`downloaded` 和复用诊断写入报告。
|
||||||
7. 支持 curl 传输层本地代理:默认自动检测 `HTTPS_PROXY` / `ALL_PROXY` / `HTTP_PROXY` 及小写环境变量(带凭据的代理推荐用环境变量配置),也可用 `--proxy <URL>` 显式指定或 `--no-proxy` 强制直连;代理决策会写入 progress log、daemon log 和 `bat doctor` 诊断输出。代理凭据不落世界可读位置:日志/`status` 脱敏,传给 curl 经 `ALL_PROXY` 环境变量而非 argv,`--daemon` 下经环境变量下传后台子进程、不进子进程 argv 或 `bat-status.json`,复用凭据存于 `bat-proxy.secret`(`0600`)且 `clean-stable` 会清除。
|
7. 支持 curl 传输层本地代理:默认自动检测 `HTTPS_PROXY` / `ALL_PROXY` / `HTTP_PROXY` 及小写环境变量(带凭据的代理推荐用环境变量配置),也可用 `--proxy <URL>` 显式指定或 `--no-proxy` 强制直连;代理决策会写入 progress log、daemon log 和 `bat doctor` 诊断输出。代理凭据不落世界可读位置:日志/`status` 脱敏,传给 curl 经 `ALL_PROXY` 环境变量而非 argv,`--daemon` 下经环境变量下传后台子进程、不进子进程 argv 或 `bat-status.json`,复用凭据存于 `bat-proxy.secret`(`0600`)且 `clean-stable` 会清除。
|
||||||
8. `bat --watch` 可常驻运行,`bat --daemon` 可后台运行并用 `bat status` / `bat stop` / `bat restart` / `bat reload` / `bat logs` 管理;daemon 使用 `bat.sock` Unix socket JSON-RPC 作为 live 控制通道,PID/状态/日志文件作为快照和 fallback,`bat-events.jsonl` 记录带轮转的结构化事件日志,`bat-control.lock` 串行化控制命令;正常检查默认每 1 小时一次;远端和本地一致时默认静默,失败后默认 60 秒快速重试,官方资源端尚未开放时状态为 `waiting` 并同样按错误重试间隔探测;`resource.state` / `catalog.status` / `parse.status` / `localized.status` 会返回 `status` 与稳定 `status_code`(如 `official.up_to_date`、`official.published`、`parse.completed`、`translation.queued_offline`、`localized.published`、`distribution.ready`),供 `bat-api` 等读侧判断阶段、终态和重试属性;CLI 默认向 stdout 输出人类可读摘要,向 stderr 输出 ASCII banner、progress log、失败分类和 quarantine 状态,需要机器输出时使用 `--json --no-progress`。
|
8. `bat --watch` 可常驻运行,`bat --daemon` 可后台运行并用 `bat status` / `bat stop` / `bat restart` / `bat reload` / `bat logs` 管理;daemon 使用 `bat.sock` Unix socket JSON-RPC 作为 live 控制通道,PID/状态/日志文件作为快照和 fallback,`bat-events.jsonl` 记录带轮转的结构化事件日志,`bat-control.lock` 串行化控制命令;正常检查默认每 1 小时一次;远端和本地一致时默认静默,失败后默认 60 秒快速重试,官方资源端尚未开放时状态为 `waiting` 并同样按错误重试间隔探测;`resource.state` / `catalog.status` / `parse.status` / `localized.status` 会返回 `status` 与稳定 `status_code`(如 `official.up_to_date`、`official.published`、`parse.completed`、`translation.queued_offline`、`localized.published`、`distribution.ready`),供 `bat-api` 等读侧判断阶段、终态和重试属性;CLI 默认向 stdout 输出人类可读摘要,向 stderr 输出 ASCII banner、progress log、失败分类和 quarantine 状态,需要机器输出时使用 `--json --no-progress`。
|
||||||
9. 远端 snapshot 未变化但输出目录为空时,会按首次运行执行全量拉取;官方 seed `.hash` 校验失败时会清理对应 manifest 条目,避免失败产物被后续本地 audit 误判为可复用。
|
9. 远端 snapshot 未变化但输出目录为空时,会按首次运行执行全量拉取;官方 seed `.hash` 校验失败时会清理对应 manifest 条目,避免失败产物被后续本地 audit 误判为可复用。
|
||||||
10. 默认官方原版资源目录是 `./bat-resources`,默认汉化产物目录是 `./bat-localized`,默认后台状态目录是 `/tmp/bat-pid`;官方资源目录是发布根目录,包含 `current` symlink、`versions/<id>` 和 `.staging/<id>`,非 dry-run 会先写 staging,校验完成后发布 versioned 目录并原子切换 `current`;启用 `--auto-discover` 的 release 会包含 `official-launcher-bootstrap.json`,up-to-date 轮询会为旧 release 补写该产物;如果上一轮同一 app version、bundle version 和 Addressables root 的 staging 失败但目录仍安全存在,下一轮会复用该 staging 并按 manifest 逐文件校验/补下载;后台状态目录包含 `bat.sock`、`bat.pid`、`bat-status.json`、`bat-daemon.log`、`bat-events.jsonl`、任务历史 `bat-tasks.json` 和短生命周期 `bat-control.lock`;非 dry-run 使用 `--output/.official-sync.lock` 防止并发写同一资源目录,live daemon 会阻止前台写命令直接修改它正在管理的同一目录。
|
10. 默认官方原版资源目录是 `./bat-resources`,默认汉化产物目录是 `./bat-localized`,默认后台状态目录是 `/tmp/bat-pid`;官方资源目录是发布根目录,包含 `current` symlink、`versions/<id>` 和 `.staging/<id>`,非 dry-run 会先写 staging,校验完成后发布 versioned 目录并原子切换 `current`;启用 `--auto-discover` 的 release 会包含 `official-launcher-bootstrap.json`,up-to-date 轮询会为旧 release 补写该产物;如果上一轮同一 app version、bundle version 和 Addressables root 的 staging 失败但目录仍安全存在,下一轮会复用该 staging 并按 manifest 逐文件校验/补下载;从 CAS 复用的 release 会在自身目录保存版本化 `official-cas-reuse-references.json`,孤儿 staging 清理或 release 清理时按清单递减 CAS 引用,避免 CAS GC 误删仍被 release 使用的对象;后台状态目录包含 `bat.sock`、`bat.pid`、`bat-status.json`、`bat-daemon.log`、`bat-events.jsonl`、任务历史 `bat-tasks.json` 和短生命周期 `bat-control.lock`;非 dry-run 使用 `--output/.official-sync.lock` 防止并发写同一资源目录,live daemon 会阻止前台写命令直接修改它正在管理的同一目录。
|
||||||
11. 官方同步会拒绝危险输出目录、路径逃逸和现有 symlink 路径组件;下载目标、`.part`、manifest、snapshot、PID、status、log 和控制锁文件不会跟随 symlink,daemon 状态类文件默认以 `0600` 权限创建。
|
11. 官方同步会拒绝危险输出目录、路径逃逸和现有 symlink 路径组件;下载目标、`.part`、manifest、snapshot、PID、status、log 和控制锁文件不会跟随 symlink,daemon 状态类文件默认以 `0600` 权限创建。
|
||||||
12. `<output>/official-version-state.json` 会明确保存当前已完成版本、正在拉取版本、上一个可用版本和失败版本;同一 app version、bundle version 和 Addressables root 的失败只保留最新一条,同一版本开始重新拉取或后续发布成功时会清理对应失败记录;`bat status` 会显示最后成功时间、下次检查时间、最后错误摘要、当前阶段、当前下载 URL 进度、版本状态摘要、最近历史失败版本和原因、结构化日志路径和轮转日志路径,人类可读输出不会把完整版本状态 JSON 内联打印。
|
12. `<output>/official-version-state.json` 会明确保存当前已完成版本、正在拉取版本、上一个可用版本和失败版本;同一 app version、bundle version 和 Addressables root 的失败只保留最新一条,同一版本开始重新拉取或后续发布成功时会清理对应失败记录;`bat status` 会显示最后成功时间、下次检查时间、最后错误摘要、当前阶段、当前下载 URL 进度、版本状态摘要、最近历史失败版本和原因、结构化日志路径和轮转日志路径,人类可读输出不会把完整版本状态 JSON 内联打印。
|
||||||
13. 资源导入链路已支持 CAS + `ResourceRepository` 索引写入,官方同步可用 `--import-repository` / `BAT_IMPORT_REPOSITORY=1` 在已校验 release 发布后触发导入,默认 CAS 路径为 `<output>/.cas`、SQLite 索引为 `<output>/resources.sqlite`,也可通过 `--import-cas-root`、`--import-resource-db`、`BAT_IMPORT_CAS_ROOT`、`BAT_IMPORT_RESOURCE_DB` 覆盖;`resource.index` RPC/CLI 可按类型、hash、路径模式、官方 release ID、平台、destination、bundle path、archive entry、parse status 和 TextUnit format 分页查询现有索引,release、平台、bundle path 和常用数组 metadata 过滤已下推到 SQLite,数据库不存在时返回 `available=false` 且不会创建空库;`bat doctor cas` 可只读检查既有 CAS 根目录、对象目录、元数据库文件和对象统计,不会因诊断创建空库。`Resource` metadata 已通过 `metadata_json` 兼容迁移保存 release、平台、bundle path、parse status、TextAsset 名称和 TextUnit 数量/格式;当前/上一个/结构变化 catalog、失败 staging 复用、403/404、hash mismatch、CRC、metadata 迁移与 UnityFS 边界校验均有离线回归 fixture 或单测覆盖。
|
13. 资源导入链路已支持 CAS + `ResourceRepository` 索引写入,官方同步可用 `--import-repository` / `BAT_IMPORT_REPOSITORY=1` 在已校验 release 发布后触发导入,默认 CAS 路径为 `<output>/.cas`、SQLite 索引为 `<output>/resources.sqlite`,也可通过 `--import-cas-root`、`--import-resource-db`、`BAT_IMPORT_CAS_ROOT`、`BAT_IMPORT_RESOURCE_DB` 覆盖;`resource.index` RPC/CLI 可按类型、hash、路径模式、官方 release ID、平台、destination、bundle path、archive entry、parse status 和 TextUnit format 分页查询现有索引,release、平台、bundle path 和常用数组 metadata 过滤已下推到 SQLite,数据库不存在时返回 `available=false` 且不会创建空库;`bat doctor cas` 可只读检查既有 CAS 根目录、对象目录、元数据库文件和对象统计,不会因诊断创建空库。`Resource` metadata 已通过 `metadata_json` 兼容迁移保存 release、平台、bundle path、parse status、TextAsset 名称和 TextUnit 数量/格式;当前/上一个/结构变化 catalog、失败 staging 复用、403/404、hash mismatch、CRC、metadata 迁移与 UnityFS 边界校验均有离线回归 fixture 或单测覆盖。
|
||||||
14. 非 dry-run 官方同步在校验完成并发布后,会先对比上一完整 release 与当前 release 的 `official-download-manifest.json`,在当前 release 下写入 `official-resource-changes.json` 和 `crowdin-translation-handoff.json`;同一 destination 只有 size 或 BLAKE3 改变才算 modified,仅 URL/CDN 根变化但内容相同不会触发解析/翻译候选。随后刷新 `official-parse-cache.json` 和 `official-textunit-index.json`,并从 Added/Modified 资源、parse cache 与 TextUnit 明细索引派生 `official-textunit-tasks.json` 和 `crowdin-textunit-queue.json`;删除资源只进入差异记录,不进入 TextUnit/Crowdin 队列。`parse.text_units` 和 `parse.errors` RPC/CLI 可按 destination、archive entry、path id、class id、field path 和 format 查询当前 release 的 TextUnit 明细与解析错误;`translation.tasks` RPC/CLI 可按 release、destination、archive entry、任务状态、parse status、TextUnit format 和 reason presence 查询离线 TextUnit 翻译任务状态与跳过/失败原因;`translation.task.update` 可回写 provider worker 状态,`translation.worker.run` 可触发 Rust provider worker 独立 claim/lease/retry 并落库 TextUnit 级译文结果,`translation.proofread` 可把汉化 workflow 标记为人工校对中;TextUnit 已包含 class id、field path、字段 offset/byte size 等可追溯定位。Crowdin provider 通过 `CROWDIN_*` 环境变量接入,mock provider 支持本地 fixture;up-to-date 轮询在已有有效解析缓存、TextUnit 明细索引和 TextUnit 队列时只读取摘要,不重复解析。官方同步报告默认 `localized_release_status=not_localized`,含义是原版资源已经发布、汉化资源未发布;受支持的 UnityFS TextAsset、TypeTree string field 和 managed-reference string field 发布成功后会写带 trace 的 `localized-patch-manifest.json`,校验 hash/size/diff/rollback 后才允许 `localized.status` 返回 `status=published`、`status_code=localized.published` 和 `localized_release_status=localized`,并可用 `localized.rollback` 显式恢复上一 release。`bat` 首次启动会在二进制所在目录释放 `.env` 配置模板(`0600`),之后每次启动自动加载(不覆盖已存在的环境变量),支持 `BAT_OUTPUT`/`BAT_LOCALIZED_OUTPUT`/`BAT_IMPORT_REPOSITORY`/`BAT_IMPORT_CAS_ROOT`/`BAT_IMPORT_RESOURCE_DB`/`BAT_STATE_DIR`/`BAT_AUTO_DISCOVER`/`BAT_WATCH`/`BAT_DAEMON`/`BAT_PROXY` 和 `BAT_TRANSLATION_*` 等键,实现编辑 `.env` 后无参启动;优先级为命令行参数 > 进程环境变量 > `.env` > 内置默认值,`BAT_SKIP_ENV_FILE=1` 可整体禁用;Redis 键为预留。daemon 任务历史持久化在 `<state-dir>/bat-tasks.json`(版本化、`0600` 原子写),重启后任务经 `task.*` 仍可查,中断任务标记 `task_interrupted`(`BAT-ERR-700005`)。
|
14. 非 dry-run 官方同步在校验完成并发布后,会先对比上一完整 release 与当前 release 的 `official-download-manifest.json`,在当前 release 下写入 `official-resource-changes.json` 和 `crowdin-translation-handoff.json`;同一 destination 只有 size 或 BLAKE3 改变才算 modified,仅 URL/CDN 根变化但内容相同不会触发解析/翻译候选。随后刷新 `official-parse-cache.json` 和 `official-textunit-index.json`,并从 Added/Modified 资源、parse cache 与 TextUnit 明细索引派生 `official-textunit-tasks.json` 和 `crowdin-textunit-queue.json`;删除资源只进入差异记录,不进入 TextUnit/Crowdin 队列。`parse.text_units` 和 `parse.errors` RPC/CLI 可按 destination、archive entry、path id、class id、field path 和 format 查询当前 release 的 TextUnit 明细与解析错误;`translation.tasks` RPC/CLI 可按 release、destination、archive entry、任务状态、parse status、TextUnit format 和 reason presence 查询离线 TextUnit 翻译任务状态与跳过/失败原因;`translation.task.update` 可回写 provider worker 状态,`translation.worker.run` 可触发 Rust provider worker 独立 claim/lease/retry 并落库 TextUnit 级译文结果,`translation.proofread` 可把汉化 workflow 标记为人工校对中;TextUnit 已包含 class id、field path、字段 offset/byte size 等可追溯定位。Crowdin provider 通过 `CROWDIN_*` 环境变量接入,mock provider 支持本地 fixture;up-to-date 轮询在已有有效解析缓存、TextUnit 明细索引和 TextUnit 队列时只读取摘要,不重复解析。官方同步报告默认 `localized_release_status=not_localized`,含义是原版资源已经发布、汉化资源未发布;受支持的 UnityFS TextAsset、TypeTree string field 和 managed-reference string field 发布成功后会写带 trace 的 `localized-patch-manifest.json`,校验 hash/size/diff/rollback 后才允许 `localized.status` 返回 `status=published`、`status_code=localized.published` 和 `localized_release_status=localized`,并可用 `localized.rollback` 显式恢复上一 release。`bat` 首次启动会在二进制所在目录释放 `.env` 配置模板(`0600`),之后每次启动自动加载(不覆盖已存在的环境变量),支持 `BAT_OUTPUT`/`BAT_LOCALIZED_OUTPUT`/`BAT_IMPORT_REPOSITORY`/`BAT_IMPORT_CAS_ROOT`/`BAT_IMPORT_RESOURCE_DB`/`BAT_STATE_DIR`/`BAT_AUTO_DISCOVER`/`BAT_WATCH`/`BAT_DAEMON`/`BAT_PROXY` 和 `BAT_TRANSLATION_*` 等键,实现编辑 `.env` 后无参启动;优先级为命令行参数 > 进程环境变量 > `.env` > 内置默认值,`BAT_SKIP_ENV_FILE=1` 可整体禁用;Redis 键为预留。daemon 任务历史持久化在 `<state-dir>/bat-tasks.json`(版本化、`0600` 原子写),重启后任务经 `task.*` 仍可查,中断任务标记 `task_interrupted`(`BAT-ERR-700005`)。官方同步报告还分别统计当前 manifest 复用、历史 release 复用、CAS 复用、网络传输字节和复用回退诊断,下载事件状态使用 `release_reused`、`cas_reused`、`downloaded` 等稳定值。
|
||||||
|
|
||||||
15. issue 43 已补齐 Rust `bat` 的 `res` / `parse` / `i18n` 工作流入口:支持资源拉取、解析刷新、可再生解析缓存清理、离线翻译工作台、人工文本查看/修改/清空、工作台发布前校验、有限 TextAsset 汉化发布、人工校对状态标记、既有 patch 能力的批量重打包、单次/限定次数/周期执行和版本化 schedule CRUD。issue 44 已接入 `translation.worker.run` provider worker:默认并发 8、范围 `1..=256`,每个 worker 独立 claim 下一项任务并落库 lease、失败分类、重试计划和 TextUnit 译文结果。schedule 查询现在按一级工作流过滤,删除/执行会校验作用域,单轮执行可限制计划数;schedule CRUD、翻译任务查询/交接视图、翻译任务状态回写、provider worker 触发和 `translation.proofread` 状态标记已通过 `bat.sock` 的 RPC 以及 `bat-api` 的鉴权管理接口暴露,dashboard 不维护第二套状态。issue 46 已提供 `bat-api` 内嵌 dashboard MVP,静态资产由 Go embed 暴露在 `/admin/dashboard/`,页面直接调用已有鉴权接口控制资源、调度、翻译、任务、日志、parse TextUnit 查询和 localized 发布/回滚。该例外只编排已有解析和 patch 能力,不扩大解析器覆盖;完整 AssetBundle 重打包和完整 Web 协作后台仍是后续工作。G-008(产品级 Go 同步 CLI)已决策关闭。真实官方网络全量拉取 smoke 已固化(G-018 已关闭);真实大文件与运行报告默认在 `/tmp` 隔离目录,不纳入 Git。Go 细节见 `docs/reports/GO_STATUS.md`。
|
15. issue 43 已补齐 Rust `bat` 的 `res` / `parse` / `i18n` 工作流入口:支持资源拉取、解析刷新、可再生解析缓存清理、离线翻译工作台、人工文本查看/修改/清空、工作台发布前校验、有限 TextAsset 汉化发布、人工校对状态标记、既有 patch 能力的批量重打包、单次/限定次数/周期执行和版本化 schedule CRUD。issue 44 已接入 `translation.worker.run` provider worker:默认并发 8、范围 `1..=256`,每个 worker 独立 claim 下一项任务并落库 lease、失败分类、重试计划和 TextUnit 译文结果。schedule 查询现在按一级工作流过滤,删除/执行会校验作用域,单轮执行可限制计划数;schedule CRUD、翻译任务查询/交接视图、翻译任务状态回写、provider worker 触发和 `translation.proofread` 状态标记已通过 `bat.sock` 的 RPC 以及 `bat-api` 的鉴权管理接口暴露,dashboard 不维护第二套状态。issue 46 已提供 `bat-api` 内嵌 dashboard MVP,静态资产由 Go embed 暴露在 `/admin/dashboard/`,页面直接调用已有鉴权接口控制资源、调度、翻译、任务、日志、parse TextUnit 查询和 localized 发布/回滚。该例外只编排已有解析和 patch 能力,不扩大解析器覆盖;完整 AssetBundle 重打包和完整 Web 协作后台仍是后续工作。G-008(产品级 Go 同步 CLI)已决策关闭。真实官方网络全量拉取 smoke 已固化(G-018 已关闭);真实大文件与运行报告默认在 `/tmp` 隔离目录,不纳入 Git。Go 细节见 `docs/reports/GO_STATUS.md`。
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -175,7 +175,7 @@ BlueArchiveToolkit 不是一次性脚本,也不是演示项目。最终交付
|
|||||||
|
|
||||||
1. Addressables Catalog 目标字段解析:**当前目标完成**。JSON/compact 已覆盖 path、hash、size、address、dependencies、provider ID、bundle name、CRC、metadata,并通过 fixture/golden 与 SQLite 迁移回归;独立二进制 catalog 仍明确拒绝。
|
1. Addressables Catalog 目标字段解析:**当前目标完成**。JSON/compact 已覆盖 path、hash、size、address、dependencies、provider ID、bundle name、CRC、metadata,并通过 fixture/golden 与 SQLite 迁移回归;独立二进制 catalog 仍明确拒绝。
|
||||||
2. 资源版本、区域、渠道、远端 URL、Hash、大小、依赖关系模型:**部分完成**。`Resource` 和官方 endpoint/snapshot 模型已扩展;仍需冻结 Go CLI/API 可见模型。
|
2. 资源版本、区域、渠道、远端 URL、Hash、大小、依赖关系模型:**部分完成**。`Resource` 和官方 endpoint/snapshot 模型已扩展;仍需冻结 Go CLI/API 可见模型。
|
||||||
3. Rust 官方下载器:**已完成当前生产入口需要的核心能力**。包含官方 URL 校验、`.part` 续传、重试、本地 manifest size+BLAKE3 校验、官方 seed `.hash` 校验、repair,以及默认 8、范围 `1..=256` 的有界并发 scheduler;worker 动态领取任务,进度按完成数单调上报,report 保持 plan 顺序。
|
3. Rust 官方下载器:**已完成当前生产入口需要的核心能力**。包含官方 URL 校验、`.part` 续传、重试、本地 manifest size+BLAKE3 校验、官方 seed `.hash` 校验、repair、已发布历史 release/CAS 复用,以及默认 8、范围 `1..=256` 的有界并发 scheduler;worker 动态领取任务,进度按完成数单调上报,report 保持 plan 顺序,复用和网络传输分别统计。
|
||||||
4. Rust 自动更新入口:**已完成当前生产入口**。`bat` 支持 snapshot、marker diff、bootstrap cache、one-shot、`--watch`、`--daemon`、默认 1 小时间隔、北京时间固定强制刷新,以及 Unix socket JSON-RPC 后台运维命令返回。
|
4. Rust 自动更新入口:**已完成当前生产入口**。`bat` 支持 snapshot、marker diff、bootstrap cache、one-shot、`--watch`、`--daemon`、默认 1 小时间隔、北京时间固定强制刷新,以及 Unix socket JSON-RPC 后台运维命令返回。
|
||||||
5. Go 入口边界:**已冻结**。同步命令行 = Rust `bat`(G-008 关闭);资源分发 = `bat-api` MVP(G-009 部分完成)。详见 `docs/reports/GO_STATUS.md`。
|
5. Go 入口边界:**已冻结**。同步命令行 = Rust `bat`(G-008 关闭);资源分发 = `bat-api` MVP(G-009 部分完成)。详见 `docs/reports/GO_STATUS.md`。
|
||||||
6. 用户级 `sync`、`manifest inspect`、`cache status`:**未完成**。Rust `bat --json` 是当前稳定进程边界;`bat-ffi` 只提供可选兼容用的 Manifest inspect 和 sync plan JSON helper。
|
6. 用户级 `sync`、`manifest inspect`、`cache status`:**未完成**。Rust `bat --json` 是当前稳定进程边界;`bat-ffi` 只提供可选兼容用的 Manifest inspect 和 sync plan JSON helper。
|
||||||
|
|||||||
@@ -13,12 +13,12 @@
|
|||||||
- `bat-adapters` Unity、Manifest、Client 集成框架,以及当前真实形态 Addressables catalog 解析覆盖,含 `m_Crc` 提取和 UnityFS 解包/TextAsset 提取基础校验。
|
- `bat-adapters` Unity、Manifest、Client 集成框架,以及当前真实形态 Addressables catalog 解析覆盖,含 `m_Crc` 提取和 UnityFS 解包/TextAsset 提取基础校验。
|
||||||
- `bat-cas-engine` CAS V1:原子写入、BLAKE3 校验、引用计数、GC、并发写入测试、损坏检测。
|
- `bat-cas-engine` CAS V1:原子写入、BLAKE3 校验、引用计数、GC、并发写入测试、损坏检测。
|
||||||
- `bat-infrastructure` CAS 适配层、SQLite Resource Repository、资源导入服务、官方资源 pull/update 服务。
|
- `bat-infrastructure` CAS 适配层、SQLite Resource Repository、资源导入服务、官方资源 pull/update 服务。
|
||||||
- `bat`:官方资源自动发现、全量拉取、原子发布到 `current -> versions/<id>`、本地 manifest audit/repair、`.part` 断点续传、403/404/5xx 分类重试、指数退避、默认并发 8(可配置 `1..=256`,report 按 plan 顺序、进度按完成数单调上报)、下载 quarantine 诊断、ZIP 结构校验、官方 seed `.hash` 校验、snapshot/cache、版本化 `official-launcher-bootstrap.json`、`--watch` 常驻更新、`--daemon` 后台运行,以及 Unix socket JSON-RPC live control/backend 方法(`daemon.*`、`resource.*`、`parse.*`、`translation.tasks/handoff/task.update`、`localized.status`、`catalog.*`、`task.*`、`patch.apply`、`unityfs.patch_*`)。
|
- `bat`:官方资源自动发现、全量拉取、原子发布到 `current -> versions/<id>`、本地 manifest audit/repair、`.part` 断点续传、403/404/5xx 分类重试、指数退避、默认并发 8(可配置 `1..=256`,report 按 plan 顺序、进度按完成数单调上报)、已发布历史 release 与 CAS 复用、下载 quarantine 诊断、ZIP 结构校验、官方 seed `.hash` 校验、snapshot/cache、版本化 `official-launcher-bootstrap.json`、`--watch` 常驻更新、`--daemon` 后台运行,以及 Unix socket JSON-RPC live control/backend 方法(`daemon.*`、`resource.*`、`parse.*`、`translation.tasks/handoff/task.update`、`localized.status`、`catalog.*`、`task.*`、`patch.apply`、`unityfs.patch_*`)。
|
||||||
- `internal/backendrpc`:Go 侧 typed Unix socket JSON-RPC client,是 `bat-api` 调用 Rust daemon 的默认路径。
|
- `internal/backendrpc`:Go 侧 typed Unix socket JSON-RPC client,是 `bat-api` 调用 Rust daemon 的默认路径。
|
||||||
- `cmd/bat-api`:资源 bootstrap + 分发 HTTP MVP(issue #19 / G-009);`/v1/bootstrap` 和 `/v1/launcher/bootstrap` 组织 `bat` 已发布 release 的启动前资源入口,launcher 形状兼容端点仅输出资源 metadata / GameMainConfig 引导,`/healthz` 暴露 RPC refresh 诊断,`/readyz` 做 release readiness,CDN path 支持 `GET`/`HEAD`/`Range`、ETag、Last-Modified 和缓存头;玩家-facing 控制面已具备 token 鉴权、限流、访问日志、反代 IP 适配、动态 JSON no-store、OpenAPI、管理控制白名单、task/log/parse/translation admin 查询控制入口和无构建内嵌 dashboard;`.env` 配置端口/RPC socket/刷新周期;生产资源根和长期状态来自 RPC,不负责自动拉取。
|
- `cmd/bat-api`:资源 bootstrap + 分发 HTTP MVP(issue #19 / G-009);`/v1/bootstrap` 和 `/v1/launcher/bootstrap` 组织 `bat` 已发布 release 的启动前资源入口,launcher 形状兼容端点仅输出资源 metadata / GameMainConfig 引导,`/healthz` 暴露 RPC refresh 诊断,`/readyz` 做 release readiness,CDN path 支持 `GET`/`HEAD`/`Range`、ETag、Last-Modified 和缓存头;玩家-facing 控制面已具备 token 鉴权、限流、访问日志、反代 IP 适配、动态 JSON no-store、OpenAPI、管理控制白名单、task/log/parse/translation admin 查询控制入口和无构建内嵌 dashboard;`.env` 配置端口/RPC socket/刷新周期;生产资源根和长期状态来自 RPC,不负责自动拉取。
|
||||||
- Go 边界权威说明:[`docs/reports/GO_STATUS.md`](docs/reports/GO_STATUS.md)(G-008 已关闭:同步 CLI = Rust `bat`)。
|
- Go 边界权威说明:[`docs/reports/GO_STATUS.md`](docs/reports/GO_STATUS.md)(G-008 已关闭:同步 CLI = Rust `bat`)。
|
||||||
- 官方同步会维护 `<output>/official-version-state.json`,明确记录当前已完成版本、正在拉取版本、上一个可用版本和失败版本。
|
- 官方同步会维护 `<output>/official-version-state.json`,明确记录当前已完成版本、正在拉取版本、上一个可用版本和失败版本。
|
||||||
- 资源导入链路可配置为在官方 release 发布后写入 CAS + `ResourceRepository`,资源 metadata 会记录 release、平台、bundle path、parse status、TextAsset 名称和 TextUnit 数量/格式,TextAsset/Table/Media 会按类型分类索引;`resource.index` RPC/CLI 可按类型、hash、路径模式、官方 release ID、平台、destination、bundle path、archive entry、parse status 和 TextUnit format 分页查询索引,常用 metadata 过滤会下推到 SQLite;`bat doctor cas` 可只读诊断既有 CAS 目录、对象数、对象字节数和元数据库文件状态。
|
- 资源导入链路可配置为在官方 release 发布后写入 CAS + `ResourceRepository`,资源 metadata 会记录 release、平台、bundle path、parse status、TextAsset 名称和 TextUnit 数量/格式,TextAsset/Table/Media 会按类型分类索引;`resource.index` RPC/CLI 可按类型、hash、路径模式、官方 release ID、平台、destination、bundle path、archive entry、parse status 和 TextUnit format 分页查询索引,常用 metadata 过滤会下推到 SQLite;历史 release 复用会重新校验 size、BLAKE3 和 ZIP 结构,失败时按历史 release、CAS、网络顺序回退,CAS 引用记录在 `official-cas-reuse-references.json` 中;`bat doctor cas` 可只读诊断既有 CAS 目录、对象数、对象字节数和元数据库文件状态。
|
||||||
- 新 release 发布后会生成 `official-resource-changes.json`、`official-parse-cache.json`、`official-textunit-index.json`、`official-textunit-tasks.json`、`crowdin-translation-handoff.json`、`crowdin-textunit-queue.json`、`translation-tasks.sqlite` 和 `translation-handoff.json`;其中 TextUnit/Crowdin 队列只使用 Added/Modified 资源,不调用 Crowdin 网络 API,离线 TextUnit 翻译任务可通过 `translation.tasks` / `translation.handoff` RPC 或 CLI 查询状态、跳过/失败原因和 provider run 交接。
|
- 新 release 发布后会生成 `official-resource-changes.json`、`official-parse-cache.json`、`official-textunit-index.json`、`official-textunit-tasks.json`、`crowdin-translation-handoff.json`、`crowdin-textunit-queue.json`、`translation-tasks.sqlite` 和 `translation-handoff.json`;其中 TextUnit/Crowdin 队列只使用 Added/Modified 资源,不调用 Crowdin 网络 API,离线 TextUnit 翻译任务可通过 `translation.tasks` / `translation.handoff` RPC 或 CLI 查询状态、跳过/失败原因和 provider run 交接。
|
||||||
- `LocalizedPatchService` 已具备受支持的 UnityFS localized patch 发布/回滚能力:在 `--localized-output` / `BAT_LOCALIZED_OUTPUT` 配置的独立汉化目录 staging 中复制官方 release、应用 TextAsset、TypeTree string field 或 managed-reference string field patch、写入带 TextUnit/provider/review/rollback trace 的 `localized-patch-manifest.json`,校验后发布到 `versions/<id>` 并切换 `current`,也可显式 rollback。
|
- `LocalizedPatchService` 已具备受支持的 UnityFS localized patch 发布/回滚能力:在 `--localized-output` / `BAT_LOCALIZED_OUTPUT` 配置的独立汉化目录 staging 中复制官方 release、应用 TextAsset、TypeTree string field 或 managed-reference string field patch、写入带 TextUnit/provider/review/rollback trace 的 `localized-patch-manifest.json`,校验后发布到 `versions/<id>` 并切换 `current`,也可显式 rollback。
|
||||||
- `bat-patch` 已具备通用 Patch 基础:确定性 Binary hunk diff/apply、RFC 6902 JSON Patch apply、UTF-8 Text Patch、Patch manifest、BLAKE3/size 完整性校验和 rollback 元数据;文件级 `patch.apply` RPC / `patch-apply` CLI 与 UnityFS TextAsset / TypeTree string / TypeTree 语义字段写入入口已开放,TypeTree 语义字段支持基础标量、固定 Unity float/int/hash 值类型的 leaf/direct-child 形态、PPtr、managed-reference registry payload 字符串、object 字段组合、unknown fixed-size raw bytes 同长度替换和 TypeTree schema 支撑的 array/vector/map 整体替换;TextUnit 提取会把 managed-reference 类型信息保留为上下文而非翻译文本,受支持 localized 发布通过独立 manifest/staging/current 流程完成。
|
- `bat-patch` 已具备通用 Patch 基础:确定性 Binary hunk diff/apply、RFC 6902 JSON Patch apply、UTF-8 Text Patch、Patch manifest、BLAKE3/size 完整性校验和 rollback 元数据;文件级 `patch.apply` RPC / `patch-apply` CLI 与 UnityFS TextAsset / TypeTree string / TypeTree 语义字段写入入口已开放,TypeTree 语义字段支持基础标量、固定 Unity float/int/hash 值类型的 leaf/direct-child 形态、PPtr、managed-reference registry payload 字符串、object 字段组合、unknown fixed-size raw bytes 同长度替换和 TypeTree schema 支撑的 array/vector/map 整体替换;TextUnit 提取会把 managed-reference 类型信息保留为上下文而非翻译文本,受支持 localized 发布通过独立 manifest/staging/current 流程完成。
|
||||||
@@ -106,7 +106,7 @@ cargo run -p bat-infrastructure --bin bat -- stop
|
|||||||
|
|
||||||
`bat` 会拒绝危险输出目录、路径逃逸和现有 symlink 路径组件;下载目标、`.part`、manifest、snapshot、PID、status、log 和控制锁文件不会跟随 symlink,daemon 状态类文件默认以 `0600` 权限创建。
|
`bat` 会拒绝危险输出目录、路径逃逸和现有 symlink 路径组件;下载目标、`.part`、manifest、snapshot、PID、status、log 和控制锁文件不会跟随 symlink,daemon 状态类文件默认以 `0600` 权限创建。
|
||||||
|
|
||||||
非 dry-run 同步不会把新文件直接写进生产可读目录。官方原版资源会先下载到 `<output>/.staging/<id>`,完成 manifest、BLAKE3、ZIP 和官方 `.hash` 校验后移动到 `<output>/versions/<id>`,再原子切换 `<output>/current` symlink;生产读取方应只读取 `<output>/current`。同步过程会更新 `<output>/official-version-state.json`:下载开始时写入 `in_progress_version`,发布成功后写入 `current_completed_version` 和 `previous_available_version`,失败或中断时写入 `failed_versions`。启用 `--auto-discover` 时,已发布 release 会写入 `official-launcher-bootstrap.json`,其中包含 launcher metadata、launcher CDN config、remote manifest 文件列表、选中的 `resources.assets` 来源和 `GameMainConfig` 摘要;官方资源端尚未开放时会写 `<output>/official-launcher-bootstrap.pending.json`,但不会切换 `current`。新 release 发布后会对比上一完整 release 的 download manifest,在当前 release 下写入 `official-resource-changes.json`、`crowdin-translation-handoff.json`、`official-parse-cache.json`、`official-textunit-index.json`、`official-textunit-tasks.json` 和 `crowdin-textunit-queue.json`;新增+变更资源作为解析/翻译候选,删除资源只进入差异记录。up-to-date 轮询在已有有效解析缓存、TextUnit 明细索引和 TextUnit 队列时只读取摘要,不重复解析。官方同步报告默认 `localized_release_status=not_localized`,表示原版资源已发布、汉化资源未发布;后续 Patch/导出写入 `--localized-output` / `BAT_LOCALIZED_OUTPUT` 指定的独立目录,保留官方相对目录结构,manifest 校验通过后才切换为 `localized`。
|
非 dry-run 同步不会把新文件直接写进生产可读目录。官方原版资源会先下载到 `<output>/.staging/<id>`,完成 manifest、BLAKE3、ZIP 和官方 `.hash` 校验后移动到 `<output>/versions/<id>`,再原子切换 `<output>/current` symlink;生产读取方应只读取 `<output>/current`。同步过程会更新 `<output>/official-version-state.json`:下载开始时写入 `in_progress_version`,发布成功后写入 `current_completed_version` 和 `previous_available_version`,失败或中断时写入 `failed_versions`。启用 `--auto-discover` 时,已发布 release 会写入 `official-launcher-bootstrap.json`,其中包含 launcher metadata、launcher CDN config、remote manifest 文件列表、选中的 `resources.assets` 来源和 `GameMainConfig` 摘要;官方资源端尚未开放时会写 `<output>/official-launcher-bootstrap.pending.json`,但不会切换 `current`。新 release 发布后会对比上一完整 release 的 download manifest,在当前 release 下写入 `official-resource-changes.json`、`crowdin-translation-handoff.json`、`official-parse-cache.json`、`official-textunit-index.json`、`official-textunit-tasks.json` 和 `crowdin-textunit-queue.json`;新增+变更资源作为解析/翻译候选,删除资源只进入差异记录。复用历史 release 时会先按 destination 找候选并重新校验 size、BLAKE3、ZIP 结构,必要时验证 CAS;候选不可靠就记录诊断并回退网络,不会静默复用。CAS release 引用存放在 `official-cas-reuse-references.json`,孤儿 staging 清理时会递减这些引用。up-to-date 轮询在已有有效解析缓存、TextUnit 明细索引和 TextUnit 队列时只读取摘要,不重复解析。官方同步报告默认 `localized_release_status=not_localized`,表示原版资源已发布、汉化资源未发布;后续 Patch/导出写入 `--localized-output` / `BAT_LOCALIZED_OUTPUT` 指定的独立目录,保留官方相对目录结构,manifest 校验通过后才切换为 `localized`。
|
||||||
|
|
||||||
资源操作命令默认输出人类可读摘要,并在没有显式 metadata 参数时默认走官方自动发现。脚本或上层程序需要稳定结构化输出时加 `--json`:
|
资源操作命令默认输出人类可读摘要,并在没有显式 metadata 参数时默认走官方自动发现。脚本或上层程序需要稳定结构化输出时加 `--json`:
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -12,9 +12,9 @@
|
|||||||
`bat` 是 Linux 上官方日服(Yostar JP)资源同步的正式入口。它可以:
|
`bat` 是 Linux 上官方日服(Yostar JP)资源同步的正式入口。它可以:
|
||||||
|
|
||||||
- `--auto-discover` 从官方 HTTP metadata 解析 `GameMainConfig`,自动获得 app-version、连接组和 server-info,不安装、不启动官方启动器;已发布 release 会保存 `official-launcher-bootstrap.json`。
|
- `--auto-discover` 从官方 HTTP metadata 解析 `GameMainConfig`,自动获得 app-version、连接组和 server-info,不安装、不启动官方启动器;已发布 release 会保存 `official-launcher-bootstrap.json`。
|
||||||
- 生成官方全量 pull plan、执行真实下载,维护 release 内的下载 manifest,并做 size + BLAKE3 复用校验、官方 seed `.hash`(标准 xxHash32(seed=0))强校验、ZIP 结构校验。
|
- 生成官方全量 pull plan、执行真实下载,维护 release 内的下载 manifest,并做 size + BLAKE3 复用校验、已发布历史 release/CAS 复用、官方 seed `.hash`(标准 xxHash32(seed=0))强校验、ZIP 结构校验。
|
||||||
- 断点续传、失败分类重试、下载 quarantine、本地 manifest audit/repair。
|
- 断点续传、失败分类重试、下载 quarantine、本地 manifest audit/repair。
|
||||||
- 原子发布:先写 `.staging/<id>`,校验通过后发布 `versions/<id>` 并原子切换 `current` symlink。
|
- 原子发布:先写 `.staging/<id>`,优先复用已验证历史 release/CAS,校验通过后发布 `versions/<id>` 并原子切换 `current` symlink;CAS 引用记录在 release 内的 `official-cas-reuse-references.json`。
|
||||||
- 常驻运行(`--watch`)或后台化(`--daemon`),通过 `bat.sock` Unix socket JSON-RPC 控制。
|
- 常驻运行(`--watch`)或后台化(`--daemon`),通过 `bat.sock` Unix socket JSON-RPC 控制。
|
||||||
|
|
||||||
### 运行形态
|
### 运行形态
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
use crate::error::{CasError, Result};
|
use crate::error::{CasError, Result};
|
||||||
use crate::hash::Hash;
|
use crate::hash::Hash;
|
||||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteQueryResult};
|
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteQueryResult};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
/// CAS 对象元数据。
|
/// CAS 对象元数据。
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -41,7 +41,9 @@ impl SqliteRefCounter {
|
|||||||
let options =
|
let options =
|
||||||
SqliteConnectOptions::from_str(&format!("sqlite://{}", path.as_ref().display()))
|
SqliteConnectOptions::from_str(&format!("sqlite://{}", path.as_ref().display()))
|
||||||
.map_err(|error| CasError::Database(error.to_string()))?
|
.map_err(|error| CasError::Database(error.to_string()))?
|
||||||
.create_if_missing(true);
|
.create_if_missing(true)
|
||||||
|
.journal_mode(SqliteJournalMode::Wal)
|
||||||
|
.busy_timeout(Duration::from_secs(30));
|
||||||
|
|
||||||
let pool = SqlitePoolOptions::new()
|
let pool = SqlitePoolOptions::new()
|
||||||
.max_connections(1)
|
.max_connections(1)
|
||||||
|
|||||||
@@ -234,12 +234,12 @@ release 根目录写入 `translation-tasks.sqlite`,由版本化 `schema_migrat
|
|||||||
7. 如果远端 snapshot 未变化但输出目录没有任何当前 plan 的本地资源,仍按首次运行处理并执行全量拉取。
|
7. 如果远端 snapshot 未变化但输出目录没有任何当前 plan 的本地资源,仍按首次运行处理并执行全量拉取。
|
||||||
8. 远端无变化且本地已有资源时执行 download manifest audit,检查路径、size、BLAKE3 和 ZIP 结构。
|
8. 远端无变化且本地已有资源时执行 download manifest audit,检查路径、size、BLAKE3 和 ZIP 结构。
|
||||||
9. 远端变化、本地 audit 发现 repair_needed,首次空目录运行,或缺少 `current` 原子发布指针时,进入下载/发布流程。
|
9. 远端变化、本地 audit 发现 repair_needed,首次空目录运行,或缺少 `current` 原子发布指针时,进入下载/发布流程。
|
||||||
10. 下载先写入 `<output>/.staging/<id>`;若已有 active release,会先 seed staging 以复用已验证文件;若 version-state 中存在同一版本的失败 staging,则优先复用该 staging 并跳过 active seed,避免旧 active 覆盖已下载的新文件。
|
10. 下载先写入 `<output>/.staging/<id>`;若已有 active release,会先 seed staging 以复用已验证文件;若 version-state 中存在同一版本的失败 staging,则优先复用该 staging 并跳过 active seed,避免旧 active 覆盖已下载的新文件。新 staging 还会扫描已发布 release 的下载 manifest,按规范化 destination 查找候选并重新验证 size、BLAKE3 和 ZIP 结构;硬链接失败时回退到临时文件复制和原子 rename,历史 release 保持不可变。
|
||||||
11. 下载、manifest、本地 BLAKE3、ZIP 和官方 `.hash` 校验完成后写入新的 snapshot,并在 staging 中写入 `official-launcher-bootstrap.json`(若本轮启用 `--auto-discover`)。
|
11. 下载、manifest、本地 BLAKE3、ZIP 和官方 `.hash` 校验完成后写入新的 snapshot,并在 staging 中写入 `official-launcher-bootstrap.json`(若本轮启用 `--auto-discover`)。
|
||||||
12. 将 staging rename 为 `<output>/versions/<id>`,再原子替换 `<output>/current` symlink 指向该 versioned 目录。
|
12. 将 staging rename 为 `<output>/versions/<id>`,再原子替换 `<output>/current` symlink 指向该 versioned 目录。
|
||||||
13. 发布完成后先对比上一完整 release 和当前 release 的 `official-download-manifest.json`,写出 `official-resource-changes.json` 和 `crowdin-translation-handoff.json`。同一 destination 只有 size 或 BLAKE3 变化才算 modified;新增+变更资源进入解析/翻译 handoff,删除资源只进入差异记录。当前只预留 Crowdin 本地 handoff,不发外部 API 请求。
|
13. 发布完成后先对比上一完整 release 和当前 release 的 `official-download-manifest.json`,写出 `official-resource-changes.json` 和 `crowdin-translation-handoff.json`。同一 destination 只有 size 或 BLAKE3 变化才算 modified;新增+变更资源进入解析/翻译 handoff,删除资源只进入差异记录。当前只预留 Crowdin 本地 handoff,不发外部 API 请求。
|
||||||
14. 随后刷新 active release 下的 `official-parse-cache.json` 和 `official-textunit-index.json`,并从 Added/Modified 资源、parse cache 与 TextUnit 明细索引派生 `official-textunit-tasks.json`、`crowdin-textunit-queue.json` 和版本化的 `translation-tasks.sqlite`;up-to-date 轮询在已有有效解析缓存、TextUnit 明细索引和 TextUnit 队列时只读取摘要,不重复解析,重新同步队列时保留已有 worker 状态。
|
14. 随后刷新 active release 下的 `official-parse-cache.json` 和 `official-textunit-index.json`,并从 Added/Modified 资源、parse cache 与 TextUnit 明细索引派生 `official-textunit-tasks.json`、`crowdin-textunit-queue.json` 和版本化的 `translation-tasks.sqlite`;up-to-date 轮询在已有有效解析缓存、TextUnit 明细索引和 TextUnit 队列时只读取摘要,不重复解析,重新同步队列时保留已有 worker 状态。
|
||||||
15. 若启用 `--import-repository`,已校验 release 会被导入 CAS + `ResourceRepository`,并可经 `resource.index` 查询。
|
15. 若启用 `--import-repository`,已校验 release 会被导入 CAS + `ResourceRepository`,并可经 `resource.index` 查询。历史 release 候选失效时,已有 CAS 对象会先经过完整性和元数据校验,再增加 release 引用并原子物化;当前 release 在 `official-cas-reuse-references.json` 中记录引用,staging/release 清理时递减,失败则回退网络并保留诊断。
|
||||||
16. 官方同步报告默认给出 `localized_release_status=not_localized`,表示原版资源已发布、汉化资源未发布;UnityFS TextAsset patch 发布成功并通过 `localized-patch-manifest.json`、current symlink 和 release ID 校验后,`localized.status` 才返回 `localized`,表示原版和汉化两套资源都已发布。`translation.proofread` 只会把 workflow 标记成 `manual_proofreading` / `translation.manual_proofreading`,不会回退已发布汉化 release 的发布状态。
|
16. 官方同步报告默认给出 `localized_release_status=not_localized`,表示原版资源已发布、汉化资源未发布;UnityFS TextAsset patch 发布成功并通过 `localized-patch-manifest.json`、current symlink 和 release ID 校验后,`localized.status` 才返回 `localized`,表示原版和汉化两套资源都已发布。`translation.proofread` 只会把 workflow 标记成 `manual_proofreading` / `translation.manual_proofreading`,不会回退已发布汉化 release 的发布状态。
|
||||||
|
|
||||||
维护期特殊分支:如果官方 launcher/server-info 已经指向新资源根,但 client-patch seed marker 或必需 seed catalog 仍返回 403/404 等未开放状态,`bat` 返回 `waiting_for_official_resources`,保留现有 `current`,不创建失败 staging;若本轮启用 `--auto-discover`,会在 `<output>/official-launcher-bootstrap.pending.json` 写入待处理 launcher bootstrap 证据,供后续排障和自研客户端开发使用。
|
维护期特殊分支:如果官方 launcher/server-info 已经指向新资源根,但 client-patch seed marker 或必需 seed catalog 仍返回 403/404 等未开放状态,`bat` 返回 `waiting_for_official_resources`,保留现有 `current`,不创建失败 staging;若本轮启用 `--auto-discover`,会在 `<output>/official-launcher-bootstrap.pending.json` 写入待处理 launcher bootstrap 证据,供后续排障和自研客户端开发使用。
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
crowdin-textunit-queue.json # Crowdin worker 离线输入队列
|
crowdin-textunit-queue.json # Crowdin worker 离线输入队列
|
||||||
official-sync-snapshot.json # 常在 active root / current 下
|
official-sync-snapshot.json # 常在 active root / current 下
|
||||||
official-launcher-bootstrap.json # 官方 launcher 引导链版本化产物
|
official-launcher-bootstrap.json # 官方 launcher 引导链版本化产物
|
||||||
|
official-cas-reuse-references.json # 当前 release 获取的 CAS 引用
|
||||||
prod-clientpatch.bluearchiveyostar.com/
|
prod-clientpatch.bluearchiveyostar.com/
|
||||||
<root_token>/
|
<root_token>/
|
||||||
TableBundles/
|
TableBundles/
|
||||||
@@ -90,6 +91,17 @@
|
|||||||
| 发布完成 | rename 到 `versions/<id>`,再切换 `current` |
|
| 发布完成 | rename 到 `versions/<id>`,再切换 `current` |
|
||||||
| 生产读取 / bat-api | RPC 给出的 `version.resource_root`;通常等价于 `current` 解析后的 versioned 目录 |
|
| 生产读取 / bat-api | RPC 给出的 `version.resource_root`;通常等价于 `current` 解析后的 versioned 目录 |
|
||||||
|
|
||||||
|
每个 release 的 `official-download-manifest.json` 是历史复用的索引。新 staging
|
||||||
|
按规范化 destination 查找候选,并重新验证 manifest 中的 size、BLAKE3 和 ZIP
|
||||||
|
结构;URL、CDN 根或 release ID 变化本身不构成失效条件。复用文件先尝试硬链接,
|
||||||
|
跨文件系统时复制到 staging 内的临时文件并原子 rename,旧 `versions/<id>` 目录
|
||||||
|
保持不可变。
|
||||||
|
|
||||||
|
从 CAS 物化资源时,`official-cas-reuse-references.json` 记录每个获取的对象引用,
|
||||||
|
文件带版本字段且允许重复 object ID。孤儿 staging 或显式 release 清理必须先按
|
||||||
|
清单减少 CAS 引用,再删除目录;CAS 对象损坏、缺失或元数据不一致时只产生诊断,
|
||||||
|
回退网络下载,不发布未经校验的文件。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. URL → 磁盘映射(核心不变量)
|
## 3. URL → 磁盘映射(核心不变量)
|
||||||
|
|||||||
@@ -238,6 +238,15 @@ Crowdin 队列当前只落本地文件,不发网络请求。
|
|||||||
`--download-concurrency 1`。本文档中的真实资源命令仅是隔离 runbook;本地轻量
|
`--download-concurrency 1`。本文档中的真实资源命令仅是隔离 runbook;本地轻量
|
||||||
验证应使用 fake-curl/fixture,不要在开发机执行真实下载或 smoke run。
|
验证应使用 fake-curl/fixture,不要在开发机执行真实下载或 smoke run。
|
||||||
|
|
||||||
|
新 release 会在网络下载前扫描已发布 release 的下载 manifest,按规范化
|
||||||
|
destination 查找候选,并重新验证 size、BLAKE3 和 ZIP 结构。命中后优先硬链接,
|
||||||
|
跨文件系统时回退为 staging 内临时文件复制和原子 rename;历史 release 保持不可变。
|
||||||
|
历史候选不满足校验时才尝试既有 CAS 对象。CAS 复用会记录
|
||||||
|
`official-cas-reuse-references.json`,清理 staging/release 时递减引用;损坏、缺失
|
||||||
|
或元数据不一致会记录诊断并回退网络。报告和进度分别暴露
|
||||||
|
`release_reused_count`、`cas_reused_count`、`reused_bytes`、
|
||||||
|
`transferred_bytes` 以及 `release_reused` / `cas_reused` / `downloaded` 状态。
|
||||||
|
|
||||||
需要把已校验官方 release 导入 CAS + `ResourceRepository` 时,显式启用:
|
需要把已校验官方 release 导入 CAS + `ResourceRepository` 时,显式启用:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -264,6 +273,14 @@ cargo run -p bat-infrastructure --bin bat -- resource-index --limit 50
|
|||||||
cargo run -p bat-infrastructure --bin bat -- resource-index --release-id <ID> --platform windows --archive-entry <PATH> --format json --limit 50
|
cargo run -p bat-infrastructure --bin bat -- resource-index --release-id <ID> --platform windows --archive-entry <PATH> --format json --limit 50
|
||||||
```
|
```
|
||||||
|
|
||||||
|
issue 47 的隔离回归测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test -p bat-infrastructure official_download::tests::reuses_verified_historical_release_when_cdn_root_changes -- --nocapture
|
||||||
|
cargo test -p bat-infrastructure official_download::tests::falls_back_to_cas_after_corrupt_historical_release -- --nocapture
|
||||||
|
cargo test -p bat-infrastructure official_download::tests::corrupted_cas_falls_back_to_network_with_diagnostic -- --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
`parse-status` 会额外显示 TextUnit 明细索引和队列摘要;`parse-text-units` /
|
`parse-status` 会额外显示 TextUnit 明细索引和队列摘要;`parse-text-units` /
|
||||||
`parse-errors` 可按 destination、archive entry、path id、class id、field path
|
`parse-errors` 可按 destination、archive entry、path id、class id、field path
|
||||||
和 format 分页查询当前官方 release 的 TextUnit 明细与解析错误;
|
和 format 分页查询当前官方 release 的 TextUnit 明细与解析错误;
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ cargo run -p bat-infrastructure --example official_pull_plan -- \
|
|||||||
- 存在 `.part` 临时文件时会尝试断点续传
|
- 存在 `.part` 临时文件时会尝试断点续传
|
||||||
- 新下载先写 `.part`,成功并通过必要校验后再替换为最终文件;如果断点续传后的 `.zip` 结构校验失败,会删除 `.part` 并重新全量下载
|
- 新下载先写 `.part`,成功并通过必要校验后再替换为最终文件;如果断点续传后的 `.zip` 结构校验失败,会删除 `.part` 并重新全量下载
|
||||||
- 如果上一轮非 dry-run 已进入 staging 但未发布成功,下一轮会优先查找 `<output>/official-version-state.json` 中同一 app version、bundle version 和 Addressables root 的失败版本;只有对应 `<output>/.staging/<id>` 仍存在、路径安全且 `versions/<id>` 尚未发布时,才复用该 staging,并继续按 manifest 校验复用或重下单个 URL
|
- 如果上一轮非 dry-run 已进入 staging 但未发布成功,下一轮会优先查找 `<output>/official-version-state.json` 中同一 app version、bundle version 和 Addressables root 的失败版本;只有对应 `<output>/.staging/<id>` 仍存在、路径安全且 `versions/<id>` 尚未发布时,才复用该 staging,并继续按 manifest 校验复用或重下单个 URL
|
||||||
|
- 新 release 的 staging 在访问网络前会扫描已发布 release 的 `official-download-manifest.json`。候选必须同时满足 manifest 记录的 destination、size、BLAKE3 和适用的 ZIP 结构校验;URL、CDN 根和 release ID 的变化本身不会阻止复用。命中后优先用硬链接,跨文件系统时回退为临时文件复制并原子 rename,旧 release 不会被修改
|
||||||
|
- 历史 release 候选失效时,如果配置的 CAS 根已有对应 BLAKE3 对象,会先通过 CAS 读取完整性和元数据,再增加当前 release 的引用并原子物化;当前 release 会写 `official-cas-reuse-references.json`,清理孤儿 staging 或显式清理 release 时递减这些引用。CAS 损坏、缺对象或元数据不一致会写入复用诊断并继续走网络下载,不会静默使用缓存
|
||||||
- 把结果发布到 `--output/current`
|
- 把结果发布到 `--output/current`
|
||||||
|
|
||||||
## 5. 自动更新检查
|
## 5. 自动更新检查
|
||||||
@@ -210,6 +212,7 @@ cargo run -p bat-infrastructure --example official_pull_plan -- \
|
|||||||
- `--dry-run`:只报告本次是否会下载,不写 snapshot;如果 cache miss,也不会写入新的 bootstrap cache。
|
- `--dry-run`:只报告本次是否会下载,不写 snapshot;如果 cache miss,也不会写入新的 bootstrap cache。
|
||||||
- `--dry-run --plan`:除更新判断外,还会解析 seed catalog 并打印完整下载 URL。
|
- `--dry-run --plan`:除更新判断外,还会解析 seed catalog 并打印完整下载 URL。
|
||||||
- 真实更新会输出 `downloaded_count`、`resumed_count`、`skipped_count`、`transferred_bytes`、`official_seed_hash_verified_count`。
|
- 真实更新会输出 `downloaded_count`、`resumed_count`、`skipped_count`、`transferred_bytes`、`official_seed_hash_verified_count`。
|
||||||
|
- 复用统计还包括 `release_reused_count`、`cas_reused_count`、`reused_bytes` 和 `reuse_warnings`;单文件 progress 状态区分 `release_reused`、`cas_reused`、`downloaded`,`transferred_bytes` 不包含复用文件。
|
||||||
- 校验报告分层输出 `official_seed_hash_verified_count`、`local_manifest_verified_count`、`addressables_marker_checked_count`、`unverified_marker_count`。
|
- 校验报告分层输出 `official_seed_hash_verified_count`、`local_manifest_verified_count`、`addressables_marker_checked_count`、`unverified_marker_count`。
|
||||||
- 下载阶段复用同一套本地清单、ZIP 结构校验和 `.part` 续传逻辑;没有清单或校验不匹配的文件会重新下载。
|
- 下载阶段复用同一套本地清单、ZIP 结构校验和 `.part` 续传逻辑;没有清单或校验不匹配的文件会重新下载。
|
||||||
- 非 dry-run 且启用 `--auto-discover` 时,成功发布的 release 会包含 `official-launcher-bootstrap.json`;up-to-date 轮询发现当前 release 缺少该文件时会补写。官方 launcher/server-info 已更新但 client-patch 资源尚未开放时,不切换 `current`,只在输出根写入 `official-launcher-bootstrap.pending.json` 作为维护期证据。
|
- 非 dry-run 且启用 `--auto-discover` 时,成功发布的 release 会包含 `official-launcher-bootstrap.json`;up-to-date 轮询发现当前 release 缺少该文件时会补写。官方 launcher/server-info 已更新但 client-patch 资源尚未开放时,不切换 `current`,只在输出根写入 `official-launcher-bootstrap.pending.json` 作为维护期证据。
|
||||||
@@ -226,6 +229,7 @@ cargo run -p bat-infrastructure --example official_pull_plan -- \
|
|||||||
- `<output>/official-bootstrap-cache.json`:`--auto-discover` 的 `GameMainConfig` 解析缓存。launcher metadata 与 remote manifest 文件列表 digest 都未变时复用缓存;任一变化时才通过官方 HTTP 按 manifest 下载必要 `resources.assets` 或旧版 game zip 到临时目录解析。
|
- `<output>/official-bootstrap-cache.json`:`--auto-discover` 的 `GameMainConfig` 解析缓存。launcher metadata 与 remote manifest 文件列表 digest 都未变时复用缓存;任一变化时才通过官方 HTTP 按 manifest 下载必要 `resources.assets` 或旧版 game zip 到临时目录解析。
|
||||||
- `<output>/official-version-state.json`:资源发布根目录的持久版本状态,包含当前已完成版本、正在拉取版本、上一个可用版本和失败版本。
|
- `<output>/official-version-state.json`:资源发布根目录的持久版本状态,包含当前已完成版本、正在拉取版本、上一个可用版本和失败版本。
|
||||||
- `<output>/current/official-download-manifest.json`:本地下载强校验清单,记录 URL、相对路径、size 和 BLAKE3。
|
- `<output>/current/official-download-manifest.json`:本地下载强校验清单,记录 URL、相对路径、size 和 BLAKE3。
|
||||||
|
- `<output>/current/official-cas-reuse-references.json`:当前 release 获取的 CAS 引用清单;每个复用项占一条记录,release 清理或孤儿 staging GC 时据此递减引用。
|
||||||
- `<output>/current/official-resource-changes.json`:当前 release 相对上一完整 release 的资源差异,记录新增、变更、删除以及解析/翻译候选计数。
|
- `<output>/current/official-resource-changes.json`:当前 release 相对上一完整 release 的资源差异,记录新增、变更、删除以及解析/翻译候选计数。
|
||||||
- `<output>/current/crowdin-translation-handoff.json`:为后续 Crowdin worker 预留的本地队列,只包含新增+变更资源;它不是 Crowdin API 调用结果。
|
- `<output>/current/crowdin-translation-handoff.json`:为后续 Crowdin worker 预留的本地队列,只包含新增+变更资源;它不是 Crowdin API 调用结果。
|
||||||
- `<output>/current/official-parse-cache.json`:官方资源发布后的派生解析缓存,记录 bundle/zip 条目解析摘要和缓存复用情况;它不是汉化产物。
|
- `<output>/current/official-parse-cache.json`:官方资源发布后的派生解析缓存,记录 bundle/zip 条目解析摘要和缓存复用情况;它不是汉化产物。
|
||||||
@@ -320,7 +324,7 @@ cargo run -p bat-infrastructure --bin bat -- \
|
|||||||
--error-retry 60s
|
--error-retry 60s
|
||||||
```
|
```
|
||||||
|
|
||||||
默认平台是 `Windows,Android`,无需显式传 `--platforms`;只有要覆盖默认平台时才传。`--interval` 是正常检查周期,默认 `1h`;watch/daemon 模式还会在每天北京时间(UTC+8)`03:00`、`16:00`、`18:00` 强制执行一次自动刷新,该轮会注入 `force=true`,并且会中断普通 interval 的 sleep。`--error-retry` 是下载、发现或校验失败后的重试周期,默认 `60s`,也可以用 `--error-retry-seconds 60`。CLI 默认启动时向 stderr 打印 `BlueArchiveToolkit` ASCII banner,并把阶段进度日志写到 stderr,包括自动发现、proxy、server-info、marker、catalog、audit、download、snapshot 和 publish 阶段;download 阶段会输出已完成计数和单文件开始/完成状态,下载执行保持顺序处理,已完成计数保持单调不倒退,audit 阶段会输出官方 `.hash`、本地 BLAKE3、需修复项和 ZIP 结构校验结果摘要。daemon 还会写 `bat-events.jsonl` 结构化日志并按大小轮转。命令结果默认以人类可读摘要写到 stdout。需要纯机器输出时加 `--json --no-progress`,需要显式开启进度日志则用 `--progress`;只想关闭横幅但保留日志时可加 `--no-banner`。错误时 stderr 输出 JSON error,watch 模式下错误 JSON 的 `next_retry_seconds` 使用失败重试周期;如果未关闭 progress,错误 JSON 前可能已有 banner 和进度日志。普通错误 exit `1`,资源目录锁冲突 exit `75`,`verify` 或 `doctor` 发现问题也返回非 0。
|
默认平台是 `Windows,Android`,无需显式传 `--platforms`;只有要覆盖默认平台时才传。`--interval` 是正常检查周期,默认 `1h`;watch/daemon 模式还会在每天北京时间(UTC+8)`03:00`、`16:00`、`18:00` 强制执行一次自动刷新,该轮会注入 `force=true`,并且会中断普通 interval 的 sleep。`--error-retry` 是下载、发现或校验失败后的重试周期,默认 `60s`,也可以用 `--error-retry-seconds 60`。CLI 默认启动时向 stderr 打印 `BlueArchiveToolkit` ASCII banner,并把阶段进度日志写到 stderr,包括自动发现、proxy、server-info、marker、catalog、audit、download、snapshot 和 publish 阶段;download 阶段会输出已完成计数和单文件开始/完成状态,worker 从共享队列独立领取任务并在完成后立即领取下一项,完成计数保持单调不倒退,最终 report 的 `items` 仍按 pull plan 顺序排列,audit 阶段会输出官方 `.hash`、本地 BLAKE3、需修复项和 ZIP 结构校验结果摘要。daemon 还会写 `bat-events.jsonl` 结构化日志并按大小轮转。命令结果默认以人类可读摘要写到 stdout。需要纯机器输出时加 `--json --no-progress`,需要显式开启进度日志则用 `--progress`;只想关闭横幅但保留日志时可加 `--no-banner`。错误时 stderr 输出 JSON error,watch 模式下错误 JSON 的 `next_retry_seconds` 使用失败重试周期;如果未关闭 progress,错误 JSON 前可能已有 banner 和进度日志。普通错误 exit `1`,资源目录锁冲突 exit `75`,`verify` 或 `doctor` 发现问题也返回非 0。
|
||||||
|
|
||||||
生产可以直接运行 `--watch`,也可以用 `--daemon` 后台运行,或者用 systemd service、容器或 Go 进程守护它。cron/systemd timer 仍可调用单次模式,但不再是 Rust 自动更新的唯一方式。下载默认并发 8,可用 `--download-concurrency` / `BAT_DOWNLOAD_CONCURRENCY` 配置为 `1..=256`;worker 动态领取共享 plan,finished 进度即时按完成数统计,发布 report 仍按 plan 顺序。项目是否热更新、热重载或重启进程,由上层业务集成决定。生产官方资源目录应使用独立输出目录,不要指向现有客户端或人工维护的资源目录;上层读取原版资源时应读取 `--output/current`,不要读取 `.staging` 或 `versions` 中未切换的目录。汉化 Patch/导出应写入 `--localized-output`,并保留官方相对目录结构,不能写回 `--output/current`。发布状态分两档:`not_localized` 只发布原版资源、不发布汉化资源;`localized` 发布原版和汉化两套资源。非 dry-run 每轮会创建 `--output/.official-sync.lock`,防止并发写同一官方资源目录;live daemon 还会阻止前台写命令直接修改它正在管理的同一目录。
|
生产可以直接运行 `--watch`,也可以用 `--daemon` 后台运行,或者用 systemd service、容器或 Go 进程守护它。cron/systemd timer 仍可调用单次模式,但不再是 Rust 自动更新的唯一方式。下载默认并发 8,可用 `--download-concurrency` / `BAT_DOWNLOAD_CONCURRENCY` 配置为 `1..=256`;worker 动态领取共享 plan,finished 进度即时按完成数统计,发布 report 仍按 plan 顺序。项目是否热更新、热重载或重启进程,由上层业务集成决定。生产官方资源目录应使用独立输出目录,不要指向现有客户端或人工维护的资源目录;上层读取原版资源时应读取 `--output/current`,不要读取 `.staging` 或 `versions` 中未切换的目录。汉化 Patch/导出应写入 `--localized-output`,并保留官方相对目录结构,不能写回 `--output/current`。发布状态分两档:`not_localized` 只发布原版资源、不发布汉化资源;`localized` 发布原版和汉化两套资源。非 dry-run 每轮会创建 `--output/.official-sync.lock`,防止并发写同一官方资源目录;live daemon 还会阻止前台写命令直接修改它正在管理的同一目录。
|
||||||
|
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ issue 43 例外说明:新增的 `parse repack` 只编排已有 TextAsset、Typ
|
|||||||
- `translation-tasks.sqlite` 由 `schema_migrations` 管理 durable task state、attempt count、provider run ID 和 failure reason;重复同步会保留已有 worker 状态。
|
- `translation-tasks.sqlite` 由 `schema_migrations` 管理 durable task state、attempt count、provider run ID 和 failure reason;重复同步会保留已有 worker 状态。
|
||||||
- `translation.tasks` RPC/CLI 优先查询 `translation-tasks.sqlite`,旧 release 没有状态库时回退到 `official-textunit-tasks.json`;返回队列 `status`、worker `task_status`、failure reason 和时间/尝试次数。
|
- `translation.tasks` RPC/CLI 优先查询 `translation-tasks.sqlite`,旧 release 没有状态库时回退到 `official-textunit-tasks.json`;返回队列 `status`、worker `task_status`、failure reason 和时间/尝试次数。
|
||||||
- `translation.tasks` / `translation.handoff` 已提供 Go typed helper 和 bat-api 鉴权查询端点;`translation.task.update` 已提供 queued/running/failed/completed/skipped 状态回写契约,并已通过 `bat i18n task update`、Go typed helper 和 bat-api 鉴权控制面暴露;`translation.worker.run` 已提供 Rust provider worker,支持 mock/Crowdin、独立 claim、lease、失败分类、重试和 TextUnit 级译文结果落库;`translation.proofread` 已提供人工校对状态标记契约。
|
- `translation.tasks` / `translation.handoff` 已提供 Go typed helper 和 bat-api 鉴权查询端点;`translation.task.update` 已提供 queued/running/failed/completed/skipped 状态回写契约,并已通过 `bat i18n task update`、Go typed helper 和 bat-api 鉴权控制面暴露;`translation.worker.run` 已提供 Rust provider worker,支持 mock/Crowdin、独立 claim、lease、失败分类、重试和 TextUnit 级译文结果落库;`translation.proofread` 已提供人工校对状态标记契约。
|
||||||
|
- issue #47 已补齐官方资源同步的历史 release 与 CAS 前置复用:按计划顺序生成结果、按共享队列动态领取任务、对复用源执行 size/BLAKE3/ZIP 校验,失败时保留诊断并回退网络;CAS release 引用写入版本化清单,staging GC 会先释放引用再删除目录。更丰富的 Resource 查询、翻译记忆和通用 Patch 发布仍属于后续缺口。
|
||||||
- `i18n get/set/unset` 可查看、修改或清空单条工作台译文,`i18n validate` 可在发布前校验工作台 release、source text、重复 patch 目标,并区分可直接发布的 TextAsset 与必须进入 `parse repack` 的条目;`i18n proofread` 可把当前汉化 workflow 标记为人工校对中,且不会遮蔽已发布汉化 release;`parse clear-cache` 只删除可再生解析/队列 JSON,保留 `translation-tasks.sqlite` 的 worker 状态。
|
- `i18n get/set/unset` 可查看、修改或清空单条工作台译文,`i18n validate` 可在发布前校验工作台 release、source text、重复 patch 目标,并区分可直接发布的 TextAsset 与必须进入 `parse repack` 的条目;`i18n proofread` 可把当前汉化 workflow 标记为人工校对中,且不会遮蔽已发布汉化 release;`parse clear-cache` 只删除可再生解析/队列 JSON,保留 `translation-tasks.sqlite` 的 worker 状态。
|
||||||
- 翻译记忆和完整 localized repack 仍属于后续翻译系统工作,不在当前 provider worker 状态闭环范围内。
|
- 翻译记忆和完整 localized repack 仍属于后续翻译系统工作,不在当前 provider worker 状态闭环范围内。
|
||||||
|
|
||||||
@@ -572,12 +573,13 @@ issue 43 例外说明:新增的 `parse repack` 只编排已有 TextAsset、Typ
|
|||||||
3. issue #17 的历史顺序/重试契约仍保留;当前 issue #33/#35 已补有界 downloader
|
3. issue #17 的历史顺序/重试契约仍保留;当前 issue #33/#35 已补有界 downloader
|
||||||
scheduler 和默认并发 8,范围 `1..=256`,worker 完成后立即领取下一个任务,
|
scheduler 和默认并发 8,范围 `1..=256`,worker 完成后立即领取下一个任务,
|
||||||
进度即时按完成数上报,最终 report 保持 plan 顺序。
|
进度即时按完成数上报,最终 report 保持 plan 顺序。
|
||||||
4. **G-008:已决策关闭**(同步 CLI = Rust `bat`;见 `GO_STATUS.md`)。
|
4. **issue #47:官方资源历史 release/CAS 复用已完成**;后续只跟踪真实官方长期运行和更丰富的版本清理策略。
|
||||||
5. **G-009 / issue #19**:资源 bootstrap/分发和同机 live 联调已完成;非「从零实现」。后续真实官方网络长期运行、持久化和完整 launcher/业务链不属于本 issue 关闭条件。
|
5. **G-008:已决策关闭**(同步 CLI = Rust `bat`;见 `GO_STATUS.md`)。
|
||||||
6. issue #2 / G-007(P1):Addressables 可校验字段。
|
6. **G-009 / issue #19**:资源 bootstrap/分发和同机 live 联调已完成;非「从零实现」。后续真实官方网络长期运行、持久化和完整 launcher/业务链不属于本 issue 关闭条件。
|
||||||
7. issue #3 / G-005(P1):UnityFS 容器基础解析已落地;对象级引擎解析继续跟踪 G-005。
|
7. issue #2 / G-007(P1):Addressables 可校验字段。
|
||||||
8. G-011:更丰富的 TextUnit 查询、翻译记忆查询和通用 Patch 发布所需资源视图。
|
8. issue #3 / G-005(P1):UnityFS 容器基础解析已落地;对象级引擎解析继续跟踪 G-005。
|
||||||
9. G-012 / G-006:Crowdin/翻译系统、复杂 AssetBundle 重打包和 Patch 发布流程统一。
|
9. G-011:更丰富的 TextUnit 查询、翻译记忆查询和通用 Patch 发布所需资源视图。
|
||||||
10. G-011D:原版/汉化双发布后的查询、分发和清理策略。
|
10. G-012 / G-006:Crowdin/翻译系统、复杂 AssetBundle 重打包和 Patch 发布流程统一。
|
||||||
|
11. G-011D:原版/汉化双发布后的查询、分发和清理策略。
|
||||||
|
|
||||||
Go 进度以 `docs/reports/GO_STATUS.md` 为准。G-018 / G-017 已关闭。
|
Go 进度以 `docs/reports/GO_STATUS.md` 为准。G-018 / G-017 已关闭。
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ use bat_infrastructure::{
|
|||||||
apply_patch_file, apply_unityfs_field_patch_file, apply_unityfs_string_field_patch_file,
|
apply_patch_file, apply_unityfs_field_patch_file, apply_unityfs_string_field_patch_file,
|
||||||
apply_unityfs_text_asset_patch_file, changed_endpoint_urls,
|
apply_unityfs_text_asset_patch_file, changed_endpoint_urls,
|
||||||
completed_worker_translation_workbench, diff_extended_snapshot, export_translation_workbench,
|
completed_worker_translation_workbench, diff_extended_snapshot, export_translation_workbench,
|
||||||
gc_orphan_staging, get_translation_entry, lexical_absolute, localized_patch_operations,
|
gc_orphan_staging_with_cas_root, get_translation_entry, lexical_absolute,
|
||||||
open_append_file, read_download_manifest_at, read_file_no_symlink,
|
localized_patch_operations, open_append_file, read_download_manifest_at, read_file_no_symlink,
|
||||||
read_localized_patch_manifest_at, read_localized_version_state, read_parse_cache_at,
|
read_localized_patch_manifest_at, read_localized_version_state, read_parse_cache_at,
|
||||||
read_snapshot, read_textunit_index_at, read_translation_workbench, read_version_state,
|
read_snapshot, read_textunit_index_at, read_translation_workbench, read_version_state,
|
||||||
redact_proxy_url, repack_bundle, resolve_curl_proxy, set_translation, unset_translation,
|
redact_proxy_url, repack_bundle, resolve_curl_proxy, set_translation, unset_translation,
|
||||||
@@ -5103,8 +5103,18 @@ impl HumanReport for OfficialUpdateReport {
|
|||||||
print_optional_field("资源数", self.resource_count);
|
print_optional_field("资源数", self.resource_count);
|
||||||
print_field("已下载", self.downloaded_count);
|
print_field("已下载", self.downloaded_count);
|
||||||
print_field("已续传", self.resumed_count);
|
print_field("已续传", self.resumed_count);
|
||||||
print_field("已跳过", self.skipped_count);
|
print_field("当前 manifest 复用", self.skipped_count);
|
||||||
|
print_field("历史 release 复用", self.release_reused_count);
|
||||||
|
print_field("CAS 复用", self.cas_reused_count);
|
||||||
|
print_field("复用量", format_bytes(self.reused_bytes));
|
||||||
print_field("传输量", format_bytes(self.transferred_bytes));
|
print_field("传输量", format_bytes(self.transferred_bytes));
|
||||||
|
print_field("复用诊断", self.reuse_warnings.len());
|
||||||
|
for warning in &self.reuse_warnings {
|
||||||
|
println!(
|
||||||
|
" - 复用回退 [{}] {} {}",
|
||||||
|
warning.source, warning.url, warning.message
|
||||||
|
);
|
||||||
|
}
|
||||||
print_field("最终大小", format_bytes(self.final_bytes));
|
print_field("最终大小", format_bytes(self.final_bytes));
|
||||||
print_field("本地校验通过", self.local_manifest_verified_count);
|
print_field("本地校验通过", self.local_manifest_verified_count);
|
||||||
print_field("需修复", self.local_manifest_repair_needed_count);
|
print_field("需修复", self.local_manifest_repair_needed_count);
|
||||||
@@ -6172,7 +6182,11 @@ fn run_clean_stable_command(options: &CliOptions) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// 后台已停止,清理未被版本状态引用的孤儿 staging 目录。
|
// 后台已停止,清理未被版本状态引用的孤儿 staging 目录。
|
||||||
if let Some(state) = read_version_state(&options.config.version_state_path())? {
|
if let Some(state) = read_version_state(&options.config.version_state_path())? {
|
||||||
removed_paths.extend(gc_orphan_staging(&options.config.output_root, &state)?);
|
removed_paths.extend(gc_orphan_staging_with_cas_root(
|
||||||
|
&options.config.output_root,
|
||||||
|
&state,
|
||||||
|
&options.config.effective_import_cas_root(),
|
||||||
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let report = CleanStableReport {
|
let report = CleanStableReport {
|
||||||
|
|||||||
@@ -70,13 +70,15 @@ pub use official_changes::{
|
|||||||
OFFICIAL_RESOURCE_CHANGES_VERSION,
|
OFFICIAL_RESOURCE_CHANGES_VERSION,
|
||||||
};
|
};
|
||||||
pub use official_download::{
|
pub use official_download::{
|
||||||
read_download_manifest_at, DownloadError, OfficialDownloadManifest,
|
read_cas_reuse_reference_manifest_at, read_download_manifest_at, release_cas_reuse_references,
|
||||||
|
DownloadError, OfficialCasReuseReferenceManifest, OfficialDownloadManifest,
|
||||||
OfficialDownloadManifestEntry, OfficialLocalManifestAuditItem,
|
OfficialDownloadManifestEntry, OfficialLocalManifestAuditItem,
|
||||||
OfficialLocalManifestAuditReport, OfficialLocalManifestAuditStatus,
|
OfficialLocalManifestAuditReport, OfficialLocalManifestAuditStatus,
|
||||||
OfficialLocalVerificationReport, OfficialResourceHashAlgorithm,
|
OfficialLocalVerificationReport, OfficialResourceHashAlgorithm,
|
||||||
OfficialResourceHashVerification, OfficialResourcePullItem, OfficialResourcePullProgress,
|
OfficialResourceHashVerification, OfficialResourcePullItem, OfficialResourcePullProgress,
|
||||||
OfficialResourcePullProgressKind, OfficialResourcePullReport, OfficialResourcePullService,
|
OfficialResourcePullProgressKind, OfficialResourcePullReport, OfficialResourcePullService,
|
||||||
OfficialResourcePullStatus, OfficialResourceVerification,
|
OfficialResourcePullStatus, OfficialResourceReuseWarning, OfficialResourceVerification,
|
||||||
|
OFFICIAL_CAS_REUSE_REFERENCES_FILE,
|
||||||
};
|
};
|
||||||
pub use official_game_main_config::OfficialGameMainConfigBootstrapService;
|
pub use official_game_main_config::OfficialGameMainConfigBootstrapService;
|
||||||
pub use official_launcher::{
|
pub use official_launcher::{
|
||||||
@@ -116,13 +118,14 @@ pub use official_textunit_queue::{
|
|||||||
};
|
};
|
||||||
pub use official_update::{
|
pub use official_update::{
|
||||||
cached_game_main_config_for_metadata, diff_extended_snapshot, gc_orphan_staging,
|
cached_game_main_config_for_metadata, diff_extended_snapshot, gc_orphan_staging,
|
||||||
read_bootstrap_cache, read_snapshot, read_version_state, write_bootstrap_cache, write_snapshot,
|
gc_orphan_staging_with_cas_root, read_bootstrap_cache, read_snapshot, read_version_state,
|
||||||
write_version_state, ExtendedSnapshotDelta, GameMainConfigSnapshot, LauncherMetadataSnapshot,
|
write_bootstrap_cache, write_snapshot, write_version_state, ExtendedSnapshotDelta,
|
||||||
LocalizedReleaseStatus, OfficialBootstrapCache, OfficialEndpointMarkerRole,
|
GameMainConfigSnapshot, LauncherMetadataSnapshot, LocalizedReleaseStatus,
|
||||||
OfficialEndpointMarkerSnapshot, OfficialFailedVersionRecord, OfficialServerInfoSource,
|
OfficialBootstrapCache, OfficialEndpointMarkerRole, OfficialEndpointMarkerSnapshot,
|
||||||
OfficialUpdateConfig, OfficialUpdateProgress, OfficialUpdateReport, OfficialUpdateService,
|
OfficialFailedVersionRecord, OfficialServerInfoSource, OfficialUpdateConfig,
|
||||||
OfficialUpdateSnapshot, OfficialUpdateStatus, OfficialVerificationSummary,
|
OfficialUpdateProgress, OfficialUpdateReport, OfficialUpdateService, OfficialUpdateSnapshot,
|
||||||
OfficialVersionRecord, OfficialVersionState, ResolvedBootstrap,
|
OfficialUpdateStatus, OfficialVerificationSummary, OfficialVersionRecord, OfficialVersionState,
|
||||||
|
ResolvedBootstrap,
|
||||||
};
|
};
|
||||||
pub use patch_ops::{
|
pub use patch_ops::{
|
||||||
apply_patch_file, apply_unityfs_field_patch_file, apply_unityfs_string_field_patch_file,
|
apply_patch_file, apply_unityfs_field_patch_file, apply_unityfs_string_field_patch_file,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -34,12 +34,12 @@ use crate::translation_tasks::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
build_official_pull_plan_for_platform_inventory, build_official_sync_plan,
|
build_official_pull_plan_for_platform_inventory, build_official_sync_plan,
|
||||||
changed_endpoint_urls, default_official_platforms, DownloadError,
|
changed_endpoint_urls, default_official_platforms, release_cas_reuse_references, DownloadError,
|
||||||
OfficialGameMainConfigBootstrapService, OfficialLauncherBootstrapService,
|
OfficialGameMainConfigBootstrapService, OfficialLauncherBootstrapService,
|
||||||
OfficialResourceHashVerification, OfficialResourcePullPlan, OfficialResourcePullProgress,
|
OfficialResourceHashVerification, OfficialResourcePullPlan, OfficialResourcePullProgress,
|
||||||
OfficialResourcePullProgressKind, OfficialResourcePullService, OfficialResourceVerification,
|
OfficialResourcePullProgressKind, OfficialResourcePullService, OfficialResourceReuseWarning,
|
||||||
YostarJpLauncherCdnConfig, YostarJpLauncherGameConfig, YostarJpLauncherManifestUrl,
|
OfficialResourceVerification, YostarJpLauncherCdnConfig, YostarJpLauncherGameConfig,
|
||||||
YostarJpLauncherRemoteManifest,
|
YostarJpLauncherManifestUrl, YostarJpLauncherRemoteManifest,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
read_parse_cache_at, OfficialParseCacheService, OfficialParseConfig, OfficialParseSummary,
|
read_parse_cache_at, OfficialParseCacheService, OfficialParseConfig, OfficialParseSummary,
|
||||||
@@ -827,6 +827,18 @@ pub struct OfficialUpdateReport {
|
|||||||
pub resumed_count: usize,
|
pub resumed_count: usize,
|
||||||
/// Number of resources skipped using the local manifest.
|
/// Number of resources skipped using the local manifest.
|
||||||
pub skipped_count: usize,
|
pub skipped_count: usize,
|
||||||
|
/// Number of resources reused from immutable historical releases.
|
||||||
|
#[serde(default)]
|
||||||
|
pub release_reused_count: usize,
|
||||||
|
/// Number of resources reused from CAS.
|
||||||
|
#[serde(default)]
|
||||||
|
pub cas_reused_count: usize,
|
||||||
|
/// Bytes materialized without network transfer.
|
||||||
|
#[serde(default)]
|
||||||
|
pub reused_bytes: u64,
|
||||||
|
/// Diagnostics recorded while invalid reuse candidates fell back.
|
||||||
|
#[serde(default)]
|
||||||
|
pub reuse_warnings: Vec<OfficialResourceReuseWarning>,
|
||||||
/// Final local byte count in the pull report.
|
/// Final local byte count in the pull report.
|
||||||
pub final_bytes: u64,
|
pub final_bytes: u64,
|
||||||
/// Bytes transferred in this run.
|
/// Bytes transferred in this run.
|
||||||
@@ -1120,6 +1132,36 @@ impl OfficialPublishLayout {
|
|||||||
path_exists_no_follow(&self.root.join(OFFICIAL_DOWNLOAD_MANIFEST_FILE))
|
path_exists_no_follow(&self.root.join(OFFICIAL_DOWNLOAD_MANIFEST_FILE))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn release_reuse_roots(&self, active_root: &Path) -> Result<Vec<PathBuf>, String> {
|
||||||
|
let mut roots = Vec::new();
|
||||||
|
if path_exists_no_follow(active_root)? {
|
||||||
|
roots.push(active_root.to_path_buf());
|
||||||
|
}
|
||||||
|
if !path_exists_no_follow(&self.versions_dir)? {
|
||||||
|
return Ok(roots);
|
||||||
|
}
|
||||||
|
ensure_safe_directory_path(&self.versions_dir, "官方资源历史 release 根目录")?;
|
||||||
|
let mut versions = fs::read_dir(&self.versions_dir)
|
||||||
|
.map_err(|error| {
|
||||||
|
format!(
|
||||||
|
"读取官方资源历史 release 根目录失败 {}:{error}",
|
||||||
|
self.versions_dir.display()
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
|
||||||
|
.filter(|path| {
|
||||||
|
fs::symlink_metadata(path)
|
||||||
|
.map(|metadata| metadata.is_dir() && !metadata.file_type().is_symlink())
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
versions.sort();
|
||||||
|
versions.reverse();
|
||||||
|
roots.extend(versions);
|
||||||
|
roots.dedup();
|
||||||
|
Ok(roots)
|
||||||
|
}
|
||||||
|
|
||||||
fn publish(&self, plan: &OfficialPublishPlan) -> Result<PathBuf, String> {
|
fn publish(&self, plan: &OfficialPublishPlan) -> Result<PathBuf, String> {
|
||||||
ensure_safe_directory_path(&self.versions_dir, "官方资源 versions 根目录")?;
|
ensure_safe_directory_path(&self.versions_dir, "官方资源 versions 根目录")?;
|
||||||
fs::create_dir_all(&self.versions_dir).map_err(|error| {
|
fs::create_dir_all(&self.versions_dir).map_err(|error| {
|
||||||
@@ -1663,6 +1705,10 @@ impl OfficialUpdateService {
|
|||||||
downloaded_count: 0,
|
downloaded_count: 0,
|
||||||
resumed_count: 0,
|
resumed_count: 0,
|
||||||
skipped_count: 0,
|
skipped_count: 0,
|
||||||
|
release_reused_count: 0,
|
||||||
|
cas_reused_count: 0,
|
||||||
|
reused_bytes: 0,
|
||||||
|
reuse_warnings: Vec::new(),
|
||||||
final_bytes: 0,
|
final_bytes: 0,
|
||||||
transferred_bytes: 0,
|
transferred_bytes: 0,
|
||||||
official_seed_hash_verified_count: 0,
|
official_seed_hash_verified_count: 0,
|
||||||
@@ -1861,6 +1907,12 @@ impl OfficialUpdateService {
|
|||||||
&config.curl_command,
|
&config.curl_command,
|
||||||
)
|
)
|
||||||
.with_proxy_config(config.curl_proxy.clone())
|
.with_proxy_config(config.curl_proxy.clone())
|
||||||
|
.with_release_reuse_roots(
|
||||||
|
publish_layout
|
||||||
|
.release_reuse_roots(&active_resource_root)
|
||||||
|
.map_err(anyhow::Error::msg)?,
|
||||||
|
)
|
||||||
|
.with_cas_reuse_root(config.effective_import_cas_root())
|
||||||
.with_max_concurrency(config.download_concurrency);
|
.with_max_concurrency(config.download_concurrency);
|
||||||
let pruned_stale_resource_count = staging_fetcher
|
let pruned_stale_resource_count = staging_fetcher
|
||||||
.prune_stale_manifest_entries(&pull_plan)
|
.prune_stale_manifest_entries(&pull_plan)
|
||||||
@@ -1898,13 +1950,24 @@ impl OfficialUpdateService {
|
|||||||
progress(OfficialUpdateProgress::new(
|
progress(OfficialUpdateProgress::new(
|
||||||
"download",
|
"download",
|
||||||
format!(
|
format!(
|
||||||
"下载阶段完成:已下载={} 已续传={} 已复用={} 本轮传输字节={}",
|
"下载阶段完成:已下载={} 已续传={} 当前 manifest 复用={} 历史 release 复用={} CAS 复用={} 本轮传输字节={}",
|
||||||
pull_report.downloaded_count(),
|
pull_report.downloaded_count(),
|
||||||
pull_report.resumed_count(),
|
pull_report.resumed_count(),
|
||||||
pull_report.skipped_count(),
|
pull_report.skipped_count(),
|
||||||
|
pull_report.release_reused_count(),
|
||||||
|
pull_report.cas_reused_count(),
|
||||||
pull_report.transferred_bytes()
|
pull_report.transferred_bytes()
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
if !pull_report.reuse_warnings.is_empty() {
|
||||||
|
progress(OfficialUpdateProgress::new(
|
||||||
|
"download",
|
||||||
|
format!(
|
||||||
|
"复用候选诊断:{} 项候选未通过校验,已按顺序回退",
|
||||||
|
pull_report.reuse_warnings.len()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
progress(OfficialUpdateProgress::new(
|
progress(OfficialUpdateProgress::new(
|
||||||
"audit",
|
"audit",
|
||||||
"执行最终本地 manifest 审计",
|
"执行最终本地 manifest 审计",
|
||||||
@@ -1996,6 +2059,10 @@ impl OfficialUpdateService {
|
|||||||
report.downloaded_count = pull_report.downloaded_count();
|
report.downloaded_count = pull_report.downloaded_count();
|
||||||
report.resumed_count = pull_report.resumed_count();
|
report.resumed_count = pull_report.resumed_count();
|
||||||
report.skipped_count = pull_report.skipped_count();
|
report.skipped_count = pull_report.skipped_count();
|
||||||
|
report.release_reused_count = pull_report.release_reused_count();
|
||||||
|
report.cas_reused_count = pull_report.cas_reused_count();
|
||||||
|
report.reused_bytes = pull_report.reused_bytes();
|
||||||
|
report.reuse_warnings = pull_report.reuse_warnings.clone();
|
||||||
report.final_bytes = pull_report.total_bytes();
|
report.final_bytes = pull_report.total_bytes();
|
||||||
report.transferred_bytes = pull_report.transferred_bytes();
|
report.transferred_bytes = pull_report.transferred_bytes();
|
||||||
report.official_seed_hash_verified_count = pull_report.official_hash_verified_count();
|
report.official_seed_hash_verified_count = pull_report.official_hash_verified_count();
|
||||||
@@ -2050,7 +2117,11 @@ impl OfficialUpdateService {
|
|||||||
|
|
||||||
// 清理未被最新版本状态引用的孤儿 staging 目录(GC 失败仅告警,不影响发布结果)。
|
// 清理未被最新版本状态引用的孤儿 staging 目录(GC 失败仅告警,不影响发布结果)。
|
||||||
match read_version_state(&version_state_path) {
|
match read_version_state(&version_state_path) {
|
||||||
Ok(Some(state)) => match gc_orphan_staging(&config.output_root, &state) {
|
Ok(Some(state)) => match gc_orphan_staging_with_cas_root(
|
||||||
|
&config.output_root,
|
||||||
|
&state,
|
||||||
|
&config.effective_import_cas_root(),
|
||||||
|
) {
|
||||||
Ok(removed) if !removed.is_empty() => progress(OfficialUpdateProgress::new(
|
Ok(removed) if !removed.is_empty() => progress(OfficialUpdateProgress::new(
|
||||||
"publish",
|
"publish",
|
||||||
format!("已清理 {} 个孤儿 staging 目录", removed.len()),
|
format!("已清理 {} 个孤儿 staging 目录", removed.len()),
|
||||||
@@ -2556,6 +2627,10 @@ fn waiting_for_official_resources_report(
|
|||||||
downloaded_count: 0,
|
downloaded_count: 0,
|
||||||
resumed_count: 0,
|
resumed_count: 0,
|
||||||
skipped_count: 0,
|
skipped_count: 0,
|
||||||
|
release_reused_count: 0,
|
||||||
|
cas_reused_count: 0,
|
||||||
|
reused_bytes: 0,
|
||||||
|
reuse_warnings: Vec::new(),
|
||||||
final_bytes: 0,
|
final_bytes: 0,
|
||||||
transferred_bytes: 0,
|
transferred_bytes: 0,
|
||||||
official_seed_hash_verified_count: 0,
|
official_seed_hash_verified_count: 0,
|
||||||
@@ -2874,6 +2949,8 @@ fn verification_progress_message(summary: &OfficialVerificationSummary) -> Strin
|
|||||||
fn localized_pull_status(status: crate::OfficialResourcePullStatus) -> &'static str {
|
fn localized_pull_status(status: crate::OfficialResourcePullStatus) -> &'static str {
|
||||||
match status {
|
match status {
|
||||||
crate::OfficialResourcePullStatus::SkippedExisting => "已复用",
|
crate::OfficialResourcePullStatus::SkippedExisting => "已复用",
|
||||||
|
crate::OfficialResourcePullStatus::ReleaseReused => "已复用历史 release",
|
||||||
|
crate::OfficialResourcePullStatus::CasReused => "已复用 CAS",
|
||||||
crate::OfficialResourcePullStatus::Resumed => "已续传",
|
crate::OfficialResourcePullStatus::Resumed => "已续传",
|
||||||
crate::OfficialResourcePullStatus::Downloaded => "已下载",
|
crate::OfficialResourcePullStatus::Downloaded => "已下载",
|
||||||
}
|
}
|
||||||
@@ -3700,12 +3777,22 @@ pub fn gc_orphan_staging(
|
|||||||
output_root: &Path,
|
output_root: &Path,
|
||||||
state: &OfficialVersionState,
|
state: &OfficialVersionState,
|
||||||
) -> anyhow::Result<Vec<PathBuf>> {
|
) -> anyhow::Result<Vec<PathBuf>> {
|
||||||
gc_orphan_staging_dirs(&output_root.join(OFFICIAL_STAGING_DIR), state)
|
gc_orphan_staging_with_cas_root(output_root, state, &output_root.join(".cas"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleans orphan staging directories and releases their recorded CAS refs.
|
||||||
|
pub fn gc_orphan_staging_with_cas_root(
|
||||||
|
output_root: &Path,
|
||||||
|
state: &OfficialVersionState,
|
||||||
|
cas_root: &Path,
|
||||||
|
) -> anyhow::Result<Vec<PathBuf>> {
|
||||||
|
gc_orphan_staging_dirs(&output_root.join(OFFICIAL_STAGING_DIR), state, cas_root)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gc_orphan_staging_dirs(
|
fn gc_orphan_staging_dirs(
|
||||||
staging_dir: &Path,
|
staging_dir: &Path,
|
||||||
state: &OfficialVersionState,
|
state: &OfficialVersionState,
|
||||||
|
cas_root: &Path,
|
||||||
) -> anyhow::Result<Vec<PathBuf>> {
|
) -> anyhow::Result<Vec<PathBuf>> {
|
||||||
let read_dir = match fs::read_dir(staging_dir) {
|
let read_dir = match fs::read_dir(staging_dir) {
|
||||||
Ok(read_dir) => read_dir,
|
Ok(read_dir) => read_dir,
|
||||||
@@ -3740,6 +3827,7 @@ fn gc_orphan_staging_dirs(
|
|||||||
if referenced.contains(&name) {
|
if referenced.contains(&name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
release_cas_reuse_references(&path, cas_root).map_err(anyhow::Error::msg)?;
|
||||||
fs::remove_dir_all(&path).map_err(|error| {
|
fs::remove_dir_all(&path).map_err(|error| {
|
||||||
anyhow::anyhow!("清理孤儿 staging 目录失败 {}:{error}", path.display())
|
anyhow::anyhow!("清理孤儿 staging 目录失败 {}:{error}", path.display())
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
Reference in New Issue
Block a user