fix(content): 不兼容档在失败路径上取项目名 + 重试轮不再贴错根的标题 (M191 r2)

复审 r2 两条 p2,都修了,并按复审给的口径验过红。

**P2-1(续):不兼容档不再只有裸 id**
`FailureKind::Incompatible` 的项目**确实存在**(站点只是没有匹配目标版本/加载器的条目),
所以失败路径上值得现取一次名字——`plan::names_for_incompatible` 按失败项去重、一项一次 GET
(`Modrinth::project`),`fetch_title` 把任何失败都折成 `None`:
**拿名字失败不许影响安装结果**,取不到 / 拿到空标题就退回 id。只有这一档发请求;
「取不到」档 404 那部分项目根本不存在,再发一次只会再吃 404。

**P2-2(新):重试轮把根的标题贴到了前置头上**
根因是判「这条是不是根项目自己失败」用了 `plan.request.roots.first()`,而重试那一轮的 plan
来自 `retry_request()`,它的 `roots` 就是上一轮的失败名单——第一个是前置。
现在判据改成 **id 比对**(`Root{id,title}`,id 由 `remember` 一起记下来):
`label_of` 只在这条失败的 id 真的等于原始根 id 时才用根的标题。复审给的那个形状
(roots=["P7dR8mSH","1eAoo2KR"] + root_title="Sodium")现在画 `P7dR8mSH`,不再是
`Sodium (P7dR8mSH)`。

**守门(每条都真跑红过,且在拆完模块的最终代码上复跑)**
- 去掉不兼容档的名字来源 ⇒ `an_incompatible_item_shows_the_project_name_once_it_is_known` 红
  (`left: "P7dR8mSH"` / `right: "Entity Culling (P7dR8mSH)"`)。
- `label_of` 换回 `roots.first()` ⇒ `the_retry_round_does_not_paste_the_root_title_onto_a_dependency` 红
  (`left: ["Sodium (P7dR8mSH)","1eAoo2KR"]`,正是复审实测的那一行)。
- 取名字不降级(`expect`)⇒ `a_failed_name_lookup_falls_back_to_the_id_without_breaking_anything` 红。

**行数**:`plan.rs` 加完这套到 414 行、`install.rs` 到 403 行,都越过 400 硬线 ⇒
把「名字从哪来」整块(`Root`/`Names`/`names_for_incompatible`/`label_of`/`fetch_title`)
纯搬移到 `plan/labels.rs`(111 行):`plan.rs` 336、`install.rs` 388。
This commit is contained in:
AskaEth committed 2026-11-09 05:06:06 +08:00
1 parent 961ff453a0
commit 26e7b7333e
7 files changed
+313 -77

No files matched your search

@@ -3,7 +3,6 @@
//! 从 `plan.rs` 拆出(父文件加了 `label_of` 之后顶到 429 行,越过 400 行硬线)。
//! 拆法与本仓既有 `-p1/-p2` 一致:**纯搬移**,一行不改语义;调用方路径不变
//! (`plan.rs` 仍然 `pub(super) use` 这三个函数)。
//!
use std::path::{Path, PathBuf};
use std::sync::Mutex;
@@ -12,7 +11,7 @@ use unlucky_core::addon::online::DependencyPlan;
use super::super::super::env::HostEnv;
/// 「上一次装入留下的计划」表(M191):`只重试这些` 的输入。
/// 「上一次装入留下的计划」表的一行(M191):`只重试这些` 的输入。
///
/// ## 为什么要一张静态表(说清楚这笔债)
///
@@ -23,23 +22,45 @@ use super::super::super::env::HostEnv;
///
/// 键里带 `instance`:同一台机器上往两个实例装过东西,重试只该重试这个实例那一次的计划。
/// 成功之后由 [`forget`] 清掉——「全成功」时不该留下任何可重试的东西。
struct Remembered {
///
/// ## 为什么连**原始根项目 id** 一起记(r2 复审 P2-2)
///
/// 「这一条是不是根项目自己失败」不能靠 `plan.request.roots.first()` 判:重试那一轮的 plan 来自
/// `DependencyPlan::retry_request()`,它的 `roots` 就是**上一轮的失败名单**——第一个是前置,
/// 拿它跟根的显示名一比,就会把 `Sodium` 贴到一个前置头上。所以判据只能是「这个 id 是不是
/// 当初那次装入的根 id」,那个 id 得跟计划一起记下来。
struct Entry {
host: PathBuf,
instance: String,
/// 落盘目录(重试要按它重新扫 `present`,且**不许**换目录)
dir: PathBuf,
/// 那次装入的显示名(重试时面板头与失败名单的可读名都沿用它,不再退回项目 id)
/// 当初那次装入的根项目 id(`ContentInstallRequest.project`,判「是不是根」的唯一依据)
root: String,
/// 当初那次装入的显示名(只配给 `root` 用)
title: String,
plan: DependencyPlan,
}
static REMEMBERED: Mutex<Vec<Remembered>> = Mutex::new(Vec::new());
static REMEMBERED: Mutex<Vec<Entry>> = Mutex::new(Vec::new());
/// 取回的那一份记录(拆成结构体而不是四元组:调用点读得懂哪一个是哪个)。
pub(in crate::host::addon) struct Remembered {
/// 落盘目录
pub dir: PathBuf,
/// 当初那次装入的根项目 id
pub root: String,
/// 当初那次装入的显示名
pub title: String,
/// 上一轮的依赖计划(`retry_request()` 的来源)
pub plan: DependencyPlan,
}
/// 记下这一轮的计划(**覆盖**同一个 host + 实例的上一份)。
pub(in crate::host::addon) fn remember(
env: &HostEnv,
instance: &str,
dir: &Path,
root: &str,
title: &str,
plan: &DependencyPlan,
) {
@@ -48,10 +69,11 @@ pub(in crate::host::addon) fn remember(
.lock()
.unwrap_or_else(|poison| poison.into_inner());
table.retain(|item| !(item.host == host && item.instance == instance));
table.push(Remembered {
table.push(Entry {
host,
instance: instance.to_string(),
dir: dir.to_path_buf(),
root: root.to_string(),
title: title.to_string(),
plan: plan.clone(),
});
@@ -66,11 +88,8 @@ pub(in crate::host::addon) fn forget(env: &HostEnv, instance: &str) {
table.retain(|item| !(item.host == host && item.instance == instance));
}
/// 这个实例上一次失败的计划(+ 当时的落盘目录 + 显示名);`None` = 没有可重试的记录。
pub(in crate::host::addon) fn remembered(
env: &HostEnv,
instance: &str,
) -> Option<(PathBuf, String, DependencyPlan)> {
/// 这个实例上一次失败的计划(+ 落盘目录 + 原始根 id 与显示名);`None` = 没有可重试的记录。
pub(in crate::host::addon) fn remembered(env: &HostEnv, instance: &str) -> Option<Remembered> {
let host = env.layout.home().to_path_buf();
let table = REMEMBERED
.lock()
@@ -78,5 +97,10 @@ pub(in crate::host::addon) fn remembered(
table
.iter()
.find(|item| item.host == host && item.instance == instance)
.map(|item| (item.dir.clone(), item.title.clone(), item.plan.clone()))
.map(|item| Remembered {
dir: item.dir.clone(),
root: item.root.clone(),
title: item.title.clone(),
plan: item.plan.clone(),
})
}