feat(core): M150 新增 GitHub Releases 更新源(D50)

D50 定案 GitHub Releases 为唯一更新源,清单地址固定为
`releases/latest/download/manifest.json`。此前 update 模块只有对拍用的
DirSource,这次把真实源补上。

- `update::github::GitHubReleaseSource` 实现 `UpdateSource`:清单从
  `<base>/<owner>/<repo>/releases/latest/download/manifest.json` 取,
  产物按 D49 命名规则(文件名)拼到同一条 latest/download 路径下;清单里
  已写完整 http(s) 地址的原样使用。
- 网络全走 `core::download`,不新造栈:包体交给引擎 `download()`(镜像展开
  → 地区判定 → 健康度排序 → 退避 → 续传 → 原子落盘),清单走引擎的
  `plan()` + 共享 client 逐候选取文本(与 `game::java` 的一锤子 GET 同一手法)。
  两条路径都吃到 mirrors.json 与地区档案里生效的 gh-proxy 前缀。
- 可配置面只有 owner/repo(默认 AskaEth/Unlucky-Launcher)与 base(默认
  https://github.com,测试指向本地假服务);渠道只有 stable 有清单,
  beta/alpha/nightly 返回 Ok(None)(D50「先只发 stable」),清单 404 也算
  「还没发布」而不是错误。
- `UpdateErrorKind` 新增 `Network`:远端取字节失败(连不上/超时/5xx/候选全挂)
  与本地 IO 分开——前者可重试,后者重试无意义。引擎把逐源失败压成
  AllSourcesFailed,本源从失败摘要里还原「所有源都 404」为 SourceMissing
  (产物没上传不该被报成网络抖动)。
- 校验仍只有一处:包体请求只带 expected_size,长度/摘要双检留在
  `update::verify`(与 DirSource 同口径)。
- 单测 13 条,全程离线(假 HTTP 服务在 127.0.0.1):固定清单地址、产物 URL
  解析、自定义 base/owner/repo、清单 404/非 JSON/字段非法/渠道不符/5xx 各自
  归到白名单分类、产物下载、产物 404 → SourceMissing、镜像优先下 gh-proxy
  前缀(清单与产物都验)、与 DirSource 同清单同产物选择结果一致。
This commit is contained in:
AskaEth committed 2026-11-07 08:06:22 +08:00
1 parent afdbc9fd51
commit f001b93942
7 files changed
+945 -18

No files matched your search

+5 -6
View File
@@ -1,12 +1,11 @@
//! 更新源抽象:清单从哪来、包体从哪来,全部收在一个 trait 后面。
//!
//! 更新源(CDN / 对象存储 / GitHub Release / 内网文件服务器)**还没拍板**,所以这里
//! 刻意不写死任何 URL、任何厂商名:core 只认 [`UpdateSource`] 三个方法,接哪家由上层
//! 注入。将来接 HTTP 源时,实现里用 `tokio`/`reqwest` 或
//! [`crate::download::blocking::block_on`] 都行,trait 形状不用动。
//! 这一层刻意不写死任何 URL、任何厂商名:core 只认 [`UpdateSource`] 三个方法,接哪家由上层
//! 注入。真实源已定(D50):[`super::GitHubReleaseSource`],它在 `github.rs`,网络与镜像策略
//! 全交给 `crate::download`。
//!
//! 内置的实现只有一个:[`DirSource`](本地目录 / `file://`),用途是测试、内网手工分发、
//! 以及给将来的 HTTP 实现当对照物。
//! 内置的另一个实现是 [`DirSource`](本地目录 / `file://`),用途是测试、内网手工分发,
//! 以及给远端实现当对拍基准。
//!
//! # 本地源的目录约定
//!