fix(download): M294 r1 —— JDK 下载页与 CLI 也接上源选择,文档同步

复审 p1:发布版 GUI 的「下载 JDK」页是第二处 DownloadEngine::new(要 with_progress
拿走引擎),此前只读了 gh_proxy,region / mirror_preference 到不了那里;报告却把 GUI
写成「已接」。复审 p2:CLI 四条命令同样不认这两个键 —— 同一份 launcher.json 在 GUI 与
CLI 上表现不同,等于把「设置项说假话」挪到 CLI 侧。

- app-iced downloads/engine.rs:新增 SourceSelection(只读键、不翻译)+
  engine_with(.., &SourceSelection),在 gh-proxy 三态之前叠
  DownloadTuning::with_source_selection(..).apply_source_selection(..)
- Request 加 source_selection 字段,构造点(app/update/settings/java/downloads.rs)
  用 SourceSelection::of_config(&config) 显式传值(不从缓存根反推配置路径:
  反推失败会静默按 auto 收场,正是本 mission 要消灭的形状)
- cli download.rs 的 tuning():链 .with_source_selection(..),四条命令共用这一处装配
- core 的 summary():把「地区 / 镜像偏好」两档印进 CLI 那行生效值(CLI 既然读了,
  少印两项就是新的半真话)
- shared.rs 注释说清「共享引擎」与本文件的第二处装配点;app-iced 模块头补对照表
- 新判据:JDK 下载页与 CLI 各一条断候选源顺序的用例,均配变异验证(换回裸
  EngineConfig::default() / 裸 tuning 即红);补一条 region=cn + gh_proxy=off 的
  叠加次序守卫(复审 P3-2)
- docs/download-settings.md:§1 表格两行、§5 拆「已接 / 未接」、新增 §3.1.1 覆盖面
  表、§6 补 M14-M16 三条变异、§7 补「镜像可达性没验」

连带(不加就编不过,已报备 tower):downloads/tests.rs 的 Request 字面量 +1 行、
downloads/tests/gh_proxy.rs 两处 engine_with 调用点补第四参。
This commit is contained in:
worker-mirrorwire committed 2026-11-13 09:00:00 +08:00
1 parent 0f64acbd05
commit 69e3e198bf
11 files changed
+395 -36

No files matched your search

+8 -4
View File
@@ -177,9 +177,11 @@ impl DownloadTuning {
/// 存在的理由就是 P21 的验收判据:用户改完设置之后,得在 `logs/launcher.log` 里**看见**生效值,
/// 而不是只有「我以为我改了」。字段顺序与 `EngineConfig` 一致,单位写在名字里。
///
/// M294 的两个「源选择」键**故意不在这一行里**:这一行唯一的读者是 CLI,而 CLI 的 `tuning()`
/// 还没读那两个键——印一句 `地区=auto` 对文件里写着 `cn` 的用户就是新的假话。等 CLI 接线
/// (见 `with_source_selection` 的说明)再一起加。
/// M294 r1:两个「源选择」档位(`地区` / `镜像偏好`)也在这里——CLI 的
/// `download::tuning` 已经读了它们,这一行若不含就等于「打了一行说生效值、却少说两项」。两个值都用
/// [`RegionPreference::as_str`] / [`MirrorPreference::as_str`],与 schema 的取值逐字相同。
///
/// (`crates/cli/src/download.rs::tuning` 是唯一读者。)
#[must_use]
pub fn summary(&self) -> String {
let gh_proxy = match &self.gh_proxy {
@@ -188,12 +190,14 @@ impl DownloadTuning {
GhProxyOverride::Prefix(prefix) => prefix.clone(),
};
format!(
"并发={} 单源尝试={} 吞吐下限={}KiB/s 无进展窗口={}s 单次请求上界={}s gh_proxy={gh_proxy}",
"并发={} 单源尝试={} 吞吐下限={}KiB/s 无进展窗口={}s 单次请求上界={}s 地区={} 镜像偏好={} gh_proxy={gh_proxy}",
self.max_concurrency,
self.max_attempts_per_source,
self.min_avg_throughput / 1024,
self.chunk_stall_timeout.as_secs(),
self.response_timeout.as_secs(),
self.region.as_str(),
self.mirror_preference.as_str(),
)
}
@@ -193,3 +193,35 @@ fn the_settings_reach_the_engines_candidate_plan() {
std::fs::remove_dir_all(&dir).ok();
}
/// **叠加次序**(r1 复审 P3-2):`gh_proxy` 三态在源选择**之后**叠,所以
/// 「`region=cn`(⇒ 镜像优先)+ `gh_proxy=off`」⇒ 一个 gh-proxy 候选都不生成。
///
/// 这条此前只靠读 `apply_source_selection` 的实现,没有断言。变异证明:把
/// `apply_source_selection` 写成 `preference::apply(self.apply_gh_proxy(p), …)`(先 gh_proxy 后
/// 源选择)⇒ 前缀被重判成内置默认,`off` 那一段红。
#[test]
fn an_explicit_gh_proxy_off_wins_over_a_mirror_first_region() {
use crate::download::config::GhProxyOverride;
let url = "https://github.com/o/r/releases/download/v1/a.jar";
let sources = |tune: &DownloadTuning| -> Vec<String> {
let profile = tune.apply_source_selection(RegionProfile::direct(0));
MirrorTable::builtin()
.expand(url, ResourceKind::GitHubRelease, &profile)
.into_iter()
.map(|candidate| candidate.source)
.collect()
};
// 基线:`region=cn` 让档案变镜像优先,GitHub 资源因此套上内置前缀。
let mut tune = DownloadTuning::default().with_source_selection(Some("cn"), None);
assert_eq!(sources(&tune), ["gh_proxy", "official"]);
// 同一次设置里再写 `off`:它最后叠 ⇒ 前缀被清掉,候选链只剩官方。
tune.gh_proxy = GhProxyOverride::Off;
let off = tune.apply_source_selection(RegionProfile::direct(0));
assert_eq!(off.mode, RegionMode::MirrorFirst, "off 不该改「谁在前」");
assert_eq!(off.gh_proxy, None, "off 必须清掉前缀");
assert_eq!(sources(&tune), ["official"]);
}
+2 -1
View File
@@ -139,7 +139,8 @@ pub fn constructions(cache_root: impl AsRef<Path>) -> usize {
.unwrap_or(0)
}
/// 真实构造(**presenter 侧**唯一一处 `DownloadEngine::new` 的宿主装配;CLI 走 [`engine_with_tuning`])。
/// 真实构造(**共享引擎**这一处的宿主装配:presenter 主路径与 CLI 都走它,CLI 经 [`engine_with_tuning`])。
/// 发布版 GUI 的 JDK 下载页另有一处装配(`app-iced` 的 `…/java/downloads/engine.rs`,要 `with_progress` 拿走引擎);两处共用同一个源选择翻译层。
/// 装配参数从该数据根的 [`wanted`] 取,不用裸 `EngineConfig::default()`(M193):并发 → `Semaphore`
/// 容量、尝试次数 → `attempts_for()` 上限、gh_proxy/地区/镜像偏好 → 地区档案(`expand()` 只认它)。
fn build(cache_root: &Path) -> DownloadEngine {