- host/install_processors:GUI 与 CLI 同序跑 processors(计划 → 依赖库 → 选 Java → 串行 java -cp), 进度经 HostTask::Install 回流(阶段文案 + 每步处理器名 + 文件/字节/速度/ETA),取消在阶段边界生效; - host/install_journal:安装过程追加到 .ULL/logs/install.log,失败文案带日志路径,不再静默失败; - host/install.rs:supported() 扩到原版 + Fabric/Quilt/Forge/NeoForge,删除 Forge/NeoForge 拒装路径 (仅剩 OptiFine/LiteLoader 两家 core 侧无管线者),spawn_install 统一持 Job 上下文; - 失败诊断:缺 Java / processor 非零退出(保留 processor_* 大类码与脱敏 stderr 尾部)/ 依赖库下漏 / 元数据缺失,均为结构化文案 + Toast + 装日志; - dto::InstallStage::Installing 文案改「生成版本」(同时覆盖写版本 JSON 与跑 installer 两段); - screenshot:新增 --screenshot-loader,Forge/NeoForge 中间态可离屏取图; - 测试 +14(138 全绿):supported() 语义、处理器阶段注入 fake 后端覆盖跳过/缺 Java/非零退出/ 依赖库失败/命令行构造/进度文案,另修旧用例(Forge 不再被门禁拒装)。
80 lines
2.8 KiB
Rust
80 lines
2.8 KiB
Rust
//! 安装过程的可回溯日志(`.ULL/logs/install.log`,一轮一行)。
|
||
//!
|
||
//! 起因(M45):Forge/NeoForge 装失败时,GUI 只有一行 Toast,processor 的 stderr 尾部
|
||
//! 与「哪一步挂了」在面板一关就没了。这里把安装过程按行追加到 `.ULL/logs/`
|
||
//! (与 `auth.jsonl` 同一分区),失败文案里带上日志路径,用户能自己回看。
|
||
//!
|
||
//! 三条纪律(与 auth 日志同源):
|
||
//! 1. **只追加**,不轮转、不截断(体积由「一行一条、只记诊断字段」控制);
|
||
//! 2. **只记诊断字段**:阶段、processor 坐标、退出码、已脱敏的 stderr 尾部(core 侧脱敏);
|
||
//! 3. **写盘尽力而为**:开不了文件就静默跳过,绝不让日志失败连累安装。
|
||
|
||
use std::io::Write;
|
||
use std::path::{Path, PathBuf};
|
||
|
||
use unlucky_core::launch::time;
|
||
|
||
use super::env::HostEnv;
|
||
|
||
/// 日志文件名(在 `.ULL/logs/` 下)。
|
||
pub const INSTALL_LOG_FILE: &str = "install.log";
|
||
|
||
/// 一轮安装的日志句柄(`Clone` 很便宜:只有一个路径)。
|
||
#[derive(Debug, Clone)]
|
||
pub struct Journal {
|
||
path: PathBuf,
|
||
}
|
||
|
||
impl Journal {
|
||
/// 按数据根打开(`logs/` 不存在就补建;建不出来只是没有日志)。
|
||
pub fn open(env: &HostEnv) -> Self {
|
||
let dir = env.layout.logs();
|
||
let _ = std::fs::create_dir_all(&dir);
|
||
Self {
|
||
path: dir.join(INSTALL_LOG_FILE),
|
||
}
|
||
}
|
||
|
||
/// 日志路径(失败文案里给用户看)。
|
||
pub fn path(&self) -> &Path {
|
||
&self.path
|
||
}
|
||
|
||
/// 追加一行(自动补 RFC3339 时间戳;写失败静默)。
|
||
pub fn line(&self, text: &str) {
|
||
let Ok(mut file) = std::fs::OpenOptions::new()
|
||
.create(true)
|
||
.append(true)
|
||
.open(&self.path)
|
||
else {
|
||
return;
|
||
};
|
||
let _ = writeln!(
|
||
file,
|
||
"[{}] {text}",
|
||
time::format_rfc3339_millis(time::now())
|
||
);
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn lines_are_appended_with_a_timestamp() {
|
||
let root = std::env::temp_dir().join(format!("ull-install-journal-{}", std::process::id()));
|
||
let _ = std::fs::remove_dir_all(&root);
|
||
let env = HostEnv::at_root(&root);
|
||
let journal = Journal::open(&env);
|
||
journal.line("第一行");
|
||
journal.line("第二行");
|
||
let text = std::fs::read_to_string(journal.path()).expect("日志存在");
|
||
assert!(text.contains("第一行") && text.contains("第二行"), "{text}");
|
||
assert_eq!(text.lines().count(), 2);
|
||
assert!(text.starts_with("[2"), "带时间戳:{text}");
|
||
assert_eq!(journal.path(), env.layout.logs().join(INSTALL_LOG_FILE));
|
||
std::fs::remove_dir_all(&root).ok();
|
||
}
|
||
}
|