feat(dlflow): M105 安装选项区(四家加载器 + 官方版本列表 + 实例名)与取消安装真停
用户实跑反馈两条,本轮各自定位到根因并修掉:
1. 「下载页没有前置加载器选项」= 功能未做:`Message::InstallVersion` 直接派
`AppCommand::InstallVersion`(= 只装原版),core 早在 M32/M35/M45 就有四家加载器的
安装管线。现在选中版本之后面板先给「安装选项」区(原版 / Forge / NeoForge / Fabric /
Quilt + 该加载器的官方版本列表 + 实例名),点「安装」才派
`AppCommand::CreateInstance`。
- 加载器版本列表复用 core 的 `LoaderSource::loader_versions`,经新增的
`AppCommand::ListLoaderVersions` / `CoreEvent::LoaderVersions` 回流;
缓存键是 `(加载器, 原版版本)`,切版本不会串列表;取不到给具名原因并**禁掉确认**。
- 不兼容组合用静态规则在**选中那一刻**说清(NeoForge 需 1.20.1+、Fabric/Quilt 需
1.14+、远古版四家都不支持),不让用户「选完才失败」,也不为不可能的组合作无谓联网。
- 选项草稿(选中 + 加载器 + 加载器版本 + 实例名)住在 `App`,`refresh_view` 带进页面
模型(与分类筛选同一做法;页面自己存不住跨事件的东西)。
2. 「点取消没反应」= 真 bug,两跳各断一半:
- 第一跳(UI 侧):取消确实生效了,但**还在跑的后台线程继续发进度**,`InstallProgress(None)`
刚把面板收回,下一条「下载中」立刻又点亮它 —— 用户看到的就是「点了没反应」。
修法:`CoreHost` 加安装代次 `install_epoch`(开工与取消都 +1),`HostTask::Install` /
`InstallDone` 带代次回来,代次不符一律丢。
- 第二跳(core 侧):`execute` 没有任何取消入参,取消只停在段边界,传输根本没停。
新增 `execute_with_cancel(…, cancel: Option<&AtomicBool>)`(既有 `execute` 原样包装,
CLI 一字未改):取消后不再起新文件,在飞的那个与「取消 future」`select` 赛跑,取消赢就
drop 掉下载 future(响应流关闭、`.ullpart` 由 `PartFile::Drop` 清掉),并且不写版本清单,
返回新的 `InstallErrorKind::Cancelled`。
- 另外:取消后**绝不登记实例**(文件段跑完但用户已取消的那一路也拦住);取消文案把
「哪些留着、哪些一定没发生」说清(D21)。
实测(真网络装置 `host/cancel_tests.rs`,`#[ignore]` 守着,跑法见文件头):
取消前 files_done=2 / `.ullpart` 613 字节;取消后 20 s 进度事件 **0 条**、`.ullpart` **0 字节**
(-613)、版本清单未落盘、实例目录不存在。取消前是 +201466 字节(同一个装置量的)。
文案全部进 i18n(中英,9 条新 key + 金表同步)。
This commit is contained in:
1 parent
d690d09b84
commit
30b1ff6f48
39 files changed
+2550
-518
No files matched your search
@@ -47,8 +47,15 @@ fn download_intents_dispatch_real_commands() {
|
||||
let _ = app.on_page(pages::Message::CancelInstall);
|
||||
let _ = app.on_page(pages::Message::SetCategory(pages::Category::Release));
|
||||
let _ = app.on_page(pages::Message::SelectVersion("1.21.8".to_string()));
|
||||
// M105:「安装」不再是「立刻装原版」,而是把**草稿里的选项**一起交出去
|
||||
// (`CreateInstance` 带加载器 / 加载器版本 / 实例名)——这正是用户报的「没有加载器可选」的修复点。
|
||||
let expected = vec![
|
||||
AppCommand::InstallVersion("1.21.9".to_string()),
|
||||
AppCommand::CreateInstance(unlucky_presenter::dto::CreateRequest {
|
||||
version: "1.21.9".to_string(),
|
||||
loader: unlucky_presenter::dto::LoaderKind::Vanilla,
|
||||
loader_version: String::new(),
|
||||
name: String::new(),
|
||||
}),
|
||||
AppCommand::CancelInstall,
|
||||
AppCommand::ListVersions(VersionCategory::Release),
|
||||
];
|
||||
@@ -57,7 +64,14 @@ fn download_intents_dispatch_real_commands() {
|
||||
expected,
|
||||
"三条各派一条;`SelectVersion` 不派"
|
||||
);
|
||||
assert_eq!(app.view.download.selected.as_deref(), Some("1.21.8"));
|
||||
// M105:选中项住在**草稿**里(`refresh_view` 每次事件都会整棵重建页面模型,
|
||||
// 页面自己存不住它);recorder 不回 `VersionListChanged` ⇒ 列表是空的 ⇒
|
||||
// 视图模型里那一行不存在,于是 `selected` 被过滤成 `None`(防「切分类后还留着一个点不动的安装」)。
|
||||
assert_eq!(app.download_draft.selected.as_deref(), Some("1.21.8"));
|
||||
assert_eq!(
|
||||
app.view.download.selected, None,
|
||||
"选中的行不在列表里时,视图模型不该拿着它"
|
||||
);
|
||||
assert_eq!(app.view.download.category, pages::Category::Release);
|
||||
assert_eq!(
|
||||
app.view.download.rows.len(),
|
||||
@@ -74,3 +88,86 @@ fn download_intents_dispatch_real_commands() {
|
||||
"重建视图模型之后分类必须还在"
|
||||
);
|
||||
}
|
||||
|
||||
/// **取消之后面板要回到「未安装态」且能立刻再装**(M105 的验收项,CI 可跑的那一半)。
|
||||
///
|
||||
/// 现场(用户原话):「取消下载按钮点了没反应」。真根因是取消生效了又被旧任务的事件盖回去
|
||||
/// (presenter 侧的代次守门在 `host/cancel_tests.rs`);这一条钉的是**页面这一侧**:
|
||||
/// 收到 `InstallProgress(None)` 之后,面板必须回到安装选项区、确认键可用、选中的版本还在
|
||||
/// ——不需要用户重新点一遍版本。
|
||||
///
|
||||
/// 抓得住:取消后仍画着进度面板 / 选项区丢了选中 / 确认键还是禁用。
|
||||
/// 抓不住:真网络下的取消延迟(那条是 `#[ignore]` 的真网络用例)。
|
||||
#[test]
|
||||
fn cancelling_gives_the_panel_back_to_the_install_options() {
|
||||
let log: Rc<RefCell<Vec<AppCommand>>> = Rc::new(RefCell::new(Vec::new()));
|
||||
let families = crate::theme::Families {
|
||||
text: "Segoe UI",
|
||||
display: "Segoe UI",
|
||||
mono: "Consolas",
|
||||
};
|
||||
let mut app = App::with_presenter(
|
||||
crate::cli::Options::default(),
|
||||
families,
|
||||
SchemeFlavor::Light,
|
||||
Box::new(Recorder(Rc::clone(&log))),
|
||||
SettingsBridge::memory_only(),
|
||||
);
|
||||
app.state.apply(AppEvent::Core(
|
||||
unlucky_presenter::dto::CoreEvent::VersionListChanged(vec![
|
||||
unlucky_presenter::dto::VersionSummary {
|
||||
id: "1.21.9".to_string(),
|
||||
kind: unlucky_presenter::dto::VersionKind::Release,
|
||||
released: "2026-09-20".to_string(),
|
||||
installed: false,
|
||||
},
|
||||
]),
|
||||
));
|
||||
app.refresh_view();
|
||||
|
||||
// 选中 → 面板给出选项区
|
||||
let _ = app.on_page(pages::Message::SelectVersion("1.21.9".to_string()));
|
||||
let options = app
|
||||
.view
|
||||
.download
|
||||
.options
|
||||
.as_ref()
|
||||
.expect("选中版本之后要出现安装选项区");
|
||||
assert!(options.can_confirm, "原版 + 列表里的版本 = 可以装");
|
||||
assert_eq!(options.version, "1.21.9");
|
||||
|
||||
// 开装:host 回一条「下载中」→ 面板换成进度
|
||||
app.state.apply(AppEvent::Core(
|
||||
unlucky_presenter::dto::CoreEvent::InstallProgress(Some(
|
||||
unlucky_presenter::dto::InstallProgressView {
|
||||
version: "1.21.9".to_string(),
|
||||
stage: unlucky_presenter::dto::InstallStage::Downloading,
|
||||
files_done: 9,
|
||||
files_total: 512,
|
||||
..unlucky_presenter::dto::InstallProgressView::new()
|
||||
},
|
||||
)),
|
||||
));
|
||||
app.refresh_view();
|
||||
assert!(app.view.download.install.is_some(), "装的时候面板讲进度");
|
||||
assert!(app.view.download.options.is_none(), "进度与选项互斥");
|
||||
|
||||
// 取消:host 回 `InstallProgress(None)`(`CancelInstall` 的既有语义)
|
||||
let _ = app.on_page(pages::Message::CancelInstall);
|
||||
app.state.apply(AppEvent::Core(
|
||||
unlucky_presenter::dto::CoreEvent::InstallProgress(None),
|
||||
));
|
||||
app.refresh_view();
|
||||
assert!(app.view.download.install.is_none(), "取消后面板必须收回");
|
||||
let options = app
|
||||
.view
|
||||
.download
|
||||
.options
|
||||
.as_ref()
|
||||
.expect("取消后要回到安装选项区(未安装态),而不是只剩一句提示");
|
||||
assert_eq!(
|
||||
options.version, "1.21.9",
|
||||
"选中项要还在,用户不必重新点一遍"
|
||||
);
|
||||
assert!(options.can_confirm, "取消后要能立刻再装一次");
|
||||
}
|
||||
Reference in new issue
Block a user