merge: origin/dev(1623f68)合入 M197 分支——解 golden/tail.rs 冲突
基线:合的是 **origin/dev**(`1623f68`)。`origin/main` 是 D41 的孤儿发布快照 (`d616d65` parents 为空),不参与合并。用 merge 而不是 rebase,保住三轮复审 clean 的 tip。 冲突只有一个:`crates/i18n/src/catalog/golden/tail.rs`(热点文件,多条 mission 都往这里加 key)。 解法(不重排别人的行、不改别人的译文): 1. 以 **dev 侧 398 行**为底稿(含其它 mission 后加的 key); 2. 把 M197 的 68 行(`SettingsJavaTooNew` … `SettingsJavaReasonInstallRootTooLong`) 整块插在 `SettingsJavaReasonWriteFailed` 之后、M174 助手域(`SettingsGroupAssistant`)之前 —— 即 `settings_java_entries` 那段之后,装配链的正确位置; 3. 两侧合起来 463 行会越过 400 行硬线,于是按既有先例把**M174 助手域起的那半截** (dev 的 260 行)整段纯搬移到 `golden/tail_ai.rs`:`tail.rs` 211 行 / `tail_ai.rs` 274 行, 链序 `golden → tail → tail_ai → tail2` 不变,逐字节不删不改; 4. 解冲突时发现 dev 的 golden 行还是 M197 之前的旧文案(`SettingsJavaEmptyBody` / `SettingsJavaAddHint` 的反引号、`SettingsJavaNeverScannedBody` 的旧说法),已把这三条的 金表行同步回域文件里的现行文案(`split_keeps_every_translation` 逐位置比对当场抓出来的)。 顺带(合并导致的越线):`crates/core/src/download/engine.rs` 两侧各 400 行、合并后 401 (我 +1 行 `kind: err.kind`,dev +1 行),把模块文档里那三行散文重排成两行(一字未删)→ 400 行。 同一次还把这文件里对 `MirrorTable::set_gh_proxy` 的旧描述改成 M193 的现实(那两个函数已被删)。 验证:fmt 0 · check(app-iced+core+i18n all-targets)0 · i18n 36 · engine_mock 8 · app-iced java 45 · core lib java 71 · setting 116 · app-iced lib 全量 832;全仓 file-size 守门脚本复制执行 → 超限 0。
This commit is contained in:
commit
6e80fd3563
283 files changed
+22601
-2188
No files matched your search
@@ -5,18 +5,14 @@
|
||||
//! ```text
|
||||
//! 校验请求 → 内容寻址缓存直取(零网络)
|
||||
//! → 候选规划(镜像表展开 + 健康度排序)
|
||||
//! → Semaphore 限流
|
||||
//! → Semaphore 限流 ← 容量来自设置(M193,见 config.rs)
|
||||
//! → 候选源外循环(换源),每源内循环(指数退避 + 抖动)
|
||||
//! → 续传判定(保守)→ 单连接 / 多连接分块 → 流式校验 → 原子落盘
|
||||
//! → 失败则保留中间文件供下次续传
|
||||
//! ```
|
||||
//!
|
||||
//! 最后一步保留的是**本会话命名的**中间文件(`<name>.ullpart-<session>-<seq>`,不再固定
|
||||
//! 叫 `<name>.ullpart`):下次(甚至下次启动、重启后再下同一个文件)由 `PartFile::for_dest`
|
||||
//! 在目标目录里认领它并改名成新的会话名,续传判定照旧。详细身份规则见 `atomic.rs` 模块头的
|
||||
//! 「中间文件的身份」。
|
||||
//!
|
||||
//! 具体传输细节在 `transfer.rs`,错误分类在 `http.rs`,本文件只负责顺序与策略。
|
||||
//! 中间文件是本会话命名的(`<name>.ullpart-<session>-<seq>`),下次(甚至下次启动)由 `PartFile::for_dest` 认领并改名,
|
||||
//! 续传判定照旧(身份规则见 `atomic.rs` 模块头)。传输细节在 `transfer.rs`,错误分类在 `http.rs`,本文件只负责顺序与策略。
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -27,7 +23,7 @@ use tokio::sync::Semaphore;
|
||||
use super::atomic::PartFile;
|
||||
use super::backoff::Jitter;
|
||||
use super::cache::{now_ms, ContentCache};
|
||||
use super::config::EngineConfig;
|
||||
use super::config::{EngineConfig, MAX_CONCURRENCY};
|
||||
use super::error::{DownloadError, DownloadErrorKind};
|
||||
use super::health::HealthRegistry;
|
||||
use super::http;
|
||||
@@ -72,8 +68,10 @@ impl DownloadEngine {
|
||||
cache: Arc<ContentCache>,
|
||||
) -> Self {
|
||||
let http = http::build_client(&cfg).unwrap_or_else(|_| reqwest::Client::new());
|
||||
let sem = Arc::new(Semaphore::new(cfg.max_concurrency.max(1)));
|
||||
let seed = now_ms() ^ (u64::from(cfg.max_concurrency as u32) << 32);
|
||||
// 硬上限夹取(M193):手改的 launcher.json / CLI 自拼的 cfg 都可能写着 9999,在唯一构造点再夹一次。
|
||||
let permits = cfg.max_concurrency.clamp(1, MAX_CONCURRENCY);
|
||||
let sem = Arc::new(Semaphore::new(permits));
|
||||
let seed = now_ms() ^ (u64::from(u32::try_from(permits).unwrap_or(u32::MAX)) << 32);
|
||||
// 纯记账(M165):卡在构造这个点上,才统计得到绕过登记处的调用点(守卫靠 `constructions`)。
|
||||
super::shared::record_build(cache.root());
|
||||
Self {
|
||||
@@ -102,10 +100,15 @@ impl DownloadEngine {
|
||||
&self.cfg
|
||||
}
|
||||
|
||||
/// HTTP 客户端(同一条连接池与超时配置)。
|
||||
///
|
||||
/// 给「一锤子查询」用——例如 Adoptium 的版本查询(`core::game::java::api`):
|
||||
/// 没必要为了一个 GET 再造一个 client,代理/超时/证书栈都在这里统一。
|
||||
/// **真实并发闸门**此刻的许可数(探针,M193):读 [`Self::acquire`] 会去抢的那张
|
||||
/// `Semaphore`,**不是** `config().max_concurrency`。有下载在飞时偏小,空闲等于容量。
|
||||
#[must_use]
|
||||
pub fn concurrency_probe(&self) -> usize {
|
||||
self.sem.available_permits()
|
||||
}
|
||||
|
||||
/// HTTP 客户端(同一条连接池与超时配置):给「一锤子查询」用(如 Adoptium 版本查询),
|
||||
/// 没必要为了一个 GET 再造一个 client。
|
||||
#[must_use]
|
||||
pub fn client(&self) -> &reqwest::Client {
|
||||
&self.http
|
||||
@@ -243,7 +246,7 @@ impl DownloadEngine {
|
||||
}
|
||||
Err(err) => {
|
||||
// 4xx(NotFound)不算「源坏」:院校镜像只留最新补丁,请求旧补丁必然 404,
|
||||
// 记成失败会把唯一可用镜像冷却掉(M33/M34 实测:TUNA 每个目录只保 1 个归档)。
|
||||
// 记成失败会把唯一可用镜像冷却掉(M33/M34 实测:TUNA 每目录只保 1 个归档)。
|
||||
if err.kind == DownloadErrorKind::NotFound {
|
||||
self.health.record_missing(
|
||||
&candidate.source,
|
||||
@@ -347,11 +350,8 @@ impl DownloadEngine {
|
||||
})
|
||||
}
|
||||
|
||||
/// 该源此刻给几次尝试:
|
||||
///
|
||||
/// * 冷却中 → 1 次(这次就是「恢复探测」,失败会重新冷却);
|
||||
/// * 刚出冷却、还没洗白 → 也只给 1 次(`is_probe()`,别让死源每次到期都吃满 3 次);
|
||||
/// * 正常 → 配置的 `max_attempts_per_source`。
|
||||
/// 该源此刻给几次尝试:冷却中 / 刚出冷却未洗白 → 1 次(那就是恢复探测,别让死源每次到期
|
||||
/// 都吃满预算);正常 → 设置里的 `download.retry_limit`(= `max_attempts_per_source`)。
|
||||
pub(crate) fn attempts_for(&self, source: &str, kind: ResourceKind, now_ms: u64) -> u32 {
|
||||
let health = self.health.health_of(source, kind);
|
||||
if health.is_cooling_down(now_ms) || health.is_probe(now_ms) {
|
||||
|
||||
Reference in new issue
Block a user