From 1c7c48a41175b3339b9fad98ddc48d7658c0c457 Mon Sep 17 00:00:00 2001 From: Kate Date: Wed, 26 Jun 2024 08:11:53 +0800 Subject: [PATCH 01/65] Update README.md --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6524010..fcf51e6 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Requirements - Recommended to use [rustup](https://rustup.rs/) to install. - x86-64/AMD64 Processor ([see notes](#notes-binaries)) - OpenSSL v3 (also required on deployments) +- fltk ([please read "FLTK Linux Dependencies"](#fltk-linux-dependencies)) + - (Optional) `fluid` for creating `.fl` files. - **Following requirements are only required for testing** - Steam Installed - Source SDK Base 2013 Multiplayer ([install](steam://instal/243750)) @@ -67,4 +69,48 @@ All the bundled/embedded binaries are for x86-64/AMD64 systems. We only support Please do not make any PRs to remove the embedded executables in favor of downloading. Some users would like to use this application offline, or they may have unreliable internet. -Linux Systems not using glibc have not been tested. \ No newline at end of file +Linux Systems not using glibc have not been tested. + +### FLTK Linux Dependencies +When building `beans-rs`, some dependencies are required to build it since we need the build dependencies for fltk. + +If your distribution is not listed (or the instructions are broken), please look at [`README.unix.txt` in the fltk repo.](https://github.com/fltk/fltk/blob/master/README.Unix.txt). + +#### Debian-based +This includes and Linux Distribution that is based off Debian or Ubuntu. Like; +- [Ubuntu](https://ubuntu.com/), +- [Debian](https://www.debian.org/), +- [Linux Mint](https://www.linuxmint.com/), +- [Zorin OS](https://zorin.com/os/), +- [Pop!_OS](https://pop.system76.com/) + +```bash +sudo apt update; +sudo apt-get install -y \ + g++ \ + gdb \ + git \ + make \ + cmake \ + autoconf \ + libx11-dev \ + libglu1-mesa-dev \ + libxft-dev \ + libxcursor-dev \ + libasound2-dev \ + freeglut3-dev \ + libcairo2-dev \ + libfontconfig1-dev \ + libglew-dev \ + libjpeg-dev \ + libpng-dev \ + libpango1.0-dev \ + libxinerama-dev; +``` + +#### Fedora +```bash +sudo yum groupinstall -y "Development Tools" +sudo yum groupinstall -y "X Software Development" +sudo yum groupinstall -y "C Development Tools and Libraries" +``` \ No newline at end of file From 1fe64cb300204710afb2855a7b28d1efa57cddd8 Mon Sep 17 00:00:00 2001 From: Kate Date: Wed, 26 Jun 2024 08:12:05 +0800 Subject: [PATCH 02/65] Update dependencies for fltk --- Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 4b09bf0..388479e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,12 @@ colored = "2.1.0" sentry-log = "0.34.0" chrono = "0.4.38" +fltk = { version = "1.4.30", features = ["fltk-bundled"] } +fltk-theme = "0.7.2" + +[build-dependencies] +fl2rust = "0.5.19" + [target.'cfg(target_os = "windows")'.dependencies] winconsole = { version = "0.11.1", features = ["window"] } winreg = "0.52.0" From 606cb37d8bc97c0f74f9c72d5841fc9728798024 Mon Sep 17 00:00:00 2001 From: Kate Date: Wed, 26 Jun 2024 08:12:25 +0800 Subject: [PATCH 03/65] Update build.rs for fltk --- build.rs | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/build.rs b/build.rs index 2cfaf2c..151f3a2 100644 --- a/build.rs +++ b/build.rs @@ -2,6 +2,7 @@ use std::{env, io}; +use std::path::PathBuf; #[cfg(target_os = "windows")] use winres::WindowsResource; #[allow(unused_macros)] @@ -12,8 +13,35 @@ macro_rules! print { } pub const OVERRIDE_ICON_LOCATION: Option<&'static str> = option_env!("ICON_LOCATION"); pub const RUST_FLAGS: Option<&'static str> = option_env!("RUSTFLAGS"); + +fn main() { + windows_icon().expect("Failed to embed icon"); + fltk().expect("Failed to build fltk files"); +} + +/// generate files for fltk ui stuff +fn fltk() -> Result<(), BuildError> { + println!("cargo:rerun-if-changed=src/gui/shared_ui.fl"); + let g = fl2rust::Generator::default(); + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + if let Err(e) = g.in_out("src/gui/shared_ui.fl", out_path.join("shared_ui.rs").to_str().unwrap()) { + return Err(BuildError::FLTK(format!("Failed to build shared_ui.fl {:#?}", e))); + } + + Ok(()) +} + +/// check if a location exists +#[allow(dead_code)] +fn path_exists(path: String) -> bool +{ + let p = std::path::Path::new(path.as_str()); + return p.exists(); +} + +/// set the icon to `icon.ico` when building for windows #[cfg(target_os = "windows")] -fn main() -> io::Result<()> { +fn windows_icon() -> Result<(), BuildError> { let icon_location = OVERRIDE_ICON_LOCATION.unwrap_or("icon.ico"); if env::var_os("CARGO_CFG_WINDOWS").is_some() { if !path_exists(icon_location.to_string()) @@ -33,13 +61,19 @@ fn main() -> io::Result<()> { } Ok(()) } +/// ignored since icon handling is done by fltk on non-windows #[cfg(not(target_os = "windows"))] -fn main() -> io::Result<()> { +fn windows_icon() -> Result<(), BuildError> { Ok(()) } -#[allow(dead_code)] -fn path_exists(path: String) -> bool -{ - let p = std::path::Path::new(path.as_str()); - return p.exists(); -} \ No newline at end of file + +#[derive(Debug)] +pub enum BuildError { + IO(io::Error), + FLTK(String) +} +impl From for BuildError { + fn from (e: io::Error) -> Self { + BuildError::IO(e) + } +} From 95a3ecec12dd11e7d475a4ad0661c2b9ff911b3a Mon Sep 17 00:00:00 2001 From: Kate Date: Wed, 26 Jun 2024 08:12:53 +0800 Subject: [PATCH 04/65] Add structure for fltk dialog (build will break btw) --- src/gui/dialog.rs | 40 +++++++++++++++++++++++++++ src/gui/mod.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++ src/gui/shared_ui.rs | 7 +++++ src/lib.rs | 1 + 4 files changed, 112 insertions(+) create mode 100644 src/gui/dialog.rs create mode 100644 src/gui/mod.rs create mode 100644 src/gui/shared_ui.rs diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs new file mode 100644 index 0000000..cab1612 --- /dev/null +++ b/src/gui/dialog.rs @@ -0,0 +1,40 @@ +use fltk::{*, prelude::*}; +use crate::gui; +use crate::gui::GUIAppStatus; +use crate::gui::shared_ui::GenericDialog; + +pub fn run(title: &str, label: &str) { + let app = app::App::default().with_scheme(app::AppScheme::Gtk); + gui::apply_app_scheme(); + let (send_action, receive_action) = app::channel::(); + let mut ui = GenericDialog::make_window(); + + ui.win.set_label(title); + ui.label.set_label(label); + ui.btn_ok.set_size(70, 24); + ui.btn_ok.emit(send_action, GUIAppStatus::Quit); + + let (label_w, label_h) = ui.label.measure_label(); + ui.win.set_size( + 25 + label_w + 25, + 10 + label_h + 5 + ui.btn_ok.height() + 5 + ); + + gui::window_centre_screen(&mut ui.win); + ui.win.handle(move |w, ev| match ev { + fltk::enums::Event::Resize => { + let height = w.height(); + ui.btn_ok.set_pos(25, height - 24 - 5); + ui.btn_ok.set_size(70, 24); + let (lw, lh) = ui.label.measure_label(); + if w.width() > lw+50 { + w.set_size(lw+50, 10+lh+5+ ui.btn_ok.height() + 5); + } + true + }, + _ => false + }); + ui.win.make_resizable(true); + ui.win.show(); + gui::wait_for_quit(&app, &receive_action); +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs new file mode 100644 index 0000000..e70d78d --- /dev/null +++ b/src/gui/mod.rs @@ -0,0 +1,64 @@ +use fltk::{*, prelude::*}; +use fltk::app::Receiver; +use fltk::window::Window; +use fltk_theme::{color_themes, ColorTheme}; + +pub(crate) mod shared_ui; +pub mod dialog; + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] +pub enum GUIAppStatus { + Update, + Quit, + + UnknownStatus, + + BtnOk, + BtnCancel, + BtnAbort, + BtnRetry, + BtnIgnore, + BtnYes, + BtnNo, + BtnTryAgain, + BtnContinue +} + +/// Make the `window` provided the in be the center of the current screen. +pub fn window_centre_screen(window: &mut Window) { + let (sx, sy) = app::screen_coords(); + let width = window.width(); + let height = window.height(); + let (mut x, mut y) = app::screen_size().clone(); + x -= width.clone() as f64; + y -= height.clone() as f64; + window.resize(((x / 2.0) as i32) + sx, ((y / 2.0) as i32) + sy, width, height); +} +/// Get the X and Y position of the center of the current screen. +pub fn get_center_screen() -> (i32, i32) { + let (px, py) = app::screen_coords(); + let (sw, sh) = app::screen_size(); + return (((sw / 2.0) as i32) + px, ((sh / 2.0) as i32) + py); +} + +/// Ensure that a window has a fixed width & height, and that it will appear in the centre of the +/// current screen. +pub fn window_ensure(win: &mut Window, width: i32, height: i32) { + window_centre_screen(win); + win.handle(move |w, ev| match ev { + fltk::enums::Event::Resize => { + if w.width() > width || w.height() > height { + w.set_size(width, height); + } + true + }, + _ => false + }); + win.make_resizable(false); + win.show(); +} +pub fn apply_app_scheme() { + let theme = ColorTheme::new(color_themes::DARK_THEME); + theme.apply(); +} diff --git a/src/gui/shared_ui.rs b/src/gui/shared_ui.rs new file mode 100644 index 0000000..c14fa36 --- /dev/null +++ b/src/gui/shared_ui.rs @@ -0,0 +1,7 @@ +#![allow(unused_variables)] +#![allow(unused_mut)] +#![allow(unused_imports)] +#![allow(dead_code)] +#![allow(clippy::needless_update)] + +include!(concat!(env!("OUT_DIR"), "/shared_ui.rs")); diff --git a/src/lib.rs b/src/lib.rs index 1fd11c0..c3825d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub mod butler; pub mod flags; pub mod appvar; pub mod logger; +pub mod gui; /// NOTE do not change, fetches from the version of beans-rs on build pub const VERSION: &str = env!("CARGO_PKG_VERSION"); From 575a78cabd94d567424d80225dd30105683acd8a Mon Sep 17 00:00:00 2001 From: Kate Date: Wed, 26 Jun 2024 08:14:08 +0800 Subject: [PATCH 05/65] Add shared_ui.fl --- src/gui/shared_ui.fl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/gui/shared_ui.fl diff --git a/src/gui/shared_ui.fl b/src/gui/shared_ui.fl new file mode 100644 index 0000000..716a2ec --- /dev/null +++ b/src/gui/shared_ui.fl @@ -0,0 +1,21 @@ +# data file for the Fltk User Interface Designer (fluid) +version 1.0308 +header_name {.h} +code_name {.cxx} +class GenericDialog {open +} { + Function {make_window()} {open + } { + Fl_Window win {open + xywh {376 147 500 150} type Double visible + } { + Fl_Text_Display label { + xywh {25 10 0 0} box NO_BOX align 11 + } + Fl_Button btn_ok { + label Ok + xywh {25 121 70 24} + } + } + } +} From 8264887276b40c32444287bd1010762f486997f7 Mon Sep 17 00:00:00 2001 From: Kate Date: Fri, 28 Jun 2024 08:13:31 +0800 Subject: [PATCH 06/65] Use gui::dialog instead of native_dialog --- src/main.rs | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 158bd83..f7792bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,6 +62,7 @@ fn init_panic_handle() if let Some(m) = info.message() { x = format!("{:#?}", m); } + log!("[panic] Fatal error!\n{:#?}", x); custom_panic_handle(x); debug!("[panic::set_hook] calling sentry_panic::panic_handler"); sentry::integrations::panic::panic_handler(&info); @@ -82,27 +83,13 @@ fn fix_msgbox_txt(txt: String) -> String { fn custom_panic_handle(msg: String) { unsafe { - if beans_rs::PAUSE_ONCE_DONE { - let mut txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg); - txt = fix_msgbox_txt(txt); - std::thread::spawn(move || { - - let d = native_dialog::MessageDialog::new() - .set_type(native_dialog::MessageType::Error) - .set_title("beans - fatal error!") - .set_text(&txt) - .show_alert(); - if let Err(e) = d { - sentry::capture_error(&e); - eprintln!("Failed to show MessageDialog {:#?}", e); - eprintln!("[msgbox_panic] Come on, we failed to show a messagebox? Well, the error has been reported and we're on it."); - eprintln!("[msgbox_panic] PLEASE report this to kate@dariox.club with as much info as possible <3"); - } - }); - } else { - info!("This error has been reported to the developers"); + if beans_rs::PAUSE_ONCE_DONE == false { + return; } } + let mut txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg); + txt = fix_msgbox_txt(txt); + beans_rs::gui::dialog::run("beans - Fatal Error!", txt.as_str()); } /// should called once the logic flow is done! /// will call `helper::get_input` when `PAUSE_ONCE_DONE` is `true`. From a6201dfa2378fada7484512ef00749f357e3db8c Mon Sep 17 00:00:00 2001 From: Kate Date: Fri, 28 Jun 2024 08:16:35 +0800 Subject: [PATCH 07/65] Add function wait_for_quit --- src/gui/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index e70d78d..53c59fd 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -62,3 +62,17 @@ pub fn apply_app_scheme() { let theme = ColorTheme::new(color_themes::DARK_THEME); theme.apply(); } + +pub fn wait_for_quit(app: &app::App, receive_action: &Receiver) { + while app.wait() { + if let Some(action) = receive_action.recv() { + match action { + GUIAppStatus::Quit => { + unsafe { crate::PAUSE_ONCE_DONE = false; } + app.quit(); + }, + _ => {} + } + } + } +} \ No newline at end of file From 9387f8a777769d3e678e067493595593f6a2f072 Mon Sep 17 00:00:00 2001 From: Kate Date: Fri, 28 Jun 2024 08:17:12 +0800 Subject: [PATCH 08/65] Fix newlines on text box --- src/main.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index f7792bf..f1668eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,7 +62,7 @@ fn init_panic_handle() if let Some(m) = info.message() { x = format!("{:#?}", m); } - log!("[panic] Fatal error!\n{:#?}", x); + info!("[panic] Fatal error!\n{:#?}", x); custom_panic_handle(x); debug!("[panic::set_hook] calling sentry_panic::panic_handler"); sentry::integrations::panic::panic_handler(&info); @@ -72,14 +72,6 @@ fn init_panic_handle() logic_done(); })); } -#[cfg(target_os = "windows")] -fn fix_msgbox_txt(txt: String) -> String { - txt.replace("\\n", "\r\n") -} -#[cfg(not(target_os = "windows"))] -fn fix_msgbox_txt(txt: String) -> String { - txt -} fn custom_panic_handle(msg: String) { unsafe { @@ -87,8 +79,7 @@ fn custom_panic_handle(msg: String) return; } } - let mut txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg); - txt = fix_msgbox_txt(txt); + let mut txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg).replace("\\n", "\n"); beans_rs::gui::dialog::run("beans - Fatal Error!", txt.as_str()); } /// should called once the logic flow is done! From 3d10ec500ba949a478d74f5e840426f5aa1d267f Mon Sep 17 00:00:00 2001 From: Kate Date: Fri, 28 Jun 2024 12:57:25 +0800 Subject: [PATCH 09/65] Add function has_gui_support --- src/lib.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c3825d6..34aeb32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,35 @@ pub fn data_dir() -> String format!("{}{}{}", PATH_SEP, av.mod_info.sourcemod_name, PATH_SEP) } +pub fn has_gui_support() -> bool +{ + unsafe { + if PAUSE_ONCE_DONE == false { + return false; + } + } + + match std::env::consts::OS { + "windows" => true, + "macos" => true, + "linux" => { + if helper::has_env_var("DISPLAY".to_string()) { + return true; + } + if let Some(x) = helper::try_get_env_var("XDG_SESSION_DESKTOP".to_string()) { + if x.len() >= 3usize { + return true; + } + } + return false; + }, + _ => { + log::warn!("Unsupported platform for GUI {}", std::env::consts::OS); + false + } + } +} + #[cfg(not(target_os = "windows"))] pub const STAGING_DIR: &str = "/butler-staging"; #[cfg(target_os = "windows")] From a9552532f4b88322c60503a667f1477c57de2a52 Mon Sep 17 00:00:00 2001 From: Kate Date: Fri, 28 Jun 2024 12:57:36 +0800 Subject: [PATCH 10/65] Fix show_msgbox_error --- src/main.rs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index f1668eb..adeae17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,7 +79,7 @@ fn custom_panic_handle(msg: String) return; } } - let mut txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg).replace("\\n", "\n"); + let txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg).replace("\\n", "\n"); beans_rs::gui::dialog::run("beans - Fatal Error!", txt.as_str()); } /// should called once the logic flow is done! @@ -389,21 +389,8 @@ impl Launcher } } fn show_msgbox_error(text: String) { - unsafe { - if beans_rs::PAUSE_ONCE_DONE { - std::thread::spawn(move || { - let d = native_dialog::MessageDialog::new() - .set_type(native_dialog::MessageType::Error) - .set_title("beans - fatal error!") - .set_text(&format!("{}", fix_msgbox_txt(text))) - .show_alert(); - if let Err(e) = d { - sentry::capture_error(&e); - eprintln!("Failed to show MessageDialog {:#?}", e); - } - }); - } - } + let t = text.replace("\\n", "\n"); + beans_rs::gui::dialog::run("beans - Fatal Error", t.as_str()); } From 9d6b9804a1bb13953bb16dd1bbc995ce4e84297b Mon Sep 17 00:00:00 2001 From: Kate Date: Fri, 28 Jun 2024 12:57:49 +0800 Subject: [PATCH 11/65] Add functions to check for environment variables --- src/helper/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 6793184..2f0b718 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -516,4 +516,21 @@ pub struct GithubReleaseItem pub html_url: String, pub draft: bool, pub prerelease: bool +} + +pub fn has_env_var(target_key: String) -> bool +{ + if let Some(x) = try_get_env_var(target_key) { + return x.len() > 1; + } + return false; +} +pub fn try_get_env_var(target_key: String) -> Option +{ + for (key, value) in std::env::vars().into_iter() { + if key == target_key { + return Some(value); + } + } + return None; } \ No newline at end of file From ff20513302c6f5bdfef5fef591305e54ff2525dd Mon Sep 17 00:00:00 2001 From: Kate Date: Fri, 28 Jun 2024 12:58:00 +0800 Subject: [PATCH 12/65] Don't show dialog when has_gui_support() is false --- src/gui/dialog.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index cab1612..312cee5 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -4,6 +4,12 @@ use crate::gui::GUIAppStatus; use crate::gui::shared_ui::GenericDialog; pub fn run(title: &str, label: &str) { + if crate::has_gui_support() == false { + println!("============ {} ============", title); + println!("{}", label); + return; + } + let app = app::App::default().with_scheme(app::AppScheme::Gtk); gui::apply_app_scheme(); let (send_action, receive_action) = app::channel::(); From 61b69c9fcb3579358fff13702a055f15db71ea58 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 30 Jun 2024 13:55:54 +0800 Subject: [PATCH 13/65] Add more comments --- src/helper/mod.rs | 3 +++ src/lib.rs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 2f0b718..e7eb528 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -518,6 +518,7 @@ pub struct GithubReleaseItem pub prerelease: bool } +/// Return `true` when `try_get_env_var` returns Some with a length greater than `1`. pub fn has_env_var(target_key: String) -> bool { if let Some(x) = try_get_env_var(target_key) { @@ -525,6 +526,8 @@ pub fn has_env_var(target_key: String) -> bool } return false; } +/// Try and get a value from `std::env::vars()` +/// Will return `None` when not found pub fn try_get_env_var(target_key: String) -> Option { for (key, value) in std::env::vars().into_iter() { diff --git a/src/lib.rs b/src/lib.rs index 34aeb32..afabed6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,12 @@ pub fn data_dir() -> String format!("{}{}{}", PATH_SEP, av.mod_info.sourcemod_name, PATH_SEP) } +/// Check if we have GUI support enabled. Will always return `false` when `PAUSE_ONCE_DONE` is `false`. +/// +/// Will return `true` when +/// - Running on Windows +/// - Running on macOS +/// - Running on Linux AND the `DISPLAY` or `XDG_SESSION_DESKTOP` environment variables are set. pub fn has_gui_support() -> bool { unsafe { From 704a3fcbc95f1b96fdef1a758ea98b16a36647c4 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 30 Jun 2024 13:57:13 +0800 Subject: [PATCH 14/65] Update girhub actions to support fltk on ubuntu --- .github/workflows/compile.yml | 24 ++++++++++++++++++++++++ .github/workflows/release.yml | 27 ++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index ed79325..ba0a7f3 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -22,5 +22,29 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: Install Build Dependencies (ubuntu) + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt-get update; + sudo apt-get install -y \ + g++ \ + gdb \ + git \ + make \ + cmake \ + autoconf \ + libx11-dev \ + libglu1-mesa-dev \ + libxft-dev \ + libxcursor-dev \ + libasound2-dev \ + freeglut3-dev \ + libcairo2-dev \ + libfontconfig1-dev \ + libglew-dev \ + libjpeg-dev \ + libpng-dev \ + libpango1.0-dev \ + libxinerama-dev; - name: Build run: cargo build --verbose \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 62508a3..dd8e596 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,9 +21,30 @@ jobs: filename: 'beans-rs.exe' steps: - uses: actions/checkout@master - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable + - name: Install Build Dependencies (ubuntu) + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + sudo apt-get update; + sudo apt-get install -y \ + g++ \ + gdb \ + git \ + make \ + cmake \ + autoconf \ + libx11-dev \ + libglu1-mesa-dev \ + libxft-dev \ + libxcursor-dev \ + libasound2-dev \ + freeglut3-dev \ + libcairo2-dev \ + libfontconfig1-dev \ + libglew-dev \ + libjpeg-dev \ + libpng-dev \ + libpango1.0-dev \ + libxinerama-dev; - uses: actions-rs/cargo@v1 with: command: build From 8c69a035d1f71166a141c1ef1f2fedd0952bd90e Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 30 Jun 2024 14:04:42 +0800 Subject: [PATCH 15/65] Add dependency dark-light --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 388479e..d44718c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ chrono = "0.4.38" fltk = { version = "1.4.30", features = ["fltk-bundled"] } fltk-theme = "0.7.2" +dark-light = "1.1.1" [build-dependencies] fl2rust = "0.5.19" From 59fbb09fc88562e9da7e3f836701b40335151ee2 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 30 Jun 2024 14:06:52 +0800 Subject: [PATCH 16/65] Detect what color theme to used based off system --- src/gui/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 53c59fd..76075e4 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -2,6 +2,7 @@ use fltk::app::Receiver; use fltk::window::Window; use fltk_theme::{color_themes, ColorTheme}; +use log::debug; pub(crate) mod shared_ui; pub mod dialog; @@ -59,7 +60,12 @@ pub fn window_ensure(win: &mut Window, width: i32, height: i32) { win.show(); } pub fn apply_app_scheme() { - let theme = ColorTheme::new(color_themes::DARK_THEME); + let theme_content = match dark_light::detect() { + dark_light::Mode::Light => color_themes::GRAY_THEME, + _ => color_themes::DARK_THEME + }; + debug!("[apply_app_scheme] using color theme: {:#?}", dark_light::detect()); + let theme = ColorTheme::new(theme_content); theme.apply(); } From 0f1c98995b772a9c244e86f1362a357aef4eb65b Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 30 Jun 2024 14:07:19 +0800 Subject: [PATCH 17/65] Fix initial button position not being correct --- src/gui/dialog.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index 312cee5..98ce83f 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -26,6 +26,7 @@ pub fn run(title: &str, label: &str) { 10 + label_h + 5 + ui.btn_ok.height() + 5 ); + ui.btn_ok.set_pos(25, ui.win.height() - 24 - 5); gui::window_centre_screen(&mut ui.win); ui.win.handle(move |w, ev| match ev { fltk::enums::Event::Resize => { From 50453b56b1c25b6d2a070ebb137f31bc746c0fe8 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 30 Jun 2024 14:39:26 +0800 Subject: [PATCH 18/65] Add icons for fltk --- Cargo.toml | 1 + data/img/default_error_x16.png | Bin 0 -> 4740 bytes data/img/default_error_x32.png | Bin 0 -> 5550 bytes data/img/default_warn_x16.png | Bin 0 -> 4731 bytes data/img/default_warn_x32.png | Bin 0 -> 5497 bytes data/img/default_x16.png | Bin 0 -> 574 bytes data/img/default_x32.png | Bin 0 -> 1128 bytes src/gui/icon.rs | 10 ++++++++++ src/gui/mod.rs | 1 + 9 files changed, 12 insertions(+) create mode 100644 data/img/default_error_x16.png create mode 100644 data/img/default_error_x32.png create mode 100644 data/img/default_warn_x16.png create mode 100644 data/img/default_warn_x32.png create mode 100644 data/img/default_x16.png create mode 100644 data/img/default_x32.png create mode 100644 src/gui/icon.rs diff --git a/Cargo.toml b/Cargo.toml index d44718c..0caa2e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ chrono = "0.4.38" fltk = { version = "1.4.30", features = ["fltk-bundled"] } fltk-theme = "0.7.2" dark-light = "1.1.1" +image = { version = "0.25.1", features = ["png"] } [build-dependencies] fl2rust = "0.5.19" diff --git a/data/img/default_error_x16.png b/data/img/default_error_x16.png new file mode 100644 index 0000000000000000000000000000000000000000..d6d1d440fe1188be5411a5751d6c18a23c27d71c GIT binary patch literal 4740 zcmeHKc~BE+7Vn6FoPsjIs9+ns(9O{~$RUw|1W1%cI>r@(AP<>T5(l1MwrhkFxQ~DQ76p&SEM=l44$Xj#CWD&P60jxHNWg@Z zR6~&Us!nmu<2da8N>f;L%dcsTc+9l4oT5cJTc_2D3hul*hnDv~=#e&1B4oupTD=cb zx^Y%(ZuU-j)Vh=2jlm^Ldwp8sw7kE3J*oF>=&^t0wki+%-xs;Q{PWkN-t-cZFS<%o zI)c_L_D3=)$18gpDwWswF1j+KMiEA?>kvMhd#LSMk+zc=tHGIS-|;J&ziMigAnL%i zEv&R{F{s+Dr=fcEeUFP<=DVa%OzwstN**Z|D`aBvyIep9<;%VjNslXh^5(|ZQ$KNb zE6g8WGbcUPyE*OzHF2sgZ(cym!rmYbDUC}S=Xg0gCv5fS&VHH&0ZESGkDQy@j>^tQ z&N{>huFLN_dT&|z^E0;(zl63gRPSl?c2u^q;<`#pTffa;nP5S47yCGjVn3B+O>j8- zG;QSgo1N6qQ!j5+e3Ni;i~FO3rr4^nzFoqu(^b38H>P;j7Vq+^7(Lhj0^AjJnb>rb z?-$@}RkyV4YR?xpscP5W3sFxJPue!wbNZgBo`_3C-eX@)eG<_+v&StbQzvLm+k-aM z<_|CSny|CbV*@%N;$T!^fzQ_F&{yK3Fd2Pw(%9s?rO}SlCN__J<9tMu>RUDW`mSX6 zj-|B^zeM2uZk)y6zh0)jwsv&mTlXZAwtTne=`V^GrjoaVDh*?Q_(4y02O1AJxX-*_ zJV8)gF>9*Nis$U!s&VI*yFID-hdFiIohOu*b2TOU6*=PML(;a?z|P7}Nfl8OViZpx^Z{D?yJk;s~=L&4dw=>4e@A zG!O#E-uoNVOj>(7IL08fgbtXR!K%ohC8K3>#d{AM1sYOkw0i-v2U%K3^#`&B#b(pk z(-{~DaDR_G$ogIGc4J^AmkT8ZEX@|4Od_J%))(RijKqcZSCoyT7|vDG)F_`rcW$6ac4_00-l85I&n=({Lu6MdR?*C{2ZOxHOE( z5DtgHxdTSNwzmbKzAptcnoi*MI006l3QIxt z8UoCZ{(^lcCx53HcnG3K2+-LaK1zTaj?n~aHixF>piWOY z<@(_;Md-mlpbT(S^2cuFfMb@EYId{)dJ9EC%IP8gS|D*TO6Qv)$ZfRkqCjQk{y^w# zk;x;SJDdWYg5dm-t%(pcj3twVEBU9^uAeR45bM9H!hEJPsr*gZ5%1uXx|ZzkDq+9L zKH-}cw12A<4|m+Fkrb9}LAq?{z}s8zIpz(GA`Pw%jQTYIO8vgF-ZMm>xGKb{wh?oX zPRKtT;CALlD`gGk-s8twW|7a1;VMYyKI0O_HsCVs?iIpKSNz z%}+3I+?u+tF?xhRT%#>->+m4R--SH)Kke>@xy?&2UvzK$ zG9+*LY^S(J>a8jJ$Bbf#@>T`v4sMWYl4d;k_2(-t>w3N`4XobTP1)XshJIeQ$f5A$ w{-?K3iyG>!nh?t3mE1?r#mEKbmAuAti<04XQ$dL^4-^z6i<~3b8AW9x10a2h*z=y$<%~32z zgDRBP#ZMdT9D~U3r{3B}8g$GJHB3+SR$MX)ty{_@tjV&l*-5YGJy&US6d#EAK!2kG6M?lu<&gRc`fJF`5Uoxi37C6N=qh3QenGF<25cN%S^IX@+3 zY{3MMvC#@^f9s@mjR6t2E1vJScgjefQB&h9!O-3p1+J8=Xu4hDj~u(&A!k~SVaUGf zPw7r+Q_Pn=s*UNH+I?XnvI9JjHO<%IwzZ|vAF1~qY?z{SHBLV4hQ|{8eD)a=dzXd2 z>+KuFRu*0C87z*?yjTaz^_U8t2lS6mx*-f^yoi5_oad?2NAFwfsApg=4T=7+Np=%R z3{T$ZwR|(p#d`AnKoK{9$bKq#dLn=MRva%RfF@oMaMi!5E29j*Xtm}^z`?e8<5r`Mf^yKRnP=Q&9~x2*1sJHrQ-H+t(_N~kx! z|7FTJ#07`F{QHk<&pf2TKi??NY0*C_T07@Ap6v-G$CR&ZlfSy%i z92%uIkw-Gof!-{XvsenE$XGHKk8xEBqKRlb9h9w<$7d{daT|evR!nq+TrOeYa0-P2 zs~};;(r_GsPN(DWL>!Tbfh;hx7?B)MVni}a6~r)x3n=4C1roVHEJCR;0gia3oQXz5 zdeqnWq9oql-{3{E5f&gma7sXeBVh5ks3_cM51HIG8Uh&!=pQ{~Y^Y6fi$R%qrIZW0 zMuQ@`1%YD)5DuP4!gKgGbPSC|r(?(z z0K@=v8WlsNk|2L7nM@}TMnU*U1yEH2!qHKwpm-3J4GkpHNjw4u;BhG!GM_}o(5QGm zhC`tdY)Ak|#1n{WC?1#LCYDA4&~XZ)fN&5e5rwN2D#96#EKeqyh{b=Gu!Mk|4>>?R zAQ17y3fXrRTMz~M$pICg1S)|}g#w@ui6kP8M)__O07_+0Evirncr1ym)~F7K0i^>` z3#cj;0#M7LY#7c`5Ri+dY_V9#M5}^AsXV`_y`hHU0dl|vkb@8?o=9fkZ5Sjxn?Poe zC=3d59-hX)kFpo@1pJu)Wvyx-ludV42eeK z@M$zM(S|g9g5S_(V!m7fNI}PNh)0Mks6f?Rp{z!ZYR-?*C?Y`BIY5+Q@HUuHl%a7W z$>LNK<7>9Ixc}nAR&6k9#6W(-GH7%`lMwf96pru(#rkjlM$X~i+yaIAwaHKM`-`q$ zbo~?qKV|%@x_;61Qw;o+@vrLoztN@h{bLFgL2p0`=u?T6CC-LEvotsh-CbZqFgF-` zp3M{?B+-_51j%49-ASqo4$I1!0tq$cp5CsS?HbcHEXnD&O=iMiEGhPaeuMSM#vyg4I zxMPc}#vY^*m$h?tqFYo)%T4Nq402t*pn|S--0W)lDWW|h{?3;N@lR_Xw+&IES3P_A zLEpn*V)^4Mt=8L(cZ@?6n3|%s`{j;TJC4xLokI9Fj|H_9P2f1|Q{>bA`L9mt?u|9h z@~sH6SX{S2sEcoSljvsP5r1wHY}$etcLZ9YmEs`Oi}P95_P$u*TUW5f-}dfFAfZR} zdrEA2ePzfDIHh@R*b%3xCYy6FRCFI-WoX1Yx$GT$U}I@bH*Wl`@hwXO0|HgFt`1FRdW&1(nCkRT>qy2BAHZ~f$2O1fe z<6}xDQ&Kcmr8ioIqLzC}TAvr5DQqabVx~`iT(p~@l)`4V4+ZXW zpWFJbrZXqmE#>IzHI^^vu>iOR$zmpr^_Hv!qb*?^spp%+=jyYj%{n+X-SNi7xO6j}jv)Bkz#P&zK_OuJz zb|cUIw)08n%Q;Ruxev%*Zxv++#XJp>Gh4^{)%ohz`GYw=>oj%S@F(;m+Lukp;3uTU zXUBYOi1eCWxv+;#AL`Mlccq1=hE%_=i8RCwZJTHty#F25zV%j4-jMU#f}x#jOP?@0 z7j*wM&wN+4GU!uRZCn!~f7h*s7ADy&u`hlp9skkPa$e8Xa?6E0%obm}{yWrm^J9L& z_rfAxgWHakYUzdP9C9k0z0PIB{!G1(>pM4EuXqd>hU*?m(7BHE$lBdE_(xy=i_j-) z7A|!0^)jRv6xu2~5o@Y$vDQ>jC>jBG@^rky8)d84lp2v(>nolJaxQBjZ&>IV8M(3+ zPb@W=62~{w%(br@^UkQSkbB||L9%kI)dW^zYv*4c%=;hWCgaCym>+Q-mrrY$Ry0A< z{F2PJJj74Z5@@yfT^g{jKOU+~8SK<8Hy@}<^AH8cz)Jo|O*Nfe-sVg*7XeW!H zy1v0`TY1iHVRAp^wluW+)^`n}$ue0amg%tMQHrAVF=EH6-~3?v`hyw{Z11hxo#Ua-M(Y0KPR z^FtGZM0chgT$FwD)#`JnjSroTe4|nJ`Dl3kKF5F=g66}AE!HOZWRXo*+5{ZyP2G2G z{qRhSa#m?;dCPpVHY%aVVZvQx6BHT)!5MPkf@l-@S)@Dn1#E?vE9w#drr2=XS{Rpvd_JWRwuoa>MvA^ zV((OZ*SlY~T)_VR4kyS(`_b1MA3R}-H{1w_$tdl(l3!K7Taa|I_PfXby3rE7{`m=R zz=}$8$Mz1v9;U{7^*U$tT;T%U*`>roadmSge8~D}>yj63Z7a`L%Qn033^`H*g{^@? z$2|BUj5B)lKkpto^`qcfVRz#-SBq1_2Pv+<{hIn21UY=nL`Ev*k&&;e0X39;uvQvd zt@JLOn{W&nC# z5yQJ3vU82`>{_q(*>0$MiP1Wt^{n}1`;72()y9n6D2Mq+R;j`!Ys`DqO{1hfJIBsa zs=wM2%hfcUI`{bJe|IhVSZlr0WquKCEpeE!R{T9%jogN}+&ao$?y8xXxX!!T+${gw z*uO5ld-~DSajiS59j|!!Zn_?pU6ed6_u$Q{eN=f(NDUFy5cJ-6r}r6eH}6Z#*w+xa zjKC`P?>ZGHTp|9vINS@Nt}CO5j7E-T?%Ei8)%rvA%;wILyX)%bI|tM`?om#ZEPXPw zQ<{1Nlm0S!2pQh7QnzfC(}#<;rJQJMCmZ!!pdM}0yO~{kJ<}eSJkL*cpWxusSXrBs zTGCN)(Lb$IzQLn@ZMSOHcIca}S3`d(s7l>txU@O-q2JfT!k24~EphVma~v7<{o)Qw zo}u+-&)I9GcZOL%H0^GQ_B{l;E|&q_m8_V9lLj4!pbTo7W6>Ew+d)v+OpB2qGier9 z(^^I^McU7tM_`7MB1u98rZ7g*=}c_CiB8CmQ<3?Zq=Z6d&TtE};DA6!vjl9>W$Deh zMT*#Yaqw<4a}n4MVKb#jvO)<*8cZ}S;0QPv8f9T}`N#}6ILt(8@OW8t9|ic5BIzt^ z#JSwOygW``2*+U3a(NPogp2XHd_D>gsM)G#2@9$>2iPcjIb^h%G%-e&G3a3%C!sdv zuu=p87<-HLyMy^oyd)OqfX_FcBqK%b@aY6+45!=Mzpd27AVe3>t zV7G&6@JJI)um+RLV91gpwiIBSXaBGQEGUX#2^qoCfE42ka7=`UU@D#f=ksuZC>Rst z*Z{nNVl>wOg|;mpIIMTlV;M7uZ?&6x*Hi+X-TT%1nZ?)_6Aasz1x}E?C76j^nzH8! zxO#`kbV9GC!T!9lPgfMh5pLI+5OaQlko+IEar)rN8ZOOG%+ zV89jw{CaI*>jFC=w|^__(+lMKXa4%;;m-^K!-GNIO5Z`b2IYDy1>Oog*j z6T^H@j_?;uTX7Wez*Wz4iZ8$Kf#*!wF(28H5IcgsH(cg5Pj@Gxs5r4{ov+t|uVlL( zC9Q1ly3F2Kl#{Z$=foyrY4?`aO(UL4j56XLv?|v`ZJd5Mf8r^<$eb6}Wp2sR|eA~Wh&UY`yk9SwzyopDY z2M@)aHhEX*r_Gu?1dl8aF3P^th!oTvehROA-t95G<|N_snX=bw=(J)$_%GtQY$8iVjsJ@ZSzR4Uq99H_{1J~-gE7Rf+54( r@ASl-ao(9UnKSEZpx30J*`4n9#V;O|N7fz#Z3M}q;$#PArG4}t(;nG$ literal 0 HcmV?d00001 diff --git a/data/img/default_warn_x32.png b/data/img/default_warn_x32.png new file mode 100644 index 0000000000000000000000000000000000000000..f063fca74745f272671da5764efb4b6effbbf77a GIT binary patch literal 5497 zcmeHKc~n#95)ZOPKx9W{@e)8$n>|2C2{e!>Sb~5cpcJ`DZXiGwldy|y(xR49DaxXi zMMR3CfMUgkMOGD1aYsO`b*XHkfC^aOO+dx-dd@qZ^ZK7Tx!Zg*znS@cGjs0U;o-h! zf!Y!^1Ol9z6o65?B0{$>pe-FwF#svSLn;>Fb+_{*l=!u3*7_07BmUBz2| z|K`NO5jX$Hz6LXYhnv?|RO|^_Zhv#hs|?DoD%+&HE;YN1m>oTF)na=5vQ=Qq(~zbN zTqvj%nRKCG>US~!Oh8?QAm;l?_Z0e*QwcAcOI}$`Xy({zRpEg?M9bdo0-Ci-3nSUD z{S-ybsy!Td_gD(VBW}O;M`mtre1JeGr1G4dJzSlg-{%8ou|Iw{)uquxFLmAe>XrJc zyVBA)rS6fQ+O1yAC_gKH>IM_Hz)5ow&&4aiSowBxisLRHRm0%#Oahc?&sCf5Hn?76 ztUZTaaWZYR;ZgklSDg=Prx1mK+_Jmc%HACWuhE>Gj`Fl5Un!Usszucyj?-h!6t9d& zEL?hj1Z97DsxSY@x{LcXp6_V#EHN?|rHrpix9A2iYJMBazE3tZ zF^J~2wG}^3b8cc)ra!XbS~^=ESf;tEZ1o$bTTtptgW&1_sKeondP)r6t|OvMyQwlw zDO1br;LZhEpqbOz)jN0S{n~0j-T0HED<(I2Tpi^56pVW4ck;LOurM2w4y&nlFMY4}q|Ej240HFh~lppkSVWiW)gvhXQyU zD$3WIiDQbKp%9)+tQcA!>+a2t4P)DJP>v32_R$m=fDcJQAetX8kWiwjC^;?#zL$xy zC_t_v4Wpv`m>z($Pz(Vi3<-lnGopEs1eAjsU@zuyDPHu|vk>qT6%`_tiYQoYR8$lu ziii=4gRyu!J3B0nfF%&num)NZBanj8Xo19B1~G#{ha_w;qr7iY%CNG@nKa7>=pls%Qdb{j}I0y3W9ljk=zO<`x8wmkNc6V zPhyj;$kUk{2yFfV_Y>{+xyzMdEhdvf7qTN{;knYOD4Bl>N66-JDDqnth+`2+EHauz z0wFXBv?ij-Y&$lZL%?&a*$|n)WwYi$xe6pwP{4*{P%t=#2jdXfb~rMGXQS;v_(Z~U z$!HdV45IPYI4(%E=CEvuxH%B(#5}kv!SK0J$)GqelnseRgt#_1G>C^tXc7e3!k{D; znv1g`fi^fY$R(2HP#iX8wNT6l;qBz{!C(k05(LW^WQ0>_9zF8 zBnp8*vBs~$ktw)2`a%wm8}q-kW$gpl&uqF2PXhaok&9+}YCW`N=4s|JoF{K40Fbu@ z1!T{pAORyGjyz5nYi5ZZ0t$j5cz(ar1I>{40bL^GN~1tAL<@#_gt>wXRL&J(HM>iWOYrS|b-3KGC?KvD3g((RNDzVK(33hUc7bi_Nv zYJ~SHn?>QUWWLD7UxGlW>&gxVMBe^IuuxU%%4DcMRWVgDARbF}#~~2P=B{*_w_js< z_RuaLAH9}@u%Y;gny9`TEiE*0R>fkZ;}{{{>Jo5+OEM=8D*u*HP*l9ob-OI&f3LMuLKVG zhYgtrB;4tYs=brmZE2>o>}$Hx)~drJsc$k67m)>OaW6{!tNL#l%~#;)^ebp3y?vX2 zR(Mfg6N>!PDN(6$d8_+DlrBmev2@EVBd zNIyf*&HGxPHaY(sTY*Vd+gNq5U$-WwyIT+4yrst!*?)1+yQ|8CafV)FKORAF)mHO# zP}#FZXOZcd3h2u1YS%nYXr4&(0*%dcQ-5fVdvxPk_xS*Ct* z#W;_#qz4v1Pl^}_cGe60{ImcwU|yy5(cUTb`1|=Z%a^M+j21}xq=Cbyb9cGM>JMAY zFTEyKSg-#_+tpWZdAICi3|AI#YK9fh)edQe?d@~ICt~BkDkb_Qr5}+Sl$G~;_6<&H zA9Q-AcwBm_Fwv(W&AYduMKdYy$hL3a)Zy1MjX@umt@T;Ys!M64ETz|uTDZ(yuZv1N zW+W+0?}_nlwP%W_==Dn)%23f;^~<-0FI?1|rnRZrpYdyhl?AfX|ESs@iu02_uab-0 zvmP`_R*b&B$W6j8ejRo;e#|=%Qj+f->DNgtF(tNKd{LS1Rzr0g2J&5aMkb`%8*JPWZQ905S1Irt!=i3D< zuimN6X_;uIKgsA?8GJm>>{{jZtNm2H9~4{9WLIptr_jBrgxkN{vdX~FpYfP1Ls#jr?n46CWK>6%mbcV_KaiYNhu!*8jLGQJcZg&bpC;RhhiM zZ9Dq>R4(2+Bi)2p^7wAo+5F>y=kq1^1>9THnv0$~S=~%Y!f-oAf+oj|@;BC1m5)F3 zc#=RfY;+Z_m$bt|GMER`$^{pSb%j>zab!C&Zj?);CAKN%M8vsVfvA0~Ax8 zb~_a!k144Akkj?rmdPJ_IT?5+{w6e7$tV?f(5t2#8YJR};;!Ph{?@RNO`@e5!-=J) zC%;!TQXyA>h?C1U8THWaB~P6$EPPPjW$OgZ%ieUptYKqZhc}z1H=^hmKka~i)uV10 zNjqjLw$^$QT@;63C&hkG&MtrS?7;=h#^IXmE57%uO)B)&cUzZ~aWgIE8&3Ux^<35B zCMRP^=X(8wZ7n9=_(pQJo8OWU0k!itVUHKEdB-<6oo$;(|CrpDNirGt`ehm8Kv`JH z={I@}s;9TU`o&{!Y~t>LmD9v5W2JyYTx;bV#U77T_t3jcB-#Qgi>XiXycf~p% xl-3@NugeBX?Euq9y$HRe!Y(4hFsbkzBAV4y_H{W$2cDn^SB5+Nm{U;le*iefP}Beb literal 0 HcmV?d00001 diff --git a/data/img/default_x16.png b/data/img/default_x16.png new file mode 100644 index 0000000000000000000000000000000000000000..df01f3393bb4b16e34ac4ffdcf493f4142320893 GIT binary patch literal 574 zcmV-E0>S->P)OOG|gux0JymO zNgWIciZaD4E`>mL=cFdcg#v6uC6ZYCnfFNIKgBHG`8W+5v3V;%70K#{XsoYLNgsfJ<+#MsH7t zcg0Wh-!b(j&cD4v(b(Sf3w)l>;<#XNenqpPB>rA%FUMGv8xsg}MoZ)}Jy=;WaU;MVBgc}v8tWXyN zqcbU28-MHsmx$sBTi-T~r$aqqZfHFCorMss^ia58XsX-h&5Zy906#I=g|1uBtN;K2 M07*qoM6N<$f;^7_g#Z8m literal 0 HcmV?d00001 diff --git a/data/img/default_x32.png b/data/img/default_x32.png new file mode 100644 index 0000000000000000000000000000000000000000..e3e037723656cc2c390198b21856a5fae01dd93d GIT binary patch literal 1128 zcmV-u1eg1XP)EaZ)V!6n%TFrhS_=V&2}>B>F(;PUswIAdWO9B_^#N%-IrYJ|aURA+ovvh01ID&q-uxIumTDOH3ILd%`Mafhf!oFPG5|wjIEKZS zv)5r*0}W_AWks*#2NAu!0#JL-g?OYn3~OY91_0oZq?w+g-d+GeAiFTYNVA~;25ky} z^`L}HZW;Rs!U)Y~0+YwFZx65zwy%21xa^jpE`p&3MpjWx60)8~1SV3S0NV3+PflKG z#z~y9Y2X|LE?b9n$-n&pSbgpL=J{hEVWrwz?;q|DKpdyq|28+54NOZD~4fxKB`mRPe)rqD653}q#@YtW;UdbDH7eZNS6 zic($)fM^kbbdj?-bpBUN{cuBxP2GwIj6MG-KHmN`?#D^NZcPA2s}SYUAVh&>PHQUxP156MxB>+k?WPS z)gdsp@J`*aWdOrbpax?GVDow{>tuXRz!d->r}4-NnGn+1>}ZY+hGD2=Gz`Gy0A$^1 z=A;@Hs_g+*EFdDs2Kh0e)ypYn)nVw=1H_hOEc1rFvWkh@<~s5qPW3vj=K-4;-+S&V zCcpcWW6vk#W6ify29$+BJRoD4-@1Dm?Zl(CWj*$eZ&DC7Ltt64!580N!>)I>aVR_t zpX%ou571x_2uU4}|GLw`yE|V(%Hx!)-oaCsuz$yMh%2s9)5~QdY4y{Q?Kph;BJRb6 z3%}gK)qmO?fNK>R(+r~RF?*zi6K5{sg(uf!#si1I zIqf4Rq|D2@$cen{ll}EPpw))>G)%NkirI-*@yXe*@XX^Q2!e>8P@z z1;BFr4M|!V{k%%quQNx-aq{dmo>i$d*XEc3E1aw0GLWdA?_e76*nmSCfWpR1mJ zXC;`K*nv+!yMV?}q`iz~Ue{@HkICzXuX7m@`6a;OgqhEUW@h4bjK23d=1%NX<1)`V zvgE3!iY%zU3rb=r1;+T4jlaRD@`6JPcSrU9N&pLb4A*vJD&3^FrdeL3KMpu+v68J! zUzrEICI9=IA0FEfCkg8OpUTO_xafNr7pdg$cr|yU{H*P%R#Z2+5N`4_$Iq>z%`yP| uFNI;;?u2`~Vl`*uzRl8o8_0000 Date: Sun, 30 Jun 2024 14:46:50 +0800 Subject: [PATCH 19/65] Rewrite dialog as DialogBuilder --- src/gui/dialog.rs | 128 +++++++++++++++++++++++++++++++--------------- src/gui/mod.rs | 3 +- src/main.rs | 14 +++-- 3 files changed, 101 insertions(+), 44 deletions(-) diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index 98ce83f..877dcd0 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -1,47 +1,95 @@ use fltk::{*, prelude::*}; -use crate::gui; -use crate::gui::GUIAppStatus; +use fltk::image::PngImage; +use log::warn; +use crate::gui::{apply_app_scheme, window_centre_screen, wait_for_quit, GUIAppStatus, icon}; use crate::gui::shared_ui::GenericDialog; -pub fn run(title: &str, label: &str) { - if crate::has_gui_support() == false { - println!("============ {} ============", title); - println!("{}", label); - return; +pub struct DialogBuilder { + pub title: String, + pub content: String, + pub icon: Option +} +pub enum DialogIconKind { + Default, + Warn, + Error +} +impl DialogBuilder { + pub fn new() -> Self { + Self { + title: format!("beans v{}", crate::VERSION), + content: String::new(), + icon: None + } + } + pub fn with_png_data(mut self, data: &Vec) -> Self { + match PngImage::from_data(data) { + Ok(img) => { + self.icon = Some(img) + }, + Err(e) => { + warn!("[DialogBuilder::with_png] Failed to set icon! {:#?}", e); + } + } + return self; + } + pub fn with_icon(self, kind: DialogIconKind) -> Self { + let data: &Vec = match kind { + DialogIconKind::Default => &icon::DEFAULT_RAW_X32, + DialogIconKind::Warn => &icon::DEFAULT_WARN_RAW_X32, + DialogIconKind::Error => &icon::DEFAULT_ERROR_RAW_X32 + }; + self.with_png_data(data) + } + pub fn with_title(mut self, content: String) -> Self { + self.title = content.clone(); + return self; } - - let app = app::App::default().with_scheme(app::AppScheme::Gtk); - gui::apply_app_scheme(); - let (send_action, receive_action) = app::channel::(); - let mut ui = GenericDialog::make_window(); + pub fn with_content(mut self, content: String) -> Self { + self.content = content.clone(); + self + } + pub fn run(&self) { + if crate::has_gui_support() == false { + println!("============ {} ============", self.title); + println!("{}", self.content); + return; + } - ui.win.set_label(title); - ui.label.set_label(label); - ui.btn_ok.set_size(70, 24); - ui.btn_ok.emit(send_action, GUIAppStatus::Quit); + let app = app::App::default().with_scheme(app::AppScheme::Gtk); + apply_app_scheme(); + let (send_action, receive_action) = app::channel::(); + let mut ui = GenericDialog::make_window(); + ui.win.set_icon(self.icon.clone()); - let (label_w, label_h) = ui.label.measure_label(); - ui.win.set_size( - 25 + label_w + 25, - 10 + label_h + 5 + ui.btn_ok.height() + 5 - ); + ui.win.set_label(&self.title); + ui.label.set_label(&self.content); + ui.btn_ok.set_size(70, 24); + ui.btn_ok.emit(send_action, GUIAppStatus::Quit); - ui.btn_ok.set_pos(25, ui.win.height() - 24 - 5); - gui::window_centre_screen(&mut ui.win); - ui.win.handle(move |w, ev| match ev { - fltk::enums::Event::Resize => { - let height = w.height(); - ui.btn_ok.set_pos(25, height - 24 - 5); - ui.btn_ok.set_size(70, 24); - let (lw, lh) = ui.label.measure_label(); - if w.width() > lw+50 { - w.set_size(lw+50, 10+lh+5+ ui.btn_ok.height() + 5); - } - true - }, - _ => false - }); - ui.win.make_resizable(true); - ui.win.show(); - gui::wait_for_quit(&app, &receive_action); -} + let (label_w, label_h) = ui.label.measure_label(); + ui.win.set_size( + 25 + label_w + 25, + 10 + label_h + 5 + ui.btn_ok.height() + 5 + ); + + ui.btn_ok.set_pos(25, ui.win.height() - 24 - 5); + window_centre_screen(&mut ui.win); + ui.win.handle(move |w, ev| match ev { + fltk::enums::Event::Resize => { + let height = w.height(); + ui.btn_ok.set_pos(25, height - 24 - 5); + ui.btn_ok.set_size(70, 24); + let (lw, lh) = ui.label.measure_label(); + if w.width() > lw+50 { + w.set_size(lw+50, 10+lh+5+ ui.btn_ok.height() + 5); + } + true + }, + _ => false + }); + ui.win.make_resizable(true); + ui.win.show(); + wait_for_quit(&app, &receive_action); + } +} \ No newline at end of file diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 99d444d..3bfe756 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -5,7 +5,8 @@ use fltk_theme::{color_themes, ColorTheme}; use log::debug; pub(crate) mod shared_ui; -pub mod dialog; +mod dialog; +pub use dialog::*; pub mod icon; #[allow(dead_code)] diff --git a/src/main.rs b/src/main.rs index adeae17..bcd4047 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command}; use log::{debug, error, info, LevelFilter, trace}; use beans_rs::{flags, helper, PANIC_MSG_CONTENT, RunnerContext, wizard}; use beans_rs::flags::LaunchFlag; +use beans_rs::gui::DialogIconKind; use beans_rs::helper::parse_location; use beans_rs::SourceModDirectoryParam; use beans_rs::workflows::{CleanWorkflow, InstallWorkflow, UpdateWorkflow, VerifyWorkflow}; @@ -80,7 +81,11 @@ fn custom_panic_handle(msg: String) } } let txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg).replace("\\n", "\n"); - beans_rs::gui::dialog::run("beans - Fatal Error!", txt.as_str()); + beans_rs::gui::DialogBuilder::new() + .with_title(String::from("beans - Fatal Error!")) + .with_icon(DialogIconKind::Error) + .with_content(txt) + .run(); } /// should called once the logic flow is done! /// will call `helper::get_input` when `PAUSE_ONCE_DONE` is `true`. @@ -389,8 +394,11 @@ impl Launcher } } fn show_msgbox_error(text: String) { - let t = text.replace("\\n", "\n"); - beans_rs::gui::dialog::run("beans - Fatal Error", t.as_str()); + beans_rs::gui::DialogBuilder::new() + .with_title(String::from("beans - Fatal Error!")) + .with_icon(DialogIconKind::Error) + .with_content(text.replace("\\n", "\n")) + .run(); } From 997a04fee68c7915d3ba672c2305e2623d244120 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 30 Jun 2024 14:47:44 +0800 Subject: [PATCH 20/65] Remove dependency native-dialog --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0caa2e3..f0e2ee6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ simple-home-dir = "0.3.4" clap = { version = "4.5.4", features = ["cargo"] } bitflags = "2.5.0" log = "0.4.21" -native-dialog = "0.7.0" sentry = "0.34.0" lazy_static = "1.4.0" thread-id = "4.2.1" From ef35a1eb60dbce14945d9ae0c0a15659dc54abfe Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 30 Jun 2024 15:02:15 +0800 Subject: [PATCH 21/65] Fix resize being triggered when it shouldn't be --- src/gui/dialog.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index 877dcd0..dae4807 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -60,6 +60,7 @@ impl DialogBuilder { apply_app_scheme(); let (send_action, receive_action) = app::channel::(); let mut ui = GenericDialog::make_window(); + let initial_width = ui.win.width(); ui.win.set_icon(self.icon.clone()); ui.win.set_label(&self.title); @@ -75,20 +76,24 @@ impl DialogBuilder { ui.btn_ok.set_pos(25, ui.win.height() - 24 - 5); window_centre_screen(&mut ui.win); - ui.win.handle(move |w, ev| match ev { - fltk::enums::Event::Resize => { - let height = w.height(); - ui.btn_ok.set_pos(25, height - 24 - 5); - ui.btn_ok.set_size(70, 24); - let (lw, lh) = ui.label.measure_label(); - if w.width() > lw+50 { - w.set_size(lw+50, 10+lh+5+ ui.btn_ok.height() + 5); - } - true - }, - _ => false - }); - ui.win.make_resizable(true); + ui.win.handle(move |w, ev| + match ev { + fltk::enums::Event::Resize => { + let height = w.height(); + ui.btn_ok.set_pos(25, height - 24 - 5); + ui.btn_ok.set_size(70, 24); + let (lw, lh) = ui.label.measure_label(); + let cw = w.width(); + if cw != initial_width { + if cw > lw+50 { + w.set_size(lw+50, 10+lh+5+ ui.btn_ok.height() + 5); + } + } + false + }, + _ => false + }); + ui.win.make_resizable(false); ui.win.show(); wait_for_quit(&app, &receive_action); } From 160e81b8fbff3f26231ee14ab70f969198ff861e Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 1 Jul 2024 06:12:59 +0000 Subject: [PATCH 22/65] Remove --all-features from CI --- .github/workflows/compile.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 41738f3..6946c0f 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -59,7 +59,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: build - args: --verbose --all-features --target ${{ matrix.target }} + args: --verbose --target ${{ matrix.target }} - name: Upload artifact uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca6ec5a..661d216 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,7 +60,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: build - args: --release --all-features --target ${{ matrix.target }} + args: --release --target ${{ matrix.target }} - name: Upload binaries to release uses: softprops/action-gh-release@v1 From 85d290ffba80d51bf65227067467e0f3fb16922e Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 1 Jul 2024 06:21:18 +0000 Subject: [PATCH 23/65] Fix indenting --- .github/workflows/compile.yml | 42 +++++++++++++++++------------------ .github/workflows/release.yml | 38 +++++++++++++++---------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 6946c0f..7fe04d1 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -29,27 +29,27 @@ jobs: run: | sudo apt-get update; sudo apt-get install -y \ - g++ \ - gdb \ - git \ - make \ - cmake \ - autoconf \ - libx11-dev \ - libglu1-mesa-dev \ - libxft-dev \ - libxcursor-dev \ - libasound2-dev \ - freeglut3-dev \ - libcairo2-dev \ - libfontconfig1-dev \ - libglew-dev \ - libjpeg-dev \ - libpng-dev \ - libpango1.0-dev \ - libxinerama-dev \ - libssl-dev \ - musl-tools + g++ \ + gdb \ + git \ + make \ + cmake \ + autoconf \ + libx11-dev \ + libglu1-mesa-dev \ + libxft-dev \ + libxcursor-dev \ + libasound2-dev \ + freeglut3-dev \ + libcairo2-dev \ + libfontconfig1-dev \ + libglew-dev \ + libjpeg-dev \ + libpng-dev \ + libpango1.0-dev \ + libxinerama-dev \ + libssl-dev \ + musl-tools - uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 661d216..1909f46 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,25 +30,25 @@ jobs: run: | sudo apt-get update; sudo apt-get install -y \ - g++ \ - gdb \ - git \ - make \ - cmake \ - autoconf \ - libx11-dev \ - libglu1-mesa-dev \ - libxft-dev \ - libxcursor-dev \ - libasound2-dev \ - freeglut3-dev \ - libcairo2-dev \ - libfontconfig1-dev \ - libglew-dev \ - libjpeg-dev \ - libpng-dev \ - libpango1.0-dev \ - libxinerama-dev \ + g++ \ + gdb \ + git \ + make \ + cmake \ + autoconf \ + libx11-dev \ + libglu1-mesa-dev \ + libxft-dev \ + libxcursor-dev \ + libasound2-dev \ + freeglut3-dev \ + libcairo2-dev \ + libfontconfig1-dev \ + libglew-dev \ + libjpeg-dev \ + libpng-dev \ + libpango1.0-dev \ + libxinerama-dev \ libssl-dev \ musl-tools From c18aa6cf99751c5908c188d7655208397c1b4283 Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 1 Jul 2024 06:55:11 +0000 Subject: [PATCH 24/65] Use fltk v1.4.32 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1efcc83..6e3c550 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ colored = "2.1.0" sentry-log = "0.34.0" chrono = "0.4.38" -fltk = { version = "1.4.30", features = ["fltk-bundled"] } +fltk = { version = "1.4.32" } fltk-theme = "0.7.2" dark-light = "1.1.1" image = { version = "0.25.1", features = ["png"] } From a3ffd32ac2baec3d0f2950d92feed8cc7177249d Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 1 Jul 2024 06:55:20 +0000 Subject: [PATCH 25/65] Use gnu for fltk :/ --- .github/workflows/compile.yml | 7 +++++-- .github/workflows/release.yml | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 7fe04d1..c873565 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -17,7 +17,7 @@ jobs: include: - os: ubuntu-20.04 filename: 'beans-rs' - target: x86_64-unknown-linux-musl + target: x86_64-unknown-linux-gnu - os: windows-latest target: x86_64-pc-windows-msvc filename: 'beans-rs.exe' @@ -48,8 +48,11 @@ jobs: libpng-dev \ libpango1.0-dev \ libxinerama-dev \ + libfltk1.3 \ + libfltk1.3-dev \ libssl-dev \ - musl-tools + musl-tools; + sudo ln -s "/usr/bin/g++" "/usr/bin/musl-g++" - uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1909f46..d33e2f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: include: - os: ubuntu-20.04 filename: 'beans-rs' - target: x86_64-unknown-linux-musl + target: x86_64-unknown-linux-gnu - os: windows-latest target: x86_64-pc-windows-msvc @@ -49,8 +49,10 @@ jobs: libpng-dev \ libpango1.0-dev \ libxinerama-dev \ + libfltk1.3 \ + libfltk1.3-dev \ libssl-dev \ - musl-tools + musl-tools; - uses: actions-rs/toolchain@v1 with: From 009d0e166e2b3a770e09b6a7c42e1f543710a150 Mon Sep 17 00:00:00 2001 From: kate Date: Wed, 10 Jul 2024 15:13:34 +0800 Subject: [PATCH 26/65] Remove musl-tools from compile.yml --- .github/workflows/compile.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index c873565..c4bda79 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -50,9 +50,7 @@ jobs: libxinerama-dev \ libfltk1.3 \ libfltk1.3-dev \ - libssl-dev \ - musl-tools; - sudo ln -s "/usr/bin/g++" "/usr/bin/musl-g++" + libssl-dev - uses: actions-rs/toolchain@v1 with: From 2d7e520718c0b64aac9f5d25582eb6f5930f0728 Mon Sep 17 00:00:00 2001 From: kate Date: Wed, 10 Jul 2024 15:13:50 +0800 Subject: [PATCH 27/65] Remove musl-tools from release.yml --- .github/workflows/release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d33e2f6..adec785 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,8 +51,7 @@ jobs: libxinerama-dev \ libfltk1.3 \ libfltk1.3-dev \ - libssl-dev \ - musl-tools; + libssl-dev - uses: actions-rs/toolchain@v1 with: @@ -81,4 +80,4 @@ jobs: # SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} # SENTRY_URL: https://sentry.kate.pet # with: - # environment: production \ No newline at end of file + # environment: production From d8729b670a87c2e120ff1ddaf0615f746c5cb0b5 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 23 Jul 2024 15:51:22 +0800 Subject: [PATCH 28/65] Add functions to check if the SourceMod is running --- src/helper/mod.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/helper/mod.rs b/src/helper/mod.rs index ef6c55b..cea3284 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -238,6 +238,71 @@ pub fn parse_location(location: String) -> String }; real_location } +/// Check if a process is running +/// +/// name: Executable name (from `Process.name(&self)`) +/// argument_contains: Check if the arguments of the process has an item that starts with this value (when some). +fn is_process_running(name: String, arguments_contains: Option) -> Option { + return find_process(move |proc: &sysinfo::Process | { + if proc.name().to_string() == name { + if let Some(x) = arguments_contains.clone() { + for item in proc.cmd().iter() { + if item.to_string().starts_with(&x) { + return true; + } + } + } + } + return false; + }); +} +/// Find a process with a selector filter. +/// +/// Will return Some when a process is found, otherwise None. +pub fn find_process(selector: TFilterSelector) -> Option + where TFilterSelector : Fn(&sysinfo::Process) -> bool +{ + let mut sys = sysinfo::System::new_all(); + sys.refresh_all(); + for (_, process) in sys.processes() { + if selector(process) { + return Some(process.pid()); + } + } + return None; +} + +/// Check if there are any processes running +/// +/// Will return `true` if any of the cases are matched; +/// - If there are any processes called `hl2.exe` that contain the `mod_directory` provided +/// - If there are any processes that contain the `mod_directory` in the arguments *that aren't* beans-rs. +/// +/// Otherwise, `false` is returned. +pub fn is_game_running(mod_directory: String) -> Option { + // check if running with the windows things + if let Some(proc) = is_process_running(String::from("hl2.exe"), Some(mod_directory.clone())) { + return Some(proc); + } + if let Some(proc) = is_process_running(String::from("hl2.exe"), Some(format!("\"{}\"", mod_directory.clone()))) { + return Some(proc); + } + // check if any process has it in the arguments + if let Some(proc) = find_process(move |proc| { + for item in proc.cmd().iter() { + if item.to_string().starts_with(&mod_directory) { + let proc_name = proc.name().to_string().to_lowercase(); + if proc_name != String::from("beans") && proc_name != String::from("beans-rs") { + return true; + } + } + } + return false; + }) { + return Some(proc); + } + return None; +} /// Get the amount of free space on the drive in the location provided. pub fn get_free_space(location: String) -> Result From ade019a9021698a9d7ede00d33f7b3ac8e7d9345 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 23 Jul 2024 15:51:55 +0800 Subject: [PATCH 29/65] Update error.rs --- src/error.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/error.rs b/src/error.rs index f67ecf2..7286110 100644 --- a/src/error.rs +++ b/src/error.rs @@ -31,6 +31,11 @@ pub enum BeansError location: String, error: std::io::Error }, + #[error("Failed to delete directory {location} ({error:})")] + DirectoryDeleteFailure { + location: String, + error: std::io::Error + }, #[error("Failed to extract {src_file} to directory {target_dir} ({error:})")] TarExtractFailure { src_file: String, @@ -165,6 +170,12 @@ pub enum BeansError CleanTempFailure { location: String, error: std::io::Error + }, + + #[error("{name:} ({pid:}) is still running. Please close it and restart beans.")] + GameStillRunning { + name: String, + pid: String } } #[derive(Debug)] From 0564d32451ac489888ae1f827299303093d7fbfc Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 23 Jul 2024 15:52:16 +0800 Subject: [PATCH 30/65] Don't report some errors since they're caused by the user --- src/main.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 45ef310..0fafbfa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use clap::{Arg, ArgAction, ArgMatches, Command}; use log::{debug, error, info, LevelFilter, trace}; -use beans_rs::{flags, helper, PANIC_MSG_CONTENT, RunnerContext, wizard}; +use beans_rs::{BeansError, flags, helper, PANIC_MSG_CONTENT, RunnerContext, wizard}; use beans_rs::flags::LaunchFlag; use beans_rs::helper::parse_location; use beans_rs::SourceModDirectoryParam; @@ -429,7 +429,15 @@ impl Launcher trace!("======== Full Error ========"); trace!("{:#?}", &e); show_msgbox_error(format!("{:}", &e)); - sentry::capture_error(&e); + let do_report = match e { + BeansError::GameStillRunning { .. } => false, + BeansError::LatestVersionAlreadyInstalled { .. } => false, + BeansError::FreeSpaceCheckFailure { .. } => false, + _ => true + }; + if do_report { + sentry::capture_error(&e); + } logic_done(); std::process::exit(1); } From 94a2d705ec4e46c7a70c97bb0f78c2938a52f882 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 23 Jul 2024 15:55:37 +0800 Subject: [PATCH 31/65] Create UninstallWorkflow --- src/workflows/mod.rs | 4 +++- src/workflows/uninstall.rs | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/workflows/uninstall.rs diff --git a/src/workflows/mod.rs b/src/workflows/mod.rs index d2b6905..9fa743b 100644 --- a/src/workflows/mod.rs +++ b/src/workflows/mod.rs @@ -2,8 +2,10 @@ mod install; mod update; mod verify; mod clean; +mod uninstall; pub use install::*; pub use update::*; pub use verify::*; -pub use clean::*; \ No newline at end of file +pub use clean::*; +pub use uninstall::*; \ No newline at end of file diff --git a/src/workflows/uninstall.rs b/src/workflows/uninstall.rs new file mode 100644 index 0000000..0fc9ea6 --- /dev/null +++ b/src/workflows/uninstall.rs @@ -0,0 +1,39 @@ +use log::{error, info, trace}; +use crate::{BeansError, helper, RunnerContext}; +use crate::appvar::AppVarData; + +#[derive(Debug, Clone)] +pub struct UninstallWorkflow { + pub context: RunnerContext +} +impl UninstallWorkflow { + pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> + { + let av = AppVarData::get(); + if ctx.current_version.is_none() { + info!("No version is currently installed."); + return Ok(()); + } + + let mod_location = ctx.get_mod_location(); + if let Some(pid) = helper::is_game_running(mod_location.clone()) { + info!("{} (pid: {:}) is running! Can't uninstall since the game files are being used.", av.mod_info.name_stylized, pid); + return Err(BeansError::GameStillRunning { + name: av.mod_info.name_stylized.clone(), + pid: format!("{:}", pid) + }); + } + + if let Err(e) = std::fs::remove_dir_all(&mod_location) { + trace!("{:#?}", e); + error!("[UninstallWorkflow] Failed to delete mod directory {} ({:})", mod_location, e); + return Err(BeansError::DirectoryDeleteFailure { + location: mod_location, + error: e + }); + } + + info!("[UninstallWorkflow] Successfully uninstalled {}. Please restart Steam.", av.mod_info.name_stylized); + return Ok(()); + } +} \ No newline at end of file From 266f36cc653b6054f04afb7f76bb7cd72b7794ee Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 23 Jul 2024 15:58:52 +0800 Subject: [PATCH 32/65] Add sub-command for UninstallWorkflow --- src/main.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0fafbfa..685f20d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use beans_rs::{BeansError, flags, helper, PANIC_MSG_CONTENT, RunnerContext, wiza use beans_rs::flags::LaunchFlag; use beans_rs::helper::parse_location; use beans_rs::SourceModDirectoryParam; -use beans_rs::workflows::{CleanWorkflow, InstallWorkflow, UpdateWorkflow, VerifyWorkflow}; +use beans_rs::workflows::{CleanWorkflow, InstallWorkflow, UninstallWorkflow, UpdateWorkflow, VerifyWorkflow}; pub const DEFAULT_LOG_LEVEL_RELEASE: LevelFilter = LevelFilter::Info; #[cfg(debug_assertions)] @@ -167,6 +167,9 @@ impl Launcher .arg(Launcher::create_location_arg())) .subcommand(Command::new("clean-tmp") .about("Clean up temporary files used by beans")) + .subcommand(Command::new("uninstall") + .about("Uninstall the target Source Mod.") + .args([Launcher::create_location_arg()])) .args([ Arg::new("debug") .long("debug") @@ -252,6 +255,9 @@ impl Launcher Some(("update", u_matches)) => { self.task_update(u_matches).await; }, + Some(("uninstall", ui_matches)) => { + self.task_uninstall(ui_matches).await; + }, Some(("wizard", wz_matches)) => { self.to_location = Launcher::find_arg_sourcemods_location(wz_matches); self.task_wizard().await; @@ -416,6 +422,22 @@ impl Launcher } } + /// handler for the `uninstall` subcommand + /// + /// NOTE this function uses `panic!` when `UninstallWorkflow::wizard` fails. panics are handled + /// and are reported via sentry. + pub async fn task_uninstall(&mut self, matches: &ArgMatches) + { + self.to_location = Launcher::find_arg_sourcemods_location(&matches); + let mut ctx = self.try_create_context().await; + + if let Err(e) = UninstallWorkflow::wizard(&mut ctx).await { + panic!("Failed to run UninstallWorkflow {:#?}", e); + } else { + logic_done(); + } + } + /// try and create an instance of `RunnerContext` via the `create_auto` method while setting /// the `sml_via` parameter to the output of `self.try_get_smdp()` /// From 0cad5418029b5bf0c74ecac7b18313f8b7feac2a Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 23 Jul 2024 15:59:16 +0800 Subject: [PATCH 33/65] Add wizard option for UninstallWorkflow --- src/wizard.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wizard.rs b/src/wizard.rs index aa2a162..952adb6 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -4,7 +4,7 @@ use async_recursion::async_recursion; use log::{debug, error, info, trace}; use std::backtrace::Backtrace; use crate::flags::LaunchFlag; -use crate::workflows::{CleanWorkflow, InstallWorkflow, UpdateWorkflow, VerifyWorkflow}; +use crate::workflows::{CleanWorkflow, InstallWorkflow, UninstallWorkflow, UpdateWorkflow, VerifyWorkflow}; #[derive(Debug, Clone)] pub struct WizardContext @@ -69,8 +69,8 @@ impl WizardContext #[async_recursion] pub async fn menu<'a>(&'a mut self) { + let av = crate::appvar::AppVarData::get(); if self.menu_trigger_count == 0 { - let av = crate::appvar::AppVarData::get(); if let Some(cv) = self.context.current_version { let (rv, _) = self.context.latest_remote_version(); if cv < rv { @@ -83,6 +83,7 @@ impl WizardContext println!("2 - Check for and apply any available updates"); println!("3 - Verify and repair game files"); println!("c - Clean up temporary files used by beans."); + println!("u - Uninstall {}", av.mod_info.name_stylized); println!(); println!("q - Quit"); let user_input = helper::get_input("-- Enter option below --"); @@ -93,6 +94,9 @@ impl WizardContext "c" | "clean" => { Self::menu_error_catch(CleanWorkflow::wizard(&mut self.context)) }, + "u" | "uninstall" => { + Self::menu_error_catch(UninstallWorkflow::wizard(&mut self.context).await) + }, "d" | "debug" => { flags::add_flag(LaunchFlag::DEBUG_MODE); info!("Debug mode enabled!"); From be66dc16edad4fc3f8976b6d66b8290909d2904c Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 23 Jul 2024 15:59:24 +0800 Subject: [PATCH 34/65] Update uninstall.rs --- src/workflows/uninstall.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workflows/uninstall.rs b/src/workflows/uninstall.rs index 0fc9ea6..61aae62 100644 --- a/src/workflows/uninstall.rs +++ b/src/workflows/uninstall.rs @@ -11,7 +11,7 @@ impl UninstallWorkflow { { let av = AppVarData::get(); if ctx.current_version.is_none() { - info!("No version is currently installed."); + info!("{} is not installed.", av.mod_info.name_stylized); return Ok(()); } From b2471683575f218db68a3a42feaabfb65fe0dade Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Tue, 23 Jul 2024 18:17:28 +0800 Subject: [PATCH 35/65] cargo fmt --- build.rs | 30 ++-- src/appvar.rs | 55 +++--- src/butler.rs | 46 +++-- src/ctx.rs | 149 ++++++++-------- src/depends.rs | 72 ++++---- src/error.rs | 129 +++++++------- src/flags.rs | 17 +- src/gui/dialog.rs | 57 +++---- src/gui/mod.rs | 32 ++-- src/helper/linux.rs | 52 +++--- src/helper/mod.rs | 359 +++++++++++++++++++++------------------ src/helper/windows.rs | 27 ++- src/lib.rs | 19 +-- src/logger.rs | 31 ++-- src/main.rs | 140 +++++++-------- src/version.rs | 162 ++++++++++-------- src/wizard.rs | 66 +++---- src/workflows/clean.rs | 14 +- src/workflows/install.rs | 85 +++++---- src/workflows/mod.rs | 4 +- src/workflows/update.rs | 52 ++++-- src/workflows/verify.rs | 32 ++-- 22 files changed, 849 insertions(+), 781 deletions(-) diff --git a/build.rs b/build.rs index 151f3a2..eb9d9a7 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,6 @@ -#[allow(dead_code, unused_macros, unused_imports)] - - -use std::{env, io}; use std::path::PathBuf; +#[allow(dead_code, unused_macros, unused_imports)] +use std::{env, io}; #[cfg(target_os = "windows")] use winres::WindowsResource; #[allow(unused_macros)] @@ -24,8 +22,14 @@ fn fltk() -> Result<(), BuildError> { println!("cargo:rerun-if-changed=src/gui/shared_ui.fl"); let g = fl2rust::Generator::default(); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - if let Err(e) = g.in_out("src/gui/shared_ui.fl", out_path.join("shared_ui.rs").to_str().unwrap()) { - return Err(BuildError::FLTK(format!("Failed to build shared_ui.fl {:#?}", e))); + if let Err(e) = g.in_out( + "src/gui/shared_ui.fl", + out_path.join("shared_ui.rs").to_str().unwrap(), + ) { + return Err(BuildError::FLTK(format!( + "Failed to build shared_ui.fl {:#?}", + e + ))); } Ok(()) @@ -33,8 +37,7 @@ fn fltk() -> Result<(), BuildError> { /// check if a location exists #[allow(dead_code)] -fn path_exists(path: String) -> bool -{ +fn path_exists(path: String) -> bool { let p = std::path::Path::new(path.as_str()); return p.exists(); } @@ -44,8 +47,7 @@ fn path_exists(path: String) -> bool fn windows_icon() -> Result<(), BuildError> { let icon_location = OVERRIDE_ICON_LOCATION.unwrap_or("icon.ico"); if env::var_os("CARGO_CFG_WINDOWS").is_some() { - if !path_exists(icon_location.to_string()) - { + if !path_exists(icon_location.to_string()) { print!("icon.ico not found. Not embedding icon"); return Ok(()); } @@ -54,9 +56,7 @@ fn windows_icon() -> Result<(), BuildError> { .set_icon(icon_location) .compile()?; print!("successfully set icon"); - } - else - { + } else { print!("not on windows, can't embed icon"); } Ok(()) @@ -70,10 +70,10 @@ fn windows_icon() -> Result<(), BuildError> { #[derive(Debug)] pub enum BuildError { IO(io::Error), - FLTK(String) + FLTK(String), } impl From for BuildError { - fn from (e: io::Error) -> Self { + fn from(e: io::Error) -> Self { BuildError::IO(e) } } diff --git a/src/appvar.rs b/src/appvar.rs index 67edd3c..81190de 100644 --- a/src/appvar.rs +++ b/src/appvar.rs @@ -1,7 +1,7 @@ -use std::sync::RwLock; -use log::{debug, error, trace}; use crate::BeansError; use lazy_static::lazy_static; +use log::{debug, error, trace}; +use std::sync::RwLock; /// Default `appvar.json` to use. pub const JSON_DATA_DEFAULT: &str = include_str!("appvar.json"); @@ -11,21 +11,18 @@ lazy_static! { } /// Going to be deprecated, use `get_appvar()` instead. -pub fn parse() -> AppVarData -{ +pub fn parse() -> AppVarData { trace!("======== IMPORTANT NOTICE ========\ncrate::appvar::parse() is going to be deprecated, use crate::appvar::get_appvar() instead!"); AppVarData::get() } - /// Configuration for the compiled application. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AppVarData -{ +pub struct AppVarData { #[serde(rename = "mod")] pub mod_info: AppVarMod, #[serde(rename = "remote")] - pub remote_info: AppVarRemote + pub remote_info: AppVarRemote, } impl AppVarData { /// Parse `JSON_DATA` to AppVarData. Should only be called by `reset_appvar()`. @@ -47,9 +44,9 @@ impl AppVarData { } /// Substitute values in the `source` string for what is defined in here. - pub fn sub(&self, source: String) -> String - { - source.clone() + pub fn sub(&self, source: String) -> String { + source + .clone() .replace("$MOD_NAME_STYLIZED", &self.mod_info.name_stylized) .replace("$MOD_NAME_SHORT", &self.mod_info.short_name) .replace("$MOD_NAME", &self.mod_info.sourcemod_name) @@ -69,8 +66,7 @@ impl AppVarData { debug!("[AppVarData::get] Instance exists in AVD_INSTANCE, so lets return that."); return x; } - } - else if let Err(e) = avd_read { + } else if let Err(e) = avd_read { panic!("[AppVarData::get] Failed to read AVD_INSTANCE {:#?}", e); } @@ -86,8 +82,11 @@ impl AppVarData { match AVD_INSTANCE.write() { Ok(mut data) => { *data = Some(instance.clone()); - debug!("[reset_appvar] set content of AVD_INSTANCE to {:#?}", instance); - }, + debug!( + "[reset_appvar] set content of AVD_INSTANCE to {:#?}", + instance + ); + } Err(e) => { panic!("[reset_appvar] Failed to set AVD_INSTANCE! {:#?}", e); } @@ -96,15 +95,12 @@ impl AppVarData { instance } - /// Serialize `data` into JSON, then set the content of `JSON_DATA` to the serialize content. /// Once that is done, `AppVarData::reset()` will be called. /// /// If `serde_json::to_string` fails, an error is printed in console and `sentry::capture_error` /// is called. - pub fn set_json_data(data: AppVarData) - -> Result<(), BeansError> - { + pub fn set_json_data(data: AppVarData) -> Result<(), BeansError> { debug!("[set_json_data] {:#?}", data); match serde_json::to_string(&data) { Ok(v) => { @@ -114,23 +110,25 @@ impl AppVarData { } Self::reset(); Ok(()) - }, + } Err(e) => { - error!("[appvar::set_json_data] Failed to serialize data to string! {:}", e); + error!( + "[appvar::set_json_data] Failed to serialize data to string! {:}", + e + ); debug!("{:#?}", e); sentry::capture_error(&e); Err(BeansError::AppVarDataSerializeFailure { error: e, - data: data.clone() + data: data.clone(), }) } } } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AppVarMod -{ +pub struct AppVarMod { /// name of the mod to use. /// e.g; `open_fortress` #[serde(rename = "sm_name")] @@ -140,15 +138,14 @@ pub struct AppVarMod pub short_name: String, /// stylized name of the sourcemod. /// e.g; `Open Fortress` - pub name_stylized: String + pub name_stylized: String, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AppVarRemote -{ +pub struct AppVarRemote { /// base URL for the versioning. /// e.g; `https://beans.adastral.net/` pub base_url: String, /// url where the version details are stored. /// e.g; `https://beans.adastral.net/versions.json` - pub versions_url: String -} \ No newline at end of file + pub versions_url: String, +} diff --git a/src/butler.rs b/src/butler.rs index 668e781..0d017b8 100644 --- a/src/butler.rs +++ b/src/butler.rs @@ -1,32 +1,29 @@ +use crate::{depends, helper, BeansError, DownloadFailureReason}; +use log::{debug, error, info}; use std::backtrace::Backtrace; use std::process::ExitStatus; -use log::{debug, error, info}; -use crate::{BeansError, depends, DownloadFailureReason, helper}; pub fn verify( signature_url: String, gamedir: String, - remote: String + remote: String, ) -> Result { let mut cmd = std::process::Command::new(&depends::get_butler_location()); cmd.args([ "verify", &signature_url, &gamedir, - format!("--heal=archive,{}", remote).as_str() + format!("--heal=archive,{}", remote).as_str(), ]); debug!("[butler::verify] {:#?}", cmd); - match cmd - .spawn() { - Err(e) => { - Err(BeansError::ButlerVerifyFailure { - signature_url, - gamedir, - remote, - error: e, - backtrace: Backtrace::capture() - }) - }, + match cmd.spawn() { + Err(e) => Err(BeansError::ButlerVerifyFailure { + signature_url, + gamedir, + remote, + error: e, + backtrace: Backtrace::capture(), + }), Ok(mut v) => { let w = v.wait()?; debug!("[butler::verify] Exited with {:#?}", w); @@ -44,7 +41,7 @@ pub async fn patch_dl( dl_url: String, staging_dir: String, patch_filename: String, - gamedir: String + gamedir: String, ) -> Result { if helper::file_exists(staging_dir.clone()) { std::fs::remove_dir_all(&staging_dir)?; @@ -55,9 +52,7 @@ pub async fn patch_dl( if helper::file_exists(tmp_file.clone()) == false { return Err(BeansError::DownloadFailure { - reason: DownloadFailureReason::FileNotFound { - location: tmp_file - } + reason: DownloadFailureReason::FileNotFound { location: tmp_file }, }); } @@ -67,29 +62,28 @@ pub async fn patch_dl( pub fn patch( patchfile_location: String, staging_dir: String, - gamedir: String + gamedir: String, ) -> Result { let mut cmd = std::process::Command::new(&depends::get_butler_location()); cmd.args([ "apply", &format!("--staging-dir={}", &staging_dir), &patchfile_location, - &gamedir + &gamedir, ]); debug!("[butler::patch] {:#?}", &cmd); - match cmd - .spawn() { + match cmd.spawn() { Err(e) => { let xe = BeansError::ButlerPatchFailure { patchfile_location, gamedir, error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }; error!("[butler::patch] {:#?}", xe); sentry::capture_error(&xe); Err(xe) - }, + } Ok(mut v) => { let w = v.wait()?; debug!("Exited with {:#?}", w); @@ -102,4 +96,4 @@ pub fn patch( Ok(w) } } -} \ No newline at end of file +} diff --git a/src/ctx.rs b/src/ctx.rs index 0056547..389280d 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -1,41 +1,46 @@ -use std::backtrace::Backtrace; -use crate::{BeansError, depends, helper, version}; -use crate::helper::{find_sourcemod_path, InstallType, parse_location}; +use crate::helper::{find_sourcemod_path, parse_location, InstallType}; use crate::version::{RemotePatch, RemoteVersion, RemoteVersionResponse}; +use crate::{depends, helper, version, BeansError}; +use log::{debug, error, info, trace}; +use std::backtrace::Backtrace; #[cfg(target_os = "linux")] use std::os::unix::fs::PermissionsExt; -use log::{debug, error, info, trace}; #[derive(Debug, Clone)] -pub struct RunnerContext -{ +pub struct RunnerContext { pub sourcemod_path: String, pub remote_version_list: RemoteVersionResponse, pub current_version: Option, - pub appvar: crate::appvar::AppVarData + pub appvar: crate::appvar::AppVarData, } -impl RunnerContext -{ - pub async fn create_auto(sml_via: SourceModDirectoryParam) -> Result - { +impl RunnerContext { + pub async fn create_auto(sml_via: SourceModDirectoryParam) -> Result { depends::try_write_deps(); if let Err(e) = depends::try_install_vcredist().await { sentry::capture_error(&e); println!("Failed to install vcredist! {:}", e); - debug!("[RunnerContext::create_auto] Failed to install vcredist! {:#?}", e); + debug!( + "[RunnerContext::create_auto] Failed to install vcredist! {:#?}", + e + ); } - let sourcemod_path = parse_location(match sml_via - { + let sourcemod_path = parse_location(match sml_via { SourceModDirectoryParam::AutoDetect => match find_sourcemod_path() { Ok(v) => v, Err(e) => { sentry::capture_error(&e); - debug!("[RunnerContext::create_auto] Failed to find sourcemods folder. {:#?}", e); + debug!( + "[RunnerContext::create_auto] Failed to find sourcemods folder. {:#?}", + e + ); return Err(BeansError::SourceModLocationNotFound); } }, SourceModDirectoryParam::WithLocation(l) => { - debug!("[RunnerContext::create_auto] Using specified location {}", l); + debug!( + "[RunnerContext::create_auto] Using specified location {}", + l + ); l } }); @@ -45,17 +50,15 @@ impl RunnerContext version::update_version_file(Some(sourcemod_path.clone()))?; } - return Ok(Self - { + return Ok(Self { sourcemod_path: parse_location(sourcemod_path.clone()), remote_version_list: version_list, current_version: crate::version::get_current_version(Some(sourcemod_path.clone())), - appvar: crate::appvar::parse() + appvar: crate::appvar::parse(), }); } /// Sets `remote_version_list` from `version::get_version_list()` - pub async fn set_remote_version_list(&mut self) -> Result<(), BeansError> - { + pub async fn set_remote_version_list(&mut self) -> Result<(), BeansError> { self.remote_version_list = version::get_version_list().await?; Ok(()) } @@ -64,8 +67,7 @@ impl RunnerContext /// {sourcemod_dir}{crate::DATA_DIR} /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/sourcemods/open_fortress/ /// C:\Games\Steam\steamapps\sourcemods\open_fortress\ - pub fn get_mod_location(&mut self) -> String - { + pub fn get_mod_location(&mut self) -> String { helper::join_path(self.sourcemod_path.clone(), crate::data_dir()) } @@ -78,8 +80,7 @@ impl RunnerContext } /// Get the latest item in `remote_version_list` - pub fn latest_remote_version(&mut self) -> (usize, RemoteVersion) - { + pub fn latest_remote_version(&mut self) -> (usize, RemoteVersion) { let mut highest = usize::MIN; for (key, _) in self.remote_version_list.clone().versions.into_iter() { if key > highest { @@ -100,33 +101,35 @@ impl RunnerContext } } return Err(BeansError::RemoteVersionNotFound { - version: self.current_version + version: self.current_version, }); - }, - None => { - Err(BeansError::RemoteVersionNotFound { - version: self.current_version - }) } + None => Err(BeansError::RemoteVersionNotFound { + version: self.current_version, + }), } } /// When self.current_version is some, iterate through patches and fetch the patch that is available /// to bring the current version in-line with the latest version. - pub fn has_patch_available(&mut self) -> Option - { + pub fn has_patch_available(&mut self) -> Option { let current_version = self.current_version.clone(); let (remote_version, _) = self.latest_remote_version(); match current_version { Some(cv) => { for (_, patch) in self.remote_version_list.clone().patches.into_iter() { - if patch.file == format!("{}-{}to{}.pwr", &self.appvar.mod_info.short_name, cv, remote_version) { + if patch.file + == format!( + "{}-{}to{}.pwr", + &self.appvar.mod_info.short_name, cv, remote_version + ) + { return Some(patch); } } return None; - }, - _ => None + } + _ => None, } } @@ -143,7 +146,7 @@ impl RunnerContext let ex = BeansError::GameInfoFileReadFail { error: e, location: location, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }; sentry::capture_error(&ex); return Err(ex); @@ -154,7 +157,7 @@ impl RunnerContext /// Get the location of `gameinfo.txt` inside of the folder returned by `self.get_mod_location()` pub fn gameinfo_location(&mut self) -> String { - let mut location = self.get_mod_location(); + let mut location = self.get_mod_location(); location.push_str("gameinfo.txt"); location } @@ -169,12 +172,15 @@ impl RunnerContext let xe = BeansError::GameInfoPermissionSetFail { error: e, permissions: perm.clone(), - location + location, }; sentry::capture_error(&xe); return Err(xe); } - debug!("[RunnerContext::gameinfo_perms] set permissions on {location} to {:#?}", perm); + debug!( + "[RunnerContext::gameinfo_perms] set permissions on {location} to {:#?}", + perm + ); } Ok(()) } @@ -185,8 +191,7 @@ impl RunnerContext /// Download package with Progress Bar. /// Ok is the location to where it was downloaded to. - pub async fn download_package(version: RemoteVersion) -> Result - { + pub async fn download_package(version: RemoteVersion) -> Result { let av = crate::appvar::parse(); let mut out_loc = helper::get_tmp_dir(); @@ -201,16 +206,21 @@ impl RunnerContext info!("[RunnerContext::download_package] writing to {}", out_loc); helper::download_with_progress( - format!("{}{}", &av.remote_info.base_url, version.file.expect("No URL for latest package!")), - out_loc.clone()).await?; + format!( + "{}{}", + &av.remote_info.base_url, + version.file.expect("No URL for latest package!") + ), + out_loc.clone(), + ) + .await?; Ok(out_loc) } /// Extract zstd_location to the detected sourcemods directory. /// TODO replace unwrap/expect with match error handling - pub fn extract_package(zstd_location: String, out_dir: String) -> Result<(), BeansError> - { + pub fn extract_package(zstd_location: String, out_dir: String) -> Result<(), BeansError> { let tar_tmp_location = helper::get_tmp_file("data.tar".to_string()); let zstd_file = std::fs::File::open(&zstd_location)?; @@ -223,8 +233,14 @@ impl RunnerContext if helper::file_exists(tar_tmp_location.clone()) { if let Err(e) = std::fs::remove_file(tar_tmp_location.clone()) { sentry::capture_error(&e); - error!("[RunnerContext::extract_package] Failed to delete temporary file: {:}", e); - debug!("[RunnerContext::extract_package] Failed to delete {}\n{:#?}", tar_tmp_location, e); + error!( + "[RunnerContext::extract_package] Failed to delete temporary file: {:}", + e + ); + debug!( + "[RunnerContext::extract_package] Failed to delete {}\n{:#?}", + tar_tmp_location, e + ); } } match x { @@ -233,27 +249,31 @@ impl RunnerContext src_file: tar_tmp_location, target_dir: out_dir, error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }; trace!("[RunnerContext::extract_package] {:}\n{:#?}", xe, xe); sentry::capture_error(&xe); return Err(xe); - }, - Ok(_) => Ok(()) + } + Ok(_) => Ok(()), } } #[cfg(target_os = "linux")] - pub fn prepare_symlink(&mut self) -> Result<(), BeansError> - { + pub fn prepare_symlink(&mut self) -> Result<(), BeansError> { for pair in SYMLINK_FILES.into_iter() { let target: &str = pair[1]; let mod_location = self.get_mod_location(); let ln_location = format!("{}{}", mod_location, target); if helper::file_exists(ln_location.clone()) - && helper::is_symlink(ln_location.clone()) == false { + && helper::is_symlink(ln_location.clone()) == false + { if let Err(e) = std::fs::remove_file(&ln_location) { - trace!("[RunnerContext::prepare_symlink] failed to remove {}\n{:#?}", ln_location, e); + trace!( + "[RunnerContext::prepare_symlink] failed to remove {}\n{:#?}", + ln_location, + e + ); return Err(e.into()); } } @@ -262,29 +282,24 @@ impl RunnerContext Ok(()) } #[cfg(not(target_os = "linux"))] - pub fn prepare_symlink(&mut self) -> Result<(), BeansError> - { + pub fn prepare_symlink(&mut self) -> Result<(), BeansError> { // ignored since this symlink stuff is for linux only Ok(()) } } -pub const SYMLINK_FILES: &'static [&'static [&'static str; 2]] = &[ - &["bin/server.so", "bin/server_srv.so"] -]; - +pub const SYMLINK_FILES: &'static [&'static [&'static str; 2]] = + &[&["bin/server.so", "bin/server_srv.so"]]; #[derive(Clone, Debug)] -pub enum SourceModDirectoryParam -{ +pub enum SourceModDirectoryParam { /// Default value. Will autodetect location. AutoDetect, /// Use from the specified sourcemod location. - WithLocation(String) + WithLocation(String), } -impl Default for SourceModDirectoryParam -{ +impl Default for SourceModDirectoryParam { fn default() -> Self { SourceModDirectoryParam::AutoDetect } -} \ No newline at end of file +} diff --git a/src/depends.rs b/src/depends.rs index 86f311c..c46f599 100644 --- a/src/depends.rs +++ b/src/depends.rs @@ -1,14 +1,13 @@ -#[cfg(not(target_os = "windows"))] -use std::os::unix::fs::PermissionsExt; +use crate::{helper, BeansError, BUTLER_BINARY, BUTLER_LIB_1, BUTLER_LIB_2}; +use log::{debug, error}; #[cfg(target_os = "windows")] use std::backtrace::Backtrace; -use crate::{BeansError, BUTLER_BINARY, BUTLER_LIB_1, BUTLER_LIB_2, helper}; -use log::{debug, error}; +#[cfg(not(target_os = "windows"))] +use std::os::unix::fs::PermissionsExt; /// try and write aria2c and butler if it doesn't exist /// paths that are used will be fetched from binary_locations() -pub fn try_write_deps() -{ +pub fn try_write_deps() { safe_write_file(get_butler_location().as_str(), &**BUTLER_BINARY); safe_write_file(get_butler_1_location().as_str(), &**BUTLER_LIB_1); safe_write_file(get_butler_2_location().as_str(), &**BUTLER_LIB_2); @@ -17,22 +16,25 @@ pub fn try_write_deps() let p = std::fs::Permissions::from_mode(0744 as u32); if let Err(e) = std::fs::set_permissions(&get_butler_location(), p) { sentry::capture_error(&e); - error!("[depends::try_write_deps] Failed to set permissions for {}", get_butler_location()); + error!( + "[depends::try_write_deps] Failed to set permissions for {}", + get_butler_location() + ); error!("[depends::try_write_deps] {:#?}", e); } - debug!("[depends::try_write_deps] set perms on {}", get_butler_location()); + debug!( + "[depends::try_write_deps] set perms on {}", + get_butler_location() + ); } } fn safe_write_file(location: &str, data: &[u8]) { - if !helper::file_exists(location.to_string()) - { + if !helper::file_exists(location.to_string()) { if let Err(e) = std::fs::write(&location, data) { sentry::capture_error(&e); error!("[depends::try_write_deps] failed to extract {}", location); error!("[depends::try_write_deps] {:#?}", e); - } - else - { + } else { debug!("[depends::try_write_deps] extracted {}", location); } } @@ -40,25 +42,25 @@ fn safe_write_file(location: &str, data: &[u8]) { /// will not do anything since this only runs on windows #[cfg(not(target_os = "windows"))] -pub async fn try_install_vcredist() -> Result<(), BeansError> -{ +pub async fn try_install_vcredist() -> Result<(), BeansError> { // ignored since we aren't windows :3 Ok(()) } /// try to download and install vcredist from microsoft via aria2c /// TODO use request instead of aria2c for downloading this. #[cfg(target_os = "windows")] -pub async fn try_install_vcredist() -> Result<(), BeansError> -{ - if !match winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE).open_subkey(String::from("Software\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64")) { +pub async fn try_install_vcredist() -> Result<(), BeansError> { + if !match winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE).open_subkey(String::from( + "Software\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", + )) { Ok(v) => { let x: std::io::Result = v.get_value("Installed"); match x { Ok(_) => false, - Err(_) => true + Err(_) => true, } - }, - Err(_) => true + } + Err(_) => true, } { debug!("[depends::try_install_vcredist] Seems like vcredist is already installed"); return Ok(()); @@ -70,39 +72,43 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> helper::download_with_progress( String::from("https://aka.ms/vs/17/release/vc_redist.x86.exe"), - out_loc.clone()).await?; + out_loc.clone(), + ) + .await?; if std::path::Path::new(&out_loc).exists() == false { - return Err(BeansError::FileNotFound { + return Err(BeansError::FileNotFound { location: out_loc.clone(), - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } std::process::Command::new(&out_loc) - .args(["/install","/passive","/norestart"]) + .args(["/install", "/passive", "/norestart"]) .spawn() .expect("Failed to install vsredist!") .wait()?; - + if helper::file_exists(out_loc.clone()) { if let Err(e) = std::fs::remove_file(&out_loc) { sentry::capture_error(&e); - debug!("[depends::try_install_vcredist] Failed to remove installer {:#?}", e); + debug!( + "[depends::try_install_vcredist] Failed to remove installer {:#?}", + e + ); } } - + Ok(()) } pub fn butler_exists() -> bool { helper::file_exists(get_butler_location()) - && helper::file_exists(get_butler_1_location()) - && helper::file_exists(get_butler_2_location()) + && helper::file_exists(get_butler_1_location()) + && helper::file_exists(get_butler_2_location()) } -pub fn get_butler_location() -> String -{ +pub fn get_butler_location() -> String { let mut path = get_tmp_dir(); path.push_str(BUTLER_LOCATION); path @@ -134,4 +140,4 @@ const BUTLER_1: &str = "7z.so"; #[cfg(target_os = "windows")] const BUTLER_2: &str = "c7zip.dll"; #[cfg(not(target_os = "windows"))] -const BUTLER_2: &str = "libc7zip.so"; \ No newline at end of file +const BUTLER_2: &str = "libc7zip.so"; diff --git a/src/error.rs b/src/error.rs index f67ecf2..489f31c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,74 +1,66 @@ +use crate::appvar::AppVarData; +use crate::version::AdastralVersionFile; use std::backtrace::Backtrace; use std::fmt::{Display, Formatter}; use std::num::ParseIntError; use thiserror::Error; -use crate::appvar::AppVarData; -use crate::version::AdastralVersionFile; #[derive(Debug, Error)] -pub enum BeansError -{ +pub enum BeansError { /// Failed to check if there is free space. Value is the location #[error("Not enough free space in {location}")] - FreeSpaceCheckFailure { - location: String - }, + FreeSpaceCheckFailure { location: String }, /// Failed to find the sourcemod mod folder. #[error("Failed to detect sourcemod folder. Please provide it via the --location argument.")] SourceModLocationNotFound, #[error("Failed to open file at {location} ({error:})")] FileOpenFailure { location: String, - error: std::io::Error + error: std::io::Error, }, #[error("Failed to write file at {location} ({error:})")] FileWriteFailure { location: String, - error: std::io::Error + error: std::io::Error, }, #[error("Failed to create directory {location} ({error:})")] DirectoryCreateFailure { location: String, - error: std::io::Error + error: std::io::Error, }, #[error("Failed to extract {src_file} to directory {target_dir} ({error:})")] TarExtractFailure { src_file: String, target_dir: String, error: std::io::Error, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Failed to send request ({error:})")] Reqwest { error: reqwest::Error, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Failed to serialize or deserialize data ({error:})")] SerdeJson { error: serde_json::Error, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Latest version is already installed. (current: {current}, latest: {latest})")] - LatestVersionAlreadyInstalled { - current: usize, - latest: usize - }, + LatestVersionAlreadyInstalled { current: usize, latest: usize }, #[error("Failed to download file\n{reason:#?}")] - DownloadFailure { - reason: DownloadFailureReason - }, + DownloadFailure { reason: DownloadFailureReason }, #[error("General IO Error\n{error:#?}")] IO { error: std::io::Error, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Unable to perform action since the mod isn't installed since {missing_file} couldn't be found")] TargetSourcemodNotInstalled { missing_file: String, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Failed to run the verify command with butler. ({error:})")] @@ -77,7 +69,7 @@ pub enum BeansError gamedir: String, remote: String, error: std::io::Error, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Failed to run the apply command with butler. {error:}")] @@ -85,19 +77,17 @@ pub enum BeansError patchfile_location: String, gamedir: String, error: std::io::Error, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Could not find file {location}")] FileNotFound { location: String, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Version {version:#?} could not be found on the server.")] - RemoteVersionNotFound { - version: Option - }, + RemoteVersionNotFound { version: Option }, #[error("Could not find steam installation, which means we can't find the sourcemods folder. Please provide the sourcemods folder with the --location parameter.")] SteamNotFound, @@ -106,111 +96,118 @@ pub enum BeansError RegistryKeyFailure { msg: String, error: std::io::Error, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Failed to migrate old version file to the new format at {location} ({error:})")] VersionFileMigrationFailure { error: std::io::Error, - location: String + location: String, }, #[error("Failed to delete old version file {location} ({error:})")] VersionFileMigrationDeleteFailure { error: std::io::Error, - location: String + location: String, }, #[error("Failed to convert version file to JSON format. ({error:})")] VersionFileSerialize { error: serde_json::Error, - instance: AdastralVersionFile + instance: AdastralVersionFile, }, - #[error("Failed to parse the version in {old_location}. It's content was {old_content} ({error:})")] + #[error( + "Failed to parse the version in {old_location}. It's content was {old_content} ({error:})" + )] VersionFileParseFailure { error: ParseIntError, old_location: String, - old_content: String + old_content: String, }, #[error("Failed to read version file at {location}. ({error:})")] VersionFileReadFailure { error: std::io::Error, - location: String + location: String, }, #[error("Failed to serialize provided AppVarData to JSON. ({error:})")] AppVarDataSerializeFailure { error: serde_json::Error, - data: AppVarData + data: AppVarData, }, #[error("Failed to read gameinfo.txt at {location} ({error:})")] GameInfoFileReadFail { error: std::io::Error, location: String, - backtrace: Backtrace + backtrace: Backtrace, }, #[error("Failed to set permissions on gameinfo.txt at {location} ({error:})")] GameInfoPermissionSetFail { error: std::io::Error, permissions: std::fs::Permissions, - location: String + location: String, }, #[error("Failed to backup gameinfo.txt, {reason:}")] - GameinfoBackupFailure { - reason: GameinfoBackupFailureReason - }, + GameinfoBackupFailure { reason: GameinfoBackupFailureReason }, #[error("Failed to remove files in {location} ({error:})")] CleanTempFailure { location: String, - error: std::io::Error - } + error: std::io::Error, + }, } #[derive(Debug)] -pub enum DownloadFailureReason -{ +pub enum DownloadFailureReason { Reqwest { url: String, - error: reqwest::Error + error: reqwest::Error, }, /// The downloaded file could not be found, perhaps it failed? FileNotFound { - location: String - } + location: String, + }, } #[derive(Debug)] -pub enum GameinfoBackupFailureReason -{ +pub enum GameinfoBackupFailureReason { ReadContentFail(GameinfoBackupReadContentFail), BackupDirectoryCreateFailure(GameinfoBackupCreateDirectoryFail), - WriteFail(GameinfoBackupWriteFail) + WriteFail(GameinfoBackupWriteFail), } #[derive(Debug)] pub struct GameinfoBackupReadContentFail { pub error: std::io::Error, pub proposed_location: String, - pub current_location: String + pub current_location: String, } #[derive(Debug)] pub struct GameinfoBackupCreateDirectoryFail { pub error: std::io::Error, - pub location: String + pub location: String, } #[derive(Debug)] pub struct GameinfoBackupWriteFail { pub error: std::io::Error, - pub location: String + pub location: String, } impl Display for GameinfoBackupFailureReason { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - GameinfoBackupFailureReason::ReadContentFail(v) - => write!(f, "Couldn't read the content at {} ({:})", v.current_location, v.error), - GameinfoBackupFailureReason::BackupDirectoryCreateFailure(v) - => write!(f, "Couldn't create backups directory {} ({:})", v.location, v.error), - GameinfoBackupFailureReason::WriteFail(v) - => write!(f, "Failed to write content to {} ({:})", v.location, v.error) + GameinfoBackupFailureReason::ReadContentFail(v) => write!( + f, + "Couldn't read the content at {} ({:})", + v.current_location, v.error + ), + GameinfoBackupFailureReason::BackupDirectoryCreateFailure(v) => write!( + f, + "Couldn't create backups directory {} ({:})", + v.location, v.error + ), + GameinfoBackupFailureReason::WriteFail(v) => write!( + f, + "Failed to write content to {} ({:})", + v.location, v.error + ), } } } @@ -218,22 +215,22 @@ impl Display for GameinfoBackupFailureReason { pub struct TarExtractFailureDetails { pub source: String, pub target: String, - pub error: std::io::Error + pub error: std::io::Error, } impl From for BeansError { fn from(e: std::io::Error) -> Self { BeansError::IO { error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), } } } impl From for BeansError { - fn from (e: reqwest::Error) -> Self { + fn from(e: reqwest::Error) -> Self { BeansError::Reqwest { error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), } } } @@ -241,7 +238,7 @@ impl From for BeansError { fn from(e: serde_json::Error) -> Self { BeansError::SerdeJson { error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), } } -} \ No newline at end of file +} diff --git a/src/flags.rs b/src/flags.rs index 3287a69..0a2efff 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -1,6 +1,5 @@ use bitflags::bitflags; -bitflags! -{ +bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct LaunchFlag: u32 { @@ -18,16 +17,14 @@ bitflags! } pub static mut LAUNCH_FLAGS: u32 = 0x00; /// check if the `flag` provided is in `LAUNCH_FLAGS` -pub fn has_flag(flag: LaunchFlag) -> bool -{ +pub fn has_flag(flag: LaunchFlag) -> bool { unsafe { let data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); data.contains(flag) } } /// Add a flag to `LAUNCH_FLAGS` -pub fn add_flag(flag: LaunchFlag) -{ +pub fn add_flag(flag: LaunchFlag) { unsafe { match flag { LaunchFlag::DEBUG_MODE => { @@ -42,8 +39,7 @@ pub fn add_flag(flag: LaunchFlag) } } /// remove a flag from `LAUNCH_FLAGS` -pub fn remove_flag(flag: LaunchFlag) -{ +pub fn remove_flag(flag: LaunchFlag) { unsafe { match flag { LaunchFlag::DEBUG_MODE => { @@ -57,7 +53,6 @@ pub fn remove_flag(flag: LaunchFlag) } } -pub fn debug_mode() -> bool -{ +pub fn debug_mode() -> bool { has_flag(LaunchFlag::DEBUG_MODE) -} \ No newline at end of file +} diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index dae4807..e7ce0d9 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -1,32 +1,30 @@ -use fltk::{*, prelude::*}; +use crate::gui::shared_ui::GenericDialog; +use crate::gui::{apply_app_scheme, icon, wait_for_quit, window_centre_screen, GUIAppStatus}; use fltk::image::PngImage; +use fltk::{prelude::*, *}; use log::warn; -use crate::gui::{apply_app_scheme, window_centre_screen, wait_for_quit, GUIAppStatus, icon}; -use crate::gui::shared_ui::GenericDialog; pub struct DialogBuilder { pub title: String, pub content: String, - pub icon: Option + pub icon: Option, } pub enum DialogIconKind { Default, Warn, - Error + Error, } impl DialogBuilder { pub fn new() -> Self { Self { title: format!("beans v{}", crate::VERSION), content: String::new(), - icon: None + icon: None, } } pub fn with_png_data(mut self, data: &Vec) -> Self { match PngImage::from_data(data) { - Ok(img) => { - self.icon = Some(img) - }, + Ok(img) => self.icon = Some(img), Err(e) => { warn!("[DialogBuilder::with_png] Failed to set icon! {:#?}", e); } @@ -37,7 +35,7 @@ impl DialogBuilder { let data: &Vec = match kind { DialogIconKind::Default => &icon::DEFAULT_RAW_X32, DialogIconKind::Warn => &icon::DEFAULT_WARN_RAW_X32, - DialogIconKind::Error => &icon::DEFAULT_ERROR_RAW_X32 + DialogIconKind::Error => &icon::DEFAULT_ERROR_RAW_X32, }; self.with_png_data(data) } @@ -69,32 +67,29 @@ impl DialogBuilder { ui.btn_ok.emit(send_action, GUIAppStatus::Quit); let (label_w, label_h) = ui.label.measure_label(); - ui.win.set_size( - 25 + label_w + 25, - 10 + label_h + 5 + ui.btn_ok.height() + 5 - ); + ui.win + .set_size(25 + label_w + 25, 10 + label_h + 5 + ui.btn_ok.height() + 5); ui.btn_ok.set_pos(25, ui.win.height() - 24 - 5); window_centre_screen(&mut ui.win); - ui.win.handle(move |w, ev| - match ev { - fltk::enums::Event::Resize => { - let height = w.height(); - ui.btn_ok.set_pos(25, height - 24 - 5); - ui.btn_ok.set_size(70, 24); - let (lw, lh) = ui.label.measure_label(); - let cw = w.width(); - if cw != initial_width { - if cw > lw+50 { - w.set_size(lw+50, 10+lh+5+ ui.btn_ok.height() + 5); - } + ui.win.handle(move |w, ev| match ev { + fltk::enums::Event::Resize => { + let height = w.height(); + ui.btn_ok.set_pos(25, height - 24 - 5); + ui.btn_ok.set_size(70, 24); + let (lw, lh) = ui.label.measure_label(); + let cw = w.width(); + if cw != initial_width { + if cw > lw + 50 { + w.set_size(lw + 50, 10 + lh + 5 + ui.btn_ok.height() + 5); } - false - }, - _ => false - }); + } + false + } + _ => false, + }); ui.win.make_resizable(false); ui.win.show(); wait_for_quit(&app, &receive_action); } -} \ No newline at end of file +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 3bfe756..0f8a60d 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,11 +1,11 @@ -use fltk::{*, prelude::*}; use fltk::app::Receiver; use fltk::window::Window; +use fltk::{prelude::*, *}; use fltk_theme::{color_themes, ColorTheme}; use log::debug; -pub(crate) mod shared_ui; mod dialog; +pub(crate) mod shared_ui; pub use dialog::*; pub mod icon; @@ -25,7 +25,7 @@ pub enum GUIAppStatus { BtnYes, BtnNo, BtnTryAgain, - BtnContinue + BtnContinue, } /// Make the `window` provided the in be the center of the current screen. @@ -36,7 +36,12 @@ pub fn window_centre_screen(window: &mut Window) { let (mut x, mut y) = app::screen_size().clone(); x -= width.clone() as f64; y -= height.clone() as f64; - window.resize(((x / 2.0) as i32) + sx, ((y / 2.0) as i32) + sy, width, height); + window.resize( + ((x / 2.0) as i32) + sx, + ((y / 2.0) as i32) + sy, + width, + height, + ); } /// Get the X and Y position of the center of the current screen. pub fn get_center_screen() -> (i32, i32) { @@ -55,8 +60,8 @@ pub fn window_ensure(win: &mut Window, width: i32, height: i32) { w.set_size(width, height); } true - }, - _ => false + } + _ => false, }); win.make_resizable(false); win.show(); @@ -64,9 +69,12 @@ pub fn window_ensure(win: &mut Window, width: i32, height: i32) { pub fn apply_app_scheme() { let theme_content = match dark_light::detect() { dark_light::Mode::Light => color_themes::GRAY_THEME, - _ => color_themes::DARK_THEME + _ => color_themes::DARK_THEME, }; - debug!("[apply_app_scheme] using color theme: {:#?}", dark_light::detect()); + debug!( + "[apply_app_scheme] using color theme: {:#?}", + dark_light::detect() + ); let theme = ColorTheme::new(theme_content); theme.apply(); } @@ -76,11 +84,13 @@ pub fn wait_for_quit(app: &app::App, receive_action: &Receiver) { if let Some(action) = receive_action.recv() { match action { GUIAppStatus::Quit => { - unsafe { crate::PAUSE_ONCE_DONE = false; } + unsafe { + crate::PAUSE_ONCE_DONE = false; + } app.quit(); - }, + } _ => {} } } } -} \ No newline at end of file +} diff --git a/src/helper/linux.rs b/src/helper/linux.rs index 05d2330..9aff1ad 100644 --- a/src/helper/linux.rs +++ b/src/helper/linux.rs @@ -1,39 +1,37 @@ -use std::fs::read_to_string; -use log::{debug, error}; -use crate::BeansError; use crate::helper::format_directory_path; +use crate::BeansError; +use log::{debug, error}; +use std::fs::read_to_string; /// all possible known directory where steam *might* be /// only is used on linux, since windows will use the registry. -pub const STEAM_POSSIBLE_DIR: &'static [&'static str] = &[ +pub const STEAM_POSSIBLE_DIR: &'static [&'static str] = &[ "~/.steam/registry.vdf", - "~/.var/app/com.valvesoftware.Steam/.steam/registry.vdf" + "~/.var/app/com.valvesoftware.Steam/.steam/registry.vdf", ]; /// find sourcemod path on linux. /// fetches the fake registry that steam uses from find_steam_reg_path /// and gets the value of Registry/HKCU/Software/Valve/Steam/SourceModInstallPath -pub fn find_sourcemod_path() -> Result -{ +pub fn find_sourcemod_path() -> Result { let reg_path = find_steam_reg_path()?; - let reg_content = match read_to_string(reg_path.as_str()) - { + let reg_content = match read_to_string(reg_path.as_str()) { Ok(v) => v, Err(e) => { sentry::capture_error(&e); return Err(BeansError::FileOpenFailure { location: reg_path, - error: e + error: e, }); } }; for line in reg_content.lines() { - if line.contains("SourceModInstallPath") - { + if line.contains("SourceModInstallPath") { let split = &line.split("\"SourceModInstallPath\""); - let last = split.clone() + let last = split + .clone() .last() .expect("Failed to find SourceModInstallPath") .trim() @@ -45,25 +43,21 @@ pub fn find_sourcemod_path() -> Result return Err(BeansError::SourceModLocationNotFound); } /// returns the first item in STEAM_POSSIBLE_DIR that exists. otherwise None -fn find_steam_reg_path() -> Result -{ +fn find_steam_reg_path() -> Result { for x in STEAM_POSSIBLE_DIR.into_iter() { match simple_home_dir::home_dir() { - Some(v) => { - match v.to_str() { - Some(k) => { - let h = format_directory_path(k.to_string()); - let reg_loc = x.replace("~", h.as_str()); - if crate::helper::file_exists(reg_loc.clone()) - { - return Ok(reg_loc.clone()); - } - }, - None => { - debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir().to_str() returned None!"); - return Err(BeansError::SteamNotFound); + Some(v) => match v.to_str() { + Some(k) => { + let h = format_directory_path(k.to_string()); + let reg_loc = x.replace("~", h.as_str()); + if crate::helper::file_exists(reg_loc.clone()) { + return Ok(reg_loc.clone()); } } + None => { + debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir().to_str() returned None!"); + return Err(BeansError::SteamNotFound); + } }, None => { debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir() returned None!"); @@ -73,4 +67,4 @@ fn find_steam_reg_path() -> Result } error!("Couldn't find any of the locations in STEAM_POSSIBLE_DIR"); return Err(BeansError::SteamNotFound); -} \ No newline at end of file +} diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 40f7855..ec89586 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -1,30 +1,32 @@ -#[cfg(not(target_os = "windows"))] +#[cfg(not(target_os = "windows"))] mod linux; -use std::backtrace::Backtrace; #[cfg(not(target_os = "windows"))] pub use linux::*; +use std::backtrace::Backtrace; #[cfg(target_os = "windows")] mod windows; #[cfg(target_os = "windows")] pub use windows::*; - -use std::io::Write; -use std::path::PathBuf; -use indicatif::{ProgressBar, ProgressStyle}; +use crate::appvar::AppVarData; +use crate::{ + BeansError, DownloadFailureReason, GameinfoBackupCreateDirectoryFail, + GameinfoBackupFailureReason, GameinfoBackupReadContentFail, GameinfoBackupWriteFail, + RunnerContext, +}; use futures::StreamExt; +use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, trace, warn}; -use crate::{BeansError, DownloadFailureReason, GameinfoBackupCreateDirectoryFail, GameinfoBackupFailureReason, GameinfoBackupReadContentFail, GameinfoBackupWriteFail, RunnerContext}; use rand::{distributions::Alphanumeric, Rng}; use reqwest::header::USER_AGENT; -use crate::appvar::AppVarData; use std::collections::HashMap; +use std::io::Write; +use std::path::PathBuf; #[derive(Clone, Debug)] -pub enum InstallType -{ +pub enum InstallType { /// when steam/sourcemods/open_fortress/ doesn't exist NotInstalled, /// when steam/sourcemods/open_fortress/.adastral exists @@ -37,61 +39,56 @@ pub enum InstallType /// OtherSourceManual, then it will return true /// /// set when only steam/sourcemods/open_fortress/gameinfo.txt exists - OtherSourceManual + OtherSourceManual, } impl PartialEq for InstallType { fn eq(&self, other: &Self) -> bool { match self { - InstallType::NotInstalled => { - match other { - InstallType::NotInstalled => true, - InstallType::Adastral => false, - InstallType::OtherSource => false, - InstallType::OtherSourceManual => false - } + InstallType::NotInstalled => match other { + InstallType::NotInstalled => true, + InstallType::Adastral => false, + InstallType::OtherSource => false, + InstallType::OtherSourceManual => false, }, - InstallType::Adastral => { - match other { - InstallType::NotInstalled => false, - InstallType::Adastral => true, - InstallType::OtherSource => false, - InstallType::OtherSourceManual => false - } + InstallType::Adastral => match other { + InstallType::NotInstalled => false, + InstallType::Adastral => true, + InstallType::OtherSource => false, + InstallType::OtherSourceManual => false, }, - InstallType::OtherSource => { - match other { - InstallType::NotInstalled => false, - InstallType::Adastral => false, - InstallType::OtherSource => true, - InstallType::OtherSourceManual => true - } + InstallType::OtherSource => match other { + InstallType::NotInstalled => false, + InstallType::Adastral => false, + InstallType::OtherSource => true, + InstallType::OtherSourceManual => true, + }, + InstallType::OtherSourceManual => match other { + InstallType::NotInstalled => false, + InstallType::Adastral => false, + InstallType::OtherSource => false, + InstallType::OtherSourceManual => true, }, - InstallType::OtherSourceManual => { - match other { - InstallType::NotInstalled => false, - InstallType::Adastral => false, - InstallType::OtherSource => false, - InstallType::OtherSourceManual => true - } - } } } } /// get the current type of installation. -pub fn install_state(sourcemods_location: Option) -> InstallType -{ +pub fn install_state(sourcemods_location: Option) -> InstallType { let mut smp_x = match sourcemods_location { Some(v) => v, None => match find_sourcemod_path() { Ok(v) => v, Err(e) => { sentry::capture_error(&e); - debug!("[helper::install_state] {} {:#?}", BeansError::SourceModLocationNotFound, e); + debug!( + "[helper::install_state] {} {:#?}", + BeansError::SourceModLocationNotFound, + e + ); return InstallType::NotInstalled; } - } + }, }; if smp_x.ends_with("/") || smp_x.ends_with("\\") { smp_x.pop(); @@ -101,40 +98,34 @@ pub fn install_state(sourcemods_location: Option) -> InstallType if file_exists(format!("{}.adastral", data_dir)) { return InstallType::Adastral; - } - else if file_exists(format!("{}.revision", data_dir)) { + } else if file_exists(format!("{}.revision", data_dir)) { return InstallType::OtherSource; - } - else if file_exists(format!("{}gameinfo.txt", data_dir)) { + } else if file_exists(format!("{}gameinfo.txt", data_dir)) { return InstallType::OtherSourceManual; } return InstallType::NotInstalled; } /// get user input from terminal. prompt is displayed on the line above where the user does input. -pub fn get_input(prompt: &str) -> String{ - println!("{}",prompt); +pub fn get_input(prompt: &str) -> String { + println!("{}", prompt); let mut input = String::new(); match std::io::stdin().read_line(&mut input) { - Ok(_goes_into_input_above) => {}, - Err(_no_updates_is_fine) => {}, + Ok(_goes_into_input_above) => {} + Err(_no_updates_is_fine) => {} } input.trim().to_string() } - /// check if a file exists -pub fn file_exists(location: String) -> bool -{ +pub fn file_exists(location: String) -> bool { std::path::Path::new(&location).exists() } /// Check if the location provided exists and it's a directory. -pub fn dir_exists(location: String) -> bool -{ +pub fn dir_exists(location: String) -> bool { file_exists(location.clone()) && is_directory(location.clone()) } -pub fn is_directory(location: String) -> bool -{ +pub fn is_directory(location: String) -> bool { let x = PathBuf::from(&location); x.is_dir() } @@ -143,12 +134,11 @@ pub fn is_directory(location: String) -> bool pub fn is_symlink(location: String) -> bool { match std::fs::symlink_metadata(&location) { Ok(meta) => meta.file_type().is_symlink(), - Err(_) => false + Err(_) => false, } } -pub fn generate_rand_str(length: usize) -> String -{ +pub fn generate_rand_str(length: usize) -> String { let s: String = rand::thread_rng() .sample_iter(Alphanumeric) .take(length) @@ -159,9 +149,9 @@ pub fn generate_rand_str(length: usize) -> String /// Join the path, using `tail` as the base, and `head` as the thing to add on top of it. /// /// This will also convert backslashes/forwardslashes to the compiled separator in `crate::PATH_SEP` -pub fn join_path(tail: String, head: String) -> String -{ - let mut h = head.to_string() +pub fn join_path(tail: String, head: String) -> String { + let mut h = head + .to_string() .replace("/", crate::PATH_SEP) .replace("\\", crate::PATH_SEP); while h.starts_with(crate::PATH_SEP) { @@ -170,8 +160,7 @@ pub fn join_path(tail: String, head: String) -> String format!("{}{}", format_directory_path(tail), h) } -pub fn remove_path_head(location: String) -> String -{ +pub fn remove_path_head(location: String) -> String { let p = std::path::Path::new(&location); if let Some(x) = p.parent() { if let Some(m) = x.to_str() { @@ -181,9 +170,9 @@ pub fn remove_path_head(location: String) -> String return String::new(); } /// Make sure that the location provided is formatted as a directory (ends with `crate::PATH_SEP`). -pub fn format_directory_path(location: String) -> String -{ - let mut x = location.to_string() +pub fn format_directory_path(location: String) -> String { + let mut x = location + .to_string() .replace("/", crate::PATH_SEP) .replace("\\", crate::PATH_SEP); while x.ends_with(crate::PATH_SEP) { @@ -203,20 +192,20 @@ pub fn canonicalize(location: &str) -> Result { pub fn canonicalize(location: &str) -> Result { dunce::canonicalize(location) } -pub fn parse_location(location: String) -> String -{ +pub fn parse_location(location: String) -> String { let path = std::path::Path::new(&location); let real_location = match path.to_str() { Some(v) => { let p = canonicalize(v); match p { - Ok(x) => { - match x.clone().to_str() { - Some(m) => m.to_string(), - None => { - debug!("[helper::parse_location] Failed to parse location to string {}", location); - return location; - } + Ok(x) => match x.clone().to_str() { + Some(m) => m.to_string(), + None => { + debug!( + "[helper::parse_location] Failed to parse location to string {}", + location + ); + return location; } }, Err(e) => { @@ -224,15 +213,21 @@ pub fn parse_location(location: String) -> String return location; } sentry::capture_error(&e); - eprintln!("[helper::parse_location] Failed to canonicalize location {}", location); + eprintln!( + "[helper::parse_location] Failed to canonicalize location {}", + location + ); eprintln!("[helper::parse_location] {:}", e); debug!("{:#?}", e); return location; } } - }, + } None => { - debug!("[helper::parse_location] Failed to parse location {}", location); + debug!( + "[helper::parse_location] Failed to parse location {}", + location + ); return location; } }; @@ -240,8 +235,7 @@ pub fn parse_location(location: String) -> String } /// Get the amount of free space on the drive in the location provided. -pub fn get_free_space(location: String) -> Result -{ +pub fn get_free_space(location: String) -> Result { let mut data: HashMap = HashMap::new(); for disk in sysinfo::Disks::new_with_refreshed_list().list() { if let Some(mp) = disk.mount_point().to_str() { @@ -260,32 +254,27 @@ pub fn get_free_space(location: String) -> Result } Err(BeansError::FreeSpaceCheckFailure { - location: parse_location(location.clone()) + location: parse_location(location.clone()), }) } /// Check if the location provided has enough free space. -pub fn has_free_space(location: String, size: usize) -> Result -{ +pub fn has_free_space(location: String, size: usize) -> Result { let space = get_free_space(location)?; return Ok((size as u64) < space); } /// Download file at the URL provided to the output location provided /// This function will also show a progress bar with indicatif. -pub async fn download_with_progress(url: String, out_location: String) -> Result<(), BeansError> -{ - let res = match reqwest::Client::new() - .get(&url) - .send() - .await { +pub async fn download_with_progress(url: String, out_location: String) -> Result<(), BeansError> { + let res = match reqwest::Client::new().get(&url).send().await { Ok(v) => v, Err(e) => { sentry::capture_error(&e); return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::Reqwest { url: url.clone(), - error: e - } + error: e, + }, }); } }; @@ -308,7 +297,7 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result sentry::capture_error(&e); return Err(BeansError::FileOpenFailure { location: out_location, - error: e + error: e, }); } }; @@ -348,7 +337,8 @@ pub fn format_size(i: usize) -> String { dec_l = decimal_points * 5; } - let dec: String = value.chars() + let dec: String = value + .chars() .into_iter() .rev() .take(dec_l as usize) @@ -368,7 +358,8 @@ pub fn format_size(i: usize) -> String { (1_000_000, "kb"), (1_000_000_000, "mb"), (1_000_000_000_000, "gb"), - (1_000_000_000_000_000, "tb")]; + (1_000_000_000_000_000, "tb"), + ]; for (s, c) in pfx_data.into_iter() { if i < s { return format!("{}{}{}", whole, dec_x, c); @@ -378,41 +369,40 @@ pub fn format_size(i: usize) -> String { } /// Check if we should use the custom temporary directory, which is stored in the environment variable /// defined in `CUSTOM_TMPDIR_NAME`. -/// +/// /// ## Return -/// `Some` when the environment variable is set, and the directory exist. +/// `Some` when the environment variable is set, and the directory exist. /// Otherwise `None` is returned. -pub fn use_custom_tmpdir() -> Option -{ +pub fn use_custom_tmpdir() -> Option { if let Ok(x) = std::env::var(CUSTOM_TMPDIR_NAME) { let s = x.to_string(); if dir_exists(s.clone()) { return Some(s); } else { - warn!("[use_custom_tmp_dir] Custom temporary directory \"{}\" doesn't exist", s); + warn!( + "[use_custom_tmp_dir] Custom temporary directory \"{}\" doesn't exist", + s + ); } } return None; } pub const CUSTOM_TMPDIR_NAME: &str = "ADASTRAL_TMPDIR"; /// Create directory in temp directory with name of "beans-rs" -pub fn get_tmp_dir() -> String -{ +pub fn get_tmp_dir() -> String { let mut dir = std::env::temp_dir().to_str().unwrap_or("").to_string(); if let Some(x) = use_custom_tmpdir() { dir = x; } else if is_steamdeck() { trace!("[helper::get_tmp_dir] Detected that we are running on a steam deck. Using ~/.tmp/beans-rs"); match simple_home_dir::home_dir() { - Some(v) => { - match v.to_str() { - Some(k) => { - dir = format_directory_path(k.to_string()); - dir = join_path(dir, String::from(".tmp")); - }, - None => { - trace!("[helper::get_tmp_dir] Failed to convert PathBuf to &str"); - } + Some(v) => match v.to_str() { + Some(k) => { + dir = format_directory_path(k.to_string()); + dir = join_path(dir, String::from(".tmp")); + } + None => { + trace!("[helper::get_tmp_dir] Failed to convert PathBuf to &str"); } }, None => { @@ -428,7 +418,10 @@ pub fn get_tmp_dir() -> String if !dir_exists(dir.clone()) { if let Err(e) = std::fs::create_dir(&dir) { trace!("[helper::get_tmp_dir] {:#?}", e); - warn!("[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", dir, e); + warn!( + "[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", + dir, e + ); } } dir = join_path(dir, String::from("beans-rs")); @@ -437,7 +430,10 @@ pub fn get_tmp_dir() -> String if !dir_exists(dir.clone()) { if let Err(e) = std::fs::create_dir(&dir) { trace!("[helper::get_tmp_dir] {:#?}", e); - warn!("[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", dir, e); + warn!( + "[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", + dir, e + ); sentry::capture_error(&e); } else { trace!("[helper::get_tmp_dir] created directory {}", dir); @@ -447,7 +443,7 @@ pub fn get_tmp_dir() -> String return dir; } /// Check if the content of `uname -r` contains `valve` (Linux Only) -/// +/// /// ## Returns /// - `true` when; /// - The output of `uname -r` contains `valve` @@ -455,10 +451,10 @@ pub fn get_tmp_dir() -> String /// - `target_os` is not `linux` /// - Failed to run `uname -r` /// - Failed to parse the stdout of `uname -r` as a String. -/// +/// /// ## Note /// Will always return `false` when `cfg!(not(target_os = "linux"))`. -/// +/// /// This function will write to `log::trace` with the full error details before writing it to `log::warn` or `log::error`. Since errors from this /// aren't significant, `sentry::capture_error` will not be called. pub fn is_steamdeck() -> bool { @@ -478,13 +474,13 @@ pub fn is_steamdeck() -> bool { Ok(x) => { trace!("[helper::is_steamdeck] stdout: {}", x); x.contains("valve") - }, + } Err(e) => { trace!("[helper::is_steamdeck] Failed to parse as utf8 {:#?}", e); false } } - }, + } Err(e) => { trace!("[helper::is_steamdeck] {:#?}", e); warn!("[helper::is_steamdeck] Failed to detect {:}", e); @@ -493,25 +489,24 @@ pub fn is_steamdeck() -> bool { } } /// Generate a full file location for a temporary file. -pub fn get_tmp_file(filename: String) -> String -{ +pub fn get_tmp_file(filename: String) -> String { let head = format!("{}_{}", generate_rand_str(8), filename); join_path(get_tmp_dir(), head) } /// Check if there is an update available. When the latest release doesn't match the current release. -pub async fn beans_has_update() -> Result, BeansError> -{ +pub async fn beans_has_update() -> Result, BeansError> { let rs = reqwest::Client::new() .get(GITHUB_RELEASES_URL) .header(USER_AGENT, &format!("beans-rs/{}", crate::VERSION)) - .send().await; + .send() + .await; let response = match rs { Ok(v) => v, Err(e) => { trace!("Failed get latest release from github \nerror: {:#?}", e); - return Err(BeansError::Reqwest{ + return Err(BeansError::Reqwest { error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } }; @@ -519,15 +514,22 @@ pub async fn beans_has_update() -> Result, BeansError> let data: GithubReleaseItem = match serde_json::from_str(&response_text) { Ok(v) => v, Err(e) => { - trace!("Failed to deserialize GithubReleaseItem\nerror: {:#?}\ncontent: {:#?}", e, response_text); + trace!( + "Failed to deserialize GithubReleaseItem\nerror: {:#?}\ncontent: {:#?}", + e, + response_text + ); return Err(BeansError::SerdeJson { error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } }; trace!("{:#?}", data); - if data.draft == false && data.prerelease == false && data.tag_name != format!("v{}", crate::VERSION) { + if data.draft == false + && data.prerelease == false + && data.tag_name != format!("v{}", crate::VERSION) + { return Ok(Some(data.clone())); } return Ok(None); @@ -539,16 +541,25 @@ pub fn restore_gameinfo(ctx: &mut RunnerContext, data: Vec) -> Result<(), Be trace!("gameinfo metadata: {:#?}", m); } if let Err(e) = ctx.gameinfo_perms() { - error!("[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", e); + error!( + "[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", + e + ); sentry::capture_error(&e); return Err(e); } if let Err(e) = std::fs::write(&loc, data) { trace!("error: {:#?}", e); - error!("[helper::restore_gameinfo] Failed to write gameinfo.txt backup {:}", e); + error!( + "[helper::restore_gameinfo] Failed to write gameinfo.txt backup {:}", + e + ); } if let Err(e) = ctx.gameinfo_perms() { - error!("[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", e); + error!( + "[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", + e + ); sentry::capture_error(&e); return Err(e); } @@ -566,22 +577,35 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { if let Err(e) = std::fs::create_dir(&backupdir) { debug!("backupdir: {}", backupdir); debug!("error: {:#?}", e); - error!("[helper::backup_gameinfo] Failed to create backup directory {:}", e); - return Err(BeansError::GameinfoBackupFailure {reason: GameinfoBackupFailureReason::BackupDirectoryCreateFailure(GameinfoBackupCreateDirectoryFail { - error: e, - location: backupdir - })}); + error!( + "[helper::backup_gameinfo] Failed to create backup directory {:}", + e + ); + return Err(BeansError::GameinfoBackupFailure { + reason: GameinfoBackupFailureReason::BackupDirectoryCreateFailure( + GameinfoBackupCreateDirectoryFail { + error: e, + location: backupdir, + }, + ), + }); } } let output_location = join_path( backupdir, - format!("{}-{}.txt", ctx.current_version.unwrap_or(0), current_time_formatted)); - let current_location = join_path( - gamedir, - String::from("gameinfo.txt")); + format!( + "{}-{}.txt", + ctx.current_version.unwrap_or(0), + current_time_formatted + ), + ); + let current_location = join_path(gamedir, String::from("gameinfo.txt")); if file_exists(current_location.clone()) == false { - debug!("[helper::backup_gameinfo] can't backup since {} doesn't exist", current_location); + debug!( + "[helper::backup_gameinfo] can't backup since {} doesn't exist", + current_location + ); return Ok(()); } @@ -590,12 +614,19 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { Err(e) => { debug!("location: {}", current_location); debug!("error: {:#?}", e); - error!("[helper::backup_gameinfo] Failed to read content of gameinfo.txt {:}", e); - return Err(BeansError::GameinfoBackupFailure { reason: GameinfoBackupFailureReason::ReadContentFail(GameinfoBackupReadContentFail{ - error: e, - proposed_location: output_location, - current_location: current_location.clone() - })}) + error!( + "[helper::backup_gameinfo] Failed to read content of gameinfo.txt {:}", + e + ); + return Err(BeansError::GameinfoBackupFailure { + reason: GameinfoBackupFailureReason::ReadContentFail( + GameinfoBackupReadContentFail { + error: e, + proposed_location: output_location, + current_location: current_location.clone(), + }, + ), + }); } }; @@ -608,11 +639,16 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { if let Err(e) = std::fs::write(&output_location, content) { debug!("location: {}", output_location); debug!("error: {:#?}", e); - error!("[helper::backup_gameinfo] Failed to write backup to {} ({:})", output_location, e); - return Err(BeansError::GameinfoBackupFailure { reason: GameinfoBackupFailureReason::WriteFail(GameinfoBackupWriteFail{ - error: e, - location: output_location - })}); + error!( + "[helper::backup_gameinfo] Failed to write backup to {} ({:})", + output_location, e + ); + return Err(BeansError::GameinfoBackupFailure { + reason: GameinfoBackupFailureReason::WriteFail(GameinfoBackupWriteFail { + error: e, + location: output_location, + }), + }); } println!("[backup_gameinfo] Created backup at {}", output_location); @@ -622,8 +658,7 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { const GAMEINFO_BACKUP_DIRNAME: &str = "gameinfo_backup"; const GITHUB_RELEASES_URL: &str = "https://api.github.com/repositories/805393469/releases/latest"; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GithubReleaseItem -{ +pub struct GithubReleaseItem { #[serde(rename = "id")] pub _id: u64, pub created_at: String, @@ -631,12 +666,11 @@ pub struct GithubReleaseItem pub url: String, pub html_url: String, pub draft: bool, - pub prerelease: bool + pub prerelease: bool, } /// Return `true` when `try_get_env_var` returns Some with a length greater than `1`. -pub fn has_env_var(target_key: String) -> bool -{ +pub fn has_env_var(target_key: String) -> bool { if let Some(x) = try_get_env_var(target_key) { return x.len() > 1; } @@ -644,12 +678,11 @@ pub fn has_env_var(target_key: String) -> bool } /// Try and get a value from `std::env::vars()` /// Will return `None` when not found -pub fn try_get_env_var(target_key: String) -> Option -{ +pub fn try_get_env_var(target_key: String) -> Option { for (key, value) in std::env::vars().into_iter() { if key == target_key { return Some(value); } } return None; -} \ No newline at end of file +} diff --git a/src/helper/windows.rs b/src/helper/windows.rs index cf17b30..0deb880 100644 --- a/src/helper/windows.rs +++ b/src/helper/windows.rs @@ -1,36 +1,35 @@ -use winreg::enums::HKEY_CURRENT_USER; +use crate::helper::format_directory_path; +use crate::BeansError; use std::backtrace::Backtrace; +use winreg::enums::HKEY_CURRENT_USER; use winreg::RegKey; -use crate::BeansError; -use crate::helper::format_directory_path; /// TODO use windows registry to get the SourceModInstallPath /// HKEY_CURRENT_USER\Software\Value\Steam /// Key: SourceModInstallPath -pub fn find_sourcemod_path() -> Result -{ +pub fn find_sourcemod_path() -> Result { match RegKey::predef(HKEY_CURRENT_USER).open_subkey(String::from("Software\\Valve\\Steam")) { Ok(rkey) => { let x: std::io::Result = rkey.get_value("SourceModInstallPath"); match x { - Ok(val) => { - Ok(format_directory_path(val)) - }, + Ok(val) => Ok(format_directory_path(val)), Err(e) => { return Err(BeansError::RegistryKeyFailure { - msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed".to_string(), + msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" + .to_string(), error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } } - }, + } Err(e) => { return Err(BeansError::RegistryKeyFailure { - msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed".to_string(), + msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" + .to_string(), error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 78a656d..f8eed52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,20 +3,20 @@ use include_flate::flate; +mod ctx; pub mod depends; pub mod helper; -pub mod wizard; pub mod version; +pub mod wizard; pub mod workflows; -mod ctx; pub use ctx::*; mod error; pub use error::*; +pub mod appvar; pub mod butler; pub mod flags; -pub mod appvar; -pub mod logger; pub mod gui; +pub mod logger; /// NOTE do not change, fetches from the version of beans-rs on build pub const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -31,7 +31,6 @@ pub static mut PAUSE_ONCE_DONE: bool = false; /// When `true`, everything that prompts the user for Y/N should use the default option. pub static mut PROMPT_DO_WHATEVER: bool = false; - // ------------------------------------------------------------------------ // please dont change consts below unless you know what you're doing <3 // @@ -42,20 +41,18 @@ pub const PATH_SEP: &str = "/"; #[cfg(target_os = "windows")] pub const PATH_SEP: &str = "\\"; -pub fn data_dir() -> String -{ +pub fn data_dir() -> String { let av = appvar::parse(); format!("{}{}{}", PATH_SEP, av.mod_info.sourcemod_name, PATH_SEP) } /// Check if we have GUI support enabled. Will always return `false` when `PAUSE_ONCE_DONE` is `false`. -/// +/// /// Will return `true` when /// - Running on Windows /// - Running on macOS /// - Running on Linux AND the `DISPLAY` or `XDG_SESSION_DESKTOP` environment variables are set. -pub fn has_gui_support() -> bool -{ +pub fn has_gui_support() -> bool { unsafe { if PAUSE_ONCE_DONE == false { return false; @@ -75,7 +72,7 @@ pub fn has_gui_support() -> bool } } return false; - }, + } _ => { log::warn!("Unsupported platform for GUI {}", std::env::consts::OS); false diff --git a/src/logger.rs b/src/logger.rs index 068dcbd..478853d 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,9 +1,9 @@ +use lazy_static::lazy_static; +use log::{LevelFilter, Log, Metadata, Record}; use std::io; use std::io::Write; use std::sync::Mutex; use std::time::Instant; -use lazy_static::lazy_static; -use log::{LevelFilter, Log, Metadata, Record}; lazy_static! { static ref LOGGER: CustomLogger = CustomLogger { @@ -25,7 +25,7 @@ impl CustomLogger { log::Level::Error => LogFilter::Exception, log::Level::Warn => LogFilter::Event, log::Level::Info | log::Level::Debug | log::Level::Trace => LogFilter::Breadcrumb, - }) + }), }); } } @@ -55,7 +55,7 @@ impl Log for CustomLogger { struct CustomLoggerInner { start: Instant, sink: Box, - sentry: sentry_log::SentryLogger + sentry: sentry_log::SentryLogger, } use colored::Colorize; use sentry_log::{LogFilter, NoopLogger}; @@ -68,8 +68,7 @@ impl CustomLoggerInner { do_print = false; } } - if do_print - { + if do_print { let now = self.start.elapsed(); let seconds = now.as_secs(); let hours = seconds / 3600; @@ -82,7 +81,8 @@ impl CustomLoggerInner { unsafe { data = LOG_FORMAT.to_string(); } - data = data.replace("#HOURS", &format!("{:02}", hours)) + data = data + .replace("#HOURS", &format!("{:02}", hours)) .replace("#MINUTES", &format!("{:02}", minutes)) .replace("#SECONDS", &format!("{:02}", seconds)) .replace("#MILLISECONDS", &format!("{:03}", milliseconds)) @@ -98,21 +98,17 @@ impl CustomLoggerInner { log::Level::Info => data.normal(), log::Level::Debug => data.green(), log::Level::Trace => data.blue(), - }.to_string() + } + .to_string() } } - let _ = write!( - self.sink, - "{}\n", - data - ); + let _ = write!(self.sink, "{}\n", data); } self.sentry.log(&record); } } -pub fn set_filter(filter: LevelFilter) -{ +pub fn set_filter(filter: LevelFilter) { unsafe { LOG_FILTER = filter; } @@ -120,7 +116,8 @@ pub fn set_filter(filter: LevelFilter) static mut LOG_FILTER: LevelFilter = LevelFilter::Trace; pub static mut LOG_FORMAT: &str = LOG_FORMAT_DEFAULT; pub static mut LOG_COLOR: bool = true; -pub const LOG_FORMAT_DEFAULT: &str = "[#HOURS:#MINUTES:#SECONDS.#MILLISECONDS] (#THREAD) #LEVEL #CONTENT"; +pub const LOG_FORMAT_DEFAULT: &str = + "[#HOURS:#MINUTES:#SECONDS.#MILLISECONDS] (#THREAD) #LEVEL #CONTENT"; pub const LOG_FORMAT_MINIMAL: &str = "#LEVEL #CONTENT"; pub fn log_to(sink: T) { LOGGER.renew(sink); @@ -131,4 +128,4 @@ pub fn log_to(sink: T) { } pub fn log_to_stdout() { log_to(io::stdout()); -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index ee5ceb9..1ab542b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,14 @@ #![feature(panic_info_message)] -use std::str::FromStr; -use clap::{Arg, ArgAction, ArgMatches, Command}; -use log::{debug, error, info, LevelFilter, trace}; -use beans_rs::{flags, helper, PANIC_MSG_CONTENT, RunnerContext, wizard}; use beans_rs::flags::LaunchFlag; use beans_rs::gui::DialogIconKind; use beans_rs::helper::parse_location; -use beans_rs::SourceModDirectoryParam; use beans_rs::workflows::{CleanWorkflow, InstallWorkflow, UpdateWorkflow, VerifyWorkflow}; +use beans_rs::SourceModDirectoryParam; +use beans_rs::{flags, helper, wizard, RunnerContext, PANIC_MSG_CONTENT}; +use clap::{Arg, ArgAction, ArgMatches, Command}; +use log::{debug, error, info, trace, LevelFilter}; +use std::str::FromStr; pub const DEFAULT_LOG_LEVEL_RELEASE: LevelFilter = LevelFilter::Info; #[cfg(debug_assertions)] @@ -25,14 +25,17 @@ fn main() { init_flags(); // initialize sentry and custom panic handler for msgbox #[cfg(not(debug_assertions))] - let _guard = sentry::init((beans_rs::SENTRY_URL, sentry::ClientOptions { - release: sentry::release_name!(), - debug: flags::has_flag(LaunchFlag::DEBUG_MODE), - max_breadcrumbs: 100, - auto_session_tracking: true, - attach_stacktrace: true, - ..Default::default() - })); + let _guard = sentry::init(( + beans_rs::SENTRY_URL, + sentry::ClientOptions { + release: sentry::release_name!(), + debug: flags::has_flag(LaunchFlag::DEBUG_MODE), + max_breadcrumbs: 100, + auto_session_tracking: true, + attach_stacktrace: true, + ..Default::default() + }, + )); init_panic_handle(); tokio::runtime::Builder::new_multi_thread() @@ -43,8 +46,7 @@ fn main() { Launcher::run().await; }); } -fn init_flags() -{ +fn init_flags() { flags::remove_flag(LaunchFlag::DEBUG_MODE); #[cfg(debug_assertions)] flags::add_flag(LaunchFlag::DEBUG_MODE); @@ -55,8 +57,7 @@ fn init_flags() beans_rs::logger::set_filter(DEFAULT_LOG_LEVEL); beans_rs::logger::log_to_stdout(); } -fn init_panic_handle() -{ +fn init_panic_handle() { std::panic::set_hook(Box::new(move |info| { debug!("[panic::set_hook] showing msgbox to notify user"); let mut x = String::new(); @@ -73,14 +74,16 @@ fn init_panic_handle() logic_done(); })); } -fn custom_panic_handle(msg: String) -{ +fn custom_panic_handle(msg: String) { unsafe { if beans_rs::PAUSE_ONCE_DONE == false { return; } } - let txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg).replace("\\n", "\n"); + let txt = PANIC_MSG_CONTENT + .to_string() + .replace("$err_msg", &msg) + .replace("\\n", "\n"); beans_rs::gui::DialogBuilder::new() .with_title(String::from("beans - Fatal Error!")) .with_icon(DialogIconKind::Error) @@ -89,8 +92,7 @@ fn custom_panic_handle(msg: String) } /// should called once the logic flow is done! /// will call `helper::get_input` when `PAUSE_ONCE_DONE` is `true`. -fn logic_done() -{ +fn logic_done() { unsafe { if beans_rs::PAUSE_ONCE_DONE { let _ = helper::get_input("Press enter/return to exit"); @@ -101,28 +103,24 @@ pub struct Launcher { /// Output location. When none, `SourceModDirectoryParam::default()` will be used. pub to_location: Option, /// Output of `Command.matches()` - pub root_matches: ArgMatches + pub root_matches: ArgMatches, } -impl Launcher -{ +impl Launcher { /// Create argument for specifying the location where the sourcemods directory is. - fn create_location_arg() -> Arg - { + fn create_location_arg() -> Arg { Arg::new("location") .long("location") .help("Manually specify sourcemods directory. When not provided, beans-rs will automatically detect the sourcemods directory.") .required(false) } - fn create_confirm_arg() -> Arg - { + fn create_confirm_arg() -> Arg { Arg::new("confirm") .long("confirm") .help("When prompted to do something (as a multi-choice option), the default option will be automatically chosen when this switch is provided, and there is a default multi-choice option available.") .required(false) .action(ArgAction::SetTrue) } - pub async fn run() - { + pub async fn run() { let cmd = Command::new("beans-rs") .version(clap::crate_version!()) .bin_name(clap::crate_name!()) @@ -179,7 +177,7 @@ impl Launcher pub fn new(matches: &ArgMatches) -> Self { let mut i = Self { to_location: None, - root_matches: matches.clone() + root_matches: matches.clone(), }; i.set_debug(); i.set_no_pause(); @@ -190,30 +188,26 @@ impl Launcher } /// add `LaunchFlag::DEBUG_MODE` to `flags` when the `--debug` parameter flag is used. - pub fn set_debug(&mut self) - { + pub fn set_debug(&mut self) { if self.root_matches.get_flag("no-debug") { flags::remove_flag(LaunchFlag::DEBUG_MODE); beans_rs::logger::set_filter(DEFAULT_LOG_LEVEL_RELEASE); info!("Disabled Debug Mode"); - } - else if self.root_matches.get_flag("debug") { + } else if self.root_matches.get_flag("debug") { flags::add_flag(LaunchFlag::DEBUG_MODE); beans_rs::logger::set_filter(LevelFilter::max()); trace!("Debug mode enabled"); } } /// Set `PAUSE_ONCE_DONE` to `false` when `--no-pause` is provided. Otherwise, set it to `true`. - pub fn set_no_pause(&mut self) - { + pub fn set_no_pause(&mut self) { unsafe { beans_rs::PAUSE_ONCE_DONE = self.root_matches.get_flag("no-pause") == false; } } /// Set `self.to_location` when provided in the arguments. - pub fn find_arg_sourcemods_location(matches: &ArgMatches) -> Option - { + pub fn find_arg_sourcemods_location(matches: &ArgMatches) -> Option { let mut sml_dir_manual: Option = None; if let Some(x) = matches.get_one::("location") { sml_dir_manual = Some(parse_location(x.to_string())); @@ -223,33 +217,31 @@ impl Launcher } /// main handler for subcommand processing. - pub async fn subcommand_processor(&mut self) - { + pub async fn subcommand_processor(&mut self) { match self.root_matches.clone().subcommand() { Some(("install", i_matches)) => { self.task_install(i_matches).await; - }, + } Some(("verify", v_matches)) => { self.task_verify(v_matches).await; - }, + } Some(("update", u_matches)) => { self.task_update(u_matches).await; - }, + } Some(("wizard", wz_matches)) => { self.to_location = Launcher::find_arg_sourcemods_location(wz_matches); self.task_wizard().await; - }, + } Some(("clean-tmp", _)) => { self.task_clean_tmp().await; - }, + } _ => { self.task_wizard().await; } } } - pub fn set_prompt_do_whatever(&mut self) - { + pub fn set_prompt_do_whatever(&mut self) { if self.root_matches.get_flag("confirm") { unsafe { beans_rs::PROMPT_DO_WHATEVER = true; @@ -259,19 +251,15 @@ impl Launcher /// Try and get `SourceModDirectoryParam`. /// Returns SourceModDirectoryParam::default() when `to_location` is `None`. - fn try_get_smdp(&mut self) -> SourceModDirectoryParam - { + fn try_get_smdp(&mut self) -> SourceModDirectoryParam { match &self.to_location { - Some(v) => { - SourceModDirectoryParam::WithLocation(v.to_string()) - }, - None => SourceModDirectoryParam::default() + Some(v) => SourceModDirectoryParam::WithLocation(v.to_string()), + None => SourceModDirectoryParam::default(), } } /// handler for the `wizard` subcommand. it's also the default subcommand. - pub async fn task_wizard(&mut self) - { + pub async fn task_wizard(&mut self) { let x = self.try_get_smdp(); if let Err(e) = wizard::WizardContext::run(x).await { panic!("Failed to run WizardContext {:#?}", e); @@ -284,8 +272,7 @@ impl Launcher /// /// NOTE this function uses `panic!` when `InstallWorkflow::wizard` fails. panics are handled /// and are reported via sentry. - pub async fn task_install(&mut self, matches: &ArgMatches) - { + pub async fn task_install(&mut self, matches: &ArgMatches) { self.to_location = Launcher::find_arg_sourcemods_location(&matches); if matches.get_flag("confirm") { unsafe { @@ -303,13 +290,18 @@ impl Launcher if let Some(x) = matches.get_one::("target-version") { self.task_install_version_specific(ctx, x.clone()).await; } - // manually install from specific `.tar.zstd` file when the // --from parameter is provided. otherwise we install/reinstall // the latest version to whatever sourcemods directory is used else if let Some(x) = matches.get_one::("from") { - info!("Manually installing from {} to {}", x.clone(), ctx.sourcemod_path.clone()); - if let Err(e) = InstallWorkflow::install_from(x.clone(), ctx.sourcemod_path.clone(), None).await { + info!( + "Manually installing from {} to {}", + x.clone(), + ctx.sourcemod_path.clone() + ); + if let Err(e) = + InstallWorkflow::install_from(x.clone(), ctx.sourcemod_path.clone(), None).await + { error!("Failed to run InstallWorkflow::install_from"); sentry::capture_error(&e); panic!("{:#?}", e); @@ -329,21 +321,20 @@ impl Launcher /// /// NOTE this function uses `expect` on `InstallWorkflow::install_version`. panics are handled /// and are reported via sentry. - pub async fn task_install_version_specific(&mut self, ctx: RunnerContext, version_str: String) - { + pub async fn task_install_version_specific(&mut self, ctx: RunnerContext, version_str: String) { let version = match usize::from_str(&version_str) { Ok(v) => v, Err(e) => { sentry::capture_error(&e); - error!("Failed to parse version argument \"{version_str}\": {:#?}", e); + error!( + "Failed to parse version argument \"{version_str}\": {:#?}", + e + ); logic_done(); return; } }; - let mut wf = InstallWorkflow - { - context: ctx - }; + let mut wf = InstallWorkflow { context: ctx }; if let Err(e) = wf.install_version(version).await { error!("Failed to run InstallWorkflow::install_version"); sentry::capture_error(&e); @@ -357,8 +348,7 @@ impl Launcher /// /// NOTE this function uses `panic!` when `VerifyWorkflow::wizard` fails. panics are handled /// and are reported via sentry. - pub async fn task_verify(&mut self, matches: &ArgMatches) - { + pub async fn task_verify(&mut self, matches: &ArgMatches) { self.to_location = Launcher::find_arg_sourcemods_location(&matches); let mut ctx = self.try_create_context().await; @@ -373,8 +363,7 @@ impl Launcher /// /// NOTE this function uses `panic!` when `UpdateWorkflow::wizard` fails. panics are handled /// and are reported via sentry. - pub async fn task_update(&mut self, matches: &ArgMatches) - { + pub async fn task_update(&mut self, matches: &ArgMatches) { self.to_location = Launcher::find_arg_sourcemods_location(&matches); let mut ctx = self.try_create_context().await; @@ -389,8 +378,7 @@ impl Launcher /// /// NOTE this function uses `panic!` when `CleanWorkflow::wizard` fails. panics are handled /// and are reported via sentry. - pub async fn task_clean_tmp(&mut self) - { + pub async fn task_clean_tmp(&mut self) { let mut ctx = self.try_create_context().await; if let Err(e) = CleanWorkflow::wizard(&mut ctx) { panic!("Failed to run CleanWorkflow {:#?}", e); @@ -426,5 +414,3 @@ fn show_msgbox_error(text: String) { .with_content(text.replace("\\n", "\n")) .run(); } - - diff --git a/src/version.rs b/src/version.rs index 14d2afe..2f16133 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,16 +1,15 @@ +use crate::helper; +use crate::helper::{find_sourcemod_path, InstallType}; +use crate::BeansError; +use log::{debug, error, trace}; use std::backtrace::Backtrace; use std::collections::HashMap; use std::fs::read_to_string; use std::io::Write; -use log::{debug, error, trace}; -use crate::helper; -use crate::helper::{find_sourcemod_path, InstallType}; -use crate::BeansError; /// get the current version installed via the .adastral file in the sourcemod mod folder. /// will parse the value of `version` as usize. -pub fn get_current_version(sourcemods_location: Option) -> Option -{ +pub fn get_current_version(sourcemods_location: Option) -> Option { let install_state = helper::install_state(sourcemods_location.clone()); if install_state != InstallType::Adastral { return None; @@ -19,53 +18,68 @@ pub fn get_current_version(sourcemods_location: Option) -> Option Some(smp_x) => { // TODO generate BeansError instead of using .expect let location = format!("{}.adastral", smp_x); - let content = read_to_string(&location).expect(format!("Failed to open {}", location).as_str()); - let data: AdastralVersionFile = serde_json::from_str(&content).expect(format!("Failed to deserialize data at {}", location).as_str()); - let parsed = data.version.parse::().expect(format!("Failed to convert version to usize! ({})", data.version).as_str()); + let content = + read_to_string(&location).expect(format!("Failed to open {}", location).as_str()); + let data: AdastralVersionFile = serde_json::from_str(&content) + .expect(format!("Failed to deserialize data at {}", location).as_str()); + let parsed = data + .version + .parse::() + .expect(format!("Failed to convert version to usize! ({})", data.version).as_str()); Some(parsed) - }, - None => None + } + None => None, } } -fn get_version_location(sourcemods_location: Option) -> Option -{ +fn get_version_location(sourcemods_location: Option) -> Option { match get_mod_location(sourcemods_location) { Some(v) => Some(format!("{}.adastral", v)), - None => None + None => None, } } /// get the full location of the sourcemod mod directory. -fn get_mod_location(sourcemods_location: Option) -> Option -{ +fn get_mod_location(sourcemods_location: Option) -> Option { let smp_x = match sourcemods_location { Some(v) => v, None => match find_sourcemod_path() { Ok(v) => v, Err(e) => { sentry::capture_error(&e); - debug!("[version::get_mod_location] {} {:#?}", BeansError::SourceModLocationNotFound, e); + debug!( + "[version::get_mod_location] {} {:#?}", + BeansError::SourceModLocationNotFound, + e + ); return None; } - } + }, }; - return Some(helper::join_path(smp_x, crate::data_dir())) + return Some(helper::join_path(smp_x, crate::data_dir())); } /// migrate from old file (.revision) to new file (.adastral) in sourcemod mod directory. -pub fn update_version_file(sourcemods_location: Option) -> Result<(), BeansError> -{ +pub fn update_version_file(sourcemods_location: Option) -> Result<(), BeansError> { let install_state = helper::install_state(sourcemods_location.clone()); if install_state == InstallType::Adastral { - debug!("[version::update_version_file] install_state is {:#?}, ignoring.", install_state); + debug!( + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state + ); return Ok(()); } // ignore :) else if install_state == InstallType::OtherSourceManual { - debug!("[version::update_version_file] install_state is {:#?}, ignoring.", install_state); + debug!( + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state + ); return Ok(()); } // ignore :) else if install_state == InstallType::NotInstalled { - debug!("[version::update_version_file] install_state is {:#?}, ignoring.", install_state); + debug!( + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state + ); return Ok(()); } @@ -74,12 +88,15 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be None => match find_sourcemod_path() { Ok(v) => v, Err(e) => { - error!("[version::update_version_file] Could not find sourcemods folder! {:}", e); + error!( + "[version::update_version_file] Could not find sourcemods folder! {:}", + e + ); debug!("{:#?}", e); sentry::capture_error(&e); return Err(e); } - } + }, }; let data_dir = helper::join_path(smp_x, crate::data_dir()); @@ -88,30 +105,35 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be let old_version_file_content = match read_to_string(&old_version_file_location) { Ok(v) => v, Err(e) => { - debug!("[update_version_file] failed to read {}. {:#?}", old_version_file_location, e); + debug!( + "[update_version_file] failed to read {}. {:#?}", + old_version_file_location, e + ); sentry::capture_error(&e); return Err(BeansError::VersionFileReadFailure { error: e, - location: old_version_file_location + location: old_version_file_location, }); } }; let old_version_idx = match old_version_file_content.parse::() { Ok(v) => v, Err(e) => { - debug!("[update_version_file] Failed to parse content {} caused error {:}", old_version_file_content, e); + debug!( + "[update_version_file] Failed to parse content {} caused error {:}", + old_version_file_content, e + ); sentry::capture_error(&e); return Err(BeansError::VersionFileParseFailure { error: e, old_location: old_version_file_location, - old_content: old_version_file_content + old_content: old_version_file_content, }); } }; - let new_file_content = AdastralVersionFile - { - version: old_version_idx.to_string() + let new_file_content = AdastralVersionFile { + version: old_version_idx.to_string(), }; let new_version_file_location = format!("{}.adastral", &data_dir); @@ -121,7 +143,7 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be sentry::capture_error(&e); return Err(BeansError::VersionFileSerialize { error: e, - instance: new_file_content + instance: new_file_content, }); } }; @@ -130,37 +152,42 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be sentry::capture_error(&e); return Err(BeansError::VersionFileMigrationFailure { error: e, - location: new_version_file_location + location: new_version_file_location, }); } if let Err(e) = std::fs::remove_file(old_version_file_location.clone()) { sentry::capture_error(&e); return Err(BeansError::VersionFileMigrationDeleteFailure { error: e, - location: old_version_file_location - }) + location: old_version_file_location, + }); } Ok(()) } /// fetch the version list from `{crate::SOURCE_URL}versions.json` -pub async fn get_version_list() -> Result -{ +pub async fn get_version_list() -> Result { let av = crate::appvar::parse(); let response = match reqwest::get(&av.remote_info.versions_url).await { Ok(v) => v, Err(e) => { - error!("[version::get_version_list] Failed to get available versions! {:}", e); + error!( + "[version::get_version_list] Failed to get available versions! {:}", + e + ); sentry::capture_error(&e); return Err(BeansError::Reqwest { error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } }; let response_text = response.text().await?; - trace!("[version::get_version_list] response text: {}", response_text); + trace!( + "[version::get_version_list] response text: {}", + response_text + ); let data: RemoteVersionResponse = serde_json::from_str(&response_text)?; return Ok(data); @@ -169,7 +196,7 @@ pub async fn get_version_list() -> Result /// Version file that is used as `.adastral` in the sourcemod mod folder. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AdastralVersionFile { - pub version: String + pub version: String, } impl AdastralVersionFile { pub fn write(&self, sourcemods_location: Option) -> Result<(), BeansError> { @@ -177,37 +204,32 @@ impl AdastralVersionFile { Some(vl) => { let f = match helper::file_exists(vl.clone()) { true => std::fs::File::create(vl.clone()), - false => std::fs::File::create_new(vl.clone()) + false => std::fs::File::create_new(vl.clone()), }; match f { - Ok(mut file) => { - match serde_json::to_string(self) { - Ok(ser) => { - match file.write_all(ser.as_bytes()) { - Ok(_) => Ok(()), - Err(e) => Err(BeansError::FileWriteFailure { - location: vl, - error: e - }) - } - }, - Err(e) => Err(e.into()) - } + Ok(mut file) => match serde_json::to_string(self) { + Ok(ser) => match file.write_all(ser.as_bytes()) { + Ok(_) => Ok(()), + Err(e) => Err(BeansError::FileWriteFailure { + location: vl, + error: e, + }), + }, + Err(e) => Err(e.into()), }, Err(e) => Err(BeansError::FileOpenFailure { location: vl, - error: e - }) + error: e, + }), } - }, - None => Err(BeansError::SourceModLocationNotFound) + } + None => Err(BeansError::SourceModLocationNotFound), } } } /// Value of the `versions` property in `RemoteVersionResponse` #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RemoteVersion -{ +pub struct RemoteVersion { pub url: Option, pub file: Option, #[serde(rename = "presz")] @@ -217,20 +239,18 @@ pub struct RemoteVersion #[serde(rename = "signature")] pub signature_url: Option, #[serde(rename = "heal")] - pub heal_url: Option + pub heal_url: Option, } /// `versions.json` response content from remote server. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RemoteVersionResponse -{ +pub struct RemoteVersionResponse { pub versions: HashMap, - pub patches: HashMap + pub patches: HashMap, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RemotePatch -{ +pub struct RemotePatch { pub url: String, pub file: String, /// Amount of file space required for temporary file. Assumed to be measured in bytes. - pub tempreq: usize -} \ No newline at end of file + pub tempreq: usize, +} diff --git a/src/wizard.rs b/src/wizard.rs index aa2a162..c87f683 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -1,34 +1,30 @@ -use crate::{BeansError, depends, flags, helper, RunnerContext, SourceModDirectoryParam}; -use crate::helper::{find_sourcemod_path, InstallType, parse_location}; +use crate::flags::LaunchFlag; +use crate::helper::{find_sourcemod_path, parse_location, InstallType}; +use crate::workflows::{CleanWorkflow, InstallWorkflow, UpdateWorkflow, VerifyWorkflow}; +use crate::{depends, flags, helper, BeansError, RunnerContext, SourceModDirectoryParam}; use async_recursion::async_recursion; use log::{debug, error, info, trace}; use std::backtrace::Backtrace; -use crate::flags::LaunchFlag; -use crate::workflows::{CleanWorkflow, InstallWorkflow, UpdateWorkflow, VerifyWorkflow}; #[derive(Debug, Clone)] -pub struct WizardContext -{ +pub struct WizardContext { pub context: RunnerContext, - pub menu_trigger_count: u32 + pub menu_trigger_count: u32, } -impl WizardContext -{ +impl WizardContext { /// run the wizard! - pub async fn run(sml_via: SourceModDirectoryParam) -> Result<(), BeansError> - { + pub async fn run(sml_via: SourceModDirectoryParam) -> Result<(), BeansError> { depends::try_write_deps(); if let Err(e) = depends::try_install_vcredist().await { sentry::capture_error(&e); println!("Failed to install vcredist! {:}", e); debug!("[WizardContext::run] {:#?}", e); } - let sourcemod_path = parse_location(match sml_via - { + let sourcemod_path = parse_location(match sml_via { SourceModDirectoryParam::AutoDetect => { debug!("[WizardContext::run] Auto-detecting sourcemods directory"); get_path() - }, + } SourceModDirectoryParam::WithLocation(loc) => { debug!("[WizardContext::run] Using specified location {}", loc); loc @@ -52,13 +48,12 @@ impl WizardContext sourcemod_path: sourcemod_path.clone(), remote_version_list: version_list, current_version: crate::version::get_current_version(Some(sourcemod_path)), - appvar: crate::appvar::parse() + appvar: crate::appvar::parse(), }; - let mut i = Self - { + let mut i = Self { context: ctx, - menu_trigger_count: 0u32 + menu_trigger_count: 0u32, }; i.menu().await; return Ok(()); @@ -67,14 +62,16 @@ impl WizardContext /// Show the menu /// When an invalid option is selected, this will be re-called. #[async_recursion] - pub async fn menu<'a>(&'a mut self) - { + pub async fn menu<'a>(&'a mut self) { if self.menu_trigger_count == 0 { let av = crate::appvar::AppVarData::get(); if let Some(cv) = self.context.current_version { let (rv, _) = self.context.latest_remote_version(); if cv < rv { - println!("======== A new update for {} is available! (v{rv}) ========", av.mod_info.name_stylized); + println!( + "======== A new update for {} is available! (v{rv}) ========", + av.mod_info.name_stylized + ); } } } @@ -90,17 +87,15 @@ impl WizardContext "1" | "install" => WizardContext::menu_error_catch(self.task_install().await), "2" | "update" => WizardContext::menu_error_catch(self.task_update().await), "3" | "verify" => WizardContext::menu_error_catch(self.task_verify().await), - "c" | "clean" => { - Self::menu_error_catch(CleanWorkflow::wizard(&mut self.context)) - }, + "c" | "clean" => Self::menu_error_catch(CleanWorkflow::wizard(&mut self.context)), "d" | "debug" => { flags::add_flag(LaunchFlag::DEBUG_MODE); info!("Debug mode enabled!"); self.menu().await; - }, + } "panic" => { panic!() - }, + } "q" => std::process::exit(0), _ => { println!("Unknown option \"{}\"", user_input); @@ -118,35 +113,28 @@ impl WizardContext } /// Install the target game. - pub async fn task_install(&mut self) -> Result<(), BeansError> - { + pub async fn task_install(&mut self) -> Result<(), BeansError> { InstallWorkflow::wizard(&mut self.context).await } /// Check for any updates, and if there are any, we install them. - pub async fn task_update(&mut self) -> Result<(), BeansError> - { + pub async fn task_update(&mut self) -> Result<(), BeansError> { UpdateWorkflow::wizard(&mut self.context).await } /// Verify the current data for the target sourcemod. - pub async fn task_verify(&mut self) -> Result<(), BeansError> - { + pub async fn task_verify(&mut self) -> Result<(), BeansError> { VerifyWorkflow::wizard(&mut self.context).await } } - - -fn get_path() -> String -{ +fn get_path() -> String { find_sourcemod_path().unwrap_or_else(|e| { error!("[get_path] Failed to automatically detect sourcemods folder!"); debug!("{:#?}", e); prompt_sourcemod_location() }) } -fn prompt_sourcemod_location() -> String -{ +fn prompt_sourcemod_location() -> String { let res = helper::get_input("Please provide your sourcemods folder, then press enter."); return if !helper::file_exists(res.clone()) { eprintln!("The location you provided doesn't exist. Try again."); @@ -156,5 +144,5 @@ fn prompt_sourcemod_location() -> String prompt_sourcemod_location() } else { res - } + }; } diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index 3d2553c..04bc793 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -1,13 +1,12 @@ +use crate::{helper, BeansError, RunnerContext}; use log::{info, warn}; -use crate::{BeansError, helper, RunnerContext}; #[derive(Debug, Clone)] pub struct CleanWorkflow { - pub context: RunnerContext + pub context: RunnerContext, } impl CleanWorkflow { - pub fn wizard(_ctx: &mut RunnerContext) -> Result<(), BeansError> - { + pub fn wizard(_ctx: &mut RunnerContext) -> Result<(), BeansError> { let target_directory = helper::get_tmp_dir(); info!("[CleanWorkflow] Cleaning up {}", target_directory); @@ -15,12 +14,11 @@ impl CleanWorkflow { warn!("[CleanWorkflow] Temporary directory not found, nothing to clean.") } - // delete directory and it's contents (and error handling) if let Err(e) = std::fs::remove_dir_all(&target_directory) { return Err(BeansError::CleanTempFailure { location: target_directory, - error: e + error: e, }); } @@ -28,11 +26,11 @@ impl CleanWorkflow { if let Err(e) = std::fs::create_dir(&target_directory) { return Err(BeansError::DirectoryCreateFailure { location: target_directory, - error: e + error: e, }); } info!("[CleanWorkflow] Done!"); return Ok(()); } -} \ No newline at end of file +} diff --git a/src/workflows/install.rs b/src/workflows/install.rs index adf030b..24897a1 100644 --- a/src/workflows/install.rs +++ b/src/workflows/install.rs @@ -1,16 +1,15 @@ -use log::{debug, error, info, warn}; -use crate::{DownloadFailureReason, helper, RunnerContext}; use crate::appvar::AppVarData; -use crate::BeansError; use crate::version::{AdastralVersionFile, RemoteVersion}; +use crate::BeansError; +use crate::{helper, DownloadFailureReason, RunnerContext}; +use log::{debug, error, info, warn}; #[derive(Debug, Clone)] pub struct InstallWorkflow { - pub context: RunnerContext + pub context: RunnerContext, } impl InstallWorkflow { - pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> - { + pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let (latest_remote_id, latest_remote) = ctx.latest_remote_version(); if let Some(_cv) = ctx.current_version { println!("[InstallWorkflow::wizard] re-installing! game files will not be touched until extraction"); @@ -24,29 +23,29 @@ impl InstallWorkflow { /// Will always return `true` when `crate::PROMPT_DO_WHATEVER` is `true`. /// /// Returns: `true` when the installation should continue, `false` when we should silently abort. - pub fn prompt_confirm(current_version: Option) -> bool - { + pub fn prompt_confirm(current_version: Option) -> bool { unsafe { if crate::PROMPT_DO_WHATEVER { - info!("[InstallWorkflow::prompt_confirm] skipping since PROMPT_DO_WHATEVER is true"); - return true; + info!( + "[InstallWorkflow::prompt_confirm] skipping since PROMPT_DO_WHATEVER is true" + ); + return true; } } let av = AppVarData::get(); if let Some(v) = current_version { - println!("[InstallWorkflow::prompt_confirm] Seems like {} is already installed (v{})", v, av.mod_info.name_stylized); + println!( + "[InstallWorkflow::prompt_confirm] Seems like {} is already installed (v{})", + v, av.mod_info.name_stylized + ); println!("Are you sure that you want to reinstall?"); println!("Yes/Y (default)"); println!("No/N"); let user_input = helper::get_input("-- Enter option below --"); match user_input.to_lowercase().as_str() { - "y" | "yes" | "" => { - true - }, - "n" | "no" => { - false - }, + "y" | "yes" | "" => true, + "n" | "no" => false, _ => { println!("Unknown option \"{}\"", user_input.to_lowercase()); Self::prompt_confirm(current_version) @@ -58,36 +57,46 @@ impl InstallWorkflow { } /// Install the specified version by its ID to the output directory. - pub async fn install_version(&mut self, version_id: usize) -> Result<(), BeansError> - { + pub async fn install_version(&mut self, version_id: usize) -> Result<(), BeansError> { let target_version = match self.context.remote_version_list.versions.get(&version_id) { Some(v) => v, None => { error!("Could not find remote version {version_id}"); return Err(BeansError::RemoteVersionNotFound { - version: Some(version_id) + version: Some(version_id), }); } }; let mut ctx = self.context.clone(); - InstallWorkflow::install_with_remote_version(&mut ctx, version_id, target_version.clone()).await + InstallWorkflow::install_with_remote_version(&mut ctx, version_id, target_version.clone()) + .await } /// Install with a specific remote version. /// /// Note: Will call Self::prompt_confirm, so set `crate::PROMPT_DO_WHATEVER` to `true` before you call /// this function if you don't want to wait for a newline from stdin. - pub async fn install_with_remote_version(ctx: &mut RunnerContext, version_id: usize, version: RemoteVersion) - -> Result<(), BeansError> - { + pub async fn install_with_remote_version( + ctx: &mut RunnerContext, + version_id: usize, + version: RemoteVersion, + ) -> Result<(), BeansError> { if Self::prompt_confirm(ctx.current_version) == false { info!("[InstallWorkflow] Operation aborted by user"); return Ok(()); } - println!("{:=>60}\nInstalling version {} to {}\n{0:=>60}", "=", version_id, &ctx.sourcemod_path); + println!( + "{:=>60}\nInstalling version {} to {}\n{0:=>60}", + "=", version_id, &ctx.sourcemod_path + ); let presz_loc = RunnerContext::download_package(version).await?; - Self::install_from(presz_loc.clone(), ctx.sourcemod_path.clone(), Some(version_id)).await?; + Self::install_from( + presz_loc.clone(), + ctx.sourcemod_path.clone(), + Some(version_id), + ) + .await?; if helper::file_exists(presz_loc.clone()) { std::fs::remove_file(presz_loc)?; } @@ -99,15 +108,17 @@ impl InstallWorkflow { /// out_dir: should be `RunnerContext.sourcemod_path` /// version_id: Version that is from `package_loc`. When not specified, `.adastral` will not be written to. /// Note: This function doesn't check the extension when extracting. - pub async fn install_from(package_loc: String, out_dir: String, version_id: Option) - -> Result<(), BeansError> - { + pub async fn install_from( + package_loc: String, + out_dir: String, + version_id: Option, + ) -> Result<(), BeansError> { if helper::file_exists(package_loc.clone()) == false { error!("[InstallWorkflow::Wizard] Failed to find package! (location: {package_loc})"); return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::FileNotFound { - location: package_loc.clone() - } + location: package_loc.clone(), + }, }); } @@ -115,10 +126,14 @@ impl InstallWorkflow { RunnerContext::extract_package(package_loc, out_dir.clone())?; if let Some(lri) = version_id { let x = AdastralVersionFile { - version: lri.to_string() - }.write(Some(out_dir.clone())); + version: lri.to_string(), + } + .write(Some(out_dir.clone())); if let Err(e) = x { - println!("[InstallWorkflow::install_from] Failed to set version to {} in .adastral", lri); + println!( + "[InstallWorkflow::install_from] Failed to set version to {} in .adastral", + lri + ); debug!("{:#?}", e); } } else { @@ -133,4 +148,4 @@ impl InstallWorkflow { #[cfg(not(target_os = "windows"))] pub const INSTALL_FINISH_MSG: &str = include_str!("../text/install_complete_linux.txt"); #[cfg(target_os = "windows")] -pub const INSTALL_FINISH_MSG: &str = include_str!("../text/install_complete_windows.txt"); \ No newline at end of file +pub const INSTALL_FINISH_MSG: &str = include_str!("../text/install_complete_windows.txt"); diff --git a/src/workflows/mod.rs b/src/workflows/mod.rs index d2b6905..57d974a 100644 --- a/src/workflows/mod.rs +++ b/src/workflows/mod.rs @@ -1,9 +1,9 @@ +mod clean; mod install; mod update; mod verify; -mod clean; +pub use clean::*; pub use install::*; pub use update::*; pub use verify::*; -pub use clean::*; \ No newline at end of file diff --git a/src/workflows/update.rs b/src/workflows/update.rs index 7125a88..9101ca6 100644 --- a/src/workflows/update.rs +++ b/src/workflows/update.rs @@ -1,20 +1,19 @@ +use crate::{butler, helper, BeansError, RunnerContext}; use log::{debug, info}; -use crate::{BeansError, butler, helper, RunnerContext}; -pub struct UpdateWorkflow -{ - pub ctx: RunnerContext +pub struct UpdateWorkflow { + pub ctx: RunnerContext, } -impl UpdateWorkflow -{ - pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> - { +impl UpdateWorkflow { + pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let av = crate::appvar::parse(); let current_version_id = match ctx.current_version { Some(v) => v, None => { - println!("[UpdateWorkflow::wizard] Unable to update game since it is not installed!"); + println!( + "[UpdateWorkflow::wizard] Unable to update game since it is not installed!" + ); return Ok(()); } }; @@ -33,14 +32,23 @@ impl UpdateWorkflow ctx.gameinfo_perms()?; if helper::has_free_space(ctx.sourcemod_path.clone(), patch.clone().tempreq)? == false { - println!("[UpdateWorkflow::wizard] Not enough free space! Requires {}", helper::format_size(patch.tempreq)); + println!( + "[UpdateWorkflow::wizard] Not enough free space! Requires {}", + helper::format_size(patch.tempreq) + ); } debug!("remote_version: {:#?}", remote_version); if remote_version.signature_url.is_none() { - eprintln!("[UpdateWorkflow::wizard] Couldn't get signature URL for version {}", current_version_id); + eprintln!( + "[UpdateWorkflow::wizard] Couldn't get signature URL for version {}", + current_version_id + ); } if remote_version.heal_url.is_none() { - eprintln!("[UpdateWorkflow::wizard] Couldn't get heal URL for version {}", current_version_id); + eprintln!( + "[UpdateWorkflow::wizard] Couldn't get heal URL for version {}", + current_version_id + ); } if remote_version.signature_url.is_none() || remote_version.heal_url.is_none() { eprintln!("[UpdateWorkflow::wizard] Unable to update, missing remote files!"); @@ -55,9 +63,18 @@ impl UpdateWorkflow ctx.gameinfo_perms()?; info!("[UpdateWorkflow] Verifying game"); if let Err(e) = butler::verify( - format!("{}{}", &av.remote_info.base_url, remote_version.signature_url.unwrap()), + format!( + "{}{}", + &av.remote_info.base_url, + remote_version.signature_url.unwrap() + ), mod_dir_location.clone(), - format!("{}{}", &av.remote_info.base_url, remote_version.heal_url.unwrap())) { + format!( + "{}{}", + &av.remote_info.base_url, + remote_version.heal_url.unwrap() + ), + ) { sentry::capture_error(&e); return Err(e); } @@ -67,7 +84,10 @@ impl UpdateWorkflow format!("{}{}", &av.remote_info.base_url, patch.file), staging_dir_location, patch.file, - mod_dir_location).await { + mod_dir_location, + ) + .await + { sentry::capture_error(&e); return Err(e); } @@ -77,4 +97,4 @@ impl UpdateWorkflow println!("Game has been updated!"); Ok(()) } -} \ No newline at end of file +} diff --git a/src/workflows/verify.rs b/src/workflows/verify.rs index 8040d92..b986881 100644 --- a/src/workflows/verify.rs +++ b/src/workflows/verify.rs @@ -1,28 +1,35 @@ -use crate::{BeansError, butler, helper, RunnerContext}; use crate::version::RemoteVersion; +use crate::{butler, helper, BeansError, RunnerContext}; pub struct VerifyWorkflow { - pub ctx: RunnerContext + pub ctx: RunnerContext, } impl VerifyWorkflow { - pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> - { + pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let av = crate::appvar::parse(); let current_version_id = match ctx.current_version { Some(v) => v, None => { - println!("[VerifyWorkflow::wizard] Unable to update game since it is not installed!"); + println!( + "[VerifyWorkflow::wizard] Unable to update game since it is not installed!" + ); return Ok(()); } }; let remote: RemoteVersion = ctx.current_remote_version()?; if remote.signature_url.is_none() { - eprintln!("[VerifyWorkflow::wizard] Couldn't get signature URL for version {}", current_version_id); + eprintln!( + "[VerifyWorkflow::wizard] Couldn't get signature URL for version {}", + current_version_id + ); } if remote.heal_url.is_none() { - eprintln!("[VerifyWorkflow::wizard] Couldn't get heal URL for version {}", current_version_id); + eprintln!( + "[VerifyWorkflow::wizard] Couldn't get heal URL for version {}", + current_version_id + ); } if remote.signature_url.is_none() || remote.heal_url.is_none() { eprintln!("[VerifyWorkflow::wizard] Unable to update, missing remote files!"); @@ -32,11 +39,16 @@ impl VerifyWorkflow { helper::backup_gameinfo(ctx)?; let mod_dir_location = ctx.get_mod_location(); butler::verify( - format!("{}{}", &av.remote_info.base_url, remote.signature_url.unwrap()), + format!( + "{}{}", + &av.remote_info.base_url, + remote.signature_url.unwrap() + ), mod_dir_location.clone(), - format!("{}{}", &av.remote_info.base_url, remote.heal_url.unwrap()))?; + format!("{}{}", &av.remote_info.base_url, remote.heal_url.unwrap()), + )?; println!("[VerifyWorkflow::wizard] The verification process has completed, and any corruption has been repaired."); ctx.gameinfo_perms()?; Ok(()) } -} \ No newline at end of file +} From 23ff4bc2c00d3feadcf925c3a73e64c61cce40e2 Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Tue, 23 Jul 2024 19:34:40 +0800 Subject: [PATCH 36/65] optimise code inline with clippy best practices --- build.rs | 3 +- src/butler.rs | 6 +- src/ctx.rs | 41 ++++---- src/depends.rs | 10 +- src/flags.rs | 17 ++- src/gui/dialog.rs | 26 +++-- src/gui/mod.rs | 25 +++-- src/helper/linux.rs | 8 +- src/helper/mod.rs | 86 ++++++++------- src/lib.rs | 11 +- src/logger.rs | 6 +- src/main.rs | 39 +++---- src/version.rs | 220 ++++++++++++++++++++------------------- src/wizard.rs | 8 +- src/workflows/clean.rs | 5 +- src/workflows/install.rs | 4 +- src/workflows/update.rs | 2 +- 17 files changed, 267 insertions(+), 250 deletions(-) diff --git a/build.rs b/build.rs index eb9d9a7..da61007 100644 --- a/build.rs +++ b/build.rs @@ -38,8 +38,7 @@ fn fltk() -> Result<(), BuildError> { /// check if a location exists #[allow(dead_code)] fn path_exists(path: String) -> bool { - let p = std::path::Path::new(path.as_str()); - return p.exists(); + std::path::Path::new(path.as_str()).exists() } /// set the icon to `icon.ico` when building for windows diff --git a/src/butler.rs b/src/butler.rs index 0d017b8..b8ff64c 100644 --- a/src/butler.rs +++ b/src/butler.rs @@ -8,7 +8,7 @@ pub fn verify( gamedir: String, remote: String, ) -> Result { - let mut cmd = std::process::Command::new(&depends::get_butler_location()); + let mut cmd = std::process::Command::new(depends::get_butler_location()); cmd.args([ "verify", &signature_url, @@ -50,7 +50,7 @@ pub async fn patch_dl( info!("[butler::patch_dl] downloading {} to {}", dl_url, tmp_file); helper::download_with_progress(dl_url, tmp_file.clone()).await?; - if helper::file_exists(tmp_file.clone()) == false { + if !helper::file_exists(tmp_file.clone()) { return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::FileNotFound { location: tmp_file }, }); @@ -64,7 +64,7 @@ pub fn patch( staging_dir: String, gamedir: String, ) -> Result { - let mut cmd = std::process::Command::new(&depends::get_butler_location()); + let mut cmd = std::process::Command::new(depends::get_butler_location()); cmd.args([ "apply", &format!("--staging-dir={}", &staging_dir), diff --git a/src/ctx.rs b/src/ctx.rs index 389280d..bb6500f 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -13,6 +13,7 @@ pub struct RunnerContext { pub current_version: Option, pub appvar: crate::appvar::AppVarData, } + impl RunnerContext { pub async fn create_auto(sml_via: SourceModDirectoryParam) -> Result { depends::try_write_deps(); @@ -50,12 +51,12 @@ impl RunnerContext { version::update_version_file(Some(sourcemod_path.clone()))?; } - return Ok(Self { + Ok(Self { sourcemod_path: parse_location(sourcemod_path.clone()), remote_version_list: version_list, current_version: crate::version::get_current_version(Some(sourcemod_path.clone())), appvar: crate::appvar::parse(), - }); + }) } /// Sets `remote_version_list` from `version::get_version_list()` pub async fn set_remote_version_list(&mut self) -> Result<(), BeansError> { @@ -100,9 +101,9 @@ impl RunnerContext { return Ok(i.clone()); } } - return Err(BeansError::RemoteVersionNotFound { + Err(BeansError::RemoteVersionNotFound { version: self.current_version, - }); + }) } None => Err(BeansError::RemoteVersionNotFound { version: self.current_version, @@ -113,7 +114,7 @@ impl RunnerContext { /// When self.current_version is some, iterate through patches and fetch the patch that is available /// to bring the current version in-line with the latest version. pub fn has_patch_available(&mut self) -> Option { - let current_version = self.current_version.clone(); + let current_version = self.current_version; let (remote_version, _) = self.latest_remote_version(); match current_version { Some(cv) => { @@ -127,7 +128,7 @@ impl RunnerContext { return Some(patch); } } - return None; + None } _ => None, } @@ -137,15 +138,15 @@ impl RunnerContext { pub fn read_gameinfo_file(&mut self) -> Result>, BeansError> { self.gameinfo_perms()?; let location = self.gameinfo_location(); - if helper::file_exists(location.clone()) == false { + if !helper::file_exists(location.clone()) { return Ok(None); } let file = match std::fs::read(&location) { Ok(v) => v, - Err(e) => { + Err(error) => { let ex = BeansError::GameInfoFileReadFail { - error: e, - location: location, + error, + location, backtrace: Backtrace::capture(), }; sentry::capture_error(&ex); @@ -196,7 +197,7 @@ impl RunnerContext { let mut out_loc = helper::get_tmp_dir(); if let Some(size) = version.pre_sz { - if helper::has_free_space(out_loc.clone(), size)? == false { + if !helper::has_free_space(out_loc.clone(), size)? { panic!("Not enough free space to install latest version!"); } } @@ -253,7 +254,7 @@ impl RunnerContext { }; trace!("[RunnerContext::extract_package] {:}\n{:#?}", xe, xe); sentry::capture_error(&xe); - return Err(xe); + Err(xe) } Ok(_) => Ok(()), } @@ -261,12 +262,11 @@ impl RunnerContext { #[cfg(target_os = "linux")] pub fn prepare_symlink(&mut self) -> Result<(), BeansError> { - for pair in SYMLINK_FILES.into_iter() { + for pair in SYMLINK_FILES.iter() { let target: &str = pair[1]; let mod_location = self.get_mod_location(); let ln_location = format!("{}{}", mod_location, target); - if helper::file_exists(ln_location.clone()) - && helper::is_symlink(ln_location.clone()) == false + if helper::file_exists(ln_location.clone()) && !helper::is_symlink(ln_location.clone()) { if let Err(e) = std::fs::remove_file(&ln_location) { trace!( @@ -288,18 +288,13 @@ impl RunnerContext { } } -pub const SYMLINK_FILES: &'static [&'static [&'static str; 2]] = - &[&["bin/server.so", "bin/server_srv.so"]]; +pub const SYMLINK_FILES: &[&[&str; 2]] = &[&["bin/server.so", "bin/server_srv.so"]]; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub enum SourceModDirectoryParam { /// Default value. Will autodetect location. + #[default] AutoDetect, /// Use from the specified sourcemod location. WithLocation(String), } -impl Default for SourceModDirectoryParam { - fn default() -> Self { - SourceModDirectoryParam::AutoDetect - } -} diff --git a/src/depends.rs b/src/depends.rs index c46f599..486e78b 100644 --- a/src/depends.rs +++ b/src/depends.rs @@ -8,13 +8,13 @@ use std::os::unix::fs::PermissionsExt; /// try and write aria2c and butler if it doesn't exist /// paths that are used will be fetched from binary_locations() pub fn try_write_deps() { - safe_write_file(get_butler_location().as_str(), &**BUTLER_BINARY); - safe_write_file(get_butler_1_location().as_str(), &**BUTLER_LIB_1); - safe_write_file(get_butler_2_location().as_str(), &**BUTLER_LIB_2); + safe_write_file(get_butler_location().as_str(), &BUTLER_BINARY); + safe_write_file(get_butler_1_location().as_str(), &BUTLER_LIB_1); + safe_write_file(get_butler_2_location().as_str(), &BUTLER_LIB_2); #[cfg(not(target_os = "windows"))] if helper::file_exists(get_butler_location()) { let p = std::fs::Permissions::from_mode(0744 as u32); - if let Err(e) = std::fs::set_permissions(&get_butler_location(), p) { + if let Err(e) = std::fs::set_permissions(get_butler_location(), p) { sentry::capture_error(&e); error!( "[depends::try_write_deps] Failed to set permissions for {}", @@ -30,7 +30,7 @@ pub fn try_write_deps() { } fn safe_write_file(location: &str, data: &[u8]) { if !helper::file_exists(location.to_string()) { - if let Err(e) = std::fs::write(&location, data) { + if let Err(e) = std::fs::write(location, data) { sentry::capture_error(&e); error!("[depends::try_write_deps] failed to extract {}", location); error!("[depends::try_write_deps] {:#?}", e); diff --git a/src/flags.rs b/src/flags.rs index 0a2efff..3329edb 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -16,6 +16,7 @@ bitflags! { } } pub static mut LAUNCH_FLAGS: u32 = 0x00; + /// check if the `flag` provided is in `LAUNCH_FLAGS` pub fn has_flag(flag: LaunchFlag) -> bool { unsafe { @@ -23,14 +24,12 @@ pub fn has_flag(flag: LaunchFlag) -> bool { data.contains(flag) } } + /// Add a flag to `LAUNCH_FLAGS` pub fn add_flag(flag: LaunchFlag) { unsafe { - match flag { - LaunchFlag::DEBUG_MODE => { - crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_DEFAULT; - } - _ => {} + if let LaunchFlag::DEBUG_MODE = flag { + crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_DEFAULT; }; let mut data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); @@ -38,14 +37,12 @@ pub fn add_flag(flag: LaunchFlag) { LAUNCH_FLAGS = data.bits(); } } + /// remove a flag from `LAUNCH_FLAGS` pub fn remove_flag(flag: LaunchFlag) { unsafe { - match flag { - LaunchFlag::DEBUG_MODE => { - crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_MINIMAL; - } - _ => {} + if flag == LaunchFlag::DEBUG_MODE { + crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_MINIMAL; }; let mut data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); data.remove(flag); diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index e7ce0d9..2ed128e 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -9,27 +9,35 @@ pub struct DialogBuilder { pub content: String, pub icon: Option, } + pub enum DialogIconKind { Default, Warn, Error, } -impl DialogBuilder { - pub fn new() -> Self { + +impl Default for DialogBuilder { + fn default() -> Self { Self { title: format!("beans v{}", crate::VERSION), content: String::new(), icon: None, } } - pub fn with_png_data(mut self, data: &Vec) -> Self { +} + +impl DialogBuilder { + pub fn new() -> Self { + Self::default() + } + pub fn with_png_data(mut self, data: &[u8]) -> Self { match PngImage::from_data(data) { Ok(img) => self.icon = Some(img), Err(e) => { warn!("[DialogBuilder::with_png] Failed to set icon! {:#?}", e); } } - return self; + self } pub fn with_icon(self, kind: DialogIconKind) -> Self { let data: &Vec = match kind { @@ -41,14 +49,14 @@ impl DialogBuilder { } pub fn with_title(mut self, content: String) -> Self { self.title = content.clone(); - return self; + self } pub fn with_content(mut self, content: String) -> Self { self.content = content.clone(); self } pub fn run(&self) { - if crate::has_gui_support() == false { + if !crate::has_gui_support() { println!("============ {} ============", self.title); println!("{}", self.content); return; @@ -79,10 +87,8 @@ impl DialogBuilder { ui.btn_ok.set_size(70, 24); let (lw, lh) = ui.label.measure_label(); let cw = w.width(); - if cw != initial_width { - if cw > lw + 50 { - w.set_size(lw + 50, 10 + lh + 5 + ui.btn_ok.height() + 5); - } + if cw != initial_width && cw > lw + 50 { + w.set_size(lw + 50, 10 + lh + 5 + ui.btn_ok.height() + 5); } false } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 0f8a60d..1808b0d 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -6,7 +6,9 @@ use log::debug; mod dialog; pub(crate) mod shared_ui; + pub use dialog::*; + pub mod icon; #[allow(dead_code)] @@ -33,9 +35,9 @@ pub fn window_centre_screen(window: &mut Window) { let (sx, sy) = app::screen_coords(); let width = window.width(); let height = window.height(); - let (mut x, mut y) = app::screen_size().clone(); - x -= width.clone() as f64; - y -= height.clone() as f64; + let (mut x, mut y) = app::screen_size(); + x -= width as f64; + y -= height as f64; window.resize( ((x / 2.0) as i32) + sx, ((y / 2.0) as i32) + sy, @@ -43,11 +45,12 @@ pub fn window_centre_screen(window: &mut Window) { height, ); } + /// Get the X and Y position of the center of the current screen. pub fn get_center_screen() -> (i32, i32) { let (px, py) = app::screen_coords(); let (sw, sh) = app::screen_size(); - return (((sw / 2.0) as i32) + px, ((sh / 2.0) as i32) + py); + (((sw / 2.0) as i32) + px, ((sh / 2.0) as i32) + py) } /// Ensure that a window has a fixed width & height, and that it will appear in the centre of the @@ -66,6 +69,7 @@ pub fn window_ensure(win: &mut Window, width: i32, height: i32) { win.make_resizable(false); win.show(); } + pub fn apply_app_scheme() { let theme_content = match dark_light::detect() { dark_light::Mode::Light => color_themes::GRAY_THEME, @@ -81,16 +85,11 @@ pub fn apply_app_scheme() { pub fn wait_for_quit(app: &app::App, receive_action: &Receiver) { while app.wait() { - if let Some(action) = receive_action.recv() { - match action { - GUIAppStatus::Quit => { - unsafe { - crate::PAUSE_ONCE_DONE = false; - } - app.quit(); - } - _ => {} + if let Some(GUIAppStatus::Quit) = receive_action.recv() { + unsafe { + crate::PAUSE_ONCE_DONE = false; } + app.quit(); } } } diff --git a/src/helper/linux.rs b/src/helper/linux.rs index 9aff1ad..bea35af 100644 --- a/src/helper/linux.rs +++ b/src/helper/linux.rs @@ -5,7 +5,7 @@ use std::fs::read_to_string; /// all possible known directory where steam *might* be /// only is used on linux, since windows will use the registry. -pub const STEAM_POSSIBLE_DIR: &'static [&'static str] = &[ +pub const STEAM_POSSIBLE_DIR: &[&str] = &[ "~/.steam/registry.vdf", "~/.var/app/com.valvesoftware.Steam/.steam/registry.vdf", ]; @@ -40,11 +40,11 @@ pub fn find_sourcemod_path() -> Result { } } - return Err(BeansError::SourceModLocationNotFound); + Err(BeansError::SourceModLocationNotFound) } /// returns the first item in STEAM_POSSIBLE_DIR that exists. otherwise None fn find_steam_reg_path() -> Result { - for x in STEAM_POSSIBLE_DIR.into_iter() { + for x in STEAM_POSSIBLE_DIR.iter() { match simple_home_dir::home_dir() { Some(v) => match v.to_str() { Some(k) => { @@ -66,5 +66,5 @@ fn find_steam_reg_path() -> Result { } } error!("Couldn't find any of the locations in STEAM_POSSIBLE_DIR"); - return Err(BeansError::SteamNotFound); + Err(BeansError::SteamNotFound) } diff --git a/src/helper/mod.rs b/src/helper/mod.rs index ec89586..2a15eba 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -7,6 +7,7 @@ use std::backtrace::Backtrace; #[cfg(target_os = "windows")] mod windows; + #[cfg(target_os = "windows")] pub use windows::*; @@ -103,7 +104,7 @@ pub fn install_state(sourcemods_location: Option) -> InstallType { } else if file_exists(format!("{}gameinfo.txt", data_dir)) { return InstallType::OtherSourceManual; } - return InstallType::NotInstalled; + InstallType::NotInstalled } /// get user input from terminal. prompt is displayed on the line above where the user does input. @@ -121,10 +122,12 @@ pub fn get_input(prompt: &str) -> String { pub fn file_exists(location: String) -> bool { std::path::Path::new(&location).exists() } + /// Check if the location provided exists and it's a directory. pub fn dir_exists(location: String) -> bool { file_exists(location.clone()) && is_directory(location.clone()) } + pub fn is_directory(location: String) -> bool { let x = PathBuf::from(&location); x.is_dir() @@ -146,6 +149,7 @@ pub fn generate_rand_str(length: usize) -> String { .collect(); s.to_uppercase() } + /// Join the path, using `tail` as the base, and `head` as the thing to add on top of it. /// /// This will also convert backslashes/forwardslashes to the compiled separator in `crate::PATH_SEP` @@ -160,38 +164,37 @@ pub fn join_path(tail: String, head: String) -> String { format!("{}{}", format_directory_path(tail), h) } + pub fn remove_path_head(location: String) -> String { - let p = std::path::Path::new(&location); - if let Some(x) = p.parent() { - if let Some(m) = x.to_str() { - return m.to_string(); - } + if let Some(Some(m)) = std::path::Path::new(&location).parent().map(|p| p.to_str()) { + return m.to_string(); } - return String::new(); + String::new() } + /// Make sure that the location provided is formatted as a directory (ends with `crate::PATH_SEP`). pub fn format_directory_path(location: String) -> String { - let mut x = location - .to_string() - .replace("/", crate::PATH_SEP) - .replace("\\", crate::PATH_SEP); + let mut x = location.to_string().replace(['/', '\\'], crate::PATH_SEP); + while x.ends_with(crate::PATH_SEP) { x.pop(); } if x.ends_with(crate::PATH_SEP) == false { x.push_str(crate::PATH_SEP); } - x } + #[cfg(not(target_os = "windows"))] pub fn canonicalize(location: &str) -> Result { std::fs::canonicalize(location) } + #[cfg(target_os = "windows")] pub fn canonicalize(location: &str) -> Result { dunce::canonicalize(location) } + pub fn parse_location(location: String) -> String { let path = std::path::Path::new(&location); let real_location = match path.to_str() { @@ -248,7 +251,7 @@ pub fn get_free_space(location: String) -> Result { while !l.is_empty() { debug!("[get_free_space] Checking if {} is in data", l); if let Some(x) = data.get(&l) { - return Ok(x.clone()); + return Ok(*x); } l = remove_path_head(l); } @@ -257,10 +260,10 @@ pub fn get_free_space(location: String) -> Result { location: parse_location(location.clone()), }) } + /// Check if the location provided has enough free space. pub fn has_free_space(location: String, size: usize) -> Result { - let space = get_free_space(location)?; - return Ok((size as u64) < space); + Ok((size as u64) < get_free_space(location)?) } /// Download file at the URL provided to the output location provided @@ -324,9 +327,9 @@ pub fn format_size(i: usize) -> String { let decimal_points: usize = 3; let mut dec_l = decimal_points * 6; if i < 1_000 { - dec_l = decimal_points * 0; + dec_l = 0 } else if i < 1_000_000 { - dec_l = decimal_points * 1; + dec_l = decimal_points } else if i < 1_000_000_000 { dec_l = decimal_points * 2; } else if i < 1_000_000_000_000 { @@ -337,20 +340,15 @@ pub fn format_size(i: usize) -> String { dec_l = decimal_points * 5; } - let dec: String = value - .chars() - .into_iter() - .rev() - .take(dec_l as usize) - .collect(); + let dec: String = value.chars().rev().take(dec_l as usize).collect(); let mut dec_x: String = dec.chars().into_iter().rev().take(decimal_points).collect(); dec_x = dec_x.trim_end_matches('0').to_string(); let whole_l = value.len() - dec_l; - let mut whole: String = value.chars().into_iter().take(whole_l).collect(); - if dec_x.len() > 0 { + let mut whole: String = value.chars().take(whole_l).collect(); + if !dec_x.is_empty() { whole.push('.'); } let pfx_data: Vec<(usize, &str)> = vec![ @@ -365,8 +363,9 @@ pub fn format_size(i: usize) -> String { return format!("{}{}{}", whole, dec_x, c); } } - return format!("{}{}", whole, dec_x); + format!("{}{}", whole, dec_x) } + /// Check if we should use the custom temporary directory, which is stored in the environment variable /// defined in `CUSTOM_TMPDIR_NAME`. /// @@ -385,9 +384,11 @@ pub fn use_custom_tmpdir() -> Option { ); } } - return None; + None } + pub const CUSTOM_TMPDIR_NAME: &str = "ADASTRAL_TMPDIR"; + /// Create directory in temp directory with name of "beans-rs" pub fn get_tmp_dir() -> String { let mut dir = std::env::temp_dir().to_str().unwrap_or("").to_string(); @@ -440,8 +441,9 @@ pub fn get_tmp_dir() -> String { } } - return dir; + dir } + /// Check if the content of `uname -r` contains `valve` (Linux Only) /// /// ## Returns @@ -484,15 +486,17 @@ pub fn is_steamdeck() -> bool { Err(e) => { trace!("[helper::is_steamdeck] {:#?}", e); warn!("[helper::is_steamdeck] Failed to detect {:}", e); - return false; + false } } } + /// Generate a full file location for a temporary file. pub fn get_tmp_file(filename: String) -> String { let head = format!("{}_{}", generate_rand_str(8), filename); join_path(get_tmp_dir(), head) } + /// Check if there is an update available. When the latest release doesn't match the current release. pub async fn beans_has_update() -> Result, BeansError> { let rs = reqwest::Client::new() @@ -526,14 +530,12 @@ pub async fn beans_has_update() -> Result, BeansError> } }; trace!("{:#?}", data); - if data.draft == false - && data.prerelease == false - && data.tag_name != format!("v{}", crate::VERSION) - { + if !data.draft && !data.prerelease && data.tag_name != format!("v{}", crate::VERSION) { return Ok(Some(data.clone())); } - return Ok(None); + Ok(None) } + pub fn restore_gameinfo(ctx: &mut RunnerContext, data: Vec) -> Result<(), BeansError> { let loc = ctx.gameinfo_location(); trace!("gameinfo location: {}", &loc); @@ -563,8 +565,9 @@ pub fn restore_gameinfo(ctx: &mut RunnerContext, data: Vec) -> Result<(), Be sentry::capture_error(&e); return Err(e); } - return Ok(()); + Ok(()) } + pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { let av = AppVarData::get(); let gamedir = join_path(ctx.clone().sourcemod_path, av.mod_info.sourcemod_name); @@ -573,7 +576,7 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { let current_time = chrono::Local::now(); let current_time_formatted = current_time.format("%Y%m%d-%H%M%S").to_string(); - if file_exists(backupdir.clone()) == false { + if !file_exists(backupdir.clone()) { if let Err(e) = std::fs::create_dir(&backupdir) { debug!("backupdir: {}", backupdir); debug!("error: {:#?}", e); @@ -601,7 +604,7 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { ); let current_location = join_path(gamedir, String::from("gameinfo.txt")); - if file_exists(current_location.clone()) == false { + if !file_exists(current_location.clone()) { debug!( "[helper::backup_gameinfo] can't backup since {} doesn't exist", current_location @@ -655,8 +658,10 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { Ok(()) } + const GAMEINFO_BACKUP_DIRNAME: &str = "gameinfo_backup"; const GITHUB_RELEASES_URL: &str = "https://api.github.com/repositories/805393469/releases/latest"; + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct GithubReleaseItem { #[serde(rename = "id")] @@ -674,15 +679,16 @@ pub fn has_env_var(target_key: String) -> bool { if let Some(x) = try_get_env_var(target_key) { return x.len() > 1; } - return false; + false } + /// Try and get a value from `std::env::vars()` /// Will return `None` when not found pub fn try_get_env_var(target_key: String) -> Option { - for (key, value) in std::env::vars().into_iter() { + for (key, value) in std::env::vars() { if key == target_key { return Some(value); } } - return None; + None } diff --git a/src/lib.rs b/src/lib.rs index f8eed52..5e4d890 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,9 +9,13 @@ pub mod helper; pub mod version; pub mod wizard; pub mod workflows; + pub use ctx::*; + mod error; + pub use error::*; + pub mod appvar; pub mod butler; pub mod flags; @@ -54,14 +58,13 @@ pub fn data_dir() -> String { /// - Running on Linux AND the `DISPLAY` or `XDG_SESSION_DESKTOP` environment variables are set. pub fn has_gui_support() -> bool { unsafe { - if PAUSE_ONCE_DONE == false { + if !PAUSE_ONCE_DONE { return false; } } match std::env::consts::OS { - "windows" => true, - "macos" => true, + "windows" | "macos" => true, "linux" => { if helper::has_env_var("DISPLAY".to_string()) { return true; @@ -71,7 +74,7 @@ pub fn has_gui_support() -> bool { return true; } } - return false; + false } _ => { log::warn!("Unsupported platform for GUI {}", std::env::consts::OS); diff --git a/src/logger.rs b/src/logger.rs index 478853d..2fe48a8 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -74,7 +74,7 @@ impl CustomLoggerInner { let hours = seconds / 3600; let minutes = (seconds / 60) % 60; let seconds = seconds % 60; - let milliseconds = now.subsec_nanos() / 1_000_000; + let milliseconds = now.subsec_millis(); #[allow(unused_assignments)] let mut data = String::new(); @@ -103,9 +103,9 @@ impl CustomLoggerInner { } } - let _ = write!(self.sink, "{}\n", data); + let _ = writeln!(self.sink, "{}", data); } - self.sentry.log(&record); + self.sentry.log(record); } } pub fn set_filter(filter: LevelFilter) { diff --git a/src/main.rs b/src/main.rs index 1ab542b..12e09ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,6 +46,7 @@ fn main() { Launcher::run().await; }); } + fn init_flags() { flags::remove_flag(LaunchFlag::DEBUG_MODE); #[cfg(debug_assertions)] @@ -57,26 +58,28 @@ fn init_flags() { beans_rs::logger::set_filter(DEFAULT_LOG_LEVEL); beans_rs::logger::log_to_stdout(); } + fn init_panic_handle() { std::panic::set_hook(Box::new(move |info| { debug!("[panic::set_hook] showing msgbox to notify user"); let mut x = String::new(); if let Some(m) = info.message() { - x = format!("{:#?}", m); + x = format!("{}", m); } info!("[panic] Fatal error!\n{:#?}", x); custom_panic_handle(x); debug!("[panic::set_hook] calling sentry_panic::panic_handler"); - sentry::integrations::panic::panic_handler(&info); + sentry::integrations::panic::panic_handler(info); if flags::has_flag(LaunchFlag::DEBUG_MODE) { eprintln!("{:#?}", info); } logic_done(); })); } + fn custom_panic_handle(msg: String) { unsafe { - if beans_rs::PAUSE_ONCE_DONE == false { + if !beans_rs::PAUSE_ONCE_DONE { return; } } @@ -90,6 +93,7 @@ fn custom_panic_handle(msg: String) { .with_content(txt) .run(); } + /// should called once the logic flow is done! /// will call `helper::get_input` when `PAUSE_ONCE_DONE` is `true`. fn logic_done() { @@ -99,12 +103,14 @@ fn logic_done() { } } } + pub struct Launcher { /// Output location. When none, `SourceModDirectoryParam::default()` will be used. pub to_location: Option, /// Output of `Command.matches()` pub root_matches: ArgMatches, } + impl Launcher { /// Create argument for specifying the location where the sourcemods directory is. fn create_location_arg() -> Arg { @@ -166,11 +172,9 @@ impl Launcher { ]); let mut i = Self::new(&cmd.get_matches()); - if let Ok(r) = helper::beans_has_update().await { - if let Some(v) = r { - info!("A new version of beans-rs is available!"); - info!("{}", v.html_url); - } + if let Ok(Some(v)) = helper::beans_has_update().await { + info!("A new version of beans-rs is available!"); + info!("{}", v.html_url); } i.subcommand_processor().await; } @@ -184,7 +188,7 @@ impl Launcher { i.set_prompt_do_whatever(); i.to_location = Launcher::find_arg_sourcemods_location(&i.root_matches); - return i; + i } /// add `LaunchFlag::DEBUG_MODE` to `flags` when the `--debug` parameter flag is used. @@ -202,7 +206,7 @@ impl Launcher { /// Set `PAUSE_ONCE_DONE` to `false` when `--no-pause` is provided. Otherwise, set it to `true`. pub fn set_no_pause(&mut self) { unsafe { - beans_rs::PAUSE_ONCE_DONE = self.root_matches.get_flag("no-pause") == false; + beans_rs::PAUSE_ONCE_DONE = !self.root_matches.get_flag("no-pause"); } } @@ -273,7 +277,7 @@ impl Launcher { /// NOTE this function uses `panic!` when `InstallWorkflow::wizard` fails. panics are handled /// and are reported via sentry. pub async fn task_install(&mut self, matches: &ArgMatches) { - self.to_location = Launcher::find_arg_sourcemods_location(&matches); + self.to_location = Launcher::find_arg_sourcemods_location(matches); if matches.get_flag("confirm") { unsafe { beans_rs::PROMPT_DO_WHATEVER = true; @@ -308,12 +312,10 @@ impl Launcher { } else { logic_done(); } + } else if let Err(e) = InstallWorkflow::wizard(&mut ctx).await { + panic!("Failed to run InstallWorkflow {:#?}", e); } else { - if let Err(e) = InstallWorkflow::wizard(&mut ctx).await { - panic!("Failed to run InstallWorkflow {:#?}", e); - } else { - logic_done(); - } + logic_done(); } } /// handler for the `install` subcommand where the `--target-version` @@ -349,7 +351,7 @@ impl Launcher { /// NOTE this function uses `panic!` when `VerifyWorkflow::wizard` fails. panics are handled /// and are reported via sentry. pub async fn task_verify(&mut self, matches: &ArgMatches) { - self.to_location = Launcher::find_arg_sourcemods_location(&matches); + self.to_location = Launcher::find_arg_sourcemods_location(matches); let mut ctx = self.try_create_context().await; if let Err(e) = VerifyWorkflow::wizard(&mut ctx).await { @@ -364,7 +366,7 @@ impl Launcher { /// NOTE this function uses `panic!` when `UpdateWorkflow::wizard` fails. panics are handled /// and are reported via sentry. pub async fn task_update(&mut self, matches: &ArgMatches) { - self.to_location = Launcher::find_arg_sourcemods_location(&matches); + self.to_location = Launcher::find_arg_sourcemods_location(matches); let mut ctx = self.try_create_context().await; if let Err(e) = UpdateWorkflow::wizard(&mut ctx).await { @@ -407,6 +409,7 @@ impl Launcher { } } } + fn show_msgbox_error(text: String) { beans_rs::gui::DialogBuilder::new() .with_title(String::from("beans - Fatal Error!")) diff --git a/src/version.rs b/src/version.rs index 2f16133..ebd8769 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,6 +1,7 @@ use crate::helper; use crate::helper::{find_sourcemod_path, InstallType}; use crate::BeansError; +use futures_util::TryFutureExt; use log::{debug, error, trace}; use std::backtrace::Backtrace; use std::collections::HashMap; @@ -16,27 +17,26 @@ pub fn get_current_version(sourcemods_location: Option) -> Option } match get_mod_location(sourcemods_location) { Some(smp_x) => { - // TODO generate BeansError instead of using .expect + // TODO generate BeansError instead of using panic let location = format!("{}.adastral", smp_x); let content = - read_to_string(&location).expect(format!("Failed to open {}", location).as_str()); + read_to_string(&location).unwrap_or_else(|_| panic!("Failed to open {}", location)); let data: AdastralVersionFile = serde_json::from_str(&content) - .expect(format!("Failed to deserialize data at {}", location).as_str()); - let parsed = data - .version - .parse::() - .expect(format!("Failed to convert version to usize! ({})", data.version).as_str()); + .unwrap_or_else(|_| panic!("Failed to deserialize data at {}", location)); + let parsed = data.version.parse::().unwrap_or_else(|_| { + panic!("Failed to convert version to usize! ({})", data.version) + }); + Some(parsed) } None => None, } } + fn get_version_location(sourcemods_location: Option) -> Option { - match get_mod_location(sourcemods_location) { - Some(v) => Some(format!("{}.adastral", v)), - None => None, - } + get_mod_location(sourcemods_location).map(|v| format!("{}.adastral", v)) } + /// get the full location of the sourcemod mod directory. fn get_mod_location(sourcemods_location: Option) -> Option { let smp_x = match sourcemods_location { @@ -54,115 +54,117 @@ fn get_mod_location(sourcemods_location: Option) -> Option { } }, }; - return Some(helper::join_path(smp_x, crate::data_dir())); + Some(helper::join_path(smp_x, crate::data_dir())) } + /// migrate from old file (.revision) to new file (.adastral) in sourcemod mod directory. pub fn update_version_file(sourcemods_location: Option) -> Result<(), BeansError> { let install_state = helper::install_state(sourcemods_location.clone()); - if install_state == InstallType::Adastral { - debug!( - "[version::update_version_file] install_state is {:#?}, ignoring.", - install_state - ); - return Ok(()); - } - // ignore :) - else if install_state == InstallType::OtherSourceManual { - debug!( - "[version::update_version_file] install_state is {:#?}, ignoring.", - install_state - ); - return Ok(()); - } - // ignore :) - else if install_state == InstallType::NotInstalled { - debug!( - "[version::update_version_file] install_state is {:#?}, ignoring.", - install_state - ); - return Ok(()); - } - let smp_x = match sourcemods_location { - Some(v) => v, - None => match find_sourcemod_path() { - Ok(v) => v, - Err(e) => { - error!( - "[version::update_version_file] Could not find sourcemods folder! {:}", - e - ); - debug!("{:#?}", e); - sentry::capture_error(&e); - return Err(e); - } - }, - }; - - let data_dir = helper::join_path(smp_x, crate::data_dir()); - - let old_version_file_location = format!("{}.revision", &data_dir); - let old_version_file_content = match read_to_string(&old_version_file_location) { - Ok(v) => v, - Err(e) => { + match install_state { + InstallType::NotInstalled => { debug!( - "[update_version_file] failed to read {}. {:#?}", - old_version_file_location, e + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state ); - sentry::capture_error(&e); - return Err(BeansError::VersionFileReadFailure { - error: e, - location: old_version_file_location, - }); } - }; - let old_version_idx = match old_version_file_content.parse::() { - Ok(v) => v, - Err(e) => { + InstallType::Adastral => { debug!( - "[update_version_file] Failed to parse content {} caused error {:}", - old_version_file_content, e + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state ); - sentry::capture_error(&e); - return Err(BeansError::VersionFileParseFailure { - error: e, - old_location: old_version_file_location, - old_content: old_version_file_content, - }); } - }; - - let new_file_content = AdastralVersionFile { - version: old_version_idx.to_string(), - }; - let new_version_file_location = format!("{}.adastral", &data_dir); - let new_version_file_content = match serde_json::to_string(&new_file_content) { - Ok(v) => v, - Err(e) => { - sentry::capture_error(&e); - return Err(BeansError::VersionFileSerialize { - error: e, - instance: new_file_content, - }); + InstallType::OtherSourceManual => { + debug!( + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state + ); } - }; + InstallType::OtherSource => { + let smp_x = match sourcemods_location { + Some(v) => v, + None => match find_sourcemod_path() { + Ok(v) => v, + Err(e) => { + error!( + "[version::update_version_file] Could not find sourcemods folder! {:}", + e + ); + debug!("{:#?}", e); + sentry::capture_error(&e); + return Err(e); + } + }, + }; - if let Err(e) = std::fs::write(new_version_file_location.clone(), new_version_file_content) { - sentry::capture_error(&e); - return Err(BeansError::VersionFileMigrationFailure { - error: e, - location: new_version_file_location, - }); - } - if let Err(e) = std::fs::remove_file(old_version_file_location.clone()) { - sentry::capture_error(&e); - return Err(BeansError::VersionFileMigrationDeleteFailure { - error: e, - location: old_version_file_location, - }); - } + let data_dir = helper::join_path(smp_x, crate::data_dir()); + + let old_version_file_location = format!("{}.revision", &data_dir); + let old_version_file_content = match read_to_string(&old_version_file_location) { + Ok(v) => v, + Err(e) => { + debug!( + "[update_version_file] failed to read {}. {:#?}", + old_version_file_location, e + ); + sentry::capture_error(&e); + return Err(BeansError::VersionFileReadFailure { + error: e, + location: old_version_file_location, + }); + } + }; + let old_version_idx = match old_version_file_content.parse::() { + Ok(v) => v, + Err(e) => { + debug!( + "[update_version_file] Failed to parse content {} caused error {:}", + old_version_file_content, e + ); + sentry::capture_error(&e); + return Err(BeansError::VersionFileParseFailure { + error: e, + old_location: old_version_file_location, + old_content: old_version_file_content, + }); + } + }; + + let new_file_content = AdastralVersionFile { + version: old_version_idx.to_string(), + }; + + let new_version_file_location = format!("{}.adastral", &data_dir); + let new_version_file_content = match serde_json::to_string(&new_file_content) { + Ok(v) => v, + Err(e) => { + sentry::capture_error(&e); + return Err(BeansError::VersionFileSerialize { + error: e, + instance: new_file_content, + }); + } + }; + if let Err(e) = + std::fs::write(new_version_file_location.clone(), new_version_file_content) + { + sentry::capture_error(&e); + return Err(BeansError::VersionFileMigrationFailure { + error: e, + location: new_version_file_location, + }); + } + if let Err(e) = std::fs::remove_file(old_version_file_location.clone()) { + sentry::capture_error(&e); + return Err(BeansError::VersionFileMigrationDeleteFailure { + error: e, + location: old_version_file_location, + }); + } + } + } Ok(()) } @@ -188,9 +190,9 @@ pub async fn get_version_list() -> Result { "[version::get_version_list] response text: {}", response_text ); - let data: RemoteVersionResponse = serde_json::from_str(&response_text)?; - return Ok(data); + let data: RemoteVersionResponse = serde_json::from_str(&response_text)?; + Ok(data) } /// Version file that is used as `.adastral` in the sourcemod mod folder. @@ -198,6 +200,7 @@ pub async fn get_version_list() -> Result { pub struct AdastralVersionFile { pub version: String, } + impl AdastralVersionFile { pub fn write(&self, sourcemods_location: Option) -> Result<(), BeansError> { match get_version_location(sourcemods_location) { @@ -227,6 +230,7 @@ impl AdastralVersionFile { } } } + /// Value of the `versions` property in `RemoteVersionResponse` #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RemoteVersion { @@ -241,12 +245,14 @@ pub struct RemoteVersion { #[serde(rename = "heal")] pub heal_url: Option, } + /// `versions.json` response content from remote server. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RemoteVersionResponse { pub versions: HashMap, pub patches: HashMap, } + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RemotePatch { pub url: String, diff --git a/src/wizard.rs b/src/wizard.rs index c87f683..a5c300a 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -11,6 +11,7 @@ pub struct WizardContext { pub context: RunnerContext, pub menu_trigger_count: u32, } + impl WizardContext { /// run the wizard! pub async fn run(sml_via: SourceModDirectoryParam) -> Result<(), BeansError> { @@ -56,7 +57,7 @@ impl WizardContext { menu_trigger_count: 0u32, }; i.menu().await; - return Ok(()); + Ok(()) } /// Show the menu @@ -134,9 +135,10 @@ fn get_path() -> String { prompt_sourcemod_location() }) } + fn prompt_sourcemod_location() -> String { let res = helper::get_input("Please provide your sourcemods folder, then press enter."); - return if !helper::file_exists(res.clone()) { + if !helper::file_exists(res.clone()) { eprintln!("The location you provided doesn't exist. Try again."); prompt_sourcemod_location() } else if !helper::is_directory(res.clone()) { @@ -144,5 +146,5 @@ fn prompt_sourcemod_location() -> String { prompt_sourcemod_location() } else { res - }; + } } diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index 04bc793..6d2aec7 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -5,12 +5,13 @@ use log::{info, warn}; pub struct CleanWorkflow { pub context: RunnerContext, } + impl CleanWorkflow { pub fn wizard(_ctx: &mut RunnerContext) -> Result<(), BeansError> { let target_directory = helper::get_tmp_dir(); info!("[CleanWorkflow] Cleaning up {}", target_directory); - if helper::file_exists(target_directory.clone()) == false { + if !helper::file_exists(target_directory.clone()) { warn!("[CleanWorkflow] Temporary directory not found, nothing to clean.") } @@ -31,6 +32,6 @@ impl CleanWorkflow { } info!("[CleanWorkflow] Done!"); - return Ok(()); + Ok(()) } } diff --git a/src/workflows/install.rs b/src/workflows/install.rs index 24897a1..93364f4 100644 --- a/src/workflows/install.rs +++ b/src/workflows/install.rs @@ -81,7 +81,7 @@ impl InstallWorkflow { version_id: usize, version: RemoteVersion, ) -> Result<(), BeansError> { - if Self::prompt_confirm(ctx.current_version) == false { + if !Self::prompt_confirm(ctx.current_version) { info!("[InstallWorkflow] Operation aborted by user"); return Ok(()); } @@ -113,7 +113,7 @@ impl InstallWorkflow { out_dir: String, version_id: Option, ) -> Result<(), BeansError> { - if helper::file_exists(package_loc.clone()) == false { + if !helper::file_exists(package_loc.clone()) { error!("[InstallWorkflow::Wizard] Failed to find package! (location: {package_loc})"); return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::FileNotFound { diff --git a/src/workflows/update.rs b/src/workflows/update.rs index 9101ca6..f1331a1 100644 --- a/src/workflows/update.rs +++ b/src/workflows/update.rs @@ -31,7 +31,7 @@ impl UpdateWorkflow { ctx.gameinfo_perms()?; - if helper::has_free_space(ctx.sourcemod_path.clone(), patch.clone().tempreq)? == false { + if !helper::has_free_space(ctx.sourcemod_path.clone(), patch.clone().tempreq)? { println!( "[UpdateWorkflow::wizard] Not enough free space! Requires {}", helper::format_size(patch.tempreq) From 04e96b3f4971b562443759677a06e23eb11305b4 Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:46:23 +0800 Subject: [PATCH 37/65] removed intoiter --- src/helper/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 2a15eba..82a1cc5 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -342,7 +342,7 @@ pub fn format_size(i: usize) -> String { let dec: String = value.chars().rev().take(dec_l as usize).collect(); - let mut dec_x: String = dec.chars().into_iter().rev().take(decimal_points).collect(); + let mut dec_x: String = dec.chars().rev().take(decimal_points).collect(); dec_x = dec_x.trim_end_matches('0').to_string(); let whole_l = value.len() - dec_l; From 81d873f327d8ca7688167b81f7cf7e1f5ce09d3a Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:49:58 +0800 Subject: [PATCH 38/65] i found a bit more --- src/helper/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 82a1cc5..c6d2056 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -179,7 +179,7 @@ pub fn format_directory_path(location: String) -> String { while x.ends_with(crate::PATH_SEP) { x.pop(); } - if x.ends_with(crate::PATH_SEP) == false { + if !x.ends_with(crate::PATH_SEP) { x.push_str(crate::PATH_SEP); } x @@ -340,7 +340,7 @@ pub fn format_size(i: usize) -> String { dec_l = decimal_points * 5; } - let dec: String = value.chars().rev().take(dec_l as usize).collect(); + let dec: String = value.chars().rev().take(dec_l).collect(); let mut dec_x: String = dec.chars().rev().take(decimal_points).collect(); dec_x = dec_x.trim_end_matches('0').to_string(); From b9e08ccd7fdb47d5b5c0ac1171b93be9af559aa9 Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:54:47 +0800 Subject: [PATCH 39/65] added supression for error size --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5e4d890..41e0061 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,12 @@ #![feature(error_generic_member_access)] #![feature(panic_info_message)] +// todo +// https://rust-lang.github.io/rust-clippy/master/index.html#/result_large_err +// https://github.com/ktwrd/beans-rs/pull/30 +#![allow(clippy::result_large_err)] + + use include_flate::flate; mod ctx; From 5cdf587f37ec54cc44119056df7651a4b041a74f Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:15:20 +0800 Subject: [PATCH 40/65] `AlwaysNextLine` with fmt --- build.rs | 13 +- rustfmt.toml | 1 + src/appvar.rs | 37 +++-- src/butler.rs | 33 +++-- src/ctx.rs | 84 ++++++++---- src/depends.rs | 37 +++-- src/error.rs | 3 +- src/flags.rs | 6 +- src/gui/dialog.rs | 21 ++- src/gui/mod.rs | 18 ++- src/helper/linux.rs | 33 +++-- src/helper/mod.rs | 285 ++++++++++++++++++++++++++------------- src/helper/windows.rs | 15 ++- src/lib.rs | 23 ++-- src/logger.rs | 24 ++-- src/main.rs | 123 +++++++++++------ src/version.rs | 87 ++++++++---- src/wizard.rs | 56 +++++--- src/workflows/clean.rs | 9 +- src/workflows/install.rs | 44 ++++-- src/workflows/update.rs | 27 ++-- src/workflows/verify.rs | 15 ++- 22 files changed, 671 insertions(+), 323 deletions(-) create mode 100644 rustfmt.toml diff --git a/build.rs b/build.rs index da61007..532b3b6 100644 --- a/build.rs +++ b/build.rs @@ -25,7 +25,8 @@ fn fltk() -> Result<(), BuildError> { if let Err(e) = g.in_out( "src/gui/shared_ui.fl", out_path.join("shared_ui.rs").to_str().unwrap(), - ) { + ) + { return Err(BuildError::FLTK(format!( "Failed to build shared_ui.fl {:#?}", e @@ -45,8 +46,10 @@ fn path_exists(path: String) -> bool { #[cfg(target_os = "windows")] fn windows_icon() -> Result<(), BuildError> { let icon_location = OVERRIDE_ICON_LOCATION.unwrap_or("icon.ico"); - if env::var_os("CARGO_CFG_WINDOWS").is_some() { - if !path_exists(icon_location.to_string()) { + if env::var_os("CARGO_CFG_WINDOWS").is_some() + { + if !path_exists(icon_location.to_string()) + { print!("icon.ico not found. Not embedding icon"); return Ok(()); } @@ -55,7 +58,9 @@ fn windows_icon() -> Result<(), BuildError> { .set_icon(icon_location) .compile()?; print!("successfully set icon"); - } else { + } + else + { print!("not on windows, can't embed icon"); } Ok(()) diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..74854ec --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +control_brace_style="AlwaysNextLine" \ No newline at end of file diff --git a/src/appvar.rs b/src/appvar.rs index 81190de..cde3ded 100644 --- a/src/appvar.rs +++ b/src/appvar.rs @@ -33,11 +33,13 @@ impl AppVarData { pub fn parse() -> Self { debug!("[AppVarData::parse] trying to get JSON_DATA"); let x = JSON_DATA.read(); - if let Ok(data) = x { + if let Ok(data) = x + { debug!("[AppVarData::parse] JSON_DATA= {:#?}", data); return serde_json::from_str(&data).expect("Failed to deserialize JSON_DATA"); } - if let Err(e) = x { + if let Err(e) = x + { panic!("[AppVarData::parse] Failed to read JSON_DATA {:#?}", e); } unreachable!(); @@ -60,13 +62,17 @@ impl AppVarData { /// NOTE this function panics when Err on `AVD_INSTANCE.read()`. pub fn get() -> Self { let avd_read = AVD_INSTANCE.read(); - if let Ok(v) = avd_read { + if let Ok(v) = avd_read + { let vc = v.clone(); - if let Some(x) = vc { + if let Some(x) = vc + { debug!("[AppVarData::get] Instance exists in AVD_INSTANCE, so lets return that."); return x; } - } else if let Err(e) = avd_read { + } + else if let Err(e) = avd_read + { panic!("[AppVarData::get] Failed to read AVD_INSTANCE {:#?}", e); } @@ -79,15 +85,18 @@ impl AppVarData { pub fn reset() -> Self { let instance = AppVarData::parse(); - match AVD_INSTANCE.write() { - Ok(mut data) => { + match AVD_INSTANCE.write() + { + Ok(mut data) => + { *data = Some(instance.clone()); debug!( "[reset_appvar] set content of AVD_INSTANCE to {:#?}", instance ); } - Err(e) => { + Err(e) => + { panic!("[reset_appvar] Failed to set AVD_INSTANCE! {:#?}", e); } } @@ -102,16 +111,20 @@ impl AppVarData { /// is called. pub fn set_json_data(data: AppVarData) -> Result<(), BeansError> { debug!("[set_json_data] {:#?}", data); - match serde_json::to_string(&data) { - Ok(v) => { - if let Ok(mut ms) = JSON_DATA.write() { + match serde_json::to_string(&data) + { + Ok(v) => + { + if let Ok(mut ms) = JSON_DATA.write() + { *ms = v.to_string(); debug!("[set_json_data] successfully set data, calling reset_appvar()"); } Self::reset(); Ok(()) } - Err(e) => { + Err(e) => + { error!( "[appvar::set_json_data] Failed to serialize data to string! {:}", e diff --git a/src/butler.rs b/src/butler.rs index b8ff64c..17f01cc 100644 --- a/src/butler.rs +++ b/src/butler.rs @@ -16,7 +16,8 @@ pub fn verify( format!("--heal=archive,{}", remote).as_str(), ]); debug!("[butler::verify] {:#?}", cmd); - match cmd.spawn() { + match cmd.spawn() + { Err(e) => Err(BeansError::ButlerVerifyFailure { signature_url, gamedir, @@ -24,11 +25,14 @@ pub fn verify( error: e, backtrace: Backtrace::capture(), }), - Ok(mut v) => { + Ok(mut v) => + { let w = v.wait()?; debug!("[butler::verify] Exited with {:#?}", w); - if let Some(c) = w.code() { - if c != 0 { + if let Some(c) = w.code() + { + if c != 0 + { error!("[butler::verify] exited with code {c}, which isn't good!"); panic!("[butler::verify] exited with code {c}"); } @@ -43,14 +47,16 @@ pub async fn patch_dl( patch_filename: String, gamedir: String, ) -> Result { - if helper::file_exists(staging_dir.clone()) { + if helper::file_exists(staging_dir.clone()) + { std::fs::remove_dir_all(&staging_dir)?; } let tmp_file = helper::get_tmp_file(patch_filename); info!("[butler::patch_dl] downloading {} to {}", dl_url, tmp_file); helper::download_with_progress(dl_url, tmp_file.clone()).await?; - if !helper::file_exists(tmp_file.clone()) { + if !helper::file_exists(tmp_file.clone()) + { return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::FileNotFound { location: tmp_file }, }); @@ -72,8 +78,10 @@ pub fn patch( &gamedir, ]); debug!("[butler::patch] {:#?}", &cmd); - match cmd.spawn() { - Err(e) => { + match cmd.spawn() + { + Err(e) => + { let xe = BeansError::ButlerPatchFailure { patchfile_location, gamedir, @@ -84,11 +92,14 @@ pub fn patch( sentry::capture_error(&xe); Err(xe) } - Ok(mut v) => { + Ok(mut v) => + { let w = v.wait()?; debug!("Exited with {:#?}", w); - if let Some(c) = w.code() { - if c != 0 { + if let Some(c) = w.code() + { + if c != 0 + { error!("[butler::patch] exited with code {c}, which isn't good!"); panic!("[butler::patch] exited with code {c}"); } diff --git a/src/ctx.rs b/src/ctx.rs index bb6500f..9ce62e1 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -17,7 +17,8 @@ pub struct RunnerContext { impl RunnerContext { pub async fn create_auto(sml_via: SourceModDirectoryParam) -> Result { depends::try_write_deps(); - if let Err(e) = depends::try_install_vcredist().await { + if let Err(e) = depends::try_install_vcredist().await + { sentry::capture_error(&e); println!("Failed to install vcredist! {:}", e); debug!( @@ -25,10 +26,13 @@ impl RunnerContext { e ); } - let sourcemod_path = parse_location(match sml_via { - SourceModDirectoryParam::AutoDetect => match find_sourcemod_path() { + let sourcemod_path = parse_location(match sml_via + { + SourceModDirectoryParam::AutoDetect => match find_sourcemod_path() + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); debug!( "[RunnerContext::create_auto] Failed to find sourcemods folder. {:#?}", @@ -37,7 +41,8 @@ impl RunnerContext { return Err(BeansError::SourceModLocationNotFound); } }, - SourceModDirectoryParam::WithLocation(l) => { + SourceModDirectoryParam::WithLocation(l) => + { debug!( "[RunnerContext::create_auto] Using specified location {}", l @@ -47,7 +52,8 @@ impl RunnerContext { }); let version_list = version::get_version_list().await?; - if helper::install_state(Some(sourcemod_path.clone())) == InstallType::OtherSource { + if helper::install_state(Some(sourcemod_path.clone())) == InstallType::OtherSource + { version::update_version_file(Some(sourcemod_path.clone()))?; } @@ -83,8 +89,10 @@ impl RunnerContext { /// Get the latest item in `remote_version_list` pub fn latest_remote_version(&mut self) -> (usize, RemoteVersion) { let mut highest = usize::MIN; - for (key, _) in self.remote_version_list.clone().versions.into_iter() { - if key > highest { + for (key, _) in self.remote_version_list.clone().versions.into_iter() + { + if key > highest + { highest = key; } } @@ -94,10 +102,14 @@ impl RunnerContext { /// Get the RemoteVersion that matches `self.current_version` pub fn current_remote_version(&mut self) -> Result { - match self.current_version { - Some(cv) => { - for (v, i) in self.remote_version_list.clone().versions.into_iter() { - if v == cv { + match self.current_version + { + Some(cv) => + { + for (v, i) in self.remote_version_list.clone().versions.into_iter() + { + if v == cv + { return Ok(i.clone()); } } @@ -116,9 +128,12 @@ impl RunnerContext { pub fn has_patch_available(&mut self) -> Option { let current_version = self.current_version; let (remote_version, _) = self.latest_remote_version(); - match current_version { - Some(cv) => { - for (_, patch) in self.remote_version_list.clone().patches.into_iter() { + match current_version + { + Some(cv) => + { + for (_, patch) in self.remote_version_list.clone().patches.into_iter() + { if patch.file == format!( "{}-{}to{}.pwr", @@ -138,12 +153,15 @@ impl RunnerContext { pub fn read_gameinfo_file(&mut self) -> Result>, BeansError> { self.gameinfo_perms()?; let location = self.gameinfo_location(); - if !helper::file_exists(location.clone()) { + if !helper::file_exists(location.clone()) + { return Ok(None); } - let file = match std::fs::read(&location) { + let file = match std::fs::read(&location) + { Ok(v) => v, - Err(error) => { + Err(error) => + { let ex = BeansError::GameInfoFileReadFail { error, location, @@ -167,9 +185,11 @@ impl RunnerContext { #[cfg(target_os = "linux")] pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> { let location = self.gameinfo_location(); - if helper::file_exists(location.clone()) { + if helper::file_exists(location.clone()) + { let perm = std::fs::Permissions::from_mode(0o644 as u32); - if let Err(e) = std::fs::set_permissions(&location, perm.clone()) { + if let Err(e) = std::fs::set_permissions(&location, perm.clone()) + { let xe = BeansError::GameInfoPermissionSetFail { error: e, permissions: perm.clone(), @@ -196,8 +216,10 @@ impl RunnerContext { let av = crate::appvar::parse(); let mut out_loc = helper::get_tmp_dir(); - if let Some(size) = version.pre_sz { - if !helper::has_free_space(out_loc.clone(), size)? { + if let Some(size) = version.pre_sz + { + if !helper::has_free_space(out_loc.clone(), size)? + { panic!("Not enough free space to install latest version!"); } } @@ -231,8 +253,10 @@ impl RunnerContext { let mut archive = tar::Archive::new(&tar_tmp_file); let x = archive.unpack(&out_dir); - if helper::file_exists(tar_tmp_location.clone()) { - if let Err(e) = std::fs::remove_file(tar_tmp_location.clone()) { + if helper::file_exists(tar_tmp_location.clone()) + { + if let Err(e) = std::fs::remove_file(tar_tmp_location.clone()) + { sentry::capture_error(&e); error!( "[RunnerContext::extract_package] Failed to delete temporary file: {:}", @@ -244,8 +268,10 @@ impl RunnerContext { ); } } - match x { - Err(e) => { + match x + { + Err(e) => + { let xe = BeansError::TarExtractFailure { src_file: tar_tmp_location, target_dir: out_dir, @@ -262,13 +288,15 @@ impl RunnerContext { #[cfg(target_os = "linux")] pub fn prepare_symlink(&mut self) -> Result<(), BeansError> { - for pair in SYMLINK_FILES.iter() { + for pair in SYMLINK_FILES.iter() + { let target: &str = pair[1]; let mod_location = self.get_mod_location(); let ln_location = format!("{}{}", mod_location, target); if helper::file_exists(ln_location.clone()) && !helper::is_symlink(ln_location.clone()) { - if let Err(e) = std::fs::remove_file(&ln_location) { + if let Err(e) = std::fs::remove_file(&ln_location) + { trace!( "[RunnerContext::prepare_symlink] failed to remove {}\n{:#?}", ln_location, diff --git a/src/depends.rs b/src/depends.rs index 486e78b..5f29160 100644 --- a/src/depends.rs +++ b/src/depends.rs @@ -12,9 +12,11 @@ pub fn try_write_deps() { safe_write_file(get_butler_1_location().as_str(), &BUTLER_LIB_1); safe_write_file(get_butler_2_location().as_str(), &BUTLER_LIB_2); #[cfg(not(target_os = "windows"))] - if helper::file_exists(get_butler_location()) { + if helper::file_exists(get_butler_location()) + { let p = std::fs::Permissions::from_mode(0744 as u32); - if let Err(e) = std::fs::set_permissions(get_butler_location(), p) { + if let Err(e) = std::fs::set_permissions(get_butler_location(), p) + { sentry::capture_error(&e); error!( "[depends::try_write_deps] Failed to set permissions for {}", @@ -29,12 +31,16 @@ pub fn try_write_deps() { } } fn safe_write_file(location: &str, data: &[u8]) { - if !helper::file_exists(location.to_string()) { - if let Err(e) = std::fs::write(location, data) { + if !helper::file_exists(location.to_string()) + { + if let Err(e) = std::fs::write(location, data) + { sentry::capture_error(&e); error!("[depends::try_write_deps] failed to extract {}", location); error!("[depends::try_write_deps] {:#?}", e); - } else { + } + else + { debug!("[depends::try_write_deps] extracted {}", location); } } @@ -52,16 +58,20 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> { pub async fn try_install_vcredist() -> Result<(), BeansError> { if !match winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE).open_subkey(String::from( "Software\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", - )) { - Ok(v) => { + )) + { + Ok(v) => + { let x: std::io::Result = v.get_value("Installed"); - match x { + match x + { Ok(_) => false, Err(_) => true, } } Err(_) => true, - } { + } + { debug!("[depends::try_install_vcredist] Seems like vcredist is already installed"); return Ok(()); } @@ -76,7 +86,8 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> { ) .await?; - if std::path::Path::new(&out_loc).exists() == false { + if std::path::Path::new(&out_loc).exists() == false + { return Err(BeansError::FileNotFound { location: out_loc.clone(), backtrace: Backtrace::capture(), @@ -89,8 +100,10 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> { .expect("Failed to install vsredist!") .wait()?; - if helper::file_exists(out_loc.clone()) { - if let Err(e) = std::fs::remove_file(&out_loc) { + if helper::file_exists(out_loc.clone()) + { + if let Err(e) = std::fs::remove_file(&out_loc) + { sentry::capture_error(&e); debug!( "[depends::try_install_vcredist] Failed to remove installer {:#?}", diff --git a/src/error.rs b/src/error.rs index 489f31c..60f77f2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -192,7 +192,8 @@ pub struct GameinfoBackupWriteFail { } impl Display for GameinfoBackupFailureReason { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { + match self + { GameinfoBackupFailureReason::ReadContentFail(v) => write!( f, "Couldn't read the content at {} ({:})", diff --git a/src/flags.rs b/src/flags.rs index 3329edb..b02a619 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -28,7 +28,8 @@ pub fn has_flag(flag: LaunchFlag) -> bool { /// Add a flag to `LAUNCH_FLAGS` pub fn add_flag(flag: LaunchFlag) { unsafe { - if let LaunchFlag::DEBUG_MODE = flag { + if let LaunchFlag::DEBUG_MODE = flag + { crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_DEFAULT; }; @@ -41,7 +42,8 @@ pub fn add_flag(flag: LaunchFlag) { /// remove a flag from `LAUNCH_FLAGS` pub fn remove_flag(flag: LaunchFlag) { unsafe { - if flag == LaunchFlag::DEBUG_MODE { + if flag == LaunchFlag::DEBUG_MODE + { crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_MINIMAL; }; let mut data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index 2ed128e..19c6837 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -31,16 +31,19 @@ impl DialogBuilder { Self::default() } pub fn with_png_data(mut self, data: &[u8]) -> Self { - match PngImage::from_data(data) { + match PngImage::from_data(data) + { Ok(img) => self.icon = Some(img), - Err(e) => { + Err(e) => + { warn!("[DialogBuilder::with_png] Failed to set icon! {:#?}", e); } } self } pub fn with_icon(self, kind: DialogIconKind) -> Self { - let data: &Vec = match kind { + let data: &Vec = match kind + { DialogIconKind::Default => &icon::DEFAULT_RAW_X32, DialogIconKind::Warn => &icon::DEFAULT_WARN_RAW_X32, DialogIconKind::Error => &icon::DEFAULT_ERROR_RAW_X32, @@ -56,7 +59,8 @@ impl DialogBuilder { self } pub fn run(&self) { - if !crate::has_gui_support() { + if !crate::has_gui_support() + { println!("============ {} ============", self.title); println!("{}", self.content); return; @@ -80,14 +84,17 @@ impl DialogBuilder { ui.btn_ok.set_pos(25, ui.win.height() - 24 - 5); window_centre_screen(&mut ui.win); - ui.win.handle(move |w, ev| match ev { - fltk::enums::Event::Resize => { + ui.win.handle(move |w, ev| match ev + { + fltk::enums::Event::Resize => + { let height = w.height(); ui.btn_ok.set_pos(25, height - 24 - 5); ui.btn_ok.set_size(70, 24); let (lw, lh) = ui.label.measure_label(); let cw = w.width(); - if cw != initial_width && cw > lw + 50 { + if cw != initial_width && cw > lw + 50 + { w.set_size(lw + 50, 10 + lh + 5 + ui.btn_ok.height() + 5); } false diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 1808b0d..4b04931 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -57,9 +57,12 @@ pub fn get_center_screen() -> (i32, i32) { /// current screen. pub fn window_ensure(win: &mut Window, width: i32, height: i32) { window_centre_screen(win); - win.handle(move |w, ev| match ev { - fltk::enums::Event::Resize => { - if w.width() > width || w.height() > height { + win.handle(move |w, ev| match ev + { + fltk::enums::Event::Resize => + { + if w.width() > width || w.height() > height + { w.set_size(width, height); } true @@ -71,7 +74,8 @@ pub fn window_ensure(win: &mut Window, width: i32, height: i32) { } pub fn apply_app_scheme() { - let theme_content = match dark_light::detect() { + let theme_content = match dark_light::detect() + { dark_light::Mode::Light => color_themes::GRAY_THEME, _ => color_themes::DARK_THEME, }; @@ -84,8 +88,10 @@ pub fn apply_app_scheme() { } pub fn wait_for_quit(app: &app::App, receive_action: &Receiver) { - while app.wait() { - if let Some(GUIAppStatus::Quit) = receive_action.recv() { + while app.wait() + { + if let Some(GUIAppStatus::Quit) = receive_action.recv() + { unsafe { crate::PAUSE_ONCE_DONE = false; } diff --git a/src/helper/linux.rs b/src/helper/linux.rs index bea35af..e89de29 100644 --- a/src/helper/linux.rs +++ b/src/helper/linux.rs @@ -16,9 +16,11 @@ pub const STEAM_POSSIBLE_DIR: &[&str] = &[ pub fn find_sourcemod_path() -> Result { let reg_path = find_steam_reg_path()?; - let reg_content = match read_to_string(reg_path.as_str()) { + let reg_content = match read_to_string(reg_path.as_str()) + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); return Err(BeansError::FileOpenFailure { location: reg_path, @@ -27,8 +29,10 @@ pub fn find_sourcemod_path() -> Result { } }; - for line in reg_content.lines() { - if line.contains("SourceModInstallPath") { + for line in reg_content.lines() + { + if line.contains("SourceModInstallPath") + { let split = &line.split("\"SourceModInstallPath\""); let last = split .clone() @@ -44,22 +48,29 @@ pub fn find_sourcemod_path() -> Result { } /// returns the first item in STEAM_POSSIBLE_DIR that exists. otherwise None fn find_steam_reg_path() -> Result { - for x in STEAM_POSSIBLE_DIR.iter() { - match simple_home_dir::home_dir() { - Some(v) => match v.to_str() { - Some(k) => { + for x in STEAM_POSSIBLE_DIR.iter() + { + match simple_home_dir::home_dir() + { + Some(v) => match v.to_str() + { + Some(k) => + { let h = format_directory_path(k.to_string()); let reg_loc = x.replace("~", h.as_str()); - if crate::helper::file_exists(reg_loc.clone()) { + if crate::helper::file_exists(reg_loc.clone()) + { return Ok(reg_loc.clone()); } } - None => { + None => + { debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir().to_str() returned None!"); return Err(BeansError::SteamNotFound); } }, - None => { + None => + { debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir() returned None!"); return Err(BeansError::SteamNotFound); } diff --git a/src/helper/mod.rs b/src/helper/mod.rs index c6d2056..8e05354 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -45,26 +45,31 @@ pub enum InstallType { impl PartialEq for InstallType { fn eq(&self, other: &Self) -> bool { - match self { - InstallType::NotInstalled => match other { + match self + { + InstallType::NotInstalled => match other + { InstallType::NotInstalled => true, InstallType::Adastral => false, InstallType::OtherSource => false, InstallType::OtherSourceManual => false, }, - InstallType::Adastral => match other { + InstallType::Adastral => match other + { InstallType::NotInstalled => false, InstallType::Adastral => true, InstallType::OtherSource => false, InstallType::OtherSourceManual => false, }, - InstallType::OtherSource => match other { + InstallType::OtherSource => match other + { InstallType::NotInstalled => false, InstallType::Adastral => false, InstallType::OtherSource => true, InstallType::OtherSourceManual => true, }, - InstallType::OtherSourceManual => match other { + InstallType::OtherSourceManual => match other + { InstallType::NotInstalled => false, InstallType::Adastral => false, InstallType::OtherSource => false, @@ -76,11 +81,14 @@ impl PartialEq for InstallType { /// get the current type of installation. pub fn install_state(sourcemods_location: Option) -> InstallType { - let mut smp_x = match sourcemods_location { + let mut smp_x = match sourcemods_location + { Some(v) => v, - None => match find_sourcemod_path() { + None => match find_sourcemod_path() + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); debug!( "[helper::install_state] {} {:#?}", @@ -91,17 +99,23 @@ pub fn install_state(sourcemods_location: Option) -> InstallType { } }, }; - if smp_x.ends_with("/") || smp_x.ends_with("\\") { + if smp_x.ends_with("/") || smp_x.ends_with("\\") + { smp_x.pop(); } let data_dir = join_path(smp_x, crate::data_dir()); - if file_exists(format!("{}.adastral", data_dir)) { + if file_exists(format!("{}.adastral", data_dir)) + { return InstallType::Adastral; - } else if file_exists(format!("{}.revision", data_dir)) { + } + else if file_exists(format!("{}.revision", data_dir)) + { return InstallType::OtherSource; - } else if file_exists(format!("{}gameinfo.txt", data_dir)) { + } + else if file_exists(format!("{}gameinfo.txt", data_dir)) + { return InstallType::OtherSourceManual; } InstallType::NotInstalled @@ -111,9 +125,12 @@ pub fn install_state(sourcemods_location: Option) -> InstallType { pub fn get_input(prompt: &str) -> String { println!("{}", prompt); let mut input = String::new(); - match std::io::stdin().read_line(&mut input) { - Ok(_goes_into_input_above) => {} - Err(_no_updates_is_fine) => {} + match std::io::stdin().read_line(&mut input) + { + Ok(_goes_into_input_above) => + {} + Err(_no_updates_is_fine) => + {} } input.trim().to_string() } @@ -135,7 +152,8 @@ pub fn is_directory(location: String) -> bool { /// Check if the file at the location provided is a symlink. pub fn is_symlink(location: String) -> bool { - match std::fs::symlink_metadata(&location) { + match std::fs::symlink_metadata(&location) + { Ok(meta) => meta.file_type().is_symlink(), Err(_) => false, } @@ -158,7 +176,8 @@ pub fn join_path(tail: String, head: String) -> String { .to_string() .replace("/", crate::PATH_SEP) .replace("\\", crate::PATH_SEP); - while h.starts_with(crate::PATH_SEP) { + while h.starts_with(crate::PATH_SEP) + { h.remove(0); } @@ -166,7 +185,8 @@ pub fn join_path(tail: String, head: String) -> String { } pub fn remove_path_head(location: String) -> String { - if let Some(Some(m)) = std::path::Path::new(&location).parent().map(|p| p.to_str()) { + if let Some(Some(m)) = std::path::Path::new(&location).parent().map(|p| p.to_str()) + { return m.to_string(); } String::new() @@ -176,10 +196,12 @@ pub fn remove_path_head(location: String) -> String { pub fn format_directory_path(location: String) -> String { let mut x = location.to_string().replace(['/', '\\'], crate::PATH_SEP); - while x.ends_with(crate::PATH_SEP) { + while x.ends_with(crate::PATH_SEP) + { x.pop(); } - if !x.ends_with(crate::PATH_SEP) { + if !x.ends_with(crate::PATH_SEP) + { x.push_str(crate::PATH_SEP); } x @@ -197,13 +219,18 @@ pub fn canonicalize(location: &str) -> Result { pub fn parse_location(location: String) -> String { let path = std::path::Path::new(&location); - let real_location = match path.to_str() { - Some(v) => { + let real_location = match path.to_str() + { + Some(v) => + { let p = canonicalize(v); - match p { - Ok(x) => match x.clone().to_str() { + match p + { + Ok(x) => match x.clone().to_str() + { Some(m) => m.to_string(), - None => { + None => + { debug!( "[helper::parse_location] Failed to parse location to string {}", location @@ -211,8 +238,10 @@ pub fn parse_location(location: String) -> String { return location; } }, - Err(e) => { - if format!("{:}", e).starts_with("No such file or directory") { + Err(e) => + { + if format!("{:}", e).starts_with("No such file or directory") + { return location; } sentry::capture_error(&e); @@ -226,7 +255,8 @@ pub fn parse_location(location: String) -> String { } } } - None => { + None => + { debug!( "[helper::parse_location] Failed to parse location {}", location @@ -240,17 +270,21 @@ pub fn parse_location(location: String) -> String { /// Get the amount of free space on the drive in the location provided. pub fn get_free_space(location: String) -> Result { let mut data: HashMap = HashMap::new(); - for disk in sysinfo::Disks::new_with_refreshed_list().list() { - if let Some(mp) = disk.mount_point().to_str() { + for disk in sysinfo::Disks::new_with_refreshed_list().list() + { + if let Some(mp) = disk.mount_point().to_str() + { debug!("[get_free_space] space: {} {}", mp, disk.available_space()); data.insert(mp.to_string(), disk.available_space()); } } let mut l = parse_location(location.clone()); - while !l.is_empty() { + while !l.is_empty() + { debug!("[get_free_space] Checking if {} is in data", l); - if let Some(x) = data.get(&l) { + if let Some(x) = data.get(&l) + { return Ok(*x); } l = remove_path_head(l); @@ -269,9 +303,11 @@ pub fn has_free_space(location: String, size: usize) -> Result /// Download file at the URL provided to the output location provided /// This function will also show a progress bar with indicatif. pub async fn download_with_progress(url: String, out_location: String) -> Result<(), BeansError> { - let res = match reqwest::Client::new().get(&url).send().await { + let res = match reqwest::Client::new().get(&url).send().await + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::Reqwest { @@ -294,9 +330,11 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result pb.set_message(format!("Downloading {}", &url)); // download chunks - let mut file = match std::fs::File::create(out_location.clone()) { + let mut file = match std::fs::File::create(out_location.clone()) + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); return Err(BeansError::FileOpenFailure { location: out_location, @@ -307,7 +345,8 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result let mut downloaded: u64 = 0; let mut stream = res.bytes_stream(); - while let Some(item) = stream.next().await { + while let Some(item) = stream.next().await + { let chunk = item.expect("Failed to write content to file"); file.write_all(&chunk) .expect("Failed to write content to file"); @@ -326,17 +365,28 @@ pub fn format_size(i: usize) -> String { let decimal_points: usize = 3; let mut dec_l = decimal_points * 6; - if i < 1_000 { + if i < 1_000 + { dec_l = 0 - } else if i < 1_000_000 { + } + else if i < 1_000_000 + { dec_l = decimal_points - } else if i < 1_000_000_000 { + } + else if i < 1_000_000_000 + { dec_l = decimal_points * 2; - } else if i < 1_000_000_000_000 { + } + else if i < 1_000_000_000_000 + { dec_l = decimal_points * 3; - } else if i < 1_000_000_000_000_000 { + } + else if i < 1_000_000_000_000_000 + { dec_l = decimal_points * 4; - } else if i < 1_000_000_000_000_000_000 { + } + else if i < 1_000_000_000_000_000_000 + { dec_l = decimal_points * 5; } @@ -348,7 +398,8 @@ pub fn format_size(i: usize) -> String { let whole_l = value.len() - dec_l; let mut whole: String = value.chars().take(whole_l).collect(); - if !dec_x.is_empty() { + if !dec_x.is_empty() + { whole.push('.'); } let pfx_data: Vec<(usize, &str)> = vec![ @@ -358,8 +409,10 @@ pub fn format_size(i: usize) -> String { (1_000_000_000_000, "gb"), (1_000_000_000_000_000, "tb"), ]; - for (s, c) in pfx_data.into_iter() { - if i < s { + for (s, c) in pfx_data.into_iter() + { + if i < s + { return format!("{}{}{}", whole, dec_x, c); } } @@ -373,11 +426,15 @@ pub fn format_size(i: usize) -> String { /// `Some` when the environment variable is set, and the directory exist. /// Otherwise `None` is returned. pub fn use_custom_tmpdir() -> Option { - if let Ok(x) = std::env::var(CUSTOM_TMPDIR_NAME) { + if let Ok(x) = std::env::var(CUSTOM_TMPDIR_NAME) + { let s = x.to_string(); - if dir_exists(s.clone()) { + if dir_exists(s.clone()) + { return Some(s); - } else { + } + else + { warn!( "[use_custom_tmp_dir] Custom temporary directory \"{}\" doesn't exist", s @@ -392,32 +449,46 @@ pub const CUSTOM_TMPDIR_NAME: &str = "ADASTRAL_TMPDIR"; /// Create directory in temp directory with name of "beans-rs" pub fn get_tmp_dir() -> String { let mut dir = std::env::temp_dir().to_str().unwrap_or("").to_string(); - if let Some(x) = use_custom_tmpdir() { + if let Some(x) = use_custom_tmpdir() + { dir = x; - } else if is_steamdeck() { + } + else if is_steamdeck() + { trace!("[helper::get_tmp_dir] Detected that we are running on a steam deck. Using ~/.tmp/beans-rs"); - match simple_home_dir::home_dir() { - Some(v) => match v.to_str() { - Some(k) => { + match simple_home_dir::home_dir() + { + Some(v) => match v.to_str() + { + Some(k) => + { dir = format_directory_path(k.to_string()); dir = join_path(dir, String::from(".tmp")); } - None => { + None => + { trace!("[helper::get_tmp_dir] Failed to convert PathBuf to &str"); } }, - None => { + None => + { trace!("[helper::get_tmp_dir] Failed to get home directory."); } }; - } else if cfg!(target_os = "android") { + } + else if cfg!(target_os = "android") + { dir = String::from("/data/var/tmp"); - } else if cfg!(not(target_os = "windows")) { + } + else if cfg!(not(target_os = "windows")) + { dir = String::from("/var/tmp"); } dir = format_directory_path(dir); - if !dir_exists(dir.clone()) { - if let Err(e) = std::fs::create_dir(&dir) { + if !dir_exists(dir.clone()) + { + if let Err(e) = std::fs::create_dir(&dir) + { trace!("[helper::get_tmp_dir] {:#?}", e); warn!( "[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", @@ -428,15 +499,19 @@ pub fn get_tmp_dir() -> String { dir = join_path(dir, String::from("beans-rs")); dir = format_directory_path(dir); - if !dir_exists(dir.clone()) { - if let Err(e) = std::fs::create_dir(&dir) { + if !dir_exists(dir.clone()) + { + if let Err(e) = std::fs::create_dir(&dir) + { trace!("[helper::get_tmp_dir] {:#?}", e); warn!( "[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", dir, e ); sentry::capture_error(&e); - } else { + } + else + { trace!("[helper::get_tmp_dir] created directory {}", dir); } } @@ -460,30 +535,38 @@ pub fn get_tmp_dir() -> String { /// This function will write to `log::trace` with the full error details before writing it to `log::warn` or `log::error`. Since errors from this /// aren't significant, `sentry::capture_error` will not be called. pub fn is_steamdeck() -> bool { - if cfg!(not(target_os = "linux")) { + if cfg!(not(target_os = "linux")) + { return false; } - match std::process::Command::new("uname").arg("-r").output() { - Ok(cmd) => { + match std::process::Command::new("uname").arg("-r").output() + { + Ok(cmd) => + { trace!("[helper::is_steamdeck] exit status: {}", &cmd.status); let stdout = &cmd.stdout.to_vec(); let stderr = &cmd.stderr.to_vec(); - if let Ok(x) = String::from_utf8(stderr.clone()) { + if let Ok(x) = String::from_utf8(stderr.clone()) + { trace!("[helper::is_steamdeck] stderr: {}", x); } - match String::from_utf8(stdout.clone()) { - Ok(x) => { + match String::from_utf8(stdout.clone()) + { + Ok(x) => + { trace!("[helper::is_steamdeck] stdout: {}", x); x.contains("valve") } - Err(e) => { + Err(e) => + { trace!("[helper::is_steamdeck] Failed to parse as utf8 {:#?}", e); false } } } - Err(e) => { + Err(e) => + { trace!("[helper::is_steamdeck] {:#?}", e); warn!("[helper::is_steamdeck] Failed to detect {:}", e); false @@ -504,9 +587,11 @@ pub async fn beans_has_update() -> Result, BeansError> .header(USER_AGENT, &format!("beans-rs/{}", crate::VERSION)) .send() .await; - let response = match rs { + let response = match rs + { Ok(v) => v, - Err(e) => { + Err(e) => + { trace!("Failed get latest release from github \nerror: {:#?}", e); return Err(BeansError::Reqwest { error: e, @@ -515,9 +600,11 @@ pub async fn beans_has_update() -> Result, BeansError> } }; let response_text = response.text().await?; - let data: GithubReleaseItem = match serde_json::from_str(&response_text) { + let data: GithubReleaseItem = match serde_json::from_str(&response_text) + { Ok(v) => v, - Err(e) => { + Err(e) => + { trace!( "Failed to deserialize GithubReleaseItem\nerror: {:#?}\ncontent: {:#?}", e, @@ -530,7 +617,8 @@ pub async fn beans_has_update() -> Result, BeansError> } }; trace!("{:#?}", data); - if !data.draft && !data.prerelease && data.tag_name != format!("v{}", crate::VERSION) { + if !data.draft && !data.prerelease && data.tag_name != format!("v{}", crate::VERSION) + { return Ok(Some(data.clone())); } Ok(None) @@ -539,10 +627,12 @@ pub async fn beans_has_update() -> Result, BeansError> pub fn restore_gameinfo(ctx: &mut RunnerContext, data: Vec) -> Result<(), BeansError> { let loc = ctx.gameinfo_location(); trace!("gameinfo location: {}", &loc); - if let Ok(m) = std::fs::metadata(&loc) { + if let Ok(m) = std::fs::metadata(&loc) + { trace!("gameinfo metadata: {:#?}", m); } - if let Err(e) = ctx.gameinfo_perms() { + if let Err(e) = ctx.gameinfo_perms() + { error!( "[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", e @@ -550,14 +640,16 @@ pub fn restore_gameinfo(ctx: &mut RunnerContext, data: Vec) -> Result<(), Be sentry::capture_error(&e); return Err(e); } - if let Err(e) = std::fs::write(&loc, data) { + if let Err(e) = std::fs::write(&loc, data) + { trace!("error: {:#?}", e); error!( "[helper::restore_gameinfo] Failed to write gameinfo.txt backup {:}", e ); } - if let Err(e) = ctx.gameinfo_perms() { + if let Err(e) = ctx.gameinfo_perms() + { error!( "[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", e @@ -576,8 +668,10 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { let current_time = chrono::Local::now(); let current_time_formatted = current_time.format("%Y%m%d-%H%M%S").to_string(); - if !file_exists(backupdir.clone()) { - if let Err(e) = std::fs::create_dir(&backupdir) { + if !file_exists(backupdir.clone()) + { + if let Err(e) = std::fs::create_dir(&backupdir) + { debug!("backupdir: {}", backupdir); debug!("error: {:#?}", e); error!( @@ -604,7 +698,8 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { ); let current_location = join_path(gamedir, String::from("gameinfo.txt")); - if !file_exists(current_location.clone()) { + if !file_exists(current_location.clone()) + { debug!( "[helper::backup_gameinfo] can't backup since {} doesn't exist", current_location @@ -612,9 +707,11 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { return Ok(()); } - let content = match std::fs::read_to_string(¤t_location) { + let content = match std::fs::read_to_string(¤t_location) + { Ok(v) => v, - Err(e) => { + Err(e) => + { debug!("location: {}", current_location); debug!("error: {:#?}", e); error!( @@ -633,13 +730,16 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { } }; - if file_exists(output_location.clone()) { - if let Err(e) = std::fs::remove_file(&output_location) { + if file_exists(output_location.clone()) + { + if let Err(e) = std::fs::remove_file(&output_location) + { warn!("[helper::backup_gameinfo] Failed to delete existing file, lets hope things don't break. {:} {}", e, output_location.clone()); } } - if let Err(e) = std::fs::write(&output_location, content) { + if let Err(e) = std::fs::write(&output_location, content) + { debug!("location: {}", output_location); debug!("error: {:#?}", e); error!( @@ -676,7 +776,8 @@ pub struct GithubReleaseItem { /// Return `true` when `try_get_env_var` returns Some with a length greater than `1`. pub fn has_env_var(target_key: String) -> bool { - if let Some(x) = try_get_env_var(target_key) { + if let Some(x) = try_get_env_var(target_key) + { return x.len() > 1; } false @@ -685,8 +786,10 @@ pub fn has_env_var(target_key: String) -> bool { /// Try and get a value from `std::env::vars()` /// Will return `None` when not found pub fn try_get_env_var(target_key: String) -> Option { - for (key, value) in std::env::vars() { - if key == target_key { + for (key, value) in std::env::vars() + { + if key == target_key + { return Some(value); } } diff --git a/src/helper/windows.rs b/src/helper/windows.rs index 0deb880..0929682 100644 --- a/src/helper/windows.rs +++ b/src/helper/windows.rs @@ -8,12 +8,16 @@ use winreg::RegKey; /// HKEY_CURRENT_USER\Software\Value\Steam /// Key: SourceModInstallPath pub fn find_sourcemod_path() -> Result { - match RegKey::predef(HKEY_CURRENT_USER).open_subkey(String::from("Software\\Valve\\Steam")) { - Ok(rkey) => { + match RegKey::predef(HKEY_CURRENT_USER).open_subkey(String::from("Software\\Valve\\Steam")) + { + Ok(rkey) => + { let x: std::io::Result = rkey.get_value("SourceModInstallPath"); - match x { + match x + { Ok(val) => Ok(format_directory_path(val)), - Err(e) => { + Err(e) => + { return Err(BeansError::RegistryKeyFailure { msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" .to_string(), @@ -23,7 +27,8 @@ pub fn find_sourcemod_path() -> Result { } } } - Err(e) => { + Err(e) => + { return Err(BeansError::RegistryKeyFailure { msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" .to_string(), diff --git a/src/lib.rs b/src/lib.rs index 41e0061..2df0fbc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,10 @@ #![feature(error_generic_member_access)] #![feature(panic_info_message)] - // todo // https://rust-lang.github.io/rust-clippy/master/index.html#/result_large_err // https://github.com/ktwrd/beans-rs/pull/30 #![allow(clippy::result_large_err)] - use include_flate::flate; mod ctx; @@ -64,25 +62,32 @@ pub fn data_dir() -> String { /// - Running on Linux AND the `DISPLAY` or `XDG_SESSION_DESKTOP` environment variables are set. pub fn has_gui_support() -> bool { unsafe { - if !PAUSE_ONCE_DONE { + if !PAUSE_ONCE_DONE + { return false; } } - match std::env::consts::OS { + match std::env::consts::OS + { "windows" | "macos" => true, - "linux" => { - if helper::has_env_var("DISPLAY".to_string()) { + "linux" => + { + if helper::has_env_var("DISPLAY".to_string()) + { return true; } - if let Some(x) = helper::try_get_env_var("XDG_SESSION_DESKTOP".to_string()) { - if x.len() >= 3usize { + if let Some(x) = helper::try_get_env_var("XDG_SESSION_DESKTOP".to_string()) + { + if x.len() >= 3usize + { return true; } } false } - _ => { + _ => + { log::warn!("Unsupported platform for GUI {}", std::env::consts::OS); false } diff --git a/src/logger.rs b/src/logger.rs index 2fe48a8..823d776 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -21,7 +21,8 @@ impl CustomLogger { *self.inner.lock().unwrap() = Some(CustomLoggerInner { start: Instant::now(), sink: Box::new(sink), - sentry: sentry_log::SentryLogger::new().filter(|md| match md.level() { + sentry: sentry_log::SentryLogger::new().filter(|md| match md.level() + { log::Level::Error => LogFilter::Exception, log::Level::Warn => LogFilter::Event, log::Level::Info | log::Level::Debug | log::Level::Trace => LogFilter::Breadcrumb, @@ -36,17 +37,20 @@ impl Log for CustomLogger { } fn log(&self, record: &Record) { - if !self.enabled(record.metadata()) { + if !self.enabled(record.metadata()) + { return; } - if let Some(ref mut inner) = *self.inner.lock().unwrap() { + if let Some(ref mut inner) = *self.inner.lock().unwrap() + { inner.log(record); } } fn flush(&self) { - if let Some(ref mut inner) = *self.inner.lock().unwrap() { + if let Some(ref mut inner) = *self.inner.lock().unwrap() + { inner.sentry.flush(); } } @@ -64,11 +68,13 @@ impl CustomLoggerInner { fn log(&mut self, record: &Record) { let mut do_print = true; unsafe { - if LOG_FILTER < record.level() { + if LOG_FILTER < record.level() + { do_print = false; } } - if do_print { + if do_print + { let now = self.start.elapsed(); let seconds = now.as_secs(); let hours = seconds / 3600; @@ -91,8 +97,10 @@ impl CustomLoggerInner { .replace("#CONTENT", &format!("{}", record.args())); unsafe { - if LOG_COLOR { - data = match record.level() { + if LOG_COLOR + { + data = match record.level() + { log::Level::Error => data.red(), log::Level::Warn => data.yellow(), log::Level::Info => data.normal(), diff --git a/src/main.rs b/src/main.rs index 12e09ef..38c6151 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,8 @@ fn init_flags() { flags::remove_flag(LaunchFlag::DEBUG_MODE); #[cfg(debug_assertions)] flags::add_flag(LaunchFlag::DEBUG_MODE); - if std::env::var("BEANS_DEBUG").is_ok_and(|x| x == "1") { + if std::env::var("BEANS_DEBUG").is_ok_and(|x| x == "1") + { flags::add_flag(LaunchFlag::DEBUG_MODE); } flags::add_flag(LaunchFlag::STANDALONE_APP); @@ -63,14 +64,16 @@ fn init_panic_handle() { std::panic::set_hook(Box::new(move |info| { debug!("[panic::set_hook] showing msgbox to notify user"); let mut x = String::new(); - if let Some(m) = info.message() { + if let Some(m) = info.message() + { x = format!("{}", m); } info!("[panic] Fatal error!\n{:#?}", x); custom_panic_handle(x); debug!("[panic::set_hook] calling sentry_panic::panic_handler"); sentry::integrations::panic::panic_handler(info); - if flags::has_flag(LaunchFlag::DEBUG_MODE) { + if flags::has_flag(LaunchFlag::DEBUG_MODE) + { eprintln!("{:#?}", info); } logic_done(); @@ -79,7 +82,8 @@ fn init_panic_handle() { fn custom_panic_handle(msg: String) { unsafe { - if !beans_rs::PAUSE_ONCE_DONE { + if !beans_rs::PAUSE_ONCE_DONE + { return; } } @@ -98,7 +102,8 @@ fn custom_panic_handle(msg: String) { /// will call `helper::get_input` when `PAUSE_ONCE_DONE` is `true`. fn logic_done() { unsafe { - if beans_rs::PAUSE_ONCE_DONE { + if beans_rs::PAUSE_ONCE_DONE + { let _ = helper::get_input("Press enter/return to exit"); } } @@ -172,7 +177,8 @@ impl Launcher { ]); let mut i = Self::new(&cmd.get_matches()); - if let Ok(Some(v)) = helper::beans_has_update().await { + if let Ok(Some(v)) = helper::beans_has_update().await + { info!("A new version of beans-rs is available!"); info!("{}", v.html_url); } @@ -193,11 +199,14 @@ impl Launcher { /// add `LaunchFlag::DEBUG_MODE` to `flags` when the `--debug` parameter flag is used. pub fn set_debug(&mut self) { - if self.root_matches.get_flag("no-debug") { + if self.root_matches.get_flag("no-debug") + { flags::remove_flag(LaunchFlag::DEBUG_MODE); beans_rs::logger::set_filter(DEFAULT_LOG_LEVEL_RELEASE); info!("Disabled Debug Mode"); - } else if self.root_matches.get_flag("debug") { + } + else if self.root_matches.get_flag("debug") + { flags::add_flag(LaunchFlag::DEBUG_MODE); beans_rs::logger::set_filter(LevelFilter::max()); trace!("Debug mode enabled"); @@ -213,7 +222,8 @@ impl Launcher { /// Set `self.to_location` when provided in the arguments. pub fn find_arg_sourcemods_location(matches: &ArgMatches) -> Option { let mut sml_dir_manual: Option = None; - if let Some(x) = matches.get_one::("location") { + if let Some(x) = matches.get_one::("location") + { sml_dir_manual = Some(parse_location(x.to_string())); info!("[Launcher::set_to_location] Found in arguments! {}", x); } @@ -222,31 +232,39 @@ impl Launcher { /// main handler for subcommand processing. pub async fn subcommand_processor(&mut self) { - match self.root_matches.clone().subcommand() { - Some(("install", i_matches)) => { + match self.root_matches.clone().subcommand() + { + Some(("install", i_matches)) => + { self.task_install(i_matches).await; } - Some(("verify", v_matches)) => { + Some(("verify", v_matches)) => + { self.task_verify(v_matches).await; } - Some(("update", u_matches)) => { + Some(("update", u_matches)) => + { self.task_update(u_matches).await; } - Some(("wizard", wz_matches)) => { + Some(("wizard", wz_matches)) => + { self.to_location = Launcher::find_arg_sourcemods_location(wz_matches); self.task_wizard().await; } - Some(("clean-tmp", _)) => { + Some(("clean-tmp", _)) => + { self.task_clean_tmp().await; } - _ => { + _ => + { self.task_wizard().await; } } } pub fn set_prompt_do_whatever(&mut self) { - if self.root_matches.get_flag("confirm") { + if self.root_matches.get_flag("confirm") + { unsafe { beans_rs::PROMPT_DO_WHATEVER = true; } @@ -256,7 +274,8 @@ impl Launcher { /// Try and get `SourceModDirectoryParam`. /// Returns SourceModDirectoryParam::default() when `to_location` is `None`. fn try_get_smdp(&mut self) -> SourceModDirectoryParam { - match &self.to_location { + match &self.to_location + { Some(v) => SourceModDirectoryParam::WithLocation(v.to_string()), None => SourceModDirectoryParam::default(), } @@ -265,9 +284,12 @@ impl Launcher { /// handler for the `wizard` subcommand. it's also the default subcommand. pub async fn task_wizard(&mut self) { let x = self.try_get_smdp(); - if let Err(e) = wizard::WizardContext::run(x).await { + if let Err(e) = wizard::WizardContext::run(x).await + { panic!("Failed to run WizardContext {:#?}", e); - } else { + } + else + { logic_done(); } } @@ -278,7 +300,8 @@ impl Launcher { /// and are reported via sentry. pub async fn task_install(&mut self, matches: &ArgMatches) { self.to_location = Launcher::find_arg_sourcemods_location(matches); - if matches.get_flag("confirm") { + if matches.get_flag("confirm") + { unsafe { beans_rs::PROMPT_DO_WHATEVER = true; } @@ -291,13 +314,15 @@ impl Launcher { // // `else if let` is used for checking the `--from` parameter, // so a return isn't required. - if let Some(x) = matches.get_one::("target-version") { + if let Some(x) = matches.get_one::("target-version") + { self.task_install_version_specific(ctx, x.clone()).await; } // manually install from specific `.tar.zstd` file when the // --from parameter is provided. otherwise we install/reinstall // the latest version to whatever sourcemods directory is used - else if let Some(x) = matches.get_one::("from") { + else if let Some(x) = matches.get_one::("from") + { info!( "Manually installing from {} to {}", x.clone(), @@ -309,12 +334,18 @@ impl Launcher { error!("Failed to run InstallWorkflow::install_from"); sentry::capture_error(&e); panic!("{:#?}", e); - } else { + } + else + { logic_done(); } - } else if let Err(e) = InstallWorkflow::wizard(&mut ctx).await { + } + else if let Err(e) = InstallWorkflow::wizard(&mut ctx).await + { panic!("Failed to run InstallWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } @@ -324,9 +355,11 @@ impl Launcher { /// NOTE this function uses `expect` on `InstallWorkflow::install_version`. panics are handled /// and are reported via sentry. pub async fn task_install_version_specific(&mut self, ctx: RunnerContext, version_str: String) { - let version = match usize::from_str(&version_str) { + let version = match usize::from_str(&version_str) + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); error!( "Failed to parse version argument \"{version_str}\": {:#?}", @@ -337,11 +370,14 @@ impl Launcher { } }; let mut wf = InstallWorkflow { context: ctx }; - if let Err(e) = wf.install_version(version).await { + if let Err(e) = wf.install_version(version).await + { error!("Failed to run InstallWorkflow::install_version"); sentry::capture_error(&e); panic!("{:#?}", e); - } else { + } + else + { logic_done(); } } @@ -354,9 +390,12 @@ impl Launcher { self.to_location = Launcher::find_arg_sourcemods_location(matches); let mut ctx = self.try_create_context().await; - if let Err(e) = VerifyWorkflow::wizard(&mut ctx).await { + if let Err(e) = VerifyWorkflow::wizard(&mut ctx).await + { panic!("Failed to run VerifyWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } @@ -369,9 +408,12 @@ impl Launcher { self.to_location = Launcher::find_arg_sourcemods_location(matches); let mut ctx = self.try_create_context().await; - if let Err(e) = UpdateWorkflow::wizard(&mut ctx).await { + if let Err(e) = UpdateWorkflow::wizard(&mut ctx).await + { panic!("Failed to run UpdateWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } @@ -382,9 +424,12 @@ impl Launcher { /// and are reported via sentry. pub async fn task_clean_tmp(&mut self) { let mut ctx = self.try_create_context().await; - if let Err(e) = CleanWorkflow::wizard(&mut ctx) { + if let Err(e) = CleanWorkflow::wizard(&mut ctx) + { panic!("Failed to run CleanWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } @@ -395,9 +440,11 @@ impl Launcher { /// on failure, `panic!` is called. but that's okay because a dialog is shown (in /// `init_panic_handle`) and the error is reported via sentry. async fn try_create_context(&mut self) -> RunnerContext { - match RunnerContext::create_auto(self.try_get_smdp()).await { + match RunnerContext::create_auto(self.try_get_smdp()).await + { Ok(v) => v, - Err(e) => { + Err(e) => + { error!("[try_create_context] {:}", e); trace!("======== Full Error ========"); trace!("{:#?}", &e); diff --git a/src/version.rs b/src/version.rs index ebd8769..e904edf 100644 --- a/src/version.rs +++ b/src/version.rs @@ -12,11 +12,14 @@ use std::io::Write; /// will parse the value of `version` as usize. pub fn get_current_version(sourcemods_location: Option) -> Option { let install_state = helper::install_state(sourcemods_location.clone()); - if install_state != InstallType::Adastral { + if install_state != InstallType::Adastral + { return None; } - match get_mod_location(sourcemods_location) { - Some(smp_x) => { + match get_mod_location(sourcemods_location) + { + Some(smp_x) => + { // TODO generate BeansError instead of using panic let location = format!("{}.adastral", smp_x); let content = @@ -39,11 +42,14 @@ fn get_version_location(sourcemods_location: Option) -> Option { /// get the full location of the sourcemod mod directory. fn get_mod_location(sourcemods_location: Option) -> Option { - let smp_x = match sourcemods_location { + let smp_x = match sourcemods_location + { Some(v) => v, - None => match find_sourcemod_path() { + None => match find_sourcemod_path() + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); debug!( "[version::get_mod_location] {} {:#?}", @@ -61,32 +67,40 @@ fn get_mod_location(sourcemods_location: Option) -> Option { pub fn update_version_file(sourcemods_location: Option) -> Result<(), BeansError> { let install_state = helper::install_state(sourcemods_location.clone()); - match install_state { - InstallType::NotInstalled => { + match install_state + { + InstallType::NotInstalled => + { debug!( "[version::update_version_file] install_state is {:#?}, ignoring.", install_state ); } - InstallType::Adastral => { + InstallType::Adastral => + { debug!( "[version::update_version_file] install_state is {:#?}, ignoring.", install_state ); } - InstallType::OtherSourceManual => { + InstallType::OtherSourceManual => + { debug!( "[version::update_version_file] install_state is {:#?}, ignoring.", install_state ); } - InstallType::OtherSource => { - let smp_x = match sourcemods_location { + InstallType::OtherSource => + { + let smp_x = match sourcemods_location + { Some(v) => v, - None => match find_sourcemod_path() { + None => match find_sourcemod_path() + { Ok(v) => v, - Err(e) => { + Err(e) => + { error!( "[version::update_version_file] Could not find sourcemods folder! {:}", e @@ -101,9 +115,11 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be let data_dir = helper::join_path(smp_x, crate::data_dir()); let old_version_file_location = format!("{}.revision", &data_dir); - let old_version_file_content = match read_to_string(&old_version_file_location) { + let old_version_file_content = match read_to_string(&old_version_file_location) + { Ok(v) => v, - Err(e) => { + Err(e) => + { debug!( "[update_version_file] failed to read {}. {:#?}", old_version_file_location, e @@ -115,9 +131,11 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be }); } }; - let old_version_idx = match old_version_file_content.parse::() { + let old_version_idx = match old_version_file_content.parse::() + { Ok(v) => v, - Err(e) => { + Err(e) => + { debug!( "[update_version_file] Failed to parse content {} caused error {:}", old_version_file_content, e @@ -136,9 +154,11 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be }; let new_version_file_location = format!("{}.adastral", &data_dir); - let new_version_file_content = match serde_json::to_string(&new_file_content) { + let new_version_file_content = match serde_json::to_string(&new_file_content) + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); return Err(BeansError::VersionFileSerialize { error: e, @@ -156,7 +176,8 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be location: new_version_file_location, }); } - if let Err(e) = std::fs::remove_file(old_version_file_location.clone()) { + if let Err(e) = std::fs::remove_file(old_version_file_location.clone()) + { sentry::capture_error(&e); return Err(BeansError::VersionFileMigrationDeleteFailure { error: e, @@ -171,9 +192,11 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be /// fetch the version list from `{crate::SOURCE_URL}versions.json` pub async fn get_version_list() -> Result { let av = crate::appvar::parse(); - let response = match reqwest::get(&av.remote_info.versions_url).await { + let response = match reqwest::get(&av.remote_info.versions_url).await + { Ok(v) => v, - Err(e) => { + Err(e) => + { error!( "[version::get_version_list] Failed to get available versions! {:}", e @@ -203,15 +226,21 @@ pub struct AdastralVersionFile { impl AdastralVersionFile { pub fn write(&self, sourcemods_location: Option) -> Result<(), BeansError> { - match get_version_location(sourcemods_location) { - Some(vl) => { - let f = match helper::file_exists(vl.clone()) { + match get_version_location(sourcemods_location) + { + Some(vl) => + { + let f = match helper::file_exists(vl.clone()) + { true => std::fs::File::create(vl.clone()), false => std::fs::File::create_new(vl.clone()), }; - match f { - Ok(mut file) => match serde_json::to_string(self) { - Ok(ser) => match file.write_all(ser.as_bytes()) { + match f + { + Ok(mut file) => match serde_json::to_string(self) + { + Ok(ser) => match file.write_all(ser.as_bytes()) + { Ok(_) => Ok(()), Err(e) => Err(BeansError::FileWriteFailure { location: vl, diff --git a/src/wizard.rs b/src/wizard.rs index a5c300a..beb57f0 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -16,24 +16,30 @@ impl WizardContext { /// run the wizard! pub async fn run(sml_via: SourceModDirectoryParam) -> Result<(), BeansError> { depends::try_write_deps(); - if let Err(e) = depends::try_install_vcredist().await { + if let Err(e) = depends::try_install_vcredist().await + { sentry::capture_error(&e); println!("Failed to install vcredist! {:}", e); debug!("[WizardContext::run] {:#?}", e); } - let sourcemod_path = parse_location(match sml_via { - SourceModDirectoryParam::AutoDetect => { + let sourcemod_path = parse_location(match sml_via + { + SourceModDirectoryParam::AutoDetect => + { debug!("[WizardContext::run] Auto-detecting sourcemods directory"); get_path() } - SourceModDirectoryParam::WithLocation(loc) => { + SourceModDirectoryParam::WithLocation(loc) => + { debug!("[WizardContext::run] Using specified location {}", loc); loc } }); - let version_list = match crate::version::get_version_list().await { + let version_list = match crate::version::get_version_list().await + { Ok(v) => v, - Err(e) => { + Err(e) => + { trace!("[WizardContext::run] Failed to run version::get_version_list()"); trace!("{:#?}", e); sentry::capture_error(&e); @@ -41,7 +47,8 @@ impl WizardContext { } }; - if helper::install_state(Some(sourcemod_path.clone())) == InstallType::OtherSource { + if helper::install_state(Some(sourcemod_path.clone())) == InstallType::OtherSource + { crate::version::update_version_file(Some(sourcemod_path.clone()))?; } @@ -64,11 +71,14 @@ impl WizardContext { /// When an invalid option is selected, this will be re-called. #[async_recursion] pub async fn menu<'a>(&'a mut self) { - if self.menu_trigger_count == 0 { + if self.menu_trigger_count == 0 + { let av = crate::appvar::AppVarData::get(); - if let Some(cv) = self.context.current_version { + if let Some(cv) = self.context.current_version + { let (rv, _) = self.context.latest_remote_version(); - if cv < rv { + if cv < rv + { println!( "======== A new update for {} is available! (v{rv}) ========", av.mod_info.name_stylized @@ -84,21 +94,25 @@ impl WizardContext { println!(); println!("q - Quit"); let user_input = helper::get_input("-- Enter option below --"); - match user_input.to_lowercase().as_str() { + match user_input.to_lowercase().as_str() + { "1" | "install" => WizardContext::menu_error_catch(self.task_install().await), "2" | "update" => WizardContext::menu_error_catch(self.task_update().await), "3" | "verify" => WizardContext::menu_error_catch(self.task_verify().await), "c" | "clean" => Self::menu_error_catch(CleanWorkflow::wizard(&mut self.context)), - "d" | "debug" => { + "d" | "debug" => + { flags::add_flag(LaunchFlag::DEBUG_MODE); info!("Debug mode enabled!"); self.menu().await; } - "panic" => { + "panic" => + { panic!() } "q" => std::process::exit(0), - _ => { + _ => + { println!("Unknown option \"{}\"", user_input); self.menu_trigger_count += 1; self.menu().await; @@ -106,7 +120,8 @@ impl WizardContext { }; } fn menu_error_catch(v: Result<(), BeansError>) { - if let Err(e) = v { + if let Err(e) = v + { let b = Backtrace::capture(); sentry::capture_error(&e); panic!("backtrace: {:#?}\n\nerror: {:#?}", b, e); @@ -138,13 +153,18 @@ fn get_path() -> String { fn prompt_sourcemod_location() -> String { let res = helper::get_input("Please provide your sourcemods folder, then press enter."); - if !helper::file_exists(res.clone()) { + if !helper::file_exists(res.clone()) + { eprintln!("The location you provided doesn't exist. Try again."); prompt_sourcemod_location() - } else if !helper::is_directory(res.clone()) { + } + else if !helper::is_directory(res.clone()) + { eprintln!("The location you provided isn't a folder. Try again."); prompt_sourcemod_location() - } else { + } + else + { res } } diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index 6d2aec7..b7ffeb6 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -11,12 +11,14 @@ impl CleanWorkflow { let target_directory = helper::get_tmp_dir(); info!("[CleanWorkflow] Cleaning up {}", target_directory); - if !helper::file_exists(target_directory.clone()) { + if !helper::file_exists(target_directory.clone()) + { warn!("[CleanWorkflow] Temporary directory not found, nothing to clean.") } // delete directory and it's contents (and error handling) - if let Err(e) = std::fs::remove_dir_all(&target_directory) { + if let Err(e) = std::fs::remove_dir_all(&target_directory) + { return Err(BeansError::CleanTempFailure { location: target_directory, error: e, @@ -24,7 +26,8 @@ impl CleanWorkflow { } // re-creating the temporary directory (and error handling) - if let Err(e) = std::fs::create_dir(&target_directory) { + if let Err(e) = std::fs::create_dir(&target_directory) + { return Err(BeansError::DirectoryCreateFailure { location: target_directory, error: e, diff --git a/src/workflows/install.rs b/src/workflows/install.rs index 93364f4..b4e8397 100644 --- a/src/workflows/install.rs +++ b/src/workflows/install.rs @@ -11,7 +11,8 @@ pub struct InstallWorkflow { impl InstallWorkflow { pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let (latest_remote_id, latest_remote) = ctx.latest_remote_version(); - if let Some(_cv) = ctx.current_version { + if let Some(_cv) = ctx.current_version + { println!("[InstallWorkflow::wizard] re-installing! game files will not be touched until extraction"); } @@ -25,7 +26,8 @@ impl InstallWorkflow { /// Returns: `true` when the installation should continue, `false` when we should silently abort. pub fn prompt_confirm(current_version: Option) -> bool { unsafe { - if crate::PROMPT_DO_WHATEVER { + if crate::PROMPT_DO_WHATEVER + { info!( "[InstallWorkflow::prompt_confirm] skipping since PROMPT_DO_WHATEVER is true" ); @@ -33,7 +35,8 @@ impl InstallWorkflow { } } let av = AppVarData::get(); - if let Some(v) = current_version { + if let Some(v) = current_version + { println!( "[InstallWorkflow::prompt_confirm] Seems like {} is already installed (v{})", v, av.mod_info.name_stylized @@ -43,24 +46,30 @@ impl InstallWorkflow { println!("Yes/Y (default)"); println!("No/N"); let user_input = helper::get_input("-- Enter option below --"); - match user_input.to_lowercase().as_str() { + match user_input.to_lowercase().as_str() + { "y" | "yes" | "" => true, "n" | "no" => false, - _ => { + _ => + { println!("Unknown option \"{}\"", user_input.to_lowercase()); Self::prompt_confirm(current_version) } } - } else { + } + else + { true } } /// Install the specified version by its ID to the output directory. pub async fn install_version(&mut self, version_id: usize) -> Result<(), BeansError> { - let target_version = match self.context.remote_version_list.versions.get(&version_id) { + let target_version = match self.context.remote_version_list.versions.get(&version_id) + { Some(v) => v, - None => { + None => + { error!("Could not find remote version {version_id}"); return Err(BeansError::RemoteVersionNotFound { version: Some(version_id), @@ -81,7 +90,8 @@ impl InstallWorkflow { version_id: usize, version: RemoteVersion, ) -> Result<(), BeansError> { - if !Self::prompt_confirm(ctx.current_version) { + if !Self::prompt_confirm(ctx.current_version) + { info!("[InstallWorkflow] Operation aborted by user"); return Ok(()); } @@ -97,7 +107,8 @@ impl InstallWorkflow { Some(version_id), ) .await?; - if helper::file_exists(presz_loc.clone()) { + if helper::file_exists(presz_loc.clone()) + { std::fs::remove_file(presz_loc)?; } Ok(()) @@ -113,7 +124,8 @@ impl InstallWorkflow { out_dir: String, version_id: Option, ) -> Result<(), BeansError> { - if !helper::file_exists(package_loc.clone()) { + if !helper::file_exists(package_loc.clone()) + { error!("[InstallWorkflow::Wizard] Failed to find package! (location: {package_loc})"); return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::FileNotFound { @@ -124,19 +136,23 @@ impl InstallWorkflow { println!("[InstallWorkflow::Wizard] Extracting to {out_dir}"); RunnerContext::extract_package(package_loc, out_dir.clone())?; - if let Some(lri) = version_id { + if let Some(lri) = version_id + { let x = AdastralVersionFile { version: lri.to_string(), } .write(Some(out_dir.clone())); - if let Err(e) = x { + if let Err(e) = x + { println!( "[InstallWorkflow::install_from] Failed to set version to {} in .adastral", lri ); debug!("{:#?}", e); } - } else { + } + else + { warn!("Not writing .adastral since the version wasn't provided"); } let av = crate::appvar::parse(); diff --git a/src/workflows/update.rs b/src/workflows/update.rs index f1331a1..0ce2452 100644 --- a/src/workflows/update.rs +++ b/src/workflows/update.rs @@ -8,9 +8,11 @@ impl UpdateWorkflow { pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let av = crate::appvar::parse(); - let current_version_id = match ctx.current_version { + let current_version_id = match ctx.current_version + { Some(v) => v, - None => { + None => + { println!( "[UpdateWorkflow::wizard] Unable to update game since it is not installed!" ); @@ -21,9 +23,11 @@ impl UpdateWorkflow { let remote_version = ctx.current_remote_version()?; ctx.prepare_symlink()?; - let patch = match ctx.has_patch_available() { + let patch = match ctx.has_patch_available() + { Some(v) => v, - None => { + None => + { println!("[UpdateWorkflow::wizard] No patch is available for the version that is currently installed."); return Ok(()); } @@ -31,26 +35,30 @@ impl UpdateWorkflow { ctx.gameinfo_perms()?; - if !helper::has_free_space(ctx.sourcemod_path.clone(), patch.clone().tempreq)? { + if !helper::has_free_space(ctx.sourcemod_path.clone(), patch.clone().tempreq)? + { println!( "[UpdateWorkflow::wizard] Not enough free space! Requires {}", helper::format_size(patch.tempreq) ); } debug!("remote_version: {:#?}", remote_version); - if remote_version.signature_url.is_none() { + if remote_version.signature_url.is_none() + { eprintln!( "[UpdateWorkflow::wizard] Couldn't get signature URL for version {}", current_version_id ); } - if remote_version.heal_url.is_none() { + if remote_version.heal_url.is_none() + { eprintln!( "[UpdateWorkflow::wizard] Couldn't get heal URL for version {}", current_version_id ); } - if remote_version.signature_url.is_none() || remote_version.heal_url.is_none() { + if remote_version.signature_url.is_none() || remote_version.heal_url.is_none() + { eprintln!("[UpdateWorkflow::wizard] Unable to update, missing remote files!"); return Ok(()); } @@ -74,7 +82,8 @@ impl UpdateWorkflow { &av.remote_info.base_url, remote_version.heal_url.unwrap() ), - ) { + ) + { sentry::capture_error(&e); return Err(e); } diff --git a/src/workflows/verify.rs b/src/workflows/verify.rs index b986881..f0adab0 100644 --- a/src/workflows/verify.rs +++ b/src/workflows/verify.rs @@ -8,9 +8,11 @@ impl VerifyWorkflow { pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let av = crate::appvar::parse(); - let current_version_id = match ctx.current_version { + let current_version_id = match ctx.current_version + { Some(v) => v, - None => { + None => + { println!( "[VerifyWorkflow::wizard] Unable to update game since it is not installed!" ); @@ -19,19 +21,22 @@ impl VerifyWorkflow { }; let remote: RemoteVersion = ctx.current_remote_version()?; - if remote.signature_url.is_none() { + if remote.signature_url.is_none() + { eprintln!( "[VerifyWorkflow::wizard] Couldn't get signature URL for version {}", current_version_id ); } - if remote.heal_url.is_none() { + if remote.heal_url.is_none() + { eprintln!( "[VerifyWorkflow::wizard] Couldn't get heal URL for version {}", current_version_id ); } - if remote.signature_url.is_none() || remote.heal_url.is_none() { + if remote.signature_url.is_none() || remote.heal_url.is_none() + { eprintln!("[VerifyWorkflow::wizard] Unable to update, missing remote files!"); return Ok(()); } From 7aa71c1fdcfa8529430c0d459c9745c9ff6e1743 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 30 Jul 2024 15:54:30 +0800 Subject: [PATCH 41/65] Update rustfmt.toml --- rustfmt.toml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/rustfmt.toml b/rustfmt.toml index 74854ec..c4b6629 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,29 @@ -control_brace_style="AlwaysNextLine" \ No newline at end of file +brace_style="AlwaysNextLine" +combine_control_expr=false +condense_wildcard_suffixes=true +control_brace_style="AlwaysNextLine" +empty_item_single_line=false +fn_params_layout="Vertical" +fn_single_line=false +force_explicit_abi=true +format_code_in_doc_comments=true +format_generated_files=false +hard_tabs=false +hex_literal_case="Upper" +imports_indent="Visual" +imports_layout="Vertical" +indent_style="Block" +inline_attribute_width=0 +imports_granularity="Crate" +normalize_comments=true +overflow_delimited_expr=true +group_imports="StdExternalCrate" +single_line_let_else_max_width=0 +space_after_colon=true +space_before_colon=false +struct_lit_single_line=false +tab_spaces=4 +trailing_comma="Never" +trailing_semicolon=true +where_single_line=false +wrap_comments=true From 2cc88be8f4a8cbc00040b2d04c66397285f8123a Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 30 Jul 2024 15:55:11 +0800 Subject: [PATCH 42/65] Apply new rules from 7aa71c1f --- build.rs | 32 ++++-- src/appvar.rs | 69 +++++++----- src/butler.rs | 42 +++++--- src/ctx.rs | 122 +++++++++++++-------- src/depends.rs | 51 ++++++--- src/error.rs | 224 ++++++++++++++++++++++++--------------- src/flags.rs | 12 ++- src/gui/dialog.rs | 69 ++++++++---- src/gui/mod.rs | 47 +++++--- src/helper/linux.rs | 22 ++-- src/helper/mod.rs | 217 +++++++++++++++++++++++-------------- src/helper/windows.rs | 17 +-- src/lib.rs | 18 ++-- src/logger.rs | 80 +++++++++----- src/main.rs | 189 +++++++++++++++++++++------------ src/version.rs | 109 +++++++++++-------- src/wizard.rs | 63 +++++++---- src/workflows/clean.rs | 23 ++-- src/workflows/install.rs | 73 ++++++++----- src/workflows/update.rs | 24 +++-- src/workflows/verify.rs | 20 ++-- 21 files changed, 989 insertions(+), 534 deletions(-) diff --git a/build.rs b/build.rs index 532b3b6..b9f4873 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; #[allow(dead_code, unused_macros, unused_imports)] -use std::{env, io}; +use std::{env, + io}; + #[cfg(target_os = "windows")] use winres::WindowsResource; #[allow(unused_macros)] @@ -12,19 +14,21 @@ macro_rules! print { pub const OVERRIDE_ICON_LOCATION: Option<&'static str> = option_env!("ICON_LOCATION"); pub const RUST_FLAGS: Option<&'static str> = option_env!("RUSTFLAGS"); -fn main() { +fn main() +{ windows_icon().expect("Failed to embed icon"); fltk().expect("Failed to build fltk files"); } /// generate files for fltk ui stuff -fn fltk() -> Result<(), BuildError> { +fn fltk() -> Result<(), BuildError> +{ println!("cargo:rerun-if-changed=src/gui/shared_ui.fl"); let g = fl2rust::Generator::default(); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); if let Err(e) = g.in_out( "src/gui/shared_ui.fl", - out_path.join("shared_ui.rs").to_str().unwrap(), + out_path.join("shared_ui.rs").to_str().unwrap() ) { return Err(BuildError::FLTK(format!( @@ -38,13 +42,15 @@ fn fltk() -> Result<(), BuildError> { /// check if a location exists #[allow(dead_code)] -fn path_exists(path: String) -> bool { +fn path_exists(path: String) -> bool +{ std::path::Path::new(path.as_str()).exists() } /// set the icon to `icon.ico` when building for windows #[cfg(target_os = "windows")] -fn windows_icon() -> Result<(), BuildError> { +fn windows_icon() -> Result<(), BuildError> +{ let icon_location = OVERRIDE_ICON_LOCATION.unwrap_or("icon.ico"); if env::var_os("CARGO_CFG_WINDOWS").is_some() { @@ -67,17 +73,21 @@ fn windows_icon() -> Result<(), BuildError> { } /// ignored since icon handling is done by fltk on non-windows #[cfg(not(target_os = "windows"))] -fn windows_icon() -> Result<(), BuildError> { +fn windows_icon() -> Result<(), BuildError> +{ Ok(()) } #[derive(Debug)] -pub enum BuildError { +pub enum BuildError +{ IO(io::Error), - FLTK(String), + FLTK(String) } -impl From for BuildError { - fn from(e: io::Error) -> Self { +impl From for BuildError +{ + fn from(e: io::Error) -> Self + { BuildError::IO(e) } } diff --git a/src/appvar.rs b/src/appvar.rs index cde3ded..eb23607 100644 --- a/src/appvar.rs +++ b/src/appvar.rs @@ -1,8 +1,12 @@ -use crate::BeansError; -use lazy_static::lazy_static; -use log::{debug, error, trace}; use std::sync::RwLock; +use lazy_static::lazy_static; +use log::{debug, + error, + trace}; + +use crate::BeansError; + /// Default `appvar.json` to use. pub const JSON_DATA_DEFAULT: &str = include_str!("appvar.json"); lazy_static! { @@ -11,26 +15,31 @@ lazy_static! { } /// Going to be deprecated, use `get_appvar()` instead. -pub fn parse() -> AppVarData { +pub fn parse() -> AppVarData +{ trace!("======== IMPORTANT NOTICE ========\ncrate::appvar::parse() is going to be deprecated, use crate::appvar::get_appvar() instead!"); AppVarData::get() } /// Configuration for the compiled application. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AppVarData { +pub struct AppVarData +{ #[serde(rename = "mod")] pub mod_info: AppVarMod, #[serde(rename = "remote")] - pub remote_info: AppVarRemote, + pub remote_info: AppVarRemote } -impl AppVarData { - /// Parse `JSON_DATA` to AppVarData. Should only be called by `reset_appvar()`. +impl AppVarData +{ + /// Parse `JSON_DATA` to AppVarData. Should only be called by + /// `reset_appvar()`. /// - /// NOTE panics when `serde_json::from_str()` is Err, or when `JSON_DATA.read()` is Err. - /// REMARKS does not set `AVD_INSTANCE` to generated data, since this is only done by - /// `AppVarData::reset()`. - pub fn parse() -> Self { + /// NOTE panics when `serde_json::from_str()` is Err, or when + /// `JSON_DATA.read()` is Err. REMARKS does not set `AVD_INSTANCE` to + /// generated data, since this is only done by `AppVarData::reset()`. + pub fn parse() -> Self + { debug!("[AppVarData::parse] trying to get JSON_DATA"); let x = JSON_DATA.read(); if let Ok(data) = x @@ -46,7 +55,11 @@ impl AppVarData { } /// Substitute values in the `source` string for what is defined in here. - pub fn sub(&self, source: String) -> String { + pub fn sub( + &self, + source: String + ) -> String + { source .clone() .replace("$MOD_NAME_STYLIZED", &self.mod_info.name_stylized) @@ -60,7 +73,8 @@ impl AppVarData { /// Otherwise, when it's none, we return `AppVarData::reset()` /// /// NOTE this function panics when Err on `AVD_INSTANCE.read()`. - pub fn get() -> Self { + pub fn get() -> Self + { let avd_read = AVD_INSTANCE.read(); if let Ok(v) = avd_read { @@ -82,7 +96,8 @@ impl AppVarData { /// Set the content of `AVD_INSTANCE` to the result of `AppVarData::parse()` /// /// NOTE this function panics when Err on `AVD_INSTANCE.write()` - pub fn reset() -> Self { + pub fn reset() -> Self + { let instance = AppVarData::parse(); match AVD_INSTANCE.write() @@ -104,12 +119,14 @@ impl AppVarData { instance } - /// Serialize `data` into JSON, then set the content of `JSON_DATA` to the serialize content. - /// Once that is done, `AppVarData::reset()` will be called. + /// Serialize `data` into JSON, then set the content of `JSON_DATA` to the + /// serialize content. Once that is done, `AppVarData::reset()` will be + /// called. /// - /// If `serde_json::to_string` fails, an error is printed in console and `sentry::capture_error` - /// is called. - pub fn set_json_data(data: AppVarData) -> Result<(), BeansError> { + /// If `serde_json::to_string` fails, an error is printed in console and + /// `sentry::capture_error` is called. + pub fn set_json_data(data: AppVarData) -> Result<(), BeansError> + { debug!("[set_json_data] {:#?}", data); match serde_json::to_string(&data) { @@ -134,14 +151,15 @@ impl AppVarData { Err(BeansError::AppVarDataSerializeFailure { error: e, - data: data.clone(), + data: data.clone() }) } } } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AppVarMod { +pub struct AppVarMod +{ /// name of the mod to use. /// e.g; `open_fortress` #[serde(rename = "sm_name")] @@ -151,14 +169,15 @@ pub struct AppVarMod { pub short_name: String, /// stylized name of the sourcemod. /// e.g; `Open Fortress` - pub name_stylized: String, + pub name_stylized: String } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AppVarRemote { +pub struct AppVarRemote +{ /// base URL for the versioning. /// e.g; `https://beans.adastral.net/` pub base_url: String, /// url where the version details are stored. /// e.g; `https://beans.adastral.net/versions.json` - pub versions_url: String, + pub versions_url: String } diff --git a/src/butler.rs b/src/butler.rs index 17f01cc..219381e 100644 --- a/src/butler.rs +++ b/src/butler.rs @@ -1,19 +1,27 @@ -use crate::{depends, helper, BeansError, DownloadFailureReason}; -use log::{debug, error, info}; -use std::backtrace::Backtrace; -use std::process::ExitStatus; +use std::{backtrace::Backtrace, + process::ExitStatus}; + +use log::{debug, + error, + info}; + +use crate::{depends, + helper, + BeansError, + DownloadFailureReason}; pub fn verify( signature_url: String, gamedir: String, - remote: String, -) -> Result { + remote: String +) -> Result +{ let mut cmd = std::process::Command::new(depends::get_butler_location()); cmd.args([ "verify", &signature_url, &gamedir, - format!("--heal=archive,{}", remote).as_str(), + format!("--heal=archive,{}", remote).as_str() ]); debug!("[butler::verify] {:#?}", cmd); match cmd.spawn() @@ -23,7 +31,7 @@ pub fn verify( gamedir, remote, error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }), Ok(mut v) => { @@ -45,8 +53,9 @@ pub async fn patch_dl( dl_url: String, staging_dir: String, patch_filename: String, - gamedir: String, -) -> Result { + gamedir: String +) -> Result +{ if helper::file_exists(staging_dir.clone()) { std::fs::remove_dir_all(&staging_dir)?; @@ -58,7 +67,9 @@ pub async fn patch_dl( if !helper::file_exists(tmp_file.clone()) { return Err(BeansError::DownloadFailure { - reason: DownloadFailureReason::FileNotFound { location: tmp_file }, + reason: DownloadFailureReason::FileNotFound { + location: tmp_file + } }); } @@ -68,14 +79,15 @@ pub async fn patch_dl( pub fn patch( patchfile_location: String, staging_dir: String, - gamedir: String, -) -> Result { + gamedir: String +) -> Result +{ let mut cmd = std::process::Command::new(depends::get_butler_location()); cmd.args([ "apply", &format!("--staging-dir={}", &staging_dir), &patchfile_location, - &gamedir, + &gamedir ]); debug!("[butler::patch] {:#?}", &cmd); match cmd.spawn() @@ -86,7 +98,7 @@ pub fn patch( patchfile_location, gamedir, error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }; error!("[butler::patch] {:#?}", xe); sentry::capture_error(&xe); diff --git a/src/ctx.rs b/src/ctx.rs index 9ce62e1..7a644fe 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -1,21 +1,36 @@ -use crate::helper::{find_sourcemod_path, parse_location, InstallType}; -use crate::version::{RemotePatch, RemoteVersion, RemoteVersionResponse}; -use crate::{depends, helper, version, BeansError}; -use log::{debug, error, info, trace}; use std::backtrace::Backtrace; #[cfg(target_os = "linux")] use std::os::unix::fs::PermissionsExt; +use log::{debug, + error, + info, + trace}; + +use crate::{depends, + helper, + helper::{find_sourcemod_path, + parse_location, + InstallType}, + version, + version::{RemotePatch, + RemoteVersion, + RemoteVersionResponse}, + BeansError}; + #[derive(Debug, Clone)] -pub struct RunnerContext { +pub struct RunnerContext +{ pub sourcemod_path: String, pub remote_version_list: RemoteVersionResponse, pub current_version: Option, - pub appvar: crate::appvar::AppVarData, + pub appvar: crate::appvar::AppVarData } -impl RunnerContext { - pub async fn create_auto(sml_via: SourceModDirectoryParam) -> Result { +impl RunnerContext +{ + pub async fn create_auto(sml_via: SourceModDirectoryParam) -> Result + { depends::try_write_deps(); if let Err(e) = depends::try_install_vcredist().await { @@ -61,33 +76,39 @@ impl RunnerContext { sourcemod_path: parse_location(sourcemod_path.clone()), remote_version_list: version_list, current_version: crate::version::get_current_version(Some(sourcemod_path.clone())), - appvar: crate::appvar::parse(), + appvar: crate::appvar::parse() }) } /// Sets `remote_version_list` from `version::get_version_list()` - pub async fn set_remote_version_list(&mut self) -> Result<(), BeansError> { + pub async fn set_remote_version_list(&mut self) -> Result<(), BeansError> + { self.remote_version_list = version::get_version_list().await?; Ok(()) } /// Get the location of the sourcemod mod /// {sourcemod_dir}{crate::DATA_DIR} - /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/sourcemods/open_fortress/ - /// C:\Games\Steam\steamapps\sourcemods\open_fortress\ - pub fn get_mod_location(&mut self) -> String { + /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/ + /// steamapps/sourcemods/open_fortress/ C:\Games\Steam\steamapps\ + /// sourcemods\open_fortress\ + pub fn get_mod_location(&mut self) -> String + { helper::join_path(self.sourcemod_path.clone(), crate::data_dir()) } /// Get staging location for butler. /// {sourcemod_dir}{crate::STAGING_DIR} - /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/sourcemods/butler-staging - /// C:\Games\Steam\steamapps\sourcemods\butler-staging - pub fn get_staging_location(&mut self) -> String { + /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/ + /// steamapps/sourcemods/butler-staging C:\Games\Steam\steamapps\ + /// sourcemods\butler-staging + pub fn get_staging_location(&mut self) -> String + { helper::join_path(self.sourcemod_path.clone(), crate::STAGING_DIR.to_string()) } /// Get the latest item in `remote_version_list` - pub fn latest_remote_version(&mut self) -> (usize, RemoteVersion) { + pub fn latest_remote_version(&mut self) -> (usize, RemoteVersion) + { let mut highest = usize::MIN; for (key, _) in self.remote_version_list.clone().versions.into_iter() { @@ -101,7 +122,8 @@ impl RunnerContext { } /// Get the RemoteVersion that matches `self.current_version` - pub fn current_remote_version(&mut self) -> Result { + pub fn current_remote_version(&mut self) -> Result + { match self.current_version { Some(cv) => @@ -114,18 +136,20 @@ impl RunnerContext { } } Err(BeansError::RemoteVersionNotFound { - version: self.current_version, + version: self.current_version }) } None => Err(BeansError::RemoteVersionNotFound { - version: self.current_version, - }), + version: self.current_version + }) } } - /// When self.current_version is some, iterate through patches and fetch the patch that is available - /// to bring the current version in-line with the latest version. - pub fn has_patch_available(&mut self) -> Option { + /// When self.current_version is some, iterate through patches and fetch the + /// patch that is available to bring the current version in-line with + /// the latest version. + pub fn has_patch_available(&mut self) -> Option + { let current_version = self.current_version; let (remote_version, _) = self.latest_remote_version(); match current_version @@ -145,12 +169,14 @@ impl RunnerContext { } None } - _ => None, + _ => None } } - /// Read the contents of `gameinfo.txt` in directory from `self.get_mod_location()` - pub fn read_gameinfo_file(&mut self) -> Result>, BeansError> { + /// Read the contents of `gameinfo.txt` in directory from + /// `self.get_mod_location()` + pub fn read_gameinfo_file(&mut self) -> Result>, BeansError> + { self.gameinfo_perms()?; let location = self.gameinfo_location(); if !helper::file_exists(location.clone()) @@ -165,7 +191,7 @@ impl RunnerContext { let ex = BeansError::GameInfoFileReadFail { error, location, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }; sentry::capture_error(&ex); return Err(ex); @@ -174,8 +200,10 @@ impl RunnerContext { Ok(Some(file)) } - /// Get the location of `gameinfo.txt` inside of the folder returned by `self.get_mod_location()` - pub fn gameinfo_location(&mut self) -> String { + /// Get the location of `gameinfo.txt` inside of the folder returned by + /// `self.get_mod_location()` + pub fn gameinfo_location(&mut self) -> String + { let mut location = self.get_mod_location(); location.push_str("gameinfo.txt"); location @@ -183,7 +211,8 @@ impl RunnerContext { /// Make sure that the permissions for gameinfo.txt on linux are 0644 #[cfg(target_os = "linux")] - pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> { + pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> + { let location = self.gameinfo_location(); if helper::file_exists(location.clone()) { @@ -193,7 +222,7 @@ impl RunnerContext { let xe = BeansError::GameInfoPermissionSetFail { error: e, permissions: perm.clone(), - location, + location }; sentry::capture_error(&xe); return Err(xe); @@ -206,13 +235,15 @@ impl RunnerContext { Ok(()) } #[cfg(not(target_os = "linux"))] - pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> { + pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> + { Ok(()) } /// Download package with Progress Bar. /// Ok is the location to where it was downloaded to. - pub async fn download_package(version: RemoteVersion) -> Result { + pub async fn download_package(version: RemoteVersion) -> Result + { let av = crate::appvar::parse(); let mut out_loc = helper::get_tmp_dir(); @@ -234,7 +265,7 @@ impl RunnerContext { &av.remote_info.base_url, version.file.expect("No URL for latest package!") ), - out_loc.clone(), + out_loc.clone() ) .await?; @@ -243,7 +274,11 @@ impl RunnerContext { /// Extract zstd_location to the detected sourcemods directory. /// TODO replace unwrap/expect with match error handling - pub fn extract_package(zstd_location: String, out_dir: String) -> Result<(), BeansError> { + pub fn extract_package( + zstd_location: String, + out_dir: String + ) -> Result<(), BeansError> + { let tar_tmp_location = helper::get_tmp_file("data.tar".to_string()); let zstd_file = std::fs::File::open(&zstd_location)?; @@ -276,18 +311,19 @@ impl RunnerContext { src_file: tar_tmp_location, target_dir: out_dir, error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }; trace!("[RunnerContext::extract_package] {:}\n{:#?}", xe, xe); sentry::capture_error(&xe); Err(xe) } - Ok(_) => Ok(()), + Ok(_) => Ok(()) } } #[cfg(target_os = "linux")] - pub fn prepare_symlink(&mut self) -> Result<(), BeansError> { + pub fn prepare_symlink(&mut self) -> Result<(), BeansError> + { for pair in SYMLINK_FILES.iter() { let target: &str = pair[1]; @@ -310,7 +346,8 @@ impl RunnerContext { Ok(()) } #[cfg(not(target_os = "linux"))] - pub fn prepare_symlink(&mut self) -> Result<(), BeansError> { + pub fn prepare_symlink(&mut self) -> Result<(), BeansError> + { // ignored since this symlink stuff is for linux only Ok(()) } @@ -319,10 +356,11 @@ impl RunnerContext { pub const SYMLINK_FILES: &[&[&str; 2]] = &[&["bin/server.so", "bin/server_srv.so"]]; #[derive(Clone, Debug, Default)] -pub enum SourceModDirectoryParam { +pub enum SourceModDirectoryParam +{ /// Default value. Will autodetect location. #[default] AutoDetect, /// Use from the specified sourcemod location. - WithLocation(String), + WithLocation(String) } diff --git a/src/depends.rs b/src/depends.rs index 5f29160..8c2d566 100644 --- a/src/depends.rs +++ b/src/depends.rs @@ -1,13 +1,21 @@ -use crate::{helper, BeansError, BUTLER_BINARY, BUTLER_LIB_1, BUTLER_LIB_2}; -use log::{debug, error}; #[cfg(target_os = "windows")] use std::backtrace::Backtrace; #[cfg(not(target_os = "windows"))] use std::os::unix::fs::PermissionsExt; +use log::{debug, + error}; + +use crate::{helper, + BeansError, + BUTLER_BINARY, + BUTLER_LIB_1, + BUTLER_LIB_2}; + /// try and write aria2c and butler if it doesn't exist /// paths that are used will be fetched from binary_locations() -pub fn try_write_deps() { +pub fn try_write_deps() +{ safe_write_file(get_butler_location().as_str(), &BUTLER_BINARY); safe_write_file(get_butler_1_location().as_str(), &BUTLER_LIB_1); safe_write_file(get_butler_2_location().as_str(), &BUTLER_LIB_2); @@ -30,7 +38,11 @@ pub fn try_write_deps() { ); } } -fn safe_write_file(location: &str, data: &[u8]) { +fn safe_write_file( + location: &str, + data: &[u8] +) +{ if !helper::file_exists(location.to_string()) { if let Err(e) = std::fs::write(location, data) @@ -48,16 +60,18 @@ fn safe_write_file(location: &str, data: &[u8]) { /// will not do anything since this only runs on windows #[cfg(not(target_os = "windows"))] -pub async fn try_install_vcredist() -> Result<(), BeansError> { +pub async fn try_install_vcredist() -> Result<(), BeansError> +{ // ignored since we aren't windows :3 Ok(()) } /// try to download and install vcredist from microsoft via aria2c /// TODO use request instead of aria2c for downloading this. #[cfg(target_os = "windows")] -pub async fn try_install_vcredist() -> Result<(), BeansError> { +pub async fn try_install_vcredist() -> Result<(), BeansError> +{ if !match winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE).open_subkey(String::from( - "Software\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", + "Software\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64" )) { Ok(v) => @@ -66,10 +80,10 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> { match x { Ok(_) => false, - Err(_) => true, + Err(_) => true } } - Err(_) => true, + Err(_) => true } { debug!("[depends::try_install_vcredist] Seems like vcredist is already installed"); @@ -82,7 +96,7 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> { helper::download_with_progress( String::from("https://aka.ms/vs/17/release/vc_redist.x86.exe"), - out_loc.clone(), + out_loc.clone() ) .await?; @@ -90,7 +104,7 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> { { return Err(BeansError::FileNotFound { location: out_loc.clone(), - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } @@ -115,28 +129,33 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> { Ok(()) } -pub fn butler_exists() -> bool { +pub fn butler_exists() -> bool +{ helper::file_exists(get_butler_location()) && helper::file_exists(get_butler_1_location()) && helper::file_exists(get_butler_2_location()) } -pub fn get_butler_location() -> String { +pub fn get_butler_location() -> String +{ let mut path = get_tmp_dir(); path.push_str(BUTLER_LOCATION); path } -pub fn get_butler_1_location() -> String { +pub fn get_butler_1_location() -> String +{ let mut path = get_tmp_dir(); path.push_str(BUTLER_1); path } -pub fn get_butler_2_location() -> String { +pub fn get_butler_2_location() -> String +{ let mut path = get_tmp_dir(); path.push_str(BUTLER_2); path } -fn get_tmp_dir() -> String { +fn get_tmp_dir() -> String +{ let path = helper::get_tmp_dir(); helper::format_directory_path(path) } diff --git a/src/error.rs b/src/error.rs index 60f77f2..c51a840 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,197 +1,248 @@ -use crate::appvar::AppVarData; -use crate::version::AdastralVersionFile; -use std::backtrace::Backtrace; -use std::fmt::{Display, Formatter}; -use std::num::ParseIntError; +use std::{backtrace::Backtrace, + fmt::{Display, + Formatter}, + num::ParseIntError}; + use thiserror::Error; +use crate::{appvar::AppVarData, + version::AdastralVersionFile}; + #[derive(Debug, Error)] -pub enum BeansError { +pub enum BeansError +{ /// Failed to check if there is free space. Value is the location #[error("Not enough free space in {location}")] - FreeSpaceCheckFailure { location: String }, + FreeSpaceCheckFailure + { + location: String + }, /// Failed to find the sourcemod mod folder. #[error("Failed to detect sourcemod folder. Please provide it via the --location argument.")] SourceModLocationNotFound, #[error("Failed to open file at {location} ({error:})")] - FileOpenFailure { + FileOpenFailure + { location: String, - error: std::io::Error, + error: std::io::Error }, #[error("Failed to write file at {location} ({error:})")] - FileWriteFailure { + FileWriteFailure + { location: String, - error: std::io::Error, + error: std::io::Error }, #[error("Failed to create directory {location} ({error:})")] - DirectoryCreateFailure { + DirectoryCreateFailure + { location: String, - error: std::io::Error, + error: std::io::Error }, #[error("Failed to extract {src_file} to directory {target_dir} ({error:})")] - TarExtractFailure { + TarExtractFailure + { src_file: String, target_dir: String, error: std::io::Error, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Failed to send request ({error:})")] - Reqwest { + Reqwest + { error: reqwest::Error, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Failed to serialize or deserialize data ({error:})")] - SerdeJson { + SerdeJson + { error: serde_json::Error, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Latest version is already installed. (current: {current}, latest: {latest})")] - LatestVersionAlreadyInstalled { current: usize, latest: usize }, + LatestVersionAlreadyInstalled + { + current: usize, latest: usize + }, #[error("Failed to download file\n{reason:#?}")] - DownloadFailure { reason: DownloadFailureReason }, + DownloadFailure + { + reason: DownloadFailureReason + }, #[error("General IO Error\n{error:#?}")] - IO { + IO + { error: std::io::Error, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Unable to perform action since the mod isn't installed since {missing_file} couldn't be found")] - TargetSourcemodNotInstalled { + TargetSourcemodNotInstalled + { missing_file: String, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Failed to run the verify command with butler. ({error:})")] - ButlerVerifyFailure { + ButlerVerifyFailure + { signature_url: String, gamedir: String, remote: String, error: std::io::Error, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Failed to run the apply command with butler. {error:}")] - ButlerPatchFailure { + ButlerPatchFailure + { patchfile_location: String, gamedir: String, error: std::io::Error, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Could not find file {location}")] - FileNotFound { + FileNotFound + { location: String, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Version {version:#?} could not be found on the server.")] - RemoteVersionNotFound { version: Option }, + RemoteVersionNotFound + { + version: Option + }, #[error("Could not find steam installation, which means we can't find the sourcemods folder. Please provide the sourcemods folder with the --location parameter.")] SteamNotFound, #[error("{msg}")] - RegistryKeyFailure { + RegistryKeyFailure + { msg: String, error: std::io::Error, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Failed to migrate old version file to the new format at {location} ({error:})")] - VersionFileMigrationFailure { + VersionFileMigrationFailure + { error: std::io::Error, - location: String, + location: String }, #[error("Failed to delete old version file {location} ({error:})")] - VersionFileMigrationDeleteFailure { + VersionFileMigrationDeleteFailure + { error: std::io::Error, - location: String, + location: String }, #[error("Failed to convert version file to JSON format. ({error:})")] - VersionFileSerialize { + VersionFileSerialize + { error: serde_json::Error, - instance: AdastralVersionFile, + instance: AdastralVersionFile }, #[error( "Failed to parse the version in {old_location}. It's content was {old_content} ({error:})" )] - VersionFileParseFailure { + VersionFileParseFailure + { error: ParseIntError, old_location: String, - old_content: String, + old_content: String }, #[error("Failed to read version file at {location}. ({error:})")] - VersionFileReadFailure { + VersionFileReadFailure + { error: std::io::Error, - location: String, + location: String }, #[error("Failed to serialize provided AppVarData to JSON. ({error:})")] - AppVarDataSerializeFailure { + AppVarDataSerializeFailure + { error: serde_json::Error, - data: AppVarData, + data: AppVarData }, #[error("Failed to read gameinfo.txt at {location} ({error:})")] - GameInfoFileReadFail { + GameInfoFileReadFail + { error: std::io::Error, location: String, - backtrace: Backtrace, + backtrace: Backtrace }, #[error("Failed to set permissions on gameinfo.txt at {location} ({error:})")] - GameInfoPermissionSetFail { + GameInfoPermissionSetFail + { error: std::io::Error, permissions: std::fs::Permissions, - location: String, + location: String }, #[error("Failed to backup gameinfo.txt, {reason:}")] - GameinfoBackupFailure { reason: GameinfoBackupFailureReason }, + GameinfoBackupFailure + { + reason: GameinfoBackupFailureReason + }, #[error("Failed to remove files in {location} ({error:})")] - CleanTempFailure { + CleanTempFailure + { location: String, - error: std::io::Error, - }, + error: std::io::Error + } } #[derive(Debug)] -pub enum DownloadFailureReason { - Reqwest { - url: String, - error: reqwest::Error, +pub enum DownloadFailureReason +{ + Reqwest + { + url: String, error: reqwest::Error }, /// The downloaded file could not be found, perhaps it failed? - FileNotFound { - location: String, - }, + FileNotFound + { + location: String + } } #[derive(Debug)] -pub enum GameinfoBackupFailureReason { +pub enum GameinfoBackupFailureReason +{ ReadContentFail(GameinfoBackupReadContentFail), BackupDirectoryCreateFailure(GameinfoBackupCreateDirectoryFail), - WriteFail(GameinfoBackupWriteFail), + WriteFail(GameinfoBackupWriteFail) } #[derive(Debug)] -pub struct GameinfoBackupReadContentFail { +pub struct GameinfoBackupReadContentFail +{ pub error: std::io::Error, pub proposed_location: String, - pub current_location: String, + pub current_location: String } #[derive(Debug)] -pub struct GameinfoBackupCreateDirectoryFail { +pub struct GameinfoBackupCreateDirectoryFail +{ pub error: std::io::Error, - pub location: String, + pub location: String } #[derive(Debug)] -pub struct GameinfoBackupWriteFail { +pub struct GameinfoBackupWriteFail +{ pub error: std::io::Error, - pub location: String, + pub location: String } -impl Display for GameinfoBackupFailureReason { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +impl Display for GameinfoBackupFailureReason +{ + fn fmt( + &self, + f: &mut Formatter<'_> + ) -> std::fmt::Result + { match self { GameinfoBackupFailureReason::ReadContentFail(v) => write!( @@ -208,38 +259,45 @@ impl Display for GameinfoBackupFailureReason { f, "Failed to write content to {} ({:})", v.location, v.error - ), + ) } } } #[derive(Debug)] -pub struct TarExtractFailureDetails { +pub struct TarExtractFailureDetails +{ pub source: String, pub target: String, - pub error: std::io::Error, + pub error: std::io::Error } -impl From for BeansError { - fn from(e: std::io::Error) -> Self { +impl From for BeansError +{ + fn from(e: std::io::Error) -> Self + { BeansError::IO { error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() } } } -impl From for BeansError { - fn from(e: reqwest::Error) -> Self { +impl From for BeansError +{ + fn from(e: reqwest::Error) -> Self + { BeansError::Reqwest { error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() } } } -impl From for BeansError { - fn from(e: serde_json::Error) -> Self { +impl From for BeansError +{ + fn from(e: serde_json::Error) -> Self + { BeansError::SerdeJson { error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() } } } diff --git a/src/flags.rs b/src/flags.rs index b02a619..63d94b0 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -18,7 +18,8 @@ bitflags! { pub static mut LAUNCH_FLAGS: u32 = 0x00; /// check if the `flag` provided is in `LAUNCH_FLAGS` -pub fn has_flag(flag: LaunchFlag) -> bool { +pub fn has_flag(flag: LaunchFlag) -> bool +{ unsafe { let data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); data.contains(flag) @@ -26,7 +27,8 @@ pub fn has_flag(flag: LaunchFlag) -> bool { } /// Add a flag to `LAUNCH_FLAGS` -pub fn add_flag(flag: LaunchFlag) { +pub fn add_flag(flag: LaunchFlag) +{ unsafe { if let LaunchFlag::DEBUG_MODE = flag { @@ -40,7 +42,8 @@ pub fn add_flag(flag: LaunchFlag) { } /// remove a flag from `LAUNCH_FLAGS` -pub fn remove_flag(flag: LaunchFlag) { +pub fn remove_flag(flag: LaunchFlag) +{ unsafe { if flag == LaunchFlag::DEBUG_MODE { @@ -52,6 +55,7 @@ pub fn remove_flag(flag: LaunchFlag) { } } -pub fn debug_mode() -> bool { +pub fn debug_mode() -> bool +{ has_flag(LaunchFlag::DEBUG_MODE) } diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index 19c6837..7b14c15 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -1,36 +1,52 @@ -use crate::gui::shared_ui::GenericDialog; -use crate::gui::{apply_app_scheme, icon, wait_for_quit, window_centre_screen, GUIAppStatus}; -use fltk::image::PngImage; -use fltk::{prelude::*, *}; +use fltk::{image::PngImage, + prelude::*, + *}; use log::warn; -pub struct DialogBuilder { +use crate::gui::{apply_app_scheme, + icon, + shared_ui::GenericDialog, + wait_for_quit, + window_centre_screen, + GUIAppStatus}; + +pub struct DialogBuilder +{ pub title: String, pub content: String, - pub icon: Option, + pub icon: Option } -pub enum DialogIconKind { +pub enum DialogIconKind +{ Default, Warn, - Error, + Error } -impl Default for DialogBuilder { - fn default() -> Self { +impl Default for DialogBuilder +{ + fn default() -> Self + { Self { title: format!("beans v{}", crate::VERSION), content: String::new(), - icon: None, + icon: None } } } -impl DialogBuilder { - pub fn new() -> Self { +impl DialogBuilder +{ + pub fn new() -> Self + { Self::default() } - pub fn with_png_data(mut self, data: &[u8]) -> Self { + pub fn with_png_data( + mut self, + data: &[u8] + ) -> Self + { match PngImage::from_data(data) { Ok(img) => self.icon = Some(img), @@ -41,24 +57,37 @@ impl DialogBuilder { } self } - pub fn with_icon(self, kind: DialogIconKind) -> Self { + pub fn with_icon( + self, + kind: DialogIconKind + ) -> Self + { let data: &Vec = match kind { DialogIconKind::Default => &icon::DEFAULT_RAW_X32, DialogIconKind::Warn => &icon::DEFAULT_WARN_RAW_X32, - DialogIconKind::Error => &icon::DEFAULT_ERROR_RAW_X32, + DialogIconKind::Error => &icon::DEFAULT_ERROR_RAW_X32 }; self.with_png_data(data) } - pub fn with_title(mut self, content: String) -> Self { + pub fn with_title( + mut self, + content: String + ) -> Self + { self.title = content.clone(); self } - pub fn with_content(mut self, content: String) -> Self { + pub fn with_content( + mut self, + content: String + ) -> Self + { self.content = content.clone(); self } - pub fn run(&self) { + pub fn run(&self) + { if !crate::has_gui_support() { println!("============ {} ============", self.title); @@ -99,7 +128,7 @@ impl DialogBuilder { } false } - _ => false, + _ => false }); ui.win.make_resizable(false); ui.win.show(); diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 4b04931..691f2a2 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,7 +1,9 @@ -use fltk::app::Receiver; -use fltk::window::Window; -use fltk::{prelude::*, *}; -use fltk_theme::{color_themes, ColorTheme}; +use fltk::{app::Receiver, + prelude::*, + window::Window, + *}; +use fltk_theme::{color_themes, + ColorTheme}; use log::debug; mod dialog; @@ -13,7 +15,8 @@ pub mod icon; #[allow(dead_code)] #[derive(Copy, Clone, Debug)] -pub enum GUIAppStatus { +pub enum GUIAppStatus +{ Update, Quit, @@ -27,11 +30,12 @@ pub enum GUIAppStatus { BtnYes, BtnNo, BtnTryAgain, - BtnContinue, + BtnContinue } /// Make the `window` provided the in be the center of the current screen. -pub fn window_centre_screen(window: &mut Window) { +pub fn window_centre_screen(window: &mut Window) +{ let (sx, sy) = app::screen_coords(); let width = window.width(); let height = window.height(); @@ -42,20 +46,26 @@ pub fn window_centre_screen(window: &mut Window) { ((x / 2.0) as i32) + sx, ((y / 2.0) as i32) + sy, width, - height, + height ); } /// Get the X and Y position of the center of the current screen. -pub fn get_center_screen() -> (i32, i32) { +pub fn get_center_screen() -> (i32, i32) +{ let (px, py) = app::screen_coords(); let (sw, sh) = app::screen_size(); (((sw / 2.0) as i32) + px, ((sh / 2.0) as i32) + py) } -/// Ensure that a window has a fixed width & height, and that it will appear in the centre of the -/// current screen. -pub fn window_ensure(win: &mut Window, width: i32, height: i32) { +/// Ensure that a window has a fixed width & height, and that it will appear in +/// the centre of the current screen. +pub fn window_ensure( + win: &mut Window, + width: i32, + height: i32 +) +{ window_centre_screen(win); win.handle(move |w, ev| match ev { @@ -67,17 +77,18 @@ pub fn window_ensure(win: &mut Window, width: i32, height: i32) { } true } - _ => false, + _ => false }); win.make_resizable(false); win.show(); } -pub fn apply_app_scheme() { +pub fn apply_app_scheme() +{ let theme_content = match dark_light::detect() { dark_light::Mode::Light => color_themes::GRAY_THEME, - _ => color_themes::DARK_THEME, + _ => color_themes::DARK_THEME }; debug!( "[apply_app_scheme] using color theme: {:#?}", @@ -87,7 +98,11 @@ pub fn apply_app_scheme() { theme.apply(); } -pub fn wait_for_quit(app: &app::App, receive_action: &Receiver) { +pub fn wait_for_quit( + app: &app::App, + receive_action: &Receiver +) +{ while app.wait() { if let Some(GUIAppStatus::Quit) = receive_action.recv() diff --git a/src/helper/linux.rs b/src/helper/linux.rs index e89de29..043c6a6 100644 --- a/src/helper/linux.rs +++ b/src/helper/linux.rs @@ -1,19 +1,24 @@ -use crate::helper::format_directory_path; -use crate::BeansError; -use log::{debug, error}; use std::fs::read_to_string; +use log::{debug, + error}; + +use crate::{helper::format_directory_path, + BeansError}; + /// all possible known directory where steam *might* be /// only is used on linux, since windows will use the registry. pub const STEAM_POSSIBLE_DIR: &[&str] = &[ "~/.steam/registry.vdf", - "~/.var/app/com.valvesoftware.Steam/.steam/registry.vdf", + "~/.var/app/com.valvesoftware.Steam/.steam/registry.vdf" ]; /// find sourcemod path on linux. /// fetches the fake registry that steam uses from find_steam_reg_path -/// and gets the value of Registry/HKCU/Software/Valve/Steam/SourceModInstallPath -pub fn find_sourcemod_path() -> Result { +/// and gets the value of +/// Registry/HKCU/Software/Valve/Steam/SourceModInstallPath +pub fn find_sourcemod_path() -> Result +{ let reg_path = find_steam_reg_path()?; let reg_content = match read_to_string(reg_path.as_str()) @@ -24,7 +29,7 @@ pub fn find_sourcemod_path() -> Result { sentry::capture_error(&e); return Err(BeansError::FileOpenFailure { location: reg_path, - error: e, + error: e }); } }; @@ -47,7 +52,8 @@ pub fn find_sourcemod_path() -> Result { Err(BeansError::SourceModLocationNotFound) } /// returns the first item in STEAM_POSSIBLE_DIR that exists. otherwise None -fn find_steam_reg_path() -> Result { +fn find_steam_reg_path() -> Result +{ for x in STEAM_POSSIBLE_DIR.iter() { match simple_home_dir::home_dir() diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 8e05354..6e06e15 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -1,33 +1,43 @@ #[cfg(not(target_os = "windows"))] mod linux; +use std::backtrace::Backtrace; + #[cfg(not(target_os = "windows"))] pub use linux::*; -use std::backtrace::Backtrace; #[cfg(target_os = "windows")] mod windows; -#[cfg(target_os = "windows")] -pub use windows::*; +use std::{collections::HashMap, + io::Write, + path::PathBuf}; -use crate::appvar::AppVarData; -use crate::{ - BeansError, DownloadFailureReason, GameinfoBackupCreateDirectoryFail, - GameinfoBackupFailureReason, GameinfoBackupReadContentFail, GameinfoBackupWriteFail, - RunnerContext, -}; use futures::StreamExt; -use indicatif::{ProgressBar, ProgressStyle}; -use log::{debug, error, trace, warn}; -use rand::{distributions::Alphanumeric, Rng}; +use indicatif::{ProgressBar, + ProgressStyle}; +use log::{debug, + error, + trace, + warn}; +use rand::{distributions::Alphanumeric, + Rng}; use reqwest::header::USER_AGENT; -use std::collections::HashMap; -use std::io::Write; -use std::path::PathBuf; +#[cfg(target_os = "windows")] +pub use windows::*; + +use crate::{appvar::AppVarData, + BeansError, + DownloadFailureReason, + GameinfoBackupCreateDirectoryFail, + GameinfoBackupFailureReason, + GameinfoBackupReadContentFail, + GameinfoBackupWriteFail, + RunnerContext}; #[derive(Clone, Debug)] -pub enum InstallType { +pub enum InstallType +{ /// when steam/sourcemods/open_fortress/ doesn't exist NotInstalled, /// when steam/sourcemods/open_fortress/.adastral exists @@ -40,11 +50,16 @@ pub enum InstallType { /// OtherSourceManual, then it will return true /// /// set when only steam/sourcemods/open_fortress/gameinfo.txt exists - OtherSourceManual, + OtherSourceManual } -impl PartialEq for InstallType { - fn eq(&self, other: &Self) -> bool { +impl PartialEq for InstallType +{ + fn eq( + &self, + other: &Self + ) -> bool + { match self { InstallType::NotInstalled => match other @@ -52,35 +67,36 @@ impl PartialEq for InstallType { InstallType::NotInstalled => true, InstallType::Adastral => false, InstallType::OtherSource => false, - InstallType::OtherSourceManual => false, + InstallType::OtherSourceManual => false }, InstallType::Adastral => match other { InstallType::NotInstalled => false, InstallType::Adastral => true, InstallType::OtherSource => false, - InstallType::OtherSourceManual => false, + InstallType::OtherSourceManual => false }, InstallType::OtherSource => match other { InstallType::NotInstalled => false, InstallType::Adastral => false, InstallType::OtherSource => true, - InstallType::OtherSourceManual => true, + InstallType::OtherSourceManual => true }, InstallType::OtherSourceManual => match other { InstallType::NotInstalled => false, InstallType::Adastral => false, InstallType::OtherSource => false, - InstallType::OtherSourceManual => true, - }, + InstallType::OtherSourceManual => true + } } } } /// get the current type of installation. -pub fn install_state(sourcemods_location: Option) -> InstallType { +pub fn install_state(sourcemods_location: Option) -> InstallType +{ let mut smp_x = match sourcemods_location { Some(v) => v, @@ -97,7 +113,7 @@ pub fn install_state(sourcemods_location: Option) -> InstallType { ); return InstallType::NotInstalled; } - }, + } }; if smp_x.ends_with("/") || smp_x.ends_with("\\") { @@ -121,8 +137,10 @@ pub fn install_state(sourcemods_location: Option) -> InstallType { InstallType::NotInstalled } -/// get user input from terminal. prompt is displayed on the line above where the user does input. -pub fn get_input(prompt: &str) -> String { +/// get user input from terminal. prompt is displayed on the line above where +/// the user does input. +pub fn get_input(prompt: &str) -> String +{ println!("{}", prompt); let mut input = String::new(); match std::io::stdin().read_line(&mut input) @@ -136,30 +154,35 @@ pub fn get_input(prompt: &str) -> String { } /// check if a file exists -pub fn file_exists(location: String) -> bool { +pub fn file_exists(location: String) -> bool +{ std::path::Path::new(&location).exists() } /// Check if the location provided exists and it's a directory. -pub fn dir_exists(location: String) -> bool { +pub fn dir_exists(location: String) -> bool +{ file_exists(location.clone()) && is_directory(location.clone()) } -pub fn is_directory(location: String) -> bool { +pub fn is_directory(location: String) -> bool +{ let x = PathBuf::from(&location); x.is_dir() } /// Check if the file at the location provided is a symlink. -pub fn is_symlink(location: String) -> bool { +pub fn is_symlink(location: String) -> bool +{ match std::fs::symlink_metadata(&location) { Ok(meta) => meta.file_type().is_symlink(), - Err(_) => false, + Err(_) => false } } -pub fn generate_rand_str(length: usize) -> String { +pub fn generate_rand_str(length: usize) -> String +{ let s: String = rand::thread_rng() .sample_iter(Alphanumeric) .take(length) @@ -168,10 +191,16 @@ pub fn generate_rand_str(length: usize) -> String { s.to_uppercase() } -/// Join the path, using `tail` as the base, and `head` as the thing to add on top of it. +/// Join the path, using `tail` as the base, and `head` as the thing to add on +/// top of it. /// -/// This will also convert backslashes/forwardslashes to the compiled separator in `crate::PATH_SEP` -pub fn join_path(tail: String, head: String) -> String { +/// This will also convert backslashes/forwardslashes to the compiled separator +/// in `crate::PATH_SEP` +pub fn join_path( + tail: String, + head: String +) -> String +{ let mut h = head .to_string() .replace("/", crate::PATH_SEP) @@ -184,7 +213,8 @@ pub fn join_path(tail: String, head: String) -> String { format!("{}{}", format_directory_path(tail), h) } -pub fn remove_path_head(location: String) -> String { +pub fn remove_path_head(location: String) -> String +{ if let Some(Some(m)) = std::path::Path::new(&location).parent().map(|p| p.to_str()) { return m.to_string(); @@ -192,8 +222,10 @@ pub fn remove_path_head(location: String) -> String { String::new() } -/// Make sure that the location provided is formatted as a directory (ends with `crate::PATH_SEP`). -pub fn format_directory_path(location: String) -> String { +/// Make sure that the location provided is formatted as a directory (ends with +/// `crate::PATH_SEP`). +pub fn format_directory_path(location: String) -> String +{ let mut x = location.to_string().replace(['/', '\\'], crate::PATH_SEP); while x.ends_with(crate::PATH_SEP) @@ -208,16 +240,19 @@ pub fn format_directory_path(location: String) -> String { } #[cfg(not(target_os = "windows"))] -pub fn canonicalize(location: &str) -> Result { +pub fn canonicalize(location: &str) -> Result +{ std::fs::canonicalize(location) } #[cfg(target_os = "windows")] -pub fn canonicalize(location: &str) -> Result { +pub fn canonicalize(location: &str) -> Result +{ dunce::canonicalize(location) } -pub fn parse_location(location: String) -> String { +pub fn parse_location(location: String) -> String +{ let path = std::path::Path::new(&location); let real_location = match path.to_str() { @@ -268,7 +303,8 @@ pub fn parse_location(location: String) -> String { } /// Get the amount of free space on the drive in the location provided. -pub fn get_free_space(location: String) -> Result { +pub fn get_free_space(location: String) -> Result +{ let mut data: HashMap = HashMap::new(); for disk in sysinfo::Disks::new_with_refreshed_list().list() { @@ -291,18 +327,26 @@ pub fn get_free_space(location: String) -> Result { } Err(BeansError::FreeSpaceCheckFailure { - location: parse_location(location.clone()), + location: parse_location(location.clone()) }) } /// Check if the location provided has enough free space. -pub fn has_free_space(location: String, size: usize) -> Result { +pub fn has_free_space( + location: String, + size: usize +) -> Result +{ Ok((size as u64) < get_free_space(location)?) } /// Download file at the URL provided to the output location provided /// This function will also show a progress bar with indicatif. -pub async fn download_with_progress(url: String, out_location: String) -> Result<(), BeansError> { +pub async fn download_with_progress( + url: String, + out_location: String +) -> Result<(), BeansError> +{ let res = match reqwest::Client::new().get(&url).send().await { Ok(v) => v, @@ -312,8 +356,8 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::Reqwest { url: url.clone(), - error: e, - }, + error: e + } }); } }; @@ -338,7 +382,7 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result sentry::capture_error(&e); return Err(BeansError::FileOpenFailure { location: out_location, - error: e, + error: e }); } }; @@ -360,7 +404,8 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result } /// Format parameter `i` to a human-readable size. -pub fn format_size(i: usize) -> String { +pub fn format_size(i: usize) -> String +{ let value = i.to_string(); let decimal_points: usize = 3; @@ -419,13 +464,14 @@ pub fn format_size(i: usize) -> String { format!("{}{}", whole, dec_x) } -/// Check if we should use the custom temporary directory, which is stored in the environment variable -/// defined in `CUSTOM_TMPDIR_NAME`. +/// Check if we should use the custom temporary directory, which is stored in +/// the environment variable defined in `CUSTOM_TMPDIR_NAME`. /// /// ## Return /// `Some` when the environment variable is set, and the directory exist. /// Otherwise `None` is returned. -pub fn use_custom_tmpdir() -> Option { +pub fn use_custom_tmpdir() -> Option +{ if let Ok(x) = std::env::var(CUSTOM_TMPDIR_NAME) { let s = x.to_string(); @@ -447,7 +493,8 @@ pub fn use_custom_tmpdir() -> Option { pub const CUSTOM_TMPDIR_NAME: &str = "ADASTRAL_TMPDIR"; /// Create directory in temp directory with name of "beans-rs" -pub fn get_tmp_dir() -> String { +pub fn get_tmp_dir() -> String +{ let mut dir = std::env::temp_dir().to_str().unwrap_or("").to_string(); if let Some(x) = use_custom_tmpdir() { @@ -532,9 +579,11 @@ pub fn get_tmp_dir() -> String { /// ## Note /// Will always return `false` when `cfg!(not(target_os = "linux"))`. /// -/// This function will write to `log::trace` with the full error details before writing it to `log::warn` or `log::error`. Since errors from this +/// This function will write to `log::trace` with the full error details before +/// writing it to `log::warn` or `log::error`. Since errors from this /// aren't significant, `sentry::capture_error` will not be called. -pub fn is_steamdeck() -> bool { +pub fn is_steamdeck() -> bool +{ if cfg!(not(target_os = "linux")) { return false; @@ -575,13 +624,16 @@ pub fn is_steamdeck() -> bool { } /// Generate a full file location for a temporary file. -pub fn get_tmp_file(filename: String) -> String { +pub fn get_tmp_file(filename: String) -> String +{ let head = format!("{}_{}", generate_rand_str(8), filename); join_path(get_tmp_dir(), head) } -/// Check if there is an update available. When the latest release doesn't match the current release. -pub async fn beans_has_update() -> Result, BeansError> { +/// Check if there is an update available. When the latest release doesn't match +/// the current release. +pub async fn beans_has_update() -> Result, BeansError> +{ let rs = reqwest::Client::new() .get(GITHUB_RELEASES_URL) .header(USER_AGENT, &format!("beans-rs/{}", crate::VERSION)) @@ -595,7 +647,7 @@ pub async fn beans_has_update() -> Result, BeansError> trace!("Failed get latest release from github \nerror: {:#?}", e); return Err(BeansError::Reqwest { error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } }; @@ -612,7 +664,7 @@ pub async fn beans_has_update() -> Result, BeansError> ); return Err(BeansError::SerdeJson { error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } }; @@ -624,7 +676,11 @@ pub async fn beans_has_update() -> Result, BeansError> Ok(None) } -pub fn restore_gameinfo(ctx: &mut RunnerContext, data: Vec) -> Result<(), BeansError> { +pub fn restore_gameinfo( + ctx: &mut RunnerContext, + data: Vec +) -> Result<(), BeansError> +{ let loc = ctx.gameinfo_location(); trace!("gameinfo location: {}", &loc); if let Ok(m) = std::fs::metadata(&loc) @@ -660,7 +716,8 @@ pub fn restore_gameinfo(ctx: &mut RunnerContext, data: Vec) -> Result<(), Be Ok(()) } -pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { +pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> +{ let av = AppVarData::get(); let gamedir = join_path(ctx.clone().sourcemod_path, av.mod_info.sourcemod_name); let backupdir = join_path(gamedir.clone(), String::from(GAMEINFO_BACKUP_DIRNAME)); @@ -682,9 +739,9 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { reason: GameinfoBackupFailureReason::BackupDirectoryCreateFailure( GameinfoBackupCreateDirectoryFail { error: e, - location: backupdir, - }, - ), + location: backupdir + } + ) }); } } @@ -694,7 +751,7 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { "{}-{}.txt", ctx.current_version.unwrap_or(0), current_time_formatted - ), + ) ); let current_location = join_path(gamedir, String::from("gameinfo.txt")); @@ -723,9 +780,9 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { GameinfoBackupReadContentFail { error: e, proposed_location: output_location, - current_location: current_location.clone(), - }, - ), + current_location: current_location.clone() + } + ) }); } }; @@ -749,8 +806,8 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { return Err(BeansError::GameinfoBackupFailure { reason: GameinfoBackupFailureReason::WriteFail(GameinfoBackupWriteFail { error: e, - location: output_location, - }), + location: output_location + }) }); } @@ -763,7 +820,8 @@ const GAMEINFO_BACKUP_DIRNAME: &str = "gameinfo_backup"; const GITHUB_RELEASES_URL: &str = "https://api.github.com/repositories/805393469/releases/latest"; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GithubReleaseItem { +pub struct GithubReleaseItem +{ #[serde(rename = "id")] pub _id: u64, pub created_at: String, @@ -771,11 +829,13 @@ pub struct GithubReleaseItem { pub url: String, pub html_url: String, pub draft: bool, - pub prerelease: bool, + pub prerelease: bool } -/// Return `true` when `try_get_env_var` returns Some with a length greater than `1`. -pub fn has_env_var(target_key: String) -> bool { +/// Return `true` when `try_get_env_var` returns Some with a length greater than +/// `1`. +pub fn has_env_var(target_key: String) -> bool +{ if let Some(x) = try_get_env_var(target_key) { return x.len() > 1; @@ -785,7 +845,8 @@ pub fn has_env_var(target_key: String) -> bool { /// Try and get a value from `std::env::vars()` /// Will return `None` when not found -pub fn try_get_env_var(target_key: String) -> Option { +pub fn try_get_env_var(target_key: String) -> Option +{ for (key, value) in std::env::vars() { if key == target_key diff --git a/src/helper/windows.rs b/src/helper/windows.rs index 0929682..80e963a 100644 --- a/src/helper/windows.rs +++ b/src/helper/windows.rs @@ -1,13 +1,16 @@ -use crate::helper::format_directory_path; -use crate::BeansError; use std::backtrace::Backtrace; -use winreg::enums::HKEY_CURRENT_USER; -use winreg::RegKey; + +use winreg::{enums::HKEY_CURRENT_USER, + RegKey}; + +use crate::{helper::format_directory_path, + BeansError}; /// TODO use windows registry to get the SourceModInstallPath /// HKEY_CURRENT_USER\Software\Value\Steam /// Key: SourceModInstallPath -pub fn find_sourcemod_path() -> Result { +pub fn find_sourcemod_path() -> Result +{ match RegKey::predef(HKEY_CURRENT_USER).open_subkey(String::from("Software\\Valve\\Steam")) { Ok(rkey) => @@ -22,7 +25,7 @@ pub fn find_sourcemod_path() -> Result { msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" .to_string(), error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } } @@ -33,7 +36,7 @@ pub fn find_sourcemod_path() -> Result { msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" .to_string(), error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } } diff --git a/src/lib.rs b/src/lib.rs index 2df0fbc..98c846a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,11 +32,13 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const SENTRY_URL: &str = "https://9df80170f0a4411bb9c834ac54734380@sentry.kate.pet/1"; /// content to display when showing a message box on panic. pub const PANIC_MSG_CONTENT: &str = include_str!("text/msgbox_panic_text.txt"); -/// once everything is done, do we wait for the user to press enter before exiting? +/// once everything is done, do we wait for the user to press enter before +/// exiting? /// /// just like the `pause` thing in batch. pub static mut PAUSE_ONCE_DONE: bool = false; -/// When `true`, everything that prompts the user for Y/N should use the default option. +/// When `true`, everything that prompts the user for Y/N should use the default +/// option. pub static mut PROMPT_DO_WHATEVER: bool = false; // ------------------------------------------------------------------------ @@ -49,18 +51,22 @@ pub const PATH_SEP: &str = "/"; #[cfg(target_os = "windows")] pub const PATH_SEP: &str = "\\"; -pub fn data_dir() -> String { +pub fn data_dir() -> String +{ let av = appvar::parse(); format!("{}{}{}", PATH_SEP, av.mod_info.sourcemod_name, PATH_SEP) } -/// Check if we have GUI support enabled. Will always return `false` when `PAUSE_ONCE_DONE` is `false`. +/// Check if we have GUI support enabled. Will always return `false` when +/// `PAUSE_ONCE_DONE` is `false`. /// /// Will return `true` when /// - Running on Windows /// - Running on macOS -/// - Running on Linux AND the `DISPLAY` or `XDG_SESSION_DESKTOP` environment variables are set. -pub fn has_gui_support() -> bool { +/// - Running on Linux AND the `DISPLAY` or `XDG_SESSION_DESKTOP` environment +/// variables are set. +pub fn has_gui_support() -> bool +{ unsafe { if !PAUSE_ONCE_DONE { diff --git a/src/logger.rs b/src/logger.rs index 823d776..d8b411d 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,23 +1,33 @@ +use std::{io, + io::Write, + sync::Mutex, + time::Instant}; + use lazy_static::lazy_static; -use log::{LevelFilter, Log, Metadata, Record}; -use std::io; -use std::io::Write; -use std::sync::Mutex; -use std::time::Instant; +use log::{LevelFilter, + Log, + Metadata, + Record}; lazy_static! { static ref LOGGER: CustomLogger = CustomLogger { - inner: Mutex::new(None), + inner: Mutex::new(None) }; } -struct CustomLogger { - inner: Mutex>, +struct CustomLogger +{ + inner: Mutex> } -impl CustomLogger { +impl CustomLogger +{ // Set this `CustomLogger`'s sink and reset the start time. - fn renew(&self, sink: T) { + fn renew( + &self, + sink: T + ) + { *self.inner.lock().unwrap() = Some(CustomLoggerInner { start: Instant::now(), sink: Box::new(sink), @@ -25,18 +35,27 @@ impl CustomLogger { { log::Level::Error => LogFilter::Exception, log::Level::Warn => LogFilter::Event, - log::Level::Info | log::Level::Debug | log::Level::Trace => LogFilter::Breadcrumb, - }), + log::Level::Info | log::Level::Debug | log::Level::Trace => LogFilter::Breadcrumb + }) }); } } -impl Log for CustomLogger { - fn enabled(&self, _: &Metadata) -> bool { +impl Log for CustomLogger +{ + fn enabled( + &self, + _: &Metadata + ) -> bool + { true } - fn log(&self, record: &Record) { + fn log( + &self, + record: &Record + ) + { if !self.enabled(record.metadata()) { return; @@ -48,7 +67,8 @@ impl Log for CustomLogger { } } - fn flush(&self) { + fn flush(&self) + { if let Some(ref mut inner) = *self.inner.lock().unwrap() { inner.sentry.flush(); @@ -56,16 +76,23 @@ impl Log for CustomLogger { } } -struct CustomLoggerInner { +struct CustomLoggerInner +{ start: Instant, sink: Box, - sentry: sentry_log::SentryLogger, + sentry: sentry_log::SentryLogger } use colored::Colorize; -use sentry_log::{LogFilter, NoopLogger}; +use sentry_log::{LogFilter, + NoopLogger}; -impl CustomLoggerInner { - fn log(&mut self, record: &Record) { +impl CustomLoggerInner +{ + fn log( + &mut self, + record: &Record + ) + { let mut do_print = true; unsafe { if LOG_FILTER < record.level() @@ -105,7 +132,7 @@ impl CustomLoggerInner { log::Level::Warn => data.yellow(), log::Level::Info => data.normal(), log::Level::Debug => data.green(), - log::Level::Trace => data.blue(), + log::Level::Trace => data.blue() } .to_string() } @@ -116,7 +143,8 @@ impl CustomLoggerInner { self.sentry.log(record); } } -pub fn set_filter(filter: LevelFilter) { +pub fn set_filter(filter: LevelFilter) +{ unsafe { LOG_FILTER = filter; } @@ -127,13 +155,15 @@ pub static mut LOG_COLOR: bool = true; pub const LOG_FORMAT_DEFAULT: &str = "[#HOURS:#MINUTES:#SECONDS.#MILLISECONDS] (#THREAD) #LEVEL #CONTENT"; pub const LOG_FORMAT_MINIMAL: &str = "#LEVEL #CONTENT"; -pub fn log_to(sink: T) { +pub fn log_to(sink: T) +{ LOGGER.renew(sink); log::set_max_level(LevelFilter::max()); // The only possible error is if this has been called before let _ = log::set_logger(&*LOGGER); assert_eq!(log::logger() as *const dyn Log, &*LOGGER as *const dyn Log); } -pub fn log_to_stdout() { +pub fn log_to_stdout() +{ log_to(io::stdout()); } diff --git a/src/main.rs b/src/main.rs index 38c6151..2cf2a44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,38 @@ #![feature(panic_info_message)] -use beans_rs::flags::LaunchFlag; -use beans_rs::gui::DialogIconKind; -use beans_rs::helper::parse_location; -use beans_rs::workflows::{CleanWorkflow, InstallWorkflow, UpdateWorkflow, VerifyWorkflow}; -use beans_rs::SourceModDirectoryParam; -use beans_rs::{flags, helper, wizard, RunnerContext, PANIC_MSG_CONTENT}; -use clap::{Arg, ArgAction, ArgMatches, Command}; -use log::{debug, error, info, trace, LevelFilter}; use std::str::FromStr; +use beans_rs::{flags, + flags::LaunchFlag, + gui::DialogIconKind, + helper, + helper::parse_location, + wizard, + workflows::{CleanWorkflow, + InstallWorkflow, + UpdateWorkflow, + VerifyWorkflow}, + RunnerContext, + SourceModDirectoryParam, + PANIC_MSG_CONTENT}; +use clap::{Arg, + ArgAction, + ArgMatches, + Command}; +use log::{debug, + error, + info, + trace, + LevelFilter}; + pub const DEFAULT_LOG_LEVEL_RELEASE: LevelFilter = LevelFilter::Info; #[cfg(debug_assertions)] pub const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Trace; #[cfg(not(debug_assertions))] pub const DEFAULT_LOG_LEVEL: LevelFilter = DEFAULT_LOG_LEVEL_RELEASE; -fn main() { +fn main() +{ #[cfg(target_os = "windows")] let _ = winconsole::window::show(true); #[cfg(target_os = "windows")] @@ -25,17 +41,14 @@ fn main() { init_flags(); // initialize sentry and custom panic handler for msgbox #[cfg(not(debug_assertions))] - let _guard = sentry::init(( - beans_rs::SENTRY_URL, - sentry::ClientOptions { - release: sentry::release_name!(), - debug: flags::has_flag(LaunchFlag::DEBUG_MODE), - max_breadcrumbs: 100, - auto_session_tracking: true, - attach_stacktrace: true, - ..Default::default() - }, - )); + let _guard = sentry::init((beans_rs::SENTRY_URL, sentry::ClientOptions { + release: sentry::release_name!(), + debug: flags::has_flag(LaunchFlag::DEBUG_MODE), + max_breadcrumbs: 100, + auto_session_tracking: true, + attach_stacktrace: true, + ..Default::default() + })); init_panic_handle(); tokio::runtime::Builder::new_multi_thread() @@ -47,7 +60,8 @@ fn main() { }); } -fn init_flags() { +fn init_flags() +{ flags::remove_flag(LaunchFlag::DEBUG_MODE); #[cfg(debug_assertions)] flags::add_flag(LaunchFlag::DEBUG_MODE); @@ -60,7 +74,8 @@ fn init_flags() { beans_rs::logger::log_to_stdout(); } -fn init_panic_handle() { +fn init_panic_handle() +{ std::panic::set_hook(Box::new(move |info| { debug!("[panic::set_hook] showing msgbox to notify user"); let mut x = String::new(); @@ -80,7 +95,8 @@ fn init_panic_handle() { })); } -fn custom_panic_handle(msg: String) { +fn custom_panic_handle(msg: String) +{ unsafe { if !beans_rs::PAUSE_ONCE_DONE { @@ -100,7 +116,8 @@ fn custom_panic_handle(msg: String) { /// should called once the logic flow is done! /// will call `helper::get_input` when `PAUSE_ONCE_DONE` is `true`. -fn logic_done() { +fn logic_done() +{ unsafe { if beans_rs::PAUSE_ONCE_DONE { @@ -109,29 +126,36 @@ fn logic_done() { } } -pub struct Launcher { - /// Output location. When none, `SourceModDirectoryParam::default()` will be used. +pub struct Launcher +{ + /// Output location. When none, `SourceModDirectoryParam::default()` will be + /// used. pub to_location: Option, /// Output of `Command.matches()` - pub root_matches: ArgMatches, + pub root_matches: ArgMatches } -impl Launcher { - /// Create argument for specifying the location where the sourcemods directory is. - fn create_location_arg() -> Arg { +impl Launcher +{ + /// Create argument for specifying the location where the sourcemods + /// directory is. + fn create_location_arg() -> Arg + { Arg::new("location") .long("location") .help("Manually specify sourcemods directory. When not provided, beans-rs will automatically detect the sourcemods directory.") .required(false) } - fn create_confirm_arg() -> Arg { + fn create_confirm_arg() -> Arg + { Arg::new("confirm") .long("confirm") .help("When prompted to do something (as a multi-choice option), the default option will be automatically chosen when this switch is provided, and there is a default multi-choice option available.") .required(false) .action(ArgAction::SetTrue) } - pub async fn run() { + pub async fn run() + { let cmd = Command::new("beans-rs") .version(clap::crate_version!()) .bin_name(clap::crate_name!()) @@ -184,10 +208,11 @@ impl Launcher { } i.subcommand_processor().await; } - pub fn new(matches: &ArgMatches) -> Self { + pub fn new(matches: &ArgMatches) -> Self + { let mut i = Self { to_location: None, - root_matches: matches.clone(), + root_matches: matches.clone() }; i.set_debug(); i.set_no_pause(); @@ -197,8 +222,10 @@ impl Launcher { i } - /// add `LaunchFlag::DEBUG_MODE` to `flags` when the `--debug` parameter flag is used. - pub fn set_debug(&mut self) { + /// add `LaunchFlag::DEBUG_MODE` to `flags` when the `--debug` parameter + /// flag is used. + pub fn set_debug(&mut self) + { if self.root_matches.get_flag("no-debug") { flags::remove_flag(LaunchFlag::DEBUG_MODE); @@ -212,15 +239,18 @@ impl Launcher { trace!("Debug mode enabled"); } } - /// Set `PAUSE_ONCE_DONE` to `false` when `--no-pause` is provided. Otherwise, set it to `true`. - pub fn set_no_pause(&mut self) { + /// Set `PAUSE_ONCE_DONE` to `false` when `--no-pause` is provided. + /// Otherwise, set it to `true`. + pub fn set_no_pause(&mut self) + { unsafe { beans_rs::PAUSE_ONCE_DONE = !self.root_matches.get_flag("no-pause"); } } /// Set `self.to_location` when provided in the arguments. - pub fn find_arg_sourcemods_location(matches: &ArgMatches) -> Option { + pub fn find_arg_sourcemods_location(matches: &ArgMatches) -> Option + { let mut sml_dir_manual: Option = None; if let Some(x) = matches.get_one::("location") { @@ -231,7 +261,8 @@ impl Launcher { } /// main handler for subcommand processing. - pub async fn subcommand_processor(&mut self) { + pub async fn subcommand_processor(&mut self) + { match self.root_matches.clone().subcommand() { Some(("install", i_matches)) => @@ -262,7 +293,8 @@ impl Launcher { } } - pub fn set_prompt_do_whatever(&mut self) { + pub fn set_prompt_do_whatever(&mut self) + { if self.root_matches.get_flag("confirm") { unsafe { @@ -273,16 +305,18 @@ impl Launcher { /// Try and get `SourceModDirectoryParam`. /// Returns SourceModDirectoryParam::default() when `to_location` is `None`. - fn try_get_smdp(&mut self) -> SourceModDirectoryParam { + fn try_get_smdp(&mut self) -> SourceModDirectoryParam + { match &self.to_location { Some(v) => SourceModDirectoryParam::WithLocation(v.to_string()), - None => SourceModDirectoryParam::default(), + None => SourceModDirectoryParam::default() } } /// handler for the `wizard` subcommand. it's also the default subcommand. - pub async fn task_wizard(&mut self) { + pub async fn task_wizard(&mut self) + { let x = self.try_get_smdp(); if let Err(e) = wizard::WizardContext::run(x).await { @@ -296,9 +330,13 @@ impl Launcher { /// handler for the `install` subcommand /// - /// NOTE this function uses `panic!` when `InstallWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. - pub async fn task_install(&mut self, matches: &ArgMatches) { + /// NOTE this function uses `panic!` when `InstallWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. + pub async fn task_install( + &mut self, + matches: &ArgMatches + ) + { self.to_location = Launcher::find_arg_sourcemods_location(matches); if matches.get_flag("confirm") { @@ -352,9 +390,14 @@ impl Launcher { /// handler for the `install` subcommand where the `--target-version` /// parameter is provided. /// - /// NOTE this function uses `expect` on `InstallWorkflow::install_version`. panics are handled - /// and are reported via sentry. - pub async fn task_install_version_specific(&mut self, ctx: RunnerContext, version_str: String) { + /// NOTE this function uses `expect` on `InstallWorkflow::install_version`. + /// panics are handled and are reported via sentry. + pub async fn task_install_version_specific( + &mut self, + ctx: RunnerContext, + version_str: String + ) + { let version = match usize::from_str(&version_str) { Ok(v) => v, @@ -369,7 +412,9 @@ impl Launcher { return; } }; - let mut wf = InstallWorkflow { context: ctx }; + let mut wf = InstallWorkflow { + context: ctx + }; if let Err(e) = wf.install_version(version).await { error!("Failed to run InstallWorkflow::install_version"); @@ -384,9 +429,13 @@ impl Launcher { /// handler for the `verify` subcommand /// - /// NOTE this function uses `panic!` when `VerifyWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. - pub async fn task_verify(&mut self, matches: &ArgMatches) { + /// NOTE this function uses `panic!` when `VerifyWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. + pub async fn task_verify( + &mut self, + matches: &ArgMatches + ) + { self.to_location = Launcher::find_arg_sourcemods_location(matches); let mut ctx = self.try_create_context().await; @@ -402,9 +451,13 @@ impl Launcher { /// handler for the `update` subcommand /// - /// NOTE this function uses `panic!` when `UpdateWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. - pub async fn task_update(&mut self, matches: &ArgMatches) { + /// NOTE this function uses `panic!` when `UpdateWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. + pub async fn task_update( + &mut self, + matches: &ArgMatches + ) + { self.to_location = Launcher::find_arg_sourcemods_location(matches); let mut ctx = self.try_create_context().await; @@ -420,9 +473,10 @@ impl Launcher { /// Handler for the `clean-tmp` subcommand. /// - /// NOTE this function uses `panic!` when `CleanWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. - pub async fn task_clean_tmp(&mut self) { + /// NOTE this function uses `panic!` when `CleanWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. + pub async fn task_clean_tmp(&mut self) + { let mut ctx = self.try_create_context().await; if let Err(e) = CleanWorkflow::wizard(&mut ctx) { @@ -434,12 +488,14 @@ impl Launcher { } } - /// try and create an instance of `RunnerContext` via the `create_auto` method while setting - /// the `sml_via` parameter to the output of `self.try_get_smdp()` + /// try and create an instance of `RunnerContext` via the `create_auto` + /// method while setting the `sml_via` parameter to the output of + /// `self.try_get_smdp()` /// - /// on failure, `panic!` is called. but that's okay because a dialog is shown (in - /// `init_panic_handle`) and the error is reported via sentry. - async fn try_create_context(&mut self) -> RunnerContext { + /// on failure, `panic!` is called. but that's okay because a dialog is + /// shown (in `init_panic_handle`) and the error is reported via sentry. + async fn try_create_context(&mut self) -> RunnerContext + { match RunnerContext::create_auto(self.try_get_smdp()).await { Ok(v) => v, @@ -457,7 +513,8 @@ impl Launcher { } } -fn show_msgbox_error(text: String) { +fn show_msgbox_error(text: String) +{ beans_rs::gui::DialogBuilder::new() .with_title(String::from("beans - Fatal Error!")) .with_icon(DialogIconKind::Error) diff --git a/src/version.rs b/src/version.rs index e904edf..85513d2 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,16 +1,22 @@ -use crate::helper; -use crate::helper::{find_sourcemod_path, InstallType}; -use crate::BeansError; +use std::{backtrace::Backtrace, + collections::HashMap, + fs::read_to_string, + io::Write}; + use futures_util::TryFutureExt; -use log::{debug, error, trace}; -use std::backtrace::Backtrace; -use std::collections::HashMap; -use std::fs::read_to_string; -use std::io::Write; +use log::{debug, + error, + trace}; + +use crate::{helper, + helper::{find_sourcemod_path, + InstallType}, + BeansError}; -/// get the current version installed via the .adastral file in the sourcemod mod folder. -/// will parse the value of `version` as usize. -pub fn get_current_version(sourcemods_location: Option) -> Option { +/// get the current version installed via the .adastral file in the sourcemod +/// mod folder. will parse the value of `version` as usize. +pub fn get_current_version(sourcemods_location: Option) -> Option +{ let install_state = helper::install_state(sourcemods_location.clone()); if install_state != InstallType::Adastral { @@ -32,16 +38,18 @@ pub fn get_current_version(sourcemods_location: Option) -> Option Some(parsed) } - None => None, + None => None } } -fn get_version_location(sourcemods_location: Option) -> Option { +fn get_version_location(sourcemods_location: Option) -> Option +{ get_mod_location(sourcemods_location).map(|v| format!("{}.adastral", v)) } /// get the full location of the sourcemod mod directory. -fn get_mod_location(sourcemods_location: Option) -> Option { +fn get_mod_location(sourcemods_location: Option) -> Option +{ let smp_x = match sourcemods_location { Some(v) => v, @@ -58,13 +66,15 @@ fn get_mod_location(sourcemods_location: Option) -> Option { ); return None; } - }, + } }; Some(helper::join_path(smp_x, crate::data_dir())) } -/// migrate from old file (.revision) to new file (.adastral) in sourcemod mod directory. -pub fn update_version_file(sourcemods_location: Option) -> Result<(), BeansError> { +/// migrate from old file (.revision) to new file (.adastral) in sourcemod mod +/// directory. +pub fn update_version_file(sourcemods_location: Option) -> Result<(), BeansError> +{ let install_state = helper::install_state(sourcemods_location.clone()); match install_state @@ -109,7 +119,7 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be sentry::capture_error(&e); return Err(e); } - }, + } }; let data_dir = helper::join_path(smp_x, crate::data_dir()); @@ -127,7 +137,7 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be sentry::capture_error(&e); return Err(BeansError::VersionFileReadFailure { error: e, - location: old_version_file_location, + location: old_version_file_location }); } }; @@ -144,13 +154,13 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be return Err(BeansError::VersionFileParseFailure { error: e, old_location: old_version_file_location, - old_content: old_version_file_content, + old_content: old_version_file_content }); } }; let new_file_content = AdastralVersionFile { - version: old_version_idx.to_string(), + version: old_version_idx.to_string() }; let new_version_file_location = format!("{}.adastral", &data_dir); @@ -162,7 +172,7 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be sentry::capture_error(&e); return Err(BeansError::VersionFileSerialize { error: e, - instance: new_file_content, + instance: new_file_content }); } }; @@ -173,7 +183,7 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be sentry::capture_error(&e); return Err(BeansError::VersionFileMigrationFailure { error: e, - location: new_version_file_location, + location: new_version_file_location }); } if let Err(e) = std::fs::remove_file(old_version_file_location.clone()) @@ -181,7 +191,7 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be sentry::capture_error(&e); return Err(BeansError::VersionFileMigrationDeleteFailure { error: e, - location: old_version_file_location, + location: old_version_file_location }); } } @@ -190,7 +200,8 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be } /// fetch the version list from `{crate::SOURCE_URL}versions.json` -pub async fn get_version_list() -> Result { +pub async fn get_version_list() -> Result +{ let av = crate::appvar::parse(); let response = match reqwest::get(&av.remote_info.versions_url).await { @@ -204,7 +215,7 @@ pub async fn get_version_list() -> Result { sentry::capture_error(&e); return Err(BeansError::Reqwest { error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } }; @@ -220,12 +231,18 @@ pub async fn get_version_list() -> Result { /// Version file that is used as `.adastral` in the sourcemod mod folder. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AdastralVersionFile { - pub version: String, +pub struct AdastralVersionFile +{ + pub version: String } -impl AdastralVersionFile { - pub fn write(&self, sourcemods_location: Option) -> Result<(), BeansError> { +impl AdastralVersionFile +{ + pub fn write( + &self, + sourcemods_location: Option + ) -> Result<(), BeansError> + { match get_version_location(sourcemods_location) { Some(vl) => @@ -233,7 +250,7 @@ impl AdastralVersionFile { let f = match helper::file_exists(vl.clone()) { true => std::fs::File::create(vl.clone()), - false => std::fs::File::create_new(vl.clone()), + false => std::fs::File::create_new(vl.clone()) }; match f { @@ -244,25 +261,26 @@ impl AdastralVersionFile { Ok(_) => Ok(()), Err(e) => Err(BeansError::FileWriteFailure { location: vl, - error: e, - }), + error: e + }) }, - Err(e) => Err(e.into()), + Err(e) => Err(e.into()) }, Err(e) => Err(BeansError::FileOpenFailure { location: vl, - error: e, - }), + error: e + }) } } - None => Err(BeansError::SourceModLocationNotFound), + None => Err(BeansError::SourceModLocationNotFound) } } } /// Value of the `versions` property in `RemoteVersionResponse` #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RemoteVersion { +pub struct RemoteVersion +{ pub url: Option, pub file: Option, #[serde(rename = "presz")] @@ -272,20 +290,23 @@ pub struct RemoteVersion { #[serde(rename = "signature")] pub signature_url: Option, #[serde(rename = "heal")] - pub heal_url: Option, + pub heal_url: Option } /// `versions.json` response content from remote server. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RemoteVersionResponse { +pub struct RemoteVersionResponse +{ pub versions: HashMap, - pub patches: HashMap, + pub patches: HashMap } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RemotePatch { +pub struct RemotePatch +{ pub url: String, pub file: String, - /// Amount of file space required for temporary file. Assumed to be measured in bytes. - pub tempreq: usize, + /// Amount of file space required for temporary file. Assumed to be measured + /// in bytes. + pub tempreq: usize } diff --git a/src/wizard.rs b/src/wizard.rs index beb57f0..1bb6b69 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -1,20 +1,38 @@ -use crate::flags::LaunchFlag; -use crate::helper::{find_sourcemod_path, parse_location, InstallType}; -use crate::workflows::{CleanWorkflow, InstallWorkflow, UpdateWorkflow, VerifyWorkflow}; -use crate::{depends, flags, helper, BeansError, RunnerContext, SourceModDirectoryParam}; -use async_recursion::async_recursion; -use log::{debug, error, info, trace}; use std::backtrace::Backtrace; +use async_recursion::async_recursion; +use log::{debug, + error, + info, + trace}; + +use crate::{depends, + flags, + flags::LaunchFlag, + helper, + helper::{find_sourcemod_path, + parse_location, + InstallType}, + workflows::{CleanWorkflow, + InstallWorkflow, + UpdateWorkflow, + VerifyWorkflow}, + BeansError, + RunnerContext, + SourceModDirectoryParam}; + #[derive(Debug, Clone)] -pub struct WizardContext { +pub struct WizardContext +{ pub context: RunnerContext, - pub menu_trigger_count: u32, + pub menu_trigger_count: u32 } -impl WizardContext { +impl WizardContext +{ /// run the wizard! - pub async fn run(sml_via: SourceModDirectoryParam) -> Result<(), BeansError> { + pub async fn run(sml_via: SourceModDirectoryParam) -> Result<(), BeansError> + { depends::try_write_deps(); if let Err(e) = depends::try_install_vcredist().await { @@ -56,12 +74,12 @@ impl WizardContext { sourcemod_path: sourcemod_path.clone(), remote_version_list: version_list, current_version: crate::version::get_current_version(Some(sourcemod_path)), - appvar: crate::appvar::parse(), + appvar: crate::appvar::parse() }; let mut i = Self { context: ctx, - menu_trigger_count: 0u32, + menu_trigger_count: 0u32 }; i.menu().await; Ok(()) @@ -70,7 +88,8 @@ impl WizardContext { /// Show the menu /// When an invalid option is selected, this will be re-called. #[async_recursion] - pub async fn menu<'a>(&'a mut self) { + pub async fn menu<'a>(&'a mut self) + { if self.menu_trigger_count == 0 { let av = crate::appvar::AppVarData::get(); @@ -119,7 +138,8 @@ impl WizardContext { } }; } - fn menu_error_catch(v: Result<(), BeansError>) { + fn menu_error_catch(v: Result<(), BeansError>) + { if let Err(e) = v { let b = Backtrace::capture(); @@ -129,21 +149,25 @@ impl WizardContext { } /// Install the target game. - pub async fn task_install(&mut self) -> Result<(), BeansError> { + pub async fn task_install(&mut self) -> Result<(), BeansError> + { InstallWorkflow::wizard(&mut self.context).await } /// Check for any updates, and if there are any, we install them. - pub async fn task_update(&mut self) -> Result<(), BeansError> { + pub async fn task_update(&mut self) -> Result<(), BeansError> + { UpdateWorkflow::wizard(&mut self.context).await } /// Verify the current data for the target sourcemod. - pub async fn task_verify(&mut self) -> Result<(), BeansError> { + pub async fn task_verify(&mut self) -> Result<(), BeansError> + { VerifyWorkflow::wizard(&mut self.context).await } } -fn get_path() -> String { +fn get_path() -> String +{ find_sourcemod_path().unwrap_or_else(|e| { error!("[get_path] Failed to automatically detect sourcemods folder!"); debug!("{:#?}", e); @@ -151,7 +175,8 @@ fn get_path() -> String { }) } -fn prompt_sourcemod_location() -> String { +fn prompt_sourcemod_location() -> String +{ let res = helper::get_input("Please provide your sourcemods folder, then press enter."); if !helper::file_exists(res.clone()) { diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index b7ffeb6..e11f139 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -1,13 +1,20 @@ -use crate::{helper, BeansError, RunnerContext}; -use log::{info, warn}; +use log::{info, + warn}; + +use crate::{helper, + BeansError, + RunnerContext}; #[derive(Debug, Clone)] -pub struct CleanWorkflow { - pub context: RunnerContext, +pub struct CleanWorkflow +{ + pub context: RunnerContext } -impl CleanWorkflow { - pub fn wizard(_ctx: &mut RunnerContext) -> Result<(), BeansError> { +impl CleanWorkflow +{ + pub fn wizard(_ctx: &mut RunnerContext) -> Result<(), BeansError> + { let target_directory = helper::get_tmp_dir(); info!("[CleanWorkflow] Cleaning up {}", target_directory); @@ -21,7 +28,7 @@ impl CleanWorkflow { { return Err(BeansError::CleanTempFailure { location: target_directory, - error: e, + error: e }); } @@ -30,7 +37,7 @@ impl CleanWorkflow { { return Err(BeansError::DirectoryCreateFailure { location: target_directory, - error: e, + error: e }); } diff --git a/src/workflows/install.rs b/src/workflows/install.rs index b4e8397..13b7fc5 100644 --- a/src/workflows/install.rs +++ b/src/workflows/install.rs @@ -1,15 +1,25 @@ -use crate::appvar::AppVarData; -use crate::version::{AdastralVersionFile, RemoteVersion}; -use crate::BeansError; -use crate::{helper, DownloadFailureReason, RunnerContext}; -use log::{debug, error, info, warn}; +use log::{debug, + error, + info, + warn}; + +use crate::{appvar::AppVarData, + helper, + version::{AdastralVersionFile, + RemoteVersion}, + BeansError, + DownloadFailureReason, + RunnerContext}; #[derive(Debug, Clone)] -pub struct InstallWorkflow { - pub context: RunnerContext, +pub struct InstallWorkflow +{ + pub context: RunnerContext } -impl InstallWorkflow { - pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { +impl InstallWorkflow +{ + pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> + { let (latest_remote_id, latest_remote) = ctx.latest_remote_version(); if let Some(_cv) = ctx.current_version { @@ -19,12 +29,15 @@ impl InstallWorkflow { Self::install_with_remote_version(ctx, latest_remote_id, latest_remote).await } - /// Prompt the user to confirm if they want to reinstall (when parameter `current_version` is Some) + /// Prompt the user to confirm if they want to reinstall (when parameter + /// `current_version` is Some) /// /// Will always return `true` when `crate::PROMPT_DO_WHATEVER` is `true`. /// - /// Returns: `true` when the installation should continue, `false` when we should silently abort. - pub fn prompt_confirm(current_version: Option) -> bool { + /// Returns: `true` when the installation should continue, `false` when we + /// should silently abort. + pub fn prompt_confirm(current_version: Option) -> bool + { unsafe { if crate::PROMPT_DO_WHATEVER { @@ -64,7 +77,11 @@ impl InstallWorkflow { } /// Install the specified version by its ID to the output directory. - pub async fn install_version(&mut self, version_id: usize) -> Result<(), BeansError> { + pub async fn install_version( + &mut self, + version_id: usize + ) -> Result<(), BeansError> + { let target_version = match self.context.remote_version_list.versions.get(&version_id) { Some(v) => v, @@ -72,7 +89,7 @@ impl InstallWorkflow { { error!("Could not find remote version {version_id}"); return Err(BeansError::RemoteVersionNotFound { - version: Some(version_id), + version: Some(version_id) }); } }; @@ -83,13 +100,15 @@ impl InstallWorkflow { /// Install with a specific remote version. /// - /// Note: Will call Self::prompt_confirm, so set `crate::PROMPT_DO_WHATEVER` to `true` before you call - /// this function if you don't want to wait for a newline from stdin. + /// Note: Will call Self::prompt_confirm, so set `crate::PROMPT_DO_WHATEVER` + /// to `true` before you call this function if you don't want to + /// wait for a newline from stdin. pub async fn install_with_remote_version( ctx: &mut RunnerContext, version_id: usize, - version: RemoteVersion, - ) -> Result<(), BeansError> { + version: RemoteVersion + ) -> Result<(), BeansError> + { if !Self::prompt_confirm(ctx.current_version) { info!("[InstallWorkflow] Operation aborted by user"); @@ -104,7 +123,7 @@ impl InstallWorkflow { Self::install_from( presz_loc.clone(), ctx.sourcemod_path.clone(), - Some(version_id), + Some(version_id) ) .await?; if helper::file_exists(presz_loc.clone()) @@ -117,20 +136,22 @@ impl InstallWorkflow { /// Install the `.tar.zstd` file at `package_loc` to `out_dir` /// package_loc: Location to a file that is a `.tar.zstd` file. /// out_dir: should be `RunnerContext.sourcemod_path` - /// version_id: Version that is from `package_loc`. When not specified, `.adastral` will not be written to. - /// Note: This function doesn't check the extension when extracting. + /// version_id: Version that is from `package_loc`. When not specified, + /// `.adastral` will not be written to. Note: This function doesn't + /// check the extension when extracting. pub async fn install_from( package_loc: String, out_dir: String, - version_id: Option, - ) -> Result<(), BeansError> { + version_id: Option + ) -> Result<(), BeansError> + { if !helper::file_exists(package_loc.clone()) { error!("[InstallWorkflow::Wizard] Failed to find package! (location: {package_loc})"); return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::FileNotFound { - location: package_loc.clone(), - }, + location: package_loc.clone() + } }); } @@ -139,7 +160,7 @@ impl InstallWorkflow { if let Some(lri) = version_id { let x = AdastralVersionFile { - version: lri.to_string(), + version: lri.to_string() } .write(Some(out_dir.clone())); if let Err(e) = x diff --git a/src/workflows/update.rs b/src/workflows/update.rs index 0ce2452..7db5729 100644 --- a/src/workflows/update.rs +++ b/src/workflows/update.rs @@ -1,11 +1,19 @@ -use crate::{butler, helper, BeansError, RunnerContext}; -use log::{debug, info}; +use log::{debug, + info}; -pub struct UpdateWorkflow { - pub ctx: RunnerContext, +use crate::{butler, + helper, + BeansError, + RunnerContext}; + +pub struct UpdateWorkflow +{ + pub ctx: RunnerContext } -impl UpdateWorkflow { - pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { +impl UpdateWorkflow +{ + pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> + { let av = crate::appvar::parse(); let current_version_id = match ctx.current_version @@ -81,7 +89,7 @@ impl UpdateWorkflow { "{}{}", &av.remote_info.base_url, remote_version.heal_url.unwrap() - ), + ) ) { sentry::capture_error(&e); @@ -93,7 +101,7 @@ impl UpdateWorkflow { format!("{}{}", &av.remote_info.base_url, patch.file), staging_dir_location, patch.file, - mod_dir_location, + mod_dir_location ) .await { diff --git a/src/workflows/verify.rs b/src/workflows/verify.rs index f0adab0..4802043 100644 --- a/src/workflows/verify.rs +++ b/src/workflows/verify.rs @@ -1,11 +1,17 @@ -use crate::version::RemoteVersion; -use crate::{butler, helper, BeansError, RunnerContext}; +use crate::{butler, + helper, + version::RemoteVersion, + BeansError, + RunnerContext}; -pub struct VerifyWorkflow { - pub ctx: RunnerContext, +pub struct VerifyWorkflow +{ + pub ctx: RunnerContext } -impl VerifyWorkflow { - pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { +impl VerifyWorkflow +{ + pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> + { let av = crate::appvar::parse(); let current_version_id = match ctx.current_version @@ -50,7 +56,7 @@ impl VerifyWorkflow { remote.signature_url.unwrap() ), mod_dir_location.clone(), - format!("{}{}", &av.remote_info.base_url, remote.heal_url.unwrap()), + format!("{}{}", &av.remote_info.base_url, remote.heal_url.unwrap()) )?; println!("[VerifyWorkflow::wizard] The verification process has completed, and any corruption has been repaired."); ctx.gameinfo_perms()?; From 4920f8c8128b4e7838cf302680f023b629944598 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 30 Jul 2024 16:17:21 +0800 Subject: [PATCH 43/65] Backport rustfmt.toml from develop branch. --- build.rs | 17 +- rustfmt.toml | 29 ++ src/appvar.rs | 103 ++++-- src/butler.rs | 79 +++-- src/ctx.rs | 229 +++++++++---- src/depends.rs | 100 ++++-- src/error.rs | 181 ++++++---- src/flags.rs | 23 +- src/helper/linux.rs | 63 ++-- src/helper/mod.rs | 675 ++++++++++++++++++++++++------------- src/helper/windows.rs | 39 ++- src/lib.rs | 13 +- src/logger.rs | 111 +++--- src/main.rs | 330 ++++++++++++------ src/version.rs | 218 ++++++++---- src/wizard.rs | 111 +++--- src/workflows/clean.rs | 26 +- src/workflows/install.rs | 146 +++++--- src/workflows/mod.rs | 8 +- src/workflows/uninstall.rs | 43 ++- src/workflows/update.rs | 73 +++- src/workflows/verify.rs | 53 ++- 22 files changed, 1779 insertions(+), 891 deletions(-) create mode 100644 rustfmt.toml diff --git a/build.rs b/build.rs index 2cfaf2c..06847e1 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,7 @@ -#[allow(dead_code, unused_macros, unused_imports)] +#[allow(dead_code, unused_macros, unused_imports)] +use std::{env, + io}; - -use std::{env, io}; #[cfg(target_os = "windows")] use winres::WindowsResource; #[allow(unused_macros)] @@ -13,9 +13,11 @@ macro_rules! print { pub const OVERRIDE_ICON_LOCATION: Option<&'static str> = option_env!("ICON_LOCATION"); pub const RUST_FLAGS: Option<&'static str> = option_env!("RUSTFLAGS"); #[cfg(target_os = "windows")] -fn main() -> io::Result<()> { +fn main() -> io::Result<()> +{ let icon_location = OVERRIDE_ICON_LOCATION.unwrap_or("icon.ico"); - if env::var_os("CARGO_CFG_WINDOWS").is_some() { + if env::var_os("CARGO_CFG_WINDOWS").is_some() + { if !path_exists(icon_location.to_string()) { print!("icon.ico not found. Not embedding icon"); @@ -34,7 +36,8 @@ fn main() -> io::Result<()> { Ok(()) } #[cfg(not(target_os = "windows"))] -fn main() -> io::Result<()> { +fn main() -> io::Result<()> +{ Ok(()) } #[allow(dead_code)] @@ -42,4 +45,4 @@ fn path_exists(path: String) -> bool { let p = std::path::Path::new(path.as_str()); return p.exists(); -} \ No newline at end of file +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..9095dbb --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,29 @@ +brace_style="AlwaysNextLine" +combine_control_expr=false +condense_wildcard_suffixes=true +control_brace_style="AlwaysNextLine" +empty_item_single_line=false +fn_params_layout="Vertical" +fn_single_line=false +force_explicit_abi=true +format_code_in_doc_comments=true +format_generated_files=false +hard_tabs=false +hex_literal_case="Upper" +imports_indent="Visual" +imports_layout="Vertical" +indent_style="Block" +inline_attribute_width=0 +imports_granularity="Crate" +normalize_comments=true +overflow_delimited_expr=true +group_imports="StdExternalCrate" +single_line_let_else_max_width=0 +space_after_colon=true +space_before_colon=false +struct_lit_single_line=false +tab_spaces=4 +trailing_comma="Never" +trailing_semicolon=true +where_single_line=false +wrap_comments=true \ No newline at end of file diff --git a/src/appvar.rs b/src/appvar.rs index 67edd3c..eb23607 100644 --- a/src/appvar.rs +++ b/src/appvar.rs @@ -1,7 +1,11 @@ use std::sync::RwLock; -use log::{debug, error, trace}; -use crate::BeansError; + use lazy_static::lazy_static; +use log::{debug, + error, + trace}; + +use crate::BeansError; /// Default `appvar.json` to use. pub const JSON_DATA_DEFAULT: &str = include_str!("appvar.json"); @@ -17,7 +21,6 @@ pub fn parse() -> AppVarData AppVarData::get() } - /// Configuration for the compiled application. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AppVarData @@ -27,29 +30,38 @@ pub struct AppVarData #[serde(rename = "remote")] pub remote_info: AppVarRemote } -impl AppVarData { - /// Parse `JSON_DATA` to AppVarData. Should only be called by `reset_appvar()`. +impl AppVarData +{ + /// Parse `JSON_DATA` to AppVarData. Should only be called by + /// `reset_appvar()`. /// - /// NOTE panics when `serde_json::from_str()` is Err, or when `JSON_DATA.read()` is Err. - /// REMARKS does not set `AVD_INSTANCE` to generated data, since this is only done by - /// `AppVarData::reset()`. - pub fn parse() -> Self { + /// NOTE panics when `serde_json::from_str()` is Err, or when + /// `JSON_DATA.read()` is Err. REMARKS does not set `AVD_INSTANCE` to + /// generated data, since this is only done by `AppVarData::reset()`. + pub fn parse() -> Self + { debug!("[AppVarData::parse] trying to get JSON_DATA"); let x = JSON_DATA.read(); - if let Ok(data) = x { + if let Ok(data) = x + { debug!("[AppVarData::parse] JSON_DATA= {:#?}", data); return serde_json::from_str(&data).expect("Failed to deserialize JSON_DATA"); } - if let Err(e) = x { + if let Err(e) = x + { panic!("[AppVarData::parse] Failed to read JSON_DATA {:#?}", e); } unreachable!(); } /// Substitute values in the `source` string for what is defined in here. - pub fn sub(&self, source: String) -> String + pub fn sub( + &self, + source: String + ) -> String { - source.clone() + source + .clone() .replace("$MOD_NAME_STYLIZED", &self.mod_info.name_stylized) .replace("$MOD_NAME_SHORT", &self.mod_info.short_name) .replace("$MOD_NAME", &self.mod_info.sourcemod_name) @@ -61,16 +73,20 @@ impl AppVarData { /// Otherwise, when it's none, we return `AppVarData::reset()` /// /// NOTE this function panics when Err on `AVD_INSTANCE.read()`. - pub fn get() -> Self { + pub fn get() -> Self + { let avd_read = AVD_INSTANCE.read(); - if let Ok(v) = avd_read { + if let Ok(v) = avd_read + { let vc = v.clone(); - if let Some(x) = vc { + if let Some(x) = vc + { debug!("[AppVarData::get] Instance exists in AVD_INSTANCE, so lets return that."); return x; } } - else if let Err(e) = avd_read { + else if let Err(e) = avd_read + { panic!("[AppVarData::get] Failed to read AVD_INSTANCE {:#?}", e); } @@ -80,15 +96,22 @@ impl AppVarData { /// Set the content of `AVD_INSTANCE` to the result of `AppVarData::parse()` /// /// NOTE this function panics when Err on `AVD_INSTANCE.write()` - pub fn reset() -> Self { + pub fn reset() -> Self + { let instance = AppVarData::parse(); - match AVD_INSTANCE.write() { - Ok(mut data) => { + match AVD_INSTANCE.write() + { + Ok(mut data) => + { *data = Some(instance.clone()); - debug!("[reset_appvar] set content of AVD_INSTANCE to {:#?}", instance); - }, - Err(e) => { + debug!( + "[reset_appvar] set content of AVD_INSTANCE to {:#?}", + instance + ); + } + Err(e) => + { panic!("[reset_appvar] Failed to set AVD_INSTANCE! {:#?}", e); } } @@ -96,27 +119,33 @@ impl AppVarData { instance } - - /// Serialize `data` into JSON, then set the content of `JSON_DATA` to the serialize content. - /// Once that is done, `AppVarData::reset()` will be called. + /// Serialize `data` into JSON, then set the content of `JSON_DATA` to the + /// serialize content. Once that is done, `AppVarData::reset()` will be + /// called. /// - /// If `serde_json::to_string` fails, an error is printed in console and `sentry::capture_error` - /// is called. - pub fn set_json_data(data: AppVarData) - -> Result<(), BeansError> + /// If `serde_json::to_string` fails, an error is printed in console and + /// `sentry::capture_error` is called. + pub fn set_json_data(data: AppVarData) -> Result<(), BeansError> { debug!("[set_json_data] {:#?}", data); - match serde_json::to_string(&data) { - Ok(v) => { - if let Ok(mut ms) = JSON_DATA.write() { + match serde_json::to_string(&data) + { + Ok(v) => + { + if let Ok(mut ms) = JSON_DATA.write() + { *ms = v.to_string(); debug!("[set_json_data] successfully set data, calling reset_appvar()"); } Self::reset(); Ok(()) - }, - Err(e) => { - error!("[appvar::set_json_data] Failed to serialize data to string! {:}", e); + } + Err(e) => + { + error!( + "[appvar::set_json_data] Failed to serialize data to string! {:}", + e + ); debug!("{:#?}", e); sentry::capture_error(&e); @@ -151,4 +180,4 @@ pub struct AppVarRemote /// url where the version details are stored. /// e.g; `https://beans.adastral.net/versions.json` pub versions_url: String -} \ No newline at end of file +} diff --git a/src/butler.rs b/src/butler.rs index 668e781..b668114 100644 --- a/src/butler.rs +++ b/src/butler.rs @@ -1,13 +1,21 @@ -use std::backtrace::Backtrace; -use std::process::ExitStatus; -use log::{debug, error, info}; -use crate::{BeansError, depends, DownloadFailureReason, helper}; +use std::{backtrace::Backtrace, + process::ExitStatus}; + +use log::{debug, + error, + info}; + +use crate::{depends, + helper, + BeansError, + DownloadFailureReason}; pub fn verify( signature_url: String, gamedir: String, remote: String -) -> Result { +) -> Result +{ let mut cmd = std::process::Command::new(&depends::get_butler_location()); cmd.args([ "verify", @@ -16,22 +24,23 @@ pub fn verify( format!("--heal=archive,{}", remote).as_str() ]); debug!("[butler::verify] {:#?}", cmd); - match cmd - .spawn() { - Err(e) => { - Err(BeansError::ButlerVerifyFailure { - signature_url, - gamedir, - remote, - error: e, - backtrace: Backtrace::capture() - }) - }, - Ok(mut v) => { + match cmd.spawn() + { + Err(e) => Err(BeansError::ButlerVerifyFailure { + signature_url, + gamedir, + remote, + error: e, + backtrace: Backtrace::capture() + }), + Ok(mut v) => + { let w = v.wait()?; debug!("[butler::verify] Exited with {:#?}", w); - if let Some(c) = w.code() { - if c != 0 { + if let Some(c) = w.code() + { + if c != 0 + { error!("[butler::verify] exited with code {c}, which isn't good!"); panic!("[butler::verify] exited with code {c}"); } @@ -45,15 +54,18 @@ pub async fn patch_dl( staging_dir: String, patch_filename: String, gamedir: String -) -> Result { - if helper::file_exists(staging_dir.clone()) { +) -> Result +{ + if helper::file_exists(staging_dir.clone()) + { std::fs::remove_dir_all(&staging_dir)?; } let tmp_file = helper::get_tmp_file(patch_filename); info!("[butler::patch_dl] downloading {} to {}", dl_url, tmp_file); helper::download_with_progress(dl_url, tmp_file.clone()).await?; - if helper::file_exists(tmp_file.clone()) == false { + if helper::file_exists(tmp_file.clone()) == false + { return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::FileNotFound { location: tmp_file @@ -68,7 +80,8 @@ pub fn patch( patchfile_location: String, staging_dir: String, gamedir: String -) -> Result { +) -> Result +{ let mut cmd = std::process::Command::new(&depends::get_butler_location()); cmd.args([ "apply", @@ -77,9 +90,10 @@ pub fn patch( &gamedir ]); debug!("[butler::patch] {:#?}", &cmd); - match cmd - .spawn() { - Err(e) => { + match cmd.spawn() + { + Err(e) => + { let xe = BeansError::ButlerPatchFailure { patchfile_location, gamedir, @@ -89,12 +103,15 @@ pub fn patch( error!("[butler::patch] {:#?}", xe); sentry::capture_error(&xe); Err(xe) - }, - Ok(mut v) => { + } + Ok(mut v) => + { let w = v.wait()?; debug!("Exited with {:#?}", w); - if let Some(c) = w.code() { - if c != 0 { + if let Some(c) = w.code() + { + if c != 0 + { error!("[butler::patch] exited with code {c}, which isn't good!"); panic!("[butler::patch] exited with code {c}"); } @@ -102,4 +119,4 @@ pub fn patch( Ok(w) } } -} \ No newline at end of file +} diff --git a/src/ctx.rs b/src/ctx.rs index 0056547..5712218 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -1,10 +1,22 @@ use std::backtrace::Backtrace; -use crate::{BeansError, depends, helper, version}; -use crate::helper::{find_sourcemod_path, InstallType, parse_location}; -use crate::version::{RemotePatch, RemoteVersion, RemoteVersionResponse}; #[cfg(target_os = "linux")] use std::os::unix::fs::PermissionsExt; -use log::{debug, error, info, trace}; + +use log::{debug, + error, + info, + trace}; + +use crate::{depends, + helper, + helper::{find_sourcemod_path, + parse_location, + InstallType}, + version, + version::{RemotePatch, + RemoteVersion, + RemoteVersionResponse}, + BeansError}; #[derive(Debug, Clone)] pub struct RunnerContext @@ -19,34 +31,47 @@ impl RunnerContext pub async fn create_auto(sml_via: SourceModDirectoryParam) -> Result { depends::try_write_deps(); - if let Err(e) = depends::try_install_vcredist().await { + if let Err(e) = depends::try_install_vcredist().await + { sentry::capture_error(&e); println!("Failed to install vcredist! {:}", e); - debug!("[RunnerContext::create_auto] Failed to install vcredist! {:#?}", e); + debug!( + "[RunnerContext::create_auto] Failed to install vcredist! {:#?}", + e + ); } let sourcemod_path = parse_location(match sml_via { - SourceModDirectoryParam::AutoDetect => match find_sourcemod_path() { + SourceModDirectoryParam::AutoDetect => match find_sourcemod_path() + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); - debug!("[RunnerContext::create_auto] Failed to find sourcemods folder. {:#?}", e); + debug!( + "[RunnerContext::create_auto] Failed to find sourcemods folder. {:#?}", + e + ); return Err(BeansError::SourceModLocationNotFound); } }, - SourceModDirectoryParam::WithLocation(l) => { - debug!("[RunnerContext::create_auto] Using specified location {}", l); + SourceModDirectoryParam::WithLocation(l) => + { + debug!( + "[RunnerContext::create_auto] Using specified location {}", + l + ); l } }); let version_list = version::get_version_list().await?; - if helper::install_state(Some(sourcemod_path.clone())) == InstallType::OtherSource { + if helper::install_state(Some(sourcemod_path.clone())) == InstallType::OtherSource + { version::update_version_file(Some(sourcemod_path.clone()))?; } - return Ok(Self - { + return Ok(Self { sourcemod_path: parse_location(sourcemod_path.clone()), remote_version_list: version_list, current_version: crate::version::get_current_version(Some(sourcemod_path.clone())), @@ -62,8 +87,9 @@ impl RunnerContext /// Get the location of the sourcemod mod /// {sourcemod_dir}{crate::DATA_DIR} - /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/sourcemods/open_fortress/ - /// C:\Games\Steam\steamapps\sourcemods\open_fortress\ + /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/ + /// steamapps/sourcemods/open_fortress/ C:\Games\Steam\steamapps\ + /// sourcemods\open_fortress\ pub fn get_mod_location(&mut self) -> String { helper::join_path(self.sourcemod_path.clone(), crate::data_dir()) @@ -71,9 +97,11 @@ impl RunnerContext /// Get staging location for butler. /// {sourcemod_dir}{crate::STAGING_DIR} - /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/sourcemods/butler-staging - /// C:\Games\Steam\steamapps\sourcemods\butler-staging - pub fn get_staging_location(&mut self) -> String { + /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/ + /// steamapps/sourcemods/butler-staging C:\Games\Steam\steamapps\ + /// sourcemods\butler-staging + pub fn get_staging_location(&mut self) -> String + { helper::join_path(self.sourcemod_path.clone(), crate::STAGING_DIR.to_string()) } @@ -81,8 +109,10 @@ impl RunnerContext pub fn latest_remote_version(&mut self) -> (usize, RemoteVersion) { let mut highest = usize::MIN; - for (key, _) in self.remote_version_list.clone().versions.into_iter() { - if key > highest { + for (key, _) in self.remote_version_list.clone().versions.into_iter() + { + if key > highest + { highest = key; } } @@ -91,55 +121,72 @@ impl RunnerContext } /// Get the RemoteVersion that matches `self.current_version` - pub fn current_remote_version(&mut self) -> Result { - match self.current_version { - Some(cv) => { - for (v, i) in self.remote_version_list.clone().versions.into_iter() { - if v == cv { + pub fn current_remote_version(&mut self) -> Result + { + match self.current_version + { + Some(cv) => + { + for (v, i) in self.remote_version_list.clone().versions.into_iter() + { + if v == cv + { return Ok(i.clone()); } } return Err(BeansError::RemoteVersionNotFound { version: self.current_version }); - }, - None => { - Err(BeansError::RemoteVersionNotFound { - version: self.current_version - }) } + None => Err(BeansError::RemoteVersionNotFound { + version: self.current_version + }) } } - /// When self.current_version is some, iterate through patches and fetch the patch that is available - /// to bring the current version in-line with the latest version. + /// When self.current_version is some, iterate through patches and fetch the + /// patch that is available to bring the current version in-line with + /// the latest version. pub fn has_patch_available(&mut self) -> Option { let current_version = self.current_version.clone(); let (remote_version, _) = self.latest_remote_version(); - match current_version { - Some(cv) => { - for (_, patch) in self.remote_version_list.clone().patches.into_iter() { - if patch.file == format!("{}-{}to{}.pwr", &self.appvar.mod_info.short_name, cv, remote_version) { + match current_version + { + Some(cv) => + { + for (_, patch) in self.remote_version_list.clone().patches.into_iter() + { + if patch.file + == format!( + "{}-{}to{}.pwr", + &self.appvar.mod_info.short_name, cv, remote_version + ) + { return Some(patch); } } return None; - }, + } _ => None } } - /// Read the contents of `gameinfo.txt` in directory from `self.get_mod_location()` - pub fn read_gameinfo_file(&mut self) -> Result>, BeansError> { + /// Read the contents of `gameinfo.txt` in directory from + /// `self.get_mod_location()` + pub fn read_gameinfo_file(&mut self) -> Result>, BeansError> + { self.gameinfo_perms()?; let location = self.gameinfo_location(); - if helper::file_exists(location.clone()) == false { + if helper::file_exists(location.clone()) == false + { return Ok(None); } - let file = match std::fs::read(&location) { + let file = match std::fs::read(&location) + { Ok(v) => v, - Err(e) => { + Err(e) => + { let ex = BeansError::GameInfoFileReadFail { error: e, location: location, @@ -152,20 +199,25 @@ impl RunnerContext Ok(Some(file)) } - /// Get the location of `gameinfo.txt` inside of the folder returned by `self.get_mod_location()` - pub fn gameinfo_location(&mut self) -> String { - let mut location = self.get_mod_location(); + /// Get the location of `gameinfo.txt` inside of the folder returned by + /// `self.get_mod_location()` + pub fn gameinfo_location(&mut self) -> String + { + let mut location = self.get_mod_location(); location.push_str("gameinfo.txt"); location } /// Make sure that the permissions for gameinfo.txt on linux are 0644 #[cfg(target_os = "linux")] - pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> { + pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> + { let location = self.gameinfo_location(); - if helper::file_exists(location.clone()) { + if helper::file_exists(location.clone()) + { let perm = std::fs::Permissions::from_mode(0o644 as u32); - if let Err(e) = std::fs::set_permissions(&location, perm.clone()) { + if let Err(e) = std::fs::set_permissions(&location, perm.clone()) + { let xe = BeansError::GameInfoPermissionSetFail { error: e, permissions: perm.clone(), @@ -174,12 +226,16 @@ impl RunnerContext sentry::capture_error(&xe); return Err(xe); } - debug!("[RunnerContext::gameinfo_perms] set permissions on {location} to {:#?}", perm); + debug!( + "[RunnerContext::gameinfo_perms] set permissions on {location} to {:#?}", + perm + ); } Ok(()) } #[cfg(not(target_os = "linux"))] - pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> { + pub fn gameinfo_perms(&mut self) -> Result<(), BeansError> + { Ok(()) } @@ -190,8 +246,10 @@ impl RunnerContext let av = crate::appvar::parse(); let mut out_loc = helper::get_tmp_dir(); - if let Some(size) = version.pre_sz { - if helper::has_free_space(out_loc.clone(), size)? == false { + if let Some(size) = version.pre_sz + { + if helper::has_free_space(out_loc.clone(), size)? == false + { panic!("Not enough free space to install latest version!"); } } @@ -201,15 +259,24 @@ impl RunnerContext info!("[RunnerContext::download_package] writing to {}", out_loc); helper::download_with_progress( - format!("{}{}", &av.remote_info.base_url, version.file.expect("No URL for latest package!")), - out_loc.clone()).await?; + format!( + "{}{}", + &av.remote_info.base_url, + version.file.expect("No URL for latest package!") + ), + out_loc.clone() + ) + .await?; Ok(out_loc) } /// Extract zstd_location to the detected sourcemods directory. /// TODO replace unwrap/expect with match error handling - pub fn extract_package(zstd_location: String, out_dir: String) -> Result<(), BeansError> + pub fn extract_package( + zstd_location: String, + out_dir: String + ) -> Result<(), BeansError> { let tar_tmp_location = helper::get_tmp_file("data.tar".to_string()); @@ -220,15 +287,25 @@ impl RunnerContext let mut archive = tar::Archive::new(&tar_tmp_file); let x = archive.unpack(&out_dir); - if helper::file_exists(tar_tmp_location.clone()) { - if let Err(e) = std::fs::remove_file(tar_tmp_location.clone()) { + if helper::file_exists(tar_tmp_location.clone()) + { + if let Err(e) = std::fs::remove_file(tar_tmp_location.clone()) + { sentry::capture_error(&e); - error!("[RunnerContext::extract_package] Failed to delete temporary file: {:}", e); - debug!("[RunnerContext::extract_package] Failed to delete {}\n{:#?}", tar_tmp_location, e); + error!( + "[RunnerContext::extract_package] Failed to delete temporary file: {:}", + e + ); + debug!( + "[RunnerContext::extract_package] Failed to delete {}\n{:#?}", + tar_tmp_location, e + ); } } - match x { - Err(e) => { + match x + { + Err(e) => + { let xe = BeansError::TarExtractFailure { src_file: tar_tmp_location, target_dir: out_dir, @@ -238,7 +315,7 @@ impl RunnerContext trace!("[RunnerContext::extract_package] {:}\n{:#?}", xe, xe); sentry::capture_error(&xe); return Err(xe); - }, + } Ok(_) => Ok(()) } } @@ -246,14 +323,21 @@ impl RunnerContext #[cfg(target_os = "linux")] pub fn prepare_symlink(&mut self) -> Result<(), BeansError> { - for pair in SYMLINK_FILES.into_iter() { + for pair in SYMLINK_FILES.into_iter() + { let target: &str = pair[1]; let mod_location = self.get_mod_location(); let ln_location = format!("{}{}", mod_location, target); if helper::file_exists(ln_location.clone()) - && helper::is_symlink(ln_location.clone()) == false { - if let Err(e) = std::fs::remove_file(&ln_location) { - trace!("[RunnerContext::prepare_symlink] failed to remove {}\n{:#?}", ln_location, e); + && helper::is_symlink(ln_location.clone()) == false + { + if let Err(e) = std::fs::remove_file(&ln_location) + { + trace!( + "[RunnerContext::prepare_symlink] failed to remove {}\n{:#?}", + ln_location, + e + ); return Err(e.into()); } } @@ -269,10 +353,8 @@ impl RunnerContext } } -pub const SYMLINK_FILES: &'static [&'static [&'static str; 2]] = &[ - &["bin/server.so", "bin/server_srv.so"] -]; - +pub const SYMLINK_FILES: &'static [&'static [&'static str; 2]] = + &[&["bin/server.so", "bin/server_srv.so"]]; #[derive(Clone, Debug)] pub enum SourceModDirectoryParam @@ -284,7 +366,8 @@ pub enum SourceModDirectoryParam } impl Default for SourceModDirectoryParam { - fn default() -> Self { + fn default() -> Self + { SourceModDirectoryParam::AutoDetect } -} \ No newline at end of file +} diff --git a/src/depends.rs b/src/depends.rs index 86f311c..0af3581 100644 --- a/src/depends.rs +++ b/src/depends.rs @@ -1,9 +1,16 @@ -#[cfg(not(target_os = "windows"))] -use std::os::unix::fs::PermissionsExt; #[cfg(target_os = "windows")] use std::backtrace::Backtrace; -use crate::{BeansError, BUTLER_BINARY, BUTLER_LIB_1, BUTLER_LIB_2, helper}; -use log::{debug, error}; +#[cfg(not(target_os = "windows"))] +use std::os::unix::fs::PermissionsExt; + +use log::{debug, + error}; + +use crate::{helper, + BeansError, + BUTLER_BINARY, + BUTLER_LIB_1, + BUTLER_LIB_2}; /// try and write aria2c and butler if it doesn't exist /// paths that are used will be fetched from binary_locations() @@ -13,20 +20,33 @@ pub fn try_write_deps() safe_write_file(get_butler_1_location().as_str(), &**BUTLER_LIB_1); safe_write_file(get_butler_2_location().as_str(), &**BUTLER_LIB_2); #[cfg(not(target_os = "windows"))] - if helper::file_exists(get_butler_location()) { + if helper::file_exists(get_butler_location()) + { let p = std::fs::Permissions::from_mode(0744 as u32); - if let Err(e) = std::fs::set_permissions(&get_butler_location(), p) { + if let Err(e) = std::fs::set_permissions(&get_butler_location(), p) + { sentry::capture_error(&e); - error!("[depends::try_write_deps] Failed to set permissions for {}", get_butler_location()); + error!( + "[depends::try_write_deps] Failed to set permissions for {}", + get_butler_location() + ); error!("[depends::try_write_deps] {:#?}", e); } - debug!("[depends::try_write_deps] set perms on {}", get_butler_location()); + debug!( + "[depends::try_write_deps] set perms on {}", + get_butler_location() + ); } } -fn safe_write_file(location: &str, data: &[u8]) { +fn safe_write_file( + location: &str, + data: &[u8] +) +{ if !helper::file_exists(location.to_string()) { - if let Err(e) = std::fs::write(&location, data) { + if let Err(e) = std::fs::write(&location, data) + { sentry::capture_error(&e); error!("[depends::try_write_deps] failed to extract {}", location); error!("[depends::try_write_deps] {:#?}", e); @@ -50,16 +70,22 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> #[cfg(target_os = "windows")] pub async fn try_install_vcredist() -> Result<(), BeansError> { - if !match winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE).open_subkey(String::from("Software\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64")) { - Ok(v) => { + if !match winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE).open_subkey(String::from( + "Software\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64" + )) + { + Ok(v) => + { let x: std::io::Result = v.get_value("Installed"); - match x { + match x + { Ok(_) => false, Err(_) => true } - }, + } Err(_) => true - } { + } + { debug!("[depends::try_install_vcredist] Seems like vcredist is already installed"); return Ok(()); } @@ -70,35 +96,44 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> helper::download_with_progress( String::from("https://aka.ms/vs/17/release/vc_redist.x86.exe"), - out_loc.clone()).await?; + out_loc.clone() + ) + .await?; - if std::path::Path::new(&out_loc).exists() == false { - return Err(BeansError::FileNotFound { + if std::path::Path::new(&out_loc).exists() == false + { + return Err(BeansError::FileNotFound { location: out_loc.clone(), backtrace: Backtrace::capture() }); } std::process::Command::new(&out_loc) - .args(["/install","/passive","/norestart"]) + .args(["/install", "/passive", "/norestart"]) .spawn() .expect("Failed to install vsredist!") .wait()?; - - if helper::file_exists(out_loc.clone()) { - if let Err(e) = std::fs::remove_file(&out_loc) { + + if helper::file_exists(out_loc.clone()) + { + if let Err(e) = std::fs::remove_file(&out_loc) + { sentry::capture_error(&e); - debug!("[depends::try_install_vcredist] Failed to remove installer {:#?}", e); + debug!( + "[depends::try_install_vcredist] Failed to remove installer {:#?}", + e + ); } } - + Ok(()) } -pub fn butler_exists() -> bool { +pub fn butler_exists() -> bool +{ helper::file_exists(get_butler_location()) - && helper::file_exists(get_butler_1_location()) - && helper::file_exists(get_butler_2_location()) + && helper::file_exists(get_butler_1_location()) + && helper::file_exists(get_butler_2_location()) } pub fn get_butler_location() -> String @@ -107,17 +142,20 @@ pub fn get_butler_location() -> String path.push_str(BUTLER_LOCATION); path } -pub fn get_butler_1_location() -> String { +pub fn get_butler_1_location() -> String +{ let mut path = get_tmp_dir(); path.push_str(BUTLER_1); path } -pub fn get_butler_2_location() -> String { +pub fn get_butler_2_location() -> String +{ let mut path = get_tmp_dir(); path.push_str(BUTLER_2); path } -fn get_tmp_dir() -> String { +fn get_tmp_dir() -> String +{ let path = helper::get_tmp_dir(); helper::format_directory_path(path) } @@ -134,4 +172,4 @@ const BUTLER_1: &str = "7z.so"; #[cfg(target_os = "windows")] const BUTLER_2: &str = "c7zip.dll"; #[cfg(not(target_os = "windows"))] -const BUTLER_2: &str = "libc7zip.so"; \ No newline at end of file +const BUTLER_2: &str = "libc7zip.so"; diff --git a/src/error.rs b/src/error.rs index 7286110..e6a5d4e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,83 +1,98 @@ -use std::backtrace::Backtrace; -use std::fmt::{Display, Formatter}; -use std::num::ParseIntError; +use std::{backtrace::Backtrace, + fmt::{Display, + Formatter}, + num::ParseIntError}; + use thiserror::Error; -use crate::appvar::AppVarData; -use crate::version::AdastralVersionFile; + +use crate::{appvar::AppVarData, + version::AdastralVersionFile}; #[derive(Debug, Error)] pub enum BeansError { /// Failed to check if there is free space. Value is the location #[error("Not enough free space in {location}")] - FreeSpaceCheckFailure { + FreeSpaceCheckFailure + { location: String }, /// Failed to find the sourcemod mod folder. #[error("Failed to detect sourcemod folder. Please provide it via the --location argument.")] SourceModLocationNotFound, #[error("Failed to open file at {location} ({error:})")] - FileOpenFailure { + FileOpenFailure + { location: String, error: std::io::Error }, #[error("Failed to write file at {location} ({error:})")] - FileWriteFailure { + FileWriteFailure + { location: String, error: std::io::Error }, #[error("Failed to create directory {location} ({error:})")] - DirectoryCreateFailure { + DirectoryCreateFailure + { location: String, error: std::io::Error }, #[error("Failed to delete directory {location} ({error:})")] - DirectoryDeleteFailure { + DirectoryDeleteFailure + { location: String, error: std::io::Error }, #[error("Failed to extract {src_file} to directory {target_dir} ({error:})")] - TarExtractFailure { + TarExtractFailure + { src_file: String, target_dir: String, error: std::io::Error, backtrace: Backtrace }, #[error("Failed to send request ({error:})")] - Reqwest { + Reqwest + { error: reqwest::Error, backtrace: Backtrace }, #[error("Failed to serialize or deserialize data ({error:})")] - SerdeJson { + SerdeJson + { error: serde_json::Error, backtrace: Backtrace }, #[error("Latest version is already installed. (current: {current}, latest: {latest})")] - LatestVersionAlreadyInstalled { - current: usize, - latest: usize + LatestVersionAlreadyInstalled + { + current: usize, latest: usize }, #[error("Failed to download file\n{reason:#?}")] - DownloadFailure { + DownloadFailure + { reason: DownloadFailureReason }, #[error("General IO Error\n{error:#?}")] - IO { + IO + { error: std::io::Error, backtrace: Backtrace }, #[error("Unable to perform action since the mod isn't installed since {missing_file} couldn't be found")] - TargetSourcemodNotInstalled { + TargetSourcemodNotInstalled + { missing_file: String, backtrace: Backtrace }, #[error("Failed to run the verify command with butler. ({error:})")] - ButlerVerifyFailure { + ButlerVerifyFailure + { signature_url: String, gamedir: String, remote: String, @@ -86,7 +101,8 @@ pub enum BeansError }, #[error("Failed to run the apply command with butler. {error:}")] - ButlerPatchFailure { + ButlerPatchFailure + { patchfile_location: String, gamedir: String, error: std::io::Error, @@ -94,13 +110,15 @@ pub enum BeansError }, #[error("Could not find file {location}")] - FileNotFound { + FileNotFound + { location: String, backtrace: Backtrace }, #[error("Version {version:#?} could not be found on the server.")] - RemoteVersionNotFound { + RemoteVersionNotFound + { version: Option }, @@ -108,85 +126,99 @@ pub enum BeansError SteamNotFound, #[error("{msg}")] - RegistryKeyFailure { + RegistryKeyFailure + { msg: String, error: std::io::Error, backtrace: Backtrace }, #[error("Failed to migrate old version file to the new format at {location} ({error:})")] - VersionFileMigrationFailure { + VersionFileMigrationFailure + { error: std::io::Error, location: String }, #[error("Failed to delete old version file {location} ({error:})")] - VersionFileMigrationDeleteFailure { + VersionFileMigrationDeleteFailure + { error: std::io::Error, location: String }, #[error("Failed to convert version file to JSON format. ({error:})")] - VersionFileSerialize { + VersionFileSerialize + { error: serde_json::Error, instance: AdastralVersionFile }, - #[error("Failed to parse the version in {old_location}. It's content was {old_content} ({error:})")] - VersionFileParseFailure { + #[error( + "Failed to parse the version in {old_location}. It's content was {old_content} ({error:})" + )] + VersionFileParseFailure + { error: ParseIntError, old_location: String, old_content: String }, #[error("Failed to read version file at {location}. ({error:})")] - VersionFileReadFailure { + VersionFileReadFailure + { error: std::io::Error, location: String }, #[error("Failed to serialize provided AppVarData to JSON. ({error:})")] - AppVarDataSerializeFailure { + AppVarDataSerializeFailure + { error: serde_json::Error, data: AppVarData }, #[error("Failed to read gameinfo.txt at {location} ({error:})")] - GameInfoFileReadFail { + GameInfoFileReadFail + { error: std::io::Error, location: String, backtrace: Backtrace }, #[error("Failed to set permissions on gameinfo.txt at {location} ({error:})")] - GameInfoPermissionSetFail { + GameInfoPermissionSetFail + { error: std::io::Error, permissions: std::fs::Permissions, location: String }, #[error("Failed to backup gameinfo.txt, {reason:}")] - GameinfoBackupFailure { + GameinfoBackupFailure + { reason: GameinfoBackupFailureReason }, #[error("Failed to remove files in {location} ({error:})")] - CleanTempFailure { + CleanTempFailure + { location: String, error: std::io::Error }, #[error("{name:} ({pid:}) is still running. Please close it and restart beans.")] - GameStillRunning { - name: String, - pid: String + GameStillRunning + { + name: String, pid: String } } #[derive(Debug)] pub enum DownloadFailureReason { - Reqwest { - url: String, - error: reqwest::Error + Reqwest + { + url: String, error: reqwest::Error }, /// The downloaded file could not be found, perhaps it failed? - FileNotFound { + FileNotFound + { location: String } } @@ -198,61 +230,86 @@ pub enum GameinfoBackupFailureReason WriteFail(GameinfoBackupWriteFail) } #[derive(Debug)] -pub struct GameinfoBackupReadContentFail { +pub struct GameinfoBackupReadContentFail +{ pub error: std::io::Error, pub proposed_location: String, pub current_location: String } #[derive(Debug)] -pub struct GameinfoBackupCreateDirectoryFail { +pub struct GameinfoBackupCreateDirectoryFail +{ pub error: std::io::Error, pub location: String } #[derive(Debug)] -pub struct GameinfoBackupWriteFail { +pub struct GameinfoBackupWriteFail +{ pub error: std::io::Error, pub location: String } -impl Display for GameinfoBackupFailureReason { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - GameinfoBackupFailureReason::ReadContentFail(v) - => write!(f, "Couldn't read the content at {} ({:})", v.current_location, v.error), - GameinfoBackupFailureReason::BackupDirectoryCreateFailure(v) - => write!(f, "Couldn't create backups directory {} ({:})", v.location, v.error), - GameinfoBackupFailureReason::WriteFail(v) - => write!(f, "Failed to write content to {} ({:})", v.location, v.error) +impl Display for GameinfoBackupFailureReason +{ + fn fmt( + &self, + f: &mut Formatter<'_> + ) -> std::fmt::Result + { + match self + { + GameinfoBackupFailureReason::ReadContentFail(v) => write!( + f, + "Couldn't read the content at {} ({:})", + v.current_location, v.error + ), + GameinfoBackupFailureReason::BackupDirectoryCreateFailure(v) => write!( + f, + "Couldn't create backups directory {} ({:})", + v.location, v.error + ), + GameinfoBackupFailureReason::WriteFail(v) => write!( + f, + "Failed to write content to {} ({:})", + v.location, v.error + ) } } } #[derive(Debug)] -pub struct TarExtractFailureDetails { +pub struct TarExtractFailureDetails +{ pub source: String, pub target: String, pub error: std::io::Error } -impl From for BeansError { - fn from(e: std::io::Error) -> Self { +impl From for BeansError +{ + fn from(e: std::io::Error) -> Self + { BeansError::IO { error: e, backtrace: Backtrace::capture() } } } -impl From for BeansError { - fn from (e: reqwest::Error) -> Self { +impl From for BeansError +{ + fn from(e: reqwest::Error) -> Self + { BeansError::Reqwest { error: e, backtrace: Backtrace::capture() } } } -impl From for BeansError { - fn from(e: serde_json::Error) -> Self { +impl From for BeansError +{ + fn from(e: serde_json::Error) -> Self + { BeansError::SerdeJson { error: e, backtrace: Backtrace::capture() } } -} \ No newline at end of file +} diff --git a/src/flags.rs b/src/flags.rs index 3287a69..341b85c 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -1,6 +1,5 @@ use bitflags::bitflags; -bitflags! -{ +bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct LaunchFlag: u32 { @@ -29,11 +28,14 @@ pub fn has_flag(flag: LaunchFlag) -> bool pub fn add_flag(flag: LaunchFlag) { unsafe { - match flag { - LaunchFlag::DEBUG_MODE => { + match flag + { + LaunchFlag::DEBUG_MODE => + { crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_DEFAULT; } - _ => {} + _ => + {} }; let mut data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); @@ -45,11 +47,14 @@ pub fn add_flag(flag: LaunchFlag) pub fn remove_flag(flag: LaunchFlag) { unsafe { - match flag { - LaunchFlag::DEBUG_MODE => { + match flag + { + LaunchFlag::DEBUG_MODE => + { crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_MINIMAL; } - _ => {} + _ => + {} }; let mut data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); data.remove(flag); @@ -60,4 +65,4 @@ pub fn remove_flag(flag: LaunchFlag) pub fn debug_mode() -> bool { has_flag(LaunchFlag::DEBUG_MODE) -} \ No newline at end of file +} diff --git a/src/helper/linux.rs b/src/helper/linux.rs index 05d2330..baf0798 100644 --- a/src/helper/linux.rs +++ b/src/helper/linux.rs @@ -1,18 +1,22 @@ -use std::fs::read_to_string; -use log::{debug, error}; -use crate::BeansError; -use crate::helper::format_directory_path; +use std::fs::read_to_string; + +use log::{debug, + error}; + +use crate::{helper::format_directory_path, + BeansError}; /// all possible known directory where steam *might* be /// only is used on linux, since windows will use the registry. -pub const STEAM_POSSIBLE_DIR: &'static [&'static str] = &[ +pub const STEAM_POSSIBLE_DIR: &'static [&'static str] = &[ "~/.steam/registry.vdf", "~/.var/app/com.valvesoftware.Steam/.steam/registry.vdf" ]; /// find sourcemod path on linux. /// fetches the fake registry that steam uses from find_steam_reg_path -/// and gets the value of Registry/HKCU/Software/Valve/Steam/SourceModInstallPath +/// and gets the value of +/// Registry/HKCU/Software/Valve/Steam/SourceModInstallPath pub fn find_sourcemod_path() -> Result { let reg_path = find_steam_reg_path()?; @@ -20,7 +24,8 @@ pub fn find_sourcemod_path() -> Result let reg_content = match read_to_string(reg_path.as_str()) { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); return Err(BeansError::FileOpenFailure { location: reg_path, @@ -29,11 +34,13 @@ pub fn find_sourcemod_path() -> Result } }; - for line in reg_content.lines() { + for line in reg_content.lines() + { if line.contains("SourceModInstallPath") { let split = &line.split("\"SourceModInstallPath\""); - let last = split.clone() + let last = split + .clone() .last() .expect("Failed to find SourceModInstallPath") .trim() @@ -47,25 +54,29 @@ pub fn find_sourcemod_path() -> Result /// returns the first item in STEAM_POSSIBLE_DIR that exists. otherwise None fn find_steam_reg_path() -> Result { - for x in STEAM_POSSIBLE_DIR.into_iter() { - match simple_home_dir::home_dir() { - Some(v) => { - match v.to_str() { - Some(k) => { - let h = format_directory_path(k.to_string()); - let reg_loc = x.replace("~", h.as_str()); - if crate::helper::file_exists(reg_loc.clone()) - { - return Ok(reg_loc.clone()); - } - }, - None => { - debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir().to_str() returned None!"); - return Err(BeansError::SteamNotFound); + for x in STEAM_POSSIBLE_DIR.into_iter() + { + match simple_home_dir::home_dir() + { + Some(v) => match v.to_str() + { + Some(k) => + { + let h = format_directory_path(k.to_string()); + let reg_loc = x.replace("~", h.as_str()); + if crate::helper::file_exists(reg_loc.clone()) + { + return Ok(reg_loc.clone()); } } + None => + { + debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir().to_str() returned None!"); + return Err(BeansError::SteamNotFound); + } }, - None => { + None => + { debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir() returned None!"); return Err(BeansError::SteamNotFound); } @@ -73,4 +84,4 @@ fn find_steam_reg_path() -> Result } error!("Couldn't find any of the locations in STEAM_POSSIBLE_DIR"); return Err(BeansError::SteamNotFound); -} \ No newline at end of file +} diff --git a/src/helper/mod.rs b/src/helper/mod.rs index cea3284..c97c1a3 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -1,26 +1,38 @@ -#[cfg(not(target_os = "windows"))] +#[cfg(not(target_os = "windows"))] mod linux; use std::backtrace::Backtrace; + #[cfg(not(target_os = "windows"))] pub use linux::*; #[cfg(target_os = "windows")] mod windows; -#[cfg(target_os = "windows")] -pub use windows::*; +use std::{collections::HashMap, + io::Write, + path::PathBuf}; - -use std::io::Write; -use std::path::PathBuf; -use indicatif::{ProgressBar, ProgressStyle}; use futures::StreamExt; -use log::{debug, error, trace, warn}; -use crate::{BeansError, DownloadFailureReason, GameinfoBackupCreateDirectoryFail, GameinfoBackupFailureReason, GameinfoBackupReadContentFail, GameinfoBackupWriteFail, RunnerContext}; -use rand::{distributions::Alphanumeric, Rng}; +use indicatif::{ProgressBar, + ProgressStyle}; +use log::{debug, + error, + trace, + warn}; +use rand::{distributions::Alphanumeric, + Rng}; use reqwest::header::USER_AGENT; -use crate::appvar::AppVarData; -use std::collections::HashMap; +#[cfg(target_os = "windows")] +pub use windows::*; + +use crate::{appvar::AppVarData, + BeansError, + DownloadFailureReason, + GameinfoBackupCreateDirectoryFail, + GameinfoBackupFailureReason, + GameinfoBackupReadContentFail, + GameinfoBackupWriteFail, + RunnerContext}; #[derive(Clone, Debug)] pub enum InstallType @@ -40,40 +52,42 @@ pub enum InstallType OtherSourceManual } -impl PartialEq for InstallType { - fn eq(&self, other: &Self) -> bool { - match self { - InstallType::NotInstalled => { - match other { - InstallType::NotInstalled => true, - InstallType::Adastral => false, - InstallType::OtherSource => false, - InstallType::OtherSourceManual => false - } +impl PartialEq for InstallType +{ + fn eq( + &self, + other: &Self + ) -> bool + { + match self + { + InstallType::NotInstalled => match other + { + InstallType::NotInstalled => true, + InstallType::Adastral => false, + InstallType::OtherSource => false, + InstallType::OtherSourceManual => false }, - InstallType::Adastral => { - match other { - InstallType::NotInstalled => false, - InstallType::Adastral => true, - InstallType::OtherSource => false, - InstallType::OtherSourceManual => false - } + InstallType::Adastral => match other + { + InstallType::NotInstalled => false, + InstallType::Adastral => true, + InstallType::OtherSource => false, + InstallType::OtherSourceManual => false }, - InstallType::OtherSource => { - match other { - InstallType::NotInstalled => false, - InstallType::Adastral => false, - InstallType::OtherSource => true, - InstallType::OtherSourceManual => true - } + InstallType::OtherSource => match other + { + InstallType::NotInstalled => false, + InstallType::Adastral => false, + InstallType::OtherSource => true, + InstallType::OtherSourceManual => true }, - InstallType::OtherSourceManual => { - match other { - InstallType::NotInstalled => false, - InstallType::Adastral => false, - InstallType::OtherSource => false, - InstallType::OtherSourceManual => true - } + InstallType::OtherSourceManual => match other + { + InstallType::NotInstalled => false, + InstallType::Adastral => false, + InstallType::OtherSource => false, + InstallType::OtherSourceManual => true } } } @@ -82,47 +96,62 @@ impl PartialEq for InstallType { /// get the current type of installation. pub fn install_state(sourcemods_location: Option) -> InstallType { - let mut smp_x = match sourcemods_location { + let mut smp_x = match sourcemods_location + { Some(v) => v, - None => match find_sourcemod_path() { + None => match find_sourcemod_path() + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); - debug!("[helper::install_state] {} {:#?}", BeansError::SourceModLocationNotFound, e); + debug!( + "[helper::install_state] {} {:#?}", + BeansError::SourceModLocationNotFound, + e + ); return InstallType::NotInstalled; } } }; - if smp_x.ends_with("/") || smp_x.ends_with("\\") { + if smp_x.ends_with("/") || smp_x.ends_with("\\") + { smp_x.pop(); } let data_dir = join_path(smp_x, crate::data_dir()); - if file_exists(format!("{}.adastral", data_dir)) { + if file_exists(format!("{}.adastral", data_dir)) + { return InstallType::Adastral; } - else if file_exists(format!("{}.revision", data_dir)) { + else if file_exists(format!("{}.revision", data_dir)) + { return InstallType::OtherSource; } - else if file_exists(format!("{}gameinfo.txt", data_dir)) { + else if file_exists(format!("{}gameinfo.txt", data_dir)) + { return InstallType::OtherSourceManual; } return InstallType::NotInstalled; } -/// get user input from terminal. prompt is displayed on the line above where the user does input. -pub fn get_input(prompt: &str) -> String{ - println!("{}",prompt); +/// get user input from terminal. prompt is displayed on the line above where +/// the user does input. +pub fn get_input(prompt: &str) -> String +{ + println!("{}", prompt); let mut input = String::new(); - match std::io::stdin().read_line(&mut input) { - Ok(_goes_into_input_above) => {}, - Err(_no_updates_is_fine) => {}, + match std::io::stdin().read_line(&mut input) + { + Ok(_goes_into_input_above) => + {} + Err(_no_updates_is_fine) => + {} } input.trim().to_string() } - /// check if a file exists pub fn file_exists(location: String) -> bool { @@ -140,8 +169,10 @@ pub fn is_directory(location: String) -> bool } /// Check if the file at the location provided is a symlink. -pub fn is_symlink(location: String) -> bool { - match std::fs::symlink_metadata(&location) { +pub fn is_symlink(location: String) -> bool +{ + match std::fs::symlink_metadata(&location) + { Ok(meta) => meta.file_type().is_symlink(), Err(_) => false } @@ -156,15 +187,22 @@ pub fn generate_rand_str(length: usize) -> String .collect(); s.to_uppercase() } -/// Join the path, using `tail` as the base, and `head` as the thing to add on top of it. +/// Join the path, using `tail` as the base, and `head` as the thing to add on +/// top of it. /// -/// This will also convert backslashes/forwardslashes to the compiled separator in `crate::PATH_SEP` -pub fn join_path(tail: String, head: String) -> String +/// This will also convert backslashes/forwardslashes to the compiled separator +/// in `crate::PATH_SEP` +pub fn join_path( + tail: String, + head: String +) -> String { - let mut h = head.to_string() + let mut h = head + .to_string() .replace("/", crate::PATH_SEP) .replace("\\", crate::PATH_SEP); - while h.starts_with(crate::PATH_SEP) { + while h.starts_with(crate::PATH_SEP) + { h.remove(0); } @@ -173,66 +211,89 @@ pub fn join_path(tail: String, head: String) -> String pub fn remove_path_head(location: String) -> String { let p = std::path::Path::new(&location); - if let Some(x) = p.parent() { - if let Some(m) = x.to_str() { + if let Some(x) = p.parent() + { + if let Some(m) = x.to_str() + { return m.to_string(); } } return String::new(); } -/// Make sure that the location provided is formatted as a directory (ends with `crate::PATH_SEP`). +/// Make sure that the location provided is formatted as a directory (ends with +/// `crate::PATH_SEP`). pub fn format_directory_path(location: String) -> String { - let mut x = location.to_string() + let mut x = location + .to_string() .replace("/", crate::PATH_SEP) .replace("\\", crate::PATH_SEP); - while x.ends_with(crate::PATH_SEP) { + while x.ends_with(crate::PATH_SEP) + { x.pop(); } - if x.ends_with(crate::PATH_SEP) == false { + if x.ends_with(crate::PATH_SEP) == false + { x.push_str(crate::PATH_SEP); } x } #[cfg(not(target_os = "windows"))] -pub fn canonicalize(location: &str) -> Result { +pub fn canonicalize(location: &str) -> Result +{ std::fs::canonicalize(location) } #[cfg(target_os = "windows")] -pub fn canonicalize(location: &str) -> Result { +pub fn canonicalize(location: &str) -> Result +{ dunce::canonicalize(location) } pub fn parse_location(location: String) -> String { let path = std::path::Path::new(&location); - let real_location = match path.to_str() { - Some(v) => { + let real_location = match path.to_str() + { + Some(v) => + { let p = canonicalize(v); - match p { - Ok(x) => { - match x.clone().to_str() { - Some(m) => m.to_string(), - None => { - debug!("[helper::parse_location] Failed to parse location to string {}", location); - return location; - } + match p + { + Ok(x) => match x.clone().to_str() + { + Some(m) => m.to_string(), + None => + { + debug!( + "[helper::parse_location] Failed to parse location to string {}", + location + ); + return location; } }, - Err(e) => { - if format!("{:}", e).starts_with("No such file or directory") { + Err(e) => + { + if format!("{:}", e).starts_with("No such file or directory") + { return location; } sentry::capture_error(&e); - eprintln!("[helper::parse_location] Failed to canonicalize location {}", location); + eprintln!( + "[helper::parse_location] Failed to canonicalize location {}", + location + ); eprintln!("[helper::parse_location] {:}", e); debug!("{:#?}", e); return location; } } - }, - None => { - debug!("[helper::parse_location] Failed to parse location {}", location); + } + None => + { + debug!( + "[helper::parse_location] Failed to parse location {}", + location + ); return location; } }; @@ -241,13 +302,22 @@ pub fn parse_location(location: String) -> String /// Check if a process is running /// /// name: Executable name (from `Process.name(&self)`) -/// argument_contains: Check if the arguments of the process has an item that starts with this value (when some). -fn is_process_running(name: String, arguments_contains: Option) -> Option { - return find_process(move |proc: &sysinfo::Process | { - if proc.name().to_string() == name { - if let Some(x) = arguments_contains.clone() { - for item in proc.cmd().iter() { - if item.to_string().starts_with(&x) { +/// argument_contains: Check if the arguments of the process has an item that +/// starts with this value (when some). +fn is_process_running( + name: String, + arguments_contains: Option +) -> Option +{ + return find_process(move |proc: &sysinfo::Process| { + if proc.name().to_string() == name + { + if let Some(x) = arguments_contains.clone() + { + for item in proc.cmd().iter() + { + if item.to_string().starts_with(&x) + { return true; } } @@ -260,12 +330,15 @@ fn is_process_running(name: String, arguments_contains: Option) -> Optio /// /// Will return Some when a process is found, otherwise None. pub fn find_process(selector: TFilterSelector) -> Option - where TFilterSelector : Fn(&sysinfo::Process) -> bool +where + TFilterSelector: Fn(&sysinfo::Process) -> bool { let mut sys = sysinfo::System::new_all(); sys.refresh_all(); - for (_, process) in sys.processes() { - if selector(process) { + for (_, process) in sys.processes() + { + if selector(process) + { return Some(process.pid()); } } @@ -275,30 +348,42 @@ pub fn find_process(selector: TFilterSelector) -> Option Option { +pub fn is_game_running(mod_directory: String) -> Option +{ // check if running with the windows things - if let Some(proc) = is_process_running(String::from("hl2.exe"), Some(mod_directory.clone())) { + if let Some(proc) = is_process_running(String::from("hl2.exe"), Some(mod_directory.clone())) + { return Some(proc); } - if let Some(proc) = is_process_running(String::from("hl2.exe"), Some(format!("\"{}\"", mod_directory.clone()))) { + if let Some(proc) = is_process_running( + String::from("hl2.exe"), + Some(format!("\"{}\"", mod_directory.clone())) + ) + { return Some(proc); } // check if any process has it in the arguments if let Some(proc) = find_process(move |proc| { - for item in proc.cmd().iter() { - if item.to_string().starts_with(&mod_directory) { + for item in proc.cmd().iter() + { + if item.to_string().starts_with(&mod_directory) + { let proc_name = proc.name().to_string().to_lowercase(); - if proc_name != String::from("beans") && proc_name != String::from("beans-rs") { + if proc_name != String::from("beans") && proc_name != String::from("beans-rs") + { return true; } } } return false; - }) { + }) + { return Some(proc); } return None; @@ -308,17 +393,21 @@ pub fn is_game_running(mod_directory: String) -> Option { pub fn get_free_space(location: String) -> Result { let mut data: HashMap = HashMap::new(); - for disk in sysinfo::Disks::new_with_refreshed_list().list() { - if let Some(mp) = disk.mount_point().to_str() { + for disk in sysinfo::Disks::new_with_refreshed_list().list() + { + if let Some(mp) = disk.mount_point().to_str() + { debug!("[get_free_space] space: {} {}", mp, disk.available_space()); data.insert(mp.to_string(), disk.available_space()); } } let mut l = parse_location(location.clone()); - while !l.is_empty() { + while !l.is_empty() + { debug!("[get_free_space] Checking if {} is in data", l); - if let Some(x) = data.get(&l) { + if let Some(x) = data.get(&l) + { return Ok(x.clone()); } l = remove_path_head(l); @@ -329,7 +418,10 @@ pub fn get_free_space(location: String) -> Result }) } /// Check if the location provided has enough free space. -pub fn has_free_space(location: String, size: usize) -> Result +pub fn has_free_space( + location: String, + size: usize +) -> Result { let space = get_free_space(location)?; return Ok((size as u64) < space); @@ -337,14 +429,16 @@ pub fn has_free_space(location: String, size: usize) -> Result /// Download file at the URL provided to the output location provided /// This function will also show a progress bar with indicatif. -pub async fn download_with_progress(url: String, out_location: String) -> Result<(), BeansError> +pub async fn download_with_progress( + url: String, + out_location: String +) -> Result<(), BeansError> { - let res = match reqwest::Client::new() - .get(&url) - .send() - .await { + let res = match reqwest::Client::new().get(&url).send().await + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::Reqwest { @@ -367,9 +461,11 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result pb.set_message(format!("Downloading {}", &url)); // download chunks - let mut file = match std::fs::File::create(out_location.clone()) { + let mut file = match std::fs::File::create(out_location.clone()) + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); return Err(BeansError::FileOpenFailure { location: out_location, @@ -380,7 +476,8 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result let mut downloaded: u64 = 0; let mut stream = res.bytes_stream(); - while let Some(item) = stream.next().await { + while let Some(item) = stream.next().await + { let chunk = item.expect("Failed to write content to file"); file.write_all(&chunk) .expect("Failed to write content to file"); @@ -394,26 +491,39 @@ pub async fn download_with_progress(url: String, out_location: String) -> Result } /// Format parameter `i` to a human-readable size. -pub fn format_size(i: usize) -> String { +pub fn format_size(i: usize) -> String +{ let value = i.to_string(); let decimal_points: usize = 3; let mut dec_l = decimal_points * 6; - if i < 1_000 { + if i < 1_000 + { dec_l = decimal_points * 0; - } else if i < 1_000_000 { + } + else if i < 1_000_000 + { dec_l = decimal_points * 1; - } else if i < 1_000_000_000 { + } + else if i < 1_000_000_000 + { dec_l = decimal_points * 2; - } else if i < 1_000_000_000_000 { + } + else if i < 1_000_000_000_000 + { dec_l = decimal_points * 3; - } else if i < 1_000_000_000_000_000 { + } + else if i < 1_000_000_000_000_000 + { dec_l = decimal_points * 4; - } else if i < 1_000_000_000_000_000_000 { + } + else if i < 1_000_000_000_000_000_000 + { dec_l = decimal_points * 5; } - let dec: String = value.chars() + let dec: String = value + .chars() .into_iter() .rev() .take(dec_l as usize) @@ -425,7 +535,8 @@ pub fn format_size(i: usize) -> String { let whole_l = value.len() - dec_l; let mut whole: String = value.chars().into_iter().take(whole_l).collect(); - if dec_x.len() > 0 { + if dec_x.len() > 0 + { whole.push('.'); } let pfx_data: Vec<(usize, &str)> = vec![ @@ -433,28 +544,38 @@ pub fn format_size(i: usize) -> String { (1_000_000, "kb"), (1_000_000_000, "mb"), (1_000_000_000_000, "gb"), - (1_000_000_000_000_000, "tb")]; - for (s, c) in pfx_data.into_iter() { - if i < s { + (1_000_000_000_000_000, "tb"), + ]; + for (s, c) in pfx_data.into_iter() + { + if i < s + { return format!("{}{}{}", whole, dec_x, c); } } return format!("{}{}", whole, dec_x); } -/// Check if we should use the custom temporary directory, which is stored in the environment variable -/// defined in `CUSTOM_TMPDIR_NAME`. -/// +/// Check if we should use the custom temporary directory, which is stored in +/// the environment variable defined in `CUSTOM_TMPDIR_NAME`. +/// /// ## Return -/// `Some` when the environment variable is set, and the directory exist. +/// `Some` when the environment variable is set, and the directory exist. /// Otherwise `None` is returned. pub fn use_custom_tmpdir() -> Option { - if let Ok(x) = std::env::var(CUSTOM_TMPDIR_NAME) { + if let Ok(x) = std::env::var(CUSTOM_TMPDIR_NAME) + { let s = x.to_string(); - if dir_exists(s.clone()) { + if dir_exists(s.clone()) + { return Some(s); - } else { - warn!("[use_custom_tmp_dir] Custom temporary directory \"{}\" doesn't exist", s); + } + else + { + warn!( + "[use_custom_tmp_dir] Custom temporary directory \"{}\" doesn't exist", + s + ); } } return None; @@ -464,47 +585,69 @@ pub const CUSTOM_TMPDIR_NAME: &str = "ADASTRAL_TMPDIR"; pub fn get_tmp_dir() -> String { let mut dir = std::env::temp_dir().to_str().unwrap_or("").to_string(); - if let Some(x) = use_custom_tmpdir() { + if let Some(x) = use_custom_tmpdir() + { dir = x; - } else if is_steamdeck() { + } + else if is_steamdeck() + { trace!("[helper::get_tmp_dir] Detected that we are running on a steam deck. Using ~/.tmp/beans-rs"); - match simple_home_dir::home_dir() { - Some(v) => { - match v.to_str() { - Some(k) => { - dir = format_directory_path(k.to_string()); - dir = join_path(dir, String::from(".tmp")); - }, - None => { - trace!("[helper::get_tmp_dir] Failed to convert PathBuf to &str"); - } + match simple_home_dir::home_dir() + { + Some(v) => match v.to_str() + { + Some(k) => + { + dir = format_directory_path(k.to_string()); + dir = join_path(dir, String::from(".tmp")); + } + None => + { + trace!("[helper::get_tmp_dir] Failed to convert PathBuf to &str"); } }, - None => { + None => + { trace!("[helper::get_tmp_dir] Failed to get home directory."); } }; - } else if cfg!(target_os = "android") { + } + else if cfg!(target_os = "android") + { dir = String::from("/data/var/tmp"); - } else if cfg!(not(target_os = "windows")) { + } + else if cfg!(not(target_os = "windows")) + { dir = String::from("/var/tmp"); } dir = format_directory_path(dir); - if !dir_exists(dir.clone()) { - if let Err(e) = std::fs::create_dir(&dir) { + if !dir_exists(dir.clone()) + { + if let Err(e) = std::fs::create_dir(&dir) + { trace!("[helper::get_tmp_dir] {:#?}", e); - warn!("[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", dir, e); + warn!( + "[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", + dir, e + ); } } dir = join_path(dir, String::from("beans-rs")); dir = format_directory_path(dir); - if !dir_exists(dir.clone()) { - if let Err(e) = std::fs::create_dir(&dir) { + if !dir_exists(dir.clone()) + { + if let Err(e) = std::fs::create_dir(&dir) + { trace!("[helper::get_tmp_dir] {:#?}", e); - warn!("[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", dir, e); + warn!( + "[helper::get_tmp_dir] failed to make tmp directory at {} ({:})", + dir, e + ); sentry::capture_error(&e); - } else { + } + else + { trace!("[helper::get_tmp_dir] created directory {}", dir); } } @@ -512,7 +655,7 @@ pub fn get_tmp_dir() -> String return dir; } /// Check if the content of `uname -r` contains `valve` (Linux Only) -/// +/// /// ## Returns /// - `true` when; /// - The output of `uname -r` contains `valve` @@ -520,37 +663,47 @@ pub fn get_tmp_dir() -> String /// - `target_os` is not `linux` /// - Failed to run `uname -r` /// - Failed to parse the stdout of `uname -r` as a String. -/// +/// /// ## Note /// Will always return `false` when `cfg!(not(target_os = "linux"))`. -/// -/// This function will write to `log::trace` with the full error details before writing it to `log::warn` or `log::error`. Since errors from this +/// +/// This function will write to `log::trace` with the full error details before +/// writing it to `log::warn` or `log::error`. Since errors from this /// aren't significant, `sentry::capture_error` will not be called. -pub fn is_steamdeck() -> bool { - if cfg!(not(target_os = "linux")) { +pub fn is_steamdeck() -> bool +{ + if cfg!(not(target_os = "linux")) + { return false; } - match std::process::Command::new("uname").arg("-r").output() { - Ok(cmd) => { + match std::process::Command::new("uname").arg("-r").output() + { + Ok(cmd) => + { trace!("[helper::is_steamdeck] exit status: {}", &cmd.status); let stdout = &cmd.stdout.to_vec(); let stderr = &cmd.stderr.to_vec(); - if let Ok(x) = String::from_utf8(stderr.clone()) { + if let Ok(x) = String::from_utf8(stderr.clone()) + { trace!("[helper::is_steamdeck] stderr: {}", x); } - match String::from_utf8(stdout.clone()) { - Ok(x) => { + match String::from_utf8(stdout.clone()) + { + Ok(x) => + { trace!("[helper::is_steamdeck] stdout: {}", x); x.contains("valve") - }, - Err(e) => { + } + Err(e) => + { trace!("[helper::is_steamdeck] Failed to parse as utf8 {:#?}", e); false } } - }, - Err(e) => { + } + Err(e) => + { trace!("[helper::is_steamdeck] {:#?}", e); warn!("[helper::is_steamdeck] Failed to detect {:}", e); return false; @@ -563,28 +716,38 @@ pub fn get_tmp_file(filename: String) -> String let head = format!("{}_{}", generate_rand_str(8), filename); join_path(get_tmp_dir(), head) } -/// Check if there is an update available. When the latest release doesn't match the current release. +/// Check if there is an update available. When the latest release doesn't match +/// the current release. pub async fn beans_has_update() -> Result, BeansError> { let rs = reqwest::Client::new() .get(GITHUB_RELEASES_URL) .header(USER_AGENT, &format!("beans-rs/{}", crate::VERSION)) - .send().await; - let response = match rs { + .send() + .await; + let response = match rs + { Ok(v) => v, - Err(e) => { + Err(e) => + { trace!("Failed get latest release from github \nerror: {:#?}", e); - return Err(BeansError::Reqwest{ + return Err(BeansError::Reqwest { error: e, backtrace: Backtrace::capture() }); } }; let response_text = response.text().await?; - let data: GithubReleaseItem = match serde_json::from_str(&response_text) { + let data: GithubReleaseItem = match serde_json::from_str(&response_text) + { Ok(v) => v, - Err(e) => { - trace!("Failed to deserialize GithubReleaseItem\nerror: {:#?}\ncontent: {:#?}", e, response_text); + Err(e) => + { + trace!( + "Failed to deserialize GithubReleaseItem\nerror: {:#?}\ncontent: {:#?}", + e, + response_text + ); return Err(BeansError::SerdeJson { error: e, backtrace: Backtrace::capture() @@ -592,34 +755,55 @@ pub async fn beans_has_update() -> Result, BeansError> } }; trace!("{:#?}", data); - if data.draft == false && data.prerelease == false && data.tag_name != format!("v{}", crate::VERSION) { + if data.draft == false + && data.prerelease == false + && data.tag_name != format!("v{}", crate::VERSION) + { return Ok(Some(data.clone())); } return Ok(None); } -pub fn restore_gameinfo(ctx: &mut RunnerContext, data: Vec) -> Result<(), BeansError> { +pub fn restore_gameinfo( + ctx: &mut RunnerContext, + data: Vec +) -> Result<(), BeansError> +{ let loc = ctx.gameinfo_location(); trace!("gameinfo location: {}", &loc); - if let Ok(m) = std::fs::metadata(&loc) { + if let Ok(m) = std::fs::metadata(&loc) + { trace!("gameinfo metadata: {:#?}", m); } - if let Err(e) = ctx.gameinfo_perms() { - error!("[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", e); + if let Err(e) = ctx.gameinfo_perms() + { + error!( + "[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", + e + ); sentry::capture_error(&e); return Err(e); } - if let Err(e) = std::fs::write(&loc, data) { + if let Err(e) = std::fs::write(&loc, data) + { trace!("error: {:#?}", e); - error!("[helper::restore_gameinfo] Failed to write gameinfo.txt backup {:}", e); - } - if let Err(e) = ctx.gameinfo_perms() { - error!("[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", e); + error!( + "[helper::restore_gameinfo] Failed to write gameinfo.txt backup {:}", + e + ); + } + if let Err(e) = ctx.gameinfo_perms() + { + error!( + "[helper::restore_gameinfo] Failed to update permissions on gameinfo.txt {:}", + e + ); sentry::capture_error(&e); return Err(e); } return Ok(()); } -pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { +pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> +{ let av = AppVarData::get(); let gamedir = join_path(ctx.clone().sourcemod_path, av.mod_info.sourcemod_name); let backupdir = join_path(gamedir.clone(), String::from(GAMEINFO_BACKUP_DIRNAME)); @@ -627,57 +811,90 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { let current_time = chrono::Local::now(); let current_time_formatted = current_time.format("%Y%m%d-%H%M%S").to_string(); - if file_exists(backupdir.clone()) == false { - if let Err(e) = std::fs::create_dir(&backupdir) { + if file_exists(backupdir.clone()) == false + { + if let Err(e) = std::fs::create_dir(&backupdir) + { debug!("backupdir: {}", backupdir); debug!("error: {:#?}", e); - error!("[helper::backup_gameinfo] Failed to create backup directory {:}", e); - return Err(BeansError::GameinfoBackupFailure {reason: GameinfoBackupFailureReason::BackupDirectoryCreateFailure(GameinfoBackupCreateDirectoryFail { - error: e, - location: backupdir - })}); + error!( + "[helper::backup_gameinfo] Failed to create backup directory {:}", + e + ); + return Err(BeansError::GameinfoBackupFailure { + reason: GameinfoBackupFailureReason::BackupDirectoryCreateFailure( + GameinfoBackupCreateDirectoryFail { + error: e, + location: backupdir + } + ) + }); } } let output_location = join_path( backupdir, - format!("{}-{}.txt", ctx.current_version.unwrap_or(0), current_time_formatted)); - let current_location = join_path( - gamedir, - String::from("gameinfo.txt")); - - if file_exists(current_location.clone()) == false { - debug!("[helper::backup_gameinfo] can't backup since {} doesn't exist", current_location); + format!( + "{}-{}.txt", + ctx.current_version.unwrap_or(0), + current_time_formatted + ) + ); + let current_location = join_path(gamedir, String::from("gameinfo.txt")); + + if file_exists(current_location.clone()) == false + { + debug!( + "[helper::backup_gameinfo] can't backup since {} doesn't exist", + current_location + ); return Ok(()); } - let content = match std::fs::read_to_string(¤t_location) { + let content = match std::fs::read_to_string(¤t_location) + { Ok(v) => v, - Err(e) => { + Err(e) => + { debug!("location: {}", current_location); debug!("error: {:#?}", e); - error!("[helper::backup_gameinfo] Failed to read content of gameinfo.txt {:}", e); - return Err(BeansError::GameinfoBackupFailure { reason: GameinfoBackupFailureReason::ReadContentFail(GameinfoBackupReadContentFail{ - error: e, - proposed_location: output_location, - current_location: current_location.clone() - })}) + error!( + "[helper::backup_gameinfo] Failed to read content of gameinfo.txt {:}", + e + ); + return Err(BeansError::GameinfoBackupFailure { + reason: GameinfoBackupFailureReason::ReadContentFail( + GameinfoBackupReadContentFail { + error: e, + proposed_location: output_location, + current_location: current_location.clone() + } + ) + }); } }; - if file_exists(output_location.clone()) { - if let Err(e) = std::fs::remove_file(&output_location) { + if file_exists(output_location.clone()) + { + if let Err(e) = std::fs::remove_file(&output_location) + { warn!("[helper::backup_gameinfo] Failed to delete existing file, lets hope things don't break. {:} {}", e, output_location.clone()); } } - if let Err(e) = std::fs::write(&output_location, content) { + if let Err(e) = std::fs::write(&output_location, content) + { debug!("location: {}", output_location); debug!("error: {:#?}", e); - error!("[helper::backup_gameinfo] Failed to write backup to {} ({:})", output_location, e); - return Err(BeansError::GameinfoBackupFailure { reason: GameinfoBackupFailureReason::WriteFail(GameinfoBackupWriteFail{ - error: e, - location: output_location - })}); + error!( + "[helper::backup_gameinfo] Failed to write backup to {} ({:})", + output_location, e + ); + return Err(BeansError::GameinfoBackupFailure { + reason: GameinfoBackupFailureReason::WriteFail(GameinfoBackupWriteFail { + error: e, + location: output_location + }) + }); } println!("[backup_gameinfo] Created backup at {}", output_location); @@ -697,4 +914,4 @@ pub struct GithubReleaseItem pub html_url: String, pub draft: bool, pub prerelease: bool -} \ No newline at end of file +} diff --git a/src/helper/windows.rs b/src/helper/windows.rs index cf17b30..80e963a 100644 --- a/src/helper/windows.rs +++ b/src/helper/windows.rs @@ -1,36 +1,43 @@ -use winreg::enums::HKEY_CURRENT_USER; use std::backtrace::Backtrace; -use winreg::RegKey; -use crate::BeansError; -use crate::helper::format_directory_path; + +use winreg::{enums::HKEY_CURRENT_USER, + RegKey}; + +use crate::{helper::format_directory_path, + BeansError}; /// TODO use windows registry to get the SourceModInstallPath /// HKEY_CURRENT_USER\Software\Value\Steam /// Key: SourceModInstallPath pub fn find_sourcemod_path() -> Result { - match RegKey::predef(HKEY_CURRENT_USER).open_subkey(String::from("Software\\Valve\\Steam")) { - Ok(rkey) => { + match RegKey::predef(HKEY_CURRENT_USER).open_subkey(String::from("Software\\Valve\\Steam")) + { + Ok(rkey) => + { let x: std::io::Result = rkey.get_value("SourceModInstallPath"); - match x { - Ok(val) => { - Ok(format_directory_path(val)) - }, - Err(e) => { + match x + { + Ok(val) => Ok(format_directory_path(val)), + Err(e) => + { return Err(BeansError::RegistryKeyFailure { - msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed".to_string(), + msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" + .to_string(), error: e, backtrace: Backtrace::capture() }); } } - }, - Err(e) => { + } + Err(e) => + { return Err(BeansError::RegistryKeyFailure { - msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed".to_string(), + msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" + .to_string(), error: e, backtrace: Backtrace::capture() }); } } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 77e85ae..34c66d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,18 +3,18 @@ use include_flate::flate; +mod ctx; pub mod depends; pub mod helper; -pub mod wizard; pub mod version; +pub mod wizard; pub mod workflows; -mod ctx; pub use ctx::*; mod error; pub use error::*; +pub mod appvar; pub mod butler; pub mod flags; -pub mod appvar; pub mod logger; /// NOTE do not change, fetches from the version of beans-rs on build @@ -23,14 +23,15 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const SENTRY_URL: &str = "https://9df80170f0a4411bb9c834ac54734380@sentry.kate.pet/1"; /// content to display when showing a message box on panic. pub const PANIC_MSG_CONTENT: &str = include_str!("text/msgbox_panic_text.txt"); -/// once everything is done, do we wait for the user to press enter before exiting? +/// once everything is done, do we wait for the user to press enter before +/// exiting? /// /// just like the `pause` thing in batch. pub static mut PAUSE_ONCE_DONE: bool = false; -/// When `true`, everything that prompts the user for Y/N should use the default option. +/// When `true`, everything that prompts the user for Y/N should use the default +/// option. pub static mut PROMPT_DO_WHATEVER: bool = false; - // ------------------------------------------------------------------------ // please dont change consts below unless you know what you're doing <3 // diff --git a/src/logger.rs b/src/logger.rs index 068dcbd..4c5eb37 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,70 +1,102 @@ -use std::io; -use std::io::Write; -use std::sync::Mutex; -use std::time::Instant; +use std::{io, + io::Write, + sync::Mutex, + time::Instant}; + use lazy_static::lazy_static; -use log::{LevelFilter, Log, Metadata, Record}; +use log::{LevelFilter, + Log, + Metadata, + Record}; lazy_static! { static ref LOGGER: CustomLogger = CustomLogger { - inner: Mutex::new(None), + inner: Mutex::new(None) }; } -struct CustomLogger { - inner: Mutex>, +struct CustomLogger +{ + inner: Mutex> } -impl CustomLogger { +impl CustomLogger +{ // Set this `CustomLogger`'s sink and reset the start time. - fn renew(&self, sink: T) { + fn renew( + &self, + sink: T + ) + { *self.inner.lock().unwrap() = Some(CustomLoggerInner { start: Instant::now(), sink: Box::new(sink), - sentry: sentry_log::SentryLogger::new().filter(|md| match md.level() { + sentry: sentry_log::SentryLogger::new().filter(|md| match md.level() + { log::Level::Error => LogFilter::Exception, log::Level::Warn => LogFilter::Event, - log::Level::Info | log::Level::Debug | log::Level::Trace => LogFilter::Breadcrumb, + log::Level::Info | log::Level::Debug | log::Level::Trace => LogFilter::Breadcrumb }) }); } } -impl Log for CustomLogger { - fn enabled(&self, _: &Metadata) -> bool { +impl Log for CustomLogger +{ + fn enabled( + &self, + _: &Metadata + ) -> bool + { true } - fn log(&self, record: &Record) { - if !self.enabled(record.metadata()) { + fn log( + &self, + record: &Record + ) + { + if !self.enabled(record.metadata()) + { return; } - if let Some(ref mut inner) = *self.inner.lock().unwrap() { + if let Some(ref mut inner) = *self.inner.lock().unwrap() + { inner.log(record); } } - fn flush(&self) { - if let Some(ref mut inner) = *self.inner.lock().unwrap() { + fn flush(&self) + { + if let Some(ref mut inner) = *self.inner.lock().unwrap() + { inner.sentry.flush(); } } } -struct CustomLoggerInner { +struct CustomLoggerInner +{ start: Instant, sink: Box, sentry: sentry_log::SentryLogger } use colored::Colorize; -use sentry_log::{LogFilter, NoopLogger}; +use sentry_log::{LogFilter, + NoopLogger}; -impl CustomLoggerInner { - fn log(&mut self, record: &Record) { +impl CustomLoggerInner +{ + fn log( + &mut self, + record: &Record + ) + { let mut do_print = true; unsafe { - if LOG_FILTER < record.level() { + if LOG_FILTER < record.level() + { do_print = false; } } @@ -82,7 +114,8 @@ impl CustomLoggerInner { unsafe { data = LOG_FORMAT.to_string(); } - data = data.replace("#HOURS", &format!("{:02}", hours)) + data = data + .replace("#HOURS", &format!("{:02}", hours)) .replace("#MINUTES", &format!("{:02}", minutes)) .replace("#SECONDS", &format!("{:02}", seconds)) .replace("#MILLISECONDS", &format!("{:03}", milliseconds)) @@ -91,22 +124,21 @@ impl CustomLoggerInner { .replace("#CONTENT", &format!("{}", record.args())); unsafe { - if LOG_COLOR { - data = match record.level() { + if LOG_COLOR + { + data = match record.level() + { log::Level::Error => data.red(), log::Level::Warn => data.yellow(), log::Level::Info => data.normal(), log::Level::Debug => data.green(), - log::Level::Trace => data.blue(), - }.to_string() + log::Level::Trace => data.blue() + } + .to_string() } } - let _ = write!( - self.sink, - "{}\n", - data - ); + let _ = write!(self.sink, "{}\n", data); } self.sentry.log(&record); } @@ -120,15 +152,18 @@ pub fn set_filter(filter: LevelFilter) static mut LOG_FILTER: LevelFilter = LevelFilter::Trace; pub static mut LOG_FORMAT: &str = LOG_FORMAT_DEFAULT; pub static mut LOG_COLOR: bool = true; -pub const LOG_FORMAT_DEFAULT: &str = "[#HOURS:#MINUTES:#SECONDS.#MILLISECONDS] (#THREAD) #LEVEL #CONTENT"; +pub const LOG_FORMAT_DEFAULT: &str = + "[#HOURS:#MINUTES:#SECONDS.#MILLISECONDS] (#THREAD) #LEVEL #CONTENT"; pub const LOG_FORMAT_MINIMAL: &str = "#LEVEL #CONTENT"; -pub fn log_to(sink: T) { +pub fn log_to(sink: T) +{ LOGGER.renew(sink); log::set_max_level(LevelFilter::max()); // The only possible error is if this has been called before let _ = log::set_logger(&*LOGGER); assert_eq!(log::logger() as *const dyn Log, &*LOGGER as *const dyn Log); } -pub fn log_to_stdout() { +pub fn log_to_stdout() +{ log_to(io::stdout()); -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 685f20d..4638862 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,30 @@ #![feature(panic_info_message)] use std::str::FromStr; -use clap::{Arg, ArgAction, ArgMatches, Command}; -use log::{debug, error, info, LevelFilter, trace}; -use beans_rs::{BeansError, flags, helper, PANIC_MSG_CONTENT, RunnerContext, wizard}; -use beans_rs::flags::LaunchFlag; -use beans_rs::helper::parse_location; -use beans_rs::SourceModDirectoryParam; -use beans_rs::workflows::{CleanWorkflow, InstallWorkflow, UninstallWorkflow, UpdateWorkflow, VerifyWorkflow}; + +use beans_rs::{flags, + flags::LaunchFlag, + helper, + helper::parse_location, + wizard, + workflows::{CleanWorkflow, + InstallWorkflow, + UninstallWorkflow, + UpdateWorkflow, + VerifyWorkflow}, + BeansError, + RunnerContext, + SourceModDirectoryParam, + PANIC_MSG_CONTENT}; +use clap::{Arg, + ArgAction, + ArgMatches, + Command}; +use log::{debug, + error, + info, + trace, + LevelFilter}; pub const DEFAULT_LOG_LEVEL_RELEASE: LevelFilter = LevelFilter::Info; #[cfg(debug_assertions)] @@ -15,7 +32,8 @@ pub const DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Trace; #[cfg(not(debug_assertions))] pub const DEFAULT_LOG_LEVEL: LevelFilter = DEFAULT_LOG_LEVEL_RELEASE; -fn main() { +fn main() +{ #[cfg(target_os = "windows")] let _ = winconsole::window::show(true); #[cfg(target_os = "windows")] @@ -47,7 +65,8 @@ fn init_flags() flags::remove_flag(LaunchFlag::DEBUG_MODE); #[cfg(debug_assertions)] flags::add_flag(LaunchFlag::DEBUG_MODE); - if std::env::var("BEANS_DEBUG").is_ok_and(|x| x == "1") { + if std::env::var("BEANS_DEBUG").is_ok_and(|x| x == "1") + { flags::add_flag(LaunchFlag::DEBUG_MODE); } flags::add_flag(LaunchFlag::STANDALONE_APP); @@ -59,47 +78,54 @@ fn init_panic_handle() std::panic::set_hook(Box::new(move |info| { debug!("[panic::set_hook] showing msgbox to notify user"); let mut x = String::new(); - if let Some(m) = info.message() { + if let Some(m) = info.message() + { x = format!("{:#?}", m); } custom_panic_handle(x); debug!("[panic::set_hook] calling sentry_panic::panic_handler"); sentry::integrations::panic::panic_handler(&info); - if flags::has_flag(LaunchFlag::DEBUG_MODE) { + if flags::has_flag(LaunchFlag::DEBUG_MODE) + { eprintln!("{:#?}", info); } logic_done(); })); } #[cfg(target_os = "windows")] -fn fix_msgbox_txt(txt: String) -> String { +fn fix_msgbox_txt(txt: String) -> String +{ txt.replace("\\n", "\r\n") } #[cfg(not(target_os = "windows"))] -fn fix_msgbox_txt(txt: String) -> String { +fn fix_msgbox_txt(txt: String) -> String +{ txt } fn custom_panic_handle(msg: String) { unsafe { - if beans_rs::PAUSE_ONCE_DONE { + if beans_rs::PAUSE_ONCE_DONE + { let mut txt = PANIC_MSG_CONTENT.to_string().replace("$err_msg", &msg); txt = fix_msgbox_txt(txt); std::thread::spawn(move || { - let d = native_dialog::MessageDialog::new() .set_type(native_dialog::MessageType::Error) .set_title("beans - fatal error!") .set_text(&txt) .show_alert(); - if let Err(e) = d { + if let Err(e) = d + { sentry::capture_error(&e); eprintln!("Failed to show MessageDialog {:#?}", e); eprintln!("[msgbox_panic] Come on, we failed to show a messagebox? Well, the error has been reported and we're on it."); eprintln!("[msgbox_panic] PLEASE report this to kate@dariox.club with as much info as possible <3"); } }); - } else { + } + else + { info!("This error has been reported to the developers"); } } @@ -109,20 +135,24 @@ fn custom_panic_handle(msg: String) fn logic_done() { unsafe { - if beans_rs::PAUSE_ONCE_DONE { + if beans_rs::PAUSE_ONCE_DONE + { let _ = helper::get_input("Press enter/return to exit"); } } } -pub struct Launcher { - /// Output location. When none, `SourceModDirectoryParam::default()` will be used. +pub struct Launcher +{ + /// Output location. When none, `SourceModDirectoryParam::default()` will be + /// used. pub to_location: Option, /// Output of `Command.matches()` pub root_matches: ArgMatches } impl Launcher { - /// Create argument for specifying the location where the sourcemods directory is. + /// Create argument for specifying the location where the sourcemods + /// directory is. fn create_location_arg() -> Arg { Arg::new("location") @@ -188,15 +218,18 @@ impl Launcher ]); let mut i = Self::new(&cmd.get_matches()); - if let Ok(r) = helper::beans_has_update().await { - if let Some(v) = r { + if let Ok(r) = helper::beans_has_update().await + { + if let Some(v) = r + { info!("A new version of beans-rs is available!"); info!("{}", v.html_url); } } i.subcommand_processor().await; } - pub fn new(matches: &ArgMatches) -> Self { + pub fn new(matches: &ArgMatches) -> Self + { let mut i = Self { to_location: None, root_matches: matches.clone() @@ -209,21 +242,25 @@ impl Launcher return i; } - /// add `LaunchFlag::DEBUG_MODE` to `flags` when the `--debug` parameter flag is used. + /// add `LaunchFlag::DEBUG_MODE` to `flags` when the `--debug` parameter + /// flag is used. pub fn set_debug(&mut self) { - if self.root_matches.get_flag("no-debug") { + if self.root_matches.get_flag("no-debug") + { flags::remove_flag(LaunchFlag::DEBUG_MODE); beans_rs::logger::set_filter(DEFAULT_LOG_LEVEL_RELEASE); info!("Disabled Debug Mode"); } - else if self.root_matches.get_flag("debug") { + else if self.root_matches.get_flag("debug") + { flags::add_flag(LaunchFlag::DEBUG_MODE); beans_rs::logger::set_filter(LevelFilter::max()); trace!("Debug mode enabled"); } } - /// Set `PAUSE_ONCE_DONE` to `false` when `--no-pause` is provided. Otherwise, set it to `true`. + /// Set `PAUSE_ONCE_DONE` to `false` when `--no-pause` is provided. + /// Otherwise, set it to `true`. pub fn set_no_pause(&mut self) { unsafe { @@ -235,7 +272,8 @@ impl Launcher pub fn find_arg_sourcemods_location(matches: &ArgMatches) -> Option { let mut sml_dir_manual: Option = None; - if let Some(x) = matches.get_one::("location") { + if let Some(x) = matches.get_one::("location") + { sml_dir_manual = Some(parse_location(x.to_string())); info!("[Launcher::set_to_location] Found in arguments! {}", x); } @@ -245,27 +283,35 @@ impl Launcher /// main handler for subcommand processing. pub async fn subcommand_processor(&mut self) { - match self.root_matches.clone().subcommand() { - Some(("install", i_matches)) => { + match self.root_matches.clone().subcommand() + { + Some(("install", i_matches)) => + { self.task_install(i_matches).await; - }, - Some(("verify", v_matches)) => { + } + Some(("verify", v_matches)) => + { self.task_verify(v_matches).await; - }, - Some(("update", u_matches)) => { + } + Some(("update", u_matches)) => + { self.task_update(u_matches).await; - }, - Some(("uninstall", ui_matches)) => { + } + Some(("uninstall", ui_matches)) => + { self.task_uninstall(ui_matches).await; - }, - Some(("wizard", wz_matches)) => { + } + Some(("wizard", wz_matches)) => + { self.to_location = Launcher::find_arg_sourcemods_location(wz_matches); self.task_wizard().await; - }, - Some(("clean-tmp", _)) => { + } + Some(("clean-tmp", _)) => + { self.task_clean_tmp().await; - }, - _ => { + } + _ => + { self.task_wizard().await; } } @@ -273,7 +319,8 @@ impl Launcher pub fn set_prompt_do_whatever(&mut self) { - if self.root_matches.get_flag("confirm") { + if self.root_matches.get_flag("confirm") + { unsafe { beans_rs::PROMPT_DO_WHATEVER = true; } @@ -284,10 +331,9 @@ impl Launcher /// Returns SourceModDirectoryParam::default() when `to_location` is `None`. fn try_get_smdp(&mut self) -> SourceModDirectoryParam { - match &self.to_location { - Some(v) => { - SourceModDirectoryParam::WithLocation(v.to_string()) - }, + match &self.to_location + { + Some(v) => SourceModDirectoryParam::WithLocation(v.to_string()), None => SourceModDirectoryParam::default() } } @@ -296,21 +342,28 @@ impl Launcher pub async fn task_wizard(&mut self) { let x = self.try_get_smdp(); - if let Err(e) = wizard::WizardContext::run(x).await { + if let Err(e) = wizard::WizardContext::run(x).await + { panic!("Failed to run WizardContext {:#?}", e); - } else { + } + else + { logic_done(); } } /// handler for the `install` subcommand /// - /// NOTE this function uses `panic!` when `InstallWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. - pub async fn task_install(&mut self, matches: &ArgMatches) + /// NOTE this function uses `panic!` when `InstallWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. + pub async fn task_install( + &mut self, + matches: &ArgMatches + ) { self.to_location = Launcher::find_arg_sourcemods_location(&matches); - if matches.get_flag("confirm") { + if matches.get_flag("confirm") + { unsafe { beans_rs::PROMPT_DO_WHATEVER = true; } @@ -323,26 +376,40 @@ impl Launcher // // `else if let` is used for checking the `--from` parameter, // so a return isn't required. - if let Some(x) = matches.get_one::("target-version") { + if let Some(x) = matches.get_one::("target-version") + { self.task_install_version_specific(ctx, x.clone()).await; } - // manually install from specific `.tar.zstd` file when the // --from parameter is provided. otherwise we install/reinstall // the latest version to whatever sourcemods directory is used - else if let Some(x) = matches.get_one::("from") { - info!("Manually installing from {} to {}", x.clone(), ctx.sourcemod_path.clone()); - if let Err(e) = InstallWorkflow::install_from(x.clone(), ctx.sourcemod_path.clone(), None).await { + else if let Some(x) = matches.get_one::("from") + { + info!( + "Manually installing from {} to {}", + x.clone(), + ctx.sourcemod_path.clone() + ); + if let Err(e) = + InstallWorkflow::install_from(x.clone(), ctx.sourcemod_path.clone(), None).await + { error!("Failed to run InstallWorkflow::install_from"); sentry::capture_error(&e); panic!("{:#?}", e); - } else { + } + else + { logic_done(); } - } else { - if let Err(e) = InstallWorkflow::wizard(&mut ctx).await { + } + else + { + if let Err(e) = InstallWorkflow::wizard(&mut ctx).await + { panic!("Failed to run InstallWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } @@ -350,114 +417,158 @@ impl Launcher /// handler for the `install` subcommand where the `--target-version` /// parameter is provided. /// - /// NOTE this function uses `expect` on `InstallWorkflow::install_version`. panics are handled - /// and are reported via sentry. - pub async fn task_install_version_specific(&mut self, ctx: RunnerContext, version_str: String) + /// NOTE this function uses `expect` on `InstallWorkflow::install_version`. + /// panics are handled and are reported via sentry. + pub async fn task_install_version_specific( + &mut self, + ctx: RunnerContext, + version_str: String + ) { - let version = match usize::from_str(&version_str) { + let version = match usize::from_str(&version_str) + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); - error!("Failed to parse version argument \"{version_str}\": {:#?}", e); + error!( + "Failed to parse version argument \"{version_str}\": {:#?}", + e + ); logic_done(); return; } }; - let mut wf = InstallWorkflow - { + let mut wf = InstallWorkflow { context: ctx }; - if let Err(e) = wf.install_version(version).await { + if let Err(e) = wf.install_version(version).await + { error!("Failed to run InstallWorkflow::install_version"); sentry::capture_error(&e); panic!("{:#?}", e); - } else { + } + else + { logic_done(); } } /// handler for the `verify` subcommand /// - /// NOTE this function uses `panic!` when `VerifyWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. - pub async fn task_verify(&mut self, matches: &ArgMatches) + /// NOTE this function uses `panic!` when `VerifyWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. + pub async fn task_verify( + &mut self, + matches: &ArgMatches + ) { self.to_location = Launcher::find_arg_sourcemods_location(&matches); let mut ctx = self.try_create_context().await; - if let Err(e) = VerifyWorkflow::wizard(&mut ctx).await { + if let Err(e) = VerifyWorkflow::wizard(&mut ctx).await + { panic!("Failed to run VerifyWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } /// handler for the `update` subcommand /// - /// NOTE this function uses `panic!` when `UpdateWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. - pub async fn task_update(&mut self, matches: &ArgMatches) + /// NOTE this function uses `panic!` when `UpdateWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. + pub async fn task_update( + &mut self, + matches: &ArgMatches + ) { self.to_location = Launcher::find_arg_sourcemods_location(&matches); let mut ctx = self.try_create_context().await; - if let Err(e) = UpdateWorkflow::wizard(&mut ctx).await { + if let Err(e) = UpdateWorkflow::wizard(&mut ctx).await + { panic!("Failed to run UpdateWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } /// Handler for the `clean-tmp` subcommand. /// - /// NOTE this function uses `panic!` when `CleanWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. + /// NOTE this function uses `panic!` when `CleanWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. pub async fn task_clean_tmp(&mut self) { let mut ctx = self.try_create_context().await; - if let Err(e) = CleanWorkflow::wizard(&mut ctx) { + if let Err(e) = CleanWorkflow::wizard(&mut ctx) + { panic!("Failed to run CleanWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } /// handler for the `uninstall` subcommand /// - /// NOTE this function uses `panic!` when `UninstallWorkflow::wizard` fails. panics are handled - /// and are reported via sentry. - pub async fn task_uninstall(&mut self, matches: &ArgMatches) + /// NOTE this function uses `panic!` when `UninstallWorkflow::wizard` fails. + /// panics are handled and are reported via sentry. + pub async fn task_uninstall( + &mut self, + matches: &ArgMatches + ) { self.to_location = Launcher::find_arg_sourcemods_location(&matches); let mut ctx = self.try_create_context().await; - if let Err(e) = UninstallWorkflow::wizard(&mut ctx).await { + if let Err(e) = UninstallWorkflow::wizard(&mut ctx).await + { panic!("Failed to run UninstallWorkflow {:#?}", e); - } else { + } + else + { logic_done(); } } - /// try and create an instance of `RunnerContext` via the `create_auto` method while setting - /// the `sml_via` parameter to the output of `self.try_get_smdp()` + /// try and create an instance of `RunnerContext` via the `create_auto` + /// method while setting the `sml_via` parameter to the output of + /// `self.try_get_smdp()` /// - /// on failure, `panic!` is called. but that's okay because a dialog is shown (in - /// `init_panic_handle`) and the error is reported via sentry. - async fn try_create_context(&mut self) -> RunnerContext { - match RunnerContext::create_auto(self.try_get_smdp()).await { + /// on failure, `panic!` is called. but that's okay because a dialog is + /// shown (in `init_panic_handle`) and the error is reported via sentry. + async fn try_create_context(&mut self) -> RunnerContext + { + match RunnerContext::create_auto(self.try_get_smdp()).await + { Ok(v) => v, - Err(e) => { + Err(e) => + { error!("[try_create_context] {:}", e); trace!("======== Full Error ========"); trace!("{:#?}", &e); show_msgbox_error(format!("{:}", &e)); - let do_report = match e { - BeansError::GameStillRunning { .. } => false, - BeansError::LatestVersionAlreadyInstalled { .. } => false, - BeansError::FreeSpaceCheckFailure { .. } => false, + let do_report = match e + { + BeansError::GameStillRunning { + .. + } => false, + BeansError::LatestVersionAlreadyInstalled { + .. + } => false, + BeansError::FreeSpaceCheckFailure { + .. + } => false, _ => true }; - if do_report { + if do_report + { sentry::capture_error(&e); } logic_done(); @@ -466,16 +577,19 @@ impl Launcher } } } -fn show_msgbox_error(text: String) { +fn show_msgbox_error(text: String) +{ unsafe { - if beans_rs::PAUSE_ONCE_DONE { + if beans_rs::PAUSE_ONCE_DONE + { std::thread::spawn(move || { let d = native_dialog::MessageDialog::new() .set_type(native_dialog::MessageType::Error) .set_title("beans - fatal error!") .set_text(&format!("{}", fix_msgbox_txt(text))) .show_alert(); - if let Err(e) = d { + if let Err(e) = d + { sentry::capture_error(&e); eprintln!("Failed to show MessageDialog {:#?}", e); } @@ -483,5 +597,3 @@ fn show_msgbox_error(text: String) { } } } - - diff --git a/src/version.rs b/src/version.rs index 14d2afe..e689605 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,35 +1,49 @@ -use std::backtrace::Backtrace; -use std::collections::HashMap; -use std::fs::read_to_string; -use std::io::Write; -use log::{debug, error, trace}; -use crate::helper; -use crate::helper::{find_sourcemod_path, InstallType}; -use crate::BeansError; +use std::{backtrace::Backtrace, + collections::HashMap, + fs::read_to_string, + io::Write}; -/// get the current version installed via the .adastral file in the sourcemod mod folder. -/// will parse the value of `version` as usize. +use log::{debug, + error, + trace}; + +use crate::{helper, + helper::{find_sourcemod_path, + InstallType}, + BeansError}; + +/// get the current version installed via the .adastral file in the sourcemod +/// mod folder. will parse the value of `version` as usize. pub fn get_current_version(sourcemods_location: Option) -> Option { let install_state = helper::install_state(sourcemods_location.clone()); - if install_state != InstallType::Adastral { + if install_state != InstallType::Adastral + { return None; } - match get_mod_location(sourcemods_location) { - Some(smp_x) => { + match get_mod_location(sourcemods_location) + { + Some(smp_x) => + { // TODO generate BeansError instead of using .expect let location = format!("{}.adastral", smp_x); - let content = read_to_string(&location).expect(format!("Failed to open {}", location).as_str()); - let data: AdastralVersionFile = serde_json::from_str(&content).expect(format!("Failed to deserialize data at {}", location).as_str()); - let parsed = data.version.parse::().expect(format!("Failed to convert version to usize! ({})", data.version).as_str()); + let content = + read_to_string(&location).expect(format!("Failed to open {}", location).as_str()); + let data: AdastralVersionFile = serde_json::from_str(&content) + .expect(format!("Failed to deserialize data at {}", location).as_str()); + let parsed = data + .version + .parse::() + .expect(format!("Failed to convert version to usize! ({})", data.version).as_str()); Some(parsed) - }, + } None => None } } fn get_version_location(sourcemods_location: Option) -> Option { - match get_mod_location(sourcemods_location) { + match get_mod_location(sourcemods_location) + { Some(v) => Some(format!("{}.adastral", v)), None => None } @@ -37,44 +51,70 @@ fn get_version_location(sourcemods_location: Option) -> Option /// get the full location of the sourcemod mod directory. fn get_mod_location(sourcemods_location: Option) -> Option { - let smp_x = match sourcemods_location { + let smp_x = match sourcemods_location + { Some(v) => v, - None => match find_sourcemod_path() { + None => match find_sourcemod_path() + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); - debug!("[version::get_mod_location] {} {:#?}", BeansError::SourceModLocationNotFound, e); + debug!( + "[version::get_mod_location] {} {:#?}", + BeansError::SourceModLocationNotFound, + e + ); return None; } } }; - return Some(helper::join_path(smp_x, crate::data_dir())) + return Some(helper::join_path(smp_x, crate::data_dir())); } -/// migrate from old file (.revision) to new file (.adastral) in sourcemod mod directory. +/// migrate from old file (.revision) to new file (.adastral) in sourcemod mod +/// directory. pub fn update_version_file(sourcemods_location: Option) -> Result<(), BeansError> { let install_state = helper::install_state(sourcemods_location.clone()); - if install_state == InstallType::Adastral { - debug!("[version::update_version_file] install_state is {:#?}, ignoring.", install_state); + if install_state == InstallType::Adastral + { + debug!( + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state + ); return Ok(()); } // ignore :) - else if install_state == InstallType::OtherSourceManual { - debug!("[version::update_version_file] install_state is {:#?}, ignoring.", install_state); + else if install_state == InstallType::OtherSourceManual + { + debug!( + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state + ); return Ok(()); } // ignore :) - else if install_state == InstallType::NotInstalled { - debug!("[version::update_version_file] install_state is {:#?}, ignoring.", install_state); + else if install_state == InstallType::NotInstalled + { + debug!( + "[version::update_version_file] install_state is {:#?}, ignoring.", + install_state + ); return Ok(()); } - let smp_x = match sourcemods_location { + let smp_x = match sourcemods_location + { Some(v) => v, - None => match find_sourcemod_path() { + None => match find_sourcemod_path() + { Ok(v) => v, - Err(e) => { - error!("[version::update_version_file] Could not find sourcemods folder! {:}", e); + Err(e) => + { + error!( + "[version::update_version_file] Could not find sourcemods folder! {:}", + e + ); debug!("{:#?}", e); sentry::capture_error(&e); return Err(e); @@ -85,10 +125,15 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be let data_dir = helper::join_path(smp_x, crate::data_dir()); let old_version_file_location = format!("{}.revision", &data_dir); - let old_version_file_content = match read_to_string(&old_version_file_location) { + let old_version_file_content = match read_to_string(&old_version_file_location) + { Ok(v) => v, - Err(e) => { - debug!("[update_version_file] failed to read {}. {:#?}", old_version_file_location, e); + Err(e) => + { + debug!( + "[update_version_file] failed to read {}. {:#?}", + old_version_file_location, e + ); sentry::capture_error(&e); return Err(BeansError::VersionFileReadFailure { error: e, @@ -96,10 +141,15 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be }); } }; - let old_version_idx = match old_version_file_content.parse::() { + let old_version_idx = match old_version_file_content.parse::() + { Ok(v) => v, - Err(e) => { - debug!("[update_version_file] Failed to parse content {} caused error {:}", old_version_file_content, e); + Err(e) => + { + debug!( + "[update_version_file] Failed to parse content {} caused error {:}", + old_version_file_content, e + ); sentry::capture_error(&e); return Err(BeansError::VersionFileParseFailure { error: e, @@ -109,15 +159,16 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be } }; - let new_file_content = AdastralVersionFile - { + let new_file_content = AdastralVersionFile { version: old_version_idx.to_string() }; let new_version_file_location = format!("{}.adastral", &data_dir); - let new_version_file_content = match serde_json::to_string(&new_file_content) { + let new_version_file_content = match serde_json::to_string(&new_file_content) + { Ok(v) => v, - Err(e) => { + Err(e) => + { sentry::capture_error(&e); return Err(BeansError::VersionFileSerialize { error: e, @@ -126,19 +177,21 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be } }; - if let Err(e) = std::fs::write(new_version_file_location.clone(), new_version_file_content) { + if let Err(e) = std::fs::write(new_version_file_location.clone(), new_version_file_content) + { sentry::capture_error(&e); return Err(BeansError::VersionFileMigrationFailure { error: e, location: new_version_file_location }); } - if let Err(e) = std::fs::remove_file(old_version_file_location.clone()) { + if let Err(e) = std::fs::remove_file(old_version_file_location.clone()) + { sentry::capture_error(&e); return Err(BeansError::VersionFileMigrationDeleteFailure { error: e, location: old_version_file_location - }) + }); } Ok(()) @@ -148,10 +201,15 @@ pub fn update_version_file(sourcemods_location: Option) -> Result<(), Be pub async fn get_version_list() -> Result { let av = crate::appvar::parse(); - let response = match reqwest::get(&av.remote_info.versions_url).await { + let response = match reqwest::get(&av.remote_info.versions_url).await + { Ok(v) => v, - Err(e) => { - error!("[version::get_version_list] Failed to get available versions! {:}", e); + Err(e) => + { + error!( + "[version::get_version_list] Failed to get available versions! {:}", + e + ); sentry::capture_error(&e); return Err(BeansError::Reqwest { error: e, @@ -160,7 +218,10 @@ pub async fn get_version_list() -> Result } }; let response_text = response.text().await?; - trace!("[version::get_version_list] response text: {}", response_text); + trace!( + "[version::get_version_list] response text: {}", + response_text + ); let data: RemoteVersionResponse = serde_json::from_str(&response_text)?; return Ok(data); @@ -168,38 +229,46 @@ pub async fn get_version_list() -> Result /// Version file that is used as `.adastral` in the sourcemod mod folder. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AdastralVersionFile { +pub struct AdastralVersionFile +{ pub version: String } -impl AdastralVersionFile { - pub fn write(&self, sourcemods_location: Option) -> Result<(), BeansError> { - match get_version_location(sourcemods_location) { - Some(vl) => { - let f = match helper::file_exists(vl.clone()) { +impl AdastralVersionFile +{ + pub fn write( + &self, + sourcemods_location: Option + ) -> Result<(), BeansError> + { + match get_version_location(sourcemods_location) + { + Some(vl) => + { + let f = match helper::file_exists(vl.clone()) + { true => std::fs::File::create(vl.clone()), false => std::fs::File::create_new(vl.clone()) }; - match f { - Ok(mut file) => { - match serde_json::to_string(self) { - Ok(ser) => { - match file.write_all(ser.as_bytes()) { - Ok(_) => Ok(()), - Err(e) => Err(BeansError::FileWriteFailure { - location: vl, - error: e - }) - } - }, - Err(e) => Err(e.into()) - } + match f + { + Ok(mut file) => match serde_json::to_string(self) + { + Ok(ser) => match file.write_all(ser.as_bytes()) + { + Ok(_) => Ok(()), + Err(e) => Err(BeansError::FileWriteFailure { + location: vl, + error: e + }) + }, + Err(e) => Err(e.into()) }, Err(e) => Err(BeansError::FileOpenFailure { location: vl, error: e }) } - }, + } None => Err(BeansError::SourceModLocationNotFound) } } @@ -231,6 +300,7 @@ pub struct RemotePatch { pub url: String, pub file: String, - /// Amount of file space required for temporary file. Assumed to be measured in bytes. + /// Amount of file space required for temporary file. Assumed to be measured + /// in bytes. pub tempreq: usize -} \ No newline at end of file +} diff --git a/src/wizard.rs b/src/wizard.rs index 952adb6..a3b7bd1 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -1,10 +1,26 @@ -use crate::{BeansError, depends, flags, helper, RunnerContext, SourceModDirectoryParam}; -use crate::helper::{find_sourcemod_path, InstallType, parse_location}; -use async_recursion::async_recursion; -use log::{debug, error, info, trace}; use std::backtrace::Backtrace; -use crate::flags::LaunchFlag; -use crate::workflows::{CleanWorkflow, InstallWorkflow, UninstallWorkflow, UpdateWorkflow, VerifyWorkflow}; + +use async_recursion::async_recursion; +use log::{debug, + error, + info, + trace}; + +use crate::{depends, + flags, + flags::LaunchFlag, + helper, + helper::{find_sourcemod_path, + parse_location, + InstallType}, + workflows::{CleanWorkflow, + InstallWorkflow, + UninstallWorkflow, + UpdateWorkflow, + VerifyWorkflow}, + BeansError, + RunnerContext, + SourceModDirectoryParam}; #[derive(Debug, Clone)] pub struct WizardContext @@ -18,25 +34,30 @@ impl WizardContext pub async fn run(sml_via: SourceModDirectoryParam) -> Result<(), BeansError> { depends::try_write_deps(); - if let Err(e) = depends::try_install_vcredist().await { + if let Err(e) = depends::try_install_vcredist().await + { sentry::capture_error(&e); println!("Failed to install vcredist! {:}", e); debug!("[WizardContext::run] {:#?}", e); } let sourcemod_path = parse_location(match sml_via { - SourceModDirectoryParam::AutoDetect => { + SourceModDirectoryParam::AutoDetect => + { debug!("[WizardContext::run] Auto-detecting sourcemods directory"); get_path() - }, - SourceModDirectoryParam::WithLocation(loc) => { + } + SourceModDirectoryParam::WithLocation(loc) => + { debug!("[WizardContext::run] Using specified location {}", loc); loc } }); - let version_list = match crate::version::get_version_list().await { + let version_list = match crate::version::get_version_list().await + { Ok(v) => v, - Err(e) => { + Err(e) => + { trace!("[WizardContext::run] Failed to run version::get_version_list()"); trace!("{:#?}", e); sentry::capture_error(&e); @@ -44,7 +65,8 @@ impl WizardContext } }; - if helper::install_state(Some(sourcemod_path.clone())) == InstallType::OtherSource { + if helper::install_state(Some(sourcemod_path.clone())) == InstallType::OtherSource + { crate::version::update_version_file(Some(sourcemod_path.clone()))?; } @@ -55,8 +77,7 @@ impl WizardContext appvar: crate::appvar::parse() }; - let mut i = Self - { + let mut i = Self { context: ctx, menu_trigger_count: 0u32 }; @@ -70,11 +91,17 @@ impl WizardContext pub async fn menu<'a>(&'a mut self) { let av = crate::appvar::AppVarData::get(); - if self.menu_trigger_count == 0 { - if let Some(cv) = self.context.current_version { + if self.menu_trigger_count == 0 + { + if let Some(cv) = self.context.current_version + { let (rv, _) = self.context.latest_remote_version(); - if cv < rv { - println!("======== A new update for {} is available! (v{rv}) ========", av.mod_info.name_stylized); + if cv < rv + { + println!( + "======== A new update for {} is available! (v{rv}) ========", + av.mod_info.name_stylized + ); } } } @@ -87,34 +114,39 @@ impl WizardContext println!(); println!("q - Quit"); let user_input = helper::get_input("-- Enter option below --"); - match user_input.to_lowercase().as_str() { + match user_input.to_lowercase().as_str() + { "1" | "install" => WizardContext::menu_error_catch(self.task_install().await), "2" | "update" => WizardContext::menu_error_catch(self.task_update().await), "3" | "verify" => WizardContext::menu_error_catch(self.task_verify().await), - "c" | "clean" => { - Self::menu_error_catch(CleanWorkflow::wizard(&mut self.context)) - }, - "u" | "uninstall" => { + "c" | "clean" => Self::menu_error_catch(CleanWorkflow::wizard(&mut self.context)), + "u" | "uninstall" => + { Self::menu_error_catch(UninstallWorkflow::wizard(&mut self.context).await) - }, - "d" | "debug" => { + } + "d" | "debug" => + { flags::add_flag(LaunchFlag::DEBUG_MODE); info!("Debug mode enabled!"); self.menu().await; - }, - "panic" => { + } + "panic" => + { panic!() - }, + } "q" => std::process::exit(0), - _ => { + _ => + { println!("Unknown option \"{}\"", user_input); self.menu_trigger_count += 1; self.menu().await; } }; } - fn menu_error_catch(v: Result<(), BeansError>) { - if let Err(e) = v { + fn menu_error_catch(v: Result<(), BeansError>) + { + if let Err(e) = v + { let b = Backtrace::capture(); sentry::capture_error(&e); panic!("backtrace: {:#?}\n\nerror: {:#?}", b, e); @@ -139,8 +171,6 @@ impl WizardContext } } - - fn get_path() -> String { find_sourcemod_path().unwrap_or_else(|e| { @@ -152,13 +182,18 @@ fn get_path() -> String fn prompt_sourcemod_location() -> String { let res = helper::get_input("Please provide your sourcemods folder, then press enter."); - return if !helper::file_exists(res.clone()) { + return if !helper::file_exists(res.clone()) + { eprintln!("The location you provided doesn't exist. Try again."); prompt_sourcemod_location() - } else if !helper::is_directory(res.clone()) { + } + else if !helper::is_directory(res.clone()) + { eprintln!("The location you provided isn't a folder. Try again."); prompt_sourcemod_location() - } else { - res } + else + { + res + }; } diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index 3d2553c..8ec54ee 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -1,23 +1,30 @@ -use log::{info, warn}; -use crate::{BeansError, helper, RunnerContext}; +use log::{info, + warn}; + +use crate::{helper, + BeansError, + RunnerContext}; #[derive(Debug, Clone)] -pub struct CleanWorkflow { +pub struct CleanWorkflow +{ pub context: RunnerContext } -impl CleanWorkflow { +impl CleanWorkflow +{ pub fn wizard(_ctx: &mut RunnerContext) -> Result<(), BeansError> { let target_directory = helper::get_tmp_dir(); info!("[CleanWorkflow] Cleaning up {}", target_directory); - if helper::file_exists(target_directory.clone()) == false { + if helper::file_exists(target_directory.clone()) == false + { warn!("[CleanWorkflow] Temporary directory not found, nothing to clean.") } - // delete directory and it's contents (and error handling) - if let Err(e) = std::fs::remove_dir_all(&target_directory) { + if let Err(e) = std::fs::remove_dir_all(&target_directory) + { return Err(BeansError::CleanTempFailure { location: target_directory, error: e @@ -25,7 +32,8 @@ impl CleanWorkflow { } // re-creating the temporary directory (and error handling) - if let Err(e) = std::fs::create_dir(&target_directory) { + if let Err(e) = std::fs::create_dir(&target_directory) + { return Err(BeansError::DirectoryCreateFailure { location: target_directory, error: e @@ -35,4 +43,4 @@ impl CleanWorkflow { info!("[CleanWorkflow] Done!"); return Ok(()); } -} \ No newline at end of file +} diff --git a/src/workflows/install.rs b/src/workflows/install.rs index adf030b..fad1f0b 100644 --- a/src/workflows/install.rs +++ b/src/workflows/install.rs @@ -1,68 +1,92 @@ -use log::{debug, error, info, warn}; -use crate::{DownloadFailureReason, helper, RunnerContext}; -use crate::appvar::AppVarData; -use crate::BeansError; -use crate::version::{AdastralVersionFile, RemoteVersion}; +use log::{debug, + error, + info, + warn}; + +use crate::{appvar::AppVarData, + helper, + version::{AdastralVersionFile, + RemoteVersion}, + BeansError, + DownloadFailureReason, + RunnerContext}; #[derive(Debug, Clone)] -pub struct InstallWorkflow { +pub struct InstallWorkflow +{ pub context: RunnerContext } -impl InstallWorkflow { +impl InstallWorkflow +{ pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let (latest_remote_id, latest_remote) = ctx.latest_remote_version(); - if let Some(_cv) = ctx.current_version { + if let Some(_cv) = ctx.current_version + { println!("[InstallWorkflow::wizard] re-installing! game files will not be touched until extraction"); } Self::install_with_remote_version(ctx, latest_remote_id, latest_remote).await } - /// Prompt the user to confirm if they want to reinstall (when parameter `current_version` is Some) + /// Prompt the user to confirm if they want to reinstall (when parameter + /// `current_version` is Some) /// /// Will always return `true` when `crate::PROMPT_DO_WHATEVER` is `true`. /// - /// Returns: `true` when the installation should continue, `false` when we should silently abort. + /// Returns: `true` when the installation should continue, `false` when we + /// should silently abort. pub fn prompt_confirm(current_version: Option) -> bool { unsafe { - if crate::PROMPT_DO_WHATEVER { - info!("[InstallWorkflow::prompt_confirm] skipping since PROMPT_DO_WHATEVER is true"); - return true; + if crate::PROMPT_DO_WHATEVER + { + info!( + "[InstallWorkflow::prompt_confirm] skipping since PROMPT_DO_WHATEVER is true" + ); + return true; } } let av = AppVarData::get(); - if let Some(v) = current_version { - println!("[InstallWorkflow::prompt_confirm] Seems like {} is already installed (v{})", v, av.mod_info.name_stylized); + if let Some(v) = current_version + { + println!( + "[InstallWorkflow::prompt_confirm] Seems like {} is already installed (v{})", + v, av.mod_info.name_stylized + ); println!("Are you sure that you want to reinstall?"); println!("Yes/Y (default)"); println!("No/N"); let user_input = helper::get_input("-- Enter option below --"); - match user_input.to_lowercase().as_str() { - "y" | "yes" | "" => { - true - }, - "n" | "no" => { - false - }, - _ => { + match user_input.to_lowercase().as_str() + { + "y" | "yes" | "" => true, + "n" | "no" => false, + _ => + { println!("Unknown option \"{}\"", user_input.to_lowercase()); Self::prompt_confirm(current_version) } } - } else { + } + else + { true } } /// Install the specified version by its ID to the output directory. - pub async fn install_version(&mut self, version_id: usize) -> Result<(), BeansError> + pub async fn install_version( + &mut self, + version_id: usize + ) -> Result<(), BeansError> { - let target_version = match self.context.remote_version_list.versions.get(&version_id) { + let target_version = match self.context.remote_version_list.versions.get(&version_id) + { Some(v) => v, - None => { + None => + { error!("Could not find remote version {version_id}"); return Err(BeansError::RemoteVersionNotFound { version: Some(version_id) @@ -70,25 +94,40 @@ impl InstallWorkflow { } }; let mut ctx = self.context.clone(); - InstallWorkflow::install_with_remote_version(&mut ctx, version_id, target_version.clone()).await + InstallWorkflow::install_with_remote_version(&mut ctx, version_id, target_version.clone()) + .await } /// Install with a specific remote version. /// - /// Note: Will call Self::prompt_confirm, so set `crate::PROMPT_DO_WHATEVER` to `true` before you call - /// this function if you don't want to wait for a newline from stdin. - pub async fn install_with_remote_version(ctx: &mut RunnerContext, version_id: usize, version: RemoteVersion) - -> Result<(), BeansError> + /// Note: Will call Self::prompt_confirm, so set `crate::PROMPT_DO_WHATEVER` + /// to `true` before you call this function if you don't want to + /// wait for a newline from stdin. + pub async fn install_with_remote_version( + ctx: &mut RunnerContext, + version_id: usize, + version: RemoteVersion + ) -> Result<(), BeansError> { - if Self::prompt_confirm(ctx.current_version) == false { + if Self::prompt_confirm(ctx.current_version) == false + { info!("[InstallWorkflow] Operation aborted by user"); return Ok(()); } - println!("{:=>60}\nInstalling version {} to {}\n{0:=>60}", "=", version_id, &ctx.sourcemod_path); + println!( + "{:=>60}\nInstalling version {} to {}\n{0:=>60}", + "=", version_id, &ctx.sourcemod_path + ); let presz_loc = RunnerContext::download_package(version).await?; - Self::install_from(presz_loc.clone(), ctx.sourcemod_path.clone(), Some(version_id)).await?; - if helper::file_exists(presz_loc.clone()) { + Self::install_from( + presz_loc.clone(), + ctx.sourcemod_path.clone(), + Some(version_id) + ) + .await?; + if helper::file_exists(presz_loc.clone()) + { std::fs::remove_file(presz_loc)?; } Ok(()) @@ -97,12 +136,17 @@ impl InstallWorkflow { /// Install the `.tar.zstd` file at `package_loc` to `out_dir` /// package_loc: Location to a file that is a `.tar.zstd` file. /// out_dir: should be `RunnerContext.sourcemod_path` - /// version_id: Version that is from `package_loc`. When not specified, `.adastral` will not be written to. - /// Note: This function doesn't check the extension when extracting. - pub async fn install_from(package_loc: String, out_dir: String, version_id: Option) - -> Result<(), BeansError> + /// version_id: Version that is from `package_loc`. When not specified, + /// `.adastral` will not be written to. Note: This function doesn't + /// check the extension when extracting. + pub async fn install_from( + package_loc: String, + out_dir: String, + version_id: Option + ) -> Result<(), BeansError> { - if helper::file_exists(package_loc.clone()) == false { + if helper::file_exists(package_loc.clone()) == false + { error!("[InstallWorkflow::Wizard] Failed to find package! (location: {package_loc})"); return Err(BeansError::DownloadFailure { reason: DownloadFailureReason::FileNotFound { @@ -113,15 +157,23 @@ impl InstallWorkflow { println!("[InstallWorkflow::Wizard] Extracting to {out_dir}"); RunnerContext::extract_package(package_loc, out_dir.clone())?; - if let Some(lri) = version_id { + if let Some(lri) = version_id + { let x = AdastralVersionFile { version: lri.to_string() - }.write(Some(out_dir.clone())); - if let Err(e) = x { - println!("[InstallWorkflow::install_from] Failed to set version to {} in .adastral", lri); + } + .write(Some(out_dir.clone())); + if let Err(e) = x + { + println!( + "[InstallWorkflow::install_from] Failed to set version to {} in .adastral", + lri + ); debug!("{:#?}", e); } - } else { + } + else + { warn!("Not writing .adastral since the version wasn't provided"); } let av = crate::appvar::parse(); @@ -133,4 +185,4 @@ impl InstallWorkflow { #[cfg(not(target_os = "windows"))] pub const INSTALL_FINISH_MSG: &str = include_str!("../text/install_complete_linux.txt"); #[cfg(target_os = "windows")] -pub const INSTALL_FINISH_MSG: &str = include_str!("../text/install_complete_windows.txt"); \ No newline at end of file +pub const INSTALL_FINISH_MSG: &str = include_str!("../text/install_complete_windows.txt"); diff --git a/src/workflows/mod.rs b/src/workflows/mod.rs index 9fa743b..1c8dae5 100644 --- a/src/workflows/mod.rs +++ b/src/workflows/mod.rs @@ -1,11 +1,11 @@ +mod clean; mod install; +mod uninstall; mod update; mod verify; -mod clean; -mod uninstall; +pub use clean::*; pub use install::*; +pub use uninstall::*; pub use update::*; pub use verify::*; -pub use clean::*; -pub use uninstall::*; \ No newline at end of file diff --git a/src/workflows/uninstall.rs b/src/workflows/uninstall.rs index 61aae62..68e2a8f 100644 --- a/src/workflows/uninstall.rs +++ b/src/workflows/uninstall.rs @@ -1,39 +1,58 @@ -use log::{error, info, trace}; -use crate::{BeansError, helper, RunnerContext}; -use crate::appvar::AppVarData; +use log::{error, + info, + trace}; + +use crate::{appvar::AppVarData, + helper, + BeansError, + RunnerContext}; #[derive(Debug, Clone)] -pub struct UninstallWorkflow { +pub struct UninstallWorkflow +{ pub context: RunnerContext } -impl UninstallWorkflow { +impl UninstallWorkflow +{ pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let av = AppVarData::get(); - if ctx.current_version.is_none() { + if ctx.current_version.is_none() + { info!("{} is not installed.", av.mod_info.name_stylized); return Ok(()); } let mod_location = ctx.get_mod_location(); - if let Some(pid) = helper::is_game_running(mod_location.clone()) { - info!("{} (pid: {:}) is running! Can't uninstall since the game files are being used.", av.mod_info.name_stylized, pid); + if let Some(pid) = helper::is_game_running(mod_location.clone()) + { + info!( + "{} (pid: {:}) is running! Can't uninstall since the game files are being used.", + av.mod_info.name_stylized, pid + ); return Err(BeansError::GameStillRunning { name: av.mod_info.name_stylized.clone(), pid: format!("{:}", pid) }); } - if let Err(e) = std::fs::remove_dir_all(&mod_location) { + if let Err(e) = std::fs::remove_dir_all(&mod_location) + { trace!("{:#?}", e); - error!("[UninstallWorkflow] Failed to delete mod directory {} ({:})", mod_location, e); + error!( + "[UninstallWorkflow] Failed to delete mod directory {} ({:})", + mod_location, e + ); return Err(BeansError::DirectoryDeleteFailure { location: mod_location, error: e }); } - info!("[UninstallWorkflow] Successfully uninstalled {}. Please restart Steam.", av.mod_info.name_stylized); + info!( + "[UninstallWorkflow] Successfully uninstalled {}. Please restart Steam.", + av.mod_info.name_stylized + ); return Ok(()); } -} \ No newline at end of file +} diff --git a/src/workflows/update.rs b/src/workflows/update.rs index 7125a88..15d2832 100644 --- a/src/workflows/update.rs +++ b/src/workflows/update.rs @@ -1,5 +1,10 @@ -use log::{debug, info}; -use crate::{BeansError, butler, helper, RunnerContext}; +use log::{debug, + info}; + +use crate::{butler, + helper, + BeansError, + RunnerContext}; pub struct UpdateWorkflow { @@ -11,10 +16,14 @@ impl UpdateWorkflow { let av = crate::appvar::parse(); - let current_version_id = match ctx.current_version { + let current_version_id = match ctx.current_version + { Some(v) => v, - None => { - println!("[UpdateWorkflow::wizard] Unable to update game since it is not installed!"); + None => + { + println!( + "[UpdateWorkflow::wizard] Unable to update game since it is not installed!" + ); return Ok(()); } }; @@ -22,9 +31,11 @@ impl UpdateWorkflow let remote_version = ctx.current_remote_version()?; ctx.prepare_symlink()?; - let patch = match ctx.has_patch_available() { + let patch = match ctx.has_patch_available() + { Some(v) => v, - None => { + None => + { println!("[UpdateWorkflow::wizard] No patch is available for the version that is currently installed."); return Ok(()); } @@ -32,17 +43,30 @@ impl UpdateWorkflow ctx.gameinfo_perms()?; - if helper::has_free_space(ctx.sourcemod_path.clone(), patch.clone().tempreq)? == false { - println!("[UpdateWorkflow::wizard] Not enough free space! Requires {}", helper::format_size(patch.tempreq)); + if helper::has_free_space(ctx.sourcemod_path.clone(), patch.clone().tempreq)? == false + { + println!( + "[UpdateWorkflow::wizard] Not enough free space! Requires {}", + helper::format_size(patch.tempreq) + ); } debug!("remote_version: {:#?}", remote_version); - if remote_version.signature_url.is_none() { - eprintln!("[UpdateWorkflow::wizard] Couldn't get signature URL for version {}", current_version_id); + if remote_version.signature_url.is_none() + { + eprintln!( + "[UpdateWorkflow::wizard] Couldn't get signature URL for version {}", + current_version_id + ); } - if remote_version.heal_url.is_none() { - eprintln!("[UpdateWorkflow::wizard] Couldn't get heal URL for version {}", current_version_id); + if remote_version.heal_url.is_none() + { + eprintln!( + "[UpdateWorkflow::wizard] Couldn't get heal URL for version {}", + current_version_id + ); } - if remote_version.signature_url.is_none() || remote_version.heal_url.is_none() { + if remote_version.signature_url.is_none() || remote_version.heal_url.is_none() + { eprintln!("[UpdateWorkflow::wizard] Unable to update, missing remote files!"); return Ok(()); } @@ -55,9 +79,19 @@ impl UpdateWorkflow ctx.gameinfo_perms()?; info!("[UpdateWorkflow] Verifying game"); if let Err(e) = butler::verify( - format!("{}{}", &av.remote_info.base_url, remote_version.signature_url.unwrap()), + format!( + "{}{}", + &av.remote_info.base_url, + remote_version.signature_url.unwrap() + ), mod_dir_location.clone(), - format!("{}{}", &av.remote_info.base_url, remote_version.heal_url.unwrap())) { + format!( + "{}{}", + &av.remote_info.base_url, + remote_version.heal_url.unwrap() + ) + ) + { sentry::capture_error(&e); return Err(e); } @@ -67,7 +101,10 @@ impl UpdateWorkflow format!("{}{}", &av.remote_info.base_url, patch.file), staging_dir_location, patch.file, - mod_dir_location).await { + mod_dir_location + ) + .await + { sentry::capture_error(&e); return Err(e); } @@ -77,4 +114,4 @@ impl UpdateWorkflow println!("Game has been updated!"); Ok(()) } -} \ No newline at end of file +} diff --git a/src/workflows/verify.rs b/src/workflows/verify.rs index 8040d92..4802043 100644 --- a/src/workflows/verify.rs +++ b/src/workflows/verify.rs @@ -1,30 +1,48 @@ -use crate::{BeansError, butler, helper, RunnerContext}; -use crate::version::RemoteVersion; +use crate::{butler, + helper, + version::RemoteVersion, + BeansError, + RunnerContext}; -pub struct VerifyWorkflow { +pub struct VerifyWorkflow +{ pub ctx: RunnerContext } -impl VerifyWorkflow { +impl VerifyWorkflow +{ pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let av = crate::appvar::parse(); - let current_version_id = match ctx.current_version { + let current_version_id = match ctx.current_version + { Some(v) => v, - None => { - println!("[VerifyWorkflow::wizard] Unable to update game since it is not installed!"); + None => + { + println!( + "[VerifyWorkflow::wizard] Unable to update game since it is not installed!" + ); return Ok(()); } }; let remote: RemoteVersion = ctx.current_remote_version()?; - if remote.signature_url.is_none() { - eprintln!("[VerifyWorkflow::wizard] Couldn't get signature URL for version {}", current_version_id); + if remote.signature_url.is_none() + { + eprintln!( + "[VerifyWorkflow::wizard] Couldn't get signature URL for version {}", + current_version_id + ); } - if remote.heal_url.is_none() { - eprintln!("[VerifyWorkflow::wizard] Couldn't get heal URL for version {}", current_version_id); + if remote.heal_url.is_none() + { + eprintln!( + "[VerifyWorkflow::wizard] Couldn't get heal URL for version {}", + current_version_id + ); } - if remote.signature_url.is_none() || remote.heal_url.is_none() { + if remote.signature_url.is_none() || remote.heal_url.is_none() + { eprintln!("[VerifyWorkflow::wizard] Unable to update, missing remote files!"); return Ok(()); } @@ -32,11 +50,16 @@ impl VerifyWorkflow { helper::backup_gameinfo(ctx)?; let mod_dir_location = ctx.get_mod_location(); butler::verify( - format!("{}{}", &av.remote_info.base_url, remote.signature_url.unwrap()), + format!( + "{}{}", + &av.remote_info.base_url, + remote.signature_url.unwrap() + ), mod_dir_location.clone(), - format!("{}{}", &av.remote_info.base_url, remote.heal_url.unwrap()))?; + format!("{}{}", &av.remote_info.base_url, remote.heal_url.unwrap()) + )?; println!("[VerifyWorkflow::wizard] The verification process has completed, and any corruption has been repaired."); ctx.gameinfo_perms()?; Ok(()) } -} \ No newline at end of file +} From ee4760b6e1c12e76ae1dec06514cce43d5a108fe Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 30 Jul 2024 16:37:25 +0800 Subject: [PATCH 44/65] aaaaaaaaaaaaa fixed builds lol --- build.rs | 1 - src/version.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/build.rs b/build.rs index c5a884c..b9f4873 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,3 @@ -======= use std::path::PathBuf; #[allow(dead_code, unused_macros, unused_imports)] use std::{env, diff --git a/src/version.rs b/src/version.rs index 85513d2..4846048 100644 --- a/src/version.rs +++ b/src/version.rs @@ -3,7 +3,6 @@ use std::{backtrace::Backtrace, fs::read_to_string, io::Write}; -use futures_util::TryFutureExt; use log::{debug, error, trace}; From 1274ed5df31b7285bb2ac297c9e641724f9fa7a8 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 6 Aug 2024 18:59:39 +0800 Subject: [PATCH 45/65] Add functions in crate for easily getting env vars --- src/helper/mod.rs | 4 +--- src/lib.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 +- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/helper/mod.rs b/src/helper/mod.rs index ccfc268..4d78b25 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -561,7 +561,7 @@ pub fn format_size(i: usize) -> String /// Otherwise `None` is returned. pub fn use_custom_tmpdir() -> Option { - if let Ok(x) = std::env::var(CUSTOM_TMPDIR_NAME) + if let Some(x) = crate::env_custom_tmpdir() { let s = x.to_string(); if dir_exists(s.clone()) @@ -579,8 +579,6 @@ pub fn use_custom_tmpdir() -> Option None } -pub const CUSTOM_TMPDIR_NAME: &str = "ADASTRAL_TMPDIR"; - /// Create directory in temp directory with name of "beans-rs" pub fn get_tmp_dir() -> String { diff --git a/src/lib.rs b/src/lib.rs index 98c846a..36be658 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,43 @@ pub fn data_dir() -> String format!("{}{}{}", PATH_SEP, av.mod_info.sourcemod_name, PATH_SEP) } +/// Temporary directory which is specified by `ADASTRAL_TMPDIR`. +/// +/// Will return `None` when the environment variable couldn't be found, or it's an empty string. +pub fn env_custom_tmpdir() -> Option +{ + let s = helper::try_get_env_var(String::from("ADASTRAL_TMPDIR")); + match s { + Some(x) => match x.trim().is_empty() { + true => None, + false => Some(x) + }, + None => s + } +} +/// Return `true` when the environment variable `BEANS_DEBUG` or `ADASTRAL_DEBUG` exists and +/// equals `1` or `true`. +pub fn env_debug() -> bool +{ + check_env_bool("BEANS_DEBUG") || check_env_bool("ADASTRAL_DEBUG") +} +/// Return `true` when the environment variable `BEANS_HEADLESS` or `ADASTRAL_HEADLESS` exists and +/// equals `1` or `true`. +pub fn env_headless() -> bool +{ + check_env_bool("BEANS_HEADLESS") || check_env_bool("ADASTRAL_HEADLESS") +} + +/// Return `true` when the environment variable exists, and it's value equals `1` or `true (when +/// trimmed and made lowercase). +fn check_env_bool>(key: K) -> bool +{ + std::env::var(key).is_ok_and(|x| { + let y = x.trim().to_lowercase(); + y == "1" || y == "true" + }) +} + /// Check if we have GUI support enabled. Will always return `false` when /// `PAUSE_ONCE_DONE` is `false`. /// @@ -74,6 +111,10 @@ pub fn has_gui_support() -> bool } } + if env_headless() { + return true; + } + match std::env::consts::OS { "windows" | "macos" => true, diff --git a/src/main.rs b/src/main.rs index 728eab1..58d83cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,7 +67,7 @@ fn init_flags() flags::remove_flag(LaunchFlag::DEBUG_MODE); #[cfg(debug_assertions)] flags::add_flag(LaunchFlag::DEBUG_MODE); - if std::env::var("BEANS_DEBUG").is_ok_and(|x| x == "1") + if beans_rs::env_debug() { flags::add_flag(LaunchFlag::DEBUG_MODE); } From 5e8eb9652f94fc1c982e4c6947b2e3239fdfd112 Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 12 Aug 2024 12:20:56 +0800 Subject: [PATCH 46/65] Show current&latest version when update is available --- src/wizard.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wizard.rs b/src/wizard.rs index 56fef8f..73bbadb 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -100,8 +100,10 @@ impl WizardContext if cv < rv { println!( - "======== A new update for {} is available! (v{rv}) ========", - av.mod_info.name_stylized + "======== A new update for {} is available! (latest: v{}, current: v{}) ========", + av.mod_info.name_stylized, + rv, + cv ); } } From 5b7a2f1cfd83e6cb9d93e011a7932547501d0aed Mon Sep 17 00:00:00 2001 From: kate Date: Wed, 14 Aug 2024 09:45:00 +0800 Subject: [PATCH 47/65] Add progress bar for zstd decompression --- src/ctx.rs | 34 +++++--------- src/error.rs | 9 ++++ src/extract.rs | 124 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 src/extract.rs diff --git a/src/ctx.rs b/src/ctx.rs index 7a644fe..0be75f5 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -281,13 +281,16 @@ impl RunnerContext { let tar_tmp_location = helper::get_tmp_file("data.tar".to_string()); - let zstd_file = std::fs::File::open(&zstd_location)?; - let mut tar_tmp_file = std::fs::File::create_new(&tar_tmp_location)?; - zstd::stream::copy_decode(zstd_file, &tar_tmp_file)?; - tar_tmp_file = std::fs::File::open(&tar_tmp_location)?; // we do this again to make sure that the tar is properly opened. - - let mut archive = tar::Archive::new(&tar_tmp_file); - let x = archive.unpack(&out_dir); + if let Err(e) = crate::extract::decompress_zstd(zstd_location.clone(), tar_tmp_location.clone(), true) { + debug!("{:#?}", e); + error!("[RunnerContext::extract_package] Failed to decompress file {} ({:})", zstd_location, e); + return Err(e); + } + if let Err(e) = crate::extract::unpack_tarball(tar_tmp_location.clone(), out_dir, true) { + debug!("{:#?}", e); + error!("[RunnerContext::extract_package] Failed to unpack tarball {} ({:})", tar_tmp_location, e); + return Err(e); + } if helper::file_exists(tar_tmp_location.clone()) { if let Err(e) = std::fs::remove_file(tar_tmp_location.clone()) @@ -303,22 +306,7 @@ impl RunnerContext ); } } - match x - { - Err(e) => - { - let xe = BeansError::TarExtractFailure { - src_file: tar_tmp_location, - target_dir: out_dir, - error: e, - backtrace: Backtrace::capture() - }; - trace!("[RunnerContext::extract_package] {:}\n{:#?}", xe, xe); - sentry::capture_error(&xe); - Err(xe) - } - Ok(_) => Ok(()) - } + Ok(()) } #[cfg(target_os = "linux")] diff --git a/src/error.rs b/src/error.rs index e6a5d4e..c7109ca 100644 --- a/src/error.rs +++ b/src/error.rs @@ -52,6 +52,15 @@ pub enum BeansError error: std::io::Error, backtrace: Backtrace }, + #[error("Failed to extract item {link_name} to directory {target_dir} ({error:})")] + TarUnpackItemFailure + { + src_file: String, + target_dir: String, + link_name: String, + error: std::io::Error, + backtrace: Backtrace + }, #[error("Failed to send request ({error:})")] Reqwest { diff --git a/src/extract.rs b/src/extract.rs new file mode 100644 index 0000000..7ad2775 --- /dev/null +++ b/src/extract.rs @@ -0,0 +1,124 @@ +use log::info; +use std::{backtrace::Backtrace, fs::File, io::Read}; +use indicatif::{ProgressBar, ProgressStyle}; +use zstd::stream::read::Decoder as ZstdDecoder; + +use crate::BeansError; + +pub fn unpack_tarball(tarball_location: String, output_directory: String, show_progress: bool) -> Result<(), BeansError> +{ + let tarball = match File::open(&tarball_location) { + Ok(x) => x, + Err(e) => { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture() + }); + } + }; + let mut archive = tar::Archive::new(&tarball); + if show_progress { + let archive_entries = match archive.entries() + { + Ok(v) => v, + Err(e) => + { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture() + }); + } + }; + let archive_entry_count = (&archive_entries.count()).clone() as u64; + info!("Extracting {} files", archive_entry_count); + + let pb = ProgressBar::new(archive_entry_count); + pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({eta})") + .unwrap() + .with_key("eta", |state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) + .progress_chars("#>-")); + pb.set_message("Extracting files"); + + archive = tar::Archive::new(&tarball); + match archive.entries() { + Ok(etrs) => + { + for entry in etrs { + match entry { + Ok(mut x) => { + let ln = x.link_name(); + pb.set_message("Extracting files"); + let mut filename = String::new(); + if let Ok(n) = ln { + if let Some(p) = n { + if let Some(s) = p.to_str() { + pb.set_message(format!("{:}", s)); + filename = String::from(s); + } + } + } + if let Err(e) = x.unpack_in(&output_directory) { + return Err(BeansError::TarUnpackItemFailure { + src_file: tarball_location, + target_dir: output_directory, + link_name: filename, + error: e, + backtrace: Backtrace::capture() + }); + } + pb.inc(1); + }, + Err(e) => { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture() + }); + } + }; + } + }, + Err(e) => + { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture() + }); + } + }; + pb.finish(); + } else { + archive.unpack(output_directory); + } + return Ok(()); +} +pub fn decompress_zstd(zstd_location: String, output_file: String, show_progress: bool) -> Result<(), BeansError> +{ + let zstd_file = File::open(&zstd_location)?; + let zstd_file_length = &zstd_file.metadata()?.len(); + let mut tar_tmp_file = File::create_new(&output_file)?; + if show_progress { + let decoder = ZstdDecoder::new(zstd_file)?; + // estimate extracted size as x2 since idk how to get the decompressed size with zstd + let pb_decompress = ProgressBar::new((zstd_file_length.clone() * 2) as u64); + pb_decompress + .set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") + .unwrap() + .with_key("eta", |state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) + .progress_chars("#>-")); + + std::io::copy(&mut pb_decompress.wrap_read(decoder), &mut tar_tmp_file).expect("Failed to decompress file"); + pb_decompress.finish(); + } else { + zstd::stream::copy_decode(zstd_file, &tar_tmp_file)?; + } + + Ok(()) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 36be658..468cd39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ pub mod butler; pub mod flags; pub mod gui; pub mod logger; +pub mod extract; /// NOTE do not change, fetches from the version of beans-rs on build pub const VERSION: &str = env!("CARGO_PKG_VERSION"); From 79a17019f296b9f66120365642d0a7e922fe65d0 Mon Sep 17 00:00:00 2001 From: kate Date: Wed, 14 Aug 2024 09:46:52 +0800 Subject: [PATCH 48/65] Use cargo fmt --- src/ctx.rs | 17 +++++++--- src/extract.rs | 85 ++++++++++++++++++++++++++++++++++---------------- src/lib.rs | 26 ++++++++------- 3 files changed, 86 insertions(+), 42 deletions(-) diff --git a/src/ctx.rs b/src/ctx.rs index 0be75f5..5fb6ece 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -281,14 +281,23 @@ impl RunnerContext { let tar_tmp_location = helper::get_tmp_file("data.tar".to_string()); - if let Err(e) = crate::extract::decompress_zstd(zstd_location.clone(), tar_tmp_location.clone(), true) { + if let Err(e) = + crate::extract::decompress_zstd(zstd_location.clone(), tar_tmp_location.clone(), true) + { debug!("{:#?}", e); - error!("[RunnerContext::extract_package] Failed to decompress file {} ({:})", zstd_location, e); + error!( + "[RunnerContext::extract_package] Failed to decompress file {} ({:})", + zstd_location, e + ); return Err(e); } - if let Err(e) = crate::extract::unpack_tarball(tar_tmp_location.clone(), out_dir, true) { + if let Err(e) = crate::extract::unpack_tarball(tar_tmp_location.clone(), out_dir, true) + { debug!("{:#?}", e); - error!("[RunnerContext::extract_package] Failed to unpack tarball {} ({:})", tar_tmp_location, e); + error!( + "[RunnerContext::extract_package] Failed to unpack tarball {} ({:})", + tar_tmp_location, e + ); return Err(e); } if helper::file_exists(tar_tmp_location.clone()) diff --git a/src/extract.rs b/src/extract.rs index 7ad2775..63bb0f8 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,15 +1,25 @@ +use std::{backtrace::Backtrace, + fs::File, + io::Read}; + +use indicatif::{ProgressBar, + ProgressStyle}; use log::info; -use std::{backtrace::Backtrace, fs::File, io::Read}; -use indicatif::{ProgressBar, ProgressStyle}; use zstd::stream::read::Decoder as ZstdDecoder; use crate::BeansError; -pub fn unpack_tarball(tarball_location: String, output_directory: String, show_progress: bool) -> Result<(), BeansError> +pub fn unpack_tarball( + tarball_location: String, + output_directory: String, + show_progress: bool +) -> Result<(), BeansError> { - let tarball = match File::open(&tarball_location) { + let tarball = match File::open(&tarball_location) + { Ok(x) => x, - Err(e) => { + Err(e) => + { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, @@ -19,7 +29,8 @@ pub fn unpack_tarball(tarball_location: String, output_directory: String, show_p } }; let mut archive = tar::Archive::new(&tarball); - if show_progress { + if show_progress + { let archive_entries = match archive.entries() { Ok(v) => v, @@ -35,33 +46,41 @@ pub fn unpack_tarball(tarball_location: String, output_directory: String, show_p }; let archive_entry_count = (&archive_entries.count()).clone() as u64; info!("Extracting {} files", archive_entry_count); - + let pb = ProgressBar::new(archive_entry_count); pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({eta})") .unwrap() .with_key("eta", |state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) .progress_chars("#>-")); pb.set_message("Extracting files"); - + archive = tar::Archive::new(&tarball); - match archive.entries() { + match archive.entries() + { Ok(etrs) => { - for entry in etrs { - match entry { - Ok(mut x) => { + for entry in etrs + { + match entry + { + Ok(mut x) => + { let ln = x.link_name(); pb.set_message("Extracting files"); let mut filename = String::new(); - if let Ok(n) = ln { - if let Some(p) = n { - if let Some(s) = p.to_str() { + if let Ok(n) = ln + { + if let Some(p) = n + { + if let Some(s) = p.to_str() + { pb.set_message(format!("{:}", s)); filename = String::from(s); } } } - if let Err(e) = x.unpack_in(&output_directory) { + if let Err(e) = x.unpack_in(&output_directory) + { return Err(BeansError::TarUnpackItemFailure { src_file: tarball_location, target_dir: output_directory, @@ -71,8 +90,9 @@ pub fn unpack_tarball(tarball_location: String, output_directory: String, show_p }); } pb.inc(1); - }, - Err(e) => { + } + Err(e) => + { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, @@ -82,7 +102,7 @@ pub fn unpack_tarball(tarball_location: String, output_directory: String, show_p } }; } - }, + } Err(e) => { return Err(BeansError::TarExtractFailure { @@ -94,31 +114,42 @@ pub fn unpack_tarball(tarball_location: String, output_directory: String, show_p } }; pb.finish(); - } else { + } + else + { archive.unpack(output_directory); } return Ok(()); } -pub fn decompress_zstd(zstd_location: String, output_file: String, show_progress: bool) -> Result<(), BeansError> +pub fn decompress_zstd( + zstd_location: String, + output_file: String, + show_progress: bool +) -> Result<(), BeansError> { let zstd_file = File::open(&zstd_location)?; let zstd_file_length = &zstd_file.metadata()?.len(); let mut tar_tmp_file = File::create_new(&output_file)?; - if show_progress { + if show_progress + { let decoder = ZstdDecoder::new(zstd_file)?; - // estimate extracted size as x2 since idk how to get the decompressed size with zstd + // estimate extracted size as x2 since idk how to get the decompressed size with + // zstd let pb_decompress = ProgressBar::new((zstd_file_length.clone() * 2) as u64); pb_decompress .set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") .unwrap() .with_key("eta", |state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) .progress_chars("#>-")); - - std::io::copy(&mut pb_decompress.wrap_read(decoder), &mut tar_tmp_file).expect("Failed to decompress file"); + + std::io::copy(&mut pb_decompress.wrap_read(decoder), &mut tar_tmp_file) + .expect("Failed to decompress file"); pb_decompress.finish(); - } else { + } + else + { zstd::stream::copy_decode(zstd_file, &tar_tmp_file)?; } Ok(()) -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 468cd39..f2c7405 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,10 +22,10 @@ pub use error::*; pub mod appvar; pub mod butler; +pub mod extract; pub mod flags; pub mod gui; pub mod logger; -pub mod extract; /// NOTE do not change, fetches from the version of beans-rs on build pub const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -60,33 +60,36 @@ pub fn data_dir() -> String /// Temporary directory which is specified by `ADASTRAL_TMPDIR`. /// -/// Will return `None` when the environment variable couldn't be found, or it's an empty string. +/// Will return `None` when the environment variable couldn't be found, or it's +/// an empty string. pub fn env_custom_tmpdir() -> Option { let s = helper::try_get_env_var(String::from("ADASTRAL_TMPDIR")); - match s { - Some(x) => match x.trim().is_empty() { + match s + { + Some(x) => match x.trim().is_empty() + { true => None, false => Some(x) }, None => s } } -/// Return `true` when the environment variable `BEANS_DEBUG` or `ADASTRAL_DEBUG` exists and -/// equals `1` or `true`. +/// Return `true` when the environment variable `BEANS_DEBUG` or +/// `ADASTRAL_DEBUG` exists and equals `1` or `true`. pub fn env_debug() -> bool { check_env_bool("BEANS_DEBUG") || check_env_bool("ADASTRAL_DEBUG") } -/// Return `true` when the environment variable `BEANS_HEADLESS` or `ADASTRAL_HEADLESS` exists and -/// equals `1` or `true`. +/// Return `true` when the environment variable `BEANS_HEADLESS` or +/// `ADASTRAL_HEADLESS` exists and equals `1` or `true`. pub fn env_headless() -> bool { check_env_bool("BEANS_HEADLESS") || check_env_bool("ADASTRAL_HEADLESS") } -/// Return `true` when the environment variable exists, and it's value equals `1` or `true (when -/// trimmed and made lowercase). +/// Return `true` when the environment variable exists, and it's value equals +/// `1` or `true (when trimmed and made lowercase). fn check_env_bool>(key: K) -> bool { std::env::var(key).is_ok_and(|x| { @@ -112,7 +115,8 @@ pub fn has_gui_support() -> bool } } - if env_headless() { + if env_headless() + { return true; } From 0fef0fef703bb798eeffafb6c34fe23230a1e082 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 8 Sep 2024 21:53:59 +0800 Subject: [PATCH 49/65] [extract::unpack_tarball] Add error handling for extract w/o progress --- src/extract.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 63bb0f8..69389d4 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -117,9 +117,17 @@ pub fn unpack_tarball( } else { - archive.unpack(output_directory); + if let Err(e) = archive.unpack(&output_directory) + { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture() + }); + } } - return Ok(()); + Ok(()) } pub fn decompress_zstd( zstd_location: String, From 98eeacfa0ad6ea9d5c2bf89699066e4be3974a36 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 8 Sep 2024 21:54:13 +0800 Subject: [PATCH 50/65] beans-rs v1.6.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 394766e..1c75977 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "beans-rs" -version = "1.5.2" +version = "1.6.0" edition = "2021" authors = [ "Kate Ward " From bd17b9284ba66aae598151b510761d0aa43089c2 Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 9 Sep 2024 19:58:09 +0800 Subject: [PATCH 51/65] bwah --- src/ctx.rs | 2 +- src/depends.rs | 2 +- src/extract.rs | 26 +++++++++++--------------- src/helper/mod.rs | 16 ++++++++-------- src/workflows/uninstall.rs | 2 +- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/ctx.rs b/src/ctx.rs index 5fb6ece..172fc4f 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -216,7 +216,7 @@ impl RunnerContext let location = self.gameinfo_location(); if helper::file_exists(location.clone()) { - let perm = std::fs::Permissions::from_mode(0o644 as u32); + let perm = std::fs::Permissions::from_mode(0o644_u32); if let Err(e) = std::fs::set_permissions(&location, perm.clone()) { let xe = BeansError::GameInfoPermissionSetFail { diff --git a/src/depends.rs b/src/depends.rs index 8c2d566..08ec580 100644 --- a/src/depends.rs +++ b/src/depends.rs @@ -22,7 +22,7 @@ pub fn try_write_deps() #[cfg(not(target_os = "windows"))] if helper::file_exists(get_butler_location()) { - let p = std::fs::Permissions::from_mode(0744 as u32); + let p = std::fs::Permissions::from_mode(0o0744_u32); if let Err(e) = std::fs::set_permissions(get_butler_location(), p) { sentry::capture_error(&e); diff --git a/src/extract.rs b/src/extract.rs index 69389d4..10e2097 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,6 +1,5 @@ use std::{backtrace::Backtrace, - fs::File, - io::Read}; + fs::File}; use indicatif::{ProgressBar, ProgressStyle}; @@ -44,7 +43,7 @@ pub fn unpack_tarball( }); } }; - let archive_entry_count = (&archive_entries.count()).clone() as u64; + let archive_entry_count = archive_entries.count() as u64; info!("Extracting {} files", archive_entry_count); let pb = ProgressBar::new(archive_entry_count); @@ -74,7 +73,7 @@ pub fn unpack_tarball( { if let Some(s) = p.to_str() { - pb.set_message(format!("{:}", s)); + pb.set_message(s.to_string()); filename = String::from(s); } } @@ -115,17 +114,14 @@ pub fn unpack_tarball( }; pb.finish(); } - else + else if let Err(e) = archive.unpack(&output_directory) { - if let Err(e) = archive.unpack(&output_directory) - { - return Err(BeansError::TarExtractFailure { - src_file: tarball_location, - target_dir: output_directory, - error: e, - backtrace: Backtrace::capture() - }); - } + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture() + }); } Ok(()) } @@ -143,7 +139,7 @@ pub fn decompress_zstd( let decoder = ZstdDecoder::new(zstd_file)?; // estimate extracted size as x2 since idk how to get the decompressed size with // zstd - let pb_decompress = ProgressBar::new((zstd_file_length.clone() * 2) as u64); + let pb_decompress = ProgressBar::new((*zstd_file_length * 2)); pb_decompress .set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") .unwrap() diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 4d78b25..3e7e485 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -311,8 +311,8 @@ fn is_process_running( arguments_contains: Option ) -> Option { - return find_process(move |proc: &sysinfo::Process| { - if proc.name().to_string() == name + find_process(move |proc: &sysinfo::Process| { + if *proc.name() == name { if let Some(x) = arguments_contains.clone() { @@ -325,8 +325,8 @@ fn is_process_running( } } } - return false; - }); + false + }) } /// Find a process with a selector filter. /// @@ -344,7 +344,7 @@ where return Some(process.pid()); } } - return None; + None } /// Check if there are any processes running @@ -377,18 +377,18 @@ pub fn is_game_running(mod_directory: String) -> Option if item.to_string().starts_with(&mod_directory) { let proc_name = proc.name().to_string().to_lowercase(); - if proc_name != String::from("beans") && proc_name != String::from("beans-rs") + if proc_name != *"beans" && proc_name != *"beans-rs" { return true; } } } - return false; + false }) { return Some(proc); } - return None; + None } /// Get the amount of free space on the drive in the location provided. diff --git a/src/workflows/uninstall.rs b/src/workflows/uninstall.rs index 68e2a8f..1bea7bc 100644 --- a/src/workflows/uninstall.rs +++ b/src/workflows/uninstall.rs @@ -53,6 +53,6 @@ impl UninstallWorkflow "[UninstallWorkflow] Successfully uninstalled {}. Please restart Steam.", av.mod_info.name_stylized ); - return Ok(()); + Ok(()) } } From 0a7695e9a721656e44387cd5e65553cf36f576a0 Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 9 Sep 2024 19:59:09 +0800 Subject: [PATCH 52/65] converted to matches macro --- src/main.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 58d83cd..6960dc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -536,19 +536,14 @@ impl Launcher trace!("======== Full Error ========"); trace!("{:#?}", &e); show_msgbox_error(format!("{:}", &e)); - let do_report = match e - { - BeansError::GameStillRunning { - .. - } => false, - BeansError::LatestVersionAlreadyInstalled { - .. - } => false, - BeansError::FreeSpaceCheckFailure { - .. - } => false, - _ => true - }; + + let do_report = !matches!( + e, + BeansError::GameStillRunning { .. } + | BeansError::LatestVersionAlreadyInstalled { .. } + | BeansError::FreeSpaceCheckFailure { .. } + ); + if do_report { sentry::capture_error(&e); From 0dfd776fadd168edb96dd516e4a84c7d9928158a Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 9 Sep 2024 20:37:59 +0800 Subject: [PATCH 53/65] reduced indents --- src/extract.rs | 144 +++++++++++++++++++++---------------------------- 1 file changed, 60 insertions(+), 84 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 10e2097..3bcba63 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,8 +1,6 @@ -use std::{backtrace::Backtrace, - fs::File}; +use std::{backtrace::Backtrace, fs::File}; -use indicatif::{ProgressBar, - ProgressStyle}; +use indicatif::{ProgressBar, ProgressStyle}; use log::info; use zstd::stream::read::Decoder as ZstdDecoder; @@ -11,35 +9,29 @@ use crate::BeansError; pub fn unpack_tarball( tarball_location: String, output_directory: String, - show_progress: bool -) -> Result<(), BeansError> -{ - let tarball = match File::open(&tarball_location) - { + show_progress: bool, +) -> Result<(), BeansError> { + let tarball = match File::open(&tarball_location) { Ok(x) => x, - Err(e) => - { + Err(e) => { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } }; let mut archive = tar::Archive::new(&tarball); - if show_progress - { - let archive_entries = match archive.entries() - { + if show_progress { + let archive_entries = match archive.entries() { Ok(v) => v, - Err(e) => - { + Err(e) => { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } }; @@ -52,75 +44,63 @@ pub fn unpack_tarball( .with_key("eta", |state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) .progress_chars("#>-")); pb.set_message("Extracting files"); - archive = tar::Archive::new(&tarball); - match archive.entries() - { - Ok(etrs) => - { - for entry in etrs - { - match entry - { - Ok(mut x) => - { - let ln = x.link_name(); - pb.set_message("Extracting files"); - let mut filename = String::new(); - if let Ok(n) = ln - { - if let Some(p) = n - { - if let Some(s) = p.to_str() - { - pb.set_message(s.to_string()); - filename = String::from(s); - } - } - } - if let Err(e) = x.unpack_in(&output_directory) - { - return Err(BeansError::TarUnpackItemFailure { - src_file: tarball_location, - target_dir: output_directory, - link_name: filename, - error: e, - backtrace: Backtrace::capture() - }); - } - pb.inc(1); - } - Err(e) => - { - return Err(BeansError::TarExtractFailure { - src_file: tarball_location, - target_dir: output_directory, - error: e, - backtrace: Backtrace::capture() - }); - } - }; - } - } - Err(e) => - { + + // TODO START OF WORK + + let entries = match archive.entries() { + Ok(a) => a, + Err(error) => { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, - error: e, - backtrace: Backtrace::capture() + error, + backtrace: Backtrace::capture(), }); } }; + + for entry in entries { + match entry { + Ok(mut x) => { + pb.set_message("Extracting files"); + let mut filename = String::new(); + + if let Ok(Some(p)) = x.link_name() { + if let Some(s) = p.to_str() { + pb.set_message(s.to_string()); + filename = String::from(s); + } + } + if let Err(error) = x.unpack_in(&output_directory) { + return Err(BeansError::TarUnpackItemFailure { + src_file: tarball_location, + target_dir: output_directory, + link_name: filename, + error, + backtrace: Backtrace::capture(), + }); + } + pb.inc(1); + } + Err(error) => { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error, + backtrace: Backtrace::capture(), + }); + } + } + } + pb.finish(); - } - else if let Err(e) = archive.unpack(&output_directory) - { + } else if let Err(e) = archive.unpack(&output_directory) { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, error: e, - backtrace: Backtrace::capture() + backtrace: Backtrace::capture(), }); } Ok(()) @@ -128,14 +108,12 @@ pub fn unpack_tarball( pub fn decompress_zstd( zstd_location: String, output_file: String, - show_progress: bool -) -> Result<(), BeansError> -{ + show_progress: bool, +) -> Result<(), BeansError> { let zstd_file = File::open(&zstd_location)?; let zstd_file_length = &zstd_file.metadata()?.len(); let mut tar_tmp_file = File::create_new(&output_file)?; - if show_progress - { + if show_progress { let decoder = ZstdDecoder::new(zstd_file)?; // estimate extracted size as x2 since idk how to get the decompressed size with // zstd @@ -149,9 +127,7 @@ pub fn decompress_zstd( std::io::copy(&mut pb_decompress.wrap_read(decoder), &mut tar_tmp_file) .expect("Failed to decompress file"); pb_decompress.finish(); - } - else - { + } else { zstd::stream::copy_decode(zstd_file, &tar_tmp_file)?; } From 5951f27356c1046978a71751718c785e36d9b38a Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 9 Sep 2024 20:45:02 +0800 Subject: [PATCH 54/65] removed more indentation --- src/extract.rs | 124 ++++++++++++++++++++++++------------------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 3bcba63..0867ef9 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -23,85 +23,85 @@ pub fn unpack_tarball( } }; let mut archive = tar::Archive::new(&tarball); - if show_progress { - let archive_entries = match archive.entries() { - Ok(v) => v, - Err(e) => { - return Err(BeansError::TarExtractFailure { - src_file: tarball_location, - target_dir: output_directory, - error: e, - backtrace: Backtrace::capture(), - }); - } - }; - let archive_entry_count = archive_entries.count() as u64; - info!("Extracting {} files", archive_entry_count); - let pb = ProgressBar::new(archive_entry_count); - pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({eta})") + if !show_progress { + if let Err(e) = archive.unpack(&output_directory) { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture(), + }); + } + return Ok(()); + }; + + let archive_entries = match archive.entries() { + Ok(v) => v, + Err(e) => { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture(), + }); + } + }; + let archive_entry_count = archive_entries.count() as u64; + info!("Extracting {} files", archive_entry_count); + + let pb = ProgressBar::new(archive_entry_count); + pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({eta})") .unwrap() .with_key("eta", |state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) .progress_chars("#>-")); - pb.set_message("Extracting files"); - archive = tar::Archive::new(&tarball); + pb.set_message("Extracting files"); + archive = tar::Archive::new(&tarball); - // TODO START OF WORK - - let entries = match archive.entries() { - Ok(a) => a, - Err(error) => { - return Err(BeansError::TarExtractFailure { - src_file: tarball_location, - target_dir: output_directory, - error, - backtrace: Backtrace::capture(), - }); - } - }; + let entries = match archive.entries() { + Ok(a) => a, + Err(error) => { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error, + backtrace: Backtrace::capture(), + }); + } + }; - for entry in entries { - match entry { - Ok(mut x) => { - pb.set_message("Extracting files"); - let mut filename = String::new(); + for entry in entries { + match entry { + Ok(mut x) => { + pb.set_message("Extracting files"); + let mut filename = String::new(); - if let Ok(Some(p)) = x.link_name() { - if let Some(s) = p.to_str() { - pb.set_message(s.to_string()); - filename = String::from(s); - } - } - if let Err(error) = x.unpack_in(&output_directory) { - return Err(BeansError::TarUnpackItemFailure { - src_file: tarball_location, - target_dir: output_directory, - link_name: filename, - error, - backtrace: Backtrace::capture(), - }); + if let Ok(Some(p)) = x.link_name() { + if let Some(s) = p.to_str() { + pb.set_message(s.to_string()); + filename = String::from(s); } - pb.inc(1); } - Err(error) => { - return Err(BeansError::TarExtractFailure { + if let Err(error) = x.unpack_in(&output_directory) { + return Err(BeansError::TarUnpackItemFailure { src_file: tarball_location, target_dir: output_directory, + link_name: filename, error, backtrace: Backtrace::capture(), }); } + pb.inc(1); + } + Err(error) => { + return Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error, + backtrace: Backtrace::capture(), + }); } } - - pb.finish(); - } else if let Err(e) = archive.unpack(&output_directory) { - return Err(BeansError::TarExtractFailure { - src_file: tarball_location, - target_dir: output_directory, - error: e, - backtrace: Backtrace::capture(), - }); } Ok(()) } From 802cced968db86b6fa0e843b9cab02ec4c9f7171 Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Mon, 9 Sep 2024 20:52:45 +0800 Subject: [PATCH 55/65] oops... --- src/extract.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/extract.rs b/src/extract.rs index 0867ef9..b405630 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -102,6 +102,7 @@ pub fn unpack_tarball( }); } } + pb.finish(); } Ok(()) } From 63ba5c7e7aa184305fccf41046efbb73f92a3358 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 10 Sep 2024 11:29:51 +0800 Subject: [PATCH 56/65] [BeansError] Add backtrace field to CleanTempFailure and DirectoryCreateFailure --- src/error.rs | 6 ++++-- src/workflows/clean.rs | 11 ++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index c7109ca..ea39f93 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,7 +36,8 @@ pub enum BeansError DirectoryCreateFailure { location: String, - error: std::io::Error + error: std::io::Error, + backtrace: Backtrace }, #[error("Failed to delete directory {location} ({error:})")] DirectoryDeleteFailure @@ -209,7 +210,8 @@ pub enum BeansError CleanTempFailure { location: String, - error: std::io::Error + error: std::io::Error, + backtrace: Backtrace }, #[error("{name:} ({pid:}) is still running. Please close it and restart beans.")] diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index e11f139..8e057b2 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -1,4 +1,5 @@ -use log::{info, +use log::{debug, + info, warn}; use crate::{helper, @@ -26,18 +27,22 @@ impl CleanWorkflow // delete directory and it's contents (and error handling) if let Err(e) = std::fs::remove_dir_all(&target_directory) { + debug!("{:#?}", e); return Err(BeansError::CleanTempFailure { location: target_directory, - error: e + error: e, + backtrace: std::backtrace::Backtrace::capture(), }); } // re-creating the temporary directory (and error handling) if let Err(e) = std::fs::create_dir(&target_directory) { + debug!("{:#?}", e); return Err(BeansError::DirectoryCreateFailure { location: target_directory, - error: e + error: e, + backtrace: std::backtrace::Backtrace::capture(), }); } From d43120f718ec6c57e77d47eb3725b2b857dd82bb Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 10 Sep 2024 12:20:30 +0800 Subject: [PATCH 57/65] [DialogBuilder] Use TextDisplay instead of Label for error. --- src/gui/dialog.rs | 29 ++++------------------------- src/gui/shared_ui.fl | 10 +++++----- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index 7b14c15..5ce55bd 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -1,4 +1,5 @@ use fltk::{image::PngImage, + text::TextBuffer, prelude::*, *}; use log::warn; @@ -99,37 +100,15 @@ impl DialogBuilder apply_app_scheme(); let (send_action, receive_action) = app::channel::(); let mut ui = GenericDialog::make_window(); - let initial_width = ui.win.width(); + let mut text_buffer = TextBuffer::default(); + text_buffer.append(&self.content); + ui.txt_disp.set_buffer(text_buffer.clone()); ui.win.set_icon(self.icon.clone()); ui.win.set_label(&self.title); - ui.label.set_label(&self.content); - ui.btn_ok.set_size(70, 24); ui.btn_ok.emit(send_action, GUIAppStatus::Quit); - let (label_w, label_h) = ui.label.measure_label(); - ui.win - .set_size(25 + label_w + 25, 10 + label_h + 5 + ui.btn_ok.height() + 5); - - ui.btn_ok.set_pos(25, ui.win.height() - 24 - 5); window_centre_screen(&mut ui.win); - ui.win.handle(move |w, ev| match ev - { - fltk::enums::Event::Resize => - { - let height = w.height(); - ui.btn_ok.set_pos(25, height - 24 - 5); - ui.btn_ok.set_size(70, 24); - let (lw, lh) = ui.label.measure_label(); - let cw = w.width(); - if cw != initial_width && cw > lw + 50 - { - w.set_size(lw + 50, 10 + lh + 5 + ui.btn_ok.height() + 5); - } - false - } - _ => false - }); ui.win.make_resizable(false); ui.win.show(); wait_for_quit(&app, &receive_action); diff --git a/src/gui/shared_ui.fl b/src/gui/shared_ui.fl index 716a2ec..ad70c0e 100644 --- a/src/gui/shared_ui.fl +++ b/src/gui/shared_ui.fl @@ -7,14 +7,14 @@ class GenericDialog {open Function {make_window()} {open } { Fl_Window win {open - xywh {376 147 500 150} type Double visible + xywh {1070 474 600 350} type Double visible } { - Fl_Text_Display label { - xywh {25 10 0 0} box NO_BOX align 11 - } Fl_Button btn_ok { label Ok - xywh {25 121 70 24} + xywh {25 321 70 24} + } + Fl_Text_Display txt_disp { + xywh {25 10 550 301} align 5 } } } From ffbe924a92c583cff3056fa074e7f76047c97ac5 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 10 Sep 2024 12:21:12 +0800 Subject: [PATCH 58/65] [extract::unpack_tarball] Fix tarball entries not being unpacked properly --- src/extract.rs | 72 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 69389d4..fdbe0f7 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -4,7 +4,7 @@ use std::{backtrace::Backtrace, use indicatif::{ProgressBar, ProgressStyle}; -use log::info; +use log::{debug, error, info}; use zstd::stream::read::Decoder as ZstdDecoder; use crate::BeansError; @@ -15,23 +15,11 @@ pub fn unpack_tarball( show_progress: bool ) -> Result<(), BeansError> { - let tarball = match File::open(&tarball_location) - { - Ok(x) => x, - Err(e) => - { - return Err(BeansError::TarExtractFailure { - src_file: tarball_location, - target_dir: output_directory, - error: e, - backtrace: Backtrace::capture() - }); - } - }; - let mut archive = tar::Archive::new(&tarball); + let mut tarball = open_tarball_file(tarball_location.clone(), output_directory.clone())?; if show_progress { - let archive_entries = match archive.entries() + let mut archive_entries_instance = tar::Archive::new(&tarball); + let archive_entries = match archive_entries_instance.entries() { Ok(v) => v, Err(e) => @@ -54,13 +42,18 @@ pub fn unpack_tarball( .progress_chars("#>-")); pb.set_message("Extracting files"); - archive = tar::Archive::new(&tarball); - match archive.entries() + // re-open the file, since tar::Archive::new will not work with a re-used file. + tarball = open_tarball_file(tarball_location.clone(), output_directory.clone())?; + let mut archive_inner = tar::Archive::new(&tarball); + archive_inner.set_preserve_permissions(false); + let mut idx: u64 = 0; + match archive_inner.entries() { Ok(etrs) => { - for entry in etrs + for (size, entry) in etrs.enumerate() { + idx += 1; match entry { Ok(mut x) => @@ -79,20 +72,34 @@ pub fn unpack_tarball( } } } + if let Err(e) = x.unpack_in(&output_directory) { - return Err(BeansError::TarUnpackItemFailure { + pb.finish_and_clear(); + debug!("[{idx:}] error={:#?}", e); + debug!("[{idx:}] entry.path={:#?}", x.path()); + debug!("[{idx:}] entry.link_name={:#?}", x.link_name()); + debug!("[{idx:}] entry.size={:#?}", x.size()); + debug!("[{idx:}] size={size:}"); + error!("[extract::unpack_tarball] Failed to unpack file {filename} ({e:})"); + let error = BeansError::TarUnpackItemFailure { src_file: tarball_location, target_dir: output_directory, link_name: filename, error: e, - backtrace: Backtrace::capture() - }); + backtrace: Backtrace::capture(), + }; + debug!("[{idx:}] {:#?}", error); + return Err(error); } pb.inc(1); } Err(e) => { + pb.finish_and_clear(); + debug!("[{idx:}] error={:#?}", e); + debug!("[extract::unpack_tarball] idx: {idx:}, size={size:}"); + error!("[extract::unpack_tarball] Failed to unpack entry ({e:})"); return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, @@ -105,6 +112,9 @@ pub fn unpack_tarball( } Err(e) => { + pb.finish_and_clear(); + debug!("{:#?}", e); + error!("[extract::unpack_tarball] Failed to extract tarball entries (src: {tarball_location:}, dest: {output_directory:}, error: {e:})"); return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, @@ -114,11 +124,14 @@ pub fn unpack_tarball( } }; pb.finish(); + debug!("[extract::unpack_tarball] Total entries extracted: {idx:}"); } else { - if let Err(e) = archive.unpack(&output_directory) + if let Err(e) = tar::Archive::new(&tarball).unpack(&output_directory) { + debug!("{:#?}", e); + error!("[extract::unpack_tarball] Failed to unpack {tarball_location} to directory {output_directory} ({e:}"); return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, @@ -129,6 +142,19 @@ pub fn unpack_tarball( } Ok(()) } +fn open_tarball_file(tarball_location: String, output_directory: String) -> Result +{ + match File::open(&tarball_location) + { + Ok(x) => Ok(x), + Err(e) => Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture() + }) + } +} pub fn decompress_zstd( zstd_location: String, output_file: String, From 7986d2770e8272d31fb47f0d21269bc3c09a3a93 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 10 Sep 2024 12:23:03 +0800 Subject: [PATCH 59/65] [Launcher::find_arg_sourcemods_location] Create directory if it doesn't exist --- src/main.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 58d83cd..b3d8d46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -259,8 +259,17 @@ impl Launcher let mut sml_dir_manual: Option = None; if let Some(x) = matches.get_one::("location") { + if !helper::dir_exists(x.clone()) + { + if let Err(e) = std::fs::create_dir(x) + { + debug!("{:#?}", e); + error!("[Launcher::find_arg_sourcemods_location] Failed to create directory {x:?} ({e:})"); + panic!("[Launcher::find_arg_sourcemods_location] Failed to create directory {x:?}\n\n{e:#?}") + } + } sml_dir_manual = Some(parse_location(x.to_string())); - info!("[Launcher::set_to_location] Found in arguments! {}", x); + info!("[Launcher::find_arg_sourcemods_location] Found in arguments! {}", x); } sml_dir_manual } From 64040c234cf97e6016fa4872a62d51be003a7d1a Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 10 Sep 2024 12:23:46 +0800 Subject: [PATCH 60/65] [InstallWorkflow::install_from] Create output directory if it doesn't exist --- src/workflows/install.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/workflows/install.rs b/src/workflows/install.rs index 13b7fc5..eca8ef8 100644 --- a/src/workflows/install.rs +++ b/src/workflows/install.rs @@ -145,6 +145,9 @@ impl InstallWorkflow version_id: Option ) -> Result<(), BeansError> { + debug!("[InstallWorkflow::install_from] package_loc={package_loc:}"); + debug!("[InstallWorkflow::install_from] out_dir={out_dir:}"); + debug!("[InstallWorkflow::install_from] version_id={version_id:?}"); if !helper::file_exists(package_loc.clone()) { error!("[InstallWorkflow::Wizard] Failed to find package! (location: {package_loc})"); @@ -154,8 +157,20 @@ impl InstallWorkflow } }); } - - println!("[InstallWorkflow::Wizard] Extracting to {out_dir}"); + if !helper::dir_exists(out_dir.clone()) + { + if let Err(e) = std::fs::create_dir(&out_dir) + { + debug!("{:#?}", e); + error!("[InstallWorkflow::install_from] Failed to create output directory, {out_dir} ({e:})"); + return Err(BeansError::DirectoryCreateFailure { + location: out_dir.clone(), + error: e, + backtrace: std::backtrace::Backtrace::capture(), + }); + } + } + info!("[InstallWorkflow::Wizard] Extracting to {out_dir}"); RunnerContext::extract_package(package_loc, out_dir.clone())?; if let Some(lri) = version_id { @@ -165,7 +180,7 @@ impl InstallWorkflow .write(Some(out_dir.clone())); if let Err(e) = x { - println!( + warn!( "[InstallWorkflow::install_from] Failed to set version to {} in .adastral", lri ); @@ -178,6 +193,7 @@ impl InstallWorkflow } let av = crate::appvar::parse(); println!("{}", av.sub(INSTALL_FINISH_MSG.to_string())); + debug!("[InstallWorkflow::install_from] Displayed INSTALL_FINISH_MSG"); Ok(()) } } From d4b7a4d2931a2044c667f93f7507bfbeb43aca4f Mon Sep 17 00:00:00 2001 From: ToastXC <100072983+toastxc@users.noreply.github.com> Date: Sat, 14 Sep 2024 22:03:57 +0800 Subject: [PATCH 61/65] final clippy changes --- src/extract.rs | 82 ++++++++++++++++++++++++++++++----------------- src/helper/mod.rs | 2 +- src/main.rs | 4 +-- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index b405630..447e223 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,6 +1,8 @@ -use std::{backtrace::Backtrace, fs::File}; +use std::{backtrace::Backtrace, + fs::File}; -use indicatif::{ProgressBar, ProgressStyle}; +use indicatif::{ProgressBar, + ProgressStyle}; use log::info; use zstd::stream::read::Decoder as ZstdDecoder; @@ -9,41 +11,48 @@ use crate::BeansError; pub fn unpack_tarball( tarball_location: String, output_directory: String, - show_progress: bool, -) -> Result<(), BeansError> { - let tarball = match File::open(&tarball_location) { + show_progress: bool +) -> Result<(), BeansError> +{ + let tarball = match File::open(&tarball_location) + { Ok(x) => x, - Err(e) => { + Err(e) => + { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } }; let mut archive = tar::Archive::new(&tarball); - if !show_progress { - if let Err(e) = archive.unpack(&output_directory) { + if !show_progress + { + if let Err(e) = archive.unpack(&output_directory) + { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } return Ok(()); }; - let archive_entries = match archive.entries() { + let archive_entries = match archive.entries() + { Ok(v) => v, - Err(e) => { + Err(e) => + { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, error: e, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } }; @@ -58,47 +67,56 @@ pub fn unpack_tarball( pb.set_message("Extracting files"); archive = tar::Archive::new(&tarball); - let entries = match archive.entries() { + let entries = match archive.entries() + { Ok(a) => a, - Err(error) => { + Err(error) => + { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, error, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } }; - for entry in entries { - match entry { - Ok(mut x) => { + for entry in entries + { + match entry + { + Ok(mut x) => + { pb.set_message("Extracting files"); let mut filename = String::new(); - if let Ok(Some(p)) = x.link_name() { - if let Some(s) = p.to_str() { + if let Ok(Some(p)) = x.link_name() + { + if let Some(s) = p.to_str() + { pb.set_message(s.to_string()); filename = String::from(s); } } - if let Err(error) = x.unpack_in(&output_directory) { + if let Err(error) = x.unpack_in(&output_directory) + { return Err(BeansError::TarUnpackItemFailure { src_file: tarball_location, target_dir: output_directory, link_name: filename, error, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } pb.inc(1); } - Err(error) => { + Err(error) => + { return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, error, - backtrace: Backtrace::capture(), + backtrace: Backtrace::capture() }); } } @@ -109,16 +127,18 @@ pub fn unpack_tarball( pub fn decompress_zstd( zstd_location: String, output_file: String, - show_progress: bool, -) -> Result<(), BeansError> { + show_progress: bool +) -> Result<(), BeansError> +{ let zstd_file = File::open(&zstd_location)?; let zstd_file_length = &zstd_file.metadata()?.len(); let mut tar_tmp_file = File::create_new(&output_file)?; - if show_progress { + if show_progress + { let decoder = ZstdDecoder::new(zstd_file)?; // estimate extracted size as x2 since idk how to get the decompressed size with // zstd - let pb_decompress = ProgressBar::new((*zstd_file_length * 2)); + let pb_decompress = ProgressBar::new(*zstd_file_length * 2); pb_decompress .set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})") .unwrap() @@ -128,7 +148,9 @@ pub fn decompress_zstd( std::io::copy(&mut pb_decompress.wrap_read(decoder), &mut tar_tmp_file) .expect("Failed to decompress file"); pb_decompress.finish(); - } else { + } + else + { zstd::stream::copy_decode(zstd_file, &tar_tmp_file)?; } diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 3e7e485..7f0d609 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -337,7 +337,7 @@ where { let mut sys = sysinfo::System::new_all(); sys.refresh_all(); - for (_, process) in sys.processes() + for process in sys.processes().values() { if selector(process) { diff --git a/src/main.rs b/src/main.rs index 6960dc4..e9ca47c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,7 +83,7 @@ fn init_panic_handle() let mut x = String::new(); if let Some(m) = info.message() { - x = format!("{:#?}", m); + x = format!("{}", m); } info!("[panic] Fatal error!\n{:#?}", x); custom_panic_handle(x); @@ -506,7 +506,7 @@ impl Launcher matches: &ArgMatches ) { - self.to_location = Launcher::find_arg_sourcemods_location(&matches); + self.to_location = Launcher::find_arg_sourcemods_location(matches); let mut ctx = self.try_create_context().await; if let Err(e) = UninstallWorkflow::wizard(&mut ctx).await From 04a7421ae294274745e9be132800f0c2af2908a3 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 15 Sep 2024 17:53:21 +0800 Subject: [PATCH 62/65] Merge branch 'develop' into housekeeping/clippy-2-katefix --- src/ctx.rs | 5 ++-- src/depends.rs | 8 ++----- src/error.rs | 6 +++-- src/extract.rs | 51 +++++++++++++++++++++++++++------------- src/gui/dialog.rs | 30 ++++------------------- src/gui/shared_ui.fl | 10 ++++---- src/helper/windows.rs | 8 +++---- src/main.rs | 11 ++++++++- src/workflows/clean.rs | 11 ++++++--- src/workflows/install.rs | 21 +++++++++++++++-- 10 files changed, 93 insertions(+), 68 deletions(-) diff --git a/src/ctx.rs b/src/ctx.rs index 172fc4f..f671c9c 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -4,8 +4,7 @@ use std::os::unix::fs::PermissionsExt; use log::{debug, error, - info, - trace}; + info}; use crate::{depends, helper, @@ -330,7 +329,7 @@ impl RunnerContext { if let Err(e) = std::fs::remove_file(&ln_location) { - trace!( + debug!( "[RunnerContext::prepare_symlink] failed to remove {}\n{:#?}", ln_location, e diff --git a/src/depends.rs b/src/depends.rs index 08ec580..ff80c6e 100644 --- a/src/depends.rs +++ b/src/depends.rs @@ -77,11 +77,7 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> Ok(v) => { let x: std::io::Result = v.get_value("Installed"); - match x - { - Ok(_) => false, - Err(_) => true - } + x.is_err() } Err(_) => true } @@ -100,7 +96,7 @@ pub async fn try_install_vcredist() -> Result<(), BeansError> ) .await?; - if std::path::Path::new(&out_loc).exists() == false + if !std::path::Path::new(&out_loc).exists() { return Err(BeansError::FileNotFound { location: out_loc.clone(), diff --git a/src/error.rs b/src/error.rs index c7109ca..ea39f93 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,7 +36,8 @@ pub enum BeansError DirectoryCreateFailure { location: String, - error: std::io::Error + error: std::io::Error, + backtrace: Backtrace }, #[error("Failed to delete directory {location} ({error:})")] DirectoryDeleteFailure @@ -209,7 +210,8 @@ pub enum BeansError CleanTempFailure { location: String, - error: std::io::Error + error: std::io::Error, + backtrace: Backtrace }, #[error("{name:} ({pid:}) is still running. Please close it and restart beans.")] diff --git a/src/extract.rs b/src/extract.rs index 447e223..77b1cea 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -3,30 +3,35 @@ use std::{backtrace::Backtrace, use indicatif::{ProgressBar, ProgressStyle}; -use log::info; +use log::{info, + debug, + error}; use zstd::stream::read::Decoder as ZstdDecoder; use crate::BeansError; +fn unpack_tarball_getfile(tarball_location: String, output_directory: String) -> Result +{ + match File::open(&tarball_location) { + Ok(x) => Ok(x), + Err(e) => { + Err(BeansError::TarExtractFailure { + src_file: tarball_location, + target_dir: output_directory, + error: e, + backtrace: Backtrace::capture(), + }) + } + } +} + pub fn unpack_tarball( tarball_location: String, output_directory: String, show_progress: bool ) -> Result<(), BeansError> { - let tarball = match File::open(&tarball_location) - { - Ok(x) => x, - Err(e) => - { - return Err(BeansError::TarExtractFailure { - src_file: tarball_location, - target_dir: output_directory, - error: e, - backtrace: Backtrace::capture() - }); - } - }; + let mut tarball = unpack_tarball_getfile(tarball_location.clone(), output_directory.clone())?; let mut archive = tar::Archive::new(&tarball); if !show_progress @@ -59,19 +64,23 @@ pub fn unpack_tarball( let archive_entry_count = archive_entries.count() as u64; info!("Extracting {} files", archive_entry_count); + tarball = unpack_tarball_getfile(tarball_location.clone(), output_directory.clone())?; + archive = tar::Archive::new(&tarball); + archive.set_preserve_permissions(false); + let pb = ProgressBar::new(archive_entry_count); pb.set_style(ProgressStyle::with_template("{msg}\n{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ({eta})") .unwrap() .with_key("eta", |state: &indicatif::ProgressState, w: &mut dyn std::fmt::Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) .progress_chars("#>-")); pb.set_message("Extracting files"); - archive = tar::Archive::new(&tarball); let entries = match archive.entries() { Ok(a) => a, Err(error) => { + pb.finish_and_clear(); return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, @@ -81,7 +90,7 @@ pub fn unpack_tarball( } }; - for entry in entries + for (size, entry) in entries.enumerate() { match entry { @@ -100,6 +109,13 @@ pub fn unpack_tarball( } if let Err(error) = x.unpack_in(&output_directory) { + pb.finish_and_clear(); + debug!("error={:#?}", error); + debug!("entry.path={:#?}", x.path()); + debug!("entry.link_name={:#?}", x.link_name()); + debug!("entry.size={:#?}", x.size()); + debug!("size={size:}"); + error!("[extract::unpack_tarball] Failed to unpack file {filename} ({error:})"); return Err(BeansError::TarUnpackItemFailure { src_file: tarball_location, target_dir: output_directory, @@ -112,6 +128,9 @@ pub fn unpack_tarball( } Err(error) => { + pb.finish_and_clear(); + debug!("[extract::unpack_tarball] size={size:}, error={:#?}", error); + error!("[extract::unpack_tarball] Failed to unpack entry ({error:})"); return Err(BeansError::TarExtractFailure { src_file: tarball_location, target_dir: output_directory, diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index 7b14c15..7c4bb40 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -1,4 +1,5 @@ use fltk::{image::PngImage, + text::TextBuffer, prelude::*, *}; use log::warn; @@ -99,37 +100,14 @@ impl DialogBuilder apply_app_scheme(); let (send_action, receive_action) = app::channel::(); let mut ui = GenericDialog::make_window(); - let initial_width = ui.win.width(); + let mut text_buffer = TextBuffer::default(); + text_buffer.append(&self.content); + ui.txt_disp.set_buffer(text_buffer.clone()); ui.win.set_icon(self.icon.clone()); ui.win.set_label(&self.title); - ui.label.set_label(&self.content); - ui.btn_ok.set_size(70, 24); ui.btn_ok.emit(send_action, GUIAppStatus::Quit); - - let (label_w, label_h) = ui.label.measure_label(); - ui.win - .set_size(25 + label_w + 25, 10 + label_h + 5 + ui.btn_ok.height() + 5); - - ui.btn_ok.set_pos(25, ui.win.height() - 24 - 5); window_centre_screen(&mut ui.win); - ui.win.handle(move |w, ev| match ev - { - fltk::enums::Event::Resize => - { - let height = w.height(); - ui.btn_ok.set_pos(25, height - 24 - 5); - ui.btn_ok.set_size(70, 24); - let (lw, lh) = ui.label.measure_label(); - let cw = w.width(); - if cw != initial_width && cw > lw + 50 - { - w.set_size(lw + 50, 10 + lh + 5 + ui.btn_ok.height() + 5); - } - false - } - _ => false - }); ui.win.make_resizable(false); ui.win.show(); wait_for_quit(&app, &receive_action); diff --git a/src/gui/shared_ui.fl b/src/gui/shared_ui.fl index 716a2ec..ad70c0e 100644 --- a/src/gui/shared_ui.fl +++ b/src/gui/shared_ui.fl @@ -7,14 +7,14 @@ class GenericDialog {open Function {make_window()} {open } { Fl_Window win {open - xywh {376 147 500 150} type Double visible + xywh {1070 474 600 350} type Double visible } { - Fl_Text_Display label { - xywh {25 10 0 0} box NO_BOX align 11 - } Fl_Button btn_ok { label Ok - xywh {25 121 70 24} + xywh {25 321 70 24} + } + Fl_Text_Display txt_disp { + xywh {25 10 550 301} align 5 } } } diff --git a/src/helper/windows.rs b/src/helper/windows.rs index 80e963a..efa2338 100644 --- a/src/helper/windows.rs +++ b/src/helper/windows.rs @@ -21,23 +21,23 @@ pub fn find_sourcemod_path() -> Result Ok(val) => Ok(format_directory_path(val)), Err(e) => { - return Err(BeansError::RegistryKeyFailure { + Err(BeansError::RegistryKeyFailure { msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" .to_string(), error: e, backtrace: Backtrace::capture() - }); + }) } } } Err(e) => { - return Err(BeansError::RegistryKeyFailure { + Err(BeansError::RegistryKeyFailure { msg: "Failed to find HKCU\\Software\\Valve. Steam might not be installed" .to_string(), error: e, backtrace: Backtrace::capture() - }); + }) } } } diff --git a/src/main.rs b/src/main.rs index e9ca47c..3042e71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -259,8 +259,17 @@ impl Launcher let mut sml_dir_manual: Option = None; if let Some(x) = matches.get_one::("location") { + if !helper::dir_exists(x.clone()) + { + if let Err(e) = std::fs::create_dir(x) + { + debug!("{:#?}", e); + error!("[Launcher::find_arg_sourcemods_location] Failed to create directory {x:?} ({e:})"); + panic!("[Launcher::find_arg_sourcemods_location] Failed to create directory {x:?}\n\n{e:#?}") + } + } sml_dir_manual = Some(parse_location(x.to_string())); - info!("[Launcher::set_to_location] Found in arguments! {}", x); + info!("[Launcher::find_arg_sourcemods_location] Found in arguments! {}", x); } sml_dir_manual } diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index e11f139..b67ffb0 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -1,4 +1,5 @@ -use log::{info, +use log::{debug, + info, warn}; use crate::{helper, @@ -26,18 +27,22 @@ impl CleanWorkflow // delete directory and it's contents (and error handling) if let Err(e) = std::fs::remove_dir_all(&target_directory) { + debug!("[CleanWorkflow::wizard] remove_dir_all {:#?}", e); return Err(BeansError::CleanTempFailure { location: target_directory, - error: e + error: e, + backtrace: std::backtrace::Backtrace::capture(), }); } // re-creating the temporary directory (and error handling) if let Err(e) = std::fs::create_dir(&target_directory) { + debug!("[CleanWorkflow::wizard] create_dir {:#?}", e); return Err(BeansError::DirectoryCreateFailure { location: target_directory, - error: e + error: e, + backtrace: std::backtrace::Backtrace::capture(), }); } diff --git a/src/workflows/install.rs b/src/workflows/install.rs index 13b7fc5..4915bd9 100644 --- a/src/workflows/install.rs +++ b/src/workflows/install.rs @@ -145,6 +145,9 @@ impl InstallWorkflow version_id: Option ) -> Result<(), BeansError> { + debug!("[InstallWorkflow::install_from] package_loc={package_loc:}"); + debug!("[InstallWorkflow::install_from] out_dir={out_dir:}"); + debug!("[InstallWorkflow::install_from] version_id={version_id:?}"); if !helper::file_exists(package_loc.clone()) { error!("[InstallWorkflow::Wizard] Failed to find package! (location: {package_loc})"); @@ -154,8 +157,21 @@ impl InstallWorkflow } }); } + if !helper::dir_exists(out_dir.clone()) + { + if let Err(e) = std::fs::create_dir(&out_dir) + { + debug!("{:#?}", e); + error!("[InstallWorkflow::install_from] Failed to create output directory, {out_dir} ({e:})"); + return Err(BeansError::DirectoryCreateFailure { + location: out_dir.clone(), + error: e, + backtrace: std::backtrace::Backtrace::capture(), + }); + } + } + info!("[InstallWorkflow::Wizard] Extracting to {out_dir}"); - println!("[InstallWorkflow::Wizard] Extracting to {out_dir}"); RunnerContext::extract_package(package_loc, out_dir.clone())?; if let Some(lri) = version_id { @@ -165,7 +181,7 @@ impl InstallWorkflow .write(Some(out_dir.clone())); if let Err(e) = x { - println!( + warn!( "[InstallWorkflow::install_from] Failed to set version to {} in .adastral", lri ); @@ -178,6 +194,7 @@ impl InstallWorkflow } let av = crate::appvar::parse(); println!("{}", av.sub(INSTALL_FINISH_MSG.to_string())); + debug!("[InstallWorkflow::install_from] Displayed INSTALL_FINISH_MSG"); Ok(()) } } From 81ce7cdb494cececfa3a1b850ddb99cde6bdc8af Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 15 Sep 2024 18:00:07 +0800 Subject: [PATCH 63/65] [extract::unpack_tarball] Fix progress bar being cleared in for loop --- src/extract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extract.rs b/src/extract.rs index 8bea4b7..9c36b10 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -139,8 +139,8 @@ pub fn unpack_tarball( }); } } - pb.finish(); } + pb.finish(); Ok(()) } fn open_tarball_file(tarball_location: String, output_directory: String) -> Result From bf2845037b86fb1a33b080785d3dca996e5940f1 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 15 Sep 2024 18:00:42 +0800 Subject: [PATCH 64/65] [extract] Remove unused function --- src/extract.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index 9c36b10..1349735 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -143,19 +143,7 @@ pub fn unpack_tarball( pb.finish(); Ok(()) } -fn open_tarball_file(tarball_location: String, output_directory: String) -> Result -{ - match File::open(&tarball_location) - { - Ok(x) => Ok(x), - Err(e) => Err(BeansError::TarExtractFailure { - src_file: tarball_location, - target_dir: output_directory, - error: e, - backtrace: Backtrace::capture() - }) - } -} + pub fn decompress_zstd( zstd_location: String, output_file: String, From 06f30bd6daffe9cd93a55d3849174bc7ba403372 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 29 Sep 2024 13:40:46 +0800 Subject: [PATCH 65/65] Updated README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9416093..2a55743 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # beans-rs -A Sourcemod Installer written with Rust, using the kachemak versioning system. Intended for general-purpose use, and for server owners. +A Sourcemod Installer written with Rust, using the Kachemak versioning system (based off TF2C). Intended for general-purpose use, and for server owners. This is a complete rewrite of the original [beans](https://github.com/int-72h/ofinstaller-beans) installer, but with rust, and extended support. `beans-rs` is licensed under `GPLv3-only`, so please respect it! -**Note** Releases for Linux v1.5.0 and later are built with Ubuntu 20.04 (using `libssl v1.1`) +**Note** Releases for Linux v1.5.0 and later are built with Ubuntu 20.04 ## Developing Requirements