feat(m160): 整合包逐文件覆盖/保留决策 + 导入前冲突清单 + 限额口径统一

主任务(逐文件覆盖/保留)
- core: 新增 import/scan.rs —— 只读预检产出「冲突清单」(目标已存在且 SHA-1 不同才进),
  逐条给路径/来源/两侧体积与哈希;用户删过的、内容相同的、被叠层遮蔽的一律不打扰。
- core: 新增 import/decision.rs —— ConflictDecisions(整批档 + 逐项表,逐项优先),
  缺省档恒为 KeepUser;ImportRequest.conflict_policy 改由它承载,CLI 调用点不变。
- core: phase.rs 的 settle 按 path 取决策;非冲突路径不受决策影响。
- presenter: ImportScanView / ImportConflictView / ConflictDecisionsView(全部保留 /
  全部覆盖 / 逐项{default,items})+ PackCommand::Scan / PackEvent::Scan;host 新增只读
  spawn_scan,且新实例路径不再提前建目录(target_of 只算路径,prepare_target 才建)。
- app-iced: 导入前冲突清单页(分页 40 条/页、三入口、计数摘要、确认/取消、两行式行布局);
  预检干净时直接往下导,不打扰用户。出图见 C:/Temp/m160-shots/{pick-light,pick-dark}.png。
- i18n: 新增 18 条 en/zh 文案 + 更新「两者都留」那条口径说明。

限额口径统一
- pack/archive/limits.rs 成为唯一来源:PACK_MAX_ENTRIES/UNCOMPRESSED_BYTES/ENTRY_BYTES
  (20k / 512 MiB / 64 MiB);读侧 PackLimits::default 与写侧 WriteLimits::production 同源。
- 写侧不再直接用 zip::ZIP64_BYTES_THR;新增单条目闸,于是「能导出的包一定能导入」。
  不等式在 pack/tests/limits.rs 里用 const 断言钉死。

测试临时物泄漏(塔台追加项)
- addon/temp.rs:TempRoot —— Drop 即删(panic 展开同样删),Deref/AsRef 到落点路径,
  under()/at() 支持"子树落点、删父根"。形状照 core::update::tests::support::TempDir。
- addon::pack 与 addon::tests 两族全部改用 TempRoot(含 fixtures::Fixture 持守卫、
  multimc/fabric_instance 等返回守卫);4 GiB 稀疏文件的语义一字未改,只改"谁负责删"。
- 自证:pack/tests/mod.rs 三条用例(drop 删 / panic 也删 / 子树删父根)。

测试
- import_decisions.rs(11 例):预检清单、不打扰、逐项决策后的最终文件集、基线记包内哈希。
- roundtrip_three.rs(2 例):叠层 + 冲突夹具 import→export→import 三轮文件集逐字节一致,
  以及「选保留就按设计分叉」的反向证据。
- pack/tests/limits.rs(5 例):单条/总量边界、读侧用同一批常量、写出即可读回。

证据:cargo fmt --check 干净;cargo clippy --workspace --all-targets -D warnings 干净;
cargo test --workspace --all-targets -j 4 → 2507 passed / 0 failed(51 个 test binary)。
This commit is contained in:
worker-modpackpick authored and AskaEth committed 2026-11-07 13:52:43 +08:00
1 parent a4f096eea3
commit 4defc41e8d
64 files changed
+3819 -442

No files matched your search

+11 -3
View File
@@ -28,14 +28,22 @@
//! | [`view`] | 画法(进度**整块**复用下载页的安装面板) |
//! | `demo` | 出图夹具(`ULL_CONTENT_PACK` 选态) |
//!
//! ## M160 新增的一屏
//!
//! `model/pick.rs` + `view/pick.rs` 是**导入前的冲突清单**:分页(每页 `PAGE_SIZE` 条)、
//! 三个入口、计数摘要、确认/取消。它出现的前提是 core 的预检给出了至少一条冲突——
//! 没有冲突时页面连这一屏都不画(直接往下导)。
//!
//! ## 本版明确的边界(报告里同样登记)
//!
//! - **没有系统文件选择框**:依赖树里没有 `rfd` 一类的对话框依赖,加它要动根 `Cargo.toml`、
//! 过许可门禁、还要给 Linux 选后端(GTK / xdg-portal),直接影响「单文件产物」。所以入口是
//! **拖拽(真能用)+ 路径输入框**,并且把原因写在界面上([`model::Pack::no_dialog_note`])。
//! - **冲突没有逐文件决策**:core 的 `ConflictPolicy` 是整次导入一个全局档(`KeepUser` 默认 /
//! `Overwrite`),`ConflictFile` 是**事后账**。所以这一块给的是「逐文件看 + 整体翻档」,
//! 文案如实写「两者都留(改名)暂不支持」。逐文件策略已登记为后续 core mission。
//! - **冲突是逐文件决策了(M160)**:点「导入」先跑一次**只读预检**(core 的 `scan_pack`),
//! 没有冲突就直接导(不打扰用户);有冲突就画那一屏清单——分页([`model::PAGE_SIZE`] 条一页)、
//! 「全部保留 / 全部覆盖 / 逐项」三个入口、计数摘要、确认与取消。**缺省永远是保留**:
//! 没点过的文件一个都不会被包内内容替换。仍然不支持的是「两者都留(改名)」——那要改落盘
//! 布局,不在本轮。
mod demo;
mod message;