fix(m161): 收更新键/按钮/矮窗口/启动错误/导入结果五笔债
① update.auto_check / update.channel 登记进 core setting schema(1.6.0)
- 新增 setting/keys/update.rs(两键 + UPDATE_CHANNELS,只允许 stable);
SettingSchema 加 with_all 供域键表批量登记。
- 新增 setting/update_prefs.rs:cache/update/prefs.json → launcher.json 的一次性迁移。
四条硬约束:源指纹闸门(幂等)、不丢 false、新值胜出、旧文件改名 .migrated 后不再被读;
主配置不可写时不动旧文件。收据 config/update-prefs-migration.json。
- presenter Runtime 建会话时迁一次并把结局留在 migration();prefs 字段换成
auto_check + 每次派发从主配置读 channel(不再硬编码 Channel::Stable)。
保留旧字段 auto_check 的真实来源(原有测试 cases 会断言)。
- CLI config get/set/dump 先跑同一次迁移(幂等),输出里给 migration 段与收据路径。
② presenter/dto/events.rs 的「本次会话不再弹提示条」注释改成真实语义
(记的是版本号:同版本压住、更高版本照弹、手动检查清掉)。
③ 检查/下载进行中按钮禁用:pages/update/busy.rs 的 busy/can_check/can_apply;
设置页两颗按钮改画禁用(不是消失),app 层 on_update_action 再挡一道
(忙碌时 CheckNow/Apply 不派发);新增两条用例(含「忙完恢回可点」)。
④ 内容库低高度:content_layout::MIN_BODY_HEIGHT=460 + BodyMode(按固定件+三整行算出来,
用例守着算式);frame::body_height 从 Ui::metrics 算正文可用高,pages::Model 带 window_height。
≥460 列表自己滚;<460 主列整列一个滚动条、列表按自然高度铺开。
⑤ 启动错误文案本地化:presenter 删掉 launch_summary / 「启动失败:」前缀 / 五条中文阶段前缀,
改发 LaunchFailureView{stage,detail};LaunchFinished 改带 exit_code/uptime_ms/failure,
Toast 由 app 层按 i18n key 现求(与 install_cancelled 同构)。InstancesFailed 拆成
InstancesFailedStage(列表单行)+ LaunchStageFailed(详情/Toast 全文)。
⑥ 导入结果块:ImportReportView 加 failure_stage(NotStarted/RolledBack/RollbackIncomplete,
由 core 的 rollback 机器位推出);标题、图标、底色三处分档,预检失败不再说「已回滚」。
顺带:六处注释/文档漂移(update.rs 的分工表与偏好落点、pack 的「已整包回滚」、
build_request 的 Errors、settings/update 的按钮禁用说明等),
以及为守 400 行把 update/cli-config/core-keys/presenter-dto/presenter-launch/i18n-golden
里贴线或越线的文件按模块拆开(纯搬移,行为不变)。
This commit is contained in:
1 parent
b2d9f3927f
commit
03741ecd2d
69 files changed
+2653
-707
No files matched your search
@@ -26,6 +26,7 @@ use crate::dto::{
|
||||
InstanceSummary,
|
||||
JavaPageView,
|
||||
LaunchArgsPatch,
|
||||
LaunchFailureView,
|
||||
LoaderKind,
|
||||
LogLine,
|
||||
LogOutcome,
|
||||
@@ -54,6 +55,11 @@ pub struct LiveLogBatch {
|
||||
}
|
||||
|
||||
/// 一次启动收尾(磁贴状态回写 + 日志页会话列表刷新)。
|
||||
///
|
||||
/// **不带人读散文**(M161):早先这里有一个 `summary: String`,内容是 presenter 拼的中文
|
||||
/// (`正常退出` / `崩溃(exit 1)` / `启动失败:找不到 Java`)。于是英文界面上弹出的是中文 Toast,
|
||||
/// 磁贴那行还叠成「Launch failed · 启动失败:…」。现在只给**结构**,文案由 app 层按 key 现求
|
||||
/// (与 `InstallCancelled` 同一条纪律:presenter 不依赖 `unlucky-i18n`)。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct LaunchFinished {
|
||||
/// 实例 id
|
||||
@@ -62,8 +68,13 @@ pub struct LaunchFinished {
|
||||
pub session: SessionId,
|
||||
/// 结局
|
||||
pub outcome: LogOutcome,
|
||||
/// 一句人读结论(`正常退出` / `崩溃(exit 1)` / `启动失败:…`)
|
||||
pub summary: String,
|
||||
/// 进程退出码(`None` = 进程没起来 / 平台没给)
|
||||
pub exit_code: Option<i32>,
|
||||
/// 从 spawn 到收尾的毫秒数(没起来时是 0)
|
||||
pub uptime_ms: u64,
|
||||
/// 失败发生在哪一步(`None` = 这一场没有装配/启动阶段的失败;
|
||||
/// 「跑起来又崩了」看 `outcome`)
|
||||
pub failure: Option<LaunchFailureView>,
|
||||
}
|
||||
|
||||
/// core 侧事件(不可变快照发布 + 事件流,01c §5 的三条通道之二)。
|
||||
@@ -248,9 +259,17 @@ pub enum AppCommand {
|
||||
CheckUpdate,
|
||||
/// 把当前候选下载下来、校验、替换本程序(结果经 [`CoreEvent::Update`] 回来)
|
||||
ApplyUpdate,
|
||||
/// 忽略这个版本(本次会话不再弹提示条;候选仍留在快照里,设置页可手动装)
|
||||
/// 忽略**这一个候选版本**(记的是版本号,不是「别再问我」)。
|
||||
///
|
||||
/// 语义三条(`UpdateView::skipped` 的契约,`dto::update` 的模块文档与用例守着):
|
||||
/// 同一次运行里同一个版本不再弹提示条;**更高的版本照弹**(0.2.3 被忽略不影响 0.2.4);
|
||||
/// 设置页的「立即检查」会清掉它。候选本身仍留在快照里,设置页可手动装。
|
||||
DismissUpdate,
|
||||
/// 开关「启动时静默检查」(写进更新域自己的偏好文件)
|
||||
/// 开关「启动时静默检查」。
|
||||
///
|
||||
/// 落点是主配置 `config/launcher.json` 的 `update.auto_check`(M161 从更新域自己的
|
||||
/// `cache/update/prefs.json` 迁过来的 schema 键)——`ull config get update.auto_check`
|
||||
/// 读到的是同一个值。
|
||||
SetUpdateAutoCheck(bool),
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use super::launch_failure::{LaunchFailureStage, LaunchFailureView};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use unlucky_core::game::LoaderKind as CoreLoaderKind;
|
||||
@@ -208,9 +210,15 @@ pub enum InstanceRuntime {
|
||||
pid: u32,
|
||||
},
|
||||
/// 上次启动失败(磁贴显示「重试」)
|
||||
///
|
||||
/// M161 之前这里只有一个 `reason: String`,而且里面已经写着「启动失败:…」——
|
||||
/// 界面再包一层 `InstancesFailed`(`Launch failed · {reason}`)就成了「英文前缀 + 中文句子」,
|
||||
/// 中文界面则是「启动失败 · 启动失败:…」。现在拆成「阶段(可本地化)+ 原文(不翻译)」。
|
||||
Failed {
|
||||
/// 失败原因(用户可见文案)
|
||||
reason: String,
|
||||
/// 失败阶段
|
||||
stage: LaunchFailureStage,
|
||||
/// 原始错误文本(不翻译)
|
||||
detail: String,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//! 启动失败的**结构**(M161:presenter 只给阶段 + 原文,文案由界面按 i18n key 现求)。
|
||||
//!
|
||||
//! 从 `dto/instances.rs` 拆出:那边贴着 400 行硬线,而这两个类型是「启动」这条线上的
|
||||
//! 独立一块(磁贴状态行、收尾事件、Toast 三处都读它)。
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 启动失败发生在**哪一步**(M161:界面文案按它选 i18n key,presenter 不写散文)。
|
||||
///
|
||||
/// 分档只在「用户该去做什么」不同的地方切,不按代码结构切:
|
||||
///
|
||||
/// | 档 | 意思 | 用户下一步 |
|
||||
/// |---|---|---|
|
||||
/// | [`Self::VersionManifest`] | `versions/<id>.json` 读不出来 / 解析不了 | 去版本管理重装这个版本 |
|
||||
/// | [`Self::Inheritance`] | 继承链断了(父版本缺失) | 同上(父版本缺了) |
|
||||
/// | [`Self::InstanceConfig`] | 实例设置文件读不出来 | 去实例页看一眼那个实例 |
|
||||
/// | [`Self::Isolation`] | 隔离目录派生失败(路径/权限) | 换隔离档位或修目录权限 |
|
||||
/// | [`Self::Identity`] | 账户装配失败(找不到账户 / 凭据过期) | 去账户页重新登录 |
|
||||
/// | [`Self::Spawn`] | 命令行装配完了但进程起不来 | 看 Java 与日志 |
|
||||
///
|
||||
/// 「跑起来了但崩了」不在这张表里:那是**结局**,由 `LogOutcome` 表达(三个变体各有 i18n key)。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum LaunchFailureStage {
|
||||
/// 版本 JSON 读不出来 / 解析不了
|
||||
VersionManifest,
|
||||
/// 继承链断裂
|
||||
Inheritance,
|
||||
/// 实例设置文件读不出来
|
||||
InstanceConfig,
|
||||
/// 隔离目录派生失败
|
||||
Isolation,
|
||||
/// 账户装配失败
|
||||
Identity,
|
||||
/// 进程起不来
|
||||
Spawn,
|
||||
}
|
||||
|
||||
impl LaunchFailureStage {
|
||||
/// 稳定 slug(日志 / 事件流 / 测试用;**不要改名**)。
|
||||
#[must_use]
|
||||
pub const fn slug(self) -> &'static str {
|
||||
match self {
|
||||
Self::VersionManifest => "version_manifest",
|
||||
Self::Inheritance => "inheritance",
|
||||
Self::InstanceConfig => "instance_config",
|
||||
Self::Isolation => "isolation",
|
||||
Self::Identity => "identity",
|
||||
Self::Spawn => "spawn",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 一次启动失败:**哪一步** + 那一步的原文。
|
||||
///
|
||||
/// `detail` 是当场的原始错误文本(core / io / 账户层给的),**故意不翻译**:里面有路径、
|
||||
/// 版本 id、HTTP 状态这类机器细节,翻成人话就丢了取证价值。要本地化的是「哪一步」
|
||||
/// ([`LaunchFailureStage`]),界面按它选词。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct LaunchFailureView {
|
||||
/// 失败阶段
|
||||
pub stage: LaunchFailureStage,
|
||||
/// 原始错误文本(不翻译)
|
||||
pub detail: String,
|
||||
}
|
||||
|
||||
impl LaunchFailureView {
|
||||
/// 建一条。
|
||||
pub fn new(stage: LaunchFailureStage, detail: impl Into<String>) -> Self {
|
||||
Self {
|
||||
stage,
|
||||
detail: detail.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ mod instances;
|
||||
mod java;
|
||||
// M157: 实例启动参数(启动参数区的读取视图 + 写回补丁)
|
||||
mod launch_args;
|
||||
mod launch_failure;
|
||||
mod logs;
|
||||
mod notices;
|
||||
// M113: import/export —— 整合包导入/导出的 DTO(M111/M112 管线的 IPC 面)
|
||||
@@ -45,8 +46,8 @@ pub use content::{
|
||||
pub use events::{AppCommand, AppEvent, Backend, CoreEvent, LaunchFinished, LiveLogBatch};
|
||||
pub use install::{derive_speed, human_bytes, InstallProgressView, InstallStage};
|
||||
pub use instances::{
|
||||
CreateRequest, InstanceId, InstanceRuntime, InstanceSummary, LaunchStage, LoaderInfo,
|
||||
LoaderKind, Progress,
|
||||
CreateRequest, InstanceId, InstanceRuntime, InstanceSummary, LaunchFailureStage,
|
||||
LaunchFailureView, LaunchStage, LoaderInfo, LoaderKind, Progress,
|
||||
};
|
||||
pub use java::{JavaChainOutcome, JavaChainStep, JavaPageView, JavaRuntimeView, JavaVendorView};
|
||||
pub use launch_args::{LaunchArgsPatch, LaunchArgsView};
|
||||
@@ -57,10 +58,10 @@ pub use pack::{
|
||||
ConflictChoiceView, ConflictDecisionView, ConflictDecisionsView, ConflictFileView,
|
||||
ConflictReasonView, ConflictSourceView, ExportClassView, ExportDoneView, ExportFileView,
|
||||
ExportPreviewView, ExportReasonView, ExportRequestView, ExportState, FileActionView,
|
||||
ImportConflictView, ImportErrorView, ImportProgressView, ImportReportView, ImportRequestView,
|
||||
ImportScanView, ImportStageView, ImportState, ImportStatusView, ImportedFileView,
|
||||
MissingFileView, MissingReasonView, PackCommand, PackEvent, PackFormatView, PackState,
|
||||
ProbeView,
|
||||
ImportConflictView, ImportErrorView, ImportFailureStageView, ImportProgressView,
|
||||
ImportReportView, ImportRequestView, ImportScanView, ImportStageView, ImportState,
|
||||
ImportStatusView, ImportedFileView, MissingFileView, MissingReasonView, PackCommand, PackEvent,
|
||||
PackFormatView, PackState, ProbeView,
|
||||
};
|
||||
pub use settings::{SettingControl, SettingGroupView, SettingRowView};
|
||||
// M159: 自更新
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
//! 1. **三态**([`ImportStatusView`]):`Partial` 不是 `Success`。把「缺了 3 个文件」说成成功,
|
||||
//! 用户会在启动后才发现缺依赖;
|
||||
//! 2. **容忍 / 致命的分野**:[`MissingFileView`] 是「搞不到某个文件」(实例可用),
|
||||
//! [`ImportErrorView`] 是「已整包回滚」(实例零残留)。UI 必须让这两类**视觉可区分**;
|
||||
//! [`ImportErrorView`] 是致命失败。UI 必须让这两类**视觉可区分**——并且致命那一类还要按
|
||||
//! [`ImportReportView::failure_stage`] 再分三档说(预检没过 / 已回滚 / 回滚没做完,M161);
|
||||
//! 3. **导出的三分类**([`ExportClassView`]):`Inline` 是「证明不了可被远程替代,原字节进
|
||||
//! `overrides/`」,`Skipped` 是「压根没进包」。`skipped > 0` 意味着包**不完整**。
|
||||
//!
|
||||
@@ -56,7 +57,7 @@ pub use export::{
|
||||
};
|
||||
pub use format::PackFormatView;
|
||||
pub use import::{
|
||||
FileActionView, ImportErrorView, ImportProgressView, ImportReportView, ImportRequestView,
|
||||
ImportStageView, ImportState, ImportStatusView, ImportedFileView, MissingFileView,
|
||||
MissingReasonView, ProbeView,
|
||||
FileActionView, ImportErrorView, ImportFailureStageView, ImportProgressView, ImportReportView,
|
||||
ImportRequestView, ImportStageView, ImportState, ImportStatusView, ImportedFileView,
|
||||
MissingFileView, MissingReasonView, ProbeView,
|
||||
};
|
||||
@@ -62,6 +62,43 @@ impl ImportStageView {
|
||||
}
|
||||
}
|
||||
|
||||
/// 致命失败的**阶段**(M161:`rolled_back` 一个人说不了两件事)。
|
||||
///
|
||||
/// core 的 `ImportReport::rolled_back` 语义是「目标目录已确认与导入前一致」——它同时覆盖
|
||||
/// 「回滚完整完成」和「**从未写过**目标目录」两种情形(见 `core::addon::import::pipeline` 的
|
||||
/// `record_rollback` / `abandoned`)。UI 拿一个 bool 判不出该说哪一句,于是预检失败也会被
|
||||
/// 说成「已回滚」,而实际上一个字节都没写。这个枚举把两种情形重新分开,并补上第三种
|
||||
/// (回滚有失败项,目标目录可能与导入前不同)。
|
||||
///
|
||||
/// 只在 `status == Failed` 时有值:成功/部分成功的报告没有「失败阶段」可言。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ImportFailureStageView {
|
||||
/// 预检/装配阶段就失败了:**目标目录一个字节都没写**(用户该去修包/修配置再试)。
|
||||
NotStarted,
|
||||
/// 已经写过目标目录,回滚**完整**完成(零残留)。
|
||||
RolledBack,
|
||||
/// 写过了,但回滚有失败项:目标目录可能与导入前不同(`warnings` 里逐条列了)。
|
||||
RollbackIncomplete,
|
||||
}
|
||||
|
||||
impl ImportFailureStageView {
|
||||
/// 稳定 slug(日志 / 测试用;**不要改名**)。
|
||||
#[must_use]
|
||||
pub const fn slug(self) -> &'static str {
|
||||
match self {
|
||||
Self::NotStarted => "not_started",
|
||||
Self::RolledBack => "rolled_back",
|
||||
Self::RollbackIncomplete => "rollback_incomplete",
|
||||
}
|
||||
}
|
||||
|
||||
/// 本次失败到底动没动目标目录。
|
||||
#[must_use]
|
||||
pub const fn touched_target(self) -> bool {
|
||||
!matches!(self, Self::NotStarted)
|
||||
}
|
||||
}
|
||||
|
||||
/// 导入三态(`Partial` **不是** `Success`)。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ImportStatusView {
|
||||
@@ -69,7 +106,11 @@ pub enum ImportStatusView {
|
||||
Success,
|
||||
/// 有缺文件或有冲突(内容不完整,但实例可用)。
|
||||
Partial,
|
||||
/// 致命失败,**已整包回滚**。
|
||||
/// 致命失败。
|
||||
///
|
||||
/// **「动没动过目标目录」不在这里**(M161):同一个 `Failed` 既可能是预检没过(一个字节都没写),
|
||||
/// 也可能是写完又回滚干净,还可能是回滚没做完——三种由
|
||||
/// [`ImportReportView::failure_stage`] 分开说,界面按它选标题与图标。
|
||||
Failed,
|
||||
}
|
||||
|
||||
@@ -243,7 +284,7 @@ pub struct MissingFileView {
|
||||
pub detail: String,
|
||||
}
|
||||
|
||||
/// 一次**已整包回滚**的致命失败。
|
||||
/// 一次致命失败(是不是「已整包回滚」看 [`ImportReportView::failure_stage`])。
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ImportErrorView {
|
||||
/// 稳定码(`pack_traversal` / `import_integrity` …,进日志与 `--json`)。
|
||||
@@ -292,7 +333,13 @@ pub struct ImportReportView {
|
||||
/// 本次写入的字节数。
|
||||
pub bytes_written: u64,
|
||||
/// 回滚是否完整(`false` = 目标目录可能与导入前不一致,逐条在 `warnings` 里)。
|
||||
///
|
||||
/// **注意**:`true` 有两种来源——「回滚完整完成」与「从未写过目标目录」(core 的
|
||||
/// `abandoned`)。要区分它们看 [`ImportReportView::failure_stage`];界面上的措辞
|
||||
/// **必须**按那个字段选(M161)。
|
||||
pub rolled_back: bool,
|
||||
/// 失败发生在哪一步(`None` = 这次不是致命失败)。
|
||||
pub failure_stage: Option<ImportFailureStageView>,
|
||||
}
|
||||
|
||||
impl ImportReportView {
|
||||
|
||||
Reference in new issue
Block a user