From 4e52643b6aaab3ac9b1d05f253ca2e1ca5782c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AA=80=E8=BD=B6=E6=AD=A5=E6=A3=8B?= <57583509+Verplitic@users.noreply.github.com> Date: Tue, 14 Nov 2023 04:39:47 -0600 Subject: [PATCH] Rewrite the program (#1) * Rewrite the program * minor fix --- .gitignore | 6 +- Cargo.lock | 222 ++++++++++++++-------- Cargo.toml | 15 +- src/colorize.rs | 338 ++++++++++++++++++++++++++++++++++ src/logger.rs | 21 +++ src/main.rs | 94 ++++++---- src/runner/decompress.rs | 125 +++++++++---- src/runner/{lib.rs => mod.rs} | 39 ++-- src/runner/progress.rs | 6 +- 9 files changed, 695 insertions(+), 171 deletions(-) create mode 100644 src/colorize.rs create mode 100644 src/logger.rs rename src/runner/{lib.rs => mod.rs} (65%) diff --git a/.gitignore b/.gitignore index 1c221f9..5ead48c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ /target /.idea -.DS_Store \ No newline at end of file +/.vscode +.DS_Store + +/src_legacy +/test \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ff1610d..261ed64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.4" @@ -85,12 +100,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - [[package]] name = "block-buffer" version = "0.10.4" @@ -100,6 +109,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "byteorder" version = "1.4.3" @@ -143,6 +158,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "cipher" version = "0.4.4" @@ -199,17 +228,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "colored" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" -dependencies = [ - "is-terminal", - "lazy_static", - "windows-sys", -] - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -317,13 +335,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "errno" -version = "0.3.5" +name = "fern" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" dependencies = [ - "libc", - "windows-sys", + "log", ] [[package]] @@ -380,23 +397,35 @@ dependencies = [ ] [[package]] -name = "inout" -version = "0.1.3" +name = "iana-time-zone" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ - "generic-array", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "is-terminal" -version = "0.4.9" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", + "cc", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", ] [[package]] @@ -409,21 +438,14 @@ dependencies = [ ] [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "js-sys" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.148" @@ -431,10 +453,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] -name = "linux-raw-sys" -version = "0.4.10" +name = "log" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memoffset" @@ -460,7 +482,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ - "winapi 0.3.9", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", ] [[package]] @@ -560,20 +591,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "rustix" -version = "0.38.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" -dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", + "bitflags", ] [[package]] @@ -586,9 +604,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" name = "seal-updater" version = "0.1.2" dependencies = [ + "chrono", "clap", - "colored", + "fern", "flate2", + "log", "once_cell", "static_vcruntime", "sysinfo", @@ -680,7 +700,7 @@ dependencies = [ "ntapi", "once_cell", "rayon", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -696,13 +716,12 @@ dependencies = [ [[package]] name = "term_size" -version = "1.0.0-beta1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a17d8699e154863becdf18e4fd28bd0be27ca72856f54daf75c00f2566898f" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" dependencies = [ - "kernel32-sys", "libc", - "winapi 0.2.8", + "winapi", ] [[package]] @@ -747,10 +766,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "winapi" -version = "0.2.8" +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "winapi" @@ -762,12 +829,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -780,6 +841,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 37c45e3..7e192c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,17 +15,16 @@ panic = "abort" [dependencies] clap = { version = "4.4.6", features = ["derive"] } sysinfo = "0.29.10" - -# From 0.10.0, `zip` changes its APIs fundamentally. -zip = "0.6.6" +zip = "0.6.6" # From 0.10.0, `zip` changes its APIs fundamentally. tar = "0.4.40" flate2 = "1.0.27" - -# Terminal interaction -term_size = "1.0.0-beta1" -colored = "2.0.4" - +term_size = "0.3.2" once_cell = "1.18.0" +chrono = "0.4.31" + +# Logging +log = "0.4.20" +fern = "0.6.2" [build-dependencies] # For static links on Windows diff --git a/src/colorize.rs b/src/colorize.rs new file mode 100644 index 0000000..2c8ab05 --- /dev/null +++ b/src/colorize.rs @@ -0,0 +1,338 @@ +use once_cell::sync::Lazy; + +static WIN_TERM: Lazy = Lazy::new(|| std::env::var("WT_SESSION").is_ok()); + +pub struct ColoredString { + original: String, + codes: Vec, +} + +impl ColoredString { + pub fn new_with(s: &str, code: i32) -> Self { + ColoredString { + original: s.to_owned(), + codes: vec![code], + } + } + + fn push_color(&mut self, code: i32) { + self.codes.push(code); + } + + fn from_str(s: &str, code: i32) -> ColoredString { + ColoredString::new_with(s, code) + } +} + +impl std::fmt::Display for ColoredString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let supports_ansi = std::env::consts::OS != "windows" || *WIN_TERM; + if !supports_ansi { + write!(f, "{}", self.original) + } else { + let codes = self + .codes + .iter() + .map(|c| c.to_string()) + .collect::>() + .join(";"); + write!(f, "\x1b[{}m{}\x1b[0m", codes, self.original) + } + } +} + +pub trait Colorize { + fn black(self) -> ColoredString + where + Self: Sized; + fn red(self) -> ColoredString + where + Self: Sized; + fn green(self) -> ColoredString + where + Self: Sized; + fn yellow(self) -> ColoredString + where + Self: Sized; + fn blue(self) -> ColoredString + where + Self: Sized; + fn magenta(self) -> ColoredString + where + Self: Sized; + fn cyan(self) -> ColoredString + where + Self: Sized; + fn white(self) -> ColoredString + where + Self: Sized; + + fn on_black(self) -> ColoredString + where + Self: Sized; + fn on_red(self) -> ColoredString + where + Self: Sized; + fn on_green(self) -> ColoredString + where + Self: Sized; + fn on_yellow(self) -> ColoredString + where + Self: Sized; + fn on_blue(self) -> ColoredString + where + Self: Sized; + fn on_magenta(self) -> ColoredString + where + Self: Sized; + fn on_cyan(self) -> ColoredString + where + Self: Sized; + fn on_white(self) -> ColoredString + where + Self: Sized; +} + +impl<'a> Colorize for &'a str { + fn black(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 30) + } + + fn red(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 31) + } + + fn green(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 32) + } + + fn yellow(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 33) + } + + fn blue(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 34) + } + + fn magenta(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 35) + } + + fn cyan(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 36) + } + + fn white(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 37) + } + + fn on_black(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 40) + } + + fn on_red(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 41) + } + + fn on_green(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 42) + } + + fn on_yellow(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 43) + } + + fn on_blue(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 44) + } + + fn on_magenta(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 45) + } + + fn on_cyan(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 46) + } + + fn on_white(self) -> ColoredString + where + Self: Sized, + { + ColoredString::from_str(self, 47) + } +} + +impl Colorize for ColoredString { + fn black(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(30); + self + } + + fn red(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(31); + self + } + + fn green(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(32); + self + } + + fn yellow(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(33); + self + } + + fn blue(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(34); + self + } + + fn magenta(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(35); + self + } + + fn cyan(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(36); + self + } + + fn white(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(37); + self + } + + fn on_black(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(40); + self + } + + fn on_red(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(41); + self + } + + fn on_green(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(42); + self + } + + fn on_yellow(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(43); + self + } + + fn on_blue(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(44); + self + } + + fn on_magenta(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(45); + self + } + + fn on_cyan(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(46); + self + } + + fn on_white(mut self) -> ColoredString + where + Self: Sized, + { + self.push_color(47); + self + } +} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..9224902 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,21 @@ +pub fn init_logger() -> Result { + let time = chrono::Local::now().format("%F_%H%M%S").to_string(); + let file_name = format!("升级日志_{}.txt", time); + + fern::Dispatch::new() + .format(|out, msg, rec| { + out.finish(format_args!( + "{} [{}] {}:{} {}", + chrono::Local::now().format("%F %H:%M:%S%.3f"), + rec.level(), + rec.file().unwrap_or("unknown"), + rec.line().unwrap_or(0), + msg + )) + }) + .level(log::LevelFilter::Info) + .chain(fern::log_file(&file_name)?) + .apply()?; + + Ok(file_name) +} diff --git a/src/main.rs b/src/main.rs index 6f03006..a65a53e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,43 +1,72 @@ -use crate::global::{CMD_OPT, SEAL_EXE}; -use colored::Colorize; -use std::io::{stdin, Read, Write}; -use std::path::Path; use std::process::Command; -use std::thread; -use std::time::Duration; -use std::{io, process}; +use std::path::Path; + +use global::{CMD_OPT, SEAL_EXE}; + +use colorize::Colorize; +use log::{info, error}; + +use crate::logger::init_logger; mod cli; +mod colorize; mod global; -#[path = "runner/lib.rs"] -mod lib; +mod logger; +mod runner; fn main() { - let args = &CMD_OPT; - if args.verbose { - println!("{}", "VERBOSE mode on".yellow()); - println!("工作路径已被设定为: {}", args.cwd.yellow()) + let arg = &CMD_OPT; + if arg.verbose { + println!("{}", "Verbose mode turned on".yellow()); + println!("Working directory: {}", arg.cwd.yellow()); + } + + let ver = env!("CARGO_PKG_VERSION"); + println!( + "{}", + format!("SealDice 升级程序 v{} by 檀轶步棋", ver) + .black() + .on_yellow() + ); + + match init_logger() { + Ok(name) => println!("本次升级日志将被写入 {}", name.yellow()), + Err(e) => eprintln!("{}", format!("未能初始化升级日志: {}", e).red()), } - println!("{}", "SealDice 升级程序 by 檀轶步棋".black().on_yellow()); - if let Err(err) = lib::run_upgrade() { - println!("\n{}\n", format!("出现错误: {}", err).red()); + info!("开始更新流程"); + if let Err(e) = runner::upgrade() { + eprintln!("{}", format!("发生错误: {}", e).red()); exit_gracefully(1); } - io::stdout().flush().unwrap(); - run_command(&args.cwd); + info!("处理新文件并(如果可能)启动海豹"); + run_command(&arg.cwd); +} + +fn exit_gracefully(code: i32) { + if cfg!(windows) && code != 0 { + use std::io::Read; + println!("按回车键退出…"); + _ = std::io::stdin().read_exact(&mut [0u8]); + } + + std::process::exit(code); } #[cfg(target_family = "unix")] fn run_command(path: impl AsRef) { use std::os::unix::process::CommandExt; - + if CMD_OPT.verbose { println!( "Running `chmod` on {}", &path.as_ref().join(SEAL_EXE).to_string_lossy().on_yellow() ); + info!( + "运行 `chmod` 于 {}", + &path.as_ref().join(SEAL_EXE).to_string_lossy() + ); } let res = Command::new("chmod") .args(["+x", &path.as_ref().join(SEAL_EXE).to_string_lossy()]) @@ -48,14 +77,16 @@ fn run_command(path: impl AsRef) { let err = o.stderr; if err.len() > 0 { let err = String::from_utf8(err).unwrap_or_default(); - println!("from stderr: {}", err.on_red()); + eprintln!("From stderr: {}", err.on_red()); + error!("`chmod` 返回的错误: {}", err); } else { - println!("no error returned from stderr"); + eprintln!("No error returned from stderr"); } } } Err(err) => { - println!("\n{}\n", format!("出现错误: {}", err).red()); + eprintln!("\n{}\n", format!("出现错误: {}", err).red()); + error!("执行 `chmod` 出错: {}", err); exit_gracefully(1); } } @@ -67,11 +98,12 @@ fn run_command(path: impl AsRef) { println!("{}\n", "升级完毕,即将启动海豹核心…".black().on_yellow()); - thread::sleep(Duration::from_secs(2)); + std::thread::sleep(std::time::Duration::from_secs(2)); let err = Command::new(Path::new("./").join(SEAL_EXE)) .current_dir(path) .exec(); - println!("\n{}\n", format!("出现错误: {}", err).red()); + eprintln!("\n{}\n", format!("出现错误: {}", err).red()); + error!("启动核心出错: {}", err); exit_gracefully(1); } @@ -84,7 +116,7 @@ fn run_command(path: impl AsRef) { println!("{}\n", "升级完毕,即将启动海豹核心…".black().on_yellow()); - thread::sleep(Duration::from_secs(2)); + std::thread::sleep(std::time::Duration::from_secs(2)); if let Err(err) = Command::new("cmd") .current_dir(path) .args([ @@ -95,16 +127,8 @@ fn run_command(path: impl AsRef) { ]) .spawn() { - println!("\n{}\n", format!("出现错误: {}", err).red()); + eprintln!("\n{}\n", format!("出现错误: {}", err).red()); + error!("启动核心出错: {}", err); exit_gracefully(1); } } - -fn exit_gracefully(code: i32) { - if cfg!(windows) && code != 0 { - println!("按回车键退出…"); - _ = stdin().read_exact(&mut [0u8]); - } - - process::exit(code); -} diff --git a/src/runner/decompress.rs b/src/runner/decompress.rs index f2eaa47..406deab 100644 --- a/src/runner/decompress.rs +++ b/src/runner/decompress.rs @@ -1,18 +1,20 @@ -use crate::global::{CMD_OPT, UPD_NAME}; -use crate::lib::progress::ProgressBar; -use colored::Colorize; use flate2::read::GzDecoder; +use log::{error, info, warn}; use std::error::Error; -use std::fs::File; -use std::io::{Read, Seek, SeekFrom, Write}; +use std::fs::{self, File}; +use std::io::{self, Read, Write, Seek, SeekFrom}; use std::path::{Component, Components, Path, PathBuf}; -use std::{fs, io}; use zip::result::ZipError; use zip::ZipArchive; -struct ResettableArchive(File); +use crate::colorize::Colorize; +use crate::global::{CMD_OPT, UPD_NAME}; + +use super::progress; + +struct ReseekableArchive(File); -impl ResettableArchive { +impl ReseekableArchive { fn new(file: File) -> Self { Self(file) } @@ -31,21 +33,41 @@ impl ResettableArchive { } } -pub fn decompress(path: impl AsRef, target: impl AsRef) -> Result<(), Box> { - if path.as_ref().as_os_str().is_empty() { +pub(crate) fn decompress( + src: impl AsRef, + dst: impl AsRef, +) -> Result<(), Box> { + if src.as_ref().as_os_str().is_empty() { + error!("更新文件路径为空: {:?}", src.as_ref()); Err("指向更新文件路径为空")?; } - let file = File::open(path.as_ref())?; - if let Err(err) = unzip(file, target.as_ref()) { - if !matches!(err.downcast_ref(), Some(ZipError::InvalidArchive(_))) { - Err(err)?; + + let file = File::open(src.as_ref()).map_err(|e| { + error!("未能打开更新压缩包: {}", e); + e + })?; + + if let Err(e) = unzip(file, dst.as_ref()) { + if !matches!(e.downcast_ref(), Some(ZipError::InvalidArchive(_))) { + error!("解压缩失败: {}", e); + Err(e)?; } - let file = File::open(path.as_ref())?; - let mut archive = ResettableArchive::new(file); - // Get count - let count = archive.count()?; - untar(archive.archive(), target.as_ref(), count)?; + info!("尝试 ZIP 解压缩失败,开始 GZ 解压缩"); + + let file = File::open(src.as_ref()).map_err(|e| { + error!("未能打开更新压缩包: {}", e); + e + })?; + let mut archive = ReseekableArchive::new(file); + let count = archive + .count() + .map_err(|e| { + warn!("无法获取 GZ 压缩包的文件数量: {}", e); + e + }) + .unwrap_or(0); + untar(archive.archive(), dst.as_ref(), count)?; } Ok(()) @@ -55,19 +77,25 @@ fn unzip(file: File, target: &Path) -> Result<(), Box> { let mut archive = ZipArchive::new(file)?; let arc_len = archive.len(); - let mut progress_bar = ProgressBar::new(arc_len); + let mut progress_bar = progress::ProgressBar::new(arc_len); for i in 0..arc_len { - let mut zip_file = archive.by_index(i)?; + let mut zip_file = archive.by_index(i).map_err(|e| { + error!("获取 ZIP 文件 entry 时出现错误: {}", e); + e + })?; if CMD_OPT.verbose { - progress_bar.blackout(); + progress::ProgressBar::blackout(); println!(" {} {}", "decompressing:".yellow(), zip_file.name()); _ = io::stdout().flush(); } - // TODO: Trust and skip name check? let name = zip_file .enclosed_name() - .ok_or("文件名不安全,可能导致 zip slip")?; + .ok_or("文件名不安全,可能导致 zip slip") + .map_err(|e| { + error!("发现不安全的文件名,解压缩终止: {}", e); + e + })?; let dest = if name.to_string_lossy() != UPD_NAME { target.join(name) } else { @@ -93,21 +121,34 @@ fn untar( !normals.is_empty() }; - let mut progress_bar = ProgressBar::new(count); + let mut progress_bar = progress::ProgressBar::new(count); + + let entries = archive.entries().map_err(|e| { + error!("获取 GZ 文件 entry 时出现错误: {}", e); + e + })?; + for entry in entries { + let mut tar_file = entry.map_err(|e| { + error!("获取 GZ 文件 entry 时出现错误: {}", e); + e + })?; + let name = tar_file.path().map_err(|e| { + error!("获取文件名时出现错误: {}", e); + e + })?; - for entry in archive.entries()? { - let mut tar_file = entry?; - let name = tar_file.path()?; if CMD_OPT.verbose { - progress_bar.blackout(); + progress::ProgressBar::blackout(); println!(" {} {}", "reading:".yellow(), name.to_string_lossy()); _ = io::stdout().flush(); } progress_bar.progress(); + if !is_path_safe(name.components()) { - // TODO: Trust and skip name check? + error!("发现不安全的文件名,解压缩终止: {:?}", name); Err("文件名不安全,可能导致 slip")?; } + let dest = if name.to_string_lossy() != UPD_NAME { target.join(name) } else { @@ -121,14 +162,28 @@ fn untar( fn make_file(mut source: &mut impl Read, dest: &PathBuf) -> Result<(), Box> { if let Some(parent) = dest.parent() { if !parent.exists() { - fs::create_dir_all(parent)?; + fs::create_dir_all(parent).map_err(|e| { + error!("未能创建文件夹: {}", e); + e + })?; } } + if dest.is_dir() || dest.to_string_lossy().ends_with('/') { - fs::create_dir_all(dest)?; + fs::create_dir_all(dest).map_err(|e| { + error!("未能创建文件夹: {}", e); + e + })?; } else { - let mut out = File::create(dest)?; - io::copy(&mut source, &mut out)?; + let mut out = File::create(dest).map_err(|e| { + error!("未能创建文件: {}", e); + e + })?; + + io::copy(&mut source, &mut out).map_err(|e| { + error!("未能复制文件: {}", e); + e + })?; } Ok(()) -} +} \ No newline at end of file diff --git a/src/runner/lib.rs b/src/runner/mod.rs similarity index 65% rename from src/runner/lib.rs rename to src/runner/mod.rs index 003d57d..75c2f03 100644 --- a/src/runner/lib.rs +++ b/src/runner/mod.rs @@ -1,22 +1,22 @@ -use crate::global::{CMD_OPT, SEAL_EXE}; -use colored::Colorize; -use std::error::Error; -use std::path::Path; -use std::time::Duration; -use std::{fs, thread}; +use std::{error::Error, fs, path::Path}; + +use log::{info, warn}; use sysinfo::{Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt}; +use crate::{ + colorize::Colorize, + global::{CMD_OPT, SEAL_EXE}, +}; + mod decompress; mod progress; -pub fn run_upgrade() -> Result<(), Box> { +pub fn upgrade() -> Result<(), Box> { let args = &CMD_OPT; - let mut sys = System::new_with_specifics( - RefreshKind::new().with_processes(ProcessRefreshKind::everything()), - ); if args.pid != 0 { - println!("等待海豹主进程关闭…"); - wait_exit_pid(args.pid, &mut sys); + println!("等待海豹主进程关闭..."); + info!("等待 PID: {}", args.pid); + wait_proc(args.pid); } let seal_path = Path::new(&args.cwd); @@ -25,16 +25,24 @@ pub fn run_upgrade() -> Result<(), Box> { let old_name = format!("{}.old", SEAL_EXE); #[cfg(target_family = "unix")] let old_name = format!("{}_old", SEAL_EXE); - fs::rename(seal_path.join(SEAL_EXE), seal_path.join(old_name))?; + fs::rename(seal_path.join(SEAL_EXE), seal_path.join(old_name)).map_err(|e| { + warn!("未能备份旧文件: {}", e); + e + })?; } decompress::decompress(&args.upgrade, &args.cwd)?; println!("\r{}", "解压成功!".green()); + info!("成功解压 {}", args.upgrade); Ok(()) } -fn wait_exit_pid(pid: u32, sys: &mut System) { +fn wait_proc(pid: u32) { + let mut sys = System::new_with_specifics( + RefreshKind::new().with_processes(ProcessRefreshKind::everything()), + ); + loop { let result = sys.process(Pid::from_u32(pid)); if let Some(proc) = result { @@ -45,6 +53,7 @@ fn wait_exit_pid(pid: u32, sys: &mut System) { break; } sys.refresh_processes(); - thread::sleep(Duration::from_secs(1)); + std::thread::sleep(std::time::Duration::from_secs(1)); } } + diff --git a/src/runner/progress.rs b/src/runner/progress.rs index 2670566..837ed8c 100644 --- a/src/runner/progress.rs +++ b/src/runner/progress.rs @@ -28,7 +28,7 @@ impl ProgressBar { bar_width / 10 * 10 } - pub fn blackout(&self) { + pub fn blackout() { let width = match term_size::dimensions_stdout() { None => 80, Some((w, _)) => w, @@ -42,6 +42,10 @@ impl ProgressBar { } pub fn progress_by(&mut self, offset: i32) -> Result<(), Box> { + if self.max == 0 { + return Ok(()); + } + let new_progress = self.current as i32 + offset; if new_progress > self.max as i32 { Err(format!(