feat(download): 把 download.region / download.mirror_preference 接进源选择(M294)
两个键此前在引擎侧零消费者:设置页画着三档 chip,改它不影响任何一次下载。
现在它们是 RegionProfile 之上的一层显式覆盖(读时叠加,不落盘,region.json
仍是自动判定的结论):
显式 mirror_preference(official/mirror)
> 显式 region(cn/global)
> 自动探测(时区+语言+系统信号)与 24h 缓存
- core::download::preference(新):两个枚举的 from_raw(未知值/空串 ⇒ auto)
与 apply;两个键都落到 RegionMode 这一个「谁在前」开关上,不造第二套排序
- region::RegionProfile::overridden:在已有判定上叠一层模式(探测逻辑不动)
- DownloadTuning 加 region / mirror_preference 与 apply_source_selection
(组合顺序 = 优先级),shared::build 改用它;默认 Auto ⇒ 逐值等于今天
- 未进 RawTuning:CLI 的 tuning() 用完整字段字面量构造它,加字段会连累
crates/cli(不在本 mission scope),改走 with_source_selection 入口
- presenter 的 tuning_of 读这两个键(否则 GUI 侧仍是假行)
判据断具体源名序列:不设/auto/未知 ⇒ ["bmclapi","official"] /
["official","bmclapi"] 逐项不变;official/mirror/cn/global ⇒ 顺序跟着换;
另有一条从设置原文到引擎 plan() 的端到端用例。
This commit is contained in:
1 parent
c2fc4b7f26
commit
0f64acbd05
9 files changed
+454
-5
No files matched your search
@@ -91,6 +91,11 @@ pub const KEY_RESPONSE_TIMEOUT: &str = "download.response_timeout_secs";
|
||||
/// `setting::keys::proxy` 的 `the_gh_proxy_key_is_bound_to_the_schema_registration`(M205)钉住:写错就红;
|
||||
/// 它抓不住两侧同时改名(公开改名)。行为退化由 `empty_gh_proxy_means_follow_builtin_not_off` 与 `shared` 的候选链拦。
|
||||
pub const KEY_GH_PROXY: &str = "download.gh_proxy";
|
||||
/// 「地区判定」键(`auto` / `cn` / `global`)。消费方与回落规则见 `download::preference` 模块头;
|
||||
/// 读它的是宿主(presenter 的 `tuning_of`),翻译成 [`DownloadTuning::with_source_selection`]。
|
||||
pub const KEY_REGION: &str = "download.region";
|
||||
/// 「镜像偏好」键(`auto` / `official` / `mirror`)。读法与优先级同 [`KEY_REGION`]。
|
||||
pub const KEY_MIRROR_PREFERENCE: &str = "download.mirror_preference";
|
||||
|
||||
/// 引擎配置。
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use super::super::preference::{self, MirrorPreference, RegionPreference};
|
||||
use super::super::region::RegionProfile;
|
||||
use super::super::stall;
|
||||
use super::{
|
||||
@@ -41,6 +42,10 @@ pub struct DownloadTuning {
|
||||
/// 分块作业的平均吞吐下限(**字节/秒**,已夹在 `MIN_THROUGHPUT_FLOOR_KIB..=MAX_THROUGHPUT_FLOOR_KIB`
|
||||
/// 换算后的区间)。
|
||||
pub min_avg_throughput: u64,
|
||||
/// 显式的「地区」覆盖(`download.region`;`Auto` = 用自动探测/缓存,即 M294 之前的行为)。
|
||||
pub region: RegionPreference,
|
||||
/// 显式的「镜像偏好」覆盖(`download.mirror_preference`;`Auto` = 跟着 `region` 走)。
|
||||
pub mirror_preference: MirrorPreference,
|
||||
}
|
||||
|
||||
impl Default for DownloadTuning {
|
||||
@@ -52,6 +57,8 @@ impl Default for DownloadTuning {
|
||||
response_timeout: DEFAULT_RESPONSE_TIMEOUT,
|
||||
chunk_stall_timeout: DEFAULT_CHUNK_STALL,
|
||||
min_avg_throughput: stall::MIN_AVG_THROUGHPUT,
|
||||
region: RegionPreference::Auto,
|
||||
mirror_preference: MirrorPreference::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +137,10 @@ impl DownloadTuning {
|
||||
.and_then(|value| u32::try_from(value).ok())
|
||||
.filter(|kib| (MIN_THROUGHPUT_FLOOR_KIB..=MAX_THROUGHPUT_FLOOR_KIB).contains(kib))
|
||||
.map_or(default.min_avg_throughput, |kib| u64::from(kib) * 1024),
|
||||
// M294 的两个「源选择」键**不走这里**,见 [`Self::with_source_selection`]:
|
||||
// `from_raw` 恒给 `Auto`,即「不发言」。
|
||||
region: RegionPreference::Auto,
|
||||
mirror_preference: MirrorPreference::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +176,10 @@ impl DownloadTuning {
|
||||
///
|
||||
/// 存在的理由就是 P21 的验收判据:用户改完设置之后,得在 `logs/launcher.log` 里**看见**生效值,
|
||||
/// 而不是只有「我以为我改了」。字段顺序与 `EngineConfig` 一致,单位写在名字里。
|
||||
///
|
||||
/// M294 的两个「源选择」键**故意不在这一行里**:这一行唯一的读者是 CLI,而 CLI 的 `tuning()`
|
||||
/// 还没读那两个键——印一句 `地区=auto` 对文件里写着 `cn` 的用户就是新的假话。等 CLI 接线
|
||||
/// (见 `with_source_selection` 的说明)再一起加。
|
||||
#[must_use]
|
||||
pub fn summary(&self) -> String {
|
||||
let gh_proxy = match &self.gh_proxy {
|
||||
@@ -191,4 +206,35 @@ impl DownloadTuning {
|
||||
GhProxyOverride::Prefix(prefix) => profile.with_gh_proxy(Some(prefix)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 叠上「源选择」两个显式键的**原文**(`download.region` / `download.mirror_preference`)。
|
||||
///
|
||||
/// 为什么不进 [`RawTuning`]:CLI 的 `tuning()` 用**完整字段字面量**构造 `RawTuning`,给它加字段
|
||||
/// 会让 `crates/cli` 编译不过——而本轮不改那个宿主。于是这两个键走**同一个翻译层、另一个入口**:
|
||||
/// 默认 [`RegionPreference::Auto`] / [`MirrorPreference::Auto`](= 与今天逐值相同),
|
||||
/// 谁读设置谁调这里。未知值/空串的回落规则见 `preference` 模块头。
|
||||
#[must_use]
|
||||
pub fn with_source_selection(
|
||||
mut self,
|
||||
region: Option<&str>,
|
||||
mirror_preference: Option<&str>,
|
||||
) -> Self {
|
||||
self.region = region.map_or(RegionPreference::Auto, RegionPreference::from_raw);
|
||||
self.mirror_preference =
|
||||
mirror_preference.map_or(MirrorPreference::Auto, MirrorPreference::from_raw);
|
||||
self
|
||||
}
|
||||
|
||||
/// 把「源选择」这一族设置盖到地区档案上(装配引擎时的唯一入口)。
|
||||
///
|
||||
/// 顺序即优先级:先 `region` / `mirror_preference` 的显式覆盖(细则见 `preference` 模块头),
|
||||
/// 再叠 gh-proxy 三态——它只管**前缀**、不管顺序,所以放在最后。
|
||||
#[must_use]
|
||||
pub fn apply_source_selection(&self, profile: RegionProfile) -> RegionProfile {
|
||||
self.apply_gh_proxy(preference::apply(
|
||||
profile,
|
||||
self.region,
|
||||
self.mirror_preference,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ pub mod font;
|
||||
pub mod health;
|
||||
pub mod jdk_sources;
|
||||
pub mod mirrors;
|
||||
pub mod preference;
|
||||
pub mod progress;
|
||||
pub mod region;
|
||||
pub mod region_system;
|
||||
@@ -92,6 +93,7 @@ pub use self::font::{
|
||||
pub use self::health::{HealthKey, HealthRegistry, SourceHealth};
|
||||
pub use self::jdk_sources::{MsOpenJdkTable, OracleMirror, ZuluTable};
|
||||
pub use self::mirrors::{direct_source_id, AdoptiumMirror, GhProxy, MirrorTable, PrefixRule};
|
||||
pub use self::preference::{MirrorPreference, RegionPreference};
|
||||
pub use self::progress::{ProgressEmitter, ProgressEvent, ProgressThrottle};
|
||||
pub use self::region::{RegionMode, RegionProfile, RegionTrigger, Signals};
|
||||
pub use self::region_system::detect_signals;
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
//! 「源选择」的两个显式设置键 → [`RegionProfile`] 之上的覆盖层(M294)。
|
||||
//!
|
||||
//! 两个键:`download.region`(`auto` / `cn` / `global`)与 `download.mirror_preference`
|
||||
//! (`auto` / `official` / `mirror`)。在本轮之前它们**一个引擎侧消费者都没有**——
|
||||
//! `grep -rn "mirror_preference\|Region::" crates/core/src | grep -v setting/keys` 是零命中,
|
||||
//! 设置页画着三档 chip,改它不改任何一次下载。本模块就是那个消费者。
|
||||
//!
|
||||
//! # 为什么两个键都落到同一个开关上
|
||||
//!
|
||||
//! 引擎里「官方在前还是镜像在前」**只有一个**开关:[`RegionProfile::mode`](`mirrors.rs` 的
|
||||
//! `expand()` 与 `gh_proxy_candidates()` 都只认它)。两个键问的是同一件事的两个侧面——
|
||||
//! 「我在哪个地区」(决定默认值)与「我要谁在前」(更具体的显式选择)——所以两者都落成
|
||||
//! 「给 `mode` 叠一层显式结论」,**不另造第二套排序开关**。再造一层的下场是「改 A 被 B 盖住、
|
||||
//! 界面上看不出为什么」,正是本任务要治的那类病。
|
||||
//!
|
||||
//! 两者的分工也据此定死:`region` 回答「我在不在大中华区」(`greater_china`,并给出该地区的
|
||||
//! 默认模式),`mirror_preference` 只回答「谁在前」(`mode`),**不动地区判定**。
|
||||
//!
|
||||
//! # 优先级(唯一一条可读规则)
|
||||
//!
|
||||
//! ```text
|
||||
//! 显式 mirror_preference(official / mirror) ← 最具体,永远优先
|
||||
//! > 显式 region(cn / global)
|
||||
//! > 自动探测(时区 + 语言 + 系统信号)与 region.json 的 24h 缓存 ← 今天的行为
|
||||
//! ```
|
||||
//!
|
||||
//! `auto` 等于「这一层不发言」,退到下一层。两个键都不设、或都写 `auto` ⇒ 覆盖层是恒等映射,
|
||||
//! 与 M294 之前**逐值相同**(守卫:`preference/tests.rs` 的 `not_set_changes_nothing`)。
|
||||
//!
|
||||
//! # 回落规则(手改文件 / 老配置 / 未知值)
|
||||
//!
|
||||
//! 只认 schema 登记的那几个词(`setting::keys::REGIONS` / `MIRROR_PREFERENCES`),大小写与前后
|
||||
//! 空白不敏感。其余一切(`""` / `"cn2"` / `"bogus"`,以及非字符串值——宿主侧 `effective_str`
|
||||
//! 给出 `None`)**一律当 `auto`**,静默退到下一层:下载不能因为一个设置字符串就停下。
|
||||
//! 这与 `gh_proxy_override` 的「空串≠关」是同一条纪律的两面。
|
||||
//!
|
||||
//! # 不落盘
|
||||
//!
|
||||
//! 覆盖只发生在**读时**(`shared::build` 装配引擎那一刻,与 `apply_gh_proxy` 同一个位置)。
|
||||
//! `region.json` 里仍然只有自动判定的结论——把显式值写回去会让 24h 重判永久失效。
|
||||
|
||||
use super::region::{RegionMode, RegionProfile};
|
||||
|
||||
/// `download.region` 的三档。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum RegionPreference {
|
||||
/// 不发言:用自动探测(或 `region.json` 的 24h 缓存)的结论。
|
||||
#[default]
|
||||
Auto,
|
||||
/// `cn`:人在大中华区 ⇒ 默认镜像优先。
|
||||
GreaterChina,
|
||||
/// `global`:人在大中华区以外 ⇒ 默认直连。
|
||||
Global,
|
||||
}
|
||||
|
||||
impl RegionPreference {
|
||||
/// 稳定字符串 id(与 schema 的取值逐字相同;日志/诊断用)。
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Auto => "auto",
|
||||
Self::GreaterChina => "cn",
|
||||
Self::Global => "global",
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置原文 → 档位(未知值 = [`Self::Auto`],见模块头的回落规则)。
|
||||
#[must_use]
|
||||
pub fn from_raw(raw: &str) -> Self {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
"cn" => Self::GreaterChina,
|
||||
"global" => Self::Global,
|
||||
_ => Self::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
/// 叠到已有判定上:`cn` ⇒ 大中华区 + 镜像优先;`global` ⇒ 非大中华区 + 直连;
|
||||
/// `auto` ⇒ **原样返回**(一个字段都不动)。
|
||||
#[must_use]
|
||||
pub fn apply(self, profile: RegionProfile) -> RegionProfile {
|
||||
match self {
|
||||
Self::Auto => profile,
|
||||
Self::GreaterChina => profile.overridden(RegionMode::MirrorFirst, true),
|
||||
Self::Global => profile.overridden(RegionMode::Direct, false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `download.mirror_preference` 的三档。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum MirrorPreference {
|
||||
/// 不发言:跟着 [`RegionPreference`] 的结论走(镜像侧再按健康度排序)。
|
||||
#[default]
|
||||
Auto,
|
||||
/// `official`:官方源永远第一条。
|
||||
Official,
|
||||
/// `mirror`:镜像永远第一条(官方兜底)。
|
||||
Mirror,
|
||||
}
|
||||
|
||||
impl MirrorPreference {
|
||||
/// 稳定字符串 id(与 schema 的取值逐字相同;日志/诊断用)。
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Auto => "auto",
|
||||
Self::Official => "official",
|
||||
Self::Mirror => "mirror",
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置原文 → 档位(未知值 = [`Self::Auto`])。
|
||||
#[must_use]
|
||||
pub fn from_raw(raw: &str) -> Self {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
"official" => Self::Official,
|
||||
"mirror" => Self::Mirror,
|
||||
_ => Self::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
/// 叠到已有判定上:只改「谁在前」(`mode`),**不动** `greater_china`——那是
|
||||
/// [`RegionPreference`] 的事。`auto` ⇒ 原样返回。
|
||||
#[must_use]
|
||||
pub fn apply(self, profile: RegionProfile) -> RegionProfile {
|
||||
let greater_china = profile.greater_china;
|
||||
match self {
|
||||
Self::Auto => profile,
|
||||
Self::Official => profile.overridden(RegionMode::Direct, greater_china),
|
||||
Self::Mirror => profile.overridden(RegionMode::MirrorFirst, greater_china),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 两层覆盖按**固定顺序**叠:`region` 先、`mirror_preference` 后 ⇒ 后者更具体、永远优先。
|
||||
#[must_use]
|
||||
pub fn apply(
|
||||
profile: RegionProfile,
|
||||
region: RegionPreference,
|
||||
mirror: MirrorPreference,
|
||||
) -> RegionProfile {
|
||||
mirror.apply(region.apply(profile))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "preference/tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,195 @@
|
||||
//! 「源选择」两个键的行为守卫(M294)。
|
||||
//!
|
||||
//! 判据一律断**具体的源名序列**,不断「非空」:今天不改设置也能选到镜像,「非空」抓不住
|
||||
//! 「设置没被读」——本任务要证明的恰好是「改了真的变、不改逐值不变」。
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::{apply, MirrorPreference, RegionPreference};
|
||||
use crate::download::cache::now_ms;
|
||||
use crate::download::config::DownloadTuning;
|
||||
use crate::download::mirrors::MirrorTable;
|
||||
use crate::download::region::{store_cached, RegionMode, RegionProfile, RegionTrigger};
|
||||
use crate::download::shared::{engine_for, set_tuning};
|
||||
use crate::download::types::{DownloadRequest, ResourceKind};
|
||||
|
||||
const LIBRARIES_URL: &str =
|
||||
"https://libraries.minecraft.net/com/google/guava/guava/32.0.0/guava-32.0.0.jar";
|
||||
|
||||
/// 一个 URL 展开后的**源名序列**(`expand()` 就是「按顺序候选源」那一步)。
|
||||
fn sources(profile: &RegionProfile) -> Vec<String> {
|
||||
MirrorTable::builtin()
|
||||
.expand(LIBRARIES_URL, ResourceKind::Library, profile)
|
||||
.into_iter()
|
||||
.map(|candidate| candidate.source)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 一个独立数据根(共享引擎登记处是进程级、按根存,键撞了会互相干扰)。
|
||||
fn root(tag: &str) -> PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!("ull-pref-{tag}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).expect("建缓存根");
|
||||
dir
|
||||
}
|
||||
|
||||
/// **默认判据**:两个键都不设 / 写 `auto` / 写不认识的值 ⇒ 候选序列与不做覆盖**逐项相同**。
|
||||
///
|
||||
/// 这里的期望值不是「非空」而是 M294 之前 `expand()` 对同一 URL 的真实结论(国内档案
|
||||
/// `["bmclapi", "official"]`、国外档案 `["official", "bmclapi"]`),所以覆盖层一旦偷偷改了顺序,
|
||||
/// 断言立刻红。变异证明:把 `RegionPreference::Auto` 分支改成 `Global` ⇒ domestic 那两行红。
|
||||
#[test]
|
||||
fn not_set_changes_nothing() {
|
||||
let domestic = RegionProfile::mirror_first(0);
|
||||
let abroad = RegionProfile::direct(0);
|
||||
assert_eq!(sources(&domestic), ["bmclapi", "official"]);
|
||||
assert_eq!(sources(&abroad), ["official", "bmclapi"]);
|
||||
|
||||
for (region, mirror) in [
|
||||
(None, None),
|
||||
(Some(""), Some("")),
|
||||
(Some("auto"), Some("auto")),
|
||||
(Some("AUTO"), Some(" Auto ")),
|
||||
(Some("mars"), Some("prefer-mirror")),
|
||||
] {
|
||||
let tuned = DownloadTuning::default()
|
||||
.with_source_selection(region, mirror)
|
||||
.apply_source_selection(domestic.clone());
|
||||
assert_eq!(
|
||||
tuned, domestic,
|
||||
"region={region:?} mirror={mirror:?} 不该动档案"
|
||||
);
|
||||
assert_eq!(
|
||||
sources(&tuned),
|
||||
["bmclapi", "official"],
|
||||
"region={region:?} mirror={mirror:?} 不该动候选顺序"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// **主判据(mirror_preference)**:设成 `official` / `mirror` ⇒ 候选链第一条真的跟着换。
|
||||
///
|
||||
/// 与探测结论**故意取反**(国内档案 + official、国外档案 + mirror),这样「覆盖没生效」时
|
||||
/// 结论仍是探测值、两条断言各自红。变异证明:`MirrorPreference::apply` 的 `Official` 分支
|
||||
/// 返回原样 ⇒ 第一条还是 `bmclapi`,红。
|
||||
#[test]
|
||||
fn mirror_preference_decides_who_comes_first() {
|
||||
let domestic = RegionProfile::mirror_first(0);
|
||||
let abroad = RegionProfile::direct(0);
|
||||
|
||||
let official = DownloadTuning::default()
|
||||
.with_source_selection(None, Some("official"))
|
||||
.apply_source_selection(domestic.clone());
|
||||
assert_eq!(official.mode, RegionMode::Direct);
|
||||
assert_eq!(
|
||||
sources(&official),
|
||||
["official", "bmclapi"],
|
||||
"官方必须第一条"
|
||||
);
|
||||
assert_eq!(
|
||||
official.greater_china, domestic.greater_china,
|
||||
"镜像偏好只定「谁在前」,地区判定不归它管"
|
||||
);
|
||||
|
||||
let mirror = DownloadTuning::default()
|
||||
.with_source_selection(None, Some("mirror"))
|
||||
.apply_source_selection(abroad.clone());
|
||||
assert_eq!(mirror.mode, RegionMode::MirrorFirst);
|
||||
assert_eq!(sources(&mirror), ["bmclapi", "official"], "镜像必须第一条");
|
||||
assert_eq!(
|
||||
mirror.gh_proxy.as_deref(),
|
||||
Some(crate::download::region::DEFAULT_GH_PROXY),
|
||||
"「镜像优先」被显式要求时补齐内置 gh-proxy 前缀(否则 GitHub 资源等于没加速)"
|
||||
);
|
||||
assert_eq!(mirror.trigger, RegionTrigger::UserPinned);
|
||||
}
|
||||
|
||||
/// **主判据(region)**:`cn` / `global` 在探测结论之上叠一层,并改到「直连还是镜像」。
|
||||
///
|
||||
/// `greater_china` 也一起变(这是 `is_domestic()` 的输入 → JDK 发行版默认序),所以两个键
|
||||
/// 的分工在这里可见:region 定地区,mirror_preference 定顺序。
|
||||
#[test]
|
||||
fn region_decides_direct_or_mirror_first() {
|
||||
let cn = DownloadTuning::default()
|
||||
.with_source_selection(Some("cn"), None)
|
||||
.apply_source_selection(RegionProfile::direct(0));
|
||||
assert_eq!(cn.mode, RegionMode::MirrorFirst);
|
||||
assert!(cn.greater_china);
|
||||
assert!(cn.is_domestic());
|
||||
assert_eq!(sources(&cn), ["bmclapi", "official"]);
|
||||
|
||||
let global = DownloadTuning::default()
|
||||
.with_source_selection(Some("global"), None)
|
||||
.apply_source_selection(RegionProfile::mirror_first(0));
|
||||
assert_eq!(global.mode, RegionMode::Direct);
|
||||
assert!(!global.greater_china);
|
||||
assert!(!global.is_domestic());
|
||||
assert_eq!(global.gh_proxy, None, "直连不需要前缀");
|
||||
assert_eq!(sources(&global), ["official", "bmclapi"]);
|
||||
}
|
||||
|
||||
/// **优先级**:两个键都显式写时,更具体的 `mirror_preference` 胜。
|
||||
///
|
||||
/// 模块头那条规则的守卫。变异证明:`apply()` 里把两层顺序对调 ⇒ 第一条变回 `bmclapi`,红。
|
||||
#[test]
|
||||
fn an_explicit_mirror_preference_wins_over_an_explicit_region() {
|
||||
let tuned = apply(
|
||||
RegionProfile::direct(0),
|
||||
RegionPreference::GreaterChina,
|
||||
MirrorPreference::Official,
|
||||
);
|
||||
assert_eq!(tuned.mode, RegionMode::Direct, "mirror_preference 优先");
|
||||
assert!(tuned.greater_china, "region 的「我在国内」仍然有效");
|
||||
assert_eq!(sources(&tuned), ["official", "bmclapi"]);
|
||||
}
|
||||
|
||||
/// **端到端**:设置原文 → `DownloadTuning` → 进程级共享引擎 → `plan()` 的候选顺序。
|
||||
///
|
||||
/// 这一条才是「改了设置下次下载真的变」的判据:`plan()` 是引擎选源的决策点,`set_tuning` 是宿主
|
||||
/// (presenter 的 `apply_tuning` / CLI 的 `engine_with_tuning`)唯一的灌入口。
|
||||
/// 变异证明:`shared::build` 里换回 `apply_gh_proxy(region)` ⇒ 第一条仍是探测值 `bmclapi`,红。
|
||||
#[test]
|
||||
fn the_settings_reach_the_engines_candidate_plan() {
|
||||
let dir = root("plan");
|
||||
store_cached(
|
||||
&dir.join("region.json"),
|
||||
&RegionProfile::mirror_first(now_ms()),
|
||||
)
|
||||
.expect("写地区缓存");
|
||||
let request = DownloadRequest::new(
|
||||
ResourceKind::Library,
|
||||
LIBRARIES_URL.to_string(),
|
||||
dir.join("guava.jar"),
|
||||
);
|
||||
let plan = |dir: &Path| -> Vec<String> {
|
||||
engine_for(dir)
|
||||
.plan(&request, 0)
|
||||
.into_iter()
|
||||
.map(|candidate| candidate.source)
|
||||
.collect()
|
||||
};
|
||||
|
||||
// 探测结论 = 镜像优先(不改设置时计划的第一条就是它)。
|
||||
assert_eq!(plan(&dir), ["bmclapi", "official"]);
|
||||
|
||||
assert!(
|
||||
set_tuning(
|
||||
&dir,
|
||||
DownloadTuning::default().with_source_selection(None, Some("official"))
|
||||
),
|
||||
"改了源选择必须报变更(否则不会重建引擎)"
|
||||
);
|
||||
assert_eq!(plan(&dir), ["official", "bmclapi"]);
|
||||
|
||||
set_tuning(
|
||||
&dir,
|
||||
DownloadTuning::default().with_source_selection(Some("cn"), Some("mirror")),
|
||||
);
|
||||
assert_eq!(plan(&dir), ["bmclapi", "official"]);
|
||||
|
||||
// 回到「不设」⇒ 回到探测结论(不是停在最后一次覆盖上)。
|
||||
set_tuning(&dir, DownloadTuning::default());
|
||||
assert_eq!(plan(&dir), ["bmclapi", "official"]);
|
||||
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
@@ -174,6 +174,36 @@ impl RegionProfile {
|
||||
self
|
||||
}
|
||||
|
||||
/// 在**已有判定**之上叠一层显式模式覆盖(M294;探测逻辑不在这里,也不重写)。
|
||||
///
|
||||
/// 只换「谁在前」这一族字段:`mode`、`greater_china`、以及由模式派生的 `gh_proxy`
|
||||
/// ——镜像优先且原来没有前缀时补 [`DEFAULT_GH_PROXY`](否则「镜像优先」在 GitHub 资源上
|
||||
/// 等于没加速);直连时置 `None`(`gh_proxy_candidates()` 只在镜像优先下读它)。
|
||||
/// `decided_at_ms` / `probes_enabled` 原样保留:那是「探测做过什么」的记录,不是「谁在前」。
|
||||
/// `trigger` 记为 [`RegionTrigger::UserPinned`]。
|
||||
///
|
||||
/// **调用方负责不落盘**:这是读时的覆盖层(同一位置还有 `DownloadTuning::apply_gh_proxy`),
|
||||
/// `region.json` 里必须留自动判定的结论,否则 24h 重判永久失效。
|
||||
///
|
||||
/// 「显式设置 vs 自动探测」谁优先由 [`super::preference`] 按固定顺序决定,本函数只提供
|
||||
/// 「叠一层」这个原语。
|
||||
#[must_use]
|
||||
pub fn overridden(self, mode: RegionMode, greater_china: bool) -> Self {
|
||||
let gh_proxy = if mode == RegionMode::MirrorFirst {
|
||||
self.gh_proxy.or_else(|| Some(DEFAULT_GH_PROXY.to_string()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self {
|
||||
mode,
|
||||
trigger: RegionTrigger::UserPinned,
|
||||
greater_china,
|
||||
gh_proxy,
|
||||
probes_enabled: self.probes_enabled,
|
||||
decided_at_ms: self.decided_at_ms,
|
||||
}
|
||||
}
|
||||
|
||||
/// 自动判定是否已过期(用户钉死永不过期)。
|
||||
#[must_use]
|
||||
pub fn is_stale(&self, now_ms: u64) -> bool {
|
||||
|
||||
@@ -141,7 +141,7 @@ pub fn constructions(cache_root: impl AsRef<Path>) -> usize {
|
||||
|
||||
/// 真实构造(**presenter 侧**唯一一处 `DownloadEngine::new` 的宿主装配;CLI 走 [`engine_with_tuning`])。
|
||||
/// 装配参数从该数据根的 [`wanted`] 取,不用裸 `EngineConfig::default()`(M193):并发 → `Semaphore`
|
||||
/// 容量、尝试次数 → `attempts_for()` 上限、gh_proxy → 地区档案(`expand()` 只认档案里那个前缀)。
|
||||
/// 容量、尝试次数 → `attempts_for()` 上限、gh_proxy/地区/镜像偏好 → 地区档案(`expand()` 只认它)。
|
||||
fn build(cache_root: &Path) -> DownloadEngine {
|
||||
let now = now_ms();
|
||||
let tune = wanted(cache_root);
|
||||
@@ -154,7 +154,7 @@ fn build(cache_root: &Path) -> DownloadEngine {
|
||||
DownloadEngine::new(
|
||||
tune.apply_to(EngineConfig::default()),
|
||||
Arc::new(MirrorTable::builtin()),
|
||||
Arc::new(tune.apply_gh_proxy(region)),
|
||||
Arc::new(tune.apply_source_selection(region)),
|
||||
Arc::new(HealthRegistry::open_in(cache_root, now)),
|
||||
Arc::new(ContentCache::open(cache_root)),
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user