Skip to content

feat(download): 下载 weg.fan 镜像前先制作镜像并展示进度 - #92

Open
std-external wants to merge 2 commits into
std-microblock:masterfrom
std-external:feat/wegfan-mirror-prepare
Open

std-external wants to merge 2 commits into
std-microblock:masterfrom
std-external:feat/wegfan-mirror-prepare

Conversation

@std-external

@std-external std-external commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

背景

weg.fan 镜像下载前会先由服务器现制作镜像文件,直接拉下载接口要一直等到镜像做好才能拿到数据,界面上就是「卡在 0%」。群主 @WEGFan 提出的流程:先 start 让服务器开始制作,轮询 status 把进度显示在下载管理里,进度满了再下载,避免阻塞。参考实现:WEGFan/Celeste-Mod-China-Mirror Modules/ChinaMirror.cs#L458(PrepareFile)。

接口契约(已在 celeste.weg.fan 上实测)

fileId 的来历:CeleMod 用的下载地址是 https://celeste.weg.fan/api/v2/download/gamebanana-files/{source},服务器 302 到 https://celeste.weg.fan/api/v2/download/files/{fileId},fileId 就是后者路径里的雪花 id(与 ChinaMirror 用 HEAD 读 Location 的做法一致)。已经是 files/{fileId} 形式的地址则直接取 id。

步骤 请求 响应
解析 fileId HEAD /api/v2/download/gamebanana-files/{source}(不跟随重定向) 302 Location: .../api/v2/download/files/590041115247004037
开始制作 POST /api/v2/mirror/start/{fileId} {"data":null,"code":200,"message":"OK"};未知文件 code:10002 "文件未找到"
轮询进度 GET /api/v2/mirror/status/{fileId} {"data":{"downloadProgress":{"current":55695,"total":10246734},"uploadProgress":{"current":0,"total":10246734}},"code":200,"message":"OK"}
下载 GET /api/v2/download/files/{fileId} 302 → https://celeste-mirror-cdn-2.wegfan.com/...(支持 Range,206)

状态机:downloadProgress 是服务器从源站拉取文件,uploadProgress 是写入镜像缓存;两者 current == total 才算制作完成。沿用 ChinaMirror 的做法:uploadProgress.current > 0 时认为下载阶段已完成(否则最后一段可能因为计数滞后一直等),只有 current == total 才继续下载。

轮询间隔 200ms;超时沿用参考实现:还没拿到任何进度(total == 0)超过 30s,或已有进度后停滞超过 10s 就放弃制作,另有 600s 总兜底。

顺带一提:/api/v1/mirror/status?type=&fileName= 现在会 302 到 /api/v2/mirror/status/{fileId},/start/{fileId} 就是 v2 的形式,所以这里直接实现 v2。

实现

后端(Rust):

  • src-tauri/src/wegfan.rs:新增镜像制作客户端。prepare_mirror()(:205)解析 fileId → start → 轮询 status → 返回 files/{fileId} 下载地址;combined_progress()(:131)合并两段进度;mirror_prepare_stalled() 判定超时。HTTP 用 ureq,超时约定与下载 agent 一致(connect/read/write 各 30s,见 fix(download): 下载链路走 ureq 自带的环境代理 + 补上读写超时 #84),并跟随环境代理;只为读 302 关掉重定向跟随。
  • src-tauri/src/backend.rs:prepare_mirror_before_download()(:2832)在下发任务线程里、真正下载之前调用它,进度通过新的 DownloadWorkerMessage::Preparing(:2768)上报;ModTaskInfo 增加 mirror_preparing 字段(:2461)。
  • 前端:stores/download.ts 把 mirror_preparing 映射成 mirrorPreparing(:22 / :225 / :408),DownloadList.tsx 的 stateLabel()(:94)在该状态下显示「制作镜像中」+ 服务端百分比 + 进度条(tone preparing,DownloadList.scss 里加了一条配色),不再像卡住。

只对 wegfan 生效的开关点

wegfan.rs:96 的 mirror_file_id() 只认 https://celeste.weg.fan/api/v2/download/gamebanana-files/ 与 .../api/v2/download/files/ 两个前缀,其余 URL(0x0a.de、GameBanana 直连、用户自定义 URL)返回 Ok(None),不产生任何请求,走原来的下载流程。单测 only_weg_fan_download_urls_are_prepared 锁住这一点。

失败、超时与重试(按 review 意见:制作镜像失败直接报错,不退回直连)

  • backend.rs:2840 的 resolve_download_url():prepare_mirror 报错(/start 请求失败、/status 接口不可用、文件未收录 code 10002、轮询超时)时,任务直接置为失败,不会静默换成直连下载。
  • 失败原因由 mirror_prepare_failure()(backend.rs:2834)拼成「制作镜像失败:<原始错误>」,原始错误会说明是哪一步坏了(Failed to query mirror status of {fileId}: ... 之类),界面显示在该行下方。
  • 轮询超时也算失败,不是自动降级:沿用仓库既有的失败任务习惯——该行变成「失败」并出现右侧的「重试」按钮(DownloadList.tsx 里 state === "failed" 且 source 存在时的现有 replay 动作),点重试会重新走一遍 start + 轮询。超时阈值仍与 ChinaMirror 一致:没有任何进度(total == 0)超过 30s、已有进度后停滞超过 10s,另有 600s 总上限。
  • 只对 weg.fan 生效这条不变:非镜像 URL 在 mirror_file_id() 就返回 Ok(None),resolve_download_url() 原样返回,其它镜像与直连的下载路径一个字都没动(download_mirror_tests::non_mirror_urls_are_downloaded_directly 锁住)。
  • 取消语义与现有任务一致:轮询里检查 cancel flag,取消时依旧返回 Download canceled、任务进入「已取消」而不是失败,重试/重下走原有路径(cancelDownload → cancel flag)。

验证

  • 接口契约在真实镜像上逐条验证(上面表格里的响应都是实测输出,包括一次真实的 start → 进度从 0 涨到 100% 的轮询过程)。
  • cargo test --lib:119 passed(含新增 6 个单测:只在 wegfan URL 上生效、fileId 解析、进度合并、超时判定、非镜像地址直连不受影响、失败原因文案)。
  • cargo fmt --check 干净,pnpm typecheck 干净。(改完 review 意见后这三个都重跑过。)
  • 实测 files/{fileId} 支持 Range(206),多线程下载不受影响。

未验证

  • 没有真机 GUI:Windows/Linux 桌面端的实际下载流程、以及「制作镜像中」这一行在界面上的显示没有跑过(只能保证类型检查和 Rust 单测通过)。
  • 取消:制作阶段取消的路径只做了代码走查,没有端到端跑。
  • 暂停:制作阶段不响应暂停(镜像制作是服务器侧动作,暂停按钮会在下载阶段生效),如有需要可以再补。
  • 「制作镜像失败」这条失败路径本身(网络中断、接口 500、超时)没有在真机上制造过,只有单测覆盖了文案与「不回退直连」的分支。

与其它 PR 的关系

src/celemod-ui/src/stores/download.ts、src/celemod-ui/src/components/DownloadList.tsx 与 #90(下载卡住/重下)改到同一批文件;本 PR 的改动是纯增量(各加一行映射 + 一个状态分支),如果 #90 先合并我来 rebase。

来源:Celeste Coders 群,@WEGFan 提出。

下载镜像文件时不再直接拉下载接口等服务器现做镜像(会长时间阻塞),
而是先 POST /api/v2/mirror/start/{fileId},再轮询
/api/v2/mirror/status/{fileId} 把服务端制作进度显示在下载管理里,
进度满后再下载。

只对 weg.fan 镜像的下载地址生效(gamebanana-files/{source} 与
files/{fileId} 两个前缀),0x0a.de、GameBanana 直连等其它来源完全保持
现状。制作镜像失败(网络/超时/文件不存在)时记录日志并回退到原来的直连
下载,不会因为镜像新接口挂了就下不了东西。
按 review 意见去掉兜底:/start 或 /status 失败、文件未收录、轮询超时
都让这次下载进入失败状态,理由(制作镜像失败:<原始错误>)显示在下载管理
里,用户可以用现有的「重试」按钮重走一遍制作流程,不再静默换成直连下载。

取消语义不变:取消仍然进入已取消而不是失败。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant