update: save [Version] to state, use runtime [og: State]

[og: State] is now completely wrapped in an [Arc<Mutex>] 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<Mutex>] 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.
This commit is contained in:
hinto-janaiyo 2022-11-02 13:58:44 -04:00
parent 20f5e2d917
commit b64e1e3a46
No known key found for this signature in database
GPG key ID: D7483F6CA27D1B1D
7 changed files with 187 additions and 100 deletions

15
Cargo.lock generated
View file

@ -1158,8 +1158,6 @@ dependencies = [
[[package]] [[package]]
name = "eframe" name = "eframe"
version = "0.19.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d49426c3e72a6728b0c790d22db8bf7bbcff10d83b8b6f3a01295be982302e"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"egui", "egui",
@ -1180,8 +1178,6 @@ dependencies = [
[[package]] [[package]]
name = "egui" name = "egui"
version = "0.19.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4"
dependencies = [ dependencies = [
"ahash 0.8.0", "ahash 0.8.0",
"epaint", "epaint",
@ -1192,8 +1188,6 @@ dependencies = [
[[package]] [[package]]
name = "egui-winit" name = "egui-winit"
version = "0.19.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07ddc525334c416e11580123e147b970f738507f427c9fb1cd09ea2dd7416a3a"
dependencies = [ dependencies = [
"arboard", "arboard",
"egui", "egui",
@ -1207,8 +1201,6 @@ dependencies = [
[[package]] [[package]]
name = "egui_extras" name = "egui_extras"
version = "0.19.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f698f685bb0ad39e87109e2f695ded0bccde77d5d40bbf7590cb5561c1e3039d"
dependencies = [ dependencies = [
"egui", "egui",
"image", "image",
@ -1217,8 +1209,6 @@ dependencies = [
[[package]] [[package]]
name = "egui_glow" name = "egui_glow"
version = "0.19.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad77d4a00402bae9658ee64be148f4b2a0b38e4fc7874970575ca01ed1c5b75d"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"egui", "egui",
@ -1238,8 +1228,6 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]] [[package]]
name = "emath" name = "emath"
version = "0.19.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9542a40106fdba943a055f418d1746a050e1a903a049b030c2b097d4686a33cf"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
] ]
@ -1296,8 +1284,6 @@ dependencies = [
[[package]] [[package]]
name = "epaint" name = "epaint"
version = "0.19.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300"
dependencies = [ dependencies = [
"ab_glyph", "ab_glyph",
"ahash 0.8.0", "ahash 0.8.0",
@ -1809,7 +1795,6 @@ dependencies = [
"reqwest", "reqwest",
"rusqlite", "rusqlite",
"serde", "serde",
"serde_derive",
"serde_json", "serde_json",
"sha2 0.10.6", "sha2 0.10.6",
"tar", "tar",

View file

@ -10,17 +10,17 @@ arti-hyper = "0.7.0"
bytes = "1.2.1" bytes = "1.2.1"
chrono = "0.4.22" chrono = "0.4.22"
dirs = "4.0.0" dirs = "4.0.0"
eframe = "0.19.0" #eframe = "0.19.0"
egui = "0.19.0" #egui = "0.19.0"
egui_extras = { version = "0.19.0", features = ["image"] } #egui_extras = { version = "0.19.0", features = ["image"] }
## [external/egui/crates/eframe/src/native/run.rs] line 41: [.with_srgb(true)] ## [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. ## 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, ## There are many issue threads and PRs to fix it but for now,
## this is here for convenience sake when I'm testing. ## this is here for convenience sake when I'm testing.
## The only change is [.with_srgb()] is set to [false]. ## The only change is [.with_srgb()] is set to [false].
#eframe = { path = "external/egui/crates/eframe" } eframe = { path = "external/egui/crates/eframe" }
#egui = { path = "external/egui/crates/egui" } egui = { path = "external/egui/crates/egui" }
#egui_extras = { path = "external/egui/crates/egui_extras", features = ["image"] } egui_extras = { path = "external/egui/crates/egui_extras", features = ["image"] }
env_logger = "0.9.1" env_logger = "0.9.1"
figment = { version = "0.10.8", features = ["toml"] } figment = { version = "0.10.8", features = ["toml"] }
flate2 = "1.0" flate2 = "1.0"
@ -37,24 +37,29 @@ rand = "0.8.5"
regex = "1.6.0" regex = "1.6.0"
reqwest = { version = "0.11.12", features = ["blocking", "json"] } reqwest = { version = "0.11.12", features = ["blocking", "json"] }
rusqlite = { version = "0.28.0", features = ["bundled"] } rusqlite = { version = "0.28.0", features = ["bundled"] }
serde = "1.0.145" serde = { version = "1.0.145", features = ["rc", "derive"] }
serde_derive = "1.0.145"
serde_json = "1.0" serde_json = "1.0"
sha2 = "0.10.6" sha2 = "0.10.6"
tar = "0.4.38"
tls-api = "0.9.0" tls-api = "0.9.0"
tls-api-native-tls = "0.9.0" tls-api-native-tls = "0.9.0"
tokio = { version = "1.21.2", features = ["full"] } tokio = { version = "1.21.2", features = ["full"] }
toml = "0.5.9" toml = "0.5.9"
tor-rtcompat = "0.7.0" tor-rtcompat = "0.7.0"
walkdir = "2.3.2" walkdir = "2.3.2"
# Unix dependencies
[target.'cfg(unix)'.dependencies]
tar = "0.4.38"
# Windows dependencies
[target.'cfg(windows)'.dependencies]
zip = "0.6.3" zip = "0.6.3"
# For Windows # For Windows build (icon)
[target.'cfg(windows)'.build-dependencies] [target.'cfg(windows)'.build-dependencies]
winres = "0.1.12" winres = "0.1.12"
# For macOS (cargo-bundle) # For macOS build (cargo-bundle)
[package.metadata.bundle] [package.metadata.bundle]
identifier = "io.github.hinto-janaiyo.gupax" identifier = "io.github.hinto-janaiyo.gupax"
icon = ["images/png/icon@2x.png"] icon = ["images/png/icon@2x.png"]

View file

@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::path::Path; use std::path::Path;
use crate::App; use crate::{App,State};
use egui::WidgetType::Button; use egui::WidgetType::Button;
use crate::constants::*; use crate::constants::*;
use crate::state::{Gupax,Version}; use crate::state::{Gupax,Version};
@ -26,7 +26,7 @@ use std::sync::{Arc,Mutex};
use log::*; use log::*;
impl Gupax { 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<Mutex<State>>, state_ver: &Arc<Mutex<Version>>, update: &Arc<Mutex<Update>>, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) {
// Update button + Progress bar // Update button + Progress bar
ui.group(|ui| { ui.group(|ui| {
// These are in unnecessary [ui.vertical()]'s // These are in unnecessary [ui.vertical()]'s
@ -35,30 +35,44 @@ impl Gupax {
// I have to pick one. This one seperates them though. // I have to pick one. This one seperates them though.
let height = height/6.0; let height = height/6.0;
let width = width - SPACE; let width = width - SPACE;
let updating = *update.updating.lock().unwrap(); let updating = *update.lock().unwrap().updating.lock().unwrap();
ui.vertical(|ui| { ui.vertical(|ui| {
ui.set_enabled(!updating); ui.set_enabled(!updating);
if ui.add_sized([width, height], egui::Button::new("Check for updates")).on_hover_text(GUPAX_UPDATE).clicked() { 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.lock().unwrap().path_p2pool = og.lock().unwrap().gupax.absolute_p2pool_path.display().to_string();
update.path_xmrig = og.absolute_xmrig_path.display().to_string(); update.lock().unwrap().path_xmrig = og.lock().unwrap().gupax.absolute_xmrig_path.display().to_string();
update.tor = og.update_via_tor; update.lock().unwrap().tor = og.lock().unwrap().gupax.update_via_tor;
let update = Arc::new(Mutex::new(update.clone())); 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|| { thread::spawn(move|| {
info!("Spawning update thread..."); info!("Spawning update thread...");
match Update::start(update.clone(), version) { match Update::start(update_thread, og_ver.clone(), state_ver.clone()) {
Err(e) => { Err(e) => {
info!("Update | {} ... FAIL", e); info!("Update ... {} ... FAIL", e);
*update.lock().unwrap().msg.lock().unwrap() = format!("{} | {}", MSG_FAILED, 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.vertical(|ui| {
ui.set_enabled(updating); 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)); ui.add_sized([width, height*1.4], egui::Label::new(msg));
let height = height/2.0; let height = height/2.0;
if updating { if updating {
@ -66,7 +80,7 @@ impl Gupax {
} else { } else {
ui.add_sized([width, height], egui::Label::new("...")); 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)));
}); });
}); });

View file

@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
// Hide console in Windows // Hide console in Windows
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] //#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
//---------------------------------------------------------------------------------------------------- Imports //---------------------------------------------------------------------------------------------------- Imports
// egui/eframe // egui/eframe
@ -70,10 +70,10 @@ pub struct App {
width: f32, // Top-level width width: f32, // Top-level width
height: f32, // Top-level height height: f32, // Top-level height
// State // State
og: State, // og = Old state to compare against og: Arc<Mutex<State>>, // og = Old state to compare against
state: State, // state = Working state (current settings) state: State, // state = Working state (current settings)
update: Update, // State for update data [update.rs] update: Arc<Mutex<Update>>, // State for update data [update.rs]
diff: bool, // Instead of comparing [og == state] every frame, this bool indicates changes diff: bool, // This bool indicates state changes
// Process/update state: // Process/update state:
// Doesn't make sense to save this on disk // Doesn't make sense to save this on disk
// so it's represented as a bool here. // so it's represented as a bool here.
@ -113,9 +113,9 @@ impl App {
width: 1280.0, width: 1280.0,
height: 720.0, height: 720.0,
node: Arc::new(Mutex::new(NodeStruct::default())), node: Arc::new(Mutex::new(NodeStruct::default())),
og: State::default(), og: Arc::new(Mutex::new(State::default())),
state: 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, diff: false,
p2pool: false, p2pool: false,
xmrig: false, xmrig: false,
@ -141,16 +141,23 @@ impl App {
// Read disk state if no [--reset] arg // Read disk state if no [--reset] arg
if app.reset == false { if app.reset == false {
app.og = match State::get() { app.og = match State::get() {
Ok(toml) => toml, Ok(toml) => Arc::new(Mutex::new(toml)),
Err(err) => { panic_main(err.to_string()); exit(1); }, Err(err) => { panic_main(err.to_string()); exit(1); },
}; };
} }
app.state = app.og.clone(); app.state = app.og.lock().unwrap().clone();
// Handle max threads // Handle max threads
app.og.xmrig.max_threads = num_cpus::get(); app.og.lock().unwrap().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; } 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] // 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 app
} }
} }
@ -304,6 +311,22 @@ pub fn get_rand_tmp(path: &String) -> String {
path 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 //---------------------------------------------------------------------------------------------------- [App] frame for [Panic] situations
fn panic_main(error: String) { fn panic_main(error: String) {
error!("{}", error); error!("{}", error);
@ -358,6 +381,10 @@ impl eframe::App for Panic {
fn main() { fn main() {
init_logger(); init_logger();
let options = init_options(); 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 app = App::new();
let name = app.name_version.clone(); let name = app.name_version.clone();
eframe::run_native(&name, options, Box::new(|cc| Box::new(App::cc(cc, app))),); 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 { impl eframe::App for App {
fn on_close_event(&mut self) -> bool { fn on_close_event(&mut self) -> bool {
self.quit = true; self.quit = true;
if self.og.gupax.ask_before_quit { if self.og.lock().unwrap().gupax.ask_before_quit {
self.quit_confirm self.quit_confirm
} else { } else {
true true
@ -390,6 +417,17 @@ impl eframe::App for App {
frame.set_fullscreen(!info.window_info.fullscreen); 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<Mutex>'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. // Close confirmation.
if self.quit { if self.quit {
// If [ask_before_quit == true] // 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.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
ui.group(|ui| { ui.group(|ui| {
if self.state == self.og { if self.diff == false {
ui.set_enabled(false) ui.set_enabled(false)
} }
let width = width / 2.0; 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("Save")).on_hover_text("Save changes").clicked() {
if ui.add_sized([width, height], egui::Button::new("Reset")).on_hover_text("Reset changes").clicked() { self.state = self.og.clone(); } 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; 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); Status::show(self, self.width, self.height, ctx, ui);
} }
Tab::Gupax => { 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 => { Tab::P2pool => {
P2pool::show(&mut self.state.p2pool, self.width, self.height, ctx, ui); P2pool::show(&mut self.state.p2pool, self.width, self.height, ctx, ui);

View file

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use serde_derive::{Serialize,Deserialize}; use serde::{Serialize,Deserialize};
use std::time::{Instant,Duration}; use std::time::{Instant,Duration};
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;

View file

@ -32,7 +32,8 @@ use std::{fs,env};
use std::fmt::Display; use std::fmt::Display;
use std::path::{Path,PathBuf}; use std::path::{Path,PathBuf};
use std::result::Result; use std::result::Result;
use serde_derive::{Serialize,Deserialize}; use std::sync::{Arc,Mutex};
use serde::{Serialize,Deserialize};
use figment::Figment; use figment::Figment;
use figment::providers::{Format,Toml}; use figment::providers::{Format,Toml};
use crate::constants::HORIZONTAL; use crate::constants::HORIZONTAL;
@ -81,10 +82,10 @@ impl State {
pool: "localhost:3333".to_string(), pool: "localhost:3333".to_string(),
address: "".to_string(), address: "".to_string(),
}, },
version: Version { version: Arc::new(Mutex::new(Version {
p2pool: P2POOL_VERSION.to_string(), p2pool: Arc::new(Mutex::new(P2POOL_VERSION.to_string())),
xmrig: XMRIG_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")] #[cfg(target_os = "windows")]
pub const DEFAULT_P2POOL_PATH: &'static str = r"P2Pool\p2pool.exe"; pub const DEFAULT_P2POOL_PATH: &'static str = r"P2Pool\p2pool.exe";
#[cfg(target_os = "macos")] #[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")] #[cfg(target_os = "linux")]
pub const DEFAULT_P2POOL_PATH: &'static str = "p2pool/p2pool"; pub const DEFAULT_P2POOL_PATH: &'static str = "p2pool/p2pool";
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub const DEFAULT_XMRIG_PATH: &'static str = r"XMRig\xmrig.exe"; pub const DEFAULT_XMRIG_PATH: &'static str = r"XMRig\xmrig.exe";
#[cfg(target_os = "macos")] #[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")] #[cfg(target_os = "linux")]
pub const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig"; pub const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig";
@ -288,12 +289,12 @@ pub enum TomlError {
} }
//---------------------------------------------------------------------------------------------------- Structs //---------------------------------------------------------------------------------------------------- Structs
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] #[derive(Clone,Debug,Deserialize,Serialize)]
pub struct State { pub struct State {
pub gupax: Gupax, pub gupax: Gupax,
pub p2pool: P2pool, pub p2pool: P2pool,
pub xmrig: Xmrig, pub xmrig: Xmrig,
pub version: Version, pub version: Arc<Mutex<Version>>,
} }
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] #[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
@ -341,8 +342,8 @@ pub struct Xmrig {
// pub args: String, // pub args: String,
} }
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)] #[derive(Clone,Debug,Deserialize,Serialize)]
pub struct Version { pub struct Version {
pub p2pool: String, pub p2pool: Arc<Mutex<String>>,
pub xmrig: String, pub xmrig: Arc<Mutex<String>>,
} }

View file

@ -38,7 +38,7 @@ use hyper_tls::HttpsConnector;
use log::*; use log::*;
use rand::distributions::Alphanumeric; use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use serde_derive::{Serialize,Deserialize}; use serde::{Serialize,Deserialize};
use std::io::{Read,Write}; use std::io::{Read,Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc,Mutex}; use std::sync::{Arc,Mutex};
@ -47,7 +47,11 @@ use tls_api::{TlsConnector, TlsConnectorBuilder};
use tokio::io::{AsyncReadExt,AsyncWriteExt}; use tokio::io::{AsyncReadExt,AsyncWriteExt};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use walkdir::WalkDir; use walkdir::WalkDir;
#[cfg(target_os = "windows")]
use zip::ZipArchive; use zip::ZipArchive;
#[cfg(target_family = "unix")]
use std::os::unix::fs::OpenOptionsExt;
//---------------------------------------------------------------------------------------------------- Constants //---------------------------------------------------------------------------------------------------- Constants
// Package naming schemes: // Package naming schemes:
@ -81,26 +85,47 @@ const P2POOL_HASH: &'static str = "sha256sums.txt.asc";
const XMRIG_HASH: &'static str = "SHA256SUMS"; const XMRIG_HASH: &'static str = "SHA256SUMS";
#[cfg(target_os = "windows")] #[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")] #[cfg(target_os = "windows")]
const P2POOL_EXTENSION: &'static str = "-windows-x64.zip"; const P2POOL_EXTENSION: &'static str = "-windows-x64.zip";
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
const XMRIG_EXTENSION: &'static str = "-msvc-win64.zip"; const XMRIG_EXTENSION: &'static str = "-msvc-win64.zip";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const GUPAX_EXTENSION: &'static str = "-macos-standalone-x64"; const GUPAX_EXTENSION: &'static str = "-macos-x64-standalone";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const P2POOL_EXTENSION: &'static str = "-macos-x64.tar.gz"; const P2POOL_EXTENSION: &'static str = "-macos-x64.tar.gz";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const XMRIG_EXTENSION: &'static str = "-macos-x64.tar.gz"; const XMRIG_EXTENSION: &'static str = "-macos-x64.tar.gz";
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
const GUPAX_EXTENSION: &'static str = "-linux-standalone-x64"; const GUPAX_EXTENSION: &'static str = "-linux-x64-standalone";
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
const P2POOL_EXTENSION: &'static str = "-linux-x64.tar.gz"; const P2POOL_EXTENSION: &'static str = "-linux-x64.tar.gz";
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
const XMRIG_EXTENSION: &'static str = "-linux-static-x64.tar.gz"; 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 // 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. // user-agent might be fingerprintable without all the associated headers.
const FAKE_USER_AGENT: [&'static str; 50] = [ const FAKE_USER_AGENT: [&'static str; 50] = [
@ -226,7 +251,7 @@ impl Update {
// Get a temporary random folder for package download contents // Get a temporary random folder for package download contents
// This used to use [std::env::temp_dir()] but there were issues // 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)). // 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<String, anyhow::Error> { pub fn get_tmp_dir() -> Result<String, anyhow::Error> {
let rand_string: String = thread_rng() let rand_string: String = thread_rng()
.sample_iter(&Alphanumeric) .sample_iter(&Alphanumeric)
@ -235,9 +260,9 @@ impl Update {
.collect(); .collect();
let base = crate::get_exe_dir()?; let base = crate::get_exe_dir()?;
#[cfg(target_os = "windows")] #[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")] #[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); info!("Update | Temporary directory ... {}", tmp_dir);
Ok(tmp_dir) Ok(tmp_dir)
} }
@ -281,7 +306,7 @@ impl Update {
// 5. extract, upgrade // 5. extract, upgrade
#[tokio::main] #[tokio::main]
pub async fn start(update: Arc<Mutex<Self>>, mut version: Version) -> Result<(), anyhow::Error> { pub async fn start(update: Arc<Mutex<Self>>, og_ver: Arc<Mutex<Version>>, state_ver: Arc<Mutex<Version>>) -> Result<(), anyhow::Error> {
//---------------------------------------------------------------------------------------------------- Init //---------------------------------------------------------------------------------------------------- Init
*update.lock().unwrap().updating.lock().unwrap() = true; *update.lock().unwrap().updating.lock().unwrap() = true;
// Set timer // Set timer
@ -408,28 +433,30 @@ impl Update {
match pkg.name { match pkg.name {
Gupax => { Gupax => {
if new_ver == GUPAX_VERSION { 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 { } else {
info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), GUPAX_VERSION); info!("Update | {} {} != {} ... ADDING", pkg.name, GUPAX_VERSION, new_ver);
new_pkgs.push(format!("\nGupax {}{}", GUPAX_VERSION, pkg.new_ver.lock().unwrap())); new_pkgs.push(format!("\nGupax {}{}", GUPAX_VERSION, new_ver));
vec3.push(pkg); vec3.push(pkg);
} }
} }
P2pool => { P2pool => {
if new_ver == version.p2pool { let old_ver = og_ver.lock().unwrap().p2pool.lock().unwrap().to_owned();
info!("Update | {} {} == {} ... SKIPPING", pkg.name, pkg.new_ver.lock().unwrap(), version.p2pool); if old_ver == new_ver {
info!("Update | {} {} == {} ... SKIPPING", pkg.name, old_ver, new_ver);
} else { } else {
info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), version.p2pool); info!("Update | {} {} != {} ... ADDING", pkg.name, old_ver, new_ver);
new_pkgs.push(format!("\nP2Pool {}{}", version.p2pool, pkg.new_ver.lock().unwrap())); new_pkgs.push(format!("\nP2Pool {}{}", old_ver, new_ver));
vec3.push(pkg); vec3.push(pkg);
} }
} }
Xmrig => { Xmrig => {
if new_ver == GUPAX_VERSION { let old_ver = og_ver.lock().unwrap().xmrig.lock().unwrap().to_owned();
info!("Update | {} {} == {} ... SKIPPING", pkg.name, pkg.new_ver.lock().unwrap(), version.xmrig); if old_ver == new_ver {
info!("Update | {} {} == {} ... SKIPPING", pkg.name, old_ver, new_ver);
} else { } else {
info!("Update | {} {} != {} ... ADDING", pkg.name, pkg.new_ver.lock().unwrap(), version.xmrig); info!("Update | {} {} != {} ... ADDING", pkg.name, old_ver, new_ver);
new_pkgs.push(format!("\nXMRig {}{}", version.xmrig, pkg.new_ver.lock().unwrap())); new_pkgs.push(format!("\nXMRig {}{}", old_ver, new_ver));
vec3.push(pkg); vec3.push(pkg);
} }
} }
@ -522,14 +549,13 @@ impl Update {
*update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_EXTRACT, new_pkgs); *update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_EXTRACT, new_pkgs);
info!("Update | {}", EXTRACT); info!("Update | {}", EXTRACT);
for pkg in vec4.iter() { for pkg in vec4.iter() {
let tmp = tmp_dir.to_owned() + &pkg.name.to_string();
if pkg.name == Name::Gupax { if pkg.name == Name::Gupax {
let tmp = tmp_dir.to_owned() + GUPAX_BINARY;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use std::os::unix::fs::OpenOptionsExt; std::fs::OpenOptions::new().create(true).write(true).mode(0o750).open(&tmp)?;
#[cfg(target_family = "unix")]
std::fs::OpenOptions::new().create(true).write(true).mode(0o770).open(&tmp)?;
std::fs::write(tmp, pkg.bytes.lock().unwrap().as_ref())?; std::fs::write(tmp, pkg.bytes.lock().unwrap().as_ref())?;
} else { } else {
let tmp = tmp_dir.to_owned() + &pkg.name.to_string();
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
ZipArchive::extract(&mut ZipArchive::new(std::io::Cursor::new(pkg.bytes.lock().unwrap().as_ref()))?, tmp)?; ZipArchive::extract(&mut ZipArchive::new(std::io::Cursor::new(pkg.bytes.lock().unwrap().as_ref()))?, tmp)?;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
@ -553,46 +579,55 @@ impl Update {
let entry = entry?.clone(); let entry = entry?.clone();
let basename = entry.file_name().to_str().ok_or(anyhow::Error::msg("WalkDir basename failed"))?; let basename = entry.file_name().to_str().ok_or(anyhow::Error::msg("WalkDir basename failed"))?;
match basename { match basename {
"Gupax" => { GUPAX_BINARY => {
let path = update.lock().unwrap().path_gupax.clone(); let path = update.lock().unwrap().path_gupax.clone();
info!("Update | Moving [{}] -> [{}]", entry.path().display(), path); 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)?; std::fs::rename(entry.path(), path)?;
*update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round(); *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round();
}, },
"p2pool" => { P2POOL_BINARY => {
let path = update.lock().unwrap().path_p2pool.clone(); let path = update.lock().unwrap().path_p2pool.clone();
let path = std::path::Path::new(&path); let path = std::path::Path::new(&path);
info!("Update | Moving [{}] -> [{}]", entry.path().display(), path.display()); 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::create_dir_all(path.parent().ok_or(anyhow::Error::msg("P2Pool path failed"))?)?;
std::fs::rename(entry.path(), path)?; 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(); *update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round();
}, },
"xmrig" => { XMRIG_BINARY => {
let path = update.lock().unwrap().path_xmrig.clone(); let path = update.lock().unwrap().path_xmrig.clone();
let path = std::path::Path::new(&path); let path = std::path::Path::new(&path);
info!("Update | Moving [{}] -> [{}]", entry.path().display(), path.display()); 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::create_dir_all(path.parent().ok_or(anyhow::Error::msg("XMRig path failed"))?)?;
std::fs::rename(entry.path(), path)?; 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(); *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); info!("Update | Removing temporary directory ... {}", tmp_dir);
#[cfg(target_family = "unix")]
std::fs::remove_dir_all(&tmp_dir)?; std::fs::remove_dir_all(&tmp_dir)?;
let seconds = now.elapsed().as_secs(); let seconds = now.elapsed().as_secs();
info!("Update ... Seconds elapsed: [{}s]", seconds); info!("Update | Seconds elapsed ... [{}s]", seconds);
info!("Update ... OK ... 100%");
match 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), 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), 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().msg.lock().unwrap() = format!("{}! Took {} seconds.{}", MSG_SUCCESS, seconds, new_pkgs),
} }
*update.lock().unwrap().prog.lock().unwrap() = 100.0; *update.lock().unwrap().prog.lock().unwrap() = 100.0;
*update.lock().unwrap().updating.lock().unwrap() = false;
Ok(()) Ok(())
} }
} }