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]]
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",

View file

@ -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"]

View file

@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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<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
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)));
});
});

View file

@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// 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<Mutex<State>>, // 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<Mutex<Update>>, // 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<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.
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);

View file

@ -15,7 +15,7 @@
// You should have received a copy of the GNU General Public License
// 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::collections::HashMap;
use std::error::Error;

View file

@ -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<Mutex<Version>>,
}
#[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<Mutex<String>>,
pub xmrig: Arc<Mutex<String>>,
}

View file

@ -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<String, anyhow::Error> {
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<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
*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(())
}
}