From c3c7a28b098b4d2b031e6720b40e1784275ea045 Mon Sep 17 00:00:00 2001 From: Cyrix126 Date: Fri, 28 Jun 2024 11:12:33 +0200 Subject: [PATCH] feat: integrate xmrig-proxy feat: integrate xmrig-proxy into Gupaxx functions feat: re-organize xmrig modules feat: refactor some common code feat: update tests --- src/app/eframe_impl.rs | 10 + src/app/keys.rs | 6 +- src/app/mod.rs | 60 +- src/app/panels/bottom.rs | 162 +++++ src/app/panels/middle/gupax.rs | 800 +++++++++++---------- src/app/panels/middle/mod.rs | 16 +- src/app/panels/middle/status/benchmarks.rs | 2 +- src/app/panels/middle/status/mod.rs | 9 +- src/app/panels/middle/status/processes.rs | 508 +++++++------ src/app/panels/middle/xmrig.rs | 2 +- src/app/panels/middle/xmrig_proxy.rs | 420 +++++++++++ src/app/panels/top.rs | 20 +- src/components/gupax.rs | 20 +- src/components/update.rs | 53 +- src/disk/consts.rs | 10 + src/disk/state.rs | 68 ++ src/disk/tests.rs | 27 + src/helper/mod.rs | 283 +++++++- src/helper/p2pool.rs | 187 +---- src/helper/tests.rs | 4 +- src/helper/xrig/mod.rs | 63 ++ src/helper/{ => xrig}/xmrig.rs | 351 ++++----- src/helper/xrig/xmrig_proxy.rs | 593 +++++++++++++++ src/helper/xvb/algorithm.rs | 144 ++-- src/helper/xvb/mod.rs | 459 +++++++----- src/helper/xvb/nodes.rs | 15 +- src/helper/xvb/priv_stats.rs | 8 +- src/helper/xvb/public_stats.rs | 11 +- src/inits.rs | 18 + src/miscs.rs | 16 + src/utils/constants.rs | 42 +- src/utils/regex.rs | 22 +- 32 files changed, 3117 insertions(+), 1292 deletions(-) create mode 100644 src/app/panels/middle/xmrig_proxy.rs create mode 100644 src/helper/xrig/mod.rs rename src/helper/{ => xrig}/xmrig.rs (72%) create mode 100644 src/helper/xrig/xmrig_proxy.rs diff --git a/src/app/eframe_impl.rs b/src/app/eframe_impl.rs index f7d703d..3165b0f 100644 --- a/src/app/eframe_impl.rs +++ b/src/app/eframe_impl.rs @@ -36,6 +36,12 @@ impl eframe::App for App { let xmrig_is_waiting = xmrig.is_waiting(); let xmrig_state = xmrig.state; drop(xmrig); + debug!("App | Locking and collecting XMRig-Proxy state..."); + let xmrig_proxy = lock!(self.xmrig_proxy); + let xmrig_proxy_is_alive = xmrig_proxy.is_alive(); + let xmrig_proxy_is_waiting = xmrig_proxy.is_waiting(); + let xmrig_proxy_state = xmrig_proxy.state; + drop(xmrig_proxy); debug!("App | Locking and collecting XvB state..."); let xvb = lock!(self.xvb); let xvb_is_alive = xvb.is_alive(); @@ -84,14 +90,17 @@ impl eframe::App for App { ctx, p2pool_state, xmrig_state, + xmrig_proxy_state, xvb_state, &key, wants_input, p2pool_is_waiting, xmrig_is_waiting, + xmrig_proxy_is_waiting, xvb_is_waiting, p2pool_is_alive, xmrig_is_alive, + xmrig_proxy_is_alive, xvb_is_alive, ); // xvb_is_alive is not the same for bottom and for middle. @@ -104,6 +113,7 @@ impl eframe::App for App { key, p2pool_is_alive, xmrig_is_alive, + xmrig_proxy_is_alive, xvb_is_alive, ); } diff --git a/src/app/keys.rs b/src/app/keys.rs index bf8ee91..e679ee0 100644 --- a/src/app/keys.rs +++ b/src/app/keys.rs @@ -128,7 +128,8 @@ impl App { Tab::Gupax => self.tab = Tab::Status, Tab::P2pool => self.tab = Tab::Gupax, Tab::Xmrig => self.tab = Tab::P2pool, - Tab::Xvb => self.tab = Tab::Xmrig, + Tab::XmrigProxy => self.tab = Tab::Xmrig, + Tab::Xvb => self.tab = Tab::XmrigProxy, }; // Change Tabs RIGHT } else if key.is_x() && !wants_input { @@ -137,7 +138,8 @@ impl App { Tab::Status => self.tab = Tab::Gupax, Tab::Gupax => self.tab = Tab::P2pool, Tab::P2pool => self.tab = Tab::Xmrig, - Tab::Xmrig => self.tab = Tab::Xvb, + Tab::Xmrig => self.tab = Tab::XmrigProxy, + Tab::XmrigProxy => self.tab = Tab::Xvb, Tab::Xvb => self.tab = Tab::About, }; // Change Submenu LEFT diff --git a/src/app/mod.rs b/src/app/mod.rs index 5676366..8c28e45 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -18,8 +18,9 @@ use crate::errors::ErrorFerris; use crate::errors::ErrorState; use crate::helper::p2pool::ImgP2pool; use crate::helper::p2pool::PubP2poolApi; -use crate::helper::xmrig::ImgXmrig; -use crate::helper::xmrig::PubXmrigApi; +use crate::helper::xrig::xmrig::ImgXmrig; +use crate::helper::xrig::xmrig::PubXmrigApi; +use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi; use crate::helper::xvb::PubXvbApi; use crate::helper::Helper; use crate::helper::Process; @@ -104,15 +105,18 @@ pub struct App { pub pub_sys: Arc>, // [Sys] state, read by [Status], mutated by [Helper] pub p2pool: Arc>, // [P2Pool] process state pub xmrig: Arc>, // [XMRig] process state + pub xmrig_proxy: Arc>, // [XMRig-Proxy] process state pub xvb: Arc>, // [Xvb] process state pub p2pool_api: Arc>, // Public ready-to-print P2Pool API made by the "helper" thread pub xmrig_api: Arc>, // Public ready-to-print XMRig API made by the "helper" thread - pub xvb_api: Arc>, // Public XvB API - pub p2pool_img: Arc>, // A one-time snapshot of what data P2Pool started with - pub xmrig_img: Arc>, // A one-time snapshot of what data XMRig started with + pub xmrig_proxy_api: Arc>, // Public ready-to-print XMRigProxy API made by the "helper" thread + pub xvb_api: Arc>, // Public XvB API + pub p2pool_img: Arc>, // A one-time snapshot of what data P2Pool started with + pub xmrig_img: Arc>, // A one-time snapshot of what data XMRig started with // STDIN Buffer pub p2pool_stdin: String, // The buffer between the p2pool console and the [Helper] pub xmrig_stdin: String, // The buffer between the xmrig console and the [Helper] + pub xmrig_proxy_stdin: String, // The buffer between the xmrig-proxy console and the [Helper] // Sudo State pub sudo: Arc>, // This is just a dummy struct on [Windows]. // State from [--flags] @@ -184,6 +188,11 @@ impl App { String::new(), PathBuf::new() )); + let xmrig_proxy = arc_mut!(Process::new( + ProcessName::Xmrig, + String::new(), + PathBuf::new() + )); let xvb = arc_mut!(Process::new( ProcessName::Xvb, String::new(), @@ -191,6 +200,7 @@ impl App { )); let p2pool_api = arc_mut!(PubP2poolApi::new()); let xmrig_api = arc_mut!(PubXmrigApi::new()); + let xmrig_proxy_api = arc_mut!(PubXmrigProxyApi::new()); let xvb_api = arc_mut!(PubXvbApi::new()); let p2pool_img = arc_mut!(ImgP2pool::new()); let xmrig_img = arc_mut!(ImgXmrig::new()); @@ -232,7 +242,12 @@ impl App { must_resize: false, og: arc_mut!(State::new()), state: State::new(), - update: arc_mut!(Update::new(String::new(), PathBuf::new(), PathBuf::new(),)), + update: arc_mut!(Update::new( + String::new(), + PathBuf::new(), + PathBuf::new(), + PathBuf::new() + )), file_window: FileWindow::new(), og_node_vec: Node::new_vec(), node_vec: Node::new_vec(), @@ -246,24 +261,29 @@ impl App { pub_sys.clone(), p2pool.clone(), xmrig.clone(), + xmrig_proxy.clone(), xvb.clone(), p2pool_api.clone(), xmrig_api.clone(), xvb_api.clone(), + xmrig_proxy_api.clone(), p2pool_img.clone(), xmrig_img.clone(), arc_mut!(GupaxP2poolApi::new()) )), p2pool, xmrig, + xmrig_proxy, xvb, p2pool_api, xvb_api, xmrig_api, + xmrig_proxy_api, p2pool_img, xmrig_img, p2pool_stdin: String::with_capacity(10), xmrig_stdin: String::with_capacity(10), + xmrig_proxy_stdin: String::with_capacity(10), sudo: arc_mut!(SudoState::new()), resizing: false, alpha: 0, @@ -508,13 +528,38 @@ impl App { app.state.xmrig.selected_name = name; app.state.xmrig.selected_ip = pool.ip; app.state.xmrig.selected_port = pool.port; + if og.xmrig_proxy.selected_index > app.og_pool_vec.len() { + warn!( + "App | Overflowing manual pool index [{} > {}], resetting to 1", + og.xmrig_proxy.selected_index, + app.og_pool_vec.len() + ); + let (name, pool) = match app.og_pool_vec.first() { + Some(zero) => zero.clone(), + None => Pool::new_tuple(), + }; + og.xmrig_proxy.selected_index = 0; + og.xmrig_proxy.selected_name.clone_from(&name); + og.xmrig_proxy.selected_ip.clone_from(&pool.ip); + og.xmrig_proxy.selected_port.clone_from(&pool.port); + app.state.xmrig_proxy.selected_index = 0; + app.state.xmrig_proxy.selected_name = name; + app.state.xmrig_proxy.selected_ip = pool.ip; + app.state.xmrig_proxy.selected_port = pool.port; + } } // Apply TOML values to [Update] info!("App Init | Applying TOML values to [Update]..."); let p2pool_path = og.gupax.absolute_p2pool_path.clone(); let xmrig_path = og.gupax.absolute_xmrig_path.clone(); - app.update = arc_mut!(Update::new(app.exe.clone(), p2pool_path, xmrig_path)); + let xmrig_proxy_path = og.gupax.absolute_xp_path.clone(); + app.update = arc_mut!(Update::new( + app.exe.clone(), + p2pool_path, + xmrig_path, + xmrig_proxy_path + )); // Set state version as compiled in version info!("App Init | Setting state Gupax version..."); @@ -640,6 +685,7 @@ pub enum Tab { Gupax, P2pool, Xmrig, + XmrigProxy, Xvb, } diff --git a/src/app/panels/bottom.rs b/src/app/panels/bottom.rs index 267ccce..288c108 100644 --- a/src/app/panels/bottom.rs +++ b/src/app/panels/bottom.rs @@ -23,14 +23,17 @@ impl crate::app::App { ctx: &egui::Context, p2pool_state: ProcessState, xmrig_state: ProcessState, + xmrig_proxy_state: ProcessState, xvb_state: ProcessState, key: &KeyPressed, wants_input: bool, p2pool_is_waiting: bool, xmrig_is_waiting: bool, + xmrig_proxy_is_waiting: bool, xvb_is_waiting: bool, p2pool_is_alive: bool, xmrig_is_alive: bool, + xmrig_proxy_is_alive: bool, xvb_is_alive: bool, ) { // Bottom: app info + state/process buttons @@ -74,6 +77,8 @@ impl crate::app::App { ui.separator(); status_xmrig(xmrig_state, ui, size); ui.separator(); + status_xp(xmrig_proxy_state, ui, size); + ui.separator(); status_xvb(xvb_state, ui, size); }); @@ -112,6 +117,17 @@ impl crate::app::App { wants_input, ); } + Tab::XmrigProxy => { + self.xmrig_proxy_submenu(ui, size); + self.xmrig_proxy_run_actions( + ui, + height, + xmrig_proxy_is_waiting, + xmrig_proxy_is_alive, + key, + wants_input, + ); + } Tab::Xvb => self.xvb_run_actions( ui, height, @@ -142,6 +158,7 @@ impl crate::app::App { self.state.gupax = og.gupax; self.state.p2pool = og.p2pool; self.state.xmrig = og.xmrig; + self.state.xmrig_proxy = og.xmrig_proxy; self.state.xvb = og.xvb; self.node_vec.clone_from(&self.og_node_vec); self.pool_vec.clone_from(&self.og_pool_vec); @@ -159,6 +176,7 @@ impl crate::app::App { og.gupax = self.state.gupax.clone(); og.p2pool = self.state.p2pool.clone(); og.xmrig = self.state.xmrig.clone(); + og.xmrig_proxy = self.state.xmrig_proxy.clone(); og.xvb = self.state.xvb.clone(); } Err(e) => { @@ -405,6 +423,33 @@ impl crate::app::App { } }); } + fn xmrig_proxy_submenu(&mut self, ui: &mut Ui, size: Vec2) { + ui.group(|ui| { + let width = size.x / 1.5; + let size = vec2(width, size.y); + if ui + .add_sized( + size, + SelectableLabel::new(!self.state.xmrig_proxy.simple, "Advanced"), + ) + .on_hover_text(XMRIG_PROXY_ADVANCED) + .clicked() + { + self.state.xmrig_proxy.simple = false; + } + ui.separator(); + if ui + .add_sized( + size, + SelectableLabel::new(self.state.xmrig_proxy.simple, "Simple"), + ) + .on_hover_text(XMRIG_PROXY_SIMPLE) + .clicked() + { + self.state.xmrig_proxy.simple = true; + } + }); + } fn xmrig_run_actions( &mut self, ui: &mut Ui, @@ -507,6 +552,94 @@ impl crate::app::App { } }); } + fn xmrig_proxy_run_actions( + &mut self, + ui: &mut Ui, + height: f32, + xmrig_proxy_is_waiting: bool, + xmrig_proxy_is_alive: bool, + key: &KeyPressed, + wants_input: bool, + ) { + ui.group(|ui| { + let width = (ui.available_width() / 3.0) - 5.0; + let size = vec2(width, height); + if xmrig_proxy_is_waiting { + ui.add_enabled_ui(false, |ui| { + ui.add_sized(size, Button::new("⟲")) + .on_disabled_hover_text(XMRIG_PROXY_MIDDLE); + ui.add_sized(size, Button::new("⏹")) + .on_disabled_hover_text(XMRIG_PROXY_MIDDLE); + ui.add_sized(size, Button::new("▶")) + .on_disabled_hover_text(XMRIG_PROXY_MIDDLE); + }); + } else if xmrig_proxy_is_alive { + if key.is_up() && !wants_input + || ui + .add_sized(size, Button::new("⟲")) + .on_hover_text("Restart XMRig-Proxy") + .clicked() + { + let _ = lock!(self.og).update_absolute_path(); + let _ = self.state.update_absolute_path(); + Helper::restart_xp( + &self.helper, + &self.state.xmrig_proxy, + &self.state.xmrig, + &self.state.gupax.absolute_xp_path, + ); + } + if key.is_down() && !wants_input + || ui + .add_sized(size, Button::new("⏹")) + .on_hover_text("Stop XMRig-Proxy") + .clicked() + { + Helper::stop_xp(&self.helper); + } + ui.add_enabled_ui(false, |ui| { + ui.add_sized(size, Button::new("▶")) + .on_disabled_hover_text("Start XMRig-Proxy"); + }); + } else { + ui.add_enabled_ui(false, |ui| { + ui.add_sized(size, Button::new("⟲")) + .on_disabled_hover_text("Restart XMRig-Proxy"); + ui.add_sized(size, Button::new("⏹")) + .on_disabled_hover_text("Stop XMRig-Proxy"); + }); + let mut text = String::new(); + let mut ui_enabled = true; + if !Gupax::path_is_file(&self.state.gupax.xmrig_proxy_path) { + ui_enabled = false; + text = format!("Error: {}", XMRIG_PROXY_PATH_NOT_FILE); + } else if !crate::components::update::check_xp_path( + &self.state.gupax.xmrig_proxy_path, + ) { + ui_enabled = false; + text = format!("Error: {}", XMRIG_PROXY_PATH_NOT_VALID); + } + ui.set_enabled(ui_enabled); + let color = if ui_enabled { GREEN } else { RED }; + if (ui_enabled && key.is_up() && !wants_input) + || ui + .add_sized(size, Button::new(RichText::new("▶").color(color))) + .on_hover_text("Start XMRig-Proxy") + .on_disabled_hover_text(text) + .clicked() + { + let _ = lock!(self.og).update_absolute_path(); + let _ = self.state.update_absolute_path(); + Helper::start_xp( + &self.helper, + &self.state.xmrig_proxy, + &self.state.xmrig, + &self.state.gupax.absolute_xp_path, + ); + } + } + }); + } fn xvb_run_actions( &mut self, ui: &mut Ui, @@ -540,6 +673,7 @@ impl crate::app::App { &self.state.xvb, &self.state.p2pool, &self.state.xmrig, + &self.state.xmrig_proxy, ); } if key.is_down() && !wants_input @@ -579,6 +713,7 @@ impl crate::app::App { &self.state.xvb, &self.state.p2pool, &self.state.xmrig, + &self.state.xmrig_proxy, ); } } @@ -639,6 +774,33 @@ fn status_xmrig(state: ProcessState, ui: &mut Ui, size: Vec2) { }; status(ui, color, hover_text, size, "XMRig ⏺"); } + +fn status_xp(state: ProcessState, ui: &mut Ui, size: Vec2) { + let color; + let hover_text = match state { + Alive => { + color = GREEN; + XMRIG_PROXY_ALIVE + } + Dead => { + color = GRAY; + XMRIG_PROXY_DEAD + } + Failed => { + color = RED; + XMRIG_PROXY_FAILED + } + NotMining | OfflineNodesAll => { + color = ORANGE; + XMRIG_PROXY_NOT_MINING + } + Middle | Waiting | Syncing | Retry => { + color = YELLOW; + XMRIG_PROXY_MIDDLE + } + }; + status(ui, color, hover_text, size, "Proxy ⏺"); +} fn status_xvb(state: ProcessState, ui: &mut Ui, size: Vec2) { let color; let hover_text = match state { diff --git a/src/app/panels/middle/gupax.rs b/src/app/panels/middle/gupax.rs index 761f313..0337ee2 100644 --- a/src/app/panels/middle/gupax.rs +++ b/src/app/panels/middle/gupax.rs @@ -27,395 +27,443 @@ impl Gupax { ) { // Update button + Progress bar debug!("Gupaxx Tab | Rendering [Update] button + progress bar"); - ui.group(|ui| { - let button = if self.simple { - size.y / 5.0 - } else { - size.y / 15.0 - }; - let height = if self.simple { - size.y / 5.0 - } else { - size.y / 10.0 - }; - let width = size.x - SPACE; - let updating = *lock2!(update, updating); - ui.vertical(|ui| { - // If [Gupax] is being built for a Linux distro, - // disable built-in updating completely. - #[cfg(feature = "distro")] - ui.set_enabled(false); - #[cfg(feature = "distro")] - ui.add_sized([width, button], Button::new("Updates are disabled")) - .on_disabled_hover_text(DISTRO_NO_UPDATE); - #[cfg(not(feature = "distro"))] - ui.set_enabled(!updating && *lock!(restart) == Restart::No); - #[cfg(not(feature = "distro"))] - if ui - .add_sized([width, button], Button::new("Check for updates")) - .on_hover_text(GUPAX_UPDATE) - .clicked() - { - Update::spawn_thread(og, self, state_path, update, error_state, restart); - } - }); - ui.vertical(|ui| { - ui.set_enabled(updating); - let prog = *lock2!(update, prog); - let msg = format!("{}\n{}{}", *lock2!(update, msg), prog, "%"); - ui.add_sized([width, height * 1.4], Label::new(RichText::new(msg))); - let height = height / 2.0; - let size = vec2(width, height); - if updating { - ui.add_sized(size, Spinner::new().size(height)); - } else { - ui.add_sized(size, Label::new("...")); - } - ui.add_sized(size, ProgressBar::new(lock2!(update, prog).round() / 100.0)); - }); - }); - - debug!("Gupaxx Tab | Rendering bool buttons"); - ui.horizontal(|ui| { + egui::ScrollArea::vertical().show(ui, |ui| { ui.group(|ui| { - let width = (size.x - SPACE * 15.0) / 7.0; - let height = if self.simple { - size.y / 10.0 + let button = if self.simple { + size.y / 5.0 } else { size.y / 15.0 }; - let size = vec2(width, height); - ui.style_mut().override_text_style = Some(egui::TextStyle::Small); - ui.add_sized(size, Checkbox::new(&mut self.auto_update, "Auto-Update")) - .on_hover_text(GUPAX_AUTO_UPDATE); - ui.separator(); - ui.add_sized(size, Checkbox::new(&mut self.bundled, "Bundle")) - .on_hover_text(GUPAX_BUNDLED_UPDATE); - ui.separator(); - ui.add_sized(size, Checkbox::new(&mut self.auto_p2pool, "Auto-P2Pool")) - .on_hover_text(GUPAX_AUTO_P2POOL); - ui.separator(); - ui.add_sized(size, Checkbox::new(&mut self.auto_xmrig, "Auto-XMRig")) - .on_hover_text(GUPAX_AUTO_XMRIG); - ui.separator(); - ui.add_sized(size, Checkbox::new(&mut self.auto_xvb, "Auto-XvB")) - .on_hover_text(GUPAX_AUTO_XVB); - ui.separator(); - ui.add_sized( - size, - Checkbox::new(&mut self.ask_before_quit, "Confirm quit"), - ) - .on_hover_text(GUPAX_ASK_BEFORE_QUIT); - ui.separator(); - ui.add_sized( - size, - Checkbox::new(&mut self.save_before_quit, "Save on quit"), - ) - .on_hover_text(GUPAX_SAVE_BEFORE_QUIT); - }); - }); - - if self.simple { - return; - } - - debug!("Gupaxx Tab | Rendering P2Pool/XMRig path selection"); - // P2Pool/XMRig binary path selection - let height = size.y / 28.0; - let text_edit = (ui.available_width() / 10.0) - SPACE; - ui.group(|ui| { - ui.add_sized( - [ui.available_width(), height / 2.0], - Label::new( - RichText::new("P2Pool/XMRig PATHs") - .underline() - .color(LIGHT_GRAY), - ), - ) - .on_hover_text("Gupaxx is online"); - ui.separator(); - ui.horizontal(|ui| { - if self.p2pool_path.is_empty() { - ui.add_sized( - [text_edit, height], - Label::new(RichText::new("P2Pool Binary Path ➖").color(LIGHT_GRAY)), - ) - .on_hover_text(P2POOL_PATH_EMPTY); - } else if !Self::path_is_file(&self.p2pool_path) { - ui.add_sized( - [text_edit, height], - Label::new(RichText::new("P2Pool Binary Path ❌").color(RED)), - ) - .on_hover_text(P2POOL_PATH_NOT_FILE); - } else if !crate::components::update::check_p2pool_path(&self.p2pool_path) { - ui.add_sized( - [text_edit, height], - Label::new(RichText::new("P2Pool Binary Path ❌").color(RED)), - ) - .on_hover_text(P2POOL_PATH_NOT_VALID); + let height = if self.simple { + size.y / 5.0 } else { - ui.add_sized( - [text_edit, height], - Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN)), - ) - .on_hover_text(P2POOL_PATH_OK); - } - ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; - ui.set_enabled(!lock!(file_window).thread); - if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() { - Self::spawn_file_window_thread(file_window, FileType::P2pool); - } - ui.add_sized( - [ui.available_width(), height], - TextEdit::singleline(&mut self.p2pool_path), - ) - .on_hover_text(GUPAX_PATH_P2POOL); - }); - ui.horizontal(|ui| { - if self.xmrig_path.is_empty() { - ui.add_sized( - [text_edit, height], - Label::new(RichText::new(" XMRig Binary Path ➖").color(LIGHT_GRAY)), - ) - .on_hover_text(XMRIG_PATH_EMPTY); - } else if !Self::path_is_file(&self.xmrig_path) { - ui.add_sized( - [text_edit, height], - Label::new(RichText::new(" XMRig Binary Path ❌").color(RED)), - ) - .on_hover_text(XMRIG_PATH_NOT_FILE); - } else if !crate::components::update::check_xmrig_path(&self.xmrig_path) { - ui.add_sized( - [text_edit, height], - Label::new(RichText::new(" XMRig Binary Path ❌").color(RED)), - ) - .on_hover_text(XMRIG_PATH_NOT_VALID); - } else { - ui.add_sized( - [text_edit, height], - Label::new(RichText::new(" XMRig Binary Path ✔").color(GREEN)), - ) - .on_hover_text(XMRIG_PATH_OK); - } - ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; - ui.set_enabled(!lock!(file_window).thread); - if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() { - Self::spawn_file_window_thread(file_window, FileType::Xmrig); - } - ui.add_sized( - [ui.available_width(), height], - TextEdit::singleline(&mut self.xmrig_path), - ) - .on_hover_text(GUPAX_PATH_XMRIG); - }); - }); - let mut guard = lock!(file_window); - if guard.picked_p2pool { - self.p2pool_path.clone_from(&guard.p2pool_path); - guard.picked_p2pool = false; - } - if guard.picked_xmrig { - self.xmrig_path.clone_from(&guard.xmrig_path); - guard.picked_xmrig = false; - } - drop(guard); - - let height = ui.available_height() / 6.0; - - // Saved [Tab] - debug!("Gupaxx Tab | Rendering [Tab] selector"); - ui.group(|ui| { - let width = (size.x / 6.0) - (SPACE * 1.93); - let size = vec2(width, height); - ui.add_sized( - [ui.available_width(), height / 2.0], - Label::new(RichText::new("Default Tab").underline().color(LIGHT_GRAY)), - ) - .on_hover_text(GUPAX_TAB); - ui.separator(); - ui.horizontal(|ui| { - if ui - .add_sized(size, SelectableLabel::new(self.tab == Tab::About, "About")) - .on_hover_text(GUPAX_TAB_ABOUT) - .clicked() - { - self.tab = Tab::About; - } - ui.separator(); - if ui - .add_sized( - size, - SelectableLabel::new(self.tab == Tab::Status, "Status"), - ) - .on_hover_text(GUPAX_TAB_STATUS) - .clicked() - { - self.tab = Tab::Status; - } - ui.separator(); - if ui - .add_sized(size, SelectableLabel::new(self.tab == Tab::Gupax, "Gupaxx")) - .on_hover_text(GUPAX_TAB_GUPAX) - .clicked() - { - self.tab = Tab::Gupax; - } - ui.separator(); - if ui - .add_sized( - size, - SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool"), - ) - .on_hover_text(GUPAX_TAB_P2POOL) - .clicked() - { - self.tab = Tab::P2pool; - } - ui.separator(); - if ui - .add_sized(size, SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")) - .on_hover_text(GUPAX_TAB_XMRIG) - .clicked() - { - self.tab = Tab::Xmrig; - } - if ui - .add_sized(size, SelectableLabel::new(self.tab == Tab::Xvb, "XvB")) - .on_hover_text(GUPAX_TAB_XVB) - .clicked() - { - self.tab = Tab::Xvb; - } - }) - }); - - // Gupax App resolution sliders - debug!("Gupaxx Tab | Rendering resolution sliders"); - ui.group(|ui| { - ui.add_sized( - [ui.available_width(), height / 2.0], - Label::new( - RichText::new("Width/Height Adjust") - .underline() - .color(LIGHT_GRAY), - ), - ) - .on_hover_text(GUPAX_ADJUST); - ui.separator(); - ui.vertical(|ui| { - let width = size.x / 10.0; - ui.spacing_mut().icon_width = width / 25.0; - ui.spacing_mut().slider_width = width * 7.6; - match self.ratio { - Ratio::None => (), - Ratio::Width => { - let width = self.selected_width as f64; - let height = (width / 1.333).round(); - self.selected_height = height as u16; + size.y / 10.0 + }; + let width = size.x - SPACE; + let updating = *lock2!(update, updating); + ui.vertical(|ui| { + // If [Gupax] is being built for a Linux distro, + // disable built-in updating completely. + #[cfg(feature = "distro")] + ui.set_enabled(false); + #[cfg(feature = "distro")] + ui.add_sized([width, button], Button::new("Updates are disabled")) + .on_disabled_hover_text(DISTRO_NO_UPDATE); + #[cfg(not(feature = "distro"))] + ui.set_enabled(!updating && *lock!(restart) == Restart::No); + #[cfg(not(feature = "distro"))] + if ui + .add_sized([width, button], Button::new("Check for updates")) + .on_hover_text(GUPAX_UPDATE) + .clicked() + { + Update::spawn_thread(og, self, state_path, update, error_state, restart); } - Ratio::Height => { - let height = self.selected_height as f64; - let width = (height * 1.333).round(); - self.selected_width = width as u16; + }); + ui.vertical(|ui| { + ui.set_enabled(updating); + let prog = *lock2!(update, prog); + let msg = format!("{}\n{}{}", *lock2!(update, msg), prog, "%"); + ui.add_sized([width, height * 1.4], Label::new(RichText::new(msg))); + let height = height / 2.0; + let size = vec2(width, height); + if updating { + ui.add_sized(size, Spinner::new().size(height)); + } else { + ui.add_sized(size, Label::new("...")); } - } - let height = height / 3.5; - let size = vec2(width, height); - ui.horizontal(|ui| { - ui.set_enabled(self.ratio != Ratio::Height); - ui.add_sized( - size, - Label::new(format!( - " Width [{}-{}]:", - APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16 - )), - ); - ui.add_sized( - size, - Slider::new( - &mut self.selected_width, - APP_MIN_WIDTH as u16..=APP_MAX_WIDTH as u16, - ), - ) - .on_hover_text(GUPAX_WIDTH); - }); - ui.horizontal(|ui| { - ui.set_enabled(self.ratio != Ratio::Width); - ui.add_sized( - size, - Label::new(format!( - "Height [{}-{}]:", - APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16 - )), - ); - ui.add_sized( - size, - Slider::new( - &mut self.selected_height, - APP_MIN_HEIGHT as u16..=APP_MAX_HEIGHT as u16, - ), - ) - .on_hover_text(GUPAX_HEIGHT); - }); - ui.horizontal(|ui| { - ui.add_sized( - size, - Label::new(format!("Scaling [{APP_MIN_SCALE}..{APP_MAX_SCALE}]:")), - ); - ui.add_sized( - size, - Slider::new(&mut self.selected_scale, APP_MIN_SCALE..=APP_MAX_SCALE) - .step_by(0.1), - ) - .on_hover_text(GUPAX_SCALE); + ui.add_sized(size, ProgressBar::new(lock2!(update, prog).round() / 100.0)); }); }); - ui.style_mut().override_text_style = Some(egui::TextStyle::Button); - ui.separator(); - // Width/Height locks + + debug!("Gupaxx Tab | Rendering bool buttons"); ui.horizontal(|ui| { - use Ratio::*; - let width = (size.x / 4.0) - (SPACE * 1.5); + ui.group(|ui| { + let width = (size.x - SPACE * 17.0) / 8.0; + let height = if self.simple { + size.y / 10.0 + } else { + size.y / 15.0 + }; + let size = vec2(width, height); + ui.style_mut().override_text_style = Some(egui::TextStyle::Small); + ui.add_sized(size, Checkbox::new(&mut self.auto_update, "Auto-Update")) + .on_hover_text(GUPAX_AUTO_UPDATE); + ui.separator(); + ui.add_sized(size, Checkbox::new(&mut self.bundled, "Bundle")) + .on_hover_text(GUPAX_BUNDLED_UPDATE); + ui.separator(); + ui.add_sized(size, Checkbox::new(&mut self.auto_p2pool, "Auto-P2Pool")) + .on_hover_text(GUPAX_AUTO_P2POOL); + ui.separator(); + ui.add_sized(size, Checkbox::new(&mut self.auto_xmrig, "Auto-XMRig")) + .on_hover_text(GUPAX_AUTO_XMRIG); + ui.separator(); + ui.add_sized( + size, + Checkbox::new(&mut self.auto_xmrig, "Auto-XMRig-Proxy"), + ) + .on_hover_text(GUPAX_AUTO_XMRIG_PROXY); + ui.separator(); + ui.add_sized(size, Checkbox::new(&mut self.auto_xvb, "Auto-XvB")) + .on_hover_text(GUPAX_AUTO_XVB); + ui.separator(); + ui.add_sized( + size, + Checkbox::new(&mut self.ask_before_quit, "Confirm quit"), + ) + .on_hover_text(GUPAX_ASK_BEFORE_QUIT); + ui.separator(); + ui.add_sized( + size, + Checkbox::new(&mut self.save_before_quit, "Save on quit"), + ) + .on_hover_text(GUPAX_SAVE_BEFORE_QUIT); + }); + }); + + if self.simple { + return; + } + + debug!("Gupaxx Tab | Rendering P2Pool/XMRig path selection"); + // P2Pool/XMRig binary path selection + let height = size.y / 28.0; + let text_edit = (ui.available_width() / 10.0) - SPACE; + ui.group(|ui| { + ui.add_sized( + [ui.available_width(), height / 2.0], + Label::new( + RichText::new("P2Pool/XMRig/XMRig-Proxy PATHs") + .underline() + .color(LIGHT_GRAY), + ), + ) + .on_hover_text("Gupaxx is online"); + ui.separator(); + ui.horizontal(|ui| { + if self.p2pool_path.is_empty() { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new("P2Pool Binary Path ➖").color(LIGHT_GRAY)), + ) + .on_hover_text(P2POOL_PATH_EMPTY); + } else if !Self::path_is_file(&self.p2pool_path) { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new("P2Pool Binary Path ❌").color(RED)), + ) + .on_hover_text(P2POOL_PATH_NOT_FILE); + } else if !crate::components::update::check_p2pool_path(&self.p2pool_path) { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new("P2Pool Binary Path ❌").color(RED)), + ) + .on_hover_text(P2POOL_PATH_NOT_VALID); + } else { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN)), + ) + .on_hover_text(P2POOL_PATH_OK); + } + ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; + ui.set_enabled(!lock!(file_window).thread); + if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() { + Self::spawn_file_window_thread(file_window, FileType::P2pool); + } + ui.add_sized( + [ui.available_width(), height], + TextEdit::singleline(&mut self.p2pool_path), + ) + .on_hover_text(GUPAX_PATH_P2POOL); + }); + ui.horizontal(|ui| { + if self.xmrig_path.is_empty() { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new(" XMRig Binary Path ➖").color(LIGHT_GRAY)), + ) + .on_hover_text(XMRIG_PATH_EMPTY); + } else if !Self::path_is_file(&self.xmrig_path) { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new(" XMRig Binary Path ❌").color(RED)), + ) + .on_hover_text(XMRIG_PATH_NOT_FILE); + } else if !crate::components::update::check_xmrig_path(&self.xmrig_path) { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new(" XMRig Binary Path ❌").color(RED)), + ) + .on_hover_text(XMRIG_PATH_NOT_VALID); + } else { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new(" XMRig Binary Path ✔").color(GREEN)), + ) + .on_hover_text(XMRIG_PATH_OK); + } + ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; + ui.set_enabled(!lock!(file_window).thread); + if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() { + Self::spawn_file_window_thread(file_window, FileType::Xmrig); + } + ui.add_sized( + [ui.available_width(), height], + TextEdit::singleline(&mut self.xmrig_path), + ) + .on_hover_text(GUPAX_PATH_XMRIG); + }); + ui.horizontal(|ui| { + if self.xmrig_proxy_path.is_empty() { + ui.add_sized( + [text_edit, height], + Label::new( + RichText::new(" XMRig-Proxy Binary Path ➖").color(LIGHT_GRAY), + ), + ) + .on_hover_text(XMRIG_PROXY_PATH_EMPTY); + } else if !Self::path_is_file(&self.xmrig_proxy_path) { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new(" XMRig-Proxy Binary Path ❌").color(RED)), + ) + .on_hover_text(XMRIG_PROXY_PATH_NOT_FILE); + } else if !crate::components::update::check_xp_path(&self.xmrig_proxy_path) { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new(" XMRig-Proxy Binary Path ❌").color(RED)), + ) + .on_hover_text(XMRIG_PROXY_PATH_NOT_VALID); + } else { + ui.add_sized( + [text_edit, height], + Label::new(RichText::new(" XMRig-Proxy Binary Path ✔").color(GREEN)), + ) + .on_hover_text(XMRIG_PROXY_PATH_OK); + } + ui.spacing_mut().text_edit_width = ui.available_width() - SPACE; + ui.set_enabled(!lock!(file_window).thread); + if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() { + Self::spawn_file_window_thread(file_window, FileType::XmrigProxy); + } + ui.add_sized( + [ui.available_width(), height], + TextEdit::singleline(&mut self.xmrig_proxy_path), + ) + .on_hover_text(GUPAX_PATH_XMRIG_PROXY); + }); + }); + let mut guard = lock!(file_window); + if guard.picked_p2pool { + self.p2pool_path.clone_from(&guard.p2pool_path); + guard.picked_p2pool = false; + } + if guard.picked_xmrig { + self.xmrig_path.clone_from(&guard.xmrig_path); + guard.picked_xmrig = false; + } + drop(guard); + + let height = ui.available_height() / 6.0; + + // Saved [Tab] + debug!("Gupaxx Tab | Rendering [Tab] selector"); + ui.group(|ui| { + let width = (size.x / 6.0) - (SPACE * 1.93); let size = vec2(width, height); - if ui - .add_sized( - size, - SelectableLabel::new(self.ratio == Width, "Lock to width"), - ) - .on_hover_text(GUPAX_LOCK_WIDTH) - .clicked() - { - self.ratio = Width; - } + ui.add_sized( + [ui.available_width(), height / 2.0], + Label::new(RichText::new("Default Tab").underline().color(LIGHT_GRAY)), + ) + .on_hover_text(GUPAX_TAB); ui.separator(); - if ui - .add_sized( - size, - SelectableLabel::new(self.ratio == Height, "Lock to height"), - ) - .on_hover_text(GUPAX_LOCK_HEIGHT) - .clicked() - { - self.ratio = Height; - } + ui.horizontal(|ui| { + if ui + .add_sized(size, SelectableLabel::new(self.tab == Tab::About, "About")) + .on_hover_text(GUPAX_TAB_ABOUT) + .clicked() + { + self.tab = Tab::About; + } + ui.separator(); + if ui + .add_sized( + size, + SelectableLabel::new(self.tab == Tab::Status, "Status"), + ) + .on_hover_text(GUPAX_TAB_STATUS) + .clicked() + { + self.tab = Tab::Status; + } + ui.separator(); + if ui + .add_sized(size, SelectableLabel::new(self.tab == Tab::Gupax, "Gupaxx")) + .on_hover_text(GUPAX_TAB_GUPAX) + .clicked() + { + self.tab = Tab::Gupax; + } + ui.separator(); + if ui + .add_sized( + size, + SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool"), + ) + .on_hover_text(GUPAX_TAB_P2POOL) + .clicked() + { + self.tab = Tab::P2pool; + } + ui.separator(); + if ui + .add_sized(size, SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")) + .on_hover_text(GUPAX_TAB_XMRIG) + .clicked() + { + self.tab = Tab::Xmrig; + } + if ui + .add_sized(size, SelectableLabel::new(self.tab == Tab::Xvb, "XvB")) + .on_hover_text(GUPAX_TAB_XVB) + .clicked() + { + self.tab = Tab::Xvb; + } + }) + }); + + // Gupax App resolution sliders + debug!("Gupaxx Tab | Rendering resolution sliders"); + ui.group(|ui| { + ui.add_sized( + [ui.available_width(), height / 2.0], + Label::new( + RichText::new("Width/Height Adjust") + .underline() + .color(LIGHT_GRAY), + ), + ) + .on_hover_text(GUPAX_ADJUST); ui.separator(); - if ui - .add_sized(size, SelectableLabel::new(self.ratio == None, "No lock")) - .on_hover_text(GUPAX_NO_LOCK) - .clicked() - { - self.ratio = None; - } - if ui - .add_sized(size, Button::new("Set")) - .on_hover_text(GUPAX_SET) - .clicked() - { - let size = Vec2::new(self.selected_width as f32, self.selected_height as f32); - ui.ctx() - .send_viewport_cmd(egui::viewport::ViewportCommand::InnerSize(size)); - } - }) + ui.vertical(|ui| { + let width = size.x / 10.0; + ui.spacing_mut().icon_width = width / 25.0; + ui.spacing_mut().slider_width = width * 7.6; + match self.ratio { + Ratio::None => (), + Ratio::Width => { + let width = self.selected_width as f64; + let height = (width / 1.333).round(); + self.selected_height = height as u16; + } + Ratio::Height => { + let height = self.selected_height as f64; + let width = (height * 1.333).round(); + self.selected_width = width as u16; + } + } + let height = height / 3.5; + let size = vec2(width, height); + ui.horizontal(|ui| { + ui.set_enabled(self.ratio != Ratio::Height); + ui.add_sized( + size, + Label::new(format!( + " Width [{}-{}]:", + APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16 + )), + ); + ui.add_sized( + size, + Slider::new( + &mut self.selected_width, + APP_MIN_WIDTH as u16..=APP_MAX_WIDTH as u16, + ), + ) + .on_hover_text(GUPAX_WIDTH); + }); + ui.horizontal(|ui| { + ui.set_enabled(self.ratio != Ratio::Width); + ui.add_sized( + size, + Label::new(format!( + "Height [{}-{}]:", + APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16 + )), + ); + ui.add_sized( + size, + Slider::new( + &mut self.selected_height, + APP_MIN_HEIGHT as u16..=APP_MAX_HEIGHT as u16, + ), + ) + .on_hover_text(GUPAX_HEIGHT); + }); + ui.horizontal(|ui| { + ui.add_sized( + size, + Label::new(format!("Scaling [{APP_MIN_SCALE}..{APP_MAX_SCALE}]:")), + ); + ui.add_sized( + size, + Slider::new(&mut self.selected_scale, APP_MIN_SCALE..=APP_MAX_SCALE) + .step_by(0.1), + ) + .on_hover_text(GUPAX_SCALE); + }); + }); + ui.style_mut().override_text_style = Some(egui::TextStyle::Button); + ui.separator(); + // Width/Height locks + ui.horizontal(|ui| { + use Ratio::*; + let width = (size.x / 4.0) - (SPACE * 1.5); + let size = vec2(width, height); + if ui + .add_sized( + size, + SelectableLabel::new(self.ratio == Width, "Lock to width"), + ) + .on_hover_text(GUPAX_LOCK_WIDTH) + .clicked() + { + self.ratio = Width; + } + ui.separator(); + if ui + .add_sized( + size, + SelectableLabel::new(self.ratio == Height, "Lock to height"), + ) + .on_hover_text(GUPAX_LOCK_HEIGHT) + .clicked() + { + self.ratio = Height; + } + ui.separator(); + if ui + .add_sized(size, SelectableLabel::new(self.ratio == None, "No lock")) + .on_hover_text(GUPAX_NO_LOCK) + .clicked() + { + self.ratio = None; + } + if ui + .add_sized(size, Button::new("Set")) + .on_hover_text(GUPAX_SET) + .clicked() + { + let size = + Vec2::new(self.selected_width as f32, self.selected_height as f32); + ui.ctx() + .send_viewport_cmd(egui::viewport::ViewportCommand::InnerSize(size)); + } + }) + }); }); } } diff --git a/src/app/panels/middle/mod.rs b/src/app/panels/middle/mod.rs index 42f8c2a..f5a5d19 100644 --- a/src/app/panels/middle/mod.rs +++ b/src/app/panels/middle/mod.rs @@ -11,8 +11,10 @@ mod gupax; mod p2pool; mod status; mod xmrig; +mod xmrig_proxy; mod xvb; impl crate::app::App { + #[allow(clippy::too_many_arguments)] pub fn middle_panel( &mut self, ctx: &egui::Context, @@ -20,6 +22,7 @@ impl crate::app::App { key: KeyPressed, p2pool_is_alive: bool, xmrig_is_alive: bool, + xmrig_proxy_is_alive: bool, xvb_is_alive: bool, ) { // Middle panel, contents of the [Tab] @@ -41,11 +44,13 @@ impl crate::app::App { let distro = false; let p2pool_gui_len = lock!(self.p2pool_api).output.len(); let xmrig_gui_len = lock!(self.xmrig_api).output.len(); + let xmrig_proxy_gui_len = lock!(self.xmrig_proxy_api).output.len(); let gupax_p2pool_api = lock!(self.gupax_p2pool_api); let debug_info = format!( "Gupax version: {}\n Bundled P2Pool version: {}\n Bundled XMRig version: {}\n +Bundled XMRig-Proxy version: {}\n Gupax uptime: {} seconds\n Selected resolution: {}x{}\n Internal resolution: {}x{}\n @@ -64,8 +69,10 @@ OS Data PATH: {}\n Gupax PATH: {}\n P2Pool PATH: {}\n XMRig PATH: {}\n +XMRig-Proxy PATH: {}\n P2Pool console byte length: {}\n XMRig console byte length: {}\n +XMRig-Proxy console byte length: {}\n ------------------------------------------ P2POOL IMAGE ------------------------------------------ {:#?}\n ------------------------------------------ XMRIG IMAGE ------------------------------------------ @@ -84,6 +91,7 @@ path_xmr: {:#?}\n GUPAX_VERSION, P2POOL_VERSION, XMRIG_VERSION, + XMRIG_PROXY_VERSION, self.now.elapsed().as_secs_f32(), self.state.gupax.selected_width, self.state.gupax.selected_height, @@ -104,8 +112,10 @@ path_xmr: {:#?}\n self.exe, self.state.gupax.absolute_p2pool_path.display(), self.state.gupax.absolute_xmrig_path.display(), + self.state.gupax.absolute_xp_path.display(), p2pool_gui_len, xmrig_gui_len, + xmrig_proxy_gui_len, lock!(self.p2pool_img), lock!(self.xmrig_img), gupax_p2pool_api.payout, @@ -146,7 +156,7 @@ path_xmr: {:#?}\n } Tab::Status => { debug!("App | Entering [Status] Tab"); - crate::disk::state::Status::show(&mut self.state.status, &self.pub_sys, &self.p2pool_api, &self.xmrig_api, &self.xvb_api,&self.p2pool_img, &self.xmrig_img, p2pool_is_alive, xmrig_is_alive, xvb_is_alive, self.max_threads, &self.gupax_p2pool_api, &self.benchmarks, self.size, ctx, ui); + crate::disk::state::Status::show(&mut self.state.status, &self.pub_sys, &self.p2pool_api, &self.xmrig_api,&self.xmrig_proxy_api, &self.xvb_api,&self.p2pool_img, &self.xmrig_img, p2pool_is_alive, xmrig_is_alive, xmrig_proxy_is_alive,xvb_is_alive, self.max_threads, &self.gupax_p2pool_api, &self.benchmarks, self.size, ctx, ui); } Tab::Gupax => { debug!("App | Entering [Gupax] Tab"); @@ -160,6 +170,10 @@ path_xmr: {:#?}\n debug!("App | Entering [XMRig] Tab"); crate::disk::state::Xmrig::show(&mut self.state.xmrig, &mut self.pool_vec, &self.xmrig, &self.xmrig_api, &mut self.xmrig_stdin, self.size, ctx, ui); } + Tab::XmrigProxy => { + debug!("App | Entering [XMRig-Proxy] Tab"); + crate::disk::state::XmrigProxy::show(&mut self.state.xmrig_proxy, &self.xmrig_proxy, &mut self.pool_vec, &self.xmrig_proxy_api, &mut self.xmrig_proxy_stdin, self.size, ui); + } Tab::Xvb => { debug!("App | Entering [XvB] Tab"); crate::disk::state::Xvb::show(&mut self.state.xvb, self.size, &self.state.p2pool.address, ctx, ui, &self.xvb_api, lock!(self.xvb).state == ProcessState::Alive); diff --git a/src/app/panels/middle/status/benchmarks.rs b/src/app/panels/middle/status/benchmarks.rs index 8c5c19d..f76e66b 100644 --- a/src/app/panels/middle/status/benchmarks.rs +++ b/src/app/panels/middle/status/benchmarks.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, Mutex}; -use crate::{app::Benchmark, disk::state::Status, helper::xmrig::PubXmrigApi}; +use crate::{app::Benchmark, disk::state::Status, helper::xrig::xmrig::PubXmrigApi}; use egui::{Hyperlink, ProgressBar, ScrollArea, Spinner, Vec2}; use egui_extras::{Column, TableBuilder}; use readable::num::{Float, Percent, Unsigned}; diff --git a/src/app/panels/middle/status/mod.rs b/src/app/panels/middle/status/mod.rs index 061ab07..a791cda 100644 --- a/src/app/panels/middle/status/mod.rs +++ b/src/app/panels/middle/status/mod.rs @@ -22,7 +22,10 @@ use crate::{ disk::{gupax_p2pool_api::GupaxP2poolApi, state::Status, status::*}, helper::{ p2pool::{ImgP2pool, PubP2poolApi}, - xmrig::{ImgXmrig, PubXmrigApi}, + xrig::{ + xmrig::{ImgXmrig, PubXmrigApi}, + xmrig_proxy::PubXmrigProxyApi, + }, xvb::PubXvbApi, Sys, }, @@ -41,11 +44,13 @@ impl Status { sys: &Arc>, p2pool_api: &Arc>, xmrig_api: &Arc>, + xmrig_proxy_api: &Arc>, xvb_api: &Arc>, p2pool_img: &Arc>, xmrig_img: &Arc>, p2pool_alive: bool, xmrig_alive: bool, + xmrig_proxy_alive: bool, xvb_alive: bool, max_threads: usize, gupax_p2pool_api: &Arc>, @@ -65,6 +70,8 @@ impl Status { p2pool_img, xmrig_alive, xmrig_api, + xmrig_proxy_alive, + xmrig_proxy_api, xmrig_img, xvb_alive, xvb_api, diff --git a/src/app/panels/middle/status/processes.rs b/src/app/panels/middle/status/processes.rs index 113594b..06039f2 100644 --- a/src/app/panels/middle/status/processes.rs +++ b/src/app/panels/middle/status/processes.rs @@ -4,7 +4,8 @@ use std::sync::{Arc, Mutex}; use crate::disk::state::Status; use crate::helper::p2pool::{ImgP2pool, PubP2poolApi}; -use crate::helper::xmrig::{ImgXmrig, PubXmrigApi}; +use crate::helper::xrig::xmrig::{ImgXmrig, PubXmrigApi}; +use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi; use crate::helper::xvb::{rounds::XvbRound, PubXvbApi}; use crate::helper::Sys; use crate::utils::macros::lock; @@ -25,50 +26,59 @@ impl Status { p2pool_img: &Arc>, xmrig_alive: bool, xmrig_api: &Arc>, + xmrig_proxy_alive: bool, + xmrig_proxy_api: &Arc>, xmrig_img: &Arc>, xvb_alive: bool, xvb_api: &Arc>, max_threads: usize, ) { - let width = (size.x / 4.0) - (SPACE * 1.7500); - let min_height = size.y - SPACE; + // set fixed text size, temporary solution before refactoring text/widget size. + let width = (size.x / 5.0) - (SPACE * 1.7500); let height = size.y / 25.0; + // height must be height - top - bottom - space * 2 - space of text + let size: Vec2 = [width, height].into(); + let min_height = size.y - SPACE; + // min width must allow to display text without wrapping. + let size_text = ui.text_style_height(&TextStyle::Body); + // ui.spacing_mut().item_spacing = Vec2::new(2.0, 2.0); + let min_width = size_text * 14.0; + let min_size: Vec2 = [min_width, min_height].into(); ui.horizontal(|ui| { - // [Gupax] - gupax(ui, min_height, width, height, sys); - // [P2Pool] - p2pool( - ui, - min_height, - width, - height, - p2pool_alive, - p2pool_api, - p2pool_img, - ); - // [XMRig] - xmrig( - ui, - min_height, - width, - height, - xmrig_alive, - xmrig_api, - xmrig_img, - max_threads, - ); - // [XvB] - xvb(ui, min_height, width, height, xvb_alive, xvb_api); + ScrollArea::horizontal().show(ui, |ui| { + ui.set_min_height(min_height * 34.2); + + // [Gupax] + gupax(ui, min_size, size, sys); + // [P2Pool] + p2pool(ui, min_size, size, p2pool_alive, p2pool_api, p2pool_img); + // [XMRig] + xmrig( + ui, + min_size, + size, + xmrig_alive, + xmrig_api, + xmrig_img, + max_threads, + ); + //[XMRig-Proxy] + xmrig_proxy(ui, min_size, size, xmrig_proxy_alive, xmrig_proxy_api); + // [XvB] + xvb(ui, min_size, size, xvb_alive, xvb_api); + }) }); } } -fn gupax(ui: &mut Ui, min_height: f32, width: f32, height: f32, sys: &Arc>) { +fn gupax(ui: &mut Ui, min_size: Vec2, size: Vec2, sys: &Arc>) { ui.group(|ui| { ui.vertical(|ui| { + ui.set_min_size(min_size); + ui.set_min_height(min_size.y * 34.0); debug!("Status Tab | Rendering [Gupaxx]"); - ui.set_min_height(min_height); + // ui.set_min_size([min_size.x, min_size.y / 2.0].into()); ui.add_sized( - [width, height], + size, Label::new( RichText::new("[Gupaxx]") .color(LIGHT_GRAY) @@ -78,50 +88,41 @@ fn gupax(ui: &mut Ui, min_height: f32, width: f32, height: f32, sys: &Arc>, p2pool_img: &Arc>, ) { ui.group(|ui| { ui.vertical(|ui| { - debug!("Status Tab | Rendering [P2Pool]"); - ui.set_enabled(p2pool_alive); - ui.set_min_height(min_height); + ScrollArea::vertical().show(ui, |ui| { + ui.set_min_height(min_size.y * 34.0); + ui.set_min_size(min_size); + debug!("Status Tab | Rendering [P2Pool]"); + ui.set_enabled(p2pool_alive); + ui.add_sized( + size, + Label::new( + RichText::new("[P2Pool]") + .color(LIGHT_GRAY) + .text_style(TextStyle::Name("MonospaceLarge".into())), + ), + ) + .on_hover_text("P2Pool is online") + .on_disabled_hover_text("P2Pool is offline"); + ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into())); + let size = [size.x, size.y / 1.4]; + let api = lock!(p2pool_api); + ui.add_sized( + size, + Label::new(RichText::new("Uptime").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_UPTIME); + ui.add_sized(size, Label::new(format!("{}", api.uptime))); + ui.add_sized( + size, + Label::new(RichText::new("Current Shares").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_CURRENT_SHARES); + ui.add_sized(size, Label::new(api.sidechain_shares.to_string())); + ui.add_sized( + size, + Label::new(RichText::new("Shares Found").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_SHARES); + ui.add_sized( + size, + Label::new( + (if let Some(s) = api.shares_found { + s.to_string() + } else { + UNKNOWN_DATA.to_string() + }) + .to_string(), + ), + ); + ui.add_sized( + size, + Label::new(RichText::new("Payouts").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_PAYOUTS); + ui.add_sized(size, Label::new(format!("Total: {}", api.payouts))); + ui.add_sized( + size, + Label::new(format!( + "[{:.7}/hour]\n[{:.7}/day]\n[{:.7}/month]", + api.payouts_hour, api.payouts_day, api.payouts_month + )), + ); + ui.add_sized( + size, + Label::new(RichText::new("XMR Mined").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_XMR); + ui.add_sized(size, Label::new(format!("Total: {:.13} XMR", api.xmr))); + ui.add_sized( + size, + Label::new(format!( + "[{:.7}/hour]\n[{:.7}/day]\n[{:.7}/month]", + api.xmr_hour, api.xmr_day, api.xmr_month + )), + ); + ui.add_sized( + size, + Label::new( + RichText::new("Hashrate (15m/1h/24h)") + .underline() + .color(BONE), + ), + ) + .on_hover_text(STATUS_P2POOL_HASHRATE); + ui.add_sized( + size, + Label::new(format!( + "[{} H/s] [{} H/s] [{} H/s]", + api.hashrate_15m, api.hashrate_1h, api.hashrate_24h + )), + ); + ui.add_sized( + size, + Label::new(RichText::new("Miners Connected").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_CONNECTIONS); + ui.add_sized(size, Label::new(format!("{}", api.connections))); + ui.add_sized( + size, + Label::new(RichText::new("Effort").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_EFFORT); + ui.add_sized( + size, + Label::new(format!( + "[Average: {}] [Current: {}]", + api.average_effort, api.current_effort + )), + ); + let img = lock!(p2pool_img); + ui.add_sized( + size, + Label::new(RichText::new("Monero Node").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_MONERO_NODE); + ui.add_sized( + size, + Label::new(format!( + "[IP: {}]\n[RPC: {}] [ZMQ: {}]", + &img.host, &img.rpc, &img.zmq + )), + ); + ui.add_sized( + size, + Label::new(RichText::new("Sidechain").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_POOL); + ui.add_sized(size, Label::new(&img.mini)); + ui.add_sized( + size, + Label::new(RichText::new("Address").underline().color(BONE)), + ) + .on_hover_text(STATUS_P2POOL_ADDRESS); + ui.add_sized(size, Label::new(&img.address)); + drop(img); + drop(api); + }) + }) + }); +} +#[allow(clippy::too_many_arguments)] +fn xmrig_proxy( + ui: &mut Ui, + min_size: Vec2, + size: Vec2, + xmrig_proxy_alive: bool, + xmrig_proxy_api: &Arc>, +) { + ui.group(|ui| { + ui.vertical(|ui| { + ui.set_min_height(min_size.y * 34.0); + debug!("Status Tab | Rendering [XMRig-Proxy]"); + ui.set_enabled(xmrig_proxy_alive); + ui.set_min_size(min_size); ui.add_sized( - [width, height], + size, Label::new( - RichText::new("[P2Pool]") + RichText::new("[XMRig-Proxy]") .color(LIGHT_GRAY) .text_style(TextStyle::Name("MonospaceLarge".into())), ), ) - .on_hover_text("P2Pool is online") - .on_disabled_hover_text("P2Pool is offline"); - ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into())); - let height = height / 1.4; - let api = lock!(p2pool_api); + .on_hover_text("XMRig-Proxy is online") + .on_disabled_hover_text("XMRig-Proxy is offline"); + let api = lock!(xmrig_proxy_api); ui.add_sized( - [width, height], + size, Label::new(RichText::new("Uptime").underline().color(BONE)), ) - .on_hover_text(STATUS_P2POOL_UPTIME); - ui.add_sized([width, height], Label::new(format!("{}", api.uptime))); + .on_hover_text(STATUS_XMRIG_PROXY_UPTIME); + ui.add_sized(size, Label::new(UptimeFull::from(api.uptime).as_str())); ui.add_sized( - [width, height], - Label::new(RichText::new("Current Shares").underline().color(BONE)), - ) - .on_hover_text(STATUS_P2POOL_CURRENT_SHARES); - ui.add_sized( - [width, height], - Label::new(api.sidechain_shares.to_string()), - ); - ui.add_sized( - [width, height], - Label::new(RichText::new("Shares Found").underline().color(BONE)), - ) - .on_hover_text(STATUS_P2POOL_SHARES); - ui.add_sized( - [width, height], + size, Label::new( - (if let Some(s) = api.shares_found { - s.to_string() - } else { - UNKNOWN_DATA.to_string() - }) - .to_string(), - ), - ); - ui.add_sized( - [width, height], - Label::new(RichText::new("Payouts").underline().color(BONE)), - ) - .on_hover_text(STATUS_P2POOL_PAYOUTS); - ui.add_sized( - [width, height], - Label::new(format!("Total: {}", api.payouts)), - ); - ui.add_sized( - [width, height], - Label::new(format!( - "[{:.7}/hour]\n[{:.7}/day]\n[{:.7}/month]", - api.payouts_hour, api.payouts_day, api.payouts_month - )), - ); - ui.add_sized( - [width, height], - Label::new(RichText::new("XMR Mined").underline().color(BONE)), - ) - .on_hover_text(STATUS_P2POOL_XMR); - ui.add_sized( - [width, height], - Label::new(format!("Total: {:.13} XMR", api.xmr)), - ); - ui.add_sized( - [width, height], - Label::new(format!( - "[{:.7}/hour]\n[{:.7}/day]\n[{:.7}/month]", - api.xmr_hour, api.xmr_day, api.xmr_month - )), - ); - ui.add_sized( - [width, height], - Label::new( - RichText::new("Hashrate (15m/1h/24h)") + RichText::new("Hashrate\n(1m/10m/1h/12h/24h)") .underline() .color(BONE), ), ) - .on_hover_text(STATUS_P2POOL_HASHRATE); + .on_hover_text(STATUS_XMRIG_PROXY_HASHRATE); ui.add_sized( - [width, height], + size, Label::new(format!( - "[{} H/s] [{} H/s] [{} H/s]", - api.hashrate_15m, api.hashrate_1h, api.hashrate_24h + "[{} H/s] [{} H/s]\n[{} H/s] [{} H/s] [{} H/s]", + api.hashrate_1m, + api.hashrate_10m, + api.hashrate_1h, + api.hashrate_12h, + api.hashrate_24h )), ); ui.add_sized( - [width, height], - Label::new(RichText::new("Miners Connected").underline().color(BONE)), - ) - .on_hover_text(STATUS_P2POOL_CONNECTIONS); - ui.add_sized([width, height], Label::new(format!("{}", api.connections))); - ui.add_sized( - [width, height], - Label::new(RichText::new("Effort").underline().color(BONE)), - ) - .on_hover_text(STATUS_P2POOL_EFFORT); - ui.add_sized( - [width, height], + size, Label::new(format!( - "[Average: {}] [Current: {}]", - api.average_effort, api.current_effort - )), - ); - let img = lock!(p2pool_img); - ui.add_sized( - [width, height], - Label::new(RichText::new("Monero Node").underline().color(BONE)), - ) - .on_hover_text(STATUS_P2POOL_MONERO_NODE); - ui.add_sized( - [width, height], - Label::new(format!( - "[IP: {}]\n[RPC: {}] [ZMQ: {}]", - &img.host, &img.rpc, &img.zmq + "[Accepted: {}]\n[Rejected: {}]", + api.accepted, api.rejected )), ); ui.add_sized( - [width, height], - Label::new(RichText::new("Sidechain").underline().color(BONE)), + size, + Label::new(RichText::new("Pool").underline().color(BONE)), ) - .on_hover_text(STATUS_P2POOL_POOL); - ui.add_sized([width, height], Label::new(&img.mini)); - ui.add_sized( - [width, height], - Label::new(RichText::new("Address").underline().color(BONE)), - ) - .on_hover_text(STATUS_P2POOL_ADDRESS); - ui.add_sized([width, height], Label::new(&img.address)); - drop(img); + .on_hover_text(STATUS_XMRIG_PROXY_POOL); + ui.add_sized(size, Label::new(api.node.to_string())); drop(api); }) }); @@ -284,21 +346,23 @@ fn p2pool( #[allow(clippy::too_many_arguments)] fn xmrig( ui: &mut Ui, - min_height: f32, - width: f32, - height: f32, + min_size: Vec2, + size: Vec2, xmrig_alive: bool, xmrig_api: &Arc>, xmrig_img: &Arc>, max_threads: usize, ) { ui.group(|ui| { + // ScrollArea::vertical().show(ui, |ui| { ui.vertical(|ui| { + ui.set_min_height(min_size.y * 34.0); + ui.spacing_mut().item_spacing = Vec2::new(2.0, 2.0); debug!("Status Tab | Rendering [XMRig]"); ui.set_enabled(xmrig_alive); - ui.set_min_height(min_height); + // ui.set_min_size(min_size); ui.add_sized( - [width, height], + size, Label::new( RichText::new("[XMRig]") .color(LIGHT_GRAY) @@ -309,91 +373,74 @@ fn xmrig( .on_disabled_hover_text("XMRig is offline"); let api = lock!(xmrig_api); ui.add_sized( - [width, height], + size, Label::new(RichText::new("Uptime").underline().color(BONE)), ) .on_hover_text(STATUS_XMRIG_UPTIME); + ui.add_sized(size, Label::new(UptimeFull::from(api.uptime).as_str())); + ui.add_sized(size, Label::new(api.resources.to_string())); ui.add_sized( - [width, height], - Label::new(UptimeFull::from(api.uptime).as_str()), - ); - ui.add_sized( - [width, height], + size, Label::new( - RichText::new("CPU Load (10s/60s/15m)") - .underline() - .color(BONE), - ), - ) - .on_hover_text(STATUS_XMRIG_CPU); - ui.add_sized([width, height], Label::new(api.resources.to_string())); - ui.add_sized( - [width, height], - Label::new( - RichText::new("Hashrate (10s/60s/15m)") + RichText::new("Hashrate\n(10s/1m/15m)") .underline() .color(BONE), ), ) .on_hover_text(STATUS_XMRIG_HASHRATE); - ui.add_sized([width, height], Label::new(api.hashrate.to_string())); + ui.add_sized(size, Label::new(api.hashrate.to_string())); ui.add_sized( - [width, height], + size, Label::new(RichText::new("Difficulty").underline().color(BONE)), ) .on_hover_text(STATUS_XMRIG_DIFFICULTY); - ui.add_sized([width, height], Label::new(api.diff.to_string())); + ui.add_sized(size, Label::new(api.diff.to_string())); ui.add_sized( - [width, height], + size, Label::new(RichText::new("Shares").underline().color(BONE)), ) .on_hover_text(STATUS_XMRIG_SHARES); ui.add_sized( - [width, height], + size, Label::new(format!( - "[Accepted: {}] [Rejected: {}]", + "[Accepted: {}]\n[Rejected: {}]", api.accepted, api.rejected )), ); ui.add_sized( - [width, height], + size, Label::new(RichText::new("Pool").underline().color(BONE)), ) .on_hover_text(STATUS_XMRIG_POOL); - ui.add_sized([width, height], Label::new(api.node.to_string())); + ui.add_sized(size, Label::new(api.node.to_string())); ui.add_sized( - [width, height], + size, Label::new(RichText::new("Threads").underline().color(BONE)), ) .on_hover_text(STATUS_XMRIG_THREADS); ui.add_sized( - [width, height], + size, Label::new(format!("{}/{}", &lock!(xmrig_img).threads, max_threads)), ); drop(api); }) + // }) }); } -fn xvb( - ui: &mut Ui, - min_height: f32, - width: f32, - height: f32, - xvb_alive: bool, - xvb_api: &Arc>, -) { +fn xvb(ui: &mut Ui, min_size: Vec2, size: Vec2, xvb_alive: bool, xvb_api: &Arc>) { // let api = &lock!(xvb_api).stats_pub; let enabled = xvb_alive; ui.group(|ui| { ScrollArea::vertical().show(ui, |ui| { + ui.set_min_height(min_size.y * 34.0); ui.vertical(|ui| { debug!("Status Tab | Rendering [XvB]"); ui.set_enabled(enabled); // for now there is no API ping or /health, so we verify if the field reward_yearly is empty or not. - ui.set_min_height(min_height); + // ui.set_min_size(min_size); ui.add_sized( - [width, height], + size, Label::new( RichText::new("[XvB Raffle]") .color(LIGHT_GRAY) @@ -404,14 +451,14 @@ fn xvb( .on_disabled_hover_text("No data received from XvB API"); // [Round Type] ui.add_sized( - [width, height], + size, Label::new(RichText::new("Round Type").underline().color(BONE)), ) .on_hover_text(STATUS_XVB_ROUND_TYPE); - ui.add_sized([width, height], Label::new(api.round_type.to_string())); + ui.add_sized(size, Label::new(api.round_type.to_string())); // [Time Remaining] ui.add_sized( - [width, height], + size, Label::new( RichText::new("Round Time Remaining") .underline() @@ -419,18 +466,15 @@ fn xvb( ), ) .on_hover_text(STATUS_XVB_TIME_REMAIN); - ui.add_sized( - [width, height], - Label::new(format!("{} minutes", api.time_remain)), - ); + ui.add_sized(size, Label::new(format!("{} minutes", api.time_remain))); // Donated Hashrate ui.add_sized( - [width, height], + size, Label::new(RichText::new("Bonus Hashrate").underline().color(BONE)), ) .on_hover_text(STATUS_XVB_DONATED_HR); ui.add_sized( - [width, height], + size, Label::new(format!( "{}kH/s\n+\n{}kH/s\ndonated by\n{} donors\n with\n{} miners", api.bonus_hr, api.donate_hr, api.donate_miners, api.donate_workers @@ -438,12 +482,12 @@ fn xvb( ); // Players ui.add_sized( - [width, height], + size, Label::new(RichText::new("Players").underline().color(BONE)), ) .on_hover_text(STATUS_XVB_PLAYERS); ui.add_sized( - [width, height], + size, Label::new(format!( "[Registered: {}]\n[Playing: {}]", api.players, api.players_round @@ -451,28 +495,28 @@ fn xvb( ); // Winner ui.add_sized( - [width, height], + size, Label::new(RichText::new("Winner").underline().color(BONE)), ) .on_hover_text(STATUS_XVB_WINNER); - ui.add_sized([width, height], Label::new(&api.winner)); + ui.add_sized(size, Label::new(&api.winner)); // Share effort ui.add_sized( - [width, height], + size, Label::new(RichText::new("Share Effort").underline().color(BONE)), ) .on_hover_text(STATUS_XVB_SHARE); - ui.add_sized([width, height], Label::new(api.share_effort.to_string())); + ui.add_sized(size, Label::new(api.share_effort.to_string())); // Block reward ui.add_sized( - [width, height], + size, Label::new(RichText::new("Block Reward").underline().color(BONE)), ) .on_hover_text(STATUS_XVB_BLOCK_REWARD); - ui.add_sized([width, height], Label::new(api.block_reward.to_string())); + ui.add_sized(size, Label::new(api.block_reward.to_string())); // reward yearly ui.add_sized( - [width, height], + size, Label::new( RichText::new("Est. Reward (Yearly)") .underline() @@ -481,10 +525,10 @@ fn xvb( ) .on_hover_text(STATUS_XVB_YEARLY); if api.reward_yearly.is_empty() { - ui.add_sized([width, height], Label::new("No information".to_string())); + ui.add_sized(size, Label::new("No information".to_string())); } else { ui.add_sized( - [width, height], + size, Label::new(format!( "{}: {} XMR\n{}: {} XMR\n{}: {} XMR\n{}: {} XMR\n{}: {} XMR", XvbRound::Vip, @@ -500,7 +544,7 @@ fn xvb( )), ); } - }); + }) // by round }); }); diff --git a/src/app/panels/middle/xmrig.rs b/src/app/panels/middle/xmrig.rs index 239bcbf..ff12e17 100644 --- a/src/app/panels/middle/xmrig.rs +++ b/src/app/panels/middle/xmrig.rs @@ -17,7 +17,7 @@ use crate::disk::pool::Pool; use crate::disk::state::Xmrig; -use crate::helper::xmrig::PubXmrigApi; +use crate::helper::xrig::xmrig::PubXmrigApi; use crate::helper::Process; use crate::regex::{num_lines, REGEXES}; use crate::utils::regex::Regexes; diff --git a/src/app/panels/middle/xmrig_proxy.rs b/src/app/panels/middle/xmrig_proxy.rs new file mode 100644 index 0000000..92f2cb2 --- /dev/null +++ b/src/app/panels/middle/xmrig_proxy.rs @@ -0,0 +1,420 @@ +use egui::{vec2, Button, Checkbox, ComboBox, Label, RichText, SelectableLabel, TextEdit, Vec2}; +use std::sync::{Arc, Mutex}; + +use egui::TextStyle::{self, Name}; +use log::{debug, info}; + +use crate::disk::pool::Pool; +use crate::disk::state::XmrigProxy; +use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi; +use crate::helper::Process; +use crate::regex::{num_lines, REGEXES}; +use crate::utils::constants::DARK_GRAY; +use crate::utils::macros::lock; +use crate::{ + GREEN, LIGHT_GRAY, LIST_ADD, LIST_CLEAR, LIST_DELETE, LIST_SAVE, RED, SPACE, XMRIG_API_IP, + XMRIG_API_PORT, XMRIG_IP, XMRIG_KEEPALIVE, XMRIG_NAME, XMRIG_PORT, XMRIG_PROXY_ARGUMENTS, + XMRIG_PROXY_INPUT, XMRIG_PROXY_REDIRECT, XMRIG_RIG, XMRIG_TLS, +}; + +impl XmrigProxy { + #[inline(always)] // called once + pub fn show( + &mut self, + process: &Arc>, + pool_vec: &mut Vec<(String, Pool)>, + api: &Arc>, + buffer: &mut String, + size: Vec2, + ui: &mut egui::Ui, + ) { + let width = size.x; + let height = size.y; + let space_h = height / 48.0; + let text_edit = size.y / 25.0; + // console output for log + debug!("XvB Tab | Rendering [Console]"); + ui.group(|ui| { + let text = &lock!(api).output; + let nb_lines = num_lines(text); + let height = size.y / 2.8; + let width = size.x - (space_h / 2.0); + egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| { + ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into())); + egui::ScrollArea::vertical() + .stick_to_bottom(true) + .max_width(width) + .max_height(height) + .auto_shrink([false; 2]) + // .show_viewport(ui, |ui, _| { + .show_rows( + ui, + ui.text_style_height(&TextStyle::Name("MonospaceSmall".into())), + nb_lines, + |ui, row_range| { + for i in row_range { + if let Some(line) = text.lines().nth(i) { + ui.label(line); + } + } + }, + ); + }); + }); + //---------------------------------------------------------------------------------------------------- [Advanced] Console + if !self.simple { + ui.separator(); + let response = ui + .add_sized( + [width, text_edit], + TextEdit::hint_text( + TextEdit::singleline(buffer), + r#"Commands: [h]ashrate, [c]onnections, [v]erbose, [w]orkers"#, + ), + ) + .on_hover_text(XMRIG_PROXY_INPUT); + // If the user pressed enter, dump buffer contents into the process STDIN + if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { + response.request_focus(); // Get focus back + let buffer = std::mem::take(buffer); // Take buffer + let mut process = lock!(process); // Lock + if process.is_alive() { + process.input.push(buffer); + } // Push only if alive + } + + //---------------------------------------------------------------------------------------------------- Arguments + debug!("XMRig Tab | Rendering [Arguments]"); + ui.group(|ui| { + ui.horizontal(|ui| { + let width = (size.x / 10.0) - SPACE; + ui.add_sized([width, text_edit], Label::new("Command arguments:")); + ui.add_sized( + [ui.available_width(), text_edit], + TextEdit::hint_text( + TextEdit::singleline(&mut self.arguments), + r#"--url <...> --user <...> --config <...>"#, + ), + ) + .on_hover_text(XMRIG_PROXY_ARGUMENTS); + self.arguments.truncate(1024); + }) + }); + ui.set_enabled(self.arguments.is_empty()); + ui.add_space(space_h); + ui.style_mut().spacing.icon_width_inner = width / 45.0; + ui.style_mut().spacing.icon_width = width / 35.0; + ui.style_mut().spacing.icon_spacing = space_h; + ui.checkbox( + &mut self.redirect_local_xmrig, + "Auto Redirect local Xmrig to Xmrig-Proxy", + ) + .on_hover_text(XMRIG_PROXY_REDIRECT); + + // idea + // need to warn the user if local firewall is blocking port + // need to warn the user if NAT is blocking port + // need to show local ip address + // need to show public ip + + debug!("XMRig Tab | Rendering [Pool List] elements"); + let width = ui.available_width() - 10.0; + let mut incorrect_input = false; // This will disable [Add/Delete] on bad input + // [Pool IP/Port] + ui.horizontal(|ui| { + ui.group(|ui| { + let width = width/10.0; + ui.vertical(|ui| { + ui.spacing_mut().text_edit_width = width*3.32; + ui.horizontal(|ui| { + let text; + let color; + let len = format!("{:02}", self.name.len()); + if self.name.is_empty() { + text = format!("Name [ {}/30 ]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if REGEXES.name.is_match(&self.name) { + text = format!("Name [ {}/30 ]✔", len); + color = GREEN; + } else { + text = format!("Name [ {}/30 ]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.name).on_hover_text(XMRIG_NAME); + self.name.truncate(30); + }); + ui.horizontal(|ui| { + let text; + let color; + let len = format!("{:03}", self.p2pool_ip.len()); + if self.p2pool_ip.is_empty() { + text = format!(" IP [{}/255]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if self.p2pool_ip == "localhost" || REGEXES.ipv4.is_match(&self.p2pool_ip) || REGEXES.domain.is_match(&self.p2pool_ip) { + text = format!(" IP [{}/255]✔", len); + color = GREEN; + } else { + text = format!(" IP [{}/255]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.p2pool_ip).on_hover_text(XMRIG_IP); + self.p2pool_ip.truncate(255); + }); + ui.horizontal(|ui| { + let text; + let color; + let len = self.p2pool_port.len(); + if self.p2pool_port.is_empty() { + text = format!("Port [ {}/5 ]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if REGEXES.port.is_match(&self.p2pool_port) { + text = format!("Port [ {}/5 ]✔", len); + color = GREEN; + } else { + text = format!("Port [ {}/5 ]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.p2pool_port).on_hover_text(XMRIG_PORT); + self.p2pool_port.truncate(5); + }); + ui.horizontal(|ui| { + let text; + let color; + let len = format!("{:02}", self.rig.len()); + if self.rig.is_empty() { + text = format!(" Rig [ {}/30 ]➖", len); + color = LIGHT_GRAY; + } else if REGEXES.name.is_match(&self.rig) { + text = format!(" Rig [ {}/30 ]✔", len); + color = GREEN; + } else { + text = format!(" Rig [ {}/30 ]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color))); + ui.text_edit_singleline(&mut self.rig).on_hover_text(XMRIG_RIG); + self.rig.truncate(30); + }); + }); + + ui.vertical(|ui| { + let width = ui.available_width(); + ui.add_space(1.0); + // [Manual node selection] + ui.spacing_mut().slider_width = width - 8.0; + ui.spacing_mut().icon_width = width / 25.0; + // [Node List] + debug!("XMRig Tab | Rendering [Node List] ComboBox"); + let text = RichText::new(format!("{}. {}", self.selected_index+1, self.selected_name)); + ComboBox::from_id_source("manual_pool").selected_text(text).width(width).show_ui(ui, |ui| { + for (n, (name, pool)) in pool_vec.iter().enumerate() { + let text = format!("{}. {}\n IP: {}\n Port: {}\n Rig: {}", n+1, name, pool.ip, pool.port, pool.rig); + if ui.add(SelectableLabel::new(self.selected_name == *name, text)).clicked() { + self.selected_index = n; + let pool = pool.clone(); + self.selected_name.clone_from(name); + self.selected_rig.clone_from(&pool.rig); + self.selected_ip.clone_from(&pool.ip); + self.selected_port.clone_from(&pool.port); + self.name.clone_from(name); + self.rig = pool.rig; + self.p2pool_ip = pool.ip; + self.p2pool_port = pool.port; + } + } + }); + // [Add/Save] + let pool_vec_len = pool_vec.len(); + let mut exists = false; + let mut save_diff = true; + let mut existing_index = 0; + for (name, pool) in pool_vec.iter() { + if *name == self.name { + exists = true; + if self.rig == pool.rig && self.p2pool_ip == pool.ip && self.p2pool_port == pool.port { + save_diff = false; + } + break + } + existing_index += 1; + } + ui.horizontal(|ui| { + let text = if exists { LIST_SAVE } else { LIST_ADD }; + let text = format!("{}\n Currently selected pool: {}. {}\n Current amount of pools: {}/1000", text, self.selected_index+1, self.selected_name, pool_vec_len); + // If the pool already exists, show [Save] and mutate the already existing pool + if exists { + ui.set_enabled(!incorrect_input && save_diff); + if ui.add_sized([width, text_edit], Button::new("Save")).on_hover_text(text).clicked() { + let pool = Pool { + rig: self.rig.clone(), + ip: self.p2pool_ip.clone(), + port: self.p2pool_port.clone(), + }; + pool_vec[existing_index].1 = pool; + self.selected_name.clone_from(&self.name); + self.selected_rig.clone_from(&self.rig); + self.selected_ip.clone_from(&self.p2pool_ip); + self.selected_port.clone_from(&self.p2pool_port); + info!("Node | S | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig: \"{}\"]", existing_index+1, self.name, self.p2pool_ip, self.p2pool_port, self.rig); + } + // Else, add to the list + } else { + ui.set_enabled(!incorrect_input && pool_vec_len < 1000); + if ui.add_sized([width, text_edit], Button::new("Add")).on_hover_text(text).clicked() { + let pool = Pool { + rig: self.rig.clone(), + ip: self.p2pool_ip.clone(), + port: self.p2pool_port.clone(), + }; + pool_vec.push((self.name.clone(), pool)); + self.selected_index = pool_vec_len; + self.selected_name.clone_from(&self.name); + self.selected_rig.clone_from(&self.rig); + self.selected_ip.clone_from(&self.p2pool_ip); + self.selected_port.clone_from(&self.p2pool_port); + info!("Node | A | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig: \"{}\"]", pool_vec_len, self.name, self.p2pool_ip, self.p2pool_port, self.rig); + } + } + }); + // [Delete] + ui.horizontal(|ui| { + ui.set_enabled(pool_vec_len > 1); + let text = format!("{}\n Currently selected pool: {}. {}\n Current amount of pools: {}/1000", LIST_DELETE, self.selected_index+1, self.selected_name, pool_vec_len); + if ui.add_sized([width, text_edit], Button::new("Delete")).on_hover_text(text).clicked() { + let new_name; + let new_pool; + match self.selected_index { + 0 => { + new_name = pool_vec[1].0.clone(); + new_pool = pool_vec[1].1.clone(); + pool_vec.remove(0); + } + _ => { + pool_vec.remove(self.selected_index); + self.selected_index -= 1; + new_name = pool_vec[self.selected_index].0.clone(); + new_pool = pool_vec[self.selected_index].1.clone(); + } + }; + self.selected_name.clone_from(&new_name); + self.selected_rig.clone_from(&new_pool.rig); + self.selected_ip.clone_from(&new_pool.ip); + self.selected_port.clone_from(&new_pool.port); + self.name = new_name; + self.rig = new_pool.rig; + self.p2pool_ip = new_pool.ip; + self.p2pool_port = new_pool.port; + info!("Node | D | [index: {}, name: \"{}\", ip: \"{}\", port: {}, rig\"{}\"]", self.selected_index, self.selected_name, self.selected_ip, self.selected_port, self.selected_rig); + } + }); + ui.horizontal(|ui| { + ui.set_enabled(!self.name.is_empty() || !self.p2pool_ip.is_empty() || !self.p2pool_port.is_empty()); + if ui.add_sized([width, text_edit], Button::new("Clear")).on_hover_text(LIST_CLEAR).clicked() { + self.name.clear(); + self.rig.clear(); + self.p2pool_ip.clear(); + self.p2pool_port.clear(); + } + }); + }); + }); + }); + ui.add_space(5.0); + + debug!("XMRig Tab | Rendering [API] TextEdits"); + // [HTTP API IP/Port] + ui.group(|ui| { + ui.horizontal(|ui| { + ui.vertical(|ui| { + let width = width / 10.0; + ui.spacing_mut().text_edit_width = width * 2.39; + // HTTP API + ui.horizontal(|ui| { + let text; + let color; + let len = format!("{:03}", self.api_ip.len()); + if self.api_ip.is_empty() { + text = format!("HTTP API IP [{}/255]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if self.api_ip == "localhost" + || REGEXES.ipv4.is_match(&self.api_ip) + || REGEXES.domain.is_match(&self.api_ip) + { + text = format!("HTTP API IP [{}/255]✔", len); + color = GREEN; + } else { + text = format!("HTTP API IP [{}/255]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized( + [width, text_edit], + Label::new(RichText::new(text).color(color)), + ); + ui.text_edit_singleline(&mut self.api_ip) + .on_hover_text(XMRIG_API_IP); + self.api_ip.truncate(255); + }); + ui.horizontal(|ui| { + let text; + let color; + let len = self.api_port.len(); + if self.api_port.is_empty() { + text = format!("HTTP API Port [ {}/5 ]➖", len); + color = LIGHT_GRAY; + incorrect_input = true; + } else if REGEXES.port.is_match(&self.api_port) { + text = format!("HTTP API Port [ {}/5 ]✔", len); + color = GREEN; + } else { + text = format!("HTTP API Port [ {}/5 ]❌", len); + color = RED; + incorrect_input = true; + } + ui.add_sized( + [width, text_edit], + Label::new(RichText::new(text).color(color)), + ); + ui.text_edit_singleline(&mut self.api_port) + .on_hover_text(XMRIG_API_PORT); + self.api_port.truncate(5); + }); + }); + + ui.separator(); + + debug!("XMRig Tab | Rendering [TLS/Keepalive] buttons"); + ui.vertical(|ui| { + // TLS/Keepalive + ui.horizontal(|ui| { + let width = (ui.available_width() / 2.0) - 11.0; + let height = text_edit * 2.0; + let size = vec2(width, height); + // let mut style = (*ctx.style()).clone(); + // style.spacing.icon_width_inner = width / 8.0; + // style.spacing.icon_width = width / 6.0; + // style.spacing.icon_spacing = 20.0; + // ctx.set_style(style); + ui.add_sized(size, Checkbox::new(&mut self.tls, "TLS Connection")) + .on_hover_text(XMRIG_TLS); + ui.separator(); + ui.add_sized(size, Checkbox::new(&mut self.keepalive, "Keepalive")) + .on_hover_text(XMRIG_KEEPALIVE); + }); + }); + }); + }); + } + } +} diff --git a/src/app/panels/top.rs b/src/app/panels/top.rs index 68de4e7..4ab6581 100644 --- a/src/app/panels/top.rs +++ b/src/app/panels/top.rs @@ -1,5 +1,5 @@ -use egui::TextStyle::Name; -use egui::{SelectableLabel, TopBottomPanel}; +use egui::TextStyle::{self, Name}; +use egui::{RichText, SelectableLabel, TopBottomPanel}; use log::debug; use crate::{app::Tab, utils::constants::SPACE}; @@ -8,7 +8,7 @@ impl crate::app::App { pub fn top_panel(&mut self, ctx: &egui::Context) { debug!("App | Rendering TOP tabs"); TopBottomPanel::top("top").show(ctx, |ui| { - let width = (self.size.x - (SPACE * 11.0)) / 6.0; + let width = (self.size.x - (SPACE * 16.0)) / 7.0; let height = self.size.y / 15.0; ui.add_space(4.0); ui.horizontal(|ui| { @@ -63,6 +63,20 @@ impl crate::app::App { self.tab = Tab::Xmrig; } ui.separator(); + let font_size = ui.text_style_height(&TextStyle::Name("Tab".into())) / 2.5; + if ui + .add_sized( + [width, height], + SelectableLabel::new( + self.tab == Tab::XmrigProxy, + RichText::new("XMRig-Proxy").size(font_size), + ), + ) + .clicked() + { + self.tab = Tab::XmrigProxy; + } + ui.separator(); if ui .add_sized( [width, height], diff --git a/src/components/gupax.rs b/src/components/gupax.rs index ad98974..307907e 100644 --- a/src/components/gupax.rs +++ b/src/components/gupax.rs @@ -31,11 +31,13 @@ use std::{ // The opened file picker is started in a new // thread so main() needs to be in sync. pub struct FileWindow { - pub thread: bool, // Is there already a FileWindow thread? - pub picked_p2pool: bool, // Did the user pick a path for p2pool? - pub picked_xmrig: bool, // Did the user pick a path for xmrig? - pub p2pool_path: String, // The picked p2pool path - pub xmrig_path: String, // The picked p2pool path + pub thread: bool, // Is there already a FileWindow thread? + pub picked_p2pool: bool, // Did the user pick a path for p2pool? + pub picked_xmrig: bool, // Did the user pick a path for xmrig? + pub picked_xp: bool, // Did the user pick a path for xmrig-proxy? + pub p2pool_path: String, // The picked p2pool path + pub xmrig_path: String, // The picked xmrig path + pub xmrig_proxy_path: String, // The picked xmrig-proxy path } impl FileWindow { @@ -44,8 +46,10 @@ impl FileWindow { thread: false, picked_p2pool: false, picked_xmrig: false, + picked_xp: false, p2pool_path: String::new(), xmrig_path: String::new(), + xmrig_proxy_path: String::new(), }) } } @@ -54,6 +58,7 @@ impl FileWindow { pub enum FileType { P2pool, Xmrig, + XmrigProxy, } //---------------------------------------------------------------------------------------------------- Ratio Lock @@ -83,6 +88,7 @@ impl Gupax { let name = match file_type { P2pool => "P2Pool", Xmrig => "XMRig", + XmrigProxy => "XMRigProxy", }; let file_window = file_window.clone(); lock!(file_window).thread = true; @@ -102,6 +108,10 @@ impl Gupax { lock!(file_window).xmrig_path = path.display().to_string(); lock!(file_window).picked_xmrig = true; } + XmrigProxy => { + lock!(file_window).xmrig_proxy_path = path.display().to_string(); + lock!(file_window).picked_xp = true; + } }; } None => info!("Gupaxx | No path selected for {}", name), diff --git a/src/components/update.rs b/src/components/update.rs index b3c6fa5..fa4c0b4 100644 --- a/src/components/update.rs +++ b/src/components/update.rs @@ -62,6 +62,7 @@ cfg_if::cfg_if! { pub(super) const GUPAX_BINARY: &str = "gupaxx"; pub(super) const P2POOL_BINARY: &str = "p2pool"; pub(super) const XMRIG_BINARY: &str = "xmrig"; + pub(super) const XMRIG_PROXY_BINARY: &str = "xmrig-proxy"; } } cfg_if::cfg_if! { @@ -71,6 +72,7 @@ cfg_if::cfg_if! { pub(super) const GUPAX_BINARY: &str = "Gupaxx.exe"; pub(super) const P2POOL_BINARY: &str = "p2pool.exe"; pub(super) const XMRIG_BINARY: &str = "xmrig.exe"; + pub(super) const XMRIG_PROXY_BINARY: &str = "xmrig-proxy.exe"; } else if #[cfg(target_os = "linux")] { pub(super) const OS_TARGET: &str = "linux"; pub(super) const ARCHIVE_EXT: &str = "tar.gz"; @@ -165,6 +167,20 @@ pub fn check_xmrig_path(path: &str) -> bool { }; path == XMRIG_BINARY } +pub fn check_xp_path(path: &str) -> bool { + let path = match crate::disk::into_absolute_path(path.to_string()) { + Ok(p) => p, + Err(_) => return false, + }; + let path = match path.file_name() { + Some(p) => p, + None => { + error!("Couldn't get XMRig-Proxy file name"); + return false; + } + }; + path == XMRIG_PROXY_BINARY +} //---------------------------------------------------------------------------------------------------- Update struct/impl // Contains values needed during update @@ -183,6 +199,7 @@ pub struct Update { pub path_gupax: String, // Full path to current gupax pub path_p2pool: String, // Full path to current p2pool pub path_xmrig: String, // Full path to current xmrig + pub path_xp: String, // Full path to current xmrig pub updating: Arc>, // Is an update in progress? pub prog: Arc>, // Holds the 0-100% progress bar number pub msg: Arc>, // Message to display on [Gupax] tab while updating @@ -190,11 +207,17 @@ pub struct Update { impl Update { // Takes in current paths from [State] - pub fn new(path_gupax: String, path_p2pool: PathBuf, path_xmrig: PathBuf) -> Self { + pub fn new( + path_gupax: String, + path_p2pool: PathBuf, + path_xmrig: PathBuf, + path_xp: PathBuf, + ) -> Self { Self { path_gupax, path_p2pool: path_p2pool.display().to_string(), path_xmrig: path_xmrig.display().to_string(), + path_xp: path_xp.display().to_string(), updating: arc_mut!(false), prog: arc_mut!(0.0), msg: arc_mut!(MSG_NONE.to_string()), @@ -242,7 +265,7 @@ impl Update { error!("Update | This is the [Linux distro] version of Gupax, updates are disabled"); #[cfg(feature = "distro")] return; - // verify validity of absolute path for p2pool and xmrig only if we want to update them. + // verify validity of absolute path for p2pool, xmrig and xmrig-proxy only if we want to update them. if lock!(og).gupax.bundled { // Check P2Pool path for safety // Attempt relative to absolute path @@ -276,8 +299,24 @@ impl Update { return; } }; + // Check XMRig-Proxy path for safety + let xmrig_proxy_path = match into_absolute_path(gupax.xmrig_proxy_path.clone()) { + Ok(p) => p, + Err(e) => { + error_state.set( + format!( + "Provided XMRig-Proxy path could not be turned into an absolute path: {}", + e + ), + ErrorFerris::Error, + ErrorButtons::Okay, + ); + return; + } + }; lock!(update).path_p2pool = p2pool_path.display().to_string(); lock!(update).path_xmrig = xmrig_path.display().to_string(); + lock!(update).path_xp = xmrig_proxy_path.display().to_string(); } // Clone before thread spawn @@ -471,7 +510,7 @@ impl Update { info!("Update | Extract ... OK ... {}%", *lock2!(update, prog)); //---------------------------------------------------------------------------------------------------- Upgrade - // if bundled, directories p2pool and xmrig will exist. + // if bundled, directories p2pool, xmrig and xmrig-proxy will exist. // if not, only gupaxx binary will be present. // 1. Walk directories // @@ -496,6 +535,7 @@ impl Update { GUPAX_BINARY => lock!(update).path_gupax.clone(), P2POOL_BINARY => lock!(update).path_p2pool.clone(), XMRIG_BINARY => lock!(update).path_xmrig.clone(), + XMRIG_PROXY_BINARY => lock!(update).path_xp.clone(), _ => continue, }; found = true; @@ -511,6 +551,7 @@ impl Update { GUPAX_BINARY => tmp_dir.clone() + "gupaxx_old.exe", P2POOL_BINARY => tmp_dir.clone() + "p2pool_old.exe", XMRIG_BINARY => tmp_dir.clone() + "xmrig_old.exe", + XMRIG_PROXY_BINARY => tmp_dir.clone() + "xmrig-proxy_old.exe", _ => continue, }; info!( @@ -525,8 +566,10 @@ impl Update { entry.path().display(), path.display() ); - // if bundled, create directory for p2pool and xmrig if not present - if lock!(og).gupax.bundled && (name == P2POOL_BINARY || name == XMRIG_BINARY) { + // if bundled, create directory for p2pool, xmrig and xmrig-proxy if not present + if lock!(og).gupax.bundled + && (name == P2POOL_BINARY || name == XMRIG_BINARY || name == XMRIG_PROXY_BINARY) + { std::fs::create_dir_all( path.parent() .ok_or_else(|| anyhow!(format!("{} path failed", name)))?, diff --git a/src/disk/consts.rs b/src/disk/consts.rs index e9166c8..4d3e29c 100644 --- a/src/disk/consts.rs +++ b/src/disk/consts.rs @@ -40,8 +40,12 @@ pub const DEFAULT_P2POOL_PATH: &str = r"P2Pool\p2pool.exe"; pub const DEFAULT_P2POOL_PATH: &str = "p2pool/p2pool"; #[cfg(target_os = "windows")] pub const DEFAULT_XMRIG_PATH: &str = r"XMRig\xmrig.exe"; +#[cfg(target_os = "windows")] +pub const DEFAULT_XMRIG_PROXY_PATH: &str = r"XMRig-Proxy\xmrig-proxy.exe"; #[cfg(target_os = "macos")] pub const DEFAULT_XMRIG_PATH: &str = "xmrig/xmrig"; +#[cfg(target_os = "macos")] +pub const DEFAULT_XMRIG_PROXY_PATH: &str = "xmrig-proxy/xmrig-proxy"; // Default to [/usr/bin/] for Linux distro builds. #[cfg(target_os = "linux")] @@ -51,8 +55,14 @@ pub const DEFAULT_P2POOL_PATH: &str = "p2pool/p2pool"; #[cfg(not(feature = "distro"))] pub const DEFAULT_XMRIG_PATH: &str = "xmrig/xmrig"; #[cfg(target_os = "linux")] +#[cfg(not(feature = "distro"))] +pub const DEFAULT_XMRIG_PROXY_PATH: &str = "xmrig/xmrig-proxy"; +#[cfg(target_os = "linux")] #[cfg(feature = "distro")] pub const DEFAULT_P2POOL_PATH: &str = "/usr/bin/p2pool"; #[cfg(target_os = "linux")] #[cfg(feature = "distro")] pub const DEFAULT_XMRIG_PATH: &str = "/usr/bin/xmrig"; +#[cfg(target_os = "linux")] +#[cfg(feature = "distro")] +pub const DEFAULT_XMRIG_PROXY_PATH: &str = "/usr/bin/xmrig-proxy"; diff --git a/src/disk/state.rs b/src/disk/state.rs index a1aeffe..f70361e 100644 --- a/src/disk/state.rs +++ b/src/disk/state.rs @@ -20,6 +20,7 @@ impl State { p2pool: P2pool::default(), xmrig: Xmrig::with_threads(max_threads, current_threads), xvb: Xvb::default(), + xmrig_proxy: XmrigProxy::default(), version: arc_mut!(Version::default()), } } @@ -27,6 +28,7 @@ impl State { pub fn update_absolute_path(&mut self) -> Result<(), TomlError> { self.gupax.absolute_p2pool_path = into_absolute_path(self.gupax.p2pool_path.clone())?; self.gupax.absolute_xmrig_path = into_absolute_path(self.gupax.xmrig_path.clone())?; + self.gupax.absolute_xp_path = into_absolute_path(self.gupax.xmrig_proxy_path.clone())?; Ok(()) } @@ -108,6 +110,7 @@ impl State { // Convert path to absolute self.gupax.absolute_p2pool_path = into_absolute_path(self.gupax.p2pool_path.clone())?; self.gupax.absolute_xmrig_path = into_absolute_path(self.gupax.xmrig_path.clone())?; + self.gupax.absolute_xp_path = into_absolute_path(self.gupax.xmrig_proxy_path.clone())?; let string = match toml::ser::to_string(&self) { Ok(string) => { info!("State | Parse ... OK"); @@ -158,6 +161,7 @@ pub struct State { pub gupax: Gupax, pub p2pool: P2pool, pub xmrig: Xmrig, + pub xmrig_proxy: XmrigProxy, pub xvb: Xvb, pub version: Arc>, } @@ -178,14 +182,17 @@ pub struct Gupax { pub auto_update: bool, pub auto_p2pool: bool, pub auto_xmrig: bool, + pub auto_xp: bool, pub auto_xvb: bool, // pub auto_monero: bool, pub ask_before_quit: bool, pub save_before_quit: bool, pub p2pool_path: String, pub xmrig_path: String, + pub xmrig_proxy_path: String, pub absolute_p2pool_path: PathBuf, pub absolute_xmrig_path: PathBuf, + pub absolute_xp_path: PathBuf, pub selected_width: u16, pub selected_height: u16, pub selected_scale: f32, @@ -243,6 +250,64 @@ pub struct Xmrig { pub token: String, } +// present for future. +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct XmrigProxy { + pub simple: bool, + pub arguments: String, + pub simple_rig: String, + pub tls: bool, + pub keepalive: bool, + pub address: String, + pub name: String, + pub rig: String, + pub ip: String, + pub port: String, + pub api_ip: String, + pub api_port: String, + pub p2pool_ip: String, + pub p2pool_port: String, + pub selected_index: usize, + pub selected_name: String, + pub selected_rig: String, + pub selected_ip: String, + pub selected_port: String, + pub token: String, + pub redirect_local_xmrig: bool, +} + +impl Default for XmrigProxy { + fn default() -> Self { + XmrigProxy { + simple: true, + arguments: Default::default(), + token: thread_rng() + .sample_iter(Alphanumeric) + .take(16) + .map(char::from) + .collect(), + redirect_local_xmrig: true, + address: String::with_capacity(96), + name: "Local P2Pool".to_string(), + rig: GUPAX_VERSION_UNDERSCORE.to_string(), + simple_rig: String::with_capacity(30), + ip: "0.0.0.0".to_string(), + port: "3355".to_string(), + p2pool_ip: "localhost".to_string(), + p2pool_port: "3333".to_string(), + selected_index: 0, + selected_name: "Local P2Pool".to_string(), + selected_ip: "localhost".to_string(), + selected_rig: GUPAX_VERSION_UNDERSCORE.to_string(), + selected_port: "3333".to_string(), + api_ip: "localhost".to_string(), + api_port: "18089".to_string(), + tls: false, + keepalive: false, + } + } +} + #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, Default)] pub struct Xvb { pub token: String, @@ -277,13 +342,16 @@ impl Default for Gupax { auto_update: false, auto_p2pool: false, auto_xmrig: false, + auto_xp: false, auto_xvb: false, ask_before_quit: true, save_before_quit: true, p2pool_path: DEFAULT_P2POOL_PATH.to_string(), xmrig_path: DEFAULT_XMRIG_PATH.to_string(), + xmrig_proxy_path: DEFAULT_XMRIG_PROXY_PATH.to_string(), absolute_p2pool_path: into_absolute_path(DEFAULT_P2POOL_PATH.to_string()).unwrap(), absolute_xmrig_path: into_absolute_path(DEFAULT_XMRIG_PATH.to_string()).unwrap(), + absolute_xp_path: into_absolute_path(DEFAULT_XMRIG_PROXY_PATH.to_string()).unwrap(), selected_width: APP_DEFAULT_WIDTH as u16, selected_height: APP_DEFAULT_HEIGHT as u16, selected_scale: APP_DEFAULT_SCALE, diff --git a/src/disk/tests.rs b/src/disk/tests.rs index 6d7a8c2..65aa5f0 100644 --- a/src/disk/tests.rs +++ b/src/disk/tests.rs @@ -32,12 +32,15 @@ mod test { auto_p2pool = false auto_xmrig = false auto_xvb = false + auto_xp = false ask_before_quit = true save_before_quit = true p2pool_path = "p2pool/p2pool" xmrig_path = "xmrig/xmrig" + xmrig_proxy_path = "xmrig-proxy/xmrig-proxy" absolute_p2pool_path = "/home/hinto/p2pool/p2pool" absolute_xmrig_path = "/home/hinto/xmrig/xmrig" + absolute_xp_path = "/home/hinto/xmrig/xmrig-proxy/xmrig-proxy" selected_width = 1280 selected_height = 960 selected_scale = 0.0 @@ -98,10 +101,34 @@ mod test { selected_port = "3333" token = "testtoken" + [xmrig_proxy] + simple = true + arguments = "" + address = "" + simple_rig = "" + tls = false + name = "linux" + rig = "Gupaxx" + keepalive = false + ip = "localhost" + port = "30948" + api_ip = "localhost" + api_port = "18088" + p2pool_ip = "localhost" + p2pool_port = "18088" + token = "testtoken" + selected_index = 1 + selected_name = "linux" + selected_rig = "Gupaxx" + selected_ip = "192.168.1.122" + selected_port = "3333" + redirect_local_xmrig = true + [xvb] token = "" hero = false node = "Europe" + [version] gupax = "v1.3.0" p2pool = "v2.5" diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 746d72b..b2cda7f 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -34,12 +34,16 @@ // piping their stdout/stderr/stdin, accessing their APIs (HTTP + disk files), etc. //---------------------------------------------------------------------------------------------------- Import +use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi; use crate::helper::{ p2pool::{ImgP2pool, PubP2poolApi}, - xmrig::{ImgXmrig, PubXmrigApi}, + xrig::{xmrig::ImgXmrig, xmrig::PubXmrigApi}, }; use crate::{constants::*, disk::gupax_p2pool_api::GupaxP2poolApi, human::*, macros::*}; use log::*; +use portable_pty::Child; +use readable::up::Uptime; +use std::fmt::Write; use std::path::Path; use std::{ path::PathBuf, @@ -51,7 +55,7 @@ use std::{ use self::xvb::{nodes::XvbNode, PubXvbApi}; pub mod p2pool; pub mod tests; -pub mod xmrig; +pub mod xrig; pub mod xvb; //---------------------------------------------------------------------------------------------------- Constants @@ -73,15 +77,18 @@ pub struct Helper { pub pub_sys: Arc>, // The public API for [sysinfo] that the [Status] tab reads from pub p2pool: Arc>, // P2Pool process state pub xmrig: Arc>, // XMRig process state + pub xmrig_proxy: Arc>, // XMRig process state pub xvb: Arc>, // XvB process state pub gui_api_p2pool: Arc>, // P2Pool API state (for GUI thread) pub gui_api_xmrig: Arc>, // XMRig API state (for GUI thread) + pub gui_api_xp: Arc>, // XMRig-Proxy API state (for GUI thread) pub gui_api_xvb: Arc>, // XMRig API state (for GUI thread) pub img_p2pool: Arc>, // A static "image" of the data P2Pool started with pub img_xmrig: Arc>, // A static "image" of the data XMRig started with pub_api_p2pool: Arc>, // P2Pool API state (for Helper/P2Pool thread) pub_api_xmrig: Arc>, // XMRig API state (for Helper/XMRig thread) - pub_api_xvb: Arc>, // XvB API state (for Helper/XvB thread) + pub_api_xp: Arc>, // XMRig-Proxy API state (for Helper/XMRig-Proxy thread) + pub_api_xvb: Arc>, // XvB API state (for Helper/XvB thread) pub gupax_p2pool_api: Arc>, // } @@ -242,6 +249,7 @@ impl Default for ProcessSignal { pub enum ProcessName { P2pool, Xmrig, + XmrigProxy, Xvb, } @@ -260,6 +268,7 @@ impl std::fmt::Display for ProcessName { match *self { ProcessName::P2pool => write!(f, "P2Pool"), ProcessName::Xmrig => write!(f, "XMRig"), + ProcessName::XmrigProxy => write!(f, "XMRig-Proxy"), ProcessName::Xvb => write!(f, "XvB"), } } @@ -274,10 +283,12 @@ impl Helper { pub_sys: Arc>, p2pool: Arc>, xmrig: Arc>, + xmrig_proxy: Arc>, xvb: Arc>, gui_api_p2pool: Arc>, gui_api_xmrig: Arc>, gui_api_xvb: Arc>, + gui_api_xp: Arc>, img_p2pool: Arc>, img_xmrig: Arc>, gupax_p2pool_api: Arc>, @@ -288,14 +299,17 @@ impl Helper { uptime: HumanTime::into_human(instant.elapsed()), pub_api_p2pool: arc_mut!(PubP2poolApi::new()), pub_api_xmrig: arc_mut!(PubXmrigApi::new()), + pub_api_xp: arc_mut!(PubXmrigProxyApi::new()), pub_api_xvb: arc_mut!(PubXvbApi::new()), // These are created when initializing [App], since it needs a handle to it as well p2pool, xmrig, + xmrig_proxy, xvb, gui_api_p2pool, gui_api_xmrig, gui_api_xvb, + gui_api_xp, img_p2pool, img_xmrig, gupax_p2pool_api, @@ -407,13 +421,16 @@ impl Helper { let lock = lock!(helper); let p2pool = Arc::clone(&lock.p2pool); let xmrig = Arc::clone(&lock.xmrig); + let xmrig_proxy = Arc::clone(&lock.xmrig_proxy); let xvb = Arc::clone(&lock.xvb); let pub_sys = Arc::clone(&lock.pub_sys); let gui_api_p2pool = Arc::clone(&lock.gui_api_p2pool); let gui_api_xmrig = Arc::clone(&lock.gui_api_xmrig); + let gui_api_xp = Arc::clone(&lock.gui_api_xp); let gui_api_xvb = Arc::clone(&lock.gui_api_xvb); let pub_api_p2pool = Arc::clone(&lock.pub_api_p2pool); let pub_api_xmrig = Arc::clone(&lock.pub_api_xmrig); + let pub_api_xp = Arc::clone(&lock.pub_api_xp); let pub_api_xvb = Arc::clone(&lock.pub_api_xvb); drop(lock); @@ -434,27 +451,33 @@ impl Helper { // 2. Lock... EVERYTHING! let mut lock = lock!(helper); - debug!("Helper | Locking (1/11) ... [helper]"); + debug!("Helper | Locking (1/12) ... [helper]"); let p2pool = lock!(p2pool); - debug!("Helper | Locking (2/11) ... [p2pool]"); + debug!("Helper | Locking (2/12) ... [p2pool]"); let xmrig = lock!(xmrig); - debug!("Helper | Locking (3/11) ... [xmrig]"); + debug!("Helper | Locking (3/12) ... [xmrig]"); + let xmrig_proxy = lock!(xmrig_proxy); + debug!("Helper | Locking (3/12) ... [xmrig_proxy]"); let xvb = lock!(xvb); - debug!("Helper | Locking (4/11) ... [xvb]"); + debug!("Helper | Locking (4/12) ... [xvb]"); let mut lock_pub_sys = lock!(pub_sys); - debug!("Helper | Locking (5/11) ... [pub_sys]"); + debug!("Helper | Locking (5/12) ... [pub_sys]"); let mut gui_api_p2pool = lock!(gui_api_p2pool); - debug!("Helper | Locking (6/11) ... [gui_api_p2pool]"); + debug!("Helper | Locking (6/12) ... [gui_api_p2pool]"); let mut gui_api_xmrig = lock!(gui_api_xmrig); - debug!("Helper | Locking (7/11) ... [gui_api_xmrig]"); + debug!("Helper | Locking (7/12) ... [gui_api_xmrig]"); + let mut gui_api_xp = lock!(gui_api_xp); + debug!("Helper | Locking (7/12) ... [gui_api_xp]"); let mut gui_api_xvb = lock!(gui_api_xvb); - debug!("Helper | Locking (8/11) ... [gui_api_xvb]"); + debug!("Helper | Locking (8/12) ... [gui_api_xvb]"); let mut pub_api_p2pool = lock!(pub_api_p2pool); - debug!("Helper | Locking (9/11) ... [pub_api_p2pool]"); + debug!("Helper | Locking (9/12) ... [pub_api_p2pool]"); let mut pub_api_xmrig = lock!(pub_api_xmrig); - debug!("Helper | Locking (10/11) ... [pub_api_xmrig]"); + debug!("Helper | Locking (10/12) ... [pub_api_xmrig]"); + let mut pub_api_xp = lock!(pub_api_xp); + debug!("Helper | Locking (11/12) ... [pub_api_xp]"); let mut pub_api_xvb = lock!(pub_api_xvb); - debug!("Helper | Locking (11/11) ... [pub_api_xvb]"); + debug!("Helper | Locking (12/12) ... [pub_api_xvb]"); // Calculate Gupax's uptime always. lock.uptime = HumanTime::into_human(lock.instant.elapsed()); // If [P2Pool] is alive... @@ -471,6 +494,13 @@ impl Helper { } else { debug!("Helper | XMRig is dead! Skipping..."); } + // If [XMRig-Proxy] is alive... + if xmrig_proxy.is_alive() { + debug!("Helper | XMRig-Proxy is alive! Running [combine_gui_pub_api()]"); + PubXmrigProxyApi::combine_gui_pub_api(&mut gui_api_xp, &mut pub_api_xp); + } else { + debug!("Helper | XMRig-Proxy is dead! Skipping..."); + } // If [XvB] is alive... if xvb.is_alive() { debug!("Helper | XvB is alive! Running [combine_gui_pub_api()]"); @@ -497,27 +527,33 @@ impl Helper { // 3. Drop... (almost) EVERYTHING... IN REVERSE! drop(lock_pub_sys); - debug!("Helper | Unlocking (1/11) ... [pub_sys]"); + debug!("Helper | Unlocking (1/12) ... [pub_sys]"); drop(xvb); - debug!("Helper | Unlocking (2/11) ... [xvb]"); + debug!("Helper | Unlocking (2/12) ... [xvb]"); + drop(xmrig_proxy); + debug!("Helper | Unlocking (3/12) ... [xmrig_proxy]"); drop(xmrig); - debug!("Helper | Unlocking (3/11) ... [xmrig]"); + debug!("Helper | Unlocking (3/12) ... [xmrig]"); drop(p2pool); - debug!("Helper | Unlocking (4/11) ... [p2pool]"); + debug!("Helper | Unlocking (4/12) ... [p2pool]"); drop(pub_api_xvb); - debug!("Helper | Unlocking (5/11) ... [pub_api_xvb]"); + debug!("Helper | Unlocking (5/12) ... [pub_api_xvb]"); + drop(pub_api_xp); + debug!("Helper | Unlocking (6/12) ... [pub_api_xp]"); drop(pub_api_xmrig); - debug!("Helper | Unlocking (6/11) ... [pub_api_xmrig]"); + debug!("Helper | Unlocking (6/12) ... [pub_api_xmrig]"); drop(pub_api_p2pool); - debug!("Helper | Unlocking (7/11) ... [pub_api_p2pool]"); + debug!("Helper | Unlocking (7/12) ... [pub_api_p2pool]"); drop(gui_api_xvb); - debug!("Helper | Unlocking (8/11) ... [gui_api_xvb]"); + debug!("Helper | Unlocking (8/12) ... [gui_api_xvb]"); + drop(gui_api_xp); + debug!("Helper | Unlocking (9/12) ... [gui_api_xp]"); drop(gui_api_xmrig); - debug!("Helper | Unlocking (9/11) ... [gui_api_xmrig]"); + debug!("Helper | Unlocking (10/12) ... [gui_api_xmrig]"); drop(gui_api_p2pool); - debug!("Helper | Unlocking (10/11) ... [gui_api_p2pool]"); + debug!("Helper | Unlocking (11/12) ... [gui_api_p2pool]"); drop(lock); - debug!("Helper | Unlocking (11/11) ... [helper]"); + debug!("Helper | Unlocking (12/12) ... [helper]"); // 4. Calculate if we should sleep or not. // If we should sleep, how long? @@ -537,3 +573,200 @@ impl Helper { }); } } + +// common functions inside watchdog thread +fn check_died( + child_pty: &Arc>>, + process: &mut Process, + start: &Instant, + gui_api_output_raw: &mut String, +) -> bool { + // Check if the process secretly died without us knowing :) + if let Ok(Some(code)) = lock!(child_pty).try_wait() { + debug!( + "{} Watchdog | Process secretly died on us! Getting exit status...", + process.name + ); + let exit_status = match code.success() { + true => { + process.state = ProcessState::Dead; + "Successful" + } + false => { + process.state = ProcessState::Failed; + "Failed" + } + }; + let uptime = Uptime::from(start.elapsed()); + info!( + "{} | Stopped ... Uptime was: [{}], Exit status: [{}]", + process.name, uptime, exit_status + ); + if let Err(e) = writeln!( + *gui_api_output_raw, + "{}\n{} stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", + process.name, HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE + ) { + error!( + "{} Watchdog | GUI Uptime/Exit status write failed: {}", + process.name, e + ); + } + process.signal = ProcessSignal::None; + debug!( + "{} Watchdog | Secret dead process reap OK, breaking", + process.name + ); + return true; + } + false +} +fn check_user_input(process: &Arc>, stdin: &mut Box) { + let mut lock = lock!(process); + if !lock.input.is_empty() { + let input = std::mem::take(&mut lock.input); + for line in input { + if line.is_empty() { + continue; + } + debug!( + "{} Watchdog | User input not empty, writing to STDIN: [{}]", + lock.name, line + ); + #[cfg(target_os = "windows")] + if let Err(e) = write!(stdin, "{}\r\n", line) { + error!("{} Watchdog | STDIN error: {}", lock.name, e); + } + #[cfg(target_family = "unix")] + if let Err(e) = writeln!(stdin, "{}", line) { + error!("{} Watchdog | STDIN error: {}", lock.name, e); + } + // Flush. + if let Err(e) = stdin.flush() { + error!("{} Watchdog | STDIN flush error: {}", lock.name, e); + } + } + } +} +fn signal_end( + process: &Arc>, + child_pty: &Arc>>, + start: &Instant, + gui_api_output_raw: &mut String, +) -> bool { + if lock!(process).signal == ProcessSignal::Stop { + debug!("{} Watchdog | Stop SIGNAL caught", lock!(process).name); + // This actually sends a SIGHUP to p2pool (closes the PTY, hangs up on p2pool) + if let Err(e) = lock!(child_pty).kill() { + error!("{} Watchdog | Kill error: {}", lock!(process).name, e); + } + // Wait to get the exit status + let exit_status = match lock!(child_pty).wait() { + Ok(e) => { + if e.success() { + lock!(process).state = ProcessState::Dead; + "Successful" + } else { + lock!(process).state = ProcessState::Failed; + "Failed" + } + } + _ => { + lock!(process).state = ProcessState::Failed; + "Unknown Error" + } + }; + let uptime = HumanTime::into_human(start.elapsed()); + info!( + "{} Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", + lock!(process).name, + uptime, + exit_status + ); + // This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it. + if let Err(e) = writeln!( + gui_api_output_raw, + "{}\n{} stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", + lock!(process).name, + HORI_CONSOLE, + uptime, + exit_status, + HORI_CONSOLE + ) { + error!( + "{} Watchdog | GUI Uptime/Exit status write failed: {}", + lock!(process).name, + e + ); + } + lock!(process).signal = ProcessSignal::None; + debug!( + "{} Watchdog | Stop SIGNAL done, breaking", + lock!(process).name, + ); + return true; + // Check RESTART + } else if lock!(process).signal == ProcessSignal::Restart { + debug!("{} Watchdog | Restart SIGNAL caught", lock!(process).name,); + // This actually sends a SIGHUP to p2pool (closes the PTY, hangs up on p2pool) + if let Err(e) = lock!(child_pty).kill() { + error!("{} Watchdog | Kill error: {}", lock!(process).name, e); + } + // Wait to get the exit status + let exit_status = match lock!(child_pty).wait() { + Ok(e) => { + if e.success() { + "Successful" + } else { + "Failed" + } + } + _ => "Unknown Error", + }; + let uptime = HumanTime::into_human(start.elapsed()); + info!( + "{} Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", + lock!(process).name, + uptime, + exit_status + ); + // This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it. + if let Err(e) = writeln!( + gui_api_output_raw, + "{}\n{} stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", + lock!(process).name, + HORI_CONSOLE, + uptime, + exit_status, + HORI_CONSOLE + ) { + error!( + "{} Watchdog | GUI Uptime/Exit status write failed: {}", + lock!(process).name, + e + ); + } + lock!(process).state = ProcessState::Waiting; + debug!( + "{} Watchdog | Restart SIGNAL done, breaking", + lock!(process).name, + ); + return true; + } + false +} +async fn sleep_end_loop(now: Instant, name: ProcessName) { + // Sleep (only if 999ms hasn't passed) + let elapsed = now.elapsed().as_millis(); + // Since logic goes off if less than 1000, casting should be safe + if elapsed < 999 { + let sleep = (999 - elapsed) as u64; + debug!( + "{} Watchdog | END OF LOOP - Sleeping for [{}]ms...", + name, sleep + ); + tokio::time::sleep(Duration::from_millis(sleep)).await; + } else { + debug!("{} Watchdog | END OF LOOP - Not sleeping!", name); + } +} diff --git a/src/helper/p2pool.rs b/src/helper/p2pool.rs index 7a29e06..1b03d93 100644 --- a/src/helper/p2pool.rs +++ b/src/helper/p2pool.rs @@ -2,6 +2,10 @@ use super::Helper; use super::Process; use crate::components::node::RemoteNode; use crate::disk::state::P2pool; +use crate::helper::check_died; +use crate::helper::check_user_input; +use crate::helper::signal_end; +use crate::helper::sleep_end_loop; use crate::helper::ProcessName; use crate::helper::ProcessSignal; use crate::helper::ProcessState; @@ -213,7 +217,6 @@ impl Helper { ); }); } - // Takes in a 95-char Monero address, returns the first and last // 8 characters separated with dots like so: [4abcdefg...abcdefgh] pub fn head_tail_of_monero_address(address: &str) -> String { @@ -394,6 +397,7 @@ impl Helper { #[cold] #[inline(never)] // The P2Pool watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works. + // or if P2Pool simple is false and extern is true, only prints data from stratum api. #[allow(clippy::too_many_arguments)] #[allow(clippy::await_holding_lock)] #[tokio::main] @@ -496,165 +500,21 @@ impl Helper { lock!(gui_api).tick_status += 1; // Check if the process is secretly died without us knowing :) - if let Ok(Some(code)) = lock!(child_pty).try_wait() { - debug!("P2Pool Watchdog | Process secretly died! Getting exit status"); - let exit_status = match code.success() { - true => { - lock!(process).state = ProcessState::Dead; - "Successful" - } - false => { - lock!(process).state = ProcessState::Failed; - "Failed" - } - }; - let uptime = HumanTime::into_human(start.elapsed()); - info!( - "P2Pool Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", - uptime, exit_status - ); - // This is written directly into the GUI, because sometimes the 900ms event loop can't catch it. - if let Err(e) = writeln!( - lock!(gui_api).output, - "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", - HORI_CONSOLE, - uptime, - exit_status, - HORI_CONSOLE - ) { - error!( - "P2Pool Watchdog | GUI Uptime/Exit status write failed: {}", - e - ); - } - lock!(process).signal = ProcessSignal::None; - debug!("P2Pool Watchdog | Secret dead process reap OK, breaking"); + if check_died( + &child_pty, + &mut lock!(process), + &start, + &mut lock!(gui_api).output, + ) { break; } // Check SIGNAL - if lock!(process).signal == ProcessSignal::Stop { - debug!("P2Pool Watchdog | Stop SIGNAL caught"); - // This actually sends a SIGHUP to p2pool (closes the PTY, hangs up on p2pool) - if let Err(e) = lock!(child_pty).kill() { - error!("P2Pool Watchdog | Kill error: {}", e); - } - // Wait to get the exit status - let exit_status = match lock!(child_pty).wait() { - Ok(e) => { - if e.success() { - lock!(process).state = ProcessState::Dead; - "Successful" - } else { - lock!(process).state = ProcessState::Failed; - "Failed" - } - } - _ => { - lock!(process).state = ProcessState::Failed; - "Unknown Error" - } - }; - let uptime = HumanTime::into_human(start.elapsed()); - info!( - "P2Pool Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", - uptime, exit_status - ); - // This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it. - if let Err(e) = writeln!( - lock!(gui_api).output, - "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", - HORI_CONSOLE, - uptime, - exit_status, - HORI_CONSOLE - ) { - error!( - "P2Pool Watchdog | GUI Uptime/Exit status write failed: {}", - e - ); - } - lock!(process).signal = ProcessSignal::None; - debug!("P2Pool Watchdog | Stop SIGNAL done, breaking"); - break; - // Check RESTART - } else if lock!(process).signal == ProcessSignal::Restart { - debug!("P2Pool Watchdog | Restart SIGNAL caught"); - // This actually sends a SIGHUP to p2pool (closes the PTY, hangs up on p2pool) - if let Err(e) = lock!(child_pty).kill() { - error!("P2Pool Watchdog | Kill error: {}", e); - } - // Wait to get the exit status - let exit_status = match lock!(child_pty).wait() { - Ok(e) => { - if e.success() { - "Successful" - } else { - "Failed" - } - } - _ => "Unknown Error", - }; - let uptime = HumanTime::into_human(start.elapsed()); - info!( - "P2Pool Watchdog | Stopped ... Uptime was: [{}], Exit status: [{}]", - uptime, exit_status - ); - // This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it. - if let Err(e) = writeln!( - lock!(gui_api).output, - "{}\nP2Pool stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", - HORI_CONSOLE, - uptime, - exit_status, - HORI_CONSOLE - ) { - error!( - "P2Pool Watchdog | GUI Uptime/Exit status write failed: {}", - e - ); - } - lock!(process).state = ProcessState::Waiting; - debug!("P2Pool Watchdog | Restart SIGNAL done, breaking"); + if signal_end(&process, &child_pty, &start, &mut lock!(gui_api).output) { break; } - // Check vector of user input - let mut lock = lock!(process); - if !lock.input.is_empty() { - let input = std::mem::take(&mut lock.input); - for line in input { - if line.is_empty() { - continue; - } - debug!( - "P2Pool Watchdog | User input not empty, writing to STDIN: [{}]", - line - ); - // Windows terminals (or at least the PTY abstraction I'm using, portable_pty) - // requires a [\r\n] to end a line, whereas Unix is okay with just a [\n]. - // - // I have literally read all of [portable_pty]'s source code, dug into Win32 APIs, - // even rewrote some of the actual PTY code in order to understand why STDIN doesn't work - // on Windows. It's because of a fucking missing [\r]. Another reason to hate Windows :D - // - // XMRig did actually work before though, since it reads STDIN directly without needing a newline. - #[cfg(target_os = "windows")] - if let Err(e) = write!(stdin, "{}\r\n", line) { - error!("P2Pool Watchdog | STDIN error: {}", e); - } - #[cfg(target_family = "unix")] - if let Err(e) = writeln!(stdin, "{}", line) { - error!("P2Pool Watchdog | STDIN error: {}", e); - } - // Flush. - if let Err(e) = stdin.flush() { - error!("P2Pool Watchdog | STDIN flush error: {}", e); - } - } - } - drop(lock); - + check_user_input(&process, &mut stdin); // Check if logs need resetting debug!("P2Pool Watchdog | Attempting GUI log reset check"); let mut lock = lock!(gui_api); @@ -716,25 +576,14 @@ impl Helper { } // Sleep (only if 900ms hasn't passed) - let elapsed = now.elapsed().as_millis(); if first_loop { first_loop = false; } - // Since logic goes off if less than 1000, casting should be safe - if elapsed < 900 { - let sleep = (900 - elapsed) as u64; - debug!( - "P2Pool Watchdog | END OF LOOP - Tick: [{}/60] - Sleeping for [{}]ms...", - lock!(gui_api).tick, - sleep - ); - tokio::time::sleep(Duration::from_millis(sleep)).await; - } else { - debug!( - "P2Pool Watchdog | END OF LOOP - Tick: [{}/60] Not sleeping!", - lock!(gui_api).tick - ); - } + sleep_end_loop(now, ProcessName::P2pool).await; + debug!( + "P2Pool Watchdog | END OF LOOP - Tick: [{}/60]", + lock!(gui_api).tick, + ); } // 5. If loop broke, we must be done here. diff --git a/src/helper/tests.rs b/src/helper/tests.rs index db76429..ef2e6a1 100644 --- a/src/helper/tests.rs +++ b/src/helper/tests.rs @@ -472,7 +472,7 @@ Uptime = 0h 2m 4s }, "hugepages": true }"#; - use crate::helper::xmrig::PrivXmrigApi; + use crate::helper::xrig::xmrig::PrivXmrigApi; let priv_api = serde_json::from_str::(data).unwrap(); let json = serde_json::ser::to_string_pretty(&priv_api).unwrap(); println!("{}", json); @@ -509,7 +509,7 @@ Uptime = 0h 2m 4s use crate::{ disk::state::P2pool, - helper::{p2pool::PubP2poolApi, xmrig::PubXmrigApi, xvb::rounds::XvbRound}, + helper::{p2pool::PubP2poolApi, xrig::xmrig::PubXmrigApi, xvb::rounds::XvbRound}, macros::lock, XVB_TIME_ALGO, }; diff --git a/src/helper/xrig/mod.rs b/src/helper/xrig/mod.rs new file mode 100644 index 0000000..9121034 --- /dev/null +++ b/src/helper/xrig/mod.rs @@ -0,0 +1,63 @@ +use crate::helper::XvbNode; +use anyhow::anyhow; +use anyhow::Result; +use log::info; +use reqwest::header::AUTHORIZATION; +use reqwest::Client; +use serde::Deserialize; +use serde::Serialize; +use serde_json::Value; + +pub mod xmrig; +pub mod xmrig_proxy; + +// update config of xmrig or xmrig-proxy +pub async fn update_xmrig_config( + client: &Client, + api_uri: &str, + token: &str, + node: &XvbNode, + address: &str, + rig: &str, +) -> Result<()> { + // get config + let request = client + .get(api_uri) + .header(AUTHORIZATION, ["Bearer ", token].concat()); + let mut config = request.send().await?.json::().await?; + // modify node configuration + let uri = [node.url(), ":".to_string(), node.port()].concat(); + info!("replace xmrig config with node {}", uri); + *config + .pointer_mut("/pools/0/url") + .ok_or_else(|| anyhow!("pools/0/url does not exist in xmrig config"))? = uri.into(); + *config + .pointer_mut("/pools/0/user") + .ok_or_else(|| anyhow!("pools/0/user does not exist in xmrig config"))? = node + .user(&address.chars().take(8).collect::()) + .into(); + *config + .pointer_mut("/pools/0/rig-id") + .ok_or_else(|| anyhow!("pools/0/rig-id does not exist in xmrig config"))? = rig.into(); + *config + .pointer_mut("/pools/0/tls") + .ok_or_else(|| anyhow!("pools/0/tls does not exist in xmrig config"))? = node.tls().into(); + *config + .pointer_mut("/pools/0/keepalive") + .ok_or_else(|| anyhow!("pools/0/keepalive does not exist in xmrig config"))? = + node.keepalive().into(); + // send new config + client + .put(api_uri) + .header("Authorization", ["Bearer ", token].concat()) + .header("Content-Type", "application/json") + .timeout(std::time::Duration::from_secs(5)) + .body(config.to_string()) + .send() + .await?; + anyhow::Ok(()) +} +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +struct Hashrate { + total: [Option; 3], +} diff --git a/src/helper/xmrig.rs b/src/helper/xrig/xmrig.rs similarity index 72% rename from src/helper/xmrig.rs rename to src/helper/xrig/xmrig.rs index 48688c8..0dd1734 100644 --- a/src/helper/xmrig.rs +++ b/src/helper/xrig/xmrig.rs @@ -1,17 +1,18 @@ -use crate::helper::{ProcessName, ProcessSignal, ProcessState}; +use crate::helper::{check_died, check_user_input, sleep_end_loop, Process}; +use crate::helper::{Helper, ProcessName, ProcessSignal, ProcessState}; +use crate::helper::{PubXvbApi, XvbNode}; use crate::regex::{contains_error, contains_usepool, detect_new_node_xmrig, XMRIG_REGEX}; use crate::utils::human::HumanNumber; use crate::utils::sudo::SudoState; use crate::{constants::*, macros::*}; -use anyhow::{anyhow, Result}; use enclose::enclose; use log::*; +use portable_pty::Child; use readable::num::Unsigned; use readable::up::Uptime; use reqwest::header::AUTHORIZATION; use reqwest::Client; use serde::{Deserialize, Serialize}; -use serde_json::Value; use std::path::Path; use std::{ fmt::Write, @@ -23,17 +24,17 @@ use std::{ }; use tokio::spawn; -use super::xvb::nodes::XvbNode; -use super::xvb::PubXvbApi; -use super::{Helper, Process}; +use super::Hashrate; + impl Helper { #[cold] #[inline(never)] - async fn read_pty_xmrig( + pub async fn read_pty_xmrig( output_parse: Arc>, output_pub: Arc>, reader: Box, process_xvb: Arc>, + process_xp: Arc>, pub_api_xvb: &Arc>, ) { use std::io::BufRead; @@ -59,27 +60,30 @@ impl Helper { while let Some(Ok(line)) = stdout.next() { // need to verify if node still working // for that need to catch "connect error" - if contains_error(&line) { - let current_node = lock!(pub_api_xvb).current_node; - if let Some(current_node) = current_node { - // updating current node to None, will stop sending signal of FailedNode until new node is set - // send signal to update node. - warn!("XMRig PTY Parse | node is offline, sending signal to update nodes."); - lock!(process_xvb).signal = ProcessSignal::UpdateNodes(current_node); - lock!(pub_api_xvb).current_node = None; + // only check if xvb process is used and xmrig-proxy is not. + if lock!(process_xvb).is_alive() && !lock!(process_xp).is_alive() { + if contains_error(&line) { + let current_node = lock!(pub_api_xvb).current_node; + if let Some(current_node) = current_node { + // updating current node to None, will stop sending signal of FailedNode until new node is set + // send signal to update node. + warn!("XMRig PTY Parse | node is offline, sending signal to update nodes."); + lock!(process_xvb).signal = ProcessSignal::UpdateNodes(current_node); + lock!(pub_api_xvb).current_node = None; + } } - } - if contains_usepool(&line) { - info!("XMRig PTY Parse | new pool detected"); - // need to update current node because it was updated. - // if custom node made by user, it is not supported because algo is deciding which node to use. - let node = detect_new_node_xmrig(&line); - if node.is_none() { - error!("XMRig PTY Parse | node is not understood, switching to backup."); - // update with default will choose which XvB to prefer. Will update XvB to use p2pool. - lock!(process_xvb).signal = ProcessSignal::UpdateNodes(XvbNode::default()); + if contains_usepool(&line) { + info!("XMRig PTY Parse | new pool detected"); + // need to update current node because it was updated. + // if custom node made by user, it is not supported because algo is deciding which node to use. + let node = detect_new_node_xmrig(&line); + if node.is_none() { + error!("XMRig PTY Parse | node is not understood, switching to backup."); + // update with default will choose which XvB to prefer. Will update XvB to use p2pool. + lock!(process_xvb).signal = ProcessSignal::UpdateNodes(XvbNode::default()); + } + lock!(pub_api_xvb).current_node = node; } - lock!(pub_api_xvb).current_node = node; } // println!("{}", line); // For debugging. if let Err(e) = writeln!(lock!(output_parse), "{}", line) { @@ -178,6 +182,7 @@ impl Helper { let gui_api = Arc::clone(&lock!(helper).gui_api_xmrig); let pub_api = Arc::clone(&lock!(helper).pub_api_xmrig); let process_xvb = Arc::clone(&lock!(helper).xvb); + let process_xp = Arc::clone(&lock!(helper).xmrig_proxy); let path = path.to_path_buf(); let token = state.token.clone(); let img_xmrig = Arc::clone(&lock!(helper).img_xmrig); @@ -193,6 +198,7 @@ impl Helper { api_ip_port, &token, process_xvb, + process_xp, &img_xmrig, &pub_api_xvb, ); @@ -374,6 +380,7 @@ impl Helper { mut api_ip_port: String, token: &str, process_xvb: Arc>, + process_xp: Arc>, img_xmrig: &Arc>, pub_api_xvb: &Arc>, ) { @@ -394,7 +401,7 @@ impl Helper { let output_parse = Arc::clone(&lock!(process).output_parse); let output_pub = Arc::clone(&lock!(process).output_pub); spawn(enclose!((pub_api_xvb) async move { - Self::read_pty_xmrig(output_parse, output_pub, reader, process_xvb, &pub_api_xvb).await; + Self::read_pty_xmrig(output_parse, output_pub, reader, process_xvb, process_xp, &pub_api_xvb).await; })); // 1b. Create command debug!("XMRig | Creating command..."); @@ -449,7 +456,7 @@ impl Helper { if !api_ip_port.ends_with('/') { api_ip_port.push('/'); } - "http://".to_owned() + &api_ip_port + XMRIG_API_URI + "http://".to_owned() + &api_ip_port + XMRIG_API_SUMMARY_URI }; info!("XMRig | Final API URI: {}", api_uri); @@ -480,138 +487,26 @@ impl Helper { debug!("XMRig Watchdog | ----------- Start of loop -----------"); // Check if the process secretly died without us knowing :) - if let Ok(Some(code)) = lock!(child_pty).try_wait() { - debug!("XMRig Watchdog | Process secretly died on us! Getting exit status..."); - let exit_status = match code.success() { - true => { - lock!(process).state = ProcessState::Dead; - "Successful" - } - false => { - lock!(process).state = ProcessState::Failed; - "Failed" - } - }; - let uptime = Uptime::from(start.elapsed()); - info!( - "XMRig | Stopped ... Uptime was: [{}], Exit status: [{}]", - uptime, exit_status - ); - if let Err(e) = writeln!( - lock!(gui_api).output, - "{}\nXMRig stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", - HORI_CONSOLE, - uptime, - exit_status, - HORI_CONSOLE - ) { - error!( - "XMRig Watchdog | GUI Uptime/Exit status write failed: {}", - e - ); - } - lock!(process).signal = ProcessSignal::None; - debug!("XMRig Watchdog | Secret dead process reap OK, breaking"); + if check_died( + &child_pty, + &mut lock!(process), + &start, + &mut lock!(gui_api).output, + ) { break; } - // Stop on [Stop/Restart] SIGNAL - let signal = lock!(process).signal; - if signal == ProcessSignal::Stop || signal == ProcessSignal::Restart { - debug!("XMRig Watchdog | Stop/Restart SIGNAL caught"); - // macOS requires [sudo] again to kill [XMRig] - if cfg!(target_os = "macos") { - // If we're at this point, that means the user has - // entered their [sudo] pass again, after we wiped it. - // So, we should be able to find it in our [Arc>]. - Self::sudo_kill(lock!(child_pty).process_id().unwrap(), &sudo); - // And... wipe it again (only if we're stopping full). - // If we're restarting, the next start will wipe it for us. - if signal != ProcessSignal::Restart { - SudoState::wipe(&sudo); - } - } else if let Err(e) = lock!(child_pty).kill() { - error!("XMRig Watchdog | Kill error: {}", e); - } - let exit_status = match lock!(child_pty).wait() { - Ok(e) => { - let mut process = lock!(process); - if e.success() { - if process.signal == ProcessSignal::Stop { - process.state = ProcessState::Dead; - } - "Successful" - } else { - if process.signal == ProcessSignal::Stop { - process.state = ProcessState::Failed; - } - "Failed" - } - } - _ => { - let mut process = lock!(process); - if process.signal == ProcessSignal::Stop { - process.state = ProcessState::Failed; - } - "Unknown Error" - } - }; - let uptime = Uptime::from(start.elapsed()); - info!( - "XMRig | Stopped ... Uptime was: [{}], Exit status: [{}]", - uptime, exit_status - ); - if let Err(e) = writeln!( - lock!(gui_api).output, - "{}\nXMRig stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", - HORI_CONSOLE, - uptime, - exit_status, - HORI_CONSOLE - ) { - error!( - "XMRig Watchdog | GUI Uptime/Exit status write failed: {}", - e - ); - } - let mut process = lock!(process); - match process.signal { - ProcessSignal::Stop => process.signal = ProcessSignal::None, - ProcessSignal::Restart => process.state = ProcessState::Waiting, - _ => (), - } - debug!("XMRig Watchdog | Stop/Restart SIGNAL done, breaking"); + if Self::xmrig_signal_end( + &process, + &child_pty, + &start, + &mut lock!(gui_api).output, + &sudo, + ) { break; } - // Check vector of user input - { - let mut lock = lock!(process); - if !lock.input.is_empty() { - let input = std::mem::take(&mut lock.input); - for line in input { - if line.is_empty() { - continue; - } - debug!( - "XMRig Watchdog | User input not empty, writing to STDIN: [{}]", - line - ); - #[cfg(target_os = "windows")] - if let Err(e) = write!(stdin, "{}\r\n", line) { - error!("XMRig Watchdog | STDIN error: {}", e); - } - #[cfg(target_family = "unix")] - if let Err(e) = writeln!(stdin, "{}", line) { - error!("XMRig Watchdog | STDIN error: {}", e); - } - // Flush. - if let Err(e) = stdin.flush() { - error!("XMRig Watchdog | STDIN flush error: {}", e); - } - } - } - } + check_user_input(&process, &mut stdin); // Check if logs need resetting debug!("XMRig Watchdog | Attempting GUI log reset check"); { @@ -643,23 +538,85 @@ impl Helper { } // Sleep (only if 900ms hasn't passed) - let elapsed = now.elapsed().as_millis(); - // Since logic goes off if less than 1000, casting should be safe - if elapsed < 900 { - let sleep = (900 - elapsed) as u64; - debug!( - "XMRig Watchdog | END OF LOOP - Sleeping for [{}]ms...", - sleep - ); - tokio::time::sleep(Duration::from_millis(sleep)).await; - } else { - debug!("XMRig Watchdog | END OF LOOP - Not sleeping!"); - } + sleep_end_loop(now, ProcessName::Xmrig).await; } // 5. If loop broke, we must be done here. info!("XMRig Watchdog | Watchdog thread exiting... Goodbye!"); } + fn xmrig_signal_end( + process: &Arc>, + child_pty: &Arc>>, + start: &Instant, + gui_api_output_raw: &mut String, + sudo: &Arc>, + ) -> bool { + let signal = lock!(process).signal; + if signal == ProcessSignal::Stop || signal == ProcessSignal::Restart { + debug!("XMRig Watchdog | Stop/Restart SIGNAL caught"); + // macOS requires [sudo] again to kill [XMRig] + if cfg!(target_os = "macos") { + // If we're at this point, that means the user has + // entered their [sudo] pass again, after we wiped it. + // So, we should be able to find it in our [Arc>]. + Self::sudo_kill(lock!(child_pty).process_id().unwrap(), sudo); + // And... wipe it again (only if we're stopping full). + // If we're restarting, the next start will wipe it for us. + if signal != ProcessSignal::Restart { + SudoState::wipe(sudo); + } + } else if let Err(e) = lock!(child_pty).kill() { + error!("XMRig Watchdog | Kill error: {}", e); + } + let exit_status = match lock!(child_pty).wait() { + Ok(e) => { + let mut process = lock!(process); + if e.success() { + if process.signal == ProcessSignal::Stop { + process.state = ProcessState::Dead; + } + "Successful" + } else { + if process.signal == ProcessSignal::Stop { + process.state = ProcessState::Failed; + } + "Failed" + } + } + _ => { + let mut process = lock!(process); + if process.signal == ProcessSignal::Stop { + process.state = ProcessState::Failed; + } + "Unknown Error" + } + }; + let uptime = Uptime::from(start.elapsed()); + info!( + "XMRig | Stopped ... Uptime was: [{}], Exit status: [{}]", + uptime, exit_status + ); + if let Err(e) = writeln!( + gui_api_output_raw, + "{}\nXMRig stopped | Uptime: [{}] | Exit status: [{}]\n{}\n\n\n\n", + HORI_CONSOLE, uptime, exit_status, HORI_CONSOLE + ) { + error!( + "XMRig Watchdog | GUI Uptime/Exit status write failed: {}", + e + ); + } + let mut process = lock!(process); + match process.signal { + ProcessSignal::Stop => process.signal = ProcessSignal::None, + ProcessSignal::Restart => process.state = ProcessState::Waiting, + _ => (), + } + debug!("XMRig Watchdog | Stop/Restart SIGNAL done, breaking"); + return true; + } + false + } } //---------------------------------------------------------------------------------------------------- [ImgXmrig] #[derive(Debug, Clone)] @@ -726,7 +683,7 @@ impl PubXmrigApi { } #[inline] - pub(super) fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) { + pub fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) { let output = std::mem::take(&mut gui_api.output); let node = std::mem::take(&mut gui_api.node); let buf = std::mem::take(&mut pub_api.output); @@ -742,7 +699,7 @@ impl PubXmrigApi { // This combines the buffer from the PTY thread [output_pub] // with the actual [PubApiXmrig] output field. - pub(super) fn update_from_output( + pub fn update_from_output( public: &Arc>, output_parse: &Arc>, output_pub: &Arc>, @@ -811,7 +768,7 @@ impl PubXmrigApi { // XMRig doesn't initialize stats at 0 (or 0.0) and instead opts for [null] // which means some elements need to be wrapped in an [Option] or else serde will [panic!]. #[derive(Debug, Serialize, Deserialize, Clone)] -pub(super) struct PrivXmrigApi { +pub struct PrivXmrigApi { worker_id: String, resources: Resources, connection: Connection, @@ -836,57 +793,6 @@ impl PrivXmrigApi { .json() .await?) } - #[inline] - // // Replace config with new node - pub async fn update_xmrig_config( - client: &Client, - api_uri: &str, - token: &str, - node: &XvbNode, - address: &str, - pub_api_xmrig: &Arc>, - rig: &str, - ) -> Result<()> { - // get config - let request = client - .get(api_uri) - .header(AUTHORIZATION, ["Bearer ", token].concat()); - let mut config = request.send().await?.json::().await?; - // modify node configuration - let uri = [node.url(), ":".to_string(), node.port()].concat(); - info!("replace xmrig config with node {}", uri); - *config - .pointer_mut("/pools/0/url") - .ok_or_else(|| anyhow!("pools/0/url does not exist in xmrig config"))? = uri.into(); - *config - .pointer_mut("/pools/0/user") - .ok_or_else(|| anyhow!("pools/0/user does not exist in xmrig config"))? = node - .user(&address.chars().take(8).collect::()) - .into(); - *config - .pointer_mut("/pools/0/rig-id") - .ok_or_else(|| anyhow!("pools/0/rig-id does not exist in xmrig config"))? = rig.into(); - *config - .pointer_mut("/pools/0/tls") - .ok_or_else(|| anyhow!("pools/0/tls does not exist in xmrig config"))? = - node.tls().into(); - *config - .pointer_mut("/pools/0/keepalive") - .ok_or_else(|| anyhow!("pools/0/keepalive does not exist in xmrig config"))? = - node.keepalive().into(); - // send new config - client - .put(api_uri) - .header("Authorization", ["Bearer ", token].concat()) - .header("Content-Type", "application/json") - .timeout(std::time::Duration::from_secs(5)) - .body(config.to_string()) - .send() - .await?; - // update process status - lock!(pub_api_xmrig).node = node.to_string(); - anyhow::Ok(()) - } } #[derive(Debug, Serialize, Deserialize, Clone, Copy)] @@ -900,8 +806,3 @@ struct Connection { accepted: u128, rejected: u128, } - -#[derive(Debug, Serialize, Deserialize, Clone, Copy)] -struct Hashrate { - total: [Option; 3], -} diff --git a/src/helper/xrig/xmrig_proxy.rs b/src/helper/xrig/xmrig_proxy.rs new file mode 100644 index 0000000..38c7e1a --- /dev/null +++ b/src/helper/xrig/xmrig_proxy.rs @@ -0,0 +1,593 @@ +use enclose::enc; +use log::{debug, error, info, warn}; +use reqwest::{header::AUTHORIZATION, Client}; +use serde::{Deserialize, Serialize}; +use std::fmt::Write; +use std::{ + path::Path, + sync::{Arc, Mutex}, + thread, + time::{Duration, Instant}, +}; +use tokio::spawn; + +use crate::{ + disk::state::Xmrig, + helper::{ + check_died, check_user_input, signal_end, sleep_end_loop, + xrig::update_xmrig_config, + xvb::{nodes::XvbNode, PubXvbApi}, + Helper, Process, ProcessName, ProcessSignal, ProcessState, + }, + macros::{arc_mut, lock, lock2, sleep}, + miscs::output_console, + regex::{contains_timeout, contains_usepool, detect_new_node_xmrig, XMRIG_REGEX}, + GUPAX_VERSION_UNDERSCORE, UNKNOWN_DATA, +}; +use crate::{XMRIG_CONFIG_URL, XMRIG_PROXY_SUMMARY_URL}; + +use super::xmrig::PubXmrigApi; +impl Helper { + // Takes in some [State/XmrigProxy] and parses it to build the actual command arguments. + // Returns the [Vec] of actual arguments, + #[cold] + #[inline(never)] + pub async fn read_pty_xp( + output_parse: Arc>, + output_pub: Arc>, + reader: Box, + process_xvb: Arc>, + pub_api_xvb: &Arc>, + ) { + use std::io::BufRead; + let mut stdout = std::io::BufReader::new(reader).lines(); + + // Run a ANSI escape sequence filter for the first few lines. + let mut i = 0; + while let Some(Ok(line)) = stdout.next() { + let line = strip_ansi_escapes::strip_str(line); + if let Err(e) = writeln!(lock!(output_parse), "{}", line) { + error!("XMRig-Proxy PTY Parse | Output error: {}", e); + } + if let Err(e) = writeln!(lock!(output_pub), "{}", line) { + error!("XMRig-Proxy PTY Pub | Output error: {}", e); + } + if i > 7 { + break; + } else { + i += 1; + } + } + + while let Some(Ok(line)) = stdout.next() { + // need to verify if node still working + // for that need to catch "connect error" + // only switch nodes of XvB if XvB process is used + if lock!(process_xvb).is_alive() { + if contains_timeout(&line) { + let current_node = lock!(pub_api_xvb).current_node; + if let Some(current_node) = current_node { + // updating current node to None, will stop sending signal of FailedNode until new node is set + // send signal to update node. + warn!( + "XMRig-Proxy PTY Parse | node is offline, sending signal to update nodes." + ); + lock!(process_xvb).signal = ProcessSignal::UpdateNodes(current_node); + lock!(pub_api_xvb).current_node = None; + } + } + if contains_usepool(&line) { + info!("XMRig-Proxy PTY Parse | new pool detected"); + // need to update current node because it was updated. + // if custom node made by user, it is not supported because algo is deciding which node to use. + + let node = detect_new_node_xmrig(&line); + if node.is_none() { + warn!( + "XMRig-Proxy PTY Parse | node is not understood, switching to backup." + ); + // update with default will choose which XvB to prefer. Will update XvB to use p2pool. + lock!(process_xvb).signal = ProcessSignal::UpdateNodes(XvbNode::default()); + } + lock!(pub_api_xvb).current_node = node; + } + } + // println!("{}", line); // For debugging. + if let Err(e) = writeln!(lock!(output_parse), "{}", line) { + error!("XMRig-Proxy PTY Parse | Output error: {}", e); + } + if let Err(e) = writeln!(lock!(output_pub), "{}", line) { + error!("XMRig-Proxy PTY Pub | Output error: {}", e); + } + } + } + pub fn build_xp_args( + helper: &Arc>, + state: &crate::disk::state::XmrigProxy, + ) -> Vec { + let mut args = Vec::with_capacity(500); + let api_ip; + let api_port; + let ip; + let port; + + // [Simple] + if state.simple { + // Build the xmrig argument + let rig = if state.simple_rig.is_empty() { + GUPAX_VERSION_UNDERSCORE.to_string() + } else { + state.simple_rig.clone() + }; // Rig name + args.push("-o".to_string()); + args.push("127.0.0.1:3333".to_string()); // Local P2Pool (the default) + args.push("-b".to_string()); + args.push("0.0.0.0:3355".to_string()); + args.push("--user".to_string()); + args.push(rig); // Rig name + args.push("--no-color".to_string()); // No color + args.push("--http-host".to_string()); + args.push("127.0.0.1".to_string()); // HTTP API IP + args.push("--http-port".to_string()); + args.push("18089".to_string()); // HTTP API Port + lock2!(helper, pub_api_xp).node = "127.0.0.1:3333 (Local P2Pool)".to_string(); + + // [Advanced] + } else { + // XMRig doesn't understand [localhost] + let p2pool_ip = if state.p2pool_ip == "localhost" || state.p2pool_ip.is_empty() { + "127.0.0.1" + } else { + &state.p2pool_ip + }; + api_ip = if state.api_ip == "localhost" || state.api_ip.is_empty() { + "127.0.0.1".to_string() + } else { + state.api_ip.to_string() + }; + api_port = if state.api_port.is_empty() { + "18089".to_string() + } else { + state.api_port.to_string() + }; + ip = if state.api_ip == "localhost" || state.ip.is_empty() { + "0.0.0.0".to_string() + } else { + state.ip.to_string() + }; + + port = if state.port.is_empty() { + "3355".to_string() + } else { + state.port.to_string() + }; + let p2pool_url = format!("{}:{}", p2pool_ip, state.p2pool_port); // Combine IP:Port into one string + let bind_url = format!("{}:{}", ip, port); // Combine IP:Port into one string + args.push("--user".to_string()); + args.push(state.address.clone()); // Wallet + args.push("--rig-id".to_string()); + args.push(state.rig.to_string()); // Rig ID + args.push("-o".to_string()); + args.push(p2pool_url.clone()); // IP/Port + args.push("-b".to_string()); + args.push(bind_url.clone()); // IP/Port + args.push("--http-host".to_string()); + args.push(api_ip.to_string()); // HTTP API IP + args.push("--http-port".to_string()); + args.push(api_port.to_string()); // HTTP API Port + args.push("--no-color".to_string()); // No color escape codes + if state.tls { + args.push("--tls".to_string()); + } // TLS + if state.keepalive { + args.push("--keepalive".to_string()); + } // Keepalive + lock2!(helper, pub_api_xp).node = p2pool_url; + } + args.push(format!("--http-access-token={}", state.token)); // HTTP API Port + args.push("--http-no-restricted".to_string()); + args + } + + pub fn stop_xp(helper: &Arc>) { + info!("XMRig-Proxy | Attempting to stop..."); + lock2!(helper, xmrig_proxy).signal = ProcessSignal::Stop; + info!("locked signal ok"); + lock2!(helper, xmrig_proxy).state = ProcessState::Middle; + info!("locked state ok"); + let gui_api = Arc::clone(&lock!(helper).gui_api_xp); + info!("clone gui ok"); + let pub_api = Arc::clone(&lock!(helper).pub_api_xp); + info!("clone pub ok"); + *lock!(pub_api) = PubXmrigProxyApi::new(); + info!("pub api reset ok"); + *lock!(gui_api) = PubXmrigProxyApi::new(); + info!("gui api reset ok"); + } + // The "restart frontend" to a "frontend" function. + // Basically calls to kill the current xmrig-proxy, waits a little, then starts the below function in a a new thread, then exit. + pub fn restart_xp( + helper: &Arc>, + state: &crate::disk::state::XmrigProxy, + state_xmrig: &Xmrig, + path: &Path, + ) { + info!("XMRig | Attempting to restart..."); + lock2!(helper, xmrig_proxy).state = ProcessState::Middle; + lock2!(helper, xmrig_proxy).signal = ProcessSignal::Restart; + + let helper = Arc::clone(helper); + let state = state.clone(); + let state_xmrig = state_xmrig.clone(); + let path = path.to_path_buf(); + // This thread lives to wait, start xmrig_proxy then die. + thread::spawn(move || { + while lock2!(helper, xmrig_proxy).state != ProcessState::Waiting { + warn!("XMRig_proxy | Want to restart but process is still alive, waiting..."); + sleep!(1000); + } + // Ok, process is not alive, start the new one! + info!("XMRig_proxy | Old process seems dead, starting new one!"); + Self::start_xp(&helper, &state, &state_xmrig, &path); + }); + info!("XMRig | Restart ... OK"); + } + pub fn start_xp( + helper: &Arc>, + state_proxy: &crate::disk::state::XmrigProxy, + state_xmrig: &Xmrig, + path: &Path, + ) { + lock2!(helper, xmrig_proxy).state = ProcessState::Middle; + + let args = Self::build_xp_args(helper, state_proxy); + // Print arguments & user settings to console + crate::disk::print_dash(&format!("XMRig-Proxy | Launch arguments: {:#?}", args)); + info!("XMRig-Proxy | Using path: [{}]", path.display()); + + // Spawn watchdog thread + let process = Arc::clone(&lock!(helper).xmrig_proxy); + let gui_api = Arc::clone(&lock!(helper).gui_api_xp); + let pub_api = Arc::clone(&lock!(helper).pub_api_xp); + let process_xvb = Arc::clone(&lock!(helper).xvb); + let process_xmrig = Arc::clone(&lock!(helper).xmrig); + let path = path.to_path_buf(); + let token = state_proxy.token.clone(); + let state_xmrig = state_xmrig.clone(); + let redirect_xmrig = state_proxy.redirect_local_xmrig; + let pub_api_xvb = Arc::clone(&lock!(helper).pub_api_xvb); + let gui_api_xmrig = Arc::clone(&lock!(helper).gui_api_xmrig); + thread::spawn(move || { + Self::spawn_xp_watchdog( + &process, + &gui_api, + &pub_api, + args, + path, + &token, + &state_xmrig, + redirect_xmrig, + process_xvb, + process_xmrig, + &pub_api_xvb, + &gui_api_xmrig, + ); + }); + } + #[tokio::main] + #[allow(clippy::await_holding_lock)] + #[allow(clippy::too_many_arguments)] + async fn spawn_xp_watchdog( + process: &Arc>, + gui_api: &Arc>, + pub_api: &Arc>, + args: Vec, + path: std::path::PathBuf, + token_proxy: &str, + state_xmrig: &Xmrig, + xmrig_redirect: bool, + process_xvb: Arc>, + process_xmrig: Arc>, + pub_api_xvb: &Arc>, + gui_api_xmrig: &Arc>, + ) { + lock!(process).start = Instant::now(); + // spawn pty + debug!("XMRig-Proxy | Creating PTY..."); + let pty = portable_pty::native_pty_system(); + let pair = pty + .openpty(portable_pty::PtySize { + rows: 100, + cols: 1000, + pixel_width: 0, + pixel_height: 0, + }) + .unwrap(); + // 4. Spawn PTY read thread + debug!("XMRig-Proxy | Spawning PTY read thread..."); + let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY + let output_parse = Arc::clone(&lock!(process).output_parse); + let output_pub = Arc::clone(&lock!(process).output_pub); + spawn(enc!((pub_api_xvb, output_parse, output_pub) async move { + Self::read_pty_xp(output_parse, output_pub, reader, process_xvb, &pub_api_xvb).await; + })); + // 1b. Create command + debug!("XMRig-Proxy | Creating command..."); + let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new(path.clone()); + cmd.args(args); + cmd.cwd(path.as_path().parent().unwrap()); + // 1c. Create child + debug!("XMRig-Proxy | Creating child..."); + let child_pty = arc_mut!(pair.slave.spawn_command(cmd).unwrap()); + drop(pair.slave); + let mut stdin = pair.master.take_writer().unwrap(); + // to refactor to let user use his own ports + let api_summary_xp = XMRIG_PROXY_SUMMARY_URL; + let api_config_xmrig = XMRIG_CONFIG_URL; + + // set state + let client = Client::new(); + lock!(process).state = ProcessState::NotMining; + lock!(process).signal = ProcessSignal::None; + // reset stats + let node = lock!(pub_api).node.to_string(); + *lock!(pub_api) = PubXmrigProxyApi::new(); + *lock!(gui_api) = PubXmrigProxyApi::new(); + lock!(gui_api).node = node; + // loop + let start = lock!(process).start; + debug!("Xmrig-Proxy Watchdog | enabling verbose mode"); + #[cfg(target_os = "windows")] + if let Err(e) = write!(stdin, "v\r\n") { + error!("P2Pool Watchdog | STDIN error: {}", e); + } + #[cfg(target_family = "unix")] + if let Err(e) = writeln!(stdin, "v") { + error!("P2Pool Watchdog | STDIN error: {}", e); + } + debug!("Xmrig-Proxy Watchdog | checking connections"); + #[cfg(target_os = "windows")] + if let Err(e) = write!(stdin, "c\r\n") { + error!("P2Pool Watchdog | STDIN error: {}", e); + } + #[cfg(target_family = "unix")] + if let Err(e) = writeln!(stdin, "c") { + error!("P2Pool Watchdog | STDIN error: {}", e); + } + info!("XMRig | Entering watchdog mode... woof!"); + loop { + let now = Instant::now(); + debug!("XMRig-Proxy Watchdog | ----------- Start of loop -----------"); + // check state + if check_died( + &child_pty, + &mut lock!(process), + &start, + &mut lock!(gui_api).output, + ) { + break; + } + // check signal + if signal_end(process, &child_pty, &start, &mut lock!(gui_api).output) { + break; + } + // check user input + check_user_input(process, &mut stdin); + // get data output/api + + // Check if logs need resetting + debug!("XMRig Watchdog | Attempting GUI log reset check"); + { + let mut lock = lock!(gui_api); + Self::check_reset_gui_output(&mut lock.output, ProcessName::XmrigProxy); + } + // Always update from output + // todo: check difference with xmrig + debug!("XMRig Watchdog | Starting [update_from_output()]"); + PubXmrigProxyApi::update_from_output( + pub_api, + &output_pub, + &output_parse, + start.elapsed(), + process, + ); + // update data from api + debug!("XMRig-Proxy Watchdog | Attempting HTTP API request..."); + match PrivXmrigProxyApi::request_xp_api(&client, api_summary_xp, token_proxy).await { + Ok(priv_api) => { + debug!("XMRig-Proxy Watchdog | HTTP API request OK, attempting [update_from_priv()]"); + PubXmrigProxyApi::update_from_priv(pub_api, priv_api); + } + Err(err) => { + warn!( + "XMRig-Proxy Watchdog | Could not send HTTP API request to: {}\n{}", + api_summary_xp, err + ); + } + } + // update xmrig to use xmrig-proxy if option enabled and local xmrig alive + if xmrig_redirect + && lock!(gui_api_xmrig).node != XvbNode::XmrigProxy.to_string() + && (lock!(process_xmrig).state == ProcessState::Alive + || lock!(process_xmrig).state == ProcessState::NotMining) + { + info!("redirect local xmrig instance to xmrig-proxy"); + if let Err(err) = update_xmrig_config( + &client, + api_config_xmrig, + &state_xmrig.token, + &XvbNode::XmrigProxy, + "", + GUPAX_VERSION_UNDERSCORE, + ) + .await + { + // show to console error about updating xmrig config + warn!("XMRig-Proxy Process | Failed request HTTP API Xmrig"); + output_console( + &mut lock!(gui_api).output, + &format!( + "Failure to update xmrig config with HTTP API.\nError: {}", + err + ), + ProcessName::Xvb, + ); + } else { + lock!(gui_api_xmrig).node = XvbNode::XmrigProxy.to_string(); + debug!("XMRig-Proxy Process | mining on Xmrig-Proxy pool"); + } + } + // do not use more than 1 second for the loop + sleep_end_loop(now, ProcessName::XmrigProxy).await; + } + + // 5. If loop broke, we must be done here. + info!("XMRig-Proxy Watchdog | Watchdog thread exiting... Goodbye!"); + // sleep + } +} +#[derive(Debug, Clone)] +pub struct PubXmrigProxyApi { + pub output: String, + pub uptime: Duration, + pub accepted: u32, + pub rejected: u32, + pub hashrate_1m: f32, + pub hashrate_10m: f32, + pub hashrate_1h: f32, + pub hashrate_12h: f32, + pub hashrate_24h: f32, + pub node: String, +} + +impl Default for PubXmrigProxyApi { + fn default() -> Self { + Self::new() + } +} +impl PubXmrigProxyApi { + pub fn new() -> Self { + Self { + output: String::new(), + uptime: Duration::from_secs(0), + accepted: 0, + rejected: 0, + hashrate_1m: 0.0, + hashrate_10m: 0.0, + hashrate_1h: 0.0, + hashrate_12h: 0.0, + hashrate_24h: 0.0, + node: UNKNOWN_DATA.to_string(), + } + } + pub fn update_from_output( + public: &Arc>, + output_parse: &Arc>, + output_pub: &Arc>, + elapsed: std::time::Duration, + process: &Arc>, + ) { + // 1. Take the process's current output buffer and combine it with Pub (if not empty) + let mut output_pub = lock!(output_pub); + + { + let mut public = lock!(public); + if !output_pub.is_empty() { + public.output.push_str(&std::mem::take(&mut *output_pub)); + } + // Update uptime + public.uptime = elapsed; + } + + // 2. Check for "new job"/"no active...". + let mut output_parse = lock!(output_parse); + if XMRIG_REGEX.new_job.is_match(&output_parse) + || XMRIG_REGEX.valid_conn.is_match(&output_parse) + { + lock!(process).state = ProcessState::Alive; + } else if XMRIG_REGEX.timeout.is_match(&output_parse) + || XMRIG_REGEX.invalid_conn.is_match(&output_parse) + || XMRIG_REGEX.error.is_match(&output_parse) + { + lock!(process).state = ProcessState::NotMining; + } + // 3. Throw away [output_parse] + output_parse.clear(); + drop(output_parse); + } + // same method as PubXmrigApi, why not make a trait ? + pub fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) { + let output = std::mem::take(&mut gui_api.output); + let node = std::mem::take(&mut gui_api.node); + let buf = std::mem::take(&mut pub_api.output); + *gui_api = Self { + output, + node, + ..std::mem::take(pub_api) + }; + if !buf.is_empty() { + gui_api.output.push_str(&buf); + } + } + fn update_from_priv(public: &Arc>, private: PrivXmrigProxyApi) { + let mut public = lock!(public); + *public = Self { + accepted: private.results.accepted, + rejected: private.results.rejected, + hashrate_1m: private.hashrate.total[0], + hashrate_10m: private.hashrate.total[1], + hashrate_1h: private.hashrate.total[2], + hashrate_12h: private.hashrate.total[3], + hashrate_24h: private.hashrate.total[4], + ..std::mem::take(&mut *public) + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct PrivXmrigProxyApi { + hashrate: HashrateProxy, + miners: Miners, + results: Results, +} + +#[derive(Deserialize, Serialize)] +struct Results { + accepted: u32, + rejected: u32, +} + +#[derive(Deserialize, Serialize)] +struct HashrateProxy { + total: [f32; 6], +} + +#[derive(Deserialize, Serialize)] +struct Miners { + now: u16, + max: u16, +} +impl PrivXmrigProxyApi { + #[inline] + // Send an HTTP request to XMRig's API, serialize it into [Self] and return it + async fn request_xp_api( + client: &Client, + api_uri: &str, + token: &str, + ) -> std::result::Result { + let request = client + .get(api_uri) + .header(AUTHORIZATION, ["Bearer ", token].concat()); + let mut private = request + .timeout(std::time::Duration::from_millis(5000)) + .send() + .await? + .json::() + .await?; + // every hashrate value of xmrig-proxy is in kH/s, so we put convert it into H/s + for h in &mut private.hashrate.total { + *h *= 1000.0 + } + Ok(private) + } +} diff --git a/src/helper/xvb/algorithm.rs b/src/helper/xvb/algorithm.rs index ab2b086..efd353b 100644 --- a/src/helper/xvb/algorithm.rs +++ b/src/helper/xvb/algorithm.rs @@ -1,3 +1,9 @@ +use crate::helper::xrig::xmrig_proxy::PubXmrigProxyApi; +use crate::helper::xvb::api_url_xmrig; +use crate::helper::xvb::current_controllable_hr; +use crate::helper::ProcessName; +use crate::miscs::output_console; +use crate::miscs::output_console_without_time; use std::{ sync::{Arc, Mutex}, time::Duration, @@ -11,12 +17,12 @@ use tokio::time::sleep; use crate::{ helper::{ p2pool::PubP2poolApi, - xmrig::{PrivXmrigApi, PubXmrigApi}, - xvb::{nodes::XvbNode, output_console, output_console_without_time}, + xrig::{update_xmrig_config, xmrig::PubXmrigApi}, + xvb::nodes::XvbNode, }, macros::lock, - BLOCK_PPLNS_WINDOW_MAIN, BLOCK_PPLNS_WINDOW_MINI, SECOND_PER_BLOCK_P2POOL, XMRIG_CONFIG_URI, - XVB_BUFFER, XVB_ROUND_DONOR_MEGA_MIN_HR, XVB_ROUND_DONOR_MIN_HR, XVB_ROUND_DONOR_VIP_MIN_HR, + BLOCK_PPLNS_WINDOW_MAIN, BLOCK_PPLNS_WINDOW_MINI, SECOND_PER_BLOCK_P2POOL, XVB_BUFFER, + XVB_ROUND_DONOR_MEGA_MIN_HR, XVB_ROUND_DONOR_MIN_HR, XVB_ROUND_DONOR_VIP_MIN_HR, XVB_ROUND_DONOR_WHALE_MIN_HR, XVB_TIME_ALGO, }; @@ -60,9 +66,21 @@ pub(crate) fn calcul_donated_time( "{} kH/s estimated sent the last hour for your address on p2pool, including this instance", Float::from_3((p2pool_ehr / 1000.0).into()) ); - output_console(gui_api_xvb, &msg_lhr); - output_console(gui_api_xvb, &msg_mhr); - output_console(gui_api_xvb, &msg_ehr); + output_console( + &mut lock!(gui_api_xvb).output, + &msg_lhr, + crate::helper::ProcessName::Xvb, + ); + output_console( + &mut lock!(gui_api_xvb).output, + &msg_mhr, + crate::helper::ProcessName::Xvb, + ); + output_console( + &mut lock!(gui_api_xvb).output, + &msg_ehr, + crate::helper::ProcessName::Xvb, + ); // calculate how much time can be spared let mut spared_time = time_that_could_be_spared(lhr, min_hr); @@ -77,7 +95,11 @@ pub(crate) fn calcul_donated_time( } } if lock!(gui_api_xvb).stats_priv.runtime_hero_mode { - output_console(gui_api_xvb, "Hero mode is enabled for this decision"); + output_console( + &mut lock!(gui_api_xvb).output, + "Hero mode is enabled for this decision", + crate::helper::ProcessName::Xvb, + ); } spared_time } @@ -185,44 +207,45 @@ async fn sleep_then_update_node_xmrig( address: &str, gui_api_xvb: &Arc>, gui_api_xmrig: &Arc>, + gui_api_xp: &Arc>, rig: &str, + xp_alive: bool, ) { let node = lock!(gui_api_xvb).stats_priv.node; debug!( "Xvb Process | algo sleep for {} while mining on P2pool", XVB_TIME_ALGO - spared_time ); + let msg_xmrig_or_xp = if xp_alive { "XMRig-Proxy" } else { "XMRig" }; sleep(Duration::from_secs((XVB_TIME_ALGO - spared_time).into())).await; // only update xmrig config if it is actually mining. if spared_time > 0 { - debug!("Xvb Process | request xmrig to mine on XvB"); + debug!("Xvb Process | request {msg_xmrig_or_xp} to mine on XvB"); if lock!(gui_api_xvb).current_node.is_none() || lock!(gui_api_xvb) .current_node .as_ref() .is_some_and(|n| n == &XvbNode::P2pool) { - if let Err(err) = PrivXmrigApi::update_xmrig_config( - client, - api_uri, - token_xmrig, - &node, - address, - gui_api_xmrig, - rig, - ) - .await + if let Err(err) = + update_xmrig_config(client, api_uri, token_xmrig, &node, address, rig).await { // show to console error about updating xmrig config - warn!("Xvb Process | Failed request HTTP API Xmrig"); + warn!("Xvb Process | Failed request HTTP API {msg_xmrig_or_xp}"); output_console( - gui_api_xvb, + &mut lock!(gui_api_xvb).output, &format!( - "Failure to update xmrig config with HTTP API.\nError: {}", + "Failure to update {msg_xmrig_or_xp} config with HTTP API.\nError: {}", err ), + crate::helper::ProcessName::Xvb, ); } else { + if xp_alive { + lock!(gui_api_xp).node = node.to_string(); + } else { + lock!(gui_api_xmrig).node = node.to_string(); + } debug!("Xvb Process | mining on XvB pool"); } } @@ -240,75 +263,79 @@ pub(crate) async fn algorithm( client: &Client, gui_api_xvb: &Arc>, gui_api_xmrig: &Arc>, + gui_api_xp: &Arc>, gui_api_p2pool: &Arc>, token_xmrig: &str, state_p2pool: &crate::disk::state::P2pool, share: u32, time_donated: &Arc>, rig: &str, + xp_alive: bool, ) { debug!("Xvb Process | Algorithm is started"); output_console( - gui_api_xvb, + &mut lock!(gui_api_xvb).output, "Algorithm of distribution HR started for the next ten minutes.", + ProcessName::Xvb, ); // the time that takes the algorithm do decide the next ten minutes could means less p2pool mining. It is solved by the buffer and spawning requests. let address = &state_p2pool.address; // request XMrig to mine on P2pool // if share is in PW, + let msg_xmrig_or_xp = if xp_alive { "XMRig-Proxy" } else { "XMRig" }; + + let api_url = api_url_xmrig(xp_alive, true); if share > 0 { debug!("Xvb Process | Algorithm share is in current window"); // calcul minimum HR output_console( - gui_api_xvb, + &mut lock!(gui_api_xvb).output, "At least one share is in current PPLNS window.", + ProcessName::Xvb, ); - let hashrate_xmrig = { - if lock!(gui_api_xmrig).hashrate_raw_15m > 0.0 { - lock!(gui_api_xmrig).hashrate_raw_15m - } else if lock!(gui_api_xmrig).hashrate_raw_1m > 0.0 { - lock!(gui_api_xmrig).hashrate_raw_1m - } else { - lock!(gui_api_xmrig).hashrate_raw - } - }; + let hashrate_xmrig = current_controllable_hr(xp_alive, gui_api_xp, gui_api_xmrig); *lock!(time_donated) = calcul_donated_time(hashrate_xmrig, gui_api_p2pool, gui_api_xvb, state_p2pool); let time_donated = *lock!(time_donated); debug!("Xvb Process | Donated time {} ", time_donated); output_console( - gui_api_xvb, + &mut lock!(gui_api_xvb).output, &format!( "Mining on P2pool node for {} seconds then on XvB for {} seconds.", XVB_TIME_ALGO - time_donated, time_donated ), + ProcessName::Xvb, ); // p2pool need to be mined if donated time is not equal to xvb_time_algo if time_donated != XVB_TIME_ALGO && lock!(gui_api_xvb).current_node != Some(XvbNode::P2pool) { - debug!("Xvb Process | request xmrig to mine on p2pool"); - if let Err(err) = PrivXmrigApi::update_xmrig_config( + debug!("Xvb Process | request {msg_xmrig_or_xp} to mine on p2pool"); + if let Err(err) = update_xmrig_config( client, - XMRIG_CONFIG_URI, + &api_url, token_xmrig, &XvbNode::P2pool, address, - gui_api_xmrig, rig, ) .await { - warn!("Xvb Process | Failed request HTTP API Xmrig"); + warn!("Xvb Process | Failed request HTTP API {msg_xmrig_or_xp}"); output_console( - gui_api_xvb, + &mut lock!(gui_api_xvb).output, &format!( - "Failure to update xmrig config with HTTP API.\nError: {}", + "Failure to update {msg_xmrig_or_xp} config with HTTP API.\nError: {}", err ), + ProcessName::Xvb, ); + } else if xp_alive { + lock!(gui_api_xmrig).node = XvbNode::P2pool.to_string() + } else { + lock!(gui_api_xmrig).node = XvbNode::P2pool.to_string(); } } @@ -316,12 +343,14 @@ pub(crate) async fn algorithm( sleep_then_update_node_xmrig( time_donated, client, - XMRIG_CONFIG_URI, + &api_url, token_xmrig, address, gui_api_xvb, gui_api_xmrig, + gui_api_xp, "", + xp_alive, ) .await; lock!(gui_api_xvb) @@ -338,36 +367,45 @@ pub(crate) async fn algorithm( } else { // no share, so we mine on p2pool. We update xmrig only if it was still mining on XvB. if lock!(gui_api_xvb).current_node != Some(XvbNode::P2pool) { - info!("Xvb Process | request xmrig to mine on p2pool"); + info!("Xvb Process | request {msg_xmrig_or_xp}to mine on p2pool"); - if let Err(err) = PrivXmrigApi::update_xmrig_config( + if let Err(err) = update_xmrig_config( client, - XMRIG_CONFIG_URI, + &api_url, token_xmrig, &XvbNode::P2pool, address, - gui_api_xmrig, rig, ) .await { - warn!("Xvb Process | Failed request HTTP API Xmrig"); + warn!("Xvb Process | Failed request HTTP API {msg_xmrig_or_xp}"); output_console( - gui_api_xvb, + &mut lock!(gui_api_xvb).output, &format!( - "Failure to update xmrig config with HTTP API.\nError: {}", + "Failure to update {msg_xmrig_or_xp}config with HTTP API.\nError: {}", err ), + ProcessName::Xvb, ); } } - output_console(gui_api_xvb, "No share in the current PPLNS Window !"); - output_console(gui_api_xvb, "Mining on P2pool for the next ten minutes."); + output_console( + &mut lock!(gui_api_xvb).output, + "No share in the current PPLNS Window !", + ProcessName::Xvb, + ); + output_console( + &mut lock!(gui_api_xvb).output, + "Mining on P2pool for the next ten minutes.", + ProcessName::Xvb, + ); sleep(Duration::from_secs(XVB_TIME_ALGO.into())).await; + let hr = current_controllable_hr(xp_alive, gui_api_xp, gui_api_xmrig); lock!(gui_api_xvb) .p2pool_sent_last_hour_samples .0 - .push_back(lock!(gui_api_xmrig).hashrate_raw_15m); + .push_back(hr); lock!(gui_api_xvb) .xvb_sent_last_hour_samples .0 @@ -375,5 +413,5 @@ pub(crate) async fn algorithm( } // algorithm has run, so do not retry but run normally // put a space to mark the difference with the next run. - output_console_without_time(gui_api_xvb, ""); + output_console_without_time(&mut lock!(gui_api_xvb).output, "", ProcessName::Xvb); } diff --git a/src/helper/xvb/mod.rs b/src/helper/xvb/mod.rs index 39e92a6..2af6b22 100644 --- a/src/helper/xvb/mod.rs +++ b/src/helper/xvb/mod.rs @@ -1,12 +1,15 @@ +use crate::helper::xrig::update_xmrig_config; use crate::helper::xvb::algorithm::algorithm; use crate::helper::xvb::priv_stats::XvbPrivStats; use crate::helper::xvb::public_stats::XvbPubStats; +use crate::helper::{sleep_end_loop, ProcessName}; +use crate::miscs::output_console; +use crate::{XMRIG_CONFIG_URL, XMRIG_PROXY_CONFIG_URL, XMRIG_PROXY_SUMMARY_URL, XMRIG_SUMMARY_URL}; use bounded_vec_deque::BoundedVecDeque; use enclose::enc; -use log::{debug, error, info, warn}; +use log::{debug, info, warn}; use readable::up::Uptime; use reqwest::Client; -use std::fmt::Write; use std::mem; use std::time::Duration; use std::{ @@ -17,9 +20,8 @@ use tokio::spawn; use tokio::task::JoinHandle; use tokio::time::{sleep, Instant}; -use crate::helper::xmrig::PrivXmrigApi; use crate::helper::xvb::rounds::round_type; -use crate::utils::constants::{XMRIG_CONFIG_URI, XVB_PUBLIC_ONLY, XVB_TIME_ALGO}; +use crate::utils::constants::{XVB_PUBLIC_ONLY, XVB_TIME_ALGO}; use crate::{ helper::{ProcessSignal, ProcessState}, utils::macros::{lock, lock2, sleep}, @@ -28,7 +30,8 @@ use crate::{ use self::nodes::XvbNode; use super::p2pool::PubP2poolApi; -use super::xmrig::PubXmrigApi; +use super::xrig::xmrig::PubXmrigApi; +use super::xrig::xmrig_proxy::PubXmrigProxyApi; use super::{Helper, Process}; pub mod algorithm; @@ -49,6 +52,7 @@ impl Helper { state_xvb: &crate::disk::state::Xvb, state_p2pool: &crate::disk::state::P2pool, state_xmrig: &crate::disk::state::Xmrig, + state_xp: &crate::disk::state::XmrigProxy, ) { info!("XvB | Attempting to restart..."); lock2!(helper, xvb).signal = ProcessSignal::Restart; @@ -57,6 +61,7 @@ impl Helper { let state_xvb = state_xvb.clone(); let state_p2pool = state_p2pool.clone(); let state_xmrig = state_xmrig.clone(); + let state_xp = state_xp.clone(); // This thread lives to wait, start xmrig then die. thread::spawn(move || { while lock2!(helper, xvb).state != ProcessState::Waiting { @@ -65,7 +70,7 @@ impl Helper { } // Ok, process is not alive, start the new one! info!("XvB | Old process seems dead, starting new one!"); - Self::start_xvb(&helper, &state_xvb, &state_p2pool, &state_xmrig); + Self::start_xvb(&helper, &state_xvb, &state_p2pool, &state_xmrig, &state_xp); }); info!("XMRig | Restart ... OK"); } @@ -74,6 +79,7 @@ impl Helper { state_xvb: &crate::disk::state::Xvb, state_p2pool: &crate::disk::state::P2pool, state_xmrig: &crate::disk::state::Xmrig, + state_xp: &crate::disk::state::XmrigProxy, ) { // 1. Clone Arc value from Helper // pub for writing new values that will show up on UI after helper thread update. (every seconds.) @@ -88,8 +94,11 @@ impl Helper { let process_p2pool = Arc::clone(&lock!(helper).p2pool); let gui_api_p2pool = Arc::clone(&lock!(helper).gui_api_p2pool); let process_xmrig = Arc::clone(&lock!(helper).xmrig); + let process_xp = Arc::clone(&lock!(helper).xmrig_proxy); let gui_api_xmrig = Arc::clone(&lock!(helper).gui_api_xmrig); let pub_api_xmrig = Arc::clone(&lock!(helper).pub_api_xmrig); + let gui_api_xp = Arc::clone(&lock!(helper).gui_api_xp); + let pub_api_xp = Arc::clone(&lock!(helper).gui_api_xp); // Reset before printing to output. // Need to reset because values of stats would stay otherwise which could bring confusion even if panel is with a disabled theme. // at the start of a process, values must be default. @@ -113,22 +122,28 @@ impl Helper { // verify if token and address are existent on XvB server info!("XvB | spawn watchdog"); - thread::spawn(enc!((state_xvb, state_p2pool, state_xmrig) move || { - // thread priority, else there are issue on windows but it is also good for other OS - Self::spawn_xvb_watchdog( - &gui_api, - &pub_api, - &process, - &state_xvb, - &state_p2pool, - &state_xmrig, - &gui_api_p2pool, - &process_p2pool, - &gui_api_xmrig, - &pub_api_xmrig, - &process_xmrig, - ); - })); + thread::spawn( + enc!((state_xvb, state_p2pool, state_xmrig, state_xmrig,state_xp) move || { + // thread priority, else there are issue on windows but it is also good for other OS + Self::spawn_xvb_watchdog( + &gui_api, + &pub_api, + &process, + &state_xvb, + &state_p2pool, + &state_xmrig, + &state_xp, + &gui_api_p2pool, + &process_p2pool, + &gui_api_xmrig, + &pub_api_xmrig, + &process_xmrig, + &gui_api_xp, + &pub_api_xp, + &process_xp, + ); + }), + ); } // need the helper so we can restart the thread after getting a signal not caused by a restart. #[allow(clippy::too_many_arguments)] @@ -140,11 +155,15 @@ impl Helper { state_xvb: &crate::disk::state::Xvb, state_p2pool: &crate::disk::state::P2pool, state_xmrig: &crate::disk::state::Xmrig, + state_xp: &crate::disk::state::XmrigProxy, gui_api_p2pool: &Arc>, process_p2pool: &Arc>, gui_api_xmrig: &Arc>, pub_api_xmrig: &Arc>, process_xmrig: &Arc>, + gui_api_xp: &Arc>, + pub_api_xp: &Arc>, + process_xp: &Arc>, ) { // create uniq client that is going to be used for during the life of the thread. let client = reqwest::Client::new(); @@ -161,12 +180,13 @@ impl Helper { gui_api, process_p2pool, process_xmrig, + process_xp, process, state_p2pool, state_xvb, ) .await; - + let xp_alive = lock!(process_xp).state == ProcessState::Alive; // uptime for log of signal check ? let start = lock!(process).start; // uptime of last run of algo @@ -187,7 +207,7 @@ impl Helper { loop { debug!("XvB Watchdog | ----------- Start of loop -----------"); // Set timer of loop - let start_loop = Instant::now(); + let start_loop = std::time::Instant::now(); // verify if p2pool and xmrig are running, else XvB must be reloaded with another token/address to start verifying the other process. check_state_outcauses_xvb( &client, @@ -195,12 +215,15 @@ impl Helper { pub_api, process, process_xmrig, + process_xp, process_p2pool, &mut first_loop, &handle_algo, pub_api_xmrig, + pub_api_xp, state_p2pool, state_xmrig, + state_xp, ) .await; @@ -213,8 +236,11 @@ impl Helper { pub_api, gui_api, gui_api_xmrig, + gui_api_xp, state_p2pool, state_xmrig, + state_xp, + xp_alive, ) { info!("XvB Watchdog | Signal has stopped the loop"); break; @@ -247,70 +273,83 @@ impl Helper { // first_loop is false here but could be changed to true under some conditions. // will send a stop signal if public stats failed or update data with new one. *lock!(handle_request) = Some(spawn( - enc!((client, pub_api, gui_api, gui_api_p2pool, gui_api_xmrig, state_xvb, state_p2pool, state_xmrig, process, last_algorithm, retry, handle_algo, time_donated, last_request) async move { - // needs to wait here for public stats to get private stats. - if last_request_expired || first_loop || should_refresh_before_next_algo { - XvbPubStats::update_stats(&client, &gui_api, &pub_api, &process).await; - *lock!(last_request) = Instant::now(); - } - // private stats needs valid token and address. - // other stats needs everything to be alive, so just require alive here for now. - // maybe later differentiate to add a way to get private stats without running the algo ? - if lock!(process).state == ProcessState::Alive { - // get current share to know if we are in a round and this is a required data for algo. - let share = lock!(gui_api_p2pool).sidechain_shares; - debug!("XvB | Number of current shares: {}", share); - // private stats can be requested every minute or first loop or if the have almost finished. - if last_request_expired || first_loop || should_refresh_before_next_algo { - debug!("XvB Watchdog | Attempting HTTP private API request..."); - // reload private stats, it send a signal if error that will be captured on the upper thread. - XvbPrivStats::update_stats( - &client, &state_p2pool.address, &state_xvb.token, &pub_api, &gui_api, &process, - ) - .await; - *lock!(last_request) = Instant::now(); - - // verify in which round type we are - let round = round_type(share, &pub_api); - // refresh the round we participate in. - debug!("XvB | Round type: {:#?}", round); - lock!(pub_api).stats_priv.round_participate = round; - // verify if we are the winner of the current round - if lock!(pub_api).stats_pub.winner - == Helper::head_tail_of_monero_address(&state_p2pool.address).as_str() - { - lock!(pub_api).stats_priv.win_current = true + enc!((client, pub_api, gui_api, gui_api_p2pool, gui_api_xmrig, gui_api_xp, state_xvb, state_p2pool, state_xmrig, state_xp, process, last_algorithm, retry, handle_algo, time_donated, last_request) async move { + // needs to wait here for public stats to get private stats. + if last_request_expired || first_loop || should_refresh_before_next_algo { + XvbPubStats::update_stats(&client, &gui_api, &pub_api, &process).await; + *lock!(last_request) = Instant::now(); } - } - if (first_loop || *lock!(retry)|| is_algo_finished) && lock!(gui_api_xmrig).hashrate_raw > 0.0 && lock!(process).state == ProcessState::Alive - { - // if algo was started, it must not retry next loop. - *lock!(retry) = false; - // reset instant because algo will start. - *lock!(last_algorithm) = Instant::now(); - *lock!(handle_algo) = Some(spawn(enc!((client, gui_api, gui_api_xmrig, state_xmrig, time_donated) async move { - algorithm( - &client, - &gui_api, - &gui_api_xmrig, - &gui_api_p2pool, - &state_xmrig.token, - &state_p2pool, - share, - &time_donated, - &state_xmrig.rig, - ).await; - }))); - } else { - // if xmrig is still at 0 HR but is alive and algorithm is skipped, recheck first 10s of xmrig inside algorithm next time (in one minute). Don't check if algo failed to start because state was not alive after getting private stats. + // private stats needs valid token and address. + // other stats needs everything to be alive, so just require alive here for now. + // maybe later differentiate to add a way to get private stats without running the algo ? + if lock!(process).state == ProcessState::Alive { + // get current share to know if we are in a round and this is a required data for algo. + let share = lock!(gui_api_p2pool).sidechain_shares; + debug!("XvB | Number of current shares: {}", share); + // private stats can be requested every minute or first loop or if the have almost finished. + if last_request_expired || first_loop || should_refresh_before_next_algo { + debug!("XvB Watchdog | Attempting HTTP private API request..."); + // reload private stats, it send a signal if error that will be captured on the upper thread. + XvbPrivStats::update_stats( + &client, &state_p2pool.address, &state_xvb.token, &pub_api, &gui_api, &process, + ) + .await; + *lock!(last_request) = Instant::now(); - if lock!(gui_api_xmrig).hashrate_raw == 0.0 && lock!(process).state == ProcessState::Alive { - *lock!(retry) = true + // verify in which round type we are + let round = round_type(share, &pub_api); + // refresh the round we participate in. + debug!("XvB | Round type: {:#?}", round); + lock!(pub_api).stats_priv.round_participate = round; + // verify if we are the winner of the current round + if lock!(pub_api).stats_pub.winner + == Helper::head_tail_of_monero_address(&state_p2pool.address).as_str() + { + lock!(pub_api).stats_priv.win_current = true } } + let hashrate = current_controllable_hr(xp_alive, &gui_api_xp, &gui_api_xmrig); + if (first_loop || *lock!(retry)|| is_algo_finished) && hashrate > 0.0 && lock!(process).state == ProcessState::Alive + { + // if algo was started, it must not retry next loop. + *lock!(retry) = false; + // reset instant because algo will start. + *lock!(last_algorithm) = Instant::now(); + *lock!(handle_algo) = Some(spawn(enc!((client, gui_api, gui_api_xmrig, gui_api_xp, state_xmrig, state_xp, time_donated) async move { + let token_xmrig = if xp_alive { + &state_xp.token + } else { + &state_xmrig.token + }; + let rig = if xp_alive { + "" + } else { + &state_xmrig.rig + }; + algorithm( + &client, + &gui_api, + &gui_api_xmrig, + &gui_api_xp, + &gui_api_p2pool, + token_xmrig, + &state_p2pool, + share, + &time_donated, + rig, + xp_alive + ).await; + }))); + } else { + // if xmrig is still at 0 HR but is alive and algorithm is skipped, recheck first 10s of xmrig inside algorithm next time (in one minute). Don't check if algo failed to start because state was not alive after getting private stats. - } - }), + if current_controllable_hr(xp_alive, &gui_api_xp, &gui_api_xmrig) == 0.0 && lock!(process).state == ProcessState::Alive { + *lock!(retry) = true + } + } + + } + }), )); } // if retry is false, next time the message about waiting for xmrig HR can be shown. @@ -320,10 +359,12 @@ impl Helper { // inform user that algorithm has not yet started because it is waiting for xmrig HR. // show this message only once before the start of algo if *lock!(retry) && !msg_retry_done { - output_console( - gui_api, - "Algorithm is waiting for 10 seconds average HR of XMRig.", - ); + let msg = if xp_alive { + "Algorithm is waiting for 1 minute average HR of XMRig-Proxy" + } else { + "Algorithm is waiting for 10 seconds average HR of XMRig." + }; + output_console(&mut lock!(gui_api).output, msg, ProcessName::Xvb); msg_retry_done = true; } // update indicator (time before switch and mining location) in private stats @@ -346,15 +387,7 @@ impl Helper { first_loop = false; } // Sleep (only if 900ms hasn't passed) - let elapsed = start_loop.elapsed().as_millis(); - // Since logic goes off if less than 1000, casting should be safe - if elapsed < 999 { - let sleep = (999 - elapsed) as u64; - debug!("XvB Watchdog | END OF LOOP - Sleeping for [{}]s...", sleep); - tokio::time::sleep(Duration::from_millis(sleep)).await; - } else { - debug!("XMRig Watchdog | END OF LOOP - Not sleeping!"); - } + sleep_end_loop(start_loop, ProcessName::Xvb).await; } } } @@ -415,11 +448,13 @@ impl PubXvbApi { }; } } +#[allow(clippy::too_many_arguments)] async fn check_conditions_for_start( client: &Client, gui_api: &Arc>, process_p2pool: &Arc>, process_xmrig: &Arc>, + process_xp: &Arc>, process_xvb: &Arc>, state_p2pool: &crate::disk::state::P2pool, state_xvb: &crate::disk::state::Xvb, @@ -430,7 +465,7 @@ async fn check_conditions_for_start( info!("XvB | verify address and token"); // send to console: token non existent for address on XvB server warn!("Xvb | Start ... Partially failed because token and associated address are not existent on XvB server: {}\n", err); - output_console(gui_api, &format!("Token and associated address are not valid on XvB API.\nCheck if you are registered.\nError: {}", err)); + output_console(&mut lock!(gui_api).output, &format!("Token and associated address are not valid on XvB API.\nCheck if you are registered.\nError: {}", err), ProcessName::Xvb); ProcessState::NotMining } else if lock!(process_p2pool).state != ProcessState::Alive { info!("XvB | verify p2pool node"); @@ -441,15 +476,18 @@ async fn check_conditions_for_start( } else { "P2pool process is not running.\nCheck the P2pool Tab" }; - output_console(gui_api, msg); + output_console(&mut lock!(gui_api).output, msg, ProcessName::Xvb); ProcessState::Syncing - } else if lock!(process_xmrig).state != ProcessState::Alive { - // send to console: p2pool process is not running - warn!("Xvb | Start ... Partially failed because Xmrig instance is not running."); + } else if lock!(process_xmrig).state != ProcessState::Alive + || lock!(process_xp).state != ProcessState::Alive + { + // send to console: xmrig process is not running + warn!("Xvb | Start ... Partially failed because Xmrig or Xmrig-Proxy instance is not running."); // output the error to console output_console( - gui_api, - "XMRig process is not running.\nCheck the Xmrig Tab.", + &mut lock!(gui_api).output, + "XMRig or Xmrig-Proxy process is not running.\nCheck the Xmrig or Xmrig-Proxy Tab. One of them must be running to start the XvB algorithm.", + ProcessName::Xvb, ); ProcessState::Syncing } else { @@ -461,8 +499,9 @@ async fn check_conditions_for_start( // while waiting for xmrig and p2pool or getting right address/token, it can get public stats info!("XvB | print to console state"); output_console( - gui_api, + &mut lock!(gui_api).output, &["XvB partially started.\n", XVB_PUBLIC_ONLY].concat(), + ProcessName::Xvb, ); } // will update the preferred node for the first loop, even if partially started. @@ -476,81 +515,106 @@ async fn check_state_outcauses_xvb( pub_api: &Arc>, process: &Arc>, process_xmrig: &Arc>, + process_xp: &Arc>, process_p2pool: &Arc>, first_loop: &mut bool, handle_algo: &Arc>>>, pub_api_xmrig: &Arc>, + pub_api_xp: &Arc>, state_p2pool: &crate::disk::state::P2pool, state_xmrig: &crate::disk::state::Xmrig, + state_xp: &crate::disk::state::XmrigProxy, ) { // will check if the state can stay as it is. // p2pool and xmrig are alive if ready and running (syncing is not alive). let state = lock!(process).state; + let xp_is_alive = lock!(process_xp).state == ProcessState::Alive; + let msg_xmrig_or_proxy = if xp_is_alive { "Xmrig-Proxy" } else { "Xmrig" }; // if state is not alive, the algo should stop if it was running and p2pool should be used by xmrig. if let Some(handle) = lock!(handle_algo).as_ref() { if state != ProcessState::Alive && !handle.is_finished() { handle.abort(); output_console( - gui_api, + &mut lock!(gui_api).output, "XvB process can not completely continue, algorithm of distribution of HR is stopped.", + ProcessName::Xvb ); // only update xmrig if it is alive and wasn't on p2pool already. if lock!(gui_api).current_node != Some(XvbNode::P2pool) - && lock!(process_xmrig).state == ProcessState::Alive + && (lock!(process_xmrig).state == ProcessState::Alive || xp_is_alive) { - let token_xmrig = state_xmrig.token.clone(); + let token_xmrig = if xp_is_alive { + state_xp.token.clone() + } else { + state_xmrig.token.clone() + }; let address = state_p2pool.address.clone(); - let rig = state_xmrig.rig.clone(); - spawn(enc!((client, pub_api_xmrig, gui_api) async move { + let rig = if xp_is_alive { + "".to_string() + } else { + state_xmrig.rig.clone() + }; + spawn( + enc!((client, pub_api_xmrig, gui_api,pub_api_xp) async move { + let url_api = api_url_xmrig(xp_is_alive, true); + if let Err(err) = update_xmrig_config( + &client, + &url_api, + &token_xmrig, + &XvbNode::P2pool, + &address, + &rig + ) + .await + { + // show to console error about updating xmrig config + output_console( + &mut lock!(gui_api).output, + &format!( + "Failure to update {msg_xmrig_or_proxy} config with HTTP API.\nError: {}", + err + ), + ProcessName::Xvb + ); + } else { + if xp_is_alive {lock!(pub_api_xp).node = XvbNode::P2pool.to_string();} else {lock!(pub_api_xmrig).node = XvbNode::P2pool.to_string();} - if let Err(err) = PrivXmrigApi::update_xmrig_config( - &client, - XMRIG_CONFIG_URI, - &token_xmrig, - &XvbNode::P2pool, - &address, - &pub_api_xmrig, - &rig - ) - .await - { - // show to console error about updating xmrig config - output_console( - &gui_api, - &format!( - "Failure to update xmrig config with HTTP API.\nError: {}", - err - ), - ); - } else { - output_console( - &gui_api, - &format!("XvB process can not completely continue, falling back to {}", XvbNode::P2pool), - ); - } + output_console( + &mut lock!(gui_api).output, + &format!("XvB process can not completely continue, falling back to {}", XvbNode::P2pool), + ProcessName::Xvb + ); + } - })); + }), + ); } } } - let is_xmrig_alive = lock!(process_xmrig).state == ProcessState::Alive; + let is_xmrig_alive = lock!(process_xp).state == ProcessState::Alive + || lock!(process_xmrig).state == ProcessState::Alive; let is_p2pool_alive = lock!(process_p2pool).state == ProcessState::Alive; let p2pool_xmrig_alive = is_xmrig_alive && is_p2pool_alive; // if state is middle because start is not finished yet, it will not do anything. match state { ProcessState::Alive if !p2pool_xmrig_alive => { // they are not both alives, so state will be at syncing and data reset, state of loop also. - info!("XvB | stopped partially because XvB Nodes are not reachable."); + warn!("XvB | stopped partially because P2pool node or xmrig/xmrig-proxy are not reachable."); // stats must be empty put to default so the UI reflect that XvB private is not running. reset_data_xvb(pub_api, gui_api); // request from public API must be executed at next loop, do not wait for 1 minute. *first_loop = true; output_console( - gui_api, - "XvB is now partially stopped because p2pool node or xmrig came offline.\nCheck P2pool and Xmrig Tabs", + &mut lock!(gui_api).output, + "XvB is now partially stopped because p2pool node or XMRig/XMRig-Proxy came offline.\nCheck P2pool and Xmrig/Xmrig-Proxy Tabs", + ProcessName::Xvb ); - output_console(gui_api, XVB_PUBLIC_ONLY); + output_console( + &mut lock!(gui_api).output, + XVB_PUBLIC_ONLY, + ProcessName::Xvb, + ); lock!(process).state = ProcessState::Syncing; } ProcessState::Syncing if p2pool_xmrig_alive => { @@ -560,8 +624,14 @@ async fn check_state_outcauses_xvb( reset_data_xvb(pub_api, gui_api); *first_loop = true; output_console( - gui_api, - "XvB is now started because p2pool and xmrig came online.", + &mut lock!(gui_api).output, + &[ + "XvB is now started because p2pool and ", + msg_xmrig_or_proxy, + " came online.", + ] + .concat(), + ProcessName::Xvb, ); } ProcessState::Retry => { @@ -580,8 +650,11 @@ fn signal_interrupt( pub_api: &Arc>, gui_api: &Arc>, gui_api_xmrig: &Arc>, + gui_api_xp: &Arc>, state_p2pool: &crate::disk::state::P2pool, state_xmrig: &crate::disk::state::Xmrig, + state_xp: &crate::disk::state::XmrigProxy, + xp_alive: bool, ) -> bool { // Check SIGNAL // check if STOP or RESTART Signal is given. @@ -599,7 +672,11 @@ fn signal_interrupt( ); // insert the signal into output of XvB // This is written directly into the GUI API, because sometimes the 900ms event loop can't catch it. - output_console(gui_api, "\n\n\nXvB stopped\n\n\n"); + output_console( + &mut lock!(gui_api).output, + "\n\n\nXvB stopped\n\n\n", + ProcessName::Xvb, + ); debug!("XvB Watchdog | Stop SIGNAL done, breaking"); lock!(process).signal = ProcessSignal::None; lock!(process).state = ProcessState::Dead; @@ -620,8 +697,16 @@ fn signal_interrupt( ProcessSignal::UpdateNodes(node) => { if lock!(process).state != ProcessState::Waiting { warn!("received the UpdateNode signal"); - let token_xmrig = state_xmrig.token.clone(); - let rig = state_xmrig.rig.clone(); + let token_xmrig = if xp_alive { + state_xp.token.clone() + } else { + state_xmrig.token.clone() + }; + let rig = if xp_alive { + "".to_string() + } else { + state_xmrig.rig.clone() + }; let address = state_p2pool.address.clone(); // check if state is alive. If it is and it is receiving such a signal, it means something a node (XvB or P2Pool) has failed. // if XvB, xmrig needs to be switch to the other node (both will be checked though to be sure). @@ -634,9 +719,11 @@ fn signal_interrupt( lock!(process).state = ProcessState::Waiting; lock!(process).signal = ProcessSignal::None; spawn( - enc!((node, process, client, gui_api, pub_api, was_alive, address, token_xmrig, gui_api_xmrig) async move { + enc!((node, process, client, gui_api, pub_api, was_alive, address, token_xmrig, gui_api_xmrig, gui_api_xp) async move { + warn!("in spawn of UpdateNodes"); match node { XvbNode::NorthAmerica|XvbNode::Europe if was_alive => { + warn!("current is XvB node ? update to see which is available"); // a node is failing. We need to first verify if a node is available XvbNode::update_fastest_node(&client, &gui_api, &pub_api, &process).await; if lock!(process).state == ProcessState::OfflineNodesAll { @@ -655,29 +742,43 @@ fn signal_interrupt( }, XvbNode::NorthAmerica|XvbNode::Europe if !was_alive => { + lock!(process).state = ProcessState::Syncing; // Probably a start. We don't consider XMRig using XvB nodes without algo. // can update xmrig and check status of state in the same time. // Need to set XMRig to P2Pool if it wasn't. XMRig should have populated this value at his start. if lock!(gui_api).current_node != Some(XvbNode::P2pool) { - spawn(enc!((client, token_xmrig, address, gui_api_xmrig, gui_api) async move{ - if let Err(err) = PrivXmrigApi::update_xmrig_config( + spawn(enc!((client, token_xmrig, address, gui_api_xmrig, gui_api_xp, gui_api) async move{ + let url_api = api_url_xmrig(xp_alive, true); + warn!("update xmrig to use node ?"); + if let Err(err) = update_xmrig_config( &client, - XMRIG_CONFIG_URI, + &url_api, &token_xmrig, &XvbNode::P2pool, &address, - &gui_api_xmrig, &rig ) .await { + let msg_xmrig_or_proxy = if xp_alive { + "XMRig-Proxy" + } else { + "XMRig" + }; output_console( - &gui_api, + &mut lock!(gui_api).output, &format!( - "Failure to update xmrig config with HTTP API.\nError: {}", + "Failure to update {msg_xmrig_or_proxy} config with HTTP API.\nError: {}", err - ), + ), ProcessName::Xvb ); + } else { + // update node xmrig + if xp_alive { + lock!(gui_api_xp).node = XvbNode::P2pool.to_string(); + } else { + lock!(gui_api_xmrig).node = XvbNode::P2pool.to_string(); + }; } } ));} @@ -706,20 +807,6 @@ fn reset_data_xvb(pub_api: &Arc>, gui_api: &Arc String { - format!("[{}] ", Local::now().format("%Y-%m-%d %H:%M:%S%.3f")) -} -pub fn output_console(gui_api: &Arc>, msg: &str) { - if let Err(e) = writeln!(lock!(gui_api).output, "{}{msg}", datetimeonsole()) { - error!("XvB Watchdog | GUI status write failed: {}", e); - } -} -pub fn output_console_without_time(gui_api: &Arc>, msg: &str) { - if let Err(e) = writeln!(lock!(gui_api).output, "{msg}") { - error!("XvB Watchdog | GUI status write failed: {}", e); - } -} fn update_indicator_algo( is_algo_started_once: bool, is_algo_finished: bool, @@ -756,3 +843,41 @@ fn update_indicator_algo( lock!(pub_api).stats_priv.msg_indicator = "Algorithm is not running".to_string(); } } + +// quick temporary function before refactor, but better than repeating this code +// if xp is alive, put true +// to get config url, true. False for summary +pub fn api_url_xmrig(xp: bool, config: bool) -> String { + if xp { + if config { + XMRIG_PROXY_CONFIG_URL.to_string() + } else { + XMRIG_PROXY_SUMMARY_URL.to_string() + } + } else if config { + XMRIG_CONFIG_URL.to_string() + } else { + XMRIG_SUMMARY_URL.to_string() + } +} +// get the current HR of xmrig or xmrig-proxy +// will get a longer average HR since it will be more accurate. Shorter timeframe can induce volatility. +fn current_controllable_hr( + xp_alive: bool, + gui_api_xp: &Arc>, + gui_api_xmrig: &Arc>, +) -> f32 { + if xp_alive { + if lock!(gui_api_xp).hashrate_10m > 0.0 { + lock!(gui_api_xp).hashrate_10m + } else { + lock!(gui_api_xp).hashrate_1m + } + } else if lock!(gui_api_xmrig).hashrate_raw_15m > 0.0 { + lock!(gui_api_xmrig).hashrate_raw_15m + } else if lock!(gui_api_xmrig).hashrate_raw_1m > 0.0 { + lock!(gui_api_xmrig).hashrate_raw_1m + } else { + lock!(gui_api_xmrig).hashrate_raw + } +} diff --git a/src/helper/xvb/nodes.rs b/src/helper/xvb/nodes.rs index 88042b2..da7bb40 100644 --- a/src/helper/xvb/nodes.rs +++ b/src/helper/xvb/nodes.rs @@ -10,7 +10,7 @@ use tokio::spawn; use crate::{ components::node::{GetInfo, TIMEOUT_NODE_PING}, - helper::{xvb::output_console, Process, ProcessState}, + helper::{xvb::output_console, Process, ProcessName, ProcessState}, macros::lock, GUPAX_VERSION_UNDERSCORE, XVB_NODE_EU, XVB_NODE_NA, XVB_NODE_PORT, XVB_NODE_RPC, }; @@ -25,6 +25,8 @@ pub enum XvbNode { Europe, #[display(fmt = "Local P2pool")] P2pool, + #[display(fmt = "Xmrig Proxy")] + XmrigProxy, } impl XvbNode { pub fn url(&self) -> String { @@ -32,12 +34,14 @@ impl XvbNode { Self::NorthAmerica => String::from(XVB_NODE_NA), Self::Europe => String::from(XVB_NODE_EU), Self::P2pool => String::from("127.0.0.1"), + Self::XmrigProxy => String::from("127.0.0.1"), } } pub fn port(&self) -> String { match self { Self::NorthAmerica | Self::Europe => String::from(XVB_NODE_PORT), Self::P2pool => String::from("3333"), + Self::XmrigProxy => String::from("3355"), } } pub fn user(&self, address: &str) -> String { @@ -45,6 +49,7 @@ impl XvbNode { Self::NorthAmerica => address.chars().take(8).collect(), Self::Europe => address.chars().take(8).collect(), Self::P2pool => GUPAX_VERSION_UNDERSCORE.to_string(), + Self::XmrigProxy => GUPAX_VERSION_UNDERSCORE.to_string(), } } pub fn tls(&self) -> bool { @@ -52,6 +57,7 @@ impl XvbNode { Self::NorthAmerica => true, Self::Europe => true, Self::P2pool => false, + Self::XmrigProxy => false, } } pub fn keepalive(&self) -> bool { @@ -59,6 +65,7 @@ impl XvbNode { Self::NorthAmerica => true, Self::Europe => true, Self::P2pool => false, + Self::XmrigProxy => false, } } @@ -105,16 +112,18 @@ impl XvbNode { // if both nodes are dead, then the state of the process must be NodesOffline info!("XvB node ping, all offline or ping failed, switching back to local p2pool",); output_console( - gui_api_xvb, + &mut lock!(gui_api_xvb).output, "XvB node ping, all offline or ping failed, switching back to local p2pool", + ProcessName::Xvb, ); lock!(process_xvb).state = ProcessState::OfflineNodesAll; } else { // if node is up and because update_fastest is used only if token/address is valid, it means XvB process is Alive. info!("XvB node ping, both online and best is {}", node.url()); output_console( - gui_api_xvb, + &mut lock!(gui_api_xvb).output, &format!("XvB node ping, {} is selected as the fastest.", node), + ProcessName::Xvb, ); info!("ProcessState to Syncing after finding joignable node"); // could be used by xmrig who signal that a node is not joignable diff --git a/src/helper/xvb/priv_stats.rs b/src/helper/xvb/priv_stats.rs index 82ac577..afae6a1 100644 --- a/src/helper/xvb/priv_stats.rs +++ b/src/helper/xvb/priv_stats.rs @@ -10,7 +10,7 @@ use serde::Deserialize; use tokio::time::sleep; use crate::{ - helper::{xvb::output_console, Process, ProcessState}, + helper::{xvb::output_console, Process, ProcessName, ProcessState}, macros::lock, XVB_URL, }; @@ -92,14 +92,16 @@ impl XvbPrivStats { XVB_URL, err ); output_console( - gui_api, + &mut lock!(gui_api).output, &format!("Failure to retrieve private stats from {}", XVB_URL), + ProcessName::Xvb, ); lock!(process).state = ProcessState::Retry; // sleep here because it is in a spawn and will not block the user stopping or restarting the service. output_console( - gui_api, + &mut lock!(gui_api).output, "Waiting 10 seconds before trying to get stats again.", + ProcessName::Xvb, ); sleep(Duration::from_secs(10)).await; } diff --git a/src/helper/xvb/public_stats.rs b/src/helper/xvb/public_stats.rs index b821cb2..90ae8ad 100644 --- a/src/helper/xvb/public_stats.rs +++ b/src/helper/xvb/public_stats.rs @@ -10,7 +10,7 @@ use serde_this_or_that::as_u64; use tokio::time::sleep; use crate::{ - helper::{xvb::output_console, Process, ProcessState}, + helper::{xvb::output_console, Process, ProcessName, ProcessState}, macros::lock, XVB_URL_PUBLIC_API, }; @@ -69,8 +69,9 @@ impl XvbPubStats { if lock!(process).state == ProcessState::Retry { lock!(process).state = ProcessState::Syncing; output_console( - gui_api, + &mut lock!(gui_api).output, "Stats are now working again after last successful request.", + ProcessName::Xvb, ); } } @@ -83,19 +84,21 @@ impl XvbPubStats { // if error already present, no need to print it multiple times. if lock!(process).state != ProcessState::Retry { output_console( - gui_api, + &mut lock!(gui_api).output, &format!( "Failure to retrieve public stats from {}\nWill retry shortly...", XVB_URL_PUBLIC_API ), + ProcessName::Xvb, ); } // we stop the algo (will be stopped by the check status on next loop) because we can't make the rest work without public stats. (winner in xvb private stats). lock!(process).state = ProcessState::Retry; // sleep here because it is in a spawn and will not block the user stopping or restarting the service. output_console( - gui_api, + &mut lock!(gui_api).output, "Waiting 10 seconds before trying to get stats again.", + ProcessName::Xvb, ); sleep(Duration::from_secs(10)).await; } diff --git a/src/inits.rs b/src/inits.rs index bf651e4..c6583ae 100644 --- a/src/inits.rs +++ b/src/inits.rs @@ -214,6 +214,23 @@ pub fn init_auto(app: &mut App) { } else { info!("Skipping auto-xmrig..."); } + // [Auto-XMRig-Proxy] + if app.state.gupax.auto_xp { + if !Gupax::path_is_file(&app.state.gupax.xmrig_proxy_path) { + warn!("Gupaxx | Xmrig-Proxy path is not a file! Skipping auto-xmrig_proxy..."); + } else if !crate::components::update::check_xp_path(&app.state.gupax.xmrig_proxy_path) { + warn!("Gupaxx | Xmrig-Proxy path is not valid! Skipping auto-xmrig_proxy..."); + } else { + Helper::start_xp( + &app.helper, + &app.state.xmrig_proxy, + &app.state.xmrig, + &app.state.gupax.absolute_xp_path, + ); + } + } else { + info!("Skipping auto-XMRig-Proxy..."); + } // [Auto-XvB] if app.state.gupax.auto_xvb { Helper::start_xvb( @@ -221,6 +238,7 @@ pub fn init_auto(app: &mut App) { &app.state.xvb, &app.state.p2pool, &app.state.xmrig, + &app.state.xmrig_proxy, ); } else { info!("Skipping auto-xvb..."); diff --git a/src/miscs.rs b/src/miscs.rs index d70af11..87428b9 100644 --- a/src/miscs.rs +++ b/src/miscs.rs @@ -1,6 +1,7 @@ //---------------------------------------------------------------------------------------------------- Misc functions // Get absolute [Gupax] binary path +use std::fmt::Write; #[cold] #[inline(never)] pub fn get_exe() -> Result { @@ -128,7 +129,9 @@ pub fn cmp_f64(a: f64, b: f64) -> std::cmp::Ordering { // Free functions. use crate::disk::gupax_p2pool_api::GupaxP2poolApi; +use crate::helper::ProcessName; use crate::utils::macros::lock; +use chrono::Local; use log::error; use log::warn; use regex::Regex; @@ -155,3 +158,16 @@ pub fn clamp_scale(scale: f32) -> f32 { // Clamp between valid range. scale.clamp(APP_MIN_SCALE, APP_MAX_SCALE) } +pub fn output_console(output: &mut String, msg: &str, p_name: ProcessName) { + if let Err(e) = writeln!(output, "{}{msg}", datetimeonsole()) { + error!("{} Watchdog | GUI status write failed: {}", p_name, e); + } +} +pub fn output_console_without_time(output: &mut String, msg: &str, p_name: ProcessName) { + if let Err(e) = writeln!(output, "{msg}") { + error!("{} Watchdog | GUI status write failed: {}", p_name, e); + } +} +fn datetimeonsole() -> String { + format!("[{}] ", Local::now().format("%Y-%m-%d %H:%M:%S%.3f")) +} diff --git a/src/utils/constants.rs b/src/utils/constants.rs index f6ad7a0..54a74c8 100644 --- a/src/utils/constants.rs +++ b/src/utils/constants.rs @@ -18,6 +18,7 @@ pub const GUPAX_VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); // e.g: v1.0.0 pub const P2POOL_VERSION: &str = "v3.10"; pub const XMRIG_VERSION: &str = "v6.21.1"; +pub const XMRIG_PROXY_VERSION: &str = "v6.21.1"; pub const COMMIT: &str = env!("COMMIT"); // set in build.rs // e.g: Gupax_v1_0_0 // Would have been [Gupax_v1.0.0] but P2Pool truncates everything after [.] @@ -85,8 +86,13 @@ pub const P2POOL_API_PATH_LOCAL: &str = "local/stratum"; pub const P2POOL_API_PATH_NETWORK: &str = "network/stats"; #[cfg(target_family = "unix")] pub const P2POOL_API_PATH_POOL: &str = "pool/stats"; -pub const XMRIG_API_URI: &str = "1/summary"; // The default relative URI of XMRig's API -pub const XMRIG_CONFIG_URI: &str = "http://127.0.0.1:18088/1/config"; // The default relative URI of XMRig's API config +pub const XMRIG_API_SUMMARY_URI: &str = "1/summary"; // The default relative URI of XMRig's API summary + // pub const XMRIG_API_CONFIG_URI: &str = "1/config"; // The default relative URI of XMRig's API config + // todo allow user to change the port of the http api for xmrig and xmrig-proxy +pub const XMRIG_CONFIG_URL: &str = "http://127.0.0.1:18088/1/config"; // The default relative URI of XMRig's API config +pub const XMRIG_PROXY_CONFIG_URL: &str = "http://127.0.0.1:18089/1/config"; // The default relative URI of XMRig Proxy's API config +pub const XMRIG_SUMMARY_URL: &str = "http://127.0.0.1:18088/1/summary"; // The default relative URI of XMRig's API config +pub const XMRIG_PROXY_SUMMARY_URL: &str = "http://127.0.0.1:18089/1/summary"; // The default relative URI of XMRig Proxy's API config // Process state tooltips (online, offline, etc) pub const P2POOL_ALIVE: &str = "P2Pool is online and fully synchronized"; @@ -102,6 +108,35 @@ pub const XMRIG_FAILED: &str = "XMRig is offline and failed when exiting"; pub const XMRIG_MIDDLE: &str = "XMRig is in the middle of (re)starting/stopping"; pub const XMRIG_NOT_MINING: &str = "XMRig is online, but not mining to any pool"; +pub const XMRIG_PROXY_ALIVE: &str = "XMRig-Proxy is online and mining"; +pub const XMRIG_PROXY_DEAD: &str = "XMRig-Proxy is offline"; +pub const XMRIG_PROXY_FAILED: &str = "XMRig-Proxy is offline and failed when exiting"; +pub const XMRIG_PROXY_MIDDLE: &str = "XMRig-Proxy is in the middle of (re)starting/stopping"; +pub const XMRIG_PROXY_NOT_MINING: &str = "XMRig-Proxy is online, but not mining to any pool"; +pub const XMRIG_PROXY_REDIRECT: &str = "point local xmrig instance on this proxy instead of the p2pool instance (recommended if using XvB)"; +pub const XMRIG_PROXY_ARGUMENTS: &str = r#"WARNING: Use [--no-color] and make sure to set [--http-host ] & [--http-port ] so that the [Status] tab can work! + +Start XMRig-Proxy with these arguments"#; +pub const XMRIG_PROXY_INPUT: &str = "Send a command to XMRig-Proxy"; +pub const XMRIG_PROXY_SIMPLE: &str = r#"Use simple XMRig-Proxy settings: + - Mine to local P2Pool (localhost:3333) + - redirect Xmrig local instance to the proxy + - HTTP API @ localhost:18089"#; +pub const XMRIG_PROXY_ADVANCED: &str = r#"Use advanced XMRig-Proxy settings: + - Terminal input + - disable/enable local xmrig instance redirection + - Overriding command arguments + - Custom HTTP API IP/Port + - TLS setting + - Keepalive setting"#; +pub const XMRIG_PROXY_PATH_NOT_FILE: &str = "XMRig-Proxy binary not found at the given PATH in the Gupaxx tab! To fix: goto the [Gupaxx Advanced] tab, select [Open] and specify where XMRig-Proxy is located."; +pub const XMRIG_PROXY_PATH_NOT_VALID: &str = "XMRig-Proxy binary at the given PATH in the Gupaxx tab doesn't look like XMRig-Proxy! To fix: goto the [Gupaxx Advanced] tab, select [Open] and specify where XMRig-Proxy is located."; +pub const XMRIG_PROXY_PATH_OK: &str = "XMRig-Proxy was found at the given PATH"; +pub const XMRIG_PROXY_PATH_EMPTY: &str = "XMRig-Proxy PATH is empty! To fix: goto the [GupaxxAdvanced] tab, select [Open] and specify where XMRig is located."; +pub const STATUS_XMRIG_PROXY_UPTIME: &str = "How long XMRig-Proxy has been online"; +pub const STATUS_XMRIG_PROXY_POOL: &str = "The pool XMRig-Proxy is currently mining to"; +pub const STATUS_XMRIG_PROXY_HASHRATE: &str = "The average hashrate of XMRig-Proxy"; + pub const XVB_ALIVE: &str = "XvB process is configured and distributing hashrate, XvB node is online"; pub const XVB_DEAD: &str = "XvB process is offline"; @@ -186,7 +221,6 @@ pub const STATUS_P2POOL_POOL: &str = "The P2Pool sidechain you're currently conn pub const STATUS_P2POOL_ADDRESS: &str = "The Monero address P2Pool will send payouts to"; //-- pub const STATUS_XMRIG_UPTIME: &str = "How long XMRig has been online"; -pub const STATUS_XMRIG_CPU: &str = "The average CPU load of XMRig. [1.0] represents 1 thread is maxed out, e.g: If you have 8 threads, [4.0] means half your threads are maxed out."; pub const STATUS_XMRIG_HASHRATE: &str = "The average hashrate of XMRig"; pub const STATUS_XMRIG_DIFFICULTY: &str = "The current difficulty of the job XMRig is working on"; pub const STATUS_XMRIG_SHARES: &str = "The amount of accepted and rejected shares"; @@ -278,6 +312,7 @@ pub const GUPAX_ASK_BEFORE_QUIT: &str = "Ask before quitting Gupaxx"; pub const GUPAX_SAVE_BEFORE_QUIT: &str = "Automatically save any changed settings before quitting"; pub const GUPAX_AUTO_P2POOL: &str = "Automatically start P2Pool on Gupaxx startup. If you are using [P2Pool Simple], this will NOT wait for your [Auto-Ping] to finish, it will start P2Pool on the pool you already have selected. This option will fail if your P2Pool settings aren't valid!"; pub const GUPAX_AUTO_XMRIG: &str = "Automatically start XMRig on Gupaxx startup. This option will fail if your XMRig settings aren't valid!"; +pub const GUPAX_AUTO_XMRIG_PROXY: &str = "Automatically start XMRig-Proxy on Gupaxx startup."; pub const GUPAX_AUTO_XVB: &str = "Automatically start XvB on Gupaxx startup. This option will fail if your XvB settings aren't valid!"; pub const GUPAX_ADJUST: &str = "Adjust and set the width/height of the Gupaxx window"; pub const GUPAX_WIDTH: &str = "Set the width of the Gupaxx window"; @@ -310,6 +345,7 @@ pub const GUPAX_ADVANCED: &str = r#"Use advanced Gupaxx settings: pub const GUPAX_SELECT: &str = "Open a file explorer to select a file"; pub const GUPAX_PATH_P2POOL: &str = "The location of the P2Pool binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path"; pub const GUPAX_PATH_XMRIG: &str = "The location of the XMRig binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path"; +pub const GUPAX_PATH_XMRIG_PROXY: &str = "The location of the XMRig-Proxy binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path"; // P2Pool pub const P2POOL_MAIN: &str = "Use the P2Pool main-chain. This P2Pool finds blocks faster, but has a higher difficulty. Suitable for miners with more than 50kH/s"; diff --git a/src/utils/regex.rs b/src/utils/regex.rs index 2eb4079..ac747f9 100644 --- a/src/utils/regex.rs +++ b/src/utils/regex.rs @@ -17,7 +17,7 @@ // Some regexes used throughout Gupax. -use log::error; +use log::{error, warn}; use once_cell::sync::Lazy; use regex::Regex; @@ -116,13 +116,22 @@ impl P2poolRegex { pub struct XmrigRegex { pub not_mining: Regex, pub new_job: Regex, + pub timeout: Regex, + pub valid_conn: Regex, + pub invalid_conn: Regex, + pub error: Regex, } impl XmrigRegex { fn new() -> Self { Self { not_mining: Regex::new("no active pools, stop mining").unwrap(), + timeout: Regex::new("timeout").unwrap(), new_job: Regex::new("new job").unwrap(), + valid_conn: Regex::new("upstreams active: 1").unwrap(), + invalid_conn: Regex::new("error: 1").unwrap(), + // we don't want to include connections status from xmrig-proxy that show the number of errors + error: Regex::new(r"error: \D").unwrap(), } } } @@ -154,11 +163,11 @@ pub fn nb_current_shares(s: &str) -> Option { } pub fn detect_new_node_xmrig(s: &str) -> Option { static CURRENT_SHARE: Lazy = - Lazy::new(|| Regex::new(r"net use pool (?P.*?) ").unwrap()); + Lazy::new(|| Regex::new(r"use pool (?P.*?) ").unwrap()); if let Some(c) = CURRENT_SHARE.captures(s) { if let Some(m) = c.name("pool") { match m.as_str() { - // if user change address of local p2pool, it could create issue ? + // if user change address of local p2pool, it could create issue "127.0.0.1:3333" => { return Some(XvbNode::P2pool); } @@ -172,7 +181,7 @@ pub fn detect_new_node_xmrig(s: &str) -> Option { } } } - error!("a line on xmrig console was detected as using a new pool but the syntax was not recognized."); + warn!("a line on xmrig console was detected as using a new pool but the syntax was not recognized or it was not a pool useable for the algorithm."); None } pub fn estimated_hr(s: &str) -> Option { @@ -210,6 +219,11 @@ pub fn estimated_hr(s: &str) -> Option { } None } + +pub fn contains_timeout(l: &str) -> bool { + static LINE_SHARE: Lazy = Lazy::new(|| Regex::new(r"timeout").unwrap()); + LINE_SHARE.is_match(l) +} pub fn contains_error(l: &str) -> bool { static LINE_SHARE: Lazy = Lazy::new(|| Regex::new(r"error").unwrap()); LINE_SHARE.is_match(l)