From b64e1e3a4640b591ec6f888600ccbd5e75da2362 Mon Sep 17 00:00:00 2001 From: hinto-janaiyo Date: Wed, 2 Nov 2022 13:58:44 -0400 Subject: [PATCH] update: save [Version] to state, use runtime [og: State] [og: State] is now completely wrapped in an [Arc] so that when the update is done, it can [.lock()] the CURRENT runtime settings of the user and save to [gupax.toml] instead of using an old copy that was given to it at the beginning of the thread. In practice, this means users can change settings around during an update and the update finishing and saving to disk won't be using their old settings, but the current ones. Wrapping all of [og: State] within in [Arc] might be overkill compared to message channels but [State] really is just a few [bool]'s, [u*], and small [String]'s, so it's not much data. To bypass a deadlock when comparing [og == state] every frame, [og]'s struct fields get cloned every frame into separate variables, then it gets compared. This is also pretty stupid, but again, the data being cloned is so tiny that it doesn't seem to slow anything down. --- Cargo.lock | 15 -------- Cargo.toml | 27 ++++++++------ src/gupax.rs | 42 ++++++++++++++-------- src/main.rs | 79 ++++++++++++++++++++++++++++++++--------- src/node.rs | 2 +- src/state.rs | 25 ++++++------- src/update.rs | 97 +++++++++++++++++++++++++++++++++++---------------- 7 files changed, 187 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14aae8f..9210fa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1158,8 +1158,6 @@ dependencies = [ [[package]] name = "eframe" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d49426c3e72a6728b0c790d22db8bf7bbcff10d83b8b6f3a01295be982302e" dependencies = [ "bytemuck", "egui", @@ -1180,8 +1178,6 @@ dependencies = [ [[package]] name = "egui" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4" dependencies = [ "ahash 0.8.0", "epaint", @@ -1192,8 +1188,6 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ddc525334c416e11580123e147b970f738507f427c9fb1cd09ea2dd7416a3a" dependencies = [ "arboard", "egui", @@ -1207,8 +1201,6 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f698f685bb0ad39e87109e2f695ded0bccde77d5d40bbf7590cb5561c1e3039d" dependencies = [ "egui", "image", @@ -1217,8 +1209,6 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad77d4a00402bae9658ee64be148f4b2a0b38e4fc7874970575ca01ed1c5b75d" dependencies = [ "bytemuck", "egui", @@ -1238,8 +1228,6 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "emath" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9542a40106fdba943a055f418d1746a050e1a903a049b030c2b097d4686a33cf" dependencies = [ "bytemuck", ] @@ -1296,8 +1284,6 @@ dependencies = [ [[package]] name = "epaint" version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300" dependencies = [ "ab_glyph", "ahash 0.8.0", @@ -1809,7 +1795,6 @@ dependencies = [ "reqwest", "rusqlite", "serde", - "serde_derive", "serde_json", "sha2 0.10.6", "tar", diff --git a/Cargo.toml b/Cargo.toml index a16b273..e5fa50a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,17 +10,17 @@ arti-hyper = "0.7.0" bytes = "1.2.1" chrono = "0.4.22" dirs = "4.0.0" -eframe = "0.19.0" -egui = "0.19.0" -egui_extras = { version = "0.19.0", features = ["image"] } +#eframe = "0.19.0" +#egui = "0.19.0" +#egui_extras = { version = "0.19.0", features = ["image"] } ## [external/egui/crates/eframe/src/native/run.rs] line 41: [.with_srgb(true)] ## This line causes a [panic!] inside a Windows VM, from a Linux host. ## There are many issue threads and PRs to fix it but for now, ## this is here for convenience sake when I'm testing. ## The only change is [.with_srgb()] is set to [false]. -#eframe = { path = "external/egui/crates/eframe" } -#egui = { path = "external/egui/crates/egui" } -#egui_extras = { path = "external/egui/crates/egui_extras", features = ["image"] } +eframe = { path = "external/egui/crates/eframe" } +egui = { path = "external/egui/crates/egui" } +egui_extras = { path = "external/egui/crates/egui_extras", features = ["image"] } env_logger = "0.9.1" figment = { version = "0.10.8", features = ["toml"] } flate2 = "1.0" @@ -37,24 +37,29 @@ rand = "0.8.5" regex = "1.6.0" reqwest = { version = "0.11.12", features = ["blocking", "json"] } rusqlite = { version = "0.28.0", features = ["bundled"] } -serde = "1.0.145" -serde_derive = "1.0.145" +serde = { version = "1.0.145", features = ["rc", "derive"] } serde_json = "1.0" sha2 = "0.10.6" -tar = "0.4.38" tls-api = "0.9.0" tls-api-native-tls = "0.9.0" tokio = { version = "1.21.2", features = ["full"] } toml = "0.5.9" tor-rtcompat = "0.7.0" walkdir = "2.3.2" + +# Unix dependencies +[target.'cfg(unix)'.dependencies] +tar = "0.4.38" + +# Windows dependencies +[target.'cfg(windows)'.dependencies] zip = "0.6.3" -# For Windows +# For Windows build (icon) [target.'cfg(windows)'.build-dependencies] winres = "0.1.12" -# For macOS (cargo-bundle) +# For macOS build (cargo-bundle) [package.metadata.bundle] identifier = "io.github.hinto-janaiyo.gupax" icon = ["images/png/icon@2x.png"] diff --git a/src/gupax.rs b/src/gupax.rs index a1cf49a..8c92d29 100644 --- a/src/gupax.rs +++ b/src/gupax.rs @@ -16,7 +16,7 @@ // along with this program. If not, see . use std::path::Path; -use crate::App; +use crate::{App,State}; use egui::WidgetType::Button; use crate::constants::*; use crate::state::{Gupax,Version}; @@ -26,7 +26,7 @@ use std::sync::{Arc,Mutex}; use log::*; impl Gupax { - pub fn show(state: &mut Gupax, og: &Gupax, width: f32, height: f32, update: &mut Update, version: Version, ctx: &egui::Context, ui: &mut egui::Ui) { + pub fn show(state: &mut Gupax, og: &Arc>, state_ver: &Arc>, update: &Arc>, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) { // Update button + Progress bar ui.group(|ui| { // These are in unnecessary [ui.vertical()]'s @@ -35,30 +35,44 @@ impl Gupax { // I have to pick one. This one seperates them though. let height = height/6.0; let width = width - SPACE; - let updating = *update.updating.lock().unwrap(); + let updating = *update.lock().unwrap().updating.lock().unwrap(); ui.vertical(|ui| { ui.set_enabled(!updating); if ui.add_sized([width, height], egui::Button::new("Check for updates")).on_hover_text(GUPAX_UPDATE).clicked() { - update.path_p2pool = og.absolute_p2pool_path.display().to_string(); - update.path_xmrig = og.absolute_xmrig_path.display().to_string(); - update.tor = og.update_via_tor; - let update = Arc::new(Mutex::new(update.clone())); + update.lock().unwrap().path_p2pool = og.lock().unwrap().gupax.absolute_p2pool_path.display().to_string(); + update.lock().unwrap().path_xmrig = og.lock().unwrap().gupax.absolute_xmrig_path.display().to_string(); + update.lock().unwrap().tor = og.lock().unwrap().gupax.update_via_tor; + let og = Arc::clone(&og); + let og_ver = Arc::clone(&og.lock().unwrap().version); + let state_ver = Arc::clone(&state_ver); + let update = Arc::clone(&update); + let update_thread = Arc::clone(&update); thread::spawn(move|| { info!("Spawning update thread..."); - match Update::start(update.clone(), version) { + match Update::start(update_thread, og_ver.clone(), state_ver.clone()) { Err(e) => { - info!("Update | {} ... FAIL", e); + info!("Update ... {} ... FAIL", e); *update.lock().unwrap().msg.lock().unwrap() = format!("{} | {}", MSG_FAILED, e); - *update.lock().unwrap().updating.lock().unwrap() = false; }, - _ => (), - } + _ => { + info!("Update | Saving state..."); + match State::save(&mut og.lock().unwrap()) { + Ok(_) => info!("Update ... OK"), + Err(e) => { + warn!("Update | Saving state ... FAIL ... {}", e); + *update.lock().unwrap().msg.lock().unwrap() = format!("Saving new versions into state failed"); + }, + }; + } + }; + *update.lock().unwrap().updating.lock().unwrap() = false; }); } }); ui.vertical(|ui| { ui.set_enabled(updating); - let msg = format!("{}\n{}{}", *update.msg.lock().unwrap(), *update.prog.lock().unwrap(), "%"); + let prog = *update.lock().unwrap().prog.lock().unwrap(); + let msg = format!("{}\n{}{}", *update.lock().unwrap().msg.lock().unwrap(), prog, "%"); ui.add_sized([width, height*1.4], egui::Label::new(msg)); let height = height/2.0; if updating { @@ -66,7 +80,7 @@ impl Gupax { } else { ui.add_sized([width, height], egui::Label::new("...")); } - ui.add_sized([width, height], egui::ProgressBar::new((update.prog.lock().unwrap().round() / 100.0))); + ui.add_sized([width, height], egui::ProgressBar::new((update.lock().unwrap().prog.lock().unwrap().round() / 100.0))); }); }); diff --git a/src/main.rs b/src/main.rs index da8b77e..32832b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ // along with this program. If not, see . // Hide console in Windows -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +//#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] //---------------------------------------------------------------------------------------------------- Imports // egui/eframe @@ -70,10 +70,10 @@ pub struct App { width: f32, // Top-level width height: f32, // Top-level height // State - og: State, // og = Old state to compare against + og: Arc>, // og = Old state to compare against state: State, // state = Working state (current settings) - update: Update, // State for update data [update.rs] - diff: bool, // Instead of comparing [og == state] every frame, this bool indicates changes + update: Arc>, // State for update data [update.rs] + diff: bool, // This bool indicates state changes // Process/update state: // Doesn't make sense to save this on disk // so it's represented as a bool here. @@ -113,9 +113,9 @@ impl App { width: 1280.0, height: 720.0, node: Arc::new(Mutex::new(NodeStruct::default())), - og: State::default(), + og: Arc::new(Mutex::new(State::default())), state: State::default(), - update: Update::new(PathBuf::new(), PathBuf::new(), true), + update: Arc::new(Mutex::new(Update::new(PathBuf::new(), PathBuf::new(), true))), diff: false, p2pool: false, xmrig: false, @@ -141,16 +141,23 @@ impl App { // Read disk state if no [--reset] arg if app.reset == false { app.og = match State::get() { - Ok(toml) => toml, + Ok(toml) => Arc::new(Mutex::new(toml)), Err(err) => { panic_main(err.to_string()); exit(1); }, }; } - app.state = app.og.clone(); + app.state = app.og.lock().unwrap().clone(); // Handle max threads - app.og.xmrig.max_threads = num_cpus::get(); - if app.og.xmrig.current_threads > app.og.xmrig.max_threads { app.og.xmrig.current_threads = app.og.xmrig.max_threads; } + app.og.lock().unwrap().xmrig.max_threads = num_cpus::get(); + let current = app.og.lock().unwrap().xmrig.current_threads; + let max = app.og.lock().unwrap().xmrig.max_threads; + if current > max { + app.og.lock().unwrap().xmrig.current_threads = max; + } // Apply TOML values to [Update] - app.update = Update::new(app.og.gupax.absolute_p2pool_path.clone(), app.og.gupax.absolute_xmrig_path.clone(), app.og.gupax.update_via_tor); + let p2pool_path = app.og.lock().unwrap().gupax.absolute_p2pool_path.clone(); + let xmrig_path = app.og.lock().unwrap().gupax.absolute_xmrig_path.clone(); + let tor = app.og.lock().unwrap().gupax.update_via_tor; + app.update = Arc::new(Mutex::new(Update::new(p2pool_path, xmrig_path, tor))); app } } @@ -304,6 +311,22 @@ pub fn get_rand_tmp(path: &String) -> String { path } +// Clean any [gupax_tmp.*] directories +pub fn clean_dir() -> Result<(), anyhow::Error> { + for entry in std::fs::read_dir(get_exe_dir()?)? { + let entry = entry?; + entry.path().is_dir() && continue; + if entry.file_name().to_str().ok_or(anyhow::Error::msg("Basename failed"))?.starts_with("gupax_tmp_") { + let path = entry.path(); + match std::fs::remove_dir_all(&path) { + Ok(_) => info!("Remove [{}] ... OK", path.display()), + Err(e) => warn!("Remove [{}] ... FAIL ... {}", path.display(), e), + } + } + } + Ok(()) +} + //---------------------------------------------------------------------------------------------------- [App] frame for [Panic] situations fn panic_main(error: String) { error!("{}", error); @@ -358,6 +381,10 @@ impl eframe::App for Panic { fn main() { init_logger(); let options = init_options(); + match clean_dir() { + Ok(_) => info!("Temporary folder cleanup ... OK"), + Err(e) => warn!("Could not cleanup [gupax_tmp] folders: {}", e), + } let app = App::new(); let name = app.name_version.clone(); eframe::run_native(&name, options, Box::new(|cc| Box::new(App::cc(cc, app))),); @@ -366,7 +393,7 @@ fn main() { impl eframe::App for App { fn on_close_event(&mut self) -> bool { self.quit = true; - if self.og.gupax.ask_before_quit { + if self.og.lock().unwrap().gupax.ask_before_quit { self.quit_confirm } else { true @@ -390,6 +417,17 @@ impl eframe::App for App { frame.set_fullscreen(!info.window_info.fullscreen); } + // If no state diff (og == state), compare and enable if found. + // The struct fields are compared directly because [Version] + // contains Arc's that cannot be compared easily. + // They don't need to be compared anyway. + let og = self.og.lock().unwrap().clone(); + if og.gupax != self.state.gupax || og.p2pool != self.state.p2pool || og.xmrig != self.state.xmrig { + self.diff = true; + } else { + self.diff = false; + } + // Close confirmation. if self.quit { // If [ask_before_quit == true] @@ -521,12 +559,21 @@ impl eframe::App for App { ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| { ui.group(|ui| { - if self.state == self.og { + if self.diff == false { ui.set_enabled(false) } let width = width / 2.0; - if ui.add_sized([width, height], egui::Button::new("Save")).on_hover_text("Save changes").clicked() { self.og = self.state.clone(); self.state.save(); } - if ui.add_sized([width, height], egui::Button::new("Reset")).on_hover_text("Reset changes").clicked() { self.state = self.og.clone(); } + if ui.add_sized([width, height], egui::Button::new("Save")).on_hover_text("Save changes").clicked() { + self.og.lock().unwrap().gupax = self.state.gupax.clone(); + self.og.lock().unwrap().p2pool = self.state.p2pool.clone(); + self.og.lock().unwrap().xmrig = self.state.xmrig.clone(); + self.og.lock().unwrap().save(); + } + if ui.add_sized([width, height], egui::Button::new("Reset")).on_hover_text("Reset changes").clicked() { + self.state.gupax = self.og.lock().unwrap().gupax.clone(); + self.state.p2pool = self.og.lock().unwrap().p2pool.clone(); + self.state.xmrig = self.og.lock().unwrap().xmrig.clone(); + } }); let width = (ui.available_width() / 3.0) - 6.2; @@ -615,7 +662,7 @@ impl eframe::App for App { Status::show(self, self.width, self.height, ctx, ui); } Tab::Gupax => { - Gupax::show(&mut self.state.gupax, &self.og.gupax, self.width, self.height, &mut self.update, self.og.version.clone(), ctx, ui); + Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, self.width, self.height, ctx, ui); } Tab::P2pool => { P2pool::show(&mut self.state.p2pool, self.width, self.height, ctx, ui); diff --git a/src/node.rs b/src/node.rs index 007cbe9..bbfdbd5 100644 --- a/src/node.rs +++ b/src/node.rs @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use serde_derive::{Serialize,Deserialize}; +use serde::{Serialize,Deserialize}; use std::time::{Instant,Duration}; use std::collections::HashMap; use std::error::Error; diff --git a/src/state.rs b/src/state.rs index 8e71d9a..9cae04d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -32,7 +32,8 @@ use std::{fs,env}; use std::fmt::Display; use std::path::{Path,PathBuf}; use std::result::Result; -use serde_derive::{Serialize,Deserialize}; +use std::sync::{Arc,Mutex}; +use serde::{Serialize,Deserialize}; use figment::Figment; use figment::providers::{Format,Toml}; use crate::constants::HORIZONTAL; @@ -81,10 +82,10 @@ impl State { pool: "localhost:3333".to_string(), address: "".to_string(), }, - version: Version { - p2pool: P2POOL_VERSION.to_string(), - xmrig: XMRIG_VERSION.to_string(), - }, + version: Arc::new(Mutex::new(Version { + p2pool: Arc::new(Mutex::new(P2POOL_VERSION.to_string())), + xmrig: Arc::new(Mutex::new(XMRIG_VERSION.to_string())), + })), } } @@ -267,13 +268,13 @@ const DIRECTORY: &'static str = "gupax"; #[cfg(target_os = "windows")] pub const DEFAULT_P2POOL_PATH: &'static str = r"P2Pool\p2pool.exe"; #[cfg(target_os = "macos")] -pub const DEFAULT_P2POOL_PATH: &'static str = "P2Pool/p2pool"; +pub const DEFAULT_P2POOL_PATH: &'static str = "P2Pool/P2Pool"; #[cfg(target_os = "linux")] pub const DEFAULT_P2POOL_PATH: &'static str = "p2pool/p2pool"; #[cfg(target_os = "windows")] pub const DEFAULT_XMRIG_PATH: &'static str = r"XMRig\xmrig.exe"; #[cfg(target_os = "macos")] -pub const DEFAULT_XMRIG_PATH: &'static str = "XMRig/xmrig"; +pub const DEFAULT_XMRIG_PATH: &'static str = "XMRig/XMRig"; #[cfg(target_os = "linux")] pub const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig"; @@ -288,12 +289,12 @@ pub enum TomlError { } //---------------------------------------------------------------------------------------------------- Structs -#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] +#[derive(Clone,Debug,Deserialize,Serialize)] pub struct State { pub gupax: Gupax, pub p2pool: P2pool, pub xmrig: Xmrig, - pub version: Version, + pub version: Arc>, } #[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] @@ -341,8 +342,8 @@ pub struct Xmrig { // pub args: String, } -#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] +#[derive(Clone,Debug,Deserialize,Serialize)] pub struct Version { - pub p2pool: String, - pub xmrig: String, + pub p2pool: Arc>, + pub xmrig: Arc>, } diff --git a/src/update.rs b/src/update.rs index e16c144..8a0f085 100644 --- a/src/update.rs +++ b/src/update.rs @@ -38,7 +38,7 @@ use hyper_tls::HttpsConnector; use log::*; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use serde_derive::{Serialize,Deserialize}; +use serde::{Serialize,Deserialize}; use std::io::{Read,Write}; use std::path::PathBuf; use std::sync::{Arc,Mutex}; @@ -47,7 +47,11 @@ use tls_api::{TlsConnector, TlsConnectorBuilder}; use tokio::io::{AsyncReadExt,AsyncWriteExt}; use tokio::task::JoinHandle; use walkdir::WalkDir; + +#[cfg(target_os = "windows")] use zip::ZipArchive; +#[cfg(target_family = "unix")] +use std::os::unix::fs::OpenOptionsExt; //---------------------------------------------------------------------------------------------------- Constants // Package naming schemes: @@ -81,26 +85,47 @@ const P2POOL_HASH: &'static str = "sha256sums.txt.asc"; const XMRIG_HASH: &'static str = "SHA256SUMS"; #[cfg(target_os = "windows")] -const GUPAX_EXTENSION: &'static str = "-windows-standalone-x64.exe"; +const GUPAX_EXTENSION: &'static str = "-windows-x64-standalone.exe"; #[cfg(target_os = "windows")] const P2POOL_EXTENSION: &'static str = "-windows-x64.zip"; #[cfg(target_os = "windows")] const XMRIG_EXTENSION: &'static str = "-msvc-win64.zip"; #[cfg(target_os = "macos")] -const GUPAX_EXTENSION: &'static str = "-macos-standalone-x64"; +const GUPAX_EXTENSION: &'static str = "-macos-x64-standalone"; #[cfg(target_os = "macos")] const P2POOL_EXTENSION: &'static str = "-macos-x64.tar.gz"; #[cfg(target_os = "macos")] const XMRIG_EXTENSION: &'static str = "-macos-x64.tar.gz"; #[cfg(target_os = "linux")] -const GUPAX_EXTENSION: &'static str = "-linux-standalone-x64"; +const GUPAX_EXTENSION: &'static str = "-linux-x64-standalone"; #[cfg(target_os = "linux")] const P2POOL_EXTENSION: &'static str = "-linux-x64.tar.gz"; #[cfg(target_os = "linux")] const XMRIG_EXTENSION: &'static str = "-linux-static-x64.tar.gz"; +#[cfg(target_os = "windows")] +const GUPAX_BINARY: &'static str = "gupax.exe"; +#[cfg(target_os = "macos")] +const GUPAX_BINARY: &'static str = "Gupax"; +#[cfg(target_os = "linux")] +const GUPAX_BINARY: &'static str = "gupax"; + +#[cfg(target_os = "windows")] +const P2POOL_BINARY: &'static str = "p2pool.exe"; +#[cfg(target_os = "macos")] +const P2POOL_BINARY: &'static str = "P2Pool"; +#[cfg(target_os = "linux")] +const P2POOL_BINARY: &'static str = "p2pool"; + +#[cfg(target_os = "windows")] +const XMRIG_BINARY: &'static str = "xmrig.exe"; +#[cfg(target_os = "macos")] +const XMRIG_BINARY: &'static str = "XMRig"; +#[cfg(target_os = "linux")] +const XMRIG_BINARY: &'static str = "xmrig"; + // Some fake Curl/Wget user-agents because GitHub API requires one and a Tor browser // user-agent might be fingerprintable without all the associated headers. const FAKE_USER_AGENT: [&'static str; 50] = [ @@ -226,7 +251,7 @@ impl Update { // Get a temporary random folder for package download contents // This used to use [std::env::temp_dir()] but there were issues // using [std::fs::rename()] on tmpfs -> disk (Invalid cross-device link (os error 18)). - // So, uses the [Gupax] binary directory as a base, something like [/home/hinto/gupax/gupax_SG4xsDdVmr] + // So, uses the [Gupax] binary directory as a base, something like [/home/hinto/gupax/gupax_tmp_SG4xsDdVmr] pub fn get_tmp_dir() -> Result { let rand_string: String = thread_rng() .sample_iter(&Alphanumeric) @@ -235,9 +260,9 @@ impl Update { .collect(); let base = crate::get_exe_dir()?; #[cfg(target_os = "windows")] - let tmp_dir = format!("{}{}{}{}", base, r"\gupax_", rand_string, r"\"); + let tmp_dir = format!("{}{}{}{}", base, r"\gupax_tmp_", rand_string, r"\"); #[cfg(target_family = "unix")] - let tmp_dir = format!("{}{}{}{}", base, "/gupax_", rand_string, "/"); + let tmp_dir = format!("{}{}{}{}", base, "/gupax_tmp_", rand_string, "/"); info!("Update | Temporary directory ... {}", tmp_dir); Ok(tmp_dir) } @@ -281,7 +306,7 @@ impl Update { // 5. extract, upgrade #[tokio::main] - pub async fn start(update: Arc>, mut version: Version) -> Result<(), anyhow::Error> { + pub async fn start(update: Arc>, og_ver: Arc>, state_ver: Arc>) -> Result<(), anyhow::Error> { //---------------------------------------------------------------------------------------------------- Init *update.lock().unwrap().updating.lock().unwrap() = true; // Set timer @@ -408,28 +433,30 @@ impl Update { match pkg.name { Gupax => { if new_ver == GUPAX_VERSION { - info!("Update | {} {} == {} ... SKIPPING", pkg.name, pkg.new_ver.lock().unwrap(), GUPAX_VERSION); + info!("Update | {} {} == {} ... SKIPPING", pkg.name, GUPAX_VERSION, new_ver); } else { - info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), GUPAX_VERSION); - new_pkgs.push(format!("\nGupax {} ➡ {}", GUPAX_VERSION, pkg.new_ver.lock().unwrap())); + info!("Update | {} {} != {} ... ADDING", pkg.name, GUPAX_VERSION, new_ver); + new_pkgs.push(format!("\nGupax {} ➡ {}", GUPAX_VERSION, new_ver)); vec3.push(pkg); } } P2pool => { - if new_ver == version.p2pool { - info!("Update | {} {} == {} ... SKIPPING", pkg.name, pkg.new_ver.lock().unwrap(), version.p2pool); + let old_ver = og_ver.lock().unwrap().p2pool.lock().unwrap().to_owned(); + if old_ver == new_ver { + info!("Update | {} {} == {} ... SKIPPING", pkg.name, old_ver, new_ver); } else { - info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), version.p2pool); - new_pkgs.push(format!("\nP2Pool {} ➡ {}", version.p2pool, pkg.new_ver.lock().unwrap())); + info!("Update | {} {} != {} ... ADDING", pkg.name, old_ver, new_ver); + new_pkgs.push(format!("\nP2Pool {} ➡ {}", old_ver, new_ver)); vec3.push(pkg); } } Xmrig => { - if new_ver == GUPAX_VERSION { - info!("Update | {} {} == {} ... SKIPPING", pkg.name, pkg.new_ver.lock().unwrap(), version.xmrig); + let old_ver = og_ver.lock().unwrap().xmrig.lock().unwrap().to_owned(); + if old_ver == new_ver { + info!("Update | {} {} == {} ... SKIPPING", pkg.name, old_ver, new_ver); } else { - info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), version.xmrig); - new_pkgs.push(format!("\nXMRig {} ➡ {}", version.xmrig, pkg.new_ver.lock().unwrap())); + info!("Update | {} {} != {} ... ADDING", pkg.name, old_ver, new_ver); + new_pkgs.push(format!("\nXMRig {} ➡ {}", old_ver, new_ver)); vec3.push(pkg); } } @@ -522,14 +549,13 @@ impl Update { *update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_EXTRACT, new_pkgs); info!("Update | {}", EXTRACT); for pkg in vec4.iter() { - let tmp = tmp_dir.to_owned() + &pkg.name.to_string(); if pkg.name == Name::Gupax { + let tmp = tmp_dir.to_owned() + GUPAX_BINARY; #[cfg(target_family = "unix")] - use std::os::unix::fs::OpenOptionsExt; - #[cfg(target_family = "unix")] - std::fs::OpenOptions::new().create(true).write(true).mode(0o770).open(&tmp)?; + std::fs::OpenOptions::new().create(true).write(true).mode(0o750).open(&tmp)?; std::fs::write(tmp, pkg.bytes.lock().unwrap().as_ref())?; } else { + let tmp = tmp_dir.to_owned() + &pkg.name.to_string(); #[cfg(target_os = "windows")] ZipArchive::extract(&mut ZipArchive::new(std::io::Cursor::new(pkg.bytes.lock().unwrap().as_ref()))?, tmp)?; #[cfg(target_family = "unix")] @@ -553,46 +579,55 @@ impl Update { let entry = entry?.clone(); let basename = entry.file_name().to_str().ok_or(anyhow::Error::msg("WalkDir basename failed"))?; match basename { - "Gupax" => { + GUPAX_BINARY => { let path = update.lock().unwrap().path_gupax.clone(); info!("Update | Moving [{}] -> [{}]", entry.path().display(), path); + // Unix can replace running binaries no problem (they're loading into memory) + // Windows locks binaries in place, so we must move (rename) current binary + // into the temp folder, then move the new binary into the old ones spot. + // Clearing the temp folder is now moved at startup instead at the end + // of this function due to this behavior, thanks Windows. + #[cfg(target_os = "windows")] + std::fs::rename(&path, tmp_dir.clone() + "gupax_old.exe")?; std::fs::rename(entry.path(), path)?; *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round(); }, - "p2pool" => { + P2POOL_BINARY => { let path = update.lock().unwrap().path_p2pool.clone(); let path = std::path::Path::new(&path); info!("Update | Moving [{}] -> [{}]", entry.path().display(), path.display()); std::fs::create_dir_all(path.parent().ok_or(anyhow::Error::msg("P2Pool path failed"))?)?; std::fs::rename(entry.path(), path)?; - version.p2pool = Pkg::get_new_pkg_version(P2pool, &vec4)?; + *og_ver.lock().unwrap().p2pool.lock().unwrap() = Pkg::get_new_pkg_version(P2pool, &vec4)?; *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round(); }, - "xmrig" => { + XMRIG_BINARY => { let path = update.lock().unwrap().path_xmrig.clone(); let path = std::path::Path::new(&path); info!("Update | Moving [{}] -> [{}]", entry.path().display(), path.display()); std::fs::create_dir_all(path.parent().ok_or(anyhow::Error::msg("XMRig path failed"))?)?; std::fs::rename(entry.path(), path)?; - version.xmrig = Pkg::get_new_pkg_version(Xmrig, &vec4)?; + *og_ver.lock().unwrap().xmrig.lock().unwrap() = Pkg::get_new_pkg_version(Xmrig, &vec4)?; *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round(); }, _ => (), } } + // Remove tmp dir (on Unix) + #[cfg(target_family = "unix")] info!("Update | Removing temporary directory ... {}", tmp_dir); + #[cfg(target_family = "unix")] std::fs::remove_dir_all(&tmp_dir)?; + let seconds = now.elapsed().as_secs(); - info!("Update ... Seconds elapsed: [{}s]", seconds); - info!("Update ... OK ... 100%"); + info!("Update | Seconds elapsed ... [{}s]", seconds); match seconds { 0 => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took 0 seconds... Do you have 10Gbit internet or something...?!{}", MSG_SUCCESS, new_pkgs), 1 => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took 1 second... Wow!{}", MSG_SUCCESS, new_pkgs), _ => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took {} seconds.{}", MSG_SUCCESS, seconds, new_pkgs), } *update.lock().unwrap().prog.lock().unwrap() = 100.0; - *update.lock().unwrap().updating.lock().unwrap() = false; Ok(()) } }