P1(阻断)`java.auto_install` 的描述与行为不一致——那句会进 AI 工具的 JSON Schema `description`,等于让助手对用户说一件实现刻意不做的事: - `crates/core/src/setting/keys.rs`:改成「缺 Java 时在 Java 页标出推荐版本并可一键安装; 不会自动下载」,去掉 `(D13 基准 JDK17)`。 - `SettingsJavaAutoInstallHint` 中英两侧各补一句显式否定(「不会自己开始下载 —— 点「安装」 才会下」/ "Nothing is downloaded on its own — you press Install."),标签从「自动准备」改成 「缺 JDK 时推荐一档」;金表同步。 - 新增守卫 `the_auto_install_description_says_it_never_downloads_by_itself`(描述必须含 「不会自动下载」、不含旧说法与内部编号)。 P2-1 内部决策编号印在界面上:`SettingsJavaDownloadBaseline` 的 `Baseline (D13)` / `基准版本(D13)` → `Recommended baseline` / `推荐基准版本`(金表同步)。另加两条目录级守卫 (`no_user_visible_string_mentions_an_internal_decision_id` / `no_user_visible_string_carries_markdown_backticks`);后者顺带清掉 M168 遗留的 `FontCjkStatusHint` 里的 `ui.font_cdn` 反引号——全仓最后一条。 P2-2 `ProgressEvent::SourceFailed.kind` 没有守卫:`engine_mock_test` - `reports_progress_events_and_errors_when_all_sources_fail` 的解构补 `kind`,断言 `ServerError`; - 新增 `a_404_is_classified_as_not_found_and_never_cools_the_source_down`(404 → `NotFound`, 与「源坏了」分开)。 P2-3 交付任务书那句「(默认值可改)」:新增 `download.gh_proxy`(空 = 跟随内置默认 / `off` = 关 / 其它 = 覆盖前缀),`engine_with` 是它的真消费者(改 `RegionProfile`,因为 `gh_proxy_candidates` 读的就是档案里的前缀);Java 页不画 UI(归 M193)。**内置默认值没动**。 schema minor 1.7.0 → 1.8.0(新键必须推 minor),三处硬编码 minor 的用例同步。 另:真下载用例改成 panic 也清理(复审顺带项 7④);`downloads/refusal.rs` 的「四档」改「五档」。
119 lines
5.4 KiB
Rust
119 lines
5.4 KiB
Rust
//! 「下载一个 JDK」的 App 层落点(M197)。
|
||
//!
|
||
//! 三件事:
|
||
//!
|
||
//! | 函数 | 谁调 | 干什么 |
|
||
//! |---|---|---|
|
||
//! | [`App::start_java_download`] | `Download(major)` 消息 | 本地判据(目录/承接方/落点/是否已在跑)→ 起后台作业 |
|
||
//! | [`App::hydrate_java_downloads`] | 心跳那一拍(经 `hydrate_java_scan`) | 把作业快照灌成视图差量;**刚成功**时重扫 + 按 `java.auto_install` 决定要不要设为默认 |
|
||
//!
|
||
//! ## 为什么轮询挂在 `hydrate_java_scan` 上
|
||
//!
|
||
//! M175 的扫描已经是「后台线程 + 快照 + 心跳比对」的形状,`app/update/clock.rs` 每拍只调
|
||
//! `hydrate_java_scan()` 这一个入口。下载作业是同一族东西(后台线程 + 快照),所以复用那一拍,
|
||
//! `clock.rs` 一个字节都不用改——代价是**下载期间进度按 1 Hz 刷新**(帧时钟的判据在
|
||
//! `app/subscription.rs` 的 `needs_frames`,那是本 mission scope 之外的文件)。百分比一秒动一次
|
||
//! 对一次 100 MB 级的下载是够的;要紧的是它**会动**,而不是每秒动 60 次。
|
||
//!
|
||
//! ## 装完之后为什么要重扫
|
||
//!
|
||
//! 列表里那一行「已安装:…」来自**真扫盘的结论**(M175 的 `ScanSlot`),不是另存的安装记录。
|
||
//! 装完不重扫,界面上就会出现「下载成功」与「列表里没有它」并存——正是本仓最忌讳的那种自相矛盾。
|
||
|
||
use iced::Task;
|
||
|
||
use crate::app::message::Message;
|
||
use crate::app::state::App;
|
||
use crate::pages;
|
||
|
||
use unlucky_core::game::java::catalog;
|
||
|
||
use crate::pages::settings::java::{prefs_store, store};
|
||
|
||
impl App {
|
||
/// 下载并安装某个大版本(`Download(major)` 的落点)。
|
||
///
|
||
/// 四道本地判据,任何一道不过都在下载区顶上写清原因(点了没反应是禁止的):
|
||
/// ① 目录里有这个大版本;② 没有别的作业在跑;③ 有可写的设置文件与能解析的落点;
|
||
/// ④ 这一档在这台机器上真有一家发行版承接(纯判据,不联网)。
|
||
pub(super) fn start_java_download(&mut self, major: u32) {
|
||
use pages::settings::java::downloads::{gh_proxy_of_config, Refusal, Request};
|
||
|
||
self.settings.java.download_notice = None;
|
||
self.settings.java.notice = None;
|
||
self.view_dirty = true;
|
||
|
||
if catalog::find(major).is_none() {
|
||
self.settings.java.download_notice = Some(Refusal::UnknownMajor);
|
||
return;
|
||
}
|
||
if self.settings.java.downloads.busy() {
|
||
self.settings.java.download_notice = Some(Refusal::Busy);
|
||
return;
|
||
}
|
||
let Some(config) = store::config_of(&self.bridge) else {
|
||
self.settings.java.download_notice = Some(Refusal::NoStorage);
|
||
return;
|
||
};
|
||
let priority = self.settings.java.priority_vendors();
|
||
let domestic = self.settings.java.effective.domestic;
|
||
if catalog::vendors_for(major, &priority, domestic).is_empty() {
|
||
self.settings.java.download_notice = Some(Refusal::NoVendor);
|
||
return;
|
||
}
|
||
let saved_root = self.settings.java.prefs.saved_install_root.clone();
|
||
let Some(cache_root) = prefs_store::cache_root(&config) else {
|
||
self.settings.java.download_notice = Some(Refusal::NoStorage);
|
||
return;
|
||
};
|
||
let Some(install_root) = prefs_store::install_root(&config, &saved_root) else {
|
||
// 有设置文件但解析不出落点(standard 模式读不到系统目录):说这一句,
|
||
// 而不是含糊的「没有可用的设置文件」。
|
||
self.settings.java.download_notice = Some(Refusal::RootUnresolved);
|
||
return;
|
||
};
|
||
let started = self.settings.java.downloads.start(Request {
|
||
major,
|
||
cache_root,
|
||
install_root,
|
||
priority,
|
||
// `download.gh_proxy`(M197 的任务书那句「默认值可改」):空 = 跟随内置默认,
|
||
// `off` = 关,其余 = 覆盖前缀。Java 页不画这一项的 UI(归 M193 的下载设置页)。
|
||
gh_proxy: gh_proxy_of_config(&config),
|
||
});
|
||
if !started {
|
||
self.settings.java.download_notice = Some(Refusal::Busy);
|
||
}
|
||
}
|
||
|
||
/// 把作业快照灌成视图差量(心跳那一拍调)。返回是否变了。
|
||
pub(in crate::app) fn hydrate_java_downloads(&mut self) -> bool {
|
||
let report = self.settings.java.downloads.report();
|
||
if report == self.settings.java.download_seen {
|
||
return false;
|
||
}
|
||
let fresh = report
|
||
.success
|
||
.clone()
|
||
.filter(|success| self.settings.java.download_seen.success.as_ref() != Some(success));
|
||
self.settings.java.download_seen = report;
|
||
if let Some(success) = fresh {
|
||
// 新装上的一份:先重扫(列表要出现它),再按 `java.auto_install` 决定默认值。
|
||
let _ = self.start_java_scan();
|
||
if self.settings.java.prefs.saved_auto_install
|
||
&& self.settings.java.selected.trim().is_empty()
|
||
{
|
||
self.select_java_default(&success.binary);
|
||
}
|
||
}
|
||
self.view_dirty = true;
|
||
true
|
||
}
|
||
}
|
||
|
||
/// 消息→落点的那一层(`java.rs` 的 match 里一行转发)。
|
||
pub(super) fn on_download(app: &mut App, major: u32) -> Task<Message> {
|
||
app.start_java_download(major);
|
||
Task::none()
|
||
}
|