fix(m162-r1): 叶子成因穿出引擎 + 安装路径切 core 在线源 + 详情迟到结果闸门
P1-1(p1-1items 的根因): - download/error.rs:`DownloadError` 增 `leaf: Option<DownloadErrorKind>` 与 `effective_kind()`;`leaf_of()` 汇总规则只此一处:全 404 ⇒ NotFound、有 Network/Timeout ⇒ Network、全 5xx ⇒ ServerError、混合按「网络错 > 5xx > 404」 取最可诊断的那个(**不从 suppressed 文本猜状态码**)。 - download/bytes.rs:候选源循环收集逐源 kind,收尾时把叶子挂到 AllSourcesFailed 上。 - online/transport.rs:`From<DownloadError>` 改用 `effective_kind()`,于是 NotFound/Server/Integrity 在生产路径可达(项目被删/id 拼错/限速不再画成「未联网」)。 - import/fetch.rs:新增 `FetchErrorKind::Offline`(真正的「没联网」)并与 Network 分开; `From<DownloadError>`/`From<OnlineError>` 都按叶子/大类映射。 - import/complete.rs:补全只在 `Offline` 时早停;404(真实 mrpack 大多托管在 CF CDN, Modrinth 必然 404)与 5xx 只影响那一条,不再掐掉整轮;注记「因未联网提前停止」因此 只会在真断网时出现。 P2-1:presenter 安装路径不再自己拼 Modrinth URL / 自己解析 JSON——改用 `core::addon::online::Modrinth::versions` + `primary_file()` + `VersionFile::sha1/sha512`; `HostTask::InstallDone.result` 改成 `Result<String, ContentFailure>`, `InstallProgressView` 增 `failure_kind`(离线 ≠ 查无此项)。 P2-2:`app/update/content.rs` 把被详情方法抢走的「装入闸门」注释还回 `content_install_request`,详情方法只留自己那一行。 P2-3:`ContentState::apply_detail/fail_detail` 带上 project(事件与 HostTask 都携带请求时的 项目 id),迟到结果一律丢弃;`request_detail` 记下在飞项目,`detail_pending` 不再被迟到 结果清掉。 顺手项:`usable_hash` 让形状非法的那一档摘要被**丢弃**(sha512 仍校验,不再整条跳过; 两档都不可用才拒绝);`digest_file` 的 IO 失败先删落盘文件再返回。i18n 的 ContentOfflineBody 改成不暗示「有缓存」的措辞(golden 同步)。 用例:download/error.rs 叶子四档 + effective_kind;online/tests/classify.rs 5 条 (AllSourcesFailed+leaf 404 ⇒ NotFound、无叶子 ⇒ NotConnected 保守、叶子一一映射、 单源自身 kind、导入侧 404≠Offline);import_online 增「404 不停」「真离线才停」 (共 7 条);download 增「sha1 形状非法仍用 sha512 校验成功」;presenter 增详情迟到闸门。
This commit is contained in:
1 parent
81278f55af
commit
b394316df4
30 files changed
+658
-172
No files matched your search
@@ -308,8 +308,10 @@ pub struct ContentState {
|
||||
pub last_page_filled: bool,
|
||||
/// 选中项目的详情(M162:`LoadContentDetail` 的结果;`None` = 还没拉过)
|
||||
pub detail: Option<Box<ContentDetail>>,
|
||||
/// 详情请求在飞
|
||||
/// 详情请求在飞(哪个项目:迟到结果靠它判「还算不算当前这次」)
|
||||
pub detail_pending: bool,
|
||||
/// 在飞的那次详情请求的项目 id(`None` = 没有在飞)
|
||||
pub detail_requested: Option<String>,
|
||||
/// 最近一次详情请求的失败
|
||||
pub detail_failed: Option<ContentFailure>,
|
||||
}
|
||||
@@ -324,6 +326,7 @@ impl Default for ContentState {
|
||||
last_page_filled: false,
|
||||
detail: None,
|
||||
detail_pending: false,
|
||||
detail_requested: None,
|
||||
detail_failed: None,
|
||||
}
|
||||
}
|
||||
@@ -340,22 +343,40 @@ impl ContentState {
|
||||
self.detail = None;
|
||||
}
|
||||
self.detail_pending = true;
|
||||
self.detail_requested = Some(project.to_string());
|
||||
self.detail_failed = None;
|
||||
}
|
||||
|
||||
/// 详情落地(迟到的结果(换了选中项)由调用方按 `project` 判)。
|
||||
pub fn apply_detail(&mut self, detail: ContentDetail) {
|
||||
/// 详情落地:**只接受当前这次请求的结果**(迟到的、别的项目的一律丢掉)。
|
||||
///
|
||||
/// 场景:选中 A(请求发出)→ 立刻切到 B(又发一条)→ A 的结果后到。没有这道闸门的话,
|
||||
/// A 的版本列表会画在 B 的详情面板下,A 的失败也会被当成本次失败(r1 P2-3)。
|
||||
pub fn apply_detail(&mut self, project: &str, detail: ContentDetail) {
|
||||
if !self.accepts_detail(project) || detail.project != project {
|
||||
return;
|
||||
}
|
||||
self.detail_pending = false;
|
||||
self.detail_requested = None;
|
||||
self.detail_failed = None;
|
||||
self.detail = Some(Box::new(detail));
|
||||
}
|
||||
|
||||
/// 详情失败。
|
||||
pub fn fail_detail(&mut self, failure: ContentFailure) {
|
||||
/// 详情失败:同样只看当前这次请求。
|
||||
pub fn fail_detail(&mut self, project: &str, failure: ContentFailure) {
|
||||
if !self.accepts_detail(project) {
|
||||
return;
|
||||
}
|
||||
self.detail_pending = false;
|
||||
self.detail_requested = None;
|
||||
self.detail_failed = Some(failure);
|
||||
}
|
||||
|
||||
/// 这条详情结果还算当前这次吗(`false` = 迟到 / 用户已经换了选中项)。
|
||||
#[must_use]
|
||||
pub fn accepts_detail(&self, project: &str) -> bool {
|
||||
self.detail_requested.as_deref() == Some(project)
|
||||
}
|
||||
|
||||
/// 这批结果还算当前条件吗(`false` = 迟到的事件,丢掉,不许覆盖新的筛选结果)。
|
||||
#[must_use]
|
||||
pub fn accepts(&self, results: &ContentResults) -> bool {
|
||||
|
||||
Reference in new issue
Block a user