Files
Unlucky-Launcher/crates/presenter/src/host/settings/download.rs
T
worker-dlmsettings 94f56f9d9c fix(m193): 复审 r1 三条 P2——版本史还原、retry_limit 0 有诚实语义、装配点措辞更正
- P2-2:keys.rs 的 schema 版本史按原文逐字还原(1.4.0/1.5.0 的键名、1.2.0 的迁移口径、
  旧读新的完整结论),不再用版本文档换行数;腾行改从我自己新加的注释里出。
- P2-1:download.retry_limit 范围恢复 0~10,并把 0 的语义定成「只试一次」(attempts = max(1, 0)):
  老配置合法、零迁移、ull config get 读到的 0 与引擎真做的事一致(不再回落出厂 3)。
  schema 描述与 presenter/iced 两处 UI 都写清「0 与 1 都 = 只试一次,不代表不下载」。
- P2-3:shared.rs 的「唯一一处装配」更正为「presenter 侧唯一一处」(CLI 还有三处自建),
  docs 加「哪些路径吃这套设置」表,并立 finding(CLI install/java/pack 不读设置、并发 64→8)。
- P3-3:mirrors.rs 的 gh_proxies 注释改成「无生产读者,仅解析留档」。
- P3-4(presenter 侧):并发/重试描述的出厂值与上限改用常量 format!,不再是第二处字面量。
- 出图重拍(含 0/1 = 只试一次)。
2026-11-09 05:24:12 +08:00

179 lines
7.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 设置页「下载」分组 + 下载调优的**宿主侧翻译**(M193)。
//!
//! ## 为什么单开一个文件
//!
//! 同 `host/settings_tests.rs` 的理由:`host/settings.rs` 正好顶在 400 行硬线上,而本轮要往这一组加
//! 「`download.gh_proxy` 三态行 + 并发硬上限说明 + 调优翻译」约 24 行。不拆就得砍 `settings.rs` 里
//! 那些**留着防复发**的论证注释(`ai_group` 的 M174 回归说明、`parse_value` 的 P1-1 浮点键事故)
//! ——那比多一个文件糟得多。搬出来还有一个附带好处:这一组是**唯一**要往 core 灌下载调优的一组,
//! 代码与它的守卫(`settings_tests.rs`)现在挨在一起。
//!
//! ## 谁在读 / 谁在写
//!
//! ```text
//! config/launcher.json ←── store.set(`settings::set`,本模块只读)
//! download.concurrency ─┐
//! download.retry_limit ─┼─→ DownloadTuning(core 决定默认值与夹取)─→ shared::set_tuning
//! download.gh_proxy ───┘ (引擎重建时生效)
//! ```
//!
//! **调优的同步点是显式的**:`host/engine.rs` 每次取引擎前、`dispatch.rs` 的 `SetSetting` 分支里,
//! 都调 [`apply_tuning`](幂等:值没变就什么都不做)。它**不是**藏在某个读函数里的副作用——
//! 「读起来是纯读、实际改全局状态」正是本仓吃过亏的那类形状。
use unlucky_core::download::config::{
DownloadTuning, DEFAULT_MAX_ATTEMPTS_PER_SOURCE, DEFAULT_MAX_CONCURRENCY, MAX_CONCURRENCY,
};
use unlucky_core::download::shared;
use crate::dto::{SettingGroupView, SettingRowView};
use super::super::env::HostEnv;
use super::{int, selected, text};
/// 并发数的推荐档位(`selected()` 找不到时回 0;手改出来的非档位值会被插进选项表,见 [`group`])。
const CONCURRENCY_LADDER: [&str; 5] = ["4", "8", "16", "32", "64"];
/// 单源尝试次数的推荐档位(`0` 是旧版 GUI 提供过的档位,语义 = 只试一次,见 schema 描述)。
const RETRY_LADDER: [&str; 6] = ["0", "1", "2", "3", "5", "10"];
/// 下载(D7):并发/重试/地区/镜像偏好/代理/GitHub 加速前缀。
pub(super) fn group(env: &HostEnv) -> SettingGroupView {
let concurrency = int(
env,
unlucky_core::download::config::KEY_CONCURRENCY,
DEFAULT_MAX_CONCURRENCY as i64,
)
.to_string();
let retry = int(
env,
unlucky_core::download::config::KEY_RETRY_LIMIT,
i64::from(DEFAULT_MAX_ATTEMPTS_PER_SOURCE),
)
.to_string();
let concurrency_options = ladder(&CONCURRENCY_LADDER, &concurrency);
let retry_options = ladder(&RETRY_LADDER, &retry);
let concurrency_options: Vec<&str> = concurrency_options.iter().map(String::as_str).collect();
let retry_options: Vec<&str> = retry_options.iter().map(String::as_str).collect();
let regions = ["auto", "cn", "global"];
let mirrors = ["auto", "official", "mirror"];
let proxies = ["none", "system", "manual"];
let region_value = text(env, "download.region");
let mirror_value = text(env, "download.mirror_preference");
let proxy_value = text(env, "download.proxy_mode");
SettingGroupView::new("download", "下载与镜像(D7)")
.with(SettingRowView::choice(
unlucky_core::download::config::KEY_CONCURRENCY,
"并发数",
&format!(
"同时下载的文件数:出厂 {DEFAULT_MAX_CONCURRENCY},可调 1~{MAX_CONCURRENCY}(引擎硬上限,防止填 9999 把机器拖死);越界或手改坏值按出厂值生效"
),
&concurrency_options,
selected(&concurrency_options, &concurrency),
))
.with(SettingRowView::choice(
unlucky_core::download::config::KEY_RETRY_LIMIT,
"单源尝试次数",
&format!(
"同一个源最多试几次(含首次;出厂 {DEFAULT_MAX_ATTEMPTS_PER_SOURCE})。0 与 1 都等于「只试一次、不重试」——0 是旧版提供的档位,不代表「不下载」。换源不受此限"
),
&retry_options,
selected(&retry_options, &retry),
))
.with(SettingRowView::choice(
"download.region",
"地区判定",
"auto = 时区+语言+探测;决定是否优先国内镜像",
&regions,
selected(
&regions,
if region_value.is_empty() {
"auto"
} else {
&region_value
},
),
))
.with(SettingRowView::choice(
"download.mirror_preference",
"镜像偏好",
"auto = 按健康度评分;official = 官方源优先",
&mirrors,
selected(
&mirrors,
if mirror_value.is_empty() {
"auto"
} else {
&mirror_value
},
),
))
.with(SettingRowView::choice(
"download.proxy_mode",
"代理模式",
"none / system(跟随系统)/ manual(下面填地址)",
&proxies,
selected(
&proxies,
if proxy_value.is_empty() {
"none"
} else {
&proxy_value
},
),
))
.with(SettingRowView::text(
"download.proxy_url",
"HTTP 代理",
"manual 模式生效;留空则用系统代理",
&text(env, "download.proxy_url"),
))
// M193:gh-proxy 三态。**留空与 off 是两件事**,描述里必须都写出来——用户最容易把
// 「我还没决定」当成「我不要加速」,于是以为自己关掉了、实际还在套前缀。
.with(SettingRowView::text(
unlucky_core::download::config::KEY_GH_PROXY,
"GitHub 加速前缀(gh-proxy)",
"留空 = 跟随内置默认(镜像优先/国内判定时自动套 gh-proxy.com);填 off = 关闭(不生成任何 gh-proxy 候选);填一个 URL 前缀 = 覆盖它",
&text(env, unlucky_core::download::config::KEY_GH_PROXY),
))
.with(SettingRowView::action(
super::super::engine::DETECT_REGION_ACTION,
"重新检测地区",
"清掉 24h 地区缓存,下次下载重新判定",
"detect_region",
))
}
/// 推荐档位 + 「当前值不在档位里就把它插进去」(否则这一行会把 2 显示成 4 —— 又一个假值)。
fn ladder(ladder: &[&str], current: &str) -> Vec<String> {
let mut options: Vec<String> = ladder.iter().map(|item| (*item).to_string()).collect();
if !current.is_empty() && !options.iter().any(|option| option == current) {
options.push(current.to_string());
options.sort_by_key(|option| option.parse::<i64>().unwrap_or(i64::MAX));
}
options
}
/// 从 `launcher.json` 读出下载调优(默认值与夹取口径在 core,本函数只做「取原始值」)。
#[must_use]
pub(crate) fn tuning_of(env: &HostEnv) -> DownloadTuning {
use unlucky_core::download::config::{KEY_CONCURRENCY, KEY_GH_PROXY, KEY_RETRY_LIMIT};
let store = env.store();
let doc = store.doc();
let gh = doc
.effective_str(KEY_GH_PROXY)
.map(str::to_string)
.unwrap_or_default();
DownloadTuning::from_settings(
doc.effective_i64(KEY_CONCURRENCY),
doc.effective_i64(KEY_RETRY_LIMIT),
Some(gh.as_str()),
)
}
/// 把当前设置灌进 core 的下载调优(按数据根),返回**是否真的变了**(变了就失效该根的引擎)。
///
/// 幂等:值没变时 `shared::set_tuning` 一行不动,所以「每次取引擎前都调」是安全的。
pub(crate) fn apply_tuning(env: &HostEnv) -> bool {
shared::set_tuning(env.layout.cache(), tuning_of(env))
}