diff --git a/src/app/panels/middle/mod.rs b/src/app/panels/middle/mod.rs index 9c68888..ac66d73 100644 --- a/src/app/panels/middle/mod.rs +++ b/src/app/panels/middle/mod.rs @@ -161,7 +161,7 @@ path_xmr: {:#?}\n } 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); + crate::disk::state::Xvb::show(&mut self.state.xvb, self.size, &self.state.p2pool.address, ctx, ui, &self.xvb_api, lock!(self.xvb).is_alive()); } } }); diff --git a/src/app/panels/middle/status/processes.rs b/src/app/panels/middle/status/processes.rs index 1933186..d0b6098 100644 --- a/src/app/panels/middle/status/processes.rs +++ b/src/app/panels/middle/status/processes.rs @@ -361,7 +361,7 @@ fn xvb( xvb_api: &Arc>, ) { // - let api = lock!(xvb_api); + let api = &lock!(xvb_api).stats_pub; let enabled = xvb_alive; ScrollArea::vertical().show(ui, |ui| { ui.group(|ui| { @@ -477,7 +477,6 @@ fn xvb( )), ); } - drop(api); }); // by round }); diff --git a/src/app/panels/middle/xvb.rs b/src/app/panels/middle/xvb.rs index 7c07cb6..d22ec74 100644 --- a/src/app/panels/middle/xvb.rs +++ b/src/app/panels/middle/xvb.rs @@ -1,11 +1,14 @@ use std::sync::{Arc, Mutex}; use egui::TextStyle::Name; -use egui::{Hyperlink, Image, Label, RichText, TextEdit, Vec2}; +use egui::{vec2, Hyperlink, Image, Layout, RichText, TextEdit, Ui, Vec2}; use log::debug; use crate::helper::xvb::PubXvbApi; -use crate::utils::constants::{GREEN, LIGHT_GRAY, ORANGE, RED, XVB_HELP, XVB_TOKEN_LEN}; +use crate::utils::constants::{ + GREEN, LIGHT_GRAY, ORANGE, RED, XVB_DONATED_1H_FIELD, XVB_DONATED_24H_FIELD, XVB_FAILURE_FIELD, + XVB_HELP, XVB_HERO_SELECT, XVB_TOKEN_FIELD, XVB_TOKEN_LEN, +}; use crate::utils::macros::lock; use crate::utils::regex::Regexes; use crate::{ @@ -22,12 +25,13 @@ impl crate::disk::state::Xvb { _ctx: &egui::Context, ui: &mut egui::Ui, api: &Arc>, + xvb_is_alive: bool, ) { + ui.reset_style(); let website_height = size.y / 10.0; // let width = size.x - SPACE; // let height = size.y - SPACE; let width = size.x; - let text_edit = size.y / 25.0; // logo and website link ui.vertical_centered(|ui| { ui.add_sized( @@ -61,43 +65,103 @@ impl crate::disk::state::Xvb { }); // input token let len_token = format!("{}", self.token.len()); - let text_check; - let color; - if self.token.is_empty() { - text_check = format!("[{}/{}] ➖", len_token, XVB_TOKEN_LEN); - color = LIGHT_GRAY; + let (text, color) = if self.token.is_empty() { + ( + format!("{} [{}/{}] ➖", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN), + LIGHT_GRAY, + ) } else if self.token.parse::().is_ok() && self.token.len() < XVB_TOKEN_LEN { - text_check = format!("[{}/{}] ", len_token, XVB_TOKEN_LEN); - color = GREEN; + ( + format!("{} [{}/{}]", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN), + GREEN, + ) } else if self.token.parse::().is_ok() && self.token.len() == XVB_TOKEN_LEN { - text_check = "✔".to_string(); - color = GREEN; + (format!("{} ✔", XVB_TOKEN_FIELD), GREEN) } else { - text_check = format!("[{}/{}] ❌", len_token, XVB_TOKEN_LEN); - color = RED; - } + ( + format!("{} [{}/{}] ❌", XVB_TOKEN_FIELD, len_token, XVB_TOKEN_LEN), + RED, + ) + }; + // let width = width - SPACE; + // ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0); ui.group(|ui| { - let width = width - SPACE; - ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0); - ui.label("Your Token:"); ui.horizontal(|ui| { - ui.add_sized( - [width / 8.0, text_edit], - TextEdit::singleline(&mut self.token), - ); - - ui.add(Label::new(RichText::new(text_check).color(color))) - }); - }) - .response - .on_hover_text_at_pointer(XVB_HELP); + // why does this group is not centered into the parent group ? + ui.with_layout(Layout::left_to_right(egui::Align::Center), |ui| { + ui.group(|ui| { + ui.colored_label(color, text); + // ui.add_sized( + // [width / 8.0, text_edit], + // Label::new(RichText::new(text).color(color)), + // ); + ui.add( + TextEdit::singleline(&mut self.token) + .char_limit(XVB_TOKEN_LEN) + .desired_width(width / 8.0) + .vertical_align(egui::Align::Center), + ); + }) + .response + .on_hover_text_at_pointer(XVB_HELP); + // hero option + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.add_space(width / 24.0); + ui.checkbox(&mut self.hero, "Hero") + .on_hover_text(XVB_HERO_SELECT); + }) + }); + }) + }); // need to warn the user if no address is set in p2pool tab if !Regexes::addr_ok(address) { debug!("XvB Tab | Rendering warning text"); ui.label(RichText::new("You don't have any payout address set in the P2pool Tab !\nXvB process needs one to function properly.") .color(ORANGE)); } - // hero option // private stats + let priv_stats = &lock!(api).stats_priv; + ui.set_enabled(xvb_is_alive); + // ui.vertical_centered(|ui| { + ui.horizontal(|ui| { + // widget takes a third less space for two separator. + let width_stat = + (ui.available_width() / 3.0) - (12.0 + ui.style().spacing.item_spacing.x) / 3.0; + // 0.0 means minimum + let height_stat = 0.0; + let size_stat = vec2(width_stat, height_stat); + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.group(|ui| { + let size_stat = vec2( + ui.available_width(), + 0.0, // + ui.spacing().item_spacing.y, + ); + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.vertical_centered(|ui| { + ui.label(XVB_FAILURE_FIELD); + ui.label(priv_stats.fails.to_string()); + }) + .response + }); + ui.separator(); + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.vertical_centered(|ui| { + ui.label(XVB_DONATED_1H_FIELD); + ui.label(priv_stats.donor_1hr_avg.to_string()); + }) + .response + }); + ui.separator(); + ui.add_sized(size_stat, |ui: &mut Ui| { + ui.vertical_centered(|ui| { + ui.label(XVB_DONATED_24H_FIELD); + ui.label(priv_stats.donor_24hr_avg.to_string()); + }) + .response + }); + }) + .response + }); + }); } } diff --git a/src/disk/state.rs b/src/disk/state.rs index 968ca1e..6e377ba 100644 --- a/src/disk/state.rs +++ b/src/disk/state.rs @@ -1,6 +1,7 @@ -use anyhow::{bail, Result}; -use hyper::StatusCode; -use hyper_tls::HttpsConnector; +use anyhow::{Result}; + + + use super::*; use crate::{components::node::RemoteNode, disk::status::*}; @@ -246,6 +247,7 @@ pub struct Xmrig { #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, Default)] pub struct Xvb { pub token: String, + pub hero: bool, pub node: XvbNode, } @@ -328,36 +330,6 @@ impl Default for P2pool { } } -impl Xvb { - pub async fn is_token_exist(address: String, token: String) -> Result<()> { - let https = HttpsConnector::new(); - let client = hyper::Client::builder().build(https); - if let Ok(request) = hyper::Request::builder() - .method("GET") - .uri(format!( - "{}/cgi-bin/p2pool_bonus_history_api.cgi?address={}&token={}", - XVB_URL, address, token - )) - .body(hyper::Body::empty()) - { - match client.request(request).await { - Ok(resp) => match resp.status() { - StatusCode::OK => Ok(()), - StatusCode::UNPROCESSABLE_ENTITY => { - bail!("the token is invalid for this xmr address.") - } - _ => bail!("The status of the response is not expected"), - }, - Err(err) => { - bail!("error from response: {}", err) - } - } - } else { - bail!("request could not be build") - } - } -} - impl Xmrig { fn with_threads(max_threads: usize, current_threads: usize) -> Self { let xmrig = Self::default(); diff --git a/src/disk/tests.rs b/src/disk/tests.rs index f0f06af..3511665 100644 --- a/src/disk/tests.rs +++ b/src/disk/tests.rs @@ -99,6 +99,7 @@ mod test { [xvb] token = "" + hero = false node = "Europe" [version] gupax = "v1.3.0" diff --git a/src/helper/xvb.rs b/src/helper/xvb.rs index 5bd7937..6d2b918 100644 --- a/src/helper/xvb.rs +++ b/src/helper/xvb.rs @@ -1,5 +1,8 @@ +use anyhow::{bail, Result}; +use bytes::Bytes; use derive_more::Display; use hyper::client::HttpConnector; +use hyper::StatusCode; use hyper_tls::HttpsConnector; use log::{debug, error, info, warn}; use serde::Deserialize; @@ -10,8 +13,8 @@ use std::{ time::Instant, }; +use crate::utils::constants::XVB_URL; use crate::{ - disk::state::Xvb, helper::{ProcessSignal, ProcessState}, utils::{ constants::{HORI_CONSOLE, XVB_URL_PUBLIC_API}, @@ -69,8 +72,8 @@ impl Helper { let gui_api = Arc::clone(&lock!(helper).gui_api_xvb); let pub_api = Arc::clone(&lock!(helper).pub_api_xvb); let process = Arc::clone(&lock!(helper).xvb); - let state_xvb = state_xvb.clone(); - let state_p2pool = state_p2pool.clone(); + let state_xvb_check = state_xvb.clone(); + let state_p2pool_check = state_p2pool.clone(); // 2. Set process state debug!("XvB | Setting process state..."); @@ -83,7 +86,7 @@ impl Helper { // verify if token and address are existent on XvB server let rt = tokio::runtime::Runtime::new().unwrap(); let resp: anyhow::Result<()> = rt.block_on(async move { - Xvb::is_token_exist(state_p2pool.address, state_xvb.token).await?; + XvbPrivStats::request_api(&state_p2pool_check.address, &state_xvb_check.token).await?; Ok(()) }); match resp { @@ -105,9 +108,17 @@ impl Helper { lock2!(helper, xvb).state = ProcessState::NotMining; } } - + let state_xvb_thread = state_xvb.clone(); + let state_p2pool_thread = state_p2pool.clone(); thread::spawn(move || { - Self::spawn_xvb_watchdog(client, gui_api, pub_api, process); + Self::spawn_xvb_watchdog( + client, + gui_api, + pub_api, + process, + &state_xvb_thread, + &state_p2pool_thread, + ); }); } #[tokio::main] @@ -116,6 +127,8 @@ impl Helper { gui_api: Arc>, pub_api: Arc>, process: Arc>, + state_xvb: &crate::disk::state::Xvb, + state_p2pool: &crate::disk::state::P2pool, ) { info!("XvB started"); @@ -142,14 +155,11 @@ impl Helper { // if since is 0, send request because it's the first time. let since = lock!(gui_api).tick; if since >= 60 || since == 0 { - // *lock!(pub_api) = PubXvbApi::new(); - // *lock!(gui_api) = PubXvbApi::new(); - debug!("XvB Watchdog | Attempting HTTP API request..."); - match PubXvbApi::request_xvb_public_api(client.clone(), XVB_URL_PUBLIC_API).await { + debug!("XvB Watchdog | Attempting HTTP public API request..."); + match XvbPubStats::request_api(client.clone()).await { Ok(new_data) => { debug!("XvB Watchdog | HTTP API request OK"); - *lock!(&pub_api) = new_data; - lock!(gui_api).tick += 0; + lock!(&pub_api).stats_pub = new_data; } Err(err) => { warn!( @@ -168,6 +178,45 @@ impl Helper { break; } } + debug!("XvB Watchdog | Attempting HTTP private API request..."); + match XvbPrivStats::request_api(&state_p2pool.address, &state_xvb.token).await { + Ok(b) => { + debug!("XvB Watchdog | HTTP API request OK"); + let new_data = match serde_json::from_slice::(&b) { + Ok(data) => data, + Err(e) => { + warn!("XvB Watchdog | Data provided from private API is not deserializ-able.Error: {}", e); + // output the error to console + if let Err(e) = writeln!( + lock!(gui_api).output, + "XvB Watchdog | Data provided from private API is not deserializ-able.Error: {}", e + ) { + error!("XvB Watchdog | GUI status write failed: {}", e); + } + break; + } + }; + lock!(&pub_api).stats_priv = new_data; + } + Err(err) => { + warn!( + "XvB Watchdog | Could not send HTTP private API request to: {}\n:{}", + XVB_URL, err + ); + // output the error to console + if let Err(e) = writeln!( + lock!(gui_api).output, + "Failure to retrieve private stats from {}", + XVB_URL + ) { + error!("XvB Watchdog | GUI status write failed: {}", e); + } + lock!(process).state = ProcessState::Failed; + break; + } + } + + lock!(gui_api).tick += 0; } lock!(gui_api).tick += 1; @@ -188,14 +237,17 @@ impl Helper { } //---------------------------------------------------------------------------------------------------- Public XvB API use serde_this_or_that::as_u64; -#[derive(Debug, Clone, Default, Deserialize)] +#[derive(Debug, Clone, Default)] pub struct PubXvbApi { - #[serde(skip)] pub output: String, - #[serde(skip)] pub uptime: HumanTime, - #[serde(skip)] pub tick: u8, + pub stats_pub: XvbPubStats, + pub stats_priv: XvbPrivStats, +} + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct XvbPubStats { pub time_remain: u32, // remaining time of round in minutes pub bonus_hr: f64, pub donate_hr: f64, // donated hr from all donors @@ -217,6 +269,62 @@ pub struct PubXvbApi { pub reward_yearly: Vec, } +impl XvbPubStats { + #[inline] + // Send an HTTP request to XvB's API, serialize it into [Self] and return it + async fn request_api( + client: hyper::Client>, + ) -> std::result::Result { + let request = hyper::Request::builder() + .method("GET") + .uri(XVB_URL_PUBLIC_API) + .body(hyper::Body::empty())?; + let response = + tokio::time::timeout(std::time::Duration::from_secs(8), client.request(request)) + .await?; + // let response = client.request(request).await; + + let body = hyper::body::to_bytes(response?.body_mut()).await?; + Ok(serde_json::from_slice::(&body)?) + } +} +impl XvbPrivStats { + pub async fn request_api(address: &str, token: &str) -> Result { + let https = HttpsConnector::new(); + let client = hyper::Client::builder().build(https); + if let Ok(request) = hyper::Request::builder() + .method("GET") + .uri(format!( + "{}/cgi-bin/p2pool_bonus_history_api.cgi?address={}&token={}", + XVB_URL, address, token + )) + .body(hyper::Body::empty()) + { + match client.request(request).await { + Ok(mut resp) => match resp.status() { + StatusCode::OK => Ok(hyper::body::to_bytes(resp.body_mut()).await?), + StatusCode::UNPROCESSABLE_ENTITY => { + bail!("the token is invalid for this xmr address.") + } + _ => bail!("The status of the response is not expected"), + }, + Err(err) => { + bail!("error from response: {}", err) + } + } + } else { + bail!("request could not be build") + } + } +} + +#[derive(Debug, Clone, Default, Deserialize)] +pub struct XvbPrivStats { + pub fails: u8, + pub donor_1hr_avg: f32, + pub donor_24hr_avg: f32, +} + #[derive(Debug, Clone, Default, Display, Deserialize)] pub enum XvbRound { #[default] @@ -257,24 +365,6 @@ impl PubXvbApi { ..pub_api.clone() }; } - #[inline] - // Send an HTTP request to XvB's API, serialize it into [Self] and return it - async fn request_xvb_public_api( - client: hyper::Client>, - api_uri: &str, - ) -> std::result::Result { - let request = hyper::Request::builder() - .method("GET") - .uri(api_uri) - .body(hyper::Body::empty())?; - let response = - tokio::time::timeout(std::time::Duration::from_secs(8), client.request(request)) - .await?; - // let response = client.request(request).await; - - let body = hyper::body::to_bytes(response?.body_mut()).await?; - Ok(serde_json::from_slice::(&body)?) - } } fn signal_interrupt( @@ -323,8 +413,7 @@ fn signal_interrupt( mod test { use std::thread; - use super::PubXvbApi; - use crate::utils::constants::XVB_URL_PUBLIC_API; + use super::XvbPubStats; use hyper::Client; use hyper_tls::HttpsConnector; @@ -337,9 +426,7 @@ mod test { dbg!(new_data); } #[tokio::main] - async fn corr(client: Client>) -> PubXvbApi { - PubXvbApi::request_xvb_public_api(client, XVB_URL_PUBLIC_API) - .await - .unwrap() + async fn corr(client: Client>) -> XvbPubStats { + XvbPubStats::request_api(client).await.unwrap() } } diff --git a/src/utils/constants.rs b/src/utils/constants.rs index bdca304..1ac93d7 100644 --- a/src/utils/constants.rs +++ b/src/utils/constants.rs @@ -411,6 +411,12 @@ pub const XVB_HELP: &str = "You need to register an account by clicking on the l pub const XVB_URL: &str = "https://xmrvsbeast.com"; pub const XVB_URL_PUBLIC_API: &str = "https://xmrvsbeast.com/p2pool/stats"; pub const XVB_TOKEN_LEN: usize = 9; +pub const XVB_HERO_SELECT: &str = + "Donate all spared hashrate to the raffle, even if there is more than enough to be in the most highest round type possible"; +pub const XVB_TOKEN_FIELD: &str = "Token"; +pub const XVB_FAILURE_FIELD: &str = "Failures"; +pub const XVB_DONATED_1H_FIELD: &str = "Donated last hour"; +pub const XVB_DONATED_24H_FIELD: &str = "Donated last 24 hours"; // CLI argument messages pub const ARG_HELP: &str = r#"USAGE: ./gupax [--flag]