diff --git a/build.rs b/build.rs
index c380dd0..d72cc31 100755
--- a/build.rs
+++ b/build.rs
@@ -5,16 +5,17 @@
// pre-compiled bytes using [include_bytes!()] on the images in [images/].
#[cfg(windows)]
fn main() -> std::io::Result<()> {
- set_commit_env();
+ set_commit_env();
- static_vcruntime::metabuild();
- let mut res = winres::WindowsResource::new();
- // This sets the icon.
- res.set_icon("images/icons/icon.ico");
- // This sets the [Run as Administrator] metadata flag for Windows.
- // Why do I do this?: [https://github.com/hinto-janai/gupax/tree/main/src#why-does-gupax-need-to-be-admin-on-windows]
- // TL;DR: Because Windows.
- res.set_manifest(r#"
+ static_vcruntime::metabuild();
+ let mut res = winres::WindowsResource::new();
+ // This sets the icon.
+ res.set_icon("images/icons/icon.ico");
+ // This sets the [Run as Administrator] metadata flag for Windows.
+ // Why do I do this?: [https://github.com/hinto-janai/gupax/tree/main/src#why-does-gupax-need-to-be-admin-on-windows]
+ // TL;DR: Because Windows.
+ res.set_manifest(
+ r#"
@@ -24,27 +25,28 @@ fn main() -> std::io::Result<()> {
- "#);
- res.compile()
+ "#,
+ );
+ res.compile()
}
#[cfg(unix)]
fn main() {
- set_commit_env();
+ set_commit_env();
}
// Set the current git commit to the env var [COMMIT].
fn set_commit_env() {
- println!("cargo:rerun-if-changed=.git/refs/heads/");
+ println!("cargo:rerun-if-changed=.git/refs/heads/");
- let output = std::process::Command::new("git")
- .args(["rev-parse", "HEAD"])
- .output()
- .unwrap();
+ let output = std::process::Command::new("git")
+ .args(["rev-parse", "HEAD"])
+ .output()
+ .unwrap();
- let commit = String::from_utf8(output.stdout).unwrap();
+ let commit = String::from_utf8(output.stdout).unwrap();
- assert!(commit.len() >= 40);
+ assert!(commit.len() >= 40);
- println!("cargo:rustc-env=COMMIT={commit}");
+ println!("cargo:rustc-env=COMMIT={commit}");
}
diff --git a/src/constants.rs b/src/constants.rs
index 828c62f..cb9f70d 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -15,51 +15,49 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-pub const GUPAX_VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); // e.g: v1.0.0
+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 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 [.]
+pub const XMRIG_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 [.]
pub const GUPAX_VERSION_UNDERSCORE: &str = concat!(
- "Gupax_v",
- env!("CARGO_PKG_VERSION_MAJOR"),
- "_",
- env!("CARGO_PKG_VERSION_MINOR"),
- "_",
- env!("CARGO_PKG_VERSION_PATCH"),
+ "Gupax_v",
+ env!("CARGO_PKG_VERSION_MAJOR"),
+ "_",
+ env!("CARGO_PKG_VERSION_MINOR"),
+ "_",
+ env!("CARGO_PKG_VERSION_PATCH"),
);
// App frame resolution, [4:3] aspect ratio, [1.33:1]
-pub const APP_MIN_WIDTH: f32 = 640.0;
+pub const APP_MIN_WIDTH: f32 = 640.0;
pub const APP_MIN_HEIGHT: f32 = 480.0;
-pub const APP_MAX_WIDTH: f32 = 3840.0;
+pub const APP_MAX_WIDTH: f32 = 3840.0;
pub const APP_MAX_HEIGHT: f32 = 2160.0;
// Default, 1280x960
-pub const APP_DEFAULT_WIDTH: f32 = 1280.0;
+pub const APP_DEFAULT_WIDTH: f32 = 1280.0;
pub const APP_DEFAULT_HEIGHT: f32 = 960.0;
// App resolution scaling
-pub const APP_MIN_SCALE: f32 = 0.1;
+pub const APP_MIN_SCALE: f32 = 0.1;
pub const APP_MAX_SCALE: f32 = 2.0;
pub const APP_DEFAULT_SCALE: f32 = 1.0;
// Constants specific for Linux distro packaging of Gupax
#[cfg(feature = "distro")]
-pub const DISTRO_NO_UPDATE: &str =
-r#"This [Gupax] was compiled for use as a Linux distro package. Built-in updates are disabled. The below settings [Update-via-Tor] & [Auto-Update] will not do anything. Please use your package manager to update [Gupax/P2Pool/XMRig]."#;
+pub const DISTRO_NO_UPDATE: &str = r#"This [Gupax] was compiled for use as a Linux distro package. Built-in updates are disabled. The below settings [Update-via-Tor] & [Auto-Update] will not do anything. Please use your package manager to update [Gupax/P2Pool/XMRig]."#;
// Use macOS shaped icon for macOS
#[cfg(target_os = "macos")]
-pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon@2x.png");
+pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon@2x.png");
#[cfg(not(target_os = "macos"))]
-pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon.png");
+pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon.png");
pub const BYTES_BANNER: &[u8] = include_bytes!("../images/banner.png");
-pub const HORIZONTAL: &str = "--------------------------------------------";
+pub const HORIZONTAL: &str = "--------------------------------------------";
pub const HORI_CONSOLE: &str = "---------------------------------------------------------------------------------------------------------------------------";
// Keyboard shortcuts
-pub const KEYBOARD_SHORTCUTS: &str =
-r#"*---------------------------------------*
+pub const KEYBOARD_SHORTCUTS: &str = r#"*---------------------------------------*
| Key shortcuts |
|---------------------------------------|
| F11 | Fullscreen |
@@ -75,28 +73,29 @@ r#"*---------------------------------------*
*---------------------------------------*"#;
// P2Pool & XMRig default API stuff
#[cfg(target_os = "windows")]
-pub const P2POOL_API_PATH_LOCAL: &str = r"local\stratum";
+pub const P2POOL_API_PATH_LOCAL: &str = r"local\stratum";
#[cfg(target_os = "windows")]
pub const P2POOL_API_PATH_NETWORK: &str = r"network\stats";
#[cfg(target_os = "windows")]
-pub const P2POOL_API_PATH_POOL: &str = r"pool\stats";
+pub const P2POOL_API_PATH_POOL: &str = r"pool\stats";
#[cfg(target_family = "unix")]
-pub const P2POOL_API_PATH_LOCAL: &str = "local/stratum";
+pub const P2POOL_API_PATH_LOCAL: &str = "local/stratum";
#[cfg(target_family = "unix")]
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 P2POOL_API_PATH_POOL: &str = "pool/stats";
+pub const XMRIG_API_URI: &str = "1/summary"; // The default relative URI of XMRig's API
// Process state tooltips (online, offline, etc)
-pub const P2POOL_ALIVE: &str = "P2Pool is online and fully synchronized";
-pub const P2POOL_DEAD: &str = "P2Pool is offline";
-pub const P2POOL_FAILED: &str = "P2Pool is offline and failed when exiting";
-pub const P2POOL_MIDDLE: &str = "P2Pool is in the middle of (re)starting/stopping";
-pub const P2POOL_SYNCING: &str = "P2Pool is still syncing. This indicator will turn GREEN when P2Pool is ready";
+pub const P2POOL_ALIVE: &str = "P2Pool is online and fully synchronized";
+pub const P2POOL_DEAD: &str = "P2Pool is offline";
+pub const P2POOL_FAILED: &str = "P2Pool is offline and failed when exiting";
+pub const P2POOL_MIDDLE: &str = "P2Pool is in the middle of (re)starting/stopping";
+pub const P2POOL_SYNCING: &str =
+ "P2Pool is still syncing. This indicator will turn GREEN when P2Pool is ready";
-pub const XMRIG_ALIVE: &str = "XMRig is online and mining";
-pub const XMRIG_DEAD: &str = "XMRig is offline";
+pub const XMRIG_ALIVE: &str = "XMRig is online and mining";
+pub const XMRIG_DEAD: &str = "XMRig is offline";
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";
@@ -108,31 +107,29 @@ pub const XMRIG_NOT_MINING: &str = "XMRig is online, but not mining to any pool"
pub const SPACE: f32 = 10.0;
// Some colors
-pub const RED: egui::Color32 = egui::Color32::from_rgb(230, 50, 50);
-pub const GREEN: egui::Color32 = egui::Color32::from_rgb(100, 230, 100);
-pub const BLUE: egui::Color32 = egui::Color32::from_rgb(100, 175, 255);
-pub const ORANGE: egui::Color32 = egui::Color32::from_rgb(255, 120, 40);
-pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100);
+pub const RED: egui::Color32 = egui::Color32::from_rgb(230, 50, 50);
+pub const GREEN: egui::Color32 = egui::Color32::from_rgb(100, 230, 100);
+pub const BLUE: egui::Color32 = egui::Color32::from_rgb(100, 175, 255);
+pub const ORANGE: egui::Color32 = egui::Color32::from_rgb(255, 120, 40);
+pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100);
pub const BRIGHT_YELLOW: egui::Color32 = egui::Color32::from_rgb(250, 250, 100);
-pub const BONE: egui::Color32 = egui::Color32::from_rgb(190, 190, 190); // In between LIGHT_GRAY <-> GRAY
-pub const WHITE: egui::Color32 = egui::Color32::WHITE;
-pub const GRAY: egui::Color32 = egui::Color32::GRAY;
-pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY;
-pub const BLACK: egui::Color32 = egui::Color32::BLACK;
-pub const DARK_GRAY: egui::Color32 = egui::Color32::from_gray(13);
+pub const BONE: egui::Color32 = egui::Color32::from_rgb(190, 190, 190); // In between LIGHT_GRAY <-> GRAY
+pub const WHITE: egui::Color32 = egui::Color32::WHITE;
+pub const GRAY: egui::Color32 = egui::Color32::GRAY;
+pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY;
+pub const BLACK: egui::Color32 = egui::Color32::BLACK;
+pub const DARK_GRAY: egui::Color32 = egui::Color32::from_gray(13);
// [Duration] constants
pub const SECOND: std::time::Duration = std::time::Duration::from_secs(1);
// The explanation given to the user on why XMRig needs sudo.
-pub const XMRIG_ADMIN_REASON: &str =
-r#"The large hashrate difference between XMRig and other miners like Monero and P2Pool's built-in miners is mostly due to XMRig configuring CPU MSRs and setting up hugepages. Other miners like Monero or P2Pool's built-in miner do not do this. It can be done manually but it isn't recommended since XMRig does this for you automatically, but only if it has the proper admin privileges."#;
+pub const XMRIG_ADMIN_REASON: &str = r#"The large hashrate difference between XMRig and other miners like Monero and P2Pool's built-in miners is mostly due to XMRig configuring CPU MSRs and setting up hugepages. Other miners like Monero or P2Pool's built-in miner do not do this. It can be done manually but it isn't recommended since XMRig does this for you automatically, but only if it has the proper admin privileges."#;
// Password buttons
-pub const PASSWORD_TEXT: &str = "Enter sudo/admin password...";
+pub const PASSWORD_TEXT: &str = "Enter sudo/admin password...";
pub const PASSWORD_LEAVE: &str = "Return to the previous screen";
pub const PASSWORD_ENTER: &str = "Attempt with the current password";
-pub const PASSWORD_HIDE: &str = "Toggle hiding/showing the password";
-
+pub const PASSWORD_HIDE: &str = "Toggle hiding/showing the password";
// OS specific
#[cfg(target_os = "windows")]
@@ -154,111 +151,132 @@ pub const OS_NAME: &str = "Linux";
// Tooltips
// Status
-pub const STATUS_GUPAX_UPTIME: &str = "How long Gupax has been online";
-pub const STATUS_GUPAX_CPU_USAGE: &str = "How much CPU Gupax is currently using. This accounts for all your threads (it is out of 100%)";
-pub const STATUS_GUPAX_MEMORY_USAGE: &str = "How much memory Gupax is currently using in Megabytes";
+pub const STATUS_GUPAX_UPTIME: &str = "How long Gupax has been online";
+pub const STATUS_GUPAX_CPU_USAGE: &str =
+ "How much CPU Gupax is currently using. This accounts for all your threads (it is out of 100%)";
+pub const STATUS_GUPAX_MEMORY_USAGE: &str = "How much memory Gupax is currently using in Megabytes";
pub const STATUS_GUPAX_SYSTEM_CPU_USAGE: &str = "How much CPU your entire system is currently using. This accounts for all your threads (it is out of 100%)";
-pub const STATUS_GUPAX_SYSTEM_MEMORY: &str = "How much memory your entire system has (including swap) and is currently using in Gigabytes";
-pub const STATUS_GUPAX_SYSTEM_CPU_MODEL: &str = "The detected model of your system's CPU and its current frequency";
+pub const STATUS_GUPAX_SYSTEM_MEMORY: &str =
+ "How much memory your entire system has (including swap) and is currently using in Gigabytes";
+pub const STATUS_GUPAX_SYSTEM_CPU_MODEL: &str =
+ "The detected model of your system's CPU and its current frequency";
//--
-pub const STATUS_P2POOL_UPTIME: &str = "How long P2Pool has been online";
+pub const STATUS_P2POOL_UPTIME: &str = "How long P2Pool has been online";
pub const STATUS_P2POOL_PAYOUTS: &str = "The total amount of payouts received in this instance of P2Pool and an extrapolated estimate of how many you will receive. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!";
pub const STATUS_P2POOL_XMR: &str = "The total amount of XMR mined in this instance of P2Pool and an extrapolated estimate of how many you will mine in the future. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!";
pub const STATUS_P2POOL_HASHRATE: &str = "The total amount of hashrate your P2Pool has pointed at it in 15 minute, 1 hour, and 24 hour averages";
-pub const STATUS_P2POOL_SHARES: &str = "The total amount of shares found on P2Pool";
-pub const STATUS_P2POOL_EFFORT: &str = "The average amount of effort needed to find a share, and the current effort";
+pub const STATUS_P2POOL_SHARES: &str = "The total amount of shares found on P2Pool";
+pub const STATUS_P2POOL_EFFORT: &str =
+ "The average amount of effort needed to find a share, and the current effort";
pub const STATUS_P2POOL_CONNECTIONS: &str = "The total amount of miner connections on this P2Pool";
pub const STATUS_P2POOL_MONERO_NODE: &str = "The Monero node being used by P2Pool";
-pub const STATUS_P2POOL_POOL: &str = "The P2Pool sidechain you're currently connected to";
-pub const STATUS_P2POOL_ADDRESS: &str = "The Monero address P2Pool will send payouts to";
+pub const STATUS_P2POOL_POOL: &str = "The P2Pool sidechain you're currently connected to";
+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_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";
-pub const STATUS_XMRIG_POOL: &str = "The pool XMRig is currently mining to";
-pub const STATUS_XMRIG_THREADS: &str = "The amount of threads XMRig is currently using";
+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";
+pub const STATUS_XMRIG_POOL: &str = "The pool XMRig is currently mining to";
+pub const STATUS_XMRIG_THREADS: &str = "The amount of threads XMRig is currently using";
// Status Submenus
-pub const STATUS_SUBMENU_PROCESSES: &str = "View the status of process related data for [Gupax|P2Pool|XMRig]";
-pub const STATUS_SUBMENU_P2POOL: &str = "View P2Pool specific data";
-pub const STATUS_SUBMENU_HASHRATE: &str = "Compare your CPU hashrate with others";
+pub const STATUS_SUBMENU_PROCESSES: &str =
+ "View the status of process related data for [Gupax|P2Pool|XMRig]";
+pub const STATUS_SUBMENU_P2POOL: &str = "View P2Pool specific data";
+pub const STATUS_SUBMENU_HASHRATE: &str = "Compare your CPU hashrate with others";
//-- P2Pool
pub const STATUS_SUBMENU_PAYOUT: &str = "The total amount of payouts received via P2Pool across all time. This includes all payouts you have ever received using Gupax and P2Pool.";
pub const STATUS_SUBMENU_XMR: &str = "The total of XMR mined via P2Pool across all time. This includes all the XMR you have ever mined using Gupax and P2Pool.";
-pub const STATUS_SUBMENU_LATEST: &str = "Sort the payouts from latest to oldest";
-pub const STATUS_SUBMENU_OLDEST: &str = "Sort the payouts from oldest to latest";
-pub const STATUS_SUBMENU_BIGGEST: &str = "Sort the payouts from biggest to smallest";
-pub const STATUS_SUBMENU_SMALLEST: &str = "Sort the payouts from smallest to biggest";
-pub const STATUS_SUBMENU_AUTOMATIC: &str = "Automatically calculate share/block time with your current P2Pool 1 hour average hashrate";
+pub const STATUS_SUBMENU_LATEST: &str = "Sort the payouts from latest to oldest";
+pub const STATUS_SUBMENU_OLDEST: &str = "Sort the payouts from oldest to latest";
+pub const STATUS_SUBMENU_BIGGEST: &str = "Sort the payouts from biggest to smallest";
+pub const STATUS_SUBMENU_SMALLEST: &str = "Sort the payouts from smallest to biggest";
+pub const STATUS_SUBMENU_AUTOMATIC: &str =
+ "Automatically calculate share/block time with your current P2Pool 1 hour average hashrate";
pub const STATUS_SUBMENU_MANUAL: &str = "Manually input a hashrate to calculate share/block time with current P2Pool/Monero network stats";
-pub const STATUS_SUBMENU_HASH: &str = "Use [Hash] as the hashrate metric";
-pub const STATUS_SUBMENU_KILO: &str = "Use [Kilo] as the hashrate metric (1,000x hash)";
-pub const STATUS_SUBMENU_MEGA: &str = "Use [Mega] as the hashrate metric (1,000,000x hash)";
-pub const STATUS_SUBMENU_GIGA: &str = "Use [Giga] as the hashrate metric (1,000,000,000x hash)";
-pub const STATUS_SUBMENU_P2POOL_BLOCK_MEAN: &str = "The average time it takes for P2Pool to find a block";
-pub const STATUS_SUBMENU_YOUR_P2POOL_HASHRATE: &str = "Your 1 hour average hashrate on P2Pool";
-pub const STATUS_SUBMENU_P2POOL_SHARE_MEAN: &str = "The average time it takes for your hashrate to find a share on P2Pool";
-pub const STATUS_SUBMENU_SOLO_BLOCK_MEAN: &str = "The average time it would take for your hashrate to find a block solo mining Monero";
+pub const STATUS_SUBMENU_HASH: &str = "Use [Hash] as the hashrate metric";
+pub const STATUS_SUBMENU_KILO: &str = "Use [Kilo] as the hashrate metric (1,000x hash)";
+pub const STATUS_SUBMENU_MEGA: &str = "Use [Mega] as the hashrate metric (1,000,000x hash)";
+pub const STATUS_SUBMENU_GIGA: &str = "Use [Giga] as the hashrate metric (1,000,000,000x hash)";
+pub const STATUS_SUBMENU_P2POOL_BLOCK_MEAN: &str =
+ "The average time it takes for P2Pool to find a block";
+pub const STATUS_SUBMENU_YOUR_P2POOL_HASHRATE: &str = "Your 1 hour average hashrate on P2Pool";
+pub const STATUS_SUBMENU_P2POOL_SHARE_MEAN: &str =
+ "The average time it takes for your hashrate to find a share on P2Pool";
+pub const STATUS_SUBMENU_SOLO_BLOCK_MEAN: &str =
+ "The average time it would take for your hashrate to find a block solo mining Monero";
pub const STATUS_SUBMENU_MONERO_DIFFICULTY: &str = "The current Monero network's difficulty (how many hashes it will take on average to find a block)";
-pub const STATUS_SUBMENU_MONERO_HASHRATE: &str = "The current Monero network's hashrate";
+pub const STATUS_SUBMENU_MONERO_HASHRATE: &str = "The current Monero network's hashrate";
pub const STATUS_SUBMENU_P2POOL_DIFFICULTY: &str = "The current P2Pool network's difficulty (how many hashes it will take on average to find a share)";
-pub const STATUS_SUBMENU_P2POOL_HASHRATE: &str = "The current P2Pool network's hashrate";
-pub const STATUS_SUBMENU_P2POOL_MINERS: &str = "The current amount of miners on P2Pool";
-pub const STATUS_SUBMENU_P2POOL_DOMINANCE: &str = "The percent of hashrate P2Pool accounts for in the entire Monero network";
-pub const STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE: &str = "The percent of hashrate you account for in P2Pool";
-pub const STATUS_SUBMENU_YOUR_MONERO_DOMINANCE: &str = "The percent of hashrate you account for in the entire Monero network";
+pub const STATUS_SUBMENU_P2POOL_HASHRATE: &str = "The current P2Pool network's hashrate";
+pub const STATUS_SUBMENU_P2POOL_MINERS: &str = "The current amount of miners on P2Pool";
+pub const STATUS_SUBMENU_P2POOL_DOMINANCE: &str =
+ "The percent of hashrate P2Pool accounts for in the entire Monero network";
+pub const STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE: &str =
+ "The percent of hashrate you account for in P2Pool";
+pub const STATUS_SUBMENU_YOUR_MONERO_DOMINANCE: &str =
+ "The percent of hashrate you account for in the entire Monero network";
pub const STATUS_SUBMENU_PROGRESS_BAR: &str = "The next time Gupax will update P2Pool stats. Each [*] is 900ms (updates roughly every 54 seconds)";
//-- Benchmarks
-pub const STATUS_SUBMENU_YOUR_CPU: &str = "The CPU detected by Gupax";
-pub const STATUS_SUBMENU_YOUR_BENCHMARKS: &str = "How many benchmarks your CPU has had uploaded to [https://xmrig.com/benchmark] ";
-pub const STATUS_SUBMENU_YOUR_RANK: &str = "Your CPU's rank out of all CPUs listed on [https://xmrig.com/benchmark] (higher is better)";
-pub const STATUS_SUBMENU_YOUR_HIGH: &str = "The highest hashrate recorded for your CPU on [https://xmrig.com/benchmark]";
-pub const STATUS_SUBMENU_YOUR_AVERAGE: &str = "The average hashrate of your CPU based off the data at [https://xmrig.com/benchmark]";
-pub const STATUS_SUBMENU_YOUR_LOW: &str = "The lowest hashrate recorded for your CPU on [https://xmrig.com/benchmark]";
+pub const STATUS_SUBMENU_YOUR_CPU: &str = "The CPU detected by Gupax";
+pub const STATUS_SUBMENU_YOUR_BENCHMARKS: &str =
+ "How many benchmarks your CPU has had uploaded to [https://xmrig.com/benchmark] ";
+pub const STATUS_SUBMENU_YOUR_RANK: &str =
+ "Your CPU's rank out of all CPUs listed on [https://xmrig.com/benchmark] (higher is better)";
+pub const STATUS_SUBMENU_YOUR_HIGH: &str =
+ "The highest hashrate recorded for your CPU on [https://xmrig.com/benchmark]";
+pub const STATUS_SUBMENU_YOUR_AVERAGE: &str =
+ "The average hashrate of your CPU based off the data at [https://xmrig.com/benchmark]";
+pub const STATUS_SUBMENU_YOUR_LOW: &str =
+ "The lowest hashrate recorded for your CPU on [https://xmrig.com/benchmark]";
pub const STATUS_SUBMENU_OTHER_CPUS: &str = "A list of ALL the recorded CPU benchmarks. The CPUs most similar to yours are listed first. All this data is taken from [https://xmrig.com/benchmark].";
-pub const STATUS_SUBMENU_OTHER_CPU: &str = "The CPU name";
+pub const STATUS_SUBMENU_OTHER_CPU: &str = "The CPU name";
pub const STATUS_SUBMENU_OTHER_RELATIVE: &str = "The relative hashrate power compared to the fastest recorded CPU, which is current: [AMD EPYC 7T83 64-Core Processor]";
-pub const STATUS_SUBMENU_OTHER_HIGH: &str = "Highest hashrate record";
-pub const STATUS_SUBMENU_OTHER_AVERAGE: &str = "Average hashrate";
-pub const STATUS_SUBMENU_OTHER_LOW: &str = "Lowest hashrate record";
-pub const STATUS_SUBMENU_OTHER_RANK: &str = "The rank of this CPU out of [1567] (lower is better)";
-pub const STATUS_SUBMENU_OTHER_BENCHMARKS: &str = "How many benchmarks this CPU has had posted to [https://xmrig.com/benchmark]";
+pub const STATUS_SUBMENU_OTHER_HIGH: &str = "Highest hashrate record";
+pub const STATUS_SUBMENU_OTHER_AVERAGE: &str = "Average hashrate";
+pub const STATUS_SUBMENU_OTHER_LOW: &str = "Lowest hashrate record";
+pub const STATUS_SUBMENU_OTHER_RANK: &str = "The rank of this CPU out of [1567] (lower is better)";
+pub const STATUS_SUBMENU_OTHER_BENCHMARKS: &str =
+ "How many benchmarks this CPU has had posted to [https://xmrig.com/benchmark]";
// Gupax
-pub const GUPAX_UPDATE: &str = "Check for updates on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically";
-pub const GUPAX_AUTO_UPDATE: &str = "Automatically check for updates at startup";
-pub const GUPAX_SHOULD_RESTART: &str = "Gupax was updated. A restart is recommended but not required";
-pub const GUPAX_UP_TO_DATE: &str = "Gupax is up-to-date";
+pub const GUPAX_UPDATE: &str =
+ "Check for updates on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically";
+pub const GUPAX_AUTO_UPDATE: &str = "Automatically check for updates at startup";
+pub const GUPAX_SHOULD_RESTART: &str =
+ "Gupax was updated. A restart is recommended but not required";
+pub const GUPAX_UP_TO_DATE: &str = "Gupax is up-to-date";
#[cfg(not(target_os = "macos"))]
pub const GUPAX_UPDATE_VIA_TOR: &str = "Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required";
#[cfg(target_os = "macos")] // Arti library has issues on macOS
pub const GUPAX_UPDATE_VIA_TOR: &str = "WARNING: This option is unstable on macOS. Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required";
-pub const GUPAX_ASK_BEFORE_QUIT: &str = "Ask before quitting Gupax";
+pub const GUPAX_ASK_BEFORE_QUIT: &str = "Ask before quitting Gupax";
pub const GUPAX_SAVE_BEFORE_QUIT: &str = "Automatically save any changed settings before quitting";
pub const GUPAX_AUTO_P2POOL: &str = "Automatically start P2Pool on Gupax 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 Gupax startup. This option will fail if your XMRig settings aren't valid!";
-pub const GUPAX_ADJUST: &str = "Adjust and set the width/height of the Gupax window";
-pub const GUPAX_WIDTH: &str = "Set the width of the Gupax window";
-pub const GUPAX_HEIGHT: &str = "Set the height of the Gupax window";
-pub const GUPAX_SCALE: &str = "Set the resolution scaling of the Gupax window (resize window to re-apply scaling)";
-pub const GUPAX_LOCK_WIDTH: &str = "Automatically match the HEIGHT against the WIDTH in a 4:3 ratio";
-pub const GUPAX_LOCK_HEIGHT: &str = "Automatically match the WIDTH against the HEIGHT in a 4:3 ratio";
-pub const GUPAX_NO_LOCK: &str = "Allow individual selection of width and height";
-pub const GUPAX_SET: &str = "Set the width/height of the Gupax window to the current values";
-pub const GUPAX_TAB: &str = "Set the default tab Gupax starts on";
-pub const GUPAX_TAB_ABOUT: &str = "Set the tab Gupax starts on to: About";
-pub const GUPAX_TAB_STATUS: &str = "Set the tab Gupax starts on to: Status";
-pub const GUPAX_TAB_GUPAX: &str = "Set the tab Gupax starts on to: Gupax";
-pub const GUPAX_TAB_P2POOL: &str = "Set the tab Gupax starts on to: P2Pool";
-pub const GUPAX_TAB_XMRIG: &str = "Set the tab Gupax starts on to: XMRig";
+pub const GUPAX_ADJUST: &str = "Adjust and set the width/height of the Gupax window";
+pub const GUPAX_WIDTH: &str = "Set the width of the Gupax window";
+pub const GUPAX_HEIGHT: &str = "Set the height of the Gupax window";
+pub const GUPAX_SCALE: &str =
+ "Set the resolution scaling of the Gupax window (resize window to re-apply scaling)";
+pub const GUPAX_LOCK_WIDTH: &str =
+ "Automatically match the HEIGHT against the WIDTH in a 4:3 ratio";
+pub const GUPAX_LOCK_HEIGHT: &str =
+ "Automatically match the WIDTH against the HEIGHT in a 4:3 ratio";
+pub const GUPAX_NO_LOCK: &str = "Allow individual selection of width and height";
+pub const GUPAX_SET: &str = "Set the width/height of the Gupax window to the current values";
+pub const GUPAX_TAB: &str = "Set the default tab Gupax starts on";
+pub const GUPAX_TAB_ABOUT: &str = "Set the tab Gupax starts on to: About";
+pub const GUPAX_TAB_STATUS: &str = "Set the tab Gupax starts on to: Status";
+pub const GUPAX_TAB_GUPAX: &str = "Set the tab Gupax starts on to: Gupax";
+pub const GUPAX_TAB_P2POOL: &str = "Set the tab Gupax starts on to: P2Pool";
+pub const GUPAX_TAB_XMRIG: &str = "Set the tab Gupax starts on to: XMRig";
-pub const GUPAX_SIMPLE: &str =
-r#"Use simple Gupax settings:
+pub const GUPAX_SIMPLE: &str = r#"Use simple Gupax settings:
- Update button
- Basic toggles"#;
-pub const GUPAX_ADVANCED: &str =
-r#"Use advanced Gupax settings:
+pub const GUPAX_ADVANCED: &str = r#"Use advanced Gupax settings:
- Update button
- Basic toggles
- P2Pool/XMRig binary path selector
@@ -272,24 +290,24 @@ pub const GUPAX_PATH_XMRIG: &str = "The location of the XMRig binary: Both absol
// 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";
pub const P2POOL_MINI: &str = "Use the P2Pool mini-chain. This P2Pool finds blocks slower, but has a lower difficulty. Suitable for miners with less than 50kH/s";
-pub const P2POOL_OUT: &str = "How many out-bound peers to connect to? (you connecting to others)";
-pub const P2POOL_IN: &str = "How many in-bound peers to allow? (others connecting to you)";
-pub const P2POOL_LOG: &str = "Verbosity of the console log";
-pub const P2POOL_AUTO_NODE: &str = "Automatically ping the remote Monero nodes at Gupax startup";
-pub const P2POOL_AUTO_SELECT: &str = "Automatically select the fastest remote Monero node after pinging";
-pub const P2POOL_BACKUP_HOST_SIMPLE: &str =
-r#"Automatically switch to the other nodes listed if the current one is down.
+pub const P2POOL_OUT: &str = "How many out-bound peers to connect to? (you connecting to others)";
+pub const P2POOL_IN: &str = "How many in-bound peers to allow? (others connecting to you)";
+pub const P2POOL_LOG: &str = "Verbosity of the console log";
+pub const P2POOL_AUTO_NODE: &str = "Automatically ping the remote Monero nodes at Gupax startup";
+pub const P2POOL_AUTO_SELECT: &str =
+ "Automatically select the fastest remote Monero node after pinging";
+pub const P2POOL_BACKUP_HOST_SIMPLE: &str = r#"Automatically switch to the other nodes listed if the current one is down.
Note: you must ping the remote nodes or this feature will default to only using the currently selected node."#;
-pub const P2POOL_BACKUP_HOST_ADVANCED: &str = "Automatically switch to the other nodes in your list if the current one is down.";
-pub const P2POOL_SELECT_FASTEST: &str = "Select the fastest remote Monero node";
-pub const P2POOL_SELECT_RANDOM: &str = "Select a random remote Monero node";
-pub const P2POOL_SELECT_LAST: &str = "Select the previous remote Monero node";
-pub const P2POOL_SELECT_NEXT: &str = "Select the next remote Monero node";
-pub const P2POOL_PING: &str = "Ping the built-in remote Monero nodes";
+pub const P2POOL_BACKUP_HOST_ADVANCED: &str =
+ "Automatically switch to the other nodes in your list if the current one is down.";
+pub const P2POOL_SELECT_FASTEST: &str = "Select the fastest remote Monero node";
+pub const P2POOL_SELECT_RANDOM: &str = "Select a random remote Monero node";
+pub const P2POOL_SELECT_LAST: &str = "Select the previous remote Monero node";
+pub const P2POOL_SELECT_NEXT: &str = "Select the next remote Monero node";
+pub const P2POOL_PING: &str = "Ping the built-in remote Monero nodes";
pub const P2POOL_ADDRESS: &str = "You must use a primary Monero address to mine on P2Pool (starts with a 4). It is highly recommended to create a new wallet since addresses are public on P2Pool!";
-pub const P2POOL_COMMUNITY_NODE_WARNING: &str =
-r#"TL;DR: Run & use your own Monero Node.
+pub const P2POOL_COMMUNITY_NODE_WARNING: &str = r#"TL;DR: Run & use your own Monero Node.
Using a Remote Monero Node is convenient but comes at the cost of privacy and reliability.
@@ -300,17 +318,14 @@ Running and using your own local Monero node improves privacy and ensures your c
For a simple guide, see the [Running a Local Monero Node] section on Gupax's GitHub by clicking this message."#;
pub const P2POOL_INPUT: &str = "Send a command to P2Pool";
-pub const P2POOL_ARGUMENTS: &str =
-r#"WARNING: Use [--no-color] and make sure to set [--data-api ] & [--local-api] so that the [Status] tab can work!
+pub const P2POOL_ARGUMENTS: &str = r#"WARNING: Use [--no-color] and make sure to set [--data-api ] & [--local-api] so that the [Status] tab can work!
Start P2Pool with these arguments and override all below settings"#;
-pub const P2POOL_SIMPLE: &str =
-r#"Use simple P2Pool settings:
+pub const P2POOL_SIMPLE: &str = r#"Use simple P2Pool settings:
- Remote remote Monero node
- Default P2Pool settings + Mini
- Backup host setting"#;
-pub const P2POOL_ADVANCED: &str =
-r#"Use advanced P2Pool settings:
+pub const P2POOL_ADVANCED: &str = r#"Use advanced P2Pool settings:
- Terminal input
- Overriding command arguments
- Manual node list
@@ -328,19 +343,17 @@ pub const P2POOL_PATH_OK: &str = "P2Pool was found at the given PATH";
pub const P2POOL_PATH_EMPTY: &str = "P2Pool PATH is empty! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where P2Pool is located.";
// Node/Pool list
-pub const LIST_ADD: &str = "Add the current values to the list";
-pub const LIST_SAVE: &str = "Save the current values to the already existing entry";
+pub const LIST_ADD: &str = "Add the current values to the list";
+pub const LIST_SAVE: &str = "Save the current values to the already existing entry";
pub const LIST_DELETE: &str = "Delete the currently selected entry";
-pub const LIST_CLEAR: &str = "Clear all current values";
+pub const LIST_CLEAR: &str = "Clear all current values";
// XMRig
-pub const XMRIG_SIMPLE: &str =
-r#"Use simple XMRig settings:
+pub const XMRIG_SIMPLE: &str = r#"Use simple XMRig settings:
- Mine to local P2Pool (localhost:3333)
- CPU thread slider
- HTTP API @ localhost:18088"#;
-pub const XMRIG_ADVANCED: &str =
-r#"Use advanced XMRig settings:
+pub const XMRIG_ADVANCED: &str = r#"Use advanced XMRig settings:
- Terminal input
- Overriding command arguments
- Custom payout address
@@ -350,30 +363,31 @@ r#"Use advanced XMRig settings:
- TLS setting
- Keepalive setting"#;
pub const XMRIG_INPUT: &str = "Send a command to XMRig";
-pub const XMRIG_ARGUMENTS: &str =
-r#"WARNING: Use [--no-color] and make sure to set [--http-host ] & [--http-port ] so that the [Status] tab can work!
+pub const XMRIG_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 with these arguments and override all below settings"#;
pub const XMRIG_ADDRESS: &str = "Specify which Monero address to payout to. This does nothing if mining to P2Pool since the address being paid out to will be the one P2Pool started with. This doubles as a rig identifier for P2Pool and some pools.";
pub const XMRIG_NAME: &str = "Add a unique name to identify this pool; Only [A-Za-z0-9-_.] and spaces allowed; Max length = 30 characters";
pub const XMRIG_IP: &str = "Specify the pool IP to connect to with XMRig; It must be a valid IPv4 address or a valid domain name; Max length = 255 characters";
-pub const XMRIG_PORT: &str = "Specify the port of the pool; [1-65535]";
+pub const XMRIG_PORT: &str = "Specify the port of the pool; [1-65535]";
pub const XMRIG_RIG: &str = "Add an optional rig ID. This will be the name shown on the pool; Only [A-Za-z0-9-_] and spaces allowed; Max length = 30 characters";
-#[cfg(not(target_os = "linux"))]
-pub const XMRIG_PAUSE: &str = "THIS SETTING IS DISABLED IF SET TO [0]. Pause mining if user is active, resume after";
-pub const XMRIG_API_IP: &str = "Specify which IP to bind to for XMRig's HTTP API; If empty: [localhost/127.0.0.1]";
-pub const XMRIG_API_PORT: &str = "Specify which port to bind to for XMRig's HTTP API; If empty: [18088]";
-pub const XMRIG_TLS: &str = "Enable SSL/TLS connections (needs pool support)";
-pub const XMRIG_KEEPALIVE: &str = "Send keepalive packets to prevent timeout (needs pool support)";
-pub const XMRIG_THREADS: &str = "Number of CPU threads to use for mining";
+#[cfg(not(target_os = "linux"))]
+pub const XMRIG_PAUSE: &str =
+ "THIS SETTING IS DISABLED IF SET TO [0]. Pause mining if user is active, resume after";
+pub const XMRIG_API_IP: &str =
+ "Specify which IP to bind to for XMRig's HTTP API; If empty: [localhost/127.0.0.1]";
+pub const XMRIG_API_PORT: &str =
+ "Specify which port to bind to for XMRig's HTTP API; If empty: [18088]";
+pub const XMRIG_TLS: &str = "Enable SSL/TLS connections (needs pool support)";
+pub const XMRIG_KEEPALIVE: &str = "Send keepalive packets to prevent timeout (needs pool support)";
+pub const XMRIG_THREADS: &str = "Number of CPU threads to use for mining";
pub const XMRIG_PATH_NOT_FILE: &str = "XMRig binary not found at the given PATH in the Gupax tab! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
pub const XMRIG_PATH_NOT_VALID: &str = "XMRig binary at the given PATH in the Gupax tab doesn't look like XMRig! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
-pub const XMRIG_PATH_OK: &str = "XMRig was found at the given PATH";
+pub const XMRIG_PATH_OK: &str = "XMRig was found at the given PATH";
pub const XMRIG_PATH_EMPTY: &str = "XMRig PATH is empty! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
// CLI argument messages
-pub const ARG_HELP: &str =
-r#"USAGE: ./gupax [--flag]
+pub const ARG_HELP: &str = r#"USAGE: ./gupax [--flag]
--help Print this help message
--version Print version and build info
@@ -390,29 +404,16 @@ r#"USAGE: ./gupax [--flag]
To view more detailed console debug information, start Gupax with
the environment variable [RUST_LOG] set to a log level like so:
RUST_LOG=(trace|debug|info|warn|error) ./gupax"#;
-pub const ARG_COPYRIGHT: &str =
-r#"Gupax is licensed under GPLv3.
+pub const ARG_COPYRIGHT: &str = r#"Gupax is licensed under GPLv3.
For more information, see link below:
"#;
//---------------------------------------------------------------------------------------------------- Visuals
-use egui::epaint::{
- Rounding,
- Shadow,
- Stroke
-};
+use egui::epaint::{Rounding, Shadow, Stroke};
-use egui::{
- Color32,
- Visuals,
- style::Spacing,
-};
+use egui::{style::Spacing, Color32, Visuals};
-use egui::style::{
- Selection,
- Widgets,
- WidgetVisuals,
-};
+use egui::style::{Selection, WidgetVisuals, Widgets};
use once_cell::sync::Lazy;
pub const ACCENT_COLOR: Color32 = Color32::from_rgb(200, 100, 100);
@@ -420,89 +421,98 @@ pub const BG: Color32 = Color32::from_gray(20);
// This is based off [`Visuals::dark()`].
pub static VISUALS: Lazy = Lazy::new(|| {
- let selection = Selection {
- bg_fill: ACCENT_COLOR,
- stroke: Stroke::new(1.0, Color32::from_gray(255)),
- };
+ let selection = Selection {
+ bg_fill: ACCENT_COLOR,
+ stroke: Stroke::new(1.0, Color32::from_gray(255)),
+ };
- // Based off default dark() mode.
- // https://docs.rs/egui/0.24.1/src/egui/style.rs.html#1210
- let widgets = Widgets {
- noninteractive: WidgetVisuals {
- bg_fill: BG,
- bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines
- fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
- rounding: Rounding::same(10.0),
- expansion: 0.0,
- weak_bg_fill: BG,
- },
- inactive: WidgetVisuals {
- bg_fill: Color32::from_gray(50),
- bg_stroke: Default::default(),
- fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
- rounding: Rounding::same(10.0),
- expansion: 0.0,
- weak_bg_fill: Color32::from_gray(50),
- },
- hovered: WidgetVisuals {
- bg_fill: Color32::from_gray(80),
- bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
- fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
- rounding: Rounding::same(10.0),
- expansion: 1.0,
- weak_bg_fill: Color32::from_gray(80),
- },
- active: WidgetVisuals {
- bg_fill: Color32::from_gray(55),
- bg_stroke: Stroke::new(1.0, Color32::WHITE),
- fg_stroke: Stroke::new(2.0, Color32::WHITE),
- rounding: Rounding::same(10.0),
- expansion: 1.0,
- weak_bg_fill: Color32::from_gray(120),
- },
- open: WidgetVisuals {
- bg_fill: Color32::from_gray(27),
- bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
- fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
- rounding: Rounding::same(10.0),
- expansion: 0.0,
- weak_bg_fill: Color32::from_gray(120),
- },
- };
+ // Based off default dark() mode.
+ // https://docs.rs/egui/0.24.1/src/egui/style.rs.html#1210
+ let widgets = Widgets {
+ noninteractive: WidgetVisuals {
+ bg_fill: BG,
+ bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines
+ fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
+ rounding: Rounding::same(10.0),
+ expansion: 0.0,
+ weak_bg_fill: BG,
+ },
+ inactive: WidgetVisuals {
+ bg_fill: Color32::from_gray(50),
+ bg_stroke: Default::default(),
+ fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
+ rounding: Rounding::same(10.0),
+ expansion: 0.0,
+ weak_bg_fill: Color32::from_gray(50),
+ },
+ hovered: WidgetVisuals {
+ bg_fill: Color32::from_gray(80),
+ bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
+ fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
+ rounding: Rounding::same(10.0),
+ expansion: 1.0,
+ weak_bg_fill: Color32::from_gray(80),
+ },
+ active: WidgetVisuals {
+ bg_fill: Color32::from_gray(55),
+ bg_stroke: Stroke::new(1.0, Color32::WHITE),
+ fg_stroke: Stroke::new(2.0, Color32::WHITE),
+ rounding: Rounding::same(10.0),
+ expansion: 1.0,
+ weak_bg_fill: Color32::from_gray(120),
+ },
+ open: WidgetVisuals {
+ bg_fill: Color32::from_gray(27),
+ bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
+ fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
+ rounding: Rounding::same(10.0),
+ expansion: 0.0,
+ weak_bg_fill: Color32::from_gray(120),
+ },
+ };
- // https://docs.rs/egui/0.24.1/src/egui/style.rs.html#1113
+ // https://docs.rs/egui/0.24.1/src/egui/style.rs.html#1113
Visuals {
- widgets,
- selection,
- hyperlink_color: Color32::from_rgb(90, 170, 255),
- faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
- extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background
- code_bg_color: Color32::from_gray(64),
- warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
- error_fg_color: Color32::from_rgb(255, 0, 0), // red
- window_rounding: Rounding::same(6.0),
- window_shadow: Shadow::big_dark(),
- popup_shadow: Shadow::small_dark(),
- ..Visuals::dark()
- }
+ widgets,
+ selection,
+ hyperlink_color: Color32::from_rgb(90, 170, 255),
+ faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
+ extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background
+ code_bg_color: Color32::from_gray(64),
+ warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
+ error_fg_color: Color32::from_rgb(255, 0, 0), // red
+ window_rounding: Rounding::same(6.0),
+ window_shadow: Shadow::big_dark(),
+ popup_shadow: Shadow::small_dark(),
+ ..Visuals::dark()
+ }
});
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
- #[test]
- fn gupax_version_is_semver() {
- assert_eq!(crate::GUPAX_VERSION.len(), 6);
- }
+ #[test]
+ fn gupax_version_is_semver() {
+ assert_eq!(crate::GUPAX_VERSION.len(), 6);
+ }
- #[test]
- fn default_app_ratio_is_4_by_3() {
- assert_eq!(format!("{:.3}", crate::APP_MIN_WIDTH/crate::APP_MIN_HEIGHT), "1.333");
- assert_eq!(format!("{:.3}", crate::APP_DEFAULT_WIDTH/crate::APP_DEFAULT_HEIGHT), "1.333");
- }
+ #[test]
+ fn default_app_ratio_is_4_by_3() {
+ assert_eq!(
+ format!("{:.3}", crate::APP_MIN_WIDTH / crate::APP_MIN_HEIGHT),
+ "1.333"
+ );
+ assert_eq!(
+ format!(
+ "{:.3}",
+ crate::APP_DEFAULT_WIDTH / crate::APP_DEFAULT_HEIGHT
+ ),
+ "1.333"
+ );
+ }
- #[test]
- fn git_commit_eq_or_gt_40_chars() {
- assert!(crate::COMMIT.len() >= 40);
- }
+ #[test]
+ fn git_commit_eq_or_gt_40_chars() {
+ assert!(crate::COMMIT.len() >= 40);
+ }
}
diff --git a/src/disk.rs b/src/disk.rs
index bc965c9..f16bbf9 100644
--- a/src/disk.rs
+++ b/src/disk.rs
@@ -30,28 +30,21 @@
// ├─ Version/
// ├─ ...
-use std::{
- fs,
- fmt::Display,
- path::PathBuf,
- result::Result,
- sync::{Arc,Mutex},
- fmt::Write,
-};
-use serde::{Serialize,Deserialize};
+use crate::{constants::*, gupax::Ratio, human::*, macros::*, xmr::*, Tab};
+use figment::providers::{Format, Toml};
use figment::Figment;
-use figment::providers::{Format,Toml};
-use crate::{
- human::*,
- constants::*,
- gupax::Ratio,
- Tab,
- xmr::*,
- macros::*,
-};
use log::*;
+use serde::{Deserialize, Serialize};
#[cfg(target_family = "unix")]
use std::os::unix::fs::PermissionsExt;
+use std::{
+ fmt::Display,
+ fmt::Write,
+ fs,
+ path::PathBuf,
+ result::Result,
+ sync::{Arc, Mutex},
+};
//---------------------------------------------------------------------------------------------------- Const
// State file
@@ -77,16 +70,16 @@ pub const POOL_TOML: &str = "pool.toml";
// ├─ payout // Single [u64] representing total payouts
// ├─ xmr // Single [u64] representing total XMR mined in atomic units
#[cfg(target_os = "windows")]
-pub const GUPAX_P2POOL_API_DIRECTORY: &str = r"p2pool\";
+pub const GUPAX_P2POOL_API_DIRECTORY: &str = r"p2pool\";
#[cfg(target_family = "unix")]
-pub const GUPAX_P2POOL_API_DIRECTORY: &str = "p2pool/";
-pub const GUPAX_P2POOL_API_LOG: &str = "log";
+pub const GUPAX_P2POOL_API_DIRECTORY: &str = "p2pool/";
+pub const GUPAX_P2POOL_API_LOG: &str = "log";
pub const GUPAX_P2POOL_API_PAYOUT: &str = "payout";
-pub const GUPAX_P2POOL_API_XMR: &str = "xmr";
+pub const GUPAX_P2POOL_API_XMR: &str = "xmr";
pub const GUPAX_P2POOL_API_FILE_ARRAY: [&str; 3] = [
- GUPAX_P2POOL_API_LOG,
- GUPAX_P2POOL_API_PAYOUT,
- GUPAX_P2POOL_API_XMR,
+ GUPAX_P2POOL_API_LOG,
+ GUPAX_P2POOL_API_PAYOUT,
+ GUPAX_P2POOL_API_XMR,
];
#[cfg(target_os = "windows")]
@@ -119,1088 +112,1262 @@ pub const DEFAULT_XMRIG_PATH: &str = "/usr/bin/xmrig";
// into_absolute_path() | Convert relative -> absolute path
pub fn get_gupax_data_path() -> Result {
- // Get OS data folder
- // Linux | $XDG_DATA_HOME or $HOME/.local/share/gupax | /home/alice/.local/state/gupax
- // macOS | $HOME/Library/Application Support/Gupax | /Users/Alice/Library/Application Support/Gupax
- // Windows | {FOLDERID_RoamingAppData}\Gupax | C:\Users\Alice\AppData\Roaming\Gupax
- match dirs::data_dir() {
- Some(mut path) => {
- path.push(DIRECTORY);
- info!("OS | Data path ... {}", path.display());
- create_gupax_dir(&path)?;
- let mut gupax_p2pool_dir = path.clone();
- gupax_p2pool_dir.push(GUPAX_P2POOL_API_DIRECTORY);
- create_gupax_p2pool_dir(&gupax_p2pool_dir)?;
- Ok(path)
- },
- None => { error!("OS | Data path ... FAIL"); Err(TomlError::Path(PATH_ERROR.to_string())) },
- }
+ // Get OS data folder
+ // Linux | $XDG_DATA_HOME or $HOME/.local/share/gupax | /home/alice/.local/state/gupax
+ // macOS | $HOME/Library/Application Support/Gupax | /Users/Alice/Library/Application Support/Gupax
+ // Windows | {FOLDERID_RoamingAppData}\Gupax | C:\Users\Alice\AppData\Roaming\Gupax
+ match dirs::data_dir() {
+ Some(mut path) => {
+ path.push(DIRECTORY);
+ info!("OS | Data path ... {}", path.display());
+ create_gupax_dir(&path)?;
+ let mut gupax_p2pool_dir = path.clone();
+ gupax_p2pool_dir.push(GUPAX_P2POOL_API_DIRECTORY);
+ create_gupax_p2pool_dir(&gupax_p2pool_dir)?;
+ Ok(path)
+ }
+ None => {
+ error!("OS | Data path ... FAIL");
+ Err(TomlError::Path(PATH_ERROR.to_string()))
+ }
+ }
}
pub fn set_unix_750_perms(path: &PathBuf) -> Result<(), TomlError> {
- #[cfg(target_os = "windows")]
- return Ok(());
- #[cfg(target_family = "unix")]
- match fs::set_permissions(path, fs::Permissions::from_mode(0o750)) {
- Ok(_) => { info!("OS | Unix 750 permissions on path [{}] ... OK", path.display()); Ok(()) },
- Err(e) => { error!("OS | Unix 750 permissions on path [{}] ... FAIL ... {}", path.display(), e); Err(TomlError::Io(e)) },
- }
+ #[cfg(target_os = "windows")]
+ return Ok(());
+ #[cfg(target_family = "unix")]
+ match fs::set_permissions(path, fs::Permissions::from_mode(0o750)) {
+ Ok(_) => {
+ info!(
+ "OS | Unix 750 permissions on path [{}] ... OK",
+ path.display()
+ );
+ Ok(())
+ }
+ Err(e) => {
+ error!(
+ "OS | Unix 750 permissions on path [{}] ... FAIL ... {}",
+ path.display(),
+ e
+ );
+ Err(TomlError::Io(e))
+ }
+ }
}
pub fn set_unix_660_perms(path: &PathBuf) -> Result<(), TomlError> {
- #[cfg(target_os = "windows")]
- return Ok(());
- #[cfg(target_family = "unix")]
- match fs::set_permissions(path, fs::Permissions::from_mode(0o660)) {
- Ok(_) => { info!("OS | Unix 660 permissions on path [{}] ... OK", path.display()); Ok(()) },
- Err(e) => { error!("OS | Unix 660 permissions on path [{}] ... FAIL ... {}", path.display(), e); Err(TomlError::Io(e)) },
- }
+ #[cfg(target_os = "windows")]
+ return Ok(());
+ #[cfg(target_family = "unix")]
+ match fs::set_permissions(path, fs::Permissions::from_mode(0o660)) {
+ Ok(_) => {
+ info!(
+ "OS | Unix 660 permissions on path [{}] ... OK",
+ path.display()
+ );
+ Ok(())
+ }
+ Err(e) => {
+ error!(
+ "OS | Unix 660 permissions on path [{}] ... FAIL ... {}",
+ path.display(),
+ e
+ );
+ Err(TomlError::Io(e))
+ }
+ }
}
pub fn get_gupax_p2pool_path(os_data_path: &PathBuf) -> PathBuf {
- let mut gupax_p2pool_dir = os_data_path.clone();
- gupax_p2pool_dir.push(GUPAX_P2POOL_API_DIRECTORY);
- gupax_p2pool_dir
+ let mut gupax_p2pool_dir = os_data_path.clone();
+ gupax_p2pool_dir.push(GUPAX_P2POOL_API_DIRECTORY);
+ gupax_p2pool_dir
}
pub fn create_gupax_dir(path: &PathBuf) -> Result<(), TomlError> {
- // Create Gupax directory
- match fs::create_dir_all(path) {
- Ok(_) => info!("OS | Create data path ... OK"),
- Err(e) => { error!("OS | Create data path ... FAIL ... {}", e); return Err(TomlError::Io(e)) },
- }
- set_unix_750_perms(path)
+ // Create Gupax directory
+ match fs::create_dir_all(path) {
+ Ok(_) => info!("OS | Create data path ... OK"),
+ Err(e) => {
+ error!("OS | Create data path ... FAIL ... {}", e);
+ return Err(TomlError::Io(e));
+ }
+ }
+ set_unix_750_perms(path)
}
pub fn create_gupax_p2pool_dir(path: &PathBuf) -> Result<(), TomlError> {
- // Create Gupax directory
- match fs::create_dir_all(path) {
- Ok(_) => { info!("OS | Create Gupax-P2Pool API path [{}] ... OK", path.display()); Ok(()) },
- Err(e) => { error!("OS | Create Gupax-P2Pool API path [{}] ... FAIL ... {}", path.display(), e); Err(TomlError::Io(e)) },
- }
+ // Create Gupax directory
+ match fs::create_dir_all(path) {
+ Ok(_) => {
+ info!(
+ "OS | Create Gupax-P2Pool API path [{}] ... OK",
+ path.display()
+ );
+ Ok(())
+ }
+ Err(e) => {
+ error!(
+ "OS | Create Gupax-P2Pool API path [{}] ... FAIL ... {}",
+ path.display(),
+ e
+ );
+ Err(TomlError::Io(e))
+ }
+ }
}
// Convert a [File] path to a [String]
pub fn read_to_string(file: File, path: &PathBuf) -> Result {
- match fs::read_to_string(path) {
- Ok(string) => {
- info!("{:?} | Read ... OK", file);
- Ok(string)
- },
- Err(err) => {
- warn!("{:?} | Read ... FAIL", file);
- Err(TomlError::Io(err))
- },
- }
+ match fs::read_to_string(path) {
+ Ok(string) => {
+ info!("{:?} | Read ... OK", file);
+ Ok(string)
+ }
+ Err(err) => {
+ warn!("{:?} | Read ... FAIL", file);
+ Err(TomlError::Io(err))
+ }
+ }
}
// Write str to console with [info!] surrounded by "---"
pub fn print_dash(toml: &str) {
- info!("{}", HORIZONTAL);
- for i in toml.lines() { info!("{}", i); }
- info!("{}", HORIZONTAL);
+ info!("{}", HORIZONTAL);
+ for i in toml.lines() {
+ info!("{}", i);
+ }
+ info!("{}", HORIZONTAL);
}
// Turn relative paths into absolute paths
pub fn into_absolute_path(path: String) -> Result {
- let path = PathBuf::from(path);
- if path.is_relative() {
- let mut dir = std::env::current_exe()?;
- dir.pop();
- dir.push(path);
- Ok(dir)
- } else {
- Ok(path)
- }
+ let path = PathBuf::from(path);
+ if path.is_relative() {
+ let mut dir = std::env::current_exe()?;
+ dir.pop();
+ dir.push(path);
+ Ok(dir)
+ } else {
+ Ok(path)
+ }
}
//---------------------------------------------------------------------------------------------------- [State] Impl
impl Default for State {
- fn default() -> Self {
- Self::new()
- }
+ fn default() -> Self {
+ Self::new()
+ }
}
impl State {
- pub fn new() -> Self {
- let max_threads = benri::threads!();
- let current_threads = if max_threads == 1 { 1 } else { max_threads / 2 };
- Self {
- status: Status::default(),
- gupax: Gupax::default(),
- p2pool: P2pool::default(),
- xmrig: Xmrig::with_threads(max_threads, current_threads),
- version: arc_mut!(Version::default()),
- }
- }
+ pub fn new() -> Self {
+ let max_threads = benri::threads!();
+ let current_threads = if max_threads == 1 { 1 } else { max_threads / 2 };
+ Self {
+ status: Status::default(),
+ gupax: Gupax::default(),
+ p2pool: P2pool::default(),
+ xmrig: Xmrig::with_threads(max_threads, current_threads),
+ version: arc_mut!(Version::default()),
+ }
+ }
- 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())?;
- Ok(())
- }
+ 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())?;
+ Ok(())
+ }
- // Convert [&str] to [State]
- pub fn from_str(string: &str) -> Result {
- match toml::de::from_str(string) {
- Ok(state) => {
- info!("State | Parse ... OK");
- print_dash(string);
- Ok(state)
- }
- Err(err) => {
- warn!("State | String -> State ... FAIL ... {}", err);
- Err(TomlError::Deserialize(err))
- },
- }
- }
+ // Convert [&str] to [State]
+ pub fn from_str(string: &str) -> Result {
+ match toml::de::from_str(string) {
+ Ok(state) => {
+ info!("State | Parse ... OK");
+ print_dash(string);
+ Ok(state)
+ }
+ Err(err) => {
+ warn!("State | String -> State ... FAIL ... {}", err);
+ Err(TomlError::Deserialize(err))
+ }
+ }
+ }
- // Convert [State] to [String]
- pub fn to_string(&self) -> Result {
- match toml::ser::to_string(self) {
- Ok(s) => Ok(s),
- Err(e) => { error!("State | Couldn't serialize default file: {}", e); Err(TomlError::Serialize(e)) },
- }
- }
+ // Convert [State] to [String]
+ pub fn to_string(&self) -> Result {
+ match toml::ser::to_string(self) {
+ Ok(s) => Ok(s),
+ Err(e) => {
+ error!("State | Couldn't serialize default file: {}", e);
+ Err(TomlError::Serialize(e))
+ }
+ }
+ }
- // Combination of multiple functions:
- // 1. Attempt to read file from path into [String]
- // |_ Create a default file if not found
- // 2. Deserialize [String] into a proper [Struct]
- // |_ Attempt to merge if deserialization fails
- pub fn get(path: &PathBuf) -> Result {
- // Read
- let file = File::State;
- let string = match read_to_string(file, path) {
- Ok(string) => string,
- // Create
- _ => {
- Self::create_new(path)?;
- match read_to_string(file, path) {
- Ok(s) => s,
- Err(e) => return Err(e),
- }
- },
- };
- // Deserialize, attempt merge if failed
- match Self::from_str(&string) {
- Ok(s) => Ok(s),
- Err(_) => {
- warn!("State | Attempting merge...");
- match Self::merge(&string) {
- Ok(mut new) => { Self::save(&mut new, path)?; Ok(new) },
- Err(e) => Err(e),
- }
- },
- }
- }
+ // Combination of multiple functions:
+ // 1. Attempt to read file from path into [String]
+ // |_ Create a default file if not found
+ // 2. Deserialize [String] into a proper [Struct]
+ // |_ Attempt to merge if deserialization fails
+ pub fn get(path: &PathBuf) -> Result {
+ // Read
+ let file = File::State;
+ let string = match read_to_string(file, path) {
+ Ok(string) => string,
+ // Create
+ _ => {
+ Self::create_new(path)?;
+ match read_to_string(file, path) {
+ Ok(s) => s,
+ Err(e) => return Err(e),
+ }
+ }
+ };
+ // Deserialize, attempt merge if failed
+ match Self::from_str(&string) {
+ Ok(s) => Ok(s),
+ Err(_) => {
+ warn!("State | Attempting merge...");
+ match Self::merge(&string) {
+ Ok(mut new) => {
+ Self::save(&mut new, path)?;
+ Ok(new)
+ }
+ Err(e) => Err(e),
+ }
+ }
+ }
+ }
- // Completely overwrite current [state.toml]
- // with a new default version, and return [Self].
- pub fn create_new(path: &PathBuf) -> Result {
- info!("State | Creating new default...");
- let new = Self::new();
- let string = Self::to_string(&new)?;
- fs::write(path, string)?;
- info!("State | Write ... OK");
- Ok(new)
- }
+ // Completely overwrite current [state.toml]
+ // with a new default version, and return [Self].
+ pub fn create_new(path: &PathBuf) -> Result {
+ info!("State | Creating new default...");
+ let new = Self::new();
+ let string = Self::to_string(&new)?;
+ fs::write(path, string)?;
+ info!("State | Write ... OK");
+ Ok(new)
+ }
- // Save [State] onto disk file [gupax.toml]
- pub fn save(&mut self, path: &PathBuf) -> Result<(), TomlError> {
- info!("State | Saving to disk...");
- // 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())?;
- let string = match toml::ser::to_string(&self) {
- Ok(string) => {
- info!("State | Parse ... OK");
- print_dash(&string);
- string
- },
- Err(err) => { error!("State | Couldn't parse TOML into string ... FAIL"); return Err(TomlError::Serialize(err)) },
- };
- match fs::write(path, string) {
- Ok(_) => { info!("State | Save ... OK"); Ok(()) },
- Err(err) => { error!("State | Couldn't overwrite TOML file ... FAIL"); Err(TomlError::Io(err)) },
- }
- }
+ // Save [State] onto disk file [gupax.toml]
+ pub fn save(&mut self, path: &PathBuf) -> Result<(), TomlError> {
+ info!("State | Saving to disk...");
+ // 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())?;
+ let string = match toml::ser::to_string(&self) {
+ Ok(string) => {
+ info!("State | Parse ... OK");
+ print_dash(&string);
+ string
+ }
+ Err(err) => {
+ error!("State | Couldn't parse TOML into string ... FAIL");
+ return Err(TomlError::Serialize(err));
+ }
+ };
+ match fs::write(path, string) {
+ Ok(_) => {
+ info!("State | Save ... OK");
+ Ok(())
+ }
+ Err(err) => {
+ error!("State | Couldn't overwrite TOML file ... FAIL");
+ Err(TomlError::Io(err))
+ }
+ }
+ }
- // Take [String] as input, merge it with whatever the current [default] is,
- // leaving behind old keys+values and updating [default] with old valid ones.
- pub fn merge(old: &str) -> Result {
- let default = toml::ser::to_string(&Self::new()).unwrap();
- let new: Self = match Figment::from(Toml::string(&default)).merge(Toml::string(old)).extract() {
- Ok(new) => { info!("State | TOML merge ... OK"); new },
- Err(err) => { error!("State | Couldn't merge default + old TOML"); return Err(TomlError::Merge(err)) },
- };
- Ok(new)
- }
+ // Take [String] as input, merge it with whatever the current [default] is,
+ // leaving behind old keys+values and updating [default] with old valid ones.
+ pub fn merge(old: &str) -> Result {
+ let default = toml::ser::to_string(&Self::new()).unwrap();
+ let new: Self = match Figment::from(Toml::string(&default))
+ .merge(Toml::string(old))
+ .extract()
+ {
+ Ok(new) => {
+ info!("State | TOML merge ... OK");
+ new
+ }
+ Err(err) => {
+ error!("State | Couldn't merge default + old TOML");
+ return Err(TomlError::Merge(err));
+ }
+ };
+ Ok(new)
+ }
}
//---------------------------------------------------------------------------------------------------- [Node] Impl
impl Node {
- pub fn localhost() -> Self {
- Self {
- ip: "localhost".to_string(),
- rpc: "18081".to_string(),
- zmq: "18083".to_string(),
- }
- }
+ pub fn localhost() -> Self {
+ Self {
+ ip: "localhost".to_string(),
+ rpc: "18081".to_string(),
+ zmq: "18083".to_string(),
+ }
+ }
- pub fn new_vec() -> Vec<(String, Self)> {
- vec![("Local Monero Node".to_string(), Self::localhost())]
- }
+ pub fn new_vec() -> Vec<(String, Self)> {
+ vec![("Local Monero Node".to_string(), Self::localhost())]
+ }
- pub fn new_tuple() -> (String, Self) {
- ("Local Monero Node".to_string(), Self::localhost())
- }
+ pub fn new_tuple() -> (String, Self) {
+ ("Local Monero Node".to_string(), Self::localhost())
+ }
- // Convert [String] to [Node] Vec
- pub fn from_str_to_vec(string: &str) -> Result, TomlError> {
- let nodes: toml::map::Map = match toml::de::from_str(string) {
- Ok(map) => {
- info!("Node | Parse ... OK");
- map
- }
- Err(err) => {
- error!("Node | String parse ... FAIL ... {}", err);
- return Err(TomlError::Deserialize(err))
- },
- };
- let size = nodes.keys().len();
- let mut vec = Vec::with_capacity(size);
- for (key, values) in nodes.iter() {
- let ip = match values.get("ip") {
- Some(ip) => match ip.as_str() {
- Some(ip) => ip.to_string(),
- None => { error!("Node | [None] at [ip] parse"); return Err(TomlError::Parse("[None] at [ip] parse")) },
- },
- None => { error!("Node | [None] at [ip] parse"); return Err(TomlError::Parse("[None] at [ip] parse")) },
- };
- let rpc = match values.get("rpc") {
- Some(rpc) => match rpc.as_str() {
- Some(rpc) => rpc.to_string(),
- None => { error!("Node | [None] at [rpc] parse"); return Err(TomlError::Parse("[None] at [rpc] parse")) },
- },
- None => { error!("Node | [None] at [rpc] parse"); return Err(TomlError::Parse("[None] at [rpc] parse")) },
- };
- let zmq = match values.get("zmq") {
- Some(zmq) => match zmq.as_str() {
- Some(zmq) => zmq.to_string(),
- None => { error!("Node | [None] at [zmq] parse"); return Err(TomlError::Parse("[None] at [zmq] parse")) },
- },
- None => { error!("Node | [None] at [zmq] parse"); return Err(TomlError::Parse("[None] at [zmq] parse")) },
- };
- let node = Node {
- ip,
- rpc,
- zmq,
- };
- vec.push((key.clone(), node));
- }
- Ok(vec)
- }
+ // Convert [String] to [Node] Vec
+ pub fn from_str_to_vec(string: &str) -> Result, TomlError> {
+ let nodes: toml::map::Map = match toml::de::from_str(string) {
+ Ok(map) => {
+ info!("Node | Parse ... OK");
+ map
+ }
+ Err(err) => {
+ error!("Node | String parse ... FAIL ... {}", err);
+ return Err(TomlError::Deserialize(err));
+ }
+ };
+ let size = nodes.keys().len();
+ let mut vec = Vec::with_capacity(size);
+ for (key, values) in nodes.iter() {
+ let ip = match values.get("ip") {
+ Some(ip) => match ip.as_str() {
+ Some(ip) => ip.to_string(),
+ None => {
+ error!("Node | [None] at [ip] parse");
+ return Err(TomlError::Parse("[None] at [ip] parse"));
+ }
+ },
+ None => {
+ error!("Node | [None] at [ip] parse");
+ return Err(TomlError::Parse("[None] at [ip] parse"));
+ }
+ };
+ let rpc = match values.get("rpc") {
+ Some(rpc) => match rpc.as_str() {
+ Some(rpc) => rpc.to_string(),
+ None => {
+ error!("Node | [None] at [rpc] parse");
+ return Err(TomlError::Parse("[None] at [rpc] parse"));
+ }
+ },
+ None => {
+ error!("Node | [None] at [rpc] parse");
+ return Err(TomlError::Parse("[None] at [rpc] parse"));
+ }
+ };
+ let zmq = match values.get("zmq") {
+ Some(zmq) => match zmq.as_str() {
+ Some(zmq) => zmq.to_string(),
+ None => {
+ error!("Node | [None] at [zmq] parse");
+ return Err(TomlError::Parse("[None] at [zmq] parse"));
+ }
+ },
+ None => {
+ error!("Node | [None] at [zmq] parse");
+ return Err(TomlError::Parse("[None] at [zmq] parse"));
+ }
+ };
+ let node = Node { ip, rpc, zmq };
+ vec.push((key.clone(), node));
+ }
+ Ok(vec)
+ }
- // Convert [Vec<(String, Self)>] into [String]
- // that can be written as a proper TOML file
- pub fn to_string(vec: &[(String, Self)]) -> Result {
- let mut toml = String::new();
- for (key, value) in vec.iter() {
- write!(
- toml,
- "[\'{}\']\nip = {:#?}\nrpc = {:#?}\nzmq = {:#?}\n\n",
- key,
- value.ip,
- value.rpc,
- value.zmq,
- )?;
- }
- Ok(toml)
- }
+ // Convert [Vec<(String, Self)>] into [String]
+ // that can be written as a proper TOML file
+ pub fn to_string(vec: &[(String, Self)]) -> Result {
+ let mut toml = String::new();
+ for (key, value) in vec.iter() {
+ write!(
+ toml,
+ "[\'{}\']\nip = {:#?}\nrpc = {:#?}\nzmq = {:#?}\n\n",
+ key, value.ip, value.rpc, value.zmq,
+ )?;
+ }
+ Ok(toml)
+ }
- // Combination of multiple functions:
- // 1. Attempt to read file from path into [String]
- // |_ Create a default file if not found
- // 2. Deserialize [String] into a proper [Struct]
- // |_ Attempt to merge if deserialization fails
- pub fn get(path: &PathBuf) -> Result, TomlError> {
- // Read
- let file = File::Node;
- let string = match read_to_string(file, path) {
- Ok(string) => string,
- // Create
- _ => {
- Self::create_new(path)?;
- read_to_string(file, path)?
- },
- };
- // Deserialize, attempt merge if failed
- Self::from_str_to_vec(&string)
- }
+ // Combination of multiple functions:
+ // 1. Attempt to read file from path into [String]
+ // |_ Create a default file if not found
+ // 2. Deserialize [String] into a proper [Struct]
+ // |_ Attempt to merge if deserialization fails
+ pub fn get(path: &PathBuf) -> Result, TomlError> {
+ // Read
+ let file = File::Node;
+ let string = match read_to_string(file, path) {
+ Ok(string) => string,
+ // Create
+ _ => {
+ Self::create_new(path)?;
+ read_to_string(file, path)?
+ }
+ };
+ // Deserialize, attempt merge if failed
+ Self::from_str_to_vec(&string)
+ }
- // Completely overwrite current [node.toml]
- // with a new default version, and return [Vec].
- pub fn create_new(path: &PathBuf) -> Result, TomlError> {
- info!("Node | Creating new default...");
- let new = Self::new_vec();
- let string = Self::to_string(&Self::new_vec())?;
- fs::write(path, string)?;
- info!("Node | Write ... OK");
- Ok(new)
- }
+ // Completely overwrite current [node.toml]
+ // with a new default version, and return [Vec].
+ pub fn create_new(path: &PathBuf) -> Result, TomlError> {
+ info!("Node | Creating new default...");
+ let new = Self::new_vec();
+ let string = Self::to_string(&Self::new_vec())?;
+ fs::write(path, string)?;
+ info!("Node | Write ... OK");
+ Ok(new)
+ }
- // Save [Node] onto disk file [node.toml]
- pub fn save(vec: &[(String, Self)], path: &PathBuf) -> Result<(), TomlError> {
- info!("Node | Saving to disk ... [{}]", path.display());
- let string = Self::to_string(vec)?;
- match fs::write(path, string) {
- Ok(_) => { info!("Node | Save ... OK"); Ok(()) },
- Err(err) => { error!("Node | Couldn't overwrite file"); Err(TomlError::Io(err)) },
- }
- }
+ // Save [Node] onto disk file [node.toml]
+ pub fn save(vec: &[(String, Self)], path: &PathBuf) -> Result<(), TomlError> {
+ info!("Node | Saving to disk ... [{}]", path.display());
+ let string = Self::to_string(vec)?;
+ match fs::write(path, string) {
+ Ok(_) => {
+ info!("Node | Save ... OK");
+ Ok(())
+ }
+ Err(err) => {
+ error!("Node | Couldn't overwrite file");
+ Err(TomlError::Io(err))
+ }
+ }
+ }
-// pub fn merge(old: &String) -> Result {
-// info!("Node | Starting TOML merge...");
-// let default = match toml::ser::to_string(&Self::new()) {
-// Ok(string) => { info!("Node | Default TOML parse ... OK"); string },
-// Err(err) => { error!("Node | Couldn't parse default TOML into string"); return Err(TomlError::Serialize(err)) },
-// };
-// let mut new: Self = match Figment::new().merge(Toml::string(&old)).merge(Toml::string(&default)).extract() {
-// Ok(new) => { info!("Node | TOML merge ... OK"); new },
-// Err(err) => { error!("Node | Couldn't merge default + old TOML"); return Err(TomlError::Merge(err)) },
-// };
-// // Attempt save
-// Self::save(&mut new)?;
-// Ok(new)
-// }
+ // pub fn merge(old: &String) -> Result {
+ // info!("Node | Starting TOML merge...");
+ // let default = match toml::ser::to_string(&Self::new()) {
+ // Ok(string) => { info!("Node | Default TOML parse ... OK"); string },
+ // Err(err) => { error!("Node | Couldn't parse default TOML into string"); return Err(TomlError::Serialize(err)) },
+ // };
+ // let mut new: Self = match Figment::new().merge(Toml::string(&old)).merge(Toml::string(&default)).extract() {
+ // Ok(new) => { info!("Node | TOML merge ... OK"); new },
+ // Err(err) => { error!("Node | Couldn't merge default + old TOML"); return Err(TomlError::Merge(err)) },
+ // };
+ // // Attempt save
+ // Self::save(&mut new)?;
+ // Ok(new)
+ // }
}
//---------------------------------------------------------------------------------------------------- [Pool] impl
impl Pool {
- pub fn p2pool() -> Self {
- Self {
- rig: GUPAX_VERSION_UNDERSCORE.to_string(),
- ip: "localhost".to_string(),
- port: "3333".to_string(),
- }
- }
+ pub fn p2pool() -> Self {
+ Self {
+ rig: GUPAX_VERSION_UNDERSCORE.to_string(),
+ ip: "localhost".to_string(),
+ port: "3333".to_string(),
+ }
+ }
- pub fn new_vec() -> Vec<(String, Self)> {
- vec![("Local P2Pool".to_string(), Self::p2pool())]
- }
+ pub fn new_vec() -> Vec<(String, Self)> {
+ vec![("Local P2Pool".to_string(), Self::p2pool())]
+ }
- pub fn new_tuple() -> (String, Self) {
- ("Local P2Pool".to_string(), Self::p2pool())
- }
+ pub fn new_tuple() -> (String, Self) {
+ ("Local P2Pool".to_string(), Self::p2pool())
+ }
- pub fn from_str_to_vec(string: &str) -> Result, TomlError> {
- let pools: toml::map::Map = match toml::de::from_str(string) {
- Ok(map) => {
- info!("Pool | Parse ... OK");
- map
- }
- Err(err) => {
- error!("Pool | String parse ... FAIL ... {}", err);
- return Err(TomlError::Deserialize(err))
- },
- };
- let size = pools.keys().len();
- let mut vec = Vec::with_capacity(size);
- // We have to do [.as_str()] -> [.to_string()] to get rid of the \"...\" that gets added on.
- for (key, values) in pools.iter() {
- let rig = match values.get("rig") {
- Some(rig) => match rig.as_str() {
- Some(rig) => rig.to_string(),
- None => { error!("Pool | [None] at [rig] parse"); return Err(TomlError::Parse("[None] at [rig] parse")) },
- },
- None => { error!("Pool | [None] at [rig] parse"); return Err(TomlError::Parse("[None] at [rig] parse")) },
- };
- let ip = match values.get("ip") {
- Some(ip) => match ip.as_str() {
- Some(ip) => ip.to_string(),
- None => { error!("Pool | [None] at [ip] parse"); return Err(TomlError::Parse("[None] at [ip] parse")) },
- },
- None => { error!("Pool | [None] at [ip] parse"); return Err(TomlError::Parse("[None] at [ip] parse")) },
- };
- let port = match values.get("port") {
- Some(port) => match port.as_str() {
- Some(port) => port.to_string(),
- None => { error!("Pool | [None] at [port] parse"); return Err(TomlError::Parse("[None] at [port] parse")) },
- },
- None => { error!("Pool | [None] at [port] parse"); return Err(TomlError::Parse("[None] at [port] parse")) },
- };
- let pool = Pool {
- rig,
- ip,
- port,
- };
- vec.push((key.clone(), pool));
- }
- Ok(vec)
- }
+ pub fn from_str_to_vec(string: &str) -> Result, TomlError> {
+ let pools: toml::map::Map = match toml::de::from_str(string) {
+ Ok(map) => {
+ info!("Pool | Parse ... OK");
+ map
+ }
+ Err(err) => {
+ error!("Pool | String parse ... FAIL ... {}", err);
+ return Err(TomlError::Deserialize(err));
+ }
+ };
+ let size = pools.keys().len();
+ let mut vec = Vec::with_capacity(size);
+ // We have to do [.as_str()] -> [.to_string()] to get rid of the \"...\" that gets added on.
+ for (key, values) in pools.iter() {
+ let rig = match values.get("rig") {
+ Some(rig) => match rig.as_str() {
+ Some(rig) => rig.to_string(),
+ None => {
+ error!("Pool | [None] at [rig] parse");
+ return Err(TomlError::Parse("[None] at [rig] parse"));
+ }
+ },
+ None => {
+ error!("Pool | [None] at [rig] parse");
+ return Err(TomlError::Parse("[None] at [rig] parse"));
+ }
+ };
+ let ip = match values.get("ip") {
+ Some(ip) => match ip.as_str() {
+ Some(ip) => ip.to_string(),
+ None => {
+ error!("Pool | [None] at [ip] parse");
+ return Err(TomlError::Parse("[None] at [ip] parse"));
+ }
+ },
+ None => {
+ error!("Pool | [None] at [ip] parse");
+ return Err(TomlError::Parse("[None] at [ip] parse"));
+ }
+ };
+ let port = match values.get("port") {
+ Some(port) => match port.as_str() {
+ Some(port) => port.to_string(),
+ None => {
+ error!("Pool | [None] at [port] parse");
+ return Err(TomlError::Parse("[None] at [port] parse"));
+ }
+ },
+ None => {
+ error!("Pool | [None] at [port] parse");
+ return Err(TomlError::Parse("[None] at [port] parse"));
+ }
+ };
+ let pool = Pool { rig, ip, port };
+ vec.push((key.clone(), pool));
+ }
+ Ok(vec)
+ }
- pub fn to_string(vec: &[(String, Self)]) -> Result {
- let mut toml = String::new();
- for (key, value) in vec.iter() {
- write!(
- toml,
- "[\'{}\']\nrig = {:#?}\nip = {:#?}\nport = {:#?}\n\n",
- key,
- value.rig,
- value.ip,
- value.port,
- )?;
- }
- Ok(toml)
- }
+ pub fn to_string(vec: &[(String, Self)]) -> Result {
+ let mut toml = String::new();
+ for (key, value) in vec.iter() {
+ write!(
+ toml,
+ "[\'{}\']\nrig = {:#?}\nip = {:#?}\nport = {:#?}\n\n",
+ key, value.rig, value.ip, value.port,
+ )?;
+ }
+ Ok(toml)
+ }
- pub fn get(path: &PathBuf) -> Result, TomlError> {
- // Read
- let file = File::Pool;
- let string = match read_to_string(file, path) {
- Ok(string) => string,
- // Create
- _ => {
- Self::create_new(path)?;
- read_to_string(file, path)?
- },
- };
- // Deserialize
- Self::from_str_to_vec(&string)
- }
+ pub fn get(path: &PathBuf) -> Result, TomlError> {
+ // Read
+ let file = File::Pool;
+ let string = match read_to_string(file, path) {
+ Ok(string) => string,
+ // Create
+ _ => {
+ Self::create_new(path)?;
+ read_to_string(file, path)?
+ }
+ };
+ // Deserialize
+ Self::from_str_to_vec(&string)
+ }
- pub fn create_new(path: &PathBuf) -> Result, TomlError> {
- info!("Pool | Creating new default...");
- let new = Self::new_vec();
- let string = Self::to_string(&Self::new_vec())?;
- fs::write(path, string)?;
- info!("Pool | Write ... OK");
- Ok(new)
- }
+ pub fn create_new(path: &PathBuf) -> Result, TomlError> {
+ info!("Pool | Creating new default...");
+ let new = Self::new_vec();
+ let string = Self::to_string(&Self::new_vec())?;
+ fs::write(path, string)?;
+ info!("Pool | Write ... OK");
+ Ok(new)
+ }
- pub fn save(vec: &[(String, Self)], path: &PathBuf) -> Result<(), TomlError> {
- info!("Pool | Saving to disk ... [{}]", path.display());
- let string = Self::to_string(vec)?;
- match fs::write(path, string) {
- Ok(_) => { info!("Pool | Save ... OK"); Ok(()) },
- Err(err) => { error!("Pool | Couldn't overwrite file"); Err(TomlError::Io(err)) },
- }
- }
+ pub fn save(vec: &[(String, Self)], path: &PathBuf) -> Result<(), TomlError> {
+ info!("Pool | Saving to disk ... [{}]", path.display());
+ let string = Self::to_string(vec)?;
+ match fs::write(path, string) {
+ Ok(_) => {
+ info!("Pool | Save ... OK");
+ Ok(())
+ }
+ Err(err) => {
+ error!("Pool | Couldn't overwrite file");
+ Err(TomlError::Io(err))
+ }
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- Gupax-P2Pool API
-#[derive(Clone,Debug)]
+#[derive(Clone, Debug)]
pub struct GupaxP2poolApi {
- pub log: String, // Log file only containing full payout lines
- pub log_rev: String, // Same as above but reversed based off lines
- pub payout: HumanNumber, // Human-friendly display of payout count
- pub payout_u64: u64, // [u64] version of above
- pub payout_ord: PayoutOrd, // Ordered Vec of payouts, see [PayoutOrd]
- pub payout_low: String, // A pre-allocated/computed [String] of the above Vec from low payout to high
- pub payout_high: String, // Same as above but high -> low
- pub xmr: AtomicUnit, // XMR stored as atomic units
- pub path_log: PathBuf, // Path to [log]
- pub path_payout: PathBuf, // Path to [payout]
- pub path_xmr: PathBuf, // Path to [xmr]
+ pub log: String, // Log file only containing full payout lines
+ pub log_rev: String, // Same as above but reversed based off lines
+ pub payout: HumanNumber, // Human-friendly display of payout count
+ pub payout_u64: u64, // [u64] version of above
+ pub payout_ord: PayoutOrd, // Ordered Vec of payouts, see [PayoutOrd]
+ pub payout_low: String, // A pre-allocated/computed [String] of the above Vec from low payout to high
+ pub payout_high: String, // Same as above but high -> low
+ pub xmr: AtomicUnit, // XMR stored as atomic units
+ pub path_log: PathBuf, // Path to [log]
+ pub path_payout: PathBuf, // Path to [payout]
+ pub path_xmr: PathBuf, // Path to [xmr]
}
-impl Default for GupaxP2poolApi { fn default() -> Self { Self::new() } }
+impl Default for GupaxP2poolApi {
+ fn default() -> Self {
+ Self::new()
+ }
+}
impl GupaxP2poolApi {
- //---------------------------------------------------------------------------------------------------- Init, these pretty much only get called once
- pub fn new() -> Self {
- Self {
- log: String::new(),
- log_rev: String::new(),
- payout: HumanNumber::unknown(),
- payout_u64: 0,
- payout_ord: PayoutOrd::new(),
- payout_low: String::new(),
- payout_high: String::new(),
- xmr: AtomicUnit::new(),
- path_xmr: PathBuf::new(),
- path_payout: PathBuf::new(),
- path_log: PathBuf::new(),
- }
- }
+ //---------------------------------------------------------------------------------------------------- Init, these pretty much only get called once
+ pub fn new() -> Self {
+ Self {
+ log: String::new(),
+ log_rev: String::new(),
+ payout: HumanNumber::unknown(),
+ payout_u64: 0,
+ payout_ord: PayoutOrd::new(),
+ payout_low: String::new(),
+ payout_high: String::new(),
+ xmr: AtomicUnit::new(),
+ path_xmr: PathBuf::new(),
+ path_payout: PathBuf::new(),
+ path_log: PathBuf::new(),
+ }
+ }
- pub fn fill_paths(&mut self, gupax_p2pool_dir: &PathBuf) {
- let mut path_log = gupax_p2pool_dir.clone();
- let mut path_payout = gupax_p2pool_dir.clone();
- let mut path_xmr = gupax_p2pool_dir.clone();
- path_log.push(GUPAX_P2POOL_API_LOG);
- path_payout.push(GUPAX_P2POOL_API_PAYOUT);
- path_xmr.push(GUPAX_P2POOL_API_XMR);
- *self = Self {
- path_log,
- path_payout,
- path_xmr,
- ..std::mem::take(self)
- };
- }
+ pub fn fill_paths(&mut self, gupax_p2pool_dir: &PathBuf) {
+ let mut path_log = gupax_p2pool_dir.clone();
+ let mut path_payout = gupax_p2pool_dir.clone();
+ let mut path_xmr = gupax_p2pool_dir.clone();
+ path_log.push(GUPAX_P2POOL_API_LOG);
+ path_payout.push(GUPAX_P2POOL_API_PAYOUT);
+ path_xmr.push(GUPAX_P2POOL_API_XMR);
+ *self = Self {
+ path_log,
+ path_payout,
+ path_xmr,
+ ..std::mem::take(self)
+ };
+ }
- pub fn create_all_files(gupax_p2pool_dir: &PathBuf) -> Result<(), TomlError> {
- use std::io::Write;
- for file in GUPAX_P2POOL_API_FILE_ARRAY {
- let mut path = gupax_p2pool_dir.clone();
- path.push(file);
- if path.exists() {
- info!("GupaxP2poolApi | [{}] already exists, skipping...", path.display());
- continue
- }
- match std::fs::File::create(&path) {
- Ok(mut f) => {
- match file {
- GUPAX_P2POOL_API_PAYOUT|GUPAX_P2POOL_API_XMR => writeln!(f, "0")?,
- _ => (),
- }
- info!("GupaxP2poolApi | [{}] create ... OK", path.display());
- },
- Err(e) => { warn!("GupaxP2poolApi | [{}] create ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) },
- }
- }
- Ok(())
- }
+ pub fn create_all_files(gupax_p2pool_dir: &PathBuf) -> Result<(), TomlError> {
+ use std::io::Write;
+ for file in GUPAX_P2POOL_API_FILE_ARRAY {
+ let mut path = gupax_p2pool_dir.clone();
+ path.push(file);
+ if path.exists() {
+ info!(
+ "GupaxP2poolApi | [{}] already exists, skipping...",
+ path.display()
+ );
+ continue;
+ }
+ match std::fs::File::create(&path) {
+ Ok(mut f) => {
+ match file {
+ GUPAX_P2POOL_API_PAYOUT | GUPAX_P2POOL_API_XMR => writeln!(f, "0")?,
+ _ => (),
+ }
+ info!("GupaxP2poolApi | [{}] create ... OK", path.display());
+ }
+ Err(e) => {
+ warn!(
+ "GupaxP2poolApi | [{}] create ... FAIL: {}",
+ path.display(),
+ e
+ );
+ return Err(TomlError::Io(e));
+ }
+ }
+ }
+ Ok(())
+ }
- pub fn read_all_files_and_update(&mut self) -> Result<(), TomlError> {
- let payout_u64 = match read_to_string(File::Payout, &self.path_payout)?.trim().parse::() {
- Ok(o) => o,
- Err(e) => { warn!("GupaxP2poolApi | [payout] parse error: {}", e); return Err(TomlError::Parse("payout")) }
- };
- let xmr = match read_to_string(File::Xmr, &self.path_xmr)?.trim().parse::() {
- Ok(o) => AtomicUnit::from_u64(o),
- Err(e) => { warn!("GupaxP2poolApi | [xmr] parse error: {}", e); return Err(TomlError::Parse("xmr")) }
- };
- let payout = HumanNumber::from_u64(payout_u64);
- let log = read_to_string(File::Log, &self.path_log)?;
- self.payout_ord.update_from_payout_log(&log);
- self.update_payout_strings();
- *self = Self {
- log,
- payout,
- payout_u64,
- xmr,
- ..std::mem::take(self)
- };
- self.update_log_rev();
- Ok(())
- }
+ pub fn read_all_files_and_update(&mut self) -> Result<(), TomlError> {
+ let payout_u64 = match read_to_string(File::Payout, &self.path_payout)?
+ .trim()
+ .parse::()
+ {
+ Ok(o) => o,
+ Err(e) => {
+ warn!("GupaxP2poolApi | [payout] parse error: {}", e);
+ return Err(TomlError::Parse("payout"));
+ }
+ };
+ let xmr = match read_to_string(File::Xmr, &self.path_xmr)?
+ .trim()
+ .parse::()
+ {
+ Ok(o) => AtomicUnit::from_u64(o),
+ Err(e) => {
+ warn!("GupaxP2poolApi | [xmr] parse error: {}", e);
+ return Err(TomlError::Parse("xmr"));
+ }
+ };
+ let payout = HumanNumber::from_u64(payout_u64);
+ let log = read_to_string(File::Log, &self.path_log)?;
+ self.payout_ord.update_from_payout_log(&log);
+ self.update_payout_strings();
+ *self = Self {
+ log,
+ payout,
+ payout_u64,
+ xmr,
+ ..std::mem::take(self)
+ };
+ self.update_log_rev();
+ Ok(())
+ }
- // Completely delete the [p2pool] folder and create defaults.
- pub fn create_new(path: &PathBuf) -> Result<(), TomlError> {
- info!("GupaxP2poolApi | Deleting old folder at [{}]...", path.display());
- std::fs::remove_dir_all(&path)?;
- info!("GupaxP2poolApi | Creating new default folder at [{}]...", path.display());
- create_gupax_p2pool_dir(&path)?;
- Self::create_all_files(&path)?;
- Ok(())
- }
+ // Completely delete the [p2pool] folder and create defaults.
+ pub fn create_new(path: &PathBuf) -> Result<(), TomlError> {
+ info!(
+ "GupaxP2poolApi | Deleting old folder at [{}]...",
+ path.display()
+ );
+ std::fs::remove_dir_all(&path)?;
+ info!(
+ "GupaxP2poolApi | Creating new default folder at [{}]...",
+ path.display()
+ );
+ create_gupax_p2pool_dir(&path)?;
+ Self::create_all_files(&path)?;
+ Ok(())
+ }
- //---------------------------------------------------------------------------------------------------- Live, functions that actually update/write live stats
- pub fn update_log_rev(&mut self) {
- let mut log_rev = String::with_capacity(self.log.len());
- for line in self.log.lines().rev() {
- log_rev.push_str(line);
- log_rev.push('\n');
- }
- self.log_rev = log_rev;
- }
+ //---------------------------------------------------------------------------------------------------- Live, functions that actually update/write live stats
+ pub fn update_log_rev(&mut self) {
+ let mut log_rev = String::with_capacity(self.log.len());
+ for line in self.log.lines().rev() {
+ log_rev.push_str(line);
+ log_rev.push('\n');
+ }
+ self.log_rev = log_rev;
+ }
- pub fn format_payout(date: &str, atomic_unit: &AtomicUnit, block: &HumanNumber) -> String {
- format!("{} | {} XMR | Block {}", date, atomic_unit, block)
- }
+ pub fn format_payout(date: &str, atomic_unit: &AtomicUnit, block: &HumanNumber) -> String {
+ format!("{} | {} XMR | Block {}", date, atomic_unit, block)
+ }
- pub fn append_log(&mut self, formatted_log_line: &str) {
- self.log.push_str(formatted_log_line);
- self.log.push('\n');
- }
+ pub fn append_log(&mut self, formatted_log_line: &str) {
+ self.log.push_str(formatted_log_line);
+ self.log.push('\n');
+ }
- pub fn append_head_log_rev(&mut self, formatted_log_line: &str) {
- self.log_rev = format!("{}\n{}", formatted_log_line, self.log_rev);
- }
+ pub fn append_head_log_rev(&mut self, formatted_log_line: &str) {
+ self.log_rev = format!("{}\n{}", formatted_log_line, self.log_rev);
+ }
- pub fn update_payout_low(&mut self) {
- self.payout_ord.sort_payout_low_to_high();
- self.payout_low = self.payout_ord.to_string();
- }
+ pub fn update_payout_low(&mut self) {
+ self.payout_ord.sort_payout_low_to_high();
+ self.payout_low = self.payout_ord.to_string();
+ }
- pub fn update_payout_high(&mut self) {
- self.payout_ord.sort_payout_high_to_low();
- self.payout_high = self.payout_ord.to_string();
- }
+ pub fn update_payout_high(&mut self) {
+ self.payout_ord.sort_payout_high_to_low();
+ self.payout_high = self.payout_ord.to_string();
+ }
- pub fn update_payout_strings(&mut self) {
- self.update_payout_low();
- self.update_payout_high();
- }
+ pub fn update_payout_strings(&mut self) {
+ self.update_payout_low();
+ self.update_payout_high();
+ }
- // Takes the (date, atomic_unit, block) and updates [self] and the [PayoutOrd]
- pub fn add_payout(&mut self, formatted_log_line: &str, date: String, atomic_unit: AtomicUnit, block: HumanNumber) {
- self.append_log(formatted_log_line);
- self.append_head_log_rev(formatted_log_line);
- self.payout_u64 += 1;
- self.payout = HumanNumber::from_u64(self.payout_u64);
- self.xmr = self.xmr.add_self(atomic_unit);
- self.payout_ord.push(date, atomic_unit, block);
- self.update_payout_strings();
- }
+ // Takes the (date, atomic_unit, block) and updates [self] and the [PayoutOrd]
+ pub fn add_payout(
+ &mut self,
+ formatted_log_line: &str,
+ date: String,
+ atomic_unit: AtomicUnit,
+ block: HumanNumber,
+ ) {
+ self.append_log(formatted_log_line);
+ self.append_head_log_rev(formatted_log_line);
+ self.payout_u64 += 1;
+ self.payout = HumanNumber::from_u64(self.payout_u64);
+ self.xmr = self.xmr.add_self(atomic_unit);
+ self.payout_ord.push(date, atomic_unit, block);
+ self.update_payout_strings();
+ }
- pub fn write_to_all_files(&self, formatted_log_line: &str) -> Result<(), TomlError> {
- Self::disk_overwrite(&self.payout_u64.to_string(), &self.path_payout)?;
- Self::disk_overwrite(&self.xmr.to_string(), &self.path_xmr)?;
- Self::disk_append(formatted_log_line, &self.path_log)?;
- Ok(())
- }
+ pub fn write_to_all_files(&self, formatted_log_line: &str) -> Result<(), TomlError> {
+ Self::disk_overwrite(&self.payout_u64.to_string(), &self.path_payout)?;
+ Self::disk_overwrite(&self.xmr.to_string(), &self.path_xmr)?;
+ Self::disk_append(formatted_log_line, &self.path_log)?;
+ Ok(())
+ }
- pub fn disk_append(formatted_log_line: &str, path: &PathBuf) -> Result<(), TomlError> {
- use std::io::Write;
- let mut file = match fs::OpenOptions::new().append(true).create(true).open(path) {
- Ok(f) => f,
- Err(e) => { error!("GupaxP2poolApi | Append [{}] ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) },
- };
- match writeln!(file, "{}", formatted_log_line) {
- Ok(_) => { debug!("GupaxP2poolApi | Append [{}] ... OK", path.display()); Ok(()) },
- Err(e) => { error!("GupaxP2poolApi | Append [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) },
- }
- }
+ pub fn disk_append(formatted_log_line: &str, path: &PathBuf) -> Result<(), TomlError> {
+ use std::io::Write;
+ let mut file = match fs::OpenOptions::new().append(true).create(true).open(path) {
+ Ok(f) => f,
+ Err(e) => {
+ error!(
+ "GupaxP2poolApi | Append [{}] ... FAIL: {}",
+ path.display(),
+ e
+ );
+ return Err(TomlError::Io(e));
+ }
+ };
+ match writeln!(file, "{}", formatted_log_line) {
+ Ok(_) => {
+ debug!("GupaxP2poolApi | Append [{}] ... OK", path.display());
+ Ok(())
+ }
+ Err(e) => {
+ error!(
+ "GupaxP2poolApi | Append [{}] ... FAIL: {}",
+ path.display(),
+ e
+ );
+ Err(TomlError::Io(e))
+ }
+ }
+ }
- pub fn disk_overwrite(string: &str, path: &PathBuf) -> Result<(), TomlError> {
- use std::io::Write;
- let mut file = match fs::OpenOptions::new().write(true).truncate(true).create(true).open(path) {
- Ok(f) => f,
- Err(e) => { error!("GupaxP2poolApi | Overwrite [{}] ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) },
- };
- match writeln!(file, "{}", string) {
- Ok(_) => { debug!("GupaxP2poolApi | Overwrite [{}] ... OK", path.display()); Ok(()) },
- Err(e) => { error!("GupaxP2poolApi | Overwrite [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) },
- }
- }
+ pub fn disk_overwrite(string: &str, path: &PathBuf) -> Result<(), TomlError> {
+ use std::io::Write;
+ let mut file = match fs::OpenOptions::new()
+ .write(true)
+ .truncate(true)
+ .create(true)
+ .open(path)
+ {
+ Ok(f) => f,
+ Err(e) => {
+ error!(
+ "GupaxP2poolApi | Overwrite [{}] ... FAIL: {}",
+ path.display(),
+ e
+ );
+ return Err(TomlError::Io(e));
+ }
+ };
+ match writeln!(file, "{}", string) {
+ Ok(_) => {
+ debug!("GupaxP2poolApi | Overwrite [{}] ... OK", path.display());
+ Ok(())
+ }
+ Err(e) => {
+ error!(
+ "GupaxP2poolApi | Overwrite [{}] ... FAIL: {}",
+ path.display(),
+ e
+ );
+ Err(TomlError::Io(e))
+ }
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- Custom Error [TomlError]
#[derive(Debug)]
pub enum TomlError {
- Io(std::io::Error),
- Path(String),
- Serialize(toml::ser::Error),
- Deserialize(toml::de::Error),
- Merge(figment::Error),
- Format(std::fmt::Error),
- Parse(&'static str),
+ Io(std::io::Error),
+ Path(String),
+ Serialize(toml::ser::Error),
+ Deserialize(toml::de::Error),
+ Merge(figment::Error),
+ Format(std::fmt::Error),
+ Parse(&'static str),
}
impl Display for TomlError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- use TomlError::*;
- match self {
- Io(err) => write!(f, "{}: IO | {}", ERROR, err),
- Path(err) => write!(f, "{}: Path | {}", ERROR, err),
- Serialize(err) => write!(f, "{}: Serialize | {}", ERROR, err),
- Deserialize(err) => write!(f, "{}: Deserialize | {}", ERROR, err),
- Merge(err) => write!(f, "{}: Merge | {}", ERROR, err),
- Format(err) => write!(f, "{}: Format | {}", ERROR, err),
- Parse(err) => write!(f, "{}: Parse | {}", ERROR, err),
- }
- }
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ use TomlError::*;
+ match self {
+ Io(err) => write!(f, "{}: IO | {}", ERROR, err),
+ Path(err) => write!(f, "{}: Path | {}", ERROR, err),
+ Serialize(err) => write!(f, "{}: Serialize | {}", ERROR, err),
+ Deserialize(err) => write!(f, "{}: Deserialize | {}", ERROR, err),
+ Merge(err) => write!(f, "{}: Merge | {}", ERROR, err),
+ Format(err) => write!(f, "{}: Format | {}", ERROR, err),
+ Parse(err) => write!(f, "{}: Parse | {}", ERROR, err),
+ }
+ }
}
impl From for TomlError {
- fn from(err: std::io::Error) -> Self {
- TomlError::Io(err)
- }
+ fn from(err: std::io::Error) -> Self {
+ TomlError::Io(err)
+ }
}
impl From for TomlError {
- fn from(err: std::fmt::Error) -> Self {
- TomlError::Format(err)
- }
+ fn from(err: std::fmt::Error) -> Self {
+ TomlError::Format(err)
+ }
}
//---------------------------------------------------------------------------------------------------- [File] Enum (for matching which file)
-#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub enum File {
- // State files
- State, // state.toml | Gupax state
- Node, // node.toml | P2Pool manual node selector
- Pool, // pool.toml | XMRig manual pool selector
+ // State files
+ State, // state.toml | Gupax state
+ Node, // node.toml | P2Pool manual node selector
+ Pool, // pool.toml | XMRig manual pool selector
- // Gupax-P2Pool API
- Log, // log | Raw log lines of P2Pool payouts received
- Payout, // payout | Single [u64] representing total payouts
- Xmr, // xmr | Single [u64] representing total XMR mined in atomic units
+ // Gupax-P2Pool API
+ Log, // log | Raw log lines of P2Pool payouts received
+ Payout, // payout | Single [u64] representing total payouts
+ Xmr, // xmr | Single [u64] representing total XMR mined in atomic units
}
//---------------------------------------------------------------------------------------------------- [Submenu] enum for [Status] tab
-#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub enum Submenu {
- Processes,
- P2pool,
- Benchmarks,
+ Processes,
+ P2pool,
+ Benchmarks,
}
impl Default for Submenu {
- fn default() -> Self {
- Self::Processes
- }
+ fn default() -> Self {
+ Self::Processes
+ }
}
impl Display for Submenu {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- use Submenu::*;
- match self {
- P2pool => write!(f, "P2Pool"),
- _ => write!(f, "{:?}", self),
- }
- }
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ use Submenu::*;
+ match self {
+ P2pool => write!(f, "P2Pool"),
+ _ => write!(f, "{:?}", self),
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- [PayoutView] enum for [Status/P2Pool] tab
// The enum buttons for selecting which "view" to sort the payout log in.
-#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub enum PayoutView {
- Latest, // Shows the most recent logs first
- Oldest, // Shows the oldest logs first
- Biggest, // Shows highest to lowest payouts
- Smallest, // Shows lowest to highest payouts
+ Latest, // Shows the most recent logs first
+ Oldest, // Shows the oldest logs first
+ Biggest, // Shows highest to lowest payouts
+ Smallest, // Shows lowest to highest payouts
}
impl PayoutView {
- fn new() -> Self {
- Self::Latest
- }
+ fn new() -> Self {
+ Self::Latest
+ }
}
impl Default for PayoutView {
- fn default() -> Self {
- Self::new()
- }
+ fn default() -> Self {
+ Self::new()
+ }
}
impl Display for PayoutView {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "{:?}", self)
- }
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{:?}", self)
+ }
}
//---------------------------------------------------------------------------------------------------- [Hash] enum for [Status/P2Pool]
-#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub enum Hash {
- Hash,
- Kilo,
- Mega,
- Giga,
+ Hash,
+ Kilo,
+ Mega,
+ Giga,
}
impl Default for Hash {
- fn default() -> Self {
- Self::Hash
- }
+ fn default() -> Self {
+ Self::Hash
+ }
}
impl Hash {
- pub fn convert_to_hash(f: f64, from: Self) -> f64 {
- match from {
- Self::Hash => f,
- Self::Kilo => f * 1_000.0,
- Self::Mega => f * 1_000_000.0,
- Self::Giga => f * 1_000_000_000.0,
- }
- }
+ pub fn convert_to_hash(f: f64, from: Self) -> f64 {
+ match from {
+ Self::Hash => f,
+ Self::Kilo => f * 1_000.0,
+ Self::Mega => f * 1_000_000.0,
+ Self::Giga => f * 1_000_000_000.0,
+ }
+ }
- pub fn convert(f: f64, og: Self, new: Self) -> f64 {
- match og {
- Self::Hash => {
- match new {
- Self::Hash => f,
- Self::Kilo => f / 1_000.0,
- Self::Mega => f / 1_000_000.0,
- Self::Giga => f / 1_000_000_000.0,
- }
- },
- Self::Kilo => {
- match new {
- Self::Hash => f * 1_000.0,
- Self::Kilo => f,
- Self::Mega => f / 1_000.0,
- Self::Giga => f / 1_000_000.0,
- }
- },
- Self::Mega => {
- match new {
- Self::Hash => f * 1_000_000.0,
- Self::Kilo => f * 1_000.0,
- Self::Mega => f,
- Self::Giga => f / 1_000.0,
- }
- },
- Self::Giga => {
- match new {
- Self::Hash => f * 1_000_000_000.0,
- Self::Kilo => f * 1_000_000.0,
- Self::Mega => f * 1_000.0,
- Self::Giga => f,
- }
- },
- }
- }
+ pub fn convert(f: f64, og: Self, new: Self) -> f64 {
+ match og {
+ Self::Hash => match new {
+ Self::Hash => f,
+ Self::Kilo => f / 1_000.0,
+ Self::Mega => f / 1_000_000.0,
+ Self::Giga => f / 1_000_000_000.0,
+ },
+ Self::Kilo => match new {
+ Self::Hash => f * 1_000.0,
+ Self::Kilo => f,
+ Self::Mega => f / 1_000.0,
+ Self::Giga => f / 1_000_000.0,
+ },
+ Self::Mega => match new {
+ Self::Hash => f * 1_000_000.0,
+ Self::Kilo => f * 1_000.0,
+ Self::Mega => f,
+ Self::Giga => f / 1_000.0,
+ },
+ Self::Giga => match new {
+ Self::Hash => f * 1_000_000_000.0,
+ Self::Kilo => f * 1_000_000.0,
+ Self::Mega => f * 1_000.0,
+ Self::Giga => f,
+ },
+ }
+ }
}
impl Display for Hash {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- match self {
- Hash::Hash => write!(f, "Hash"),
- _ => write!(f, "{:?}hash", self),
- }
- }
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ Hash::Hash => write!(f, "Hash"),
+ _ => write!(f, "{:?}hash", self),
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- [Node] Struct
-#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct Node {
- pub ip: String,
- pub rpc: String,
- pub zmq: String,
+ pub ip: String,
+ pub rpc: String,
+ pub zmq: String,
}
//---------------------------------------------------------------------------------------------------- [Pool] Struct
-#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct Pool {
- pub rig: String,
- pub ip: String,
- pub port: String,
+ pub rig: String,
+ pub ip: String,
+ pub port: String,
}
//---------------------------------------------------------------------------------------------------- [State] Struct
-#[derive(Clone,Debug,Deserialize,Serialize)]
+#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct State {
- pub status: Status,
- pub gupax: Gupax,
- pub p2pool: P2pool,
- pub xmrig: Xmrig,
- pub version: Arc>,
+ pub status: Status,
+ pub gupax: Gupax,
+ pub p2pool: P2pool,
+ pub xmrig: Xmrig,
+ pub version: Arc>,
}
-#[derive(Clone,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct Status {
- pub submenu: Submenu,
- pub payout_view: PayoutView,
- pub monero_enabled: bool,
- pub manual_hash: bool,
- pub hashrate: f64,
- pub hash_metric: Hash,
+ pub submenu: Submenu,
+ pub payout_view: PayoutView,
+ pub monero_enabled: bool,
+ pub manual_hash: bool,
+ pub hashrate: f64,
+ pub hash_metric: Hash,
}
-#[derive(Clone,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct Gupax {
- pub simple: bool,
- pub auto_update: bool,
- pub auto_p2pool: bool,
- pub auto_xmrig: bool,
-// pub auto_monero: bool,
- pub ask_before_quit: bool,
- pub save_before_quit: bool,
- pub update_via_tor: bool,
- pub p2pool_path: String,
- pub xmrig_path: String,
- pub absolute_p2pool_path: PathBuf,
- pub absolute_xmrig_path: PathBuf,
- pub selected_width: u16,
- pub selected_height: u16,
- pub selected_scale: f32,
- pub tab: Tab,
- pub ratio: Ratio,
+ pub simple: bool,
+ pub auto_update: bool,
+ pub auto_p2pool: bool,
+ pub auto_xmrig: bool,
+ // pub auto_monero: bool,
+ pub ask_before_quit: bool,
+ pub save_before_quit: bool,
+ pub update_via_tor: bool,
+ pub p2pool_path: String,
+ pub xmrig_path: String,
+ pub absolute_p2pool_path: PathBuf,
+ pub absolute_xmrig_path: PathBuf,
+ pub selected_width: u16,
+ pub selected_height: u16,
+ pub selected_scale: f32,
+ pub tab: Tab,
+ pub ratio: Ratio,
}
-#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct P2pool {
- pub simple: bool,
- pub mini: bool,
- pub auto_ping: bool,
- pub auto_select: bool,
- pub backup_host: bool,
- pub out_peers: u16,
- pub in_peers: u16,
- pub log_level: u8,
- pub node: String,
- pub arguments: String,
- pub address: String,
- pub name: String,
- pub ip: String,
- pub rpc: String,
- pub zmq: String,
- pub selected_index: usize,
- pub selected_name: String,
- pub selected_ip: String,
- pub selected_rpc: String,
- pub selected_zmq: String,
+ pub simple: bool,
+ pub mini: bool,
+ pub auto_ping: bool,
+ pub auto_select: bool,
+ pub backup_host: bool,
+ pub out_peers: u16,
+ pub in_peers: u16,
+ pub log_level: u8,
+ pub node: String,
+ pub arguments: String,
+ pub address: String,
+ pub name: String,
+ pub ip: String,
+ pub rpc: String,
+ pub zmq: String,
+ pub selected_index: usize,
+ pub selected_name: String,
+ pub selected_ip: String,
+ pub selected_rpc: String,
+ pub selected_zmq: String,
}
-#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub struct Xmrig {
- pub simple: bool,
- pub pause: u8,
- pub simple_rig: String,
- pub arguments: String,
- pub tls: bool,
- pub keepalive: bool,
- pub max_threads: usize,
- pub current_threads: usize,
- pub address: String,
- pub api_ip: String,
- pub api_port: String,
- pub name: String,
- pub rig: String,
- pub ip: String,
- pub port: String,
- pub selected_index: usize,
- pub selected_name: String,
- pub selected_rig: String,
- pub selected_ip: String,
- pub selected_port: String,
+ pub simple: bool,
+ pub pause: u8,
+ pub simple_rig: String,
+ pub arguments: String,
+ pub tls: bool,
+ pub keepalive: bool,
+ pub max_threads: usize,
+ pub current_threads: usize,
+ pub address: String,
+ pub api_ip: String,
+ pub api_port: String,
+ pub name: String,
+ pub rig: String,
+ pub ip: String,
+ pub port: String,
+ pub selected_index: usize,
+ pub selected_name: String,
+ pub selected_rig: String,
+ pub selected_ip: String,
+ pub selected_port: String,
}
-#[derive(Clone,Debug,Deserialize,Serialize)]
+#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Version {
- pub gupax: String,
- pub p2pool: String,
- pub xmrig: String,
+ pub gupax: String,
+ pub p2pool: String,
+ pub xmrig: String,
}
//---------------------------------------------------------------------------------------------------- [State] Defaults
impl Default for Status {
- fn default() -> Self {
- Self {
- submenu: Submenu::default(),
- payout_view: PayoutView::default(),
- monero_enabled: false,
- manual_hash: false,
- hashrate: 1.0,
- hash_metric: Hash::default(),
- }
- }
+ fn default() -> Self {
+ Self {
+ submenu: Submenu::default(),
+ payout_view: PayoutView::default(),
+ monero_enabled: false,
+ manual_hash: false,
+ hashrate: 1.0,
+ hash_metric: Hash::default(),
+ }
+ }
}
impl Default for Gupax {
- fn default() -> Self {
- Self {
- simple: true,
- auto_update: true,
- auto_p2pool: false,
- auto_xmrig: false,
- ask_before_quit: true,
- save_before_quit: true,
- update_via_tor: true,
- p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
- xmrig_path: DEFAULT_XMRIG_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(),
- selected_width: APP_DEFAULT_WIDTH as u16,
- selected_height: APP_DEFAULT_HEIGHT as u16,
- selected_scale: APP_DEFAULT_SCALE,
- ratio: Ratio::Width,
- tab: Tab::About,
- }
- }
+ fn default() -> Self {
+ Self {
+ simple: true,
+ auto_update: true,
+ auto_p2pool: false,
+ auto_xmrig: false,
+ ask_before_quit: true,
+ save_before_quit: true,
+ update_via_tor: true,
+ p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
+ xmrig_path: DEFAULT_XMRIG_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(),
+ selected_width: APP_DEFAULT_WIDTH as u16,
+ selected_height: APP_DEFAULT_HEIGHT as u16,
+ selected_scale: APP_DEFAULT_SCALE,
+ ratio: Ratio::Width,
+ tab: Tab::About,
+ }
+ }
}
impl Default for P2pool {
- fn default() -> Self {
- Self {
- simple: true,
- mini: true,
- auto_ping: true,
- auto_select: true,
- backup_host: true,
- out_peers: 10,
- in_peers: 10,
- log_level: 3,
- node: crate::RemoteNode::new().to_string(),
- arguments: String::new(),
- address: String::with_capacity(96),
- name: "Local Monero Node".to_string(),
- ip: "localhost".to_string(),
- rpc: "18081".to_string(),
- zmq: "18083".to_string(),
- selected_index: 0,
- selected_name: "Local Monero Node".to_string(),
- selected_ip: "localhost".to_string(),
- selected_rpc: "18081".to_string(),
- selected_zmq: "18083".to_string(),
- }
- }
+ fn default() -> Self {
+ Self {
+ simple: true,
+ mini: true,
+ auto_ping: true,
+ auto_select: true,
+ backup_host: true,
+ out_peers: 10,
+ in_peers: 10,
+ log_level: 3,
+ node: crate::RemoteNode::new().to_string(),
+ arguments: String::new(),
+ address: String::with_capacity(96),
+ name: "Local Monero Node".to_string(),
+ ip: "localhost".to_string(),
+ rpc: "18081".to_string(),
+ zmq: "18083".to_string(),
+ selected_index: 0,
+ selected_name: "Local Monero Node".to_string(),
+ selected_ip: "localhost".to_string(),
+ selected_rpc: "18081".to_string(),
+ selected_zmq: "18083".to_string(),
+ }
+ }
}
impl Xmrig {
- fn with_threads(max_threads: usize, current_threads: usize) -> Self {
- let xmrig = Self::default();
- Self {
- max_threads,
- current_threads,
- ..xmrig
- }
- }
+ fn with_threads(max_threads: usize, current_threads: usize) -> Self {
+ let xmrig = Self::default();
+ Self {
+ max_threads,
+ current_threads,
+ ..xmrig
+ }
+ }
}
impl Default for Xmrig {
- fn default() -> Self {
- Self {
- simple: true,
- pause: 0,
- simple_rig: String::with_capacity(30),
- arguments: String::with_capacity(300),
- address: String::with_capacity(96),
- name: "Local P2Pool".to_string(),
- rig: GUPAX_VERSION_UNDERSCORE.to_string(),
- ip: "localhost".to_string(),
- 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: "18088".to_string(),
- tls: false,
- keepalive: false,
- current_threads: 1,
- max_threads: 1,
- }
- }
+ fn default() -> Self {
+ Self {
+ simple: true,
+ pause: 0,
+ simple_rig: String::with_capacity(30),
+ arguments: String::with_capacity(300),
+ address: String::with_capacity(96),
+ name: "Local P2Pool".to_string(),
+ rig: GUPAX_VERSION_UNDERSCORE.to_string(),
+ ip: "localhost".to_string(),
+ 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: "18088".to_string(),
+ tls: false,
+ keepalive: false,
+ current_threads: 1,
+ max_threads: 1,
+ }
+ }
}
impl Default for Version {
- fn default() -> Self {
- Self {
- gupax: GUPAX_VERSION.to_string(),
- p2pool: P2POOL_VERSION.to_string(),
- xmrig: XMRIG_VERSION.to_string(),
- }
- }
+ fn default() -> Self {
+ Self {
+ gupax: GUPAX_VERSION.to_string(),
+ p2pool: P2POOL_VERSION.to_string(),
+ xmrig: XMRIG_VERSION.to_string(),
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
- #[test]
- fn serde_default_state() {
- let state = crate::State::new();
- let string = crate::State::to_string(&state).unwrap();
- crate::State::from_str(&string).unwrap();
- }
- #[test]
- fn serde_default_node() {
- let node = crate::Node::new_vec();
- let string = crate::Node::to_string(&node).unwrap();
- crate::Node::from_str_to_vec(&string).unwrap();
- }
- #[test]
- fn serde_default_pool() {
- let pool = crate::Pool::new_vec();
- let string = crate::Pool::to_string(&pool).unwrap();
- crate::Pool::from_str_to_vec(&string).unwrap();
- }
+ #[test]
+ fn serde_default_state() {
+ let state = crate::State::new();
+ let string = crate::State::to_string(&state).unwrap();
+ crate::State::from_str(&string).unwrap();
+ }
+ #[test]
+ fn serde_default_node() {
+ let node = crate::Node::new_vec();
+ let string = crate::Node::to_string(&node).unwrap();
+ crate::Node::from_str_to_vec(&string).unwrap();
+ }
+ #[test]
+ fn serde_default_pool() {
+ let pool = crate::Pool::new_vec();
+ let string = crate::Pool::to_string(&pool).unwrap();
+ crate::Pool::from_str_to_vec(&string).unwrap();
+ }
- #[test]
- fn serde_custom_state() {
- let state = r#"
+ #[test]
+ fn serde_custom_state() {
+ let state = r#"
[gupax]
simple = true
auto_update = true
@@ -1276,13 +1443,13 @@ mod test {
p2pool = "v2.5"
xmrig = "v6.18.0"
"#;
- let state = crate::State::from_str(state).unwrap();
- crate::State::to_string(&state).unwrap();
- }
+ let state = crate::State::from_str(state).unwrap();
+ crate::State::to_string(&state).unwrap();
+ }
- #[test]
- fn serde_custom_node() {
- let node = r#"
+ #[test]
+ fn serde_custom_node() {
+ let node = r#"
['Local Monero Node']
ip = "localhost"
rpc = "18081"
@@ -1298,13 +1465,13 @@ mod test {
rpc = "1"
zmq = "65535"
"#;
- let node = crate::Node::from_str_to_vec(node).unwrap();
- crate::Node::to_string(&node).unwrap();
- }
+ let node = crate::Node::from_str_to_vec(node).unwrap();
+ crate::Node::to_string(&node).unwrap();
+ }
- #[test]
- fn serde_custom_pool() {
- let pool = r#"
+ #[test]
+ fn serde_custom_pool() {
+ let pool = r#"
['Local P2Pool']
rig = "Gupax_v1.0.0"
ip = "localhost"
@@ -1320,15 +1487,15 @@ mod test {
ip = "127.0.0.1"
port = "65535"
"#;
- let pool = crate::Pool::from_str_to_vec(pool).unwrap();
- crate::Pool::to_string(&pool).unwrap();
- }
+ let pool = crate::Pool::from_str_to_vec(pool).unwrap();
+ crate::Pool::to_string(&pool).unwrap();
+ }
- // Make sure we keep the user's old values that are still
- // valid but discard the ones that don't exist anymore.
- #[test]
- fn merge_state() {
- let bad_state = r#"
+ // Make sure we keep the user's old values that are still
+ // valid but discard the ones that don't exist anymore.
+ #[test]
+ fn merge_state() {
+ let bad_state = r#"
[gupax]
SETTING_THAT_DOESNT_EXIST_ANYMORE = 123123
simple = false
@@ -1397,74 +1564,76 @@ mod test {
p2pool = "v2.5"
xmrig = "v6.18.0"
"#.to_string();
- let merged_state = crate::State::merge(&bad_state).unwrap();
- let merged_state = crate::State::to_string(&merged_state).unwrap();
- println!("{}", merged_state);
- assert!(merged_state.contains("simple = false"));
- assert!(merged_state.contains("in_peers = 450"));
- assert!(merged_state.contains("log_level = 6"));
- assert!(merged_state.contains(r#"node = "Seth""#));
- assert!(!merged_state.contains("SETTING_THAT_DOESNT_EXIST_ANYMORE"));
- assert!(merged_state.contains("44hintoFpuo3ugKfcqJvh5BmrsTRpnTasJmetKC4VXCt6QDtbHVuixdTtsm6Ptp7Y8haXnJ6j8Gj2dra8CKy5ewz7Vi9CYW"));
- assert!(merged_state.contains("backup_host = true"));
- }
+ let merged_state = crate::State::merge(&bad_state).unwrap();
+ let merged_state = crate::State::to_string(&merged_state).unwrap();
+ println!("{}", merged_state);
+ assert!(merged_state.contains("simple = false"));
+ assert!(merged_state.contains("in_peers = 450"));
+ assert!(merged_state.contains("log_level = 6"));
+ assert!(merged_state.contains(r#"node = "Seth""#));
+ assert!(!merged_state.contains("SETTING_THAT_DOESNT_EXIST_ANYMORE"));
+ assert!(merged_state.contains("44hintoFpuo3ugKfcqJvh5BmrsTRpnTasJmetKC4VXCt6QDtbHVuixdTtsm6Ptp7Y8haXnJ6j8Gj2dra8CKy5ewz7Vi9CYW"));
+ assert!(merged_state.contains("backup_host = true"));
+ }
- #[test]
- fn create_and_serde_gupax_p2pool_api() {
- use crate::disk::GupaxP2poolApi;
- use crate::xmr::PayoutOrd;
- use crate::xmr::AtomicUnit;
+ #[test]
+ fn create_and_serde_gupax_p2pool_api() {
+ use crate::disk::GupaxP2poolApi;
+ use crate::xmr::AtomicUnit;
+ use crate::xmr::PayoutOrd;
- // Get API dir, fill paths.
- let mut api = GupaxP2poolApi::new();
- let mut path = crate::disk::get_gupax_data_path().unwrap();
- path.push(crate::disk::GUPAX_P2POOL_API_DIRECTORY);
- GupaxP2poolApi::fill_paths(&mut api, &path);
- println!("{:#?}", api);
+ // Get API dir, fill paths.
+ let mut api = GupaxP2poolApi::new();
+ let mut path = crate::disk::get_gupax_data_path().unwrap();
+ path.push(crate::disk::GUPAX_P2POOL_API_DIRECTORY);
+ GupaxP2poolApi::fill_paths(&mut api, &path);
+ println!("{:#?}", api);
- // Create, write some fake data.
- GupaxP2poolApi::create_all_files(&path).unwrap();
- api.log = "NOTICE 2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816".to_string();
- api.payout_u64 = 1;
- api.xmr = AtomicUnit::from_u64(2);
- let (date, atomic_unit, block) = PayoutOrd::parse_raw_payout_line(&api.log);
- let formatted_log_line = GupaxP2poolApi::format_payout(&date, &atomic_unit, &block);
- GupaxP2poolApi::write_to_all_files(&api, &formatted_log_line).unwrap();
- println!("AFTER WRITE: {:#?}", api);
+ // Create, write some fake data.
+ GupaxP2poolApi::create_all_files(&path).unwrap();
+ api.log = "NOTICE 2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816".to_string();
+ api.payout_u64 = 1;
+ api.xmr = AtomicUnit::from_u64(2);
+ let (date, atomic_unit, block) = PayoutOrd::parse_raw_payout_line(&api.log);
+ let formatted_log_line = GupaxP2poolApi::format_payout(&date, &atomic_unit, &block);
+ GupaxP2poolApi::write_to_all_files(&api, &formatted_log_line).unwrap();
+ println!("AFTER WRITE: {:#?}", api);
- // Read
- GupaxP2poolApi::read_all_files_and_update(&mut api).unwrap();
- println!("AFTER READ: {:#?}", api);
+ // Read
+ GupaxP2poolApi::read_all_files_and_update(&mut api).unwrap();
+ println!("AFTER READ: {:#?}", api);
- // Assert that the file read mutated the internal struct correctly.
- assert_eq!(api.payout_u64, 1);
- assert_eq!(api.xmr.to_u64(), 2);
- assert!(!api.payout_ord.is_empty());
- assert!(api.log.contains("2022-01-27 01:30:23.1377 | 0.000000000001 XMR | Block 2,642,816"));
- }
+ // Assert that the file read mutated the internal struct correctly.
+ assert_eq!(api.payout_u64, 1);
+ assert_eq!(api.xmr.to_u64(), 2);
+ assert!(!api.payout_ord.is_empty());
+ assert!(api
+ .log
+ .contains("2022-01-27 01:30:23.1377 | 0.000000000001 XMR | Block 2,642,816"));
+ }
- #[test]
- fn convert_hash() {
- use crate::disk::Hash;
- let hash = 1.0;
- assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Hash), 1.0);
- assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Kilo), 0.001);
- assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Mega), 0.000_001);
- assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Giga), 0.000_000_001);
- let hash = 1.0;
- assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Hash), 1_000.0);
- assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Kilo), 1.0);
- assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Mega), 0.001);
- assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Giga), 0.000_001);
- let hash = 1.0;
- assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Hash), 1_000_000.0);
- assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Kilo), 1_000.0);
- assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Mega), 1.0);
- assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Giga), 0.001);
- let hash = 1.0;
- assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Hash), 1_000_000_000.0);
- assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Kilo), 1_000_000.0);
- assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Mega), 1_000.0);
- assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Giga), 1.0);
- }
+ #[test]
+ fn convert_hash() {
+ use crate::disk::Hash;
+ let hash = 1.0;
+ assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Hash), 1.0);
+ assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Kilo), 0.001);
+ assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Mega), 0.000_001);
+ assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Giga), 0.000_000_001);
+ let hash = 1.0;
+ assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Hash), 1_000.0);
+ assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Kilo), 1.0);
+ assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Mega), 0.001);
+ assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Giga), 0.000_001);
+ let hash = 1.0;
+ assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Hash), 1_000_000.0);
+ assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Kilo), 1_000.0);
+ assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Mega), 1.0);
+ assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Giga), 0.001);
+ let hash = 1.0;
+ assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Hash), 1_000_000_000.0);
+ assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Kilo), 1_000_000.0);
+ assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Mega), 1_000.0);
+ assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Giga), 1.0);
+ }
}
diff --git a/src/ferris.rs b/src/ferris.rs
index e5b1a18..f4bb2a5 100644
--- a/src/ferris.rs
+++ b/src/ferris.rs
@@ -24,65 +24,9 @@ pub const FERRIS_ERROR: &[u8] = include_bytes!("../images/ferris/error.png");
pub const FERRIS_PANIC: &[u8] = include_bytes!("../images/ferris/panic.png"); // This isnt technically ferris but its ok since its spooky
pub const FERRIS_SUDO: &[u8] = include_bytes!("../images/ferris/sudo.png");
-
-
-
-
-
-
-
-
-
-
-
-
-
-
// This is the ANSI representation of Ferris in string form.
// Calling [println!] on this straight up prints a 256-bit color Ferris to the terminal.
// The ANSI codes were generated with [https://docs.rs/ansipix], but there is no reason to include the library and
// do the computation at runtime since [ansipix] outputs a [String], so why not just put the output here as a const :D
// It's hidden below with whitespace because it makes my text editor crash...
pub const FERRIS_ANSI: &str = " \n \u{1b}[38;2;244;149;0m▄\u{1b}[0m\u{1b}[38;2;245;150;0m▄\u{1b}[0m \u{1b}[38;2;244;148;0m▄\u{1b}[0m\u{1b}[38;2;244;148;0m▄\u{1b}[0m \u{1b}[38;2;244;146;0m▄\u{1b}[0m \n \u{1b}[38;2;244;146;0m▄\u{1b}[0m\u{1b}[38;2;244;146;0m▄\u{1b}[0m \u{1b}[38;2;244;145;0m▄\u{1b}[0m\u{1b}[38;2;244;144;0;48;2;244;147;0m▄\u{1b}[0m\u{1b}[38;2;244;144;0;48;2;244;147;0m▄\u{1b}[0m\u{1b}[38;2;244;144;0m▄\u{1b}[0m \u{1b}[38;2;244;143;0;48;2;244;146;0m▄\u{1b}[0m\u{1b}[38;2;244;143;0;48;2;244;145;0m▄\u{1b}[0m\u{1b}[38;2;244;143;0;48;2;244;145;0m▄\u{1b}[0m\u{1b}[38;2;244;143;0;48;2;244;145;0m▄\u{1b}[0m \u{1b}[38;2;244;142;0m▄\u{1b}[0m\u{1b}[38;2;244;142;0;48;2;244;144;0m▄\u{1b}[0m\u{1b}[38;2;244;142;0;48;2;244;144;0m▄\u{1b}[0m\u{1b}[38;2;244;141;0;48;2;244;144;0m▄\u{1b}[0m \u{1b}[38;2;244;140;0m▄\u{1b}[0m \n \u{1b}[38;2;244;141;0;48;2;244;143;0m▄\u{1b}[0m\u{1b}[38;2;244;141;0;48;2;244;143;0m▄\u{1b}[0m\u{1b}[38;2;244;141;0;48;2;244;143;0m▄\u{1b}[0m\u{1b}[38;2;244;140;0m▄\u{1b}[0m\u{1b}[38;2;244;140;0m▄\u{1b}[0m\u{1b}[38;2;244;140;0;48;2;244;142;0m▄\u{1b}[0m\u{1b}[38;2;244;140;0;48;2;244;142;0m▄\u{1b}[0m\u{1b}[38;2;244;140;0;48;2;244;142;0m▄\u{1b}[0m\u{1b}[38;2;244;139;0;48;2;244;142;0m▄\u{1b}[0m\u{1b}[38;2;244;139;0;48;2;244;141;0m▄\u{1b}[0m\u{1b}[38;2;244;139;0;48;2;244;141;0m▄\u{1b}[0m\u{1b}[38;2;244;139;0;48;2;244;141;0m▄\u{1b}[0m\u{1b}[38;2;244;139;0;48;2;244;141;0m▄\u{1b}[0m\u{1b}[38;2;244;138;0;48;2;244;141;0m▄\u{1b}[0m\u{1b}[38;2;244;138;0;48;2;244;140;0m▄\u{1b}[0m\u{1b}[38;2;244;138;0;48;2;244;140;0m▄\u{1b}[0m\u{1b}[38;2;244;138;0;48;2;244;140;0m▄\u{1b}[0m\u{1b}[38;2;245;137;0;48;2;244;140;0m▄\u{1b}[0m\u{1b}[38;2;245;137;0;48;2;244;140;0m▄\u{1b}[0m\u{1b}[38;2;245;137;0;48;2;244;139;0m▄\u{1b}[0m\u{1b}[38;2;245;137;0;48;2;244;139;0m▄\u{1b}[0m\u{1b}[38;2;245;137;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;138;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;138;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;138;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;138;0m▄\u{1b}[0m \n \u{1b}[38;2;244;138;0;48;2;243;140;0m▄\u{1b}[0m\u{1b}[38;2;245;138;0;48;2;244;140;0m▄\u{1b}[0m\u{1b}[38;2;245;137;0m▄\u{1b}[0m\u{1b}[38;2;245;137;0m▄\u{1b}[0m \u{1b}[38;2;245;137;0;48;2;244;139;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;139;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;139;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;138;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;138;0m▄\u{1b}[0m\u{1b}[38;2;245;136;0;48;2;244;138;0m▄\u{1b}[0m\u{1b}[38;2;245;135;0;48;2;244;138;0m▄\u{1b}[0m\u{1b}[38;2;245;135;0;48;2;245;137;0m▄\u{1b}[0m\u{1b}[38;2;245;135;0;48;2;245;137;0m▄\u{1b}[0m\u{1b}[38;2;245;135;0;48;2;245;137;0m▄\u{1b}[0m\u{1b}[38;2;245;134;0;48;2;245;137;0m▄\u{1b}[0m\u{1b}[38;2;245;134;0;48;2;245;137;0m▄\u{1b}[0m\u{1b}[38;2;245;134;0;48;2;245;136;0m▄\u{1b}[0m\u{1b}[38;2;245;134;0;48;2;245;136;0m▄\u{1b}[0m\u{1b}[38;2;245;134;0;48;2;245;136;0m▄\u{1b}[0m\u{1b}[38;2;245;133;0;48;2;245;136;0m▄\u{1b}[0m\u{1b}[38;2;245;133;0;48;2;245;136;0m▄\u{1b}[0m\u{1b}[38;2;245;133;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;133;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;133;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;131;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;131;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;131;0;48;2;245;133;0m▄\u{1b}[0m\u{1b}[38;2;245;131;0;48;2;245;133;0m▄\u{1b}[0m \u{1b}[38;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;130;0;48;2;245;132;0m▄\u{1b}[0m\u{1b}[38;2;245;130;0;48;2;245;132;0m▄\u{1b}[0m \n \u{1b}[38;2;245;133;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;133;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;133;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;135;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;132;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;131;0;48;2;245;134;0m▄\u{1b}[0m\u{1b}[38;2;245;131;0;48;2;245;133;0m▄\u{1b}[0m\u{1b}[38;2;245;131;0;48;2;245;133;0m▄\u{1b}[0m\u{1b}[38;2;245;131;0;48;2;245;133;0m▄\u{1b}[0m\u{1b}[38;2;245;130;0;48;2;245;133;0m▄\u{1b}[0m\u{1b}[38;2;245;130;0;48;2;245;133;0m▄\u{1b}[0m\u{1b}[38;2;245;130;0;48;2;245;132;0m▄\u{1b}[0m\u{1b}[38;2;245;130;0;48;2;245;132;0m▄\u{1b}[0m\u{1b}[38;2;245;130;0;48;2;245;132;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0;48;2;245;132;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0;48;2;245;132;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0;48;2;245;131;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0;48;2;245;131;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0;48;2;245;131;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;131;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;128;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;128;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;128;0m▄\u{1b}[0m\u{1b}[38;2;245;125;0;48;2;245;128;0m▄\u{1b}[0m\u{1b}[38;2;245;125;0;48;2;245;127;0m▄\u{1b}[0m \n \u{1b}[38;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0;48;2;245;132;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;131;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;131;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;128;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;130;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;127;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;129;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;128;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;128;0m▄\u{1b}[0m\u{1b}[38;2;245;126;0;48;2;245;128;0m▄\u{1b}[0m\u{1b}[38;2;245;125;0;48;2;245;128;0m▄\u{1b}[0m\u{1b}[38;2;245;125;0;48;2;245;127;0m▄\u{1b}[0m\u{1b}[38;2;245;125;0;48;2;245;127;0m▄\u{1b}[0m\u{1b}[38;2;245;125;0;48;2;245;127;0m▄\u{1b}[0m\u{1b}[38;2;245;125;0;48;2;245;127;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;127;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0m▄\u{1b}[0m \n \u{1b}[38;2;246;126;0m▀\u{1b}[0m\u{1b}[38;2;245;125;0;48;2;245;127;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;127;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;127;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;124;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;126;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;123;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;125;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;122;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;124;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;121;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0;48;2;245;123;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;117;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;117;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;117;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;117;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m \n \u{1b}[38;2;245;120;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;120;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;122;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;119;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;121;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;118;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;117;0;48;2;245;120;0m▄\u{1b}[0m\u{1b}[38;2;245;117;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;117;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;117;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;119;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;113;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;113;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;113;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;113;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;113;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;113;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;113;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;246;113;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;113;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;113;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0m▄\u{1b}[0m \n \u{1b}[38;2;245;119;0m▀\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;116;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;118;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;115;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;117;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;114;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;113;0;48;2;245;116;0m▄\u{1b}[0m\u{1b}[38;2;245;113;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;113;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;113;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;115;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;112;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;113;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;113;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;246;113;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;113;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m \n \u{1b}[38;2;246;112;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;114;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;113;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;245;113;0m▄\u{1b}[0m\u{1b}[38;2;246;111;0;48;2;246;113;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;113;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;110;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;112;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;109;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;111;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;108;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;110;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;102;0m▄\u{1b}[0m \n \u{1b}[38;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;107;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;109;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;106;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;108;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;105;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;107;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;104;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;106;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;99;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;99;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;99;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;99;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0m▄\u{1b}[0m \n \u{1b}[38;2;246;105;0m▀\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;103;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;105;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;102;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;104;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;101;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;103;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;100;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;99;0;48;2;246;102;0m▄\u{1b}[0m\u{1b}[38;2;246;99;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;99;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;99;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;101;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;246;93;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;246;93;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;93;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;93;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;90;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;90;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;90;0;48;2;246;92;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0m▀\u{1b}[0m \n \u{1b}[38;2;246;98;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;246;98;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;100;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;97;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;99;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;96;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0;48;2;246;98;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;246;95;0;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;97;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;246;94;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;248;248;248m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;255;255;255m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;255;255;255m▄\u{1b}[0m\u{1b}[38;2;145;145;145;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;203;203;203m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;255;255;255m▄\u{1b}[0m\u{1b}[38;2;255;255;255;48;2;255;255;255m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;92;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;246;88;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;246;88;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;247;88;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;247;88;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0m▀\u{1b}[0m \n \u{1b}[38;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;93;0;48;2;246;96;0m▄\u{1b}[0m\u{1b}[38;2;246;93;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;93;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;93;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;95;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;92;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;94;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;91;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;90;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;90;0;48;2;246;93;0m▄\u{1b}[0m\u{1b}[38;2;246;90;0;48;2;246;92;0m▄\u{1b}[0m\u{1b}[38;2;246;90;0;48;2;246;92;0m▄\u{1b}[0m\u{1b}[38;2;246;90;0;48;2;246;92;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;92;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;255;255;255m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;255;255;255m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;255;255;255m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;255;255;255m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0;48;2;247;83;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0;48;2;247;83;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0m▄\u{1b}[0m \n \u{1b}[38;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;246;89;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;246;88;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0;48;2;246;91;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;88;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;90;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;87;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;246;89;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;246;88;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;247;88;0m▄\u{1b}[0m\u{1b}[38;2;247;86;0;48;2;247;88;0m▄\u{1b}[0m\u{1b}[38;2;247;85;0;48;2;247;88;0m▄\u{1b}[0m\u{1b}[38;2;247;85;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;85;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;85;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;85;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;247;81;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;247;81;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;247;81;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;247;81;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;247;81;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0m▄\u{1b}[0m \n \u{1b}[38;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;85;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;85;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;87;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;247;84;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;86;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;83;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;85;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;82;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0;48;2;247;84;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0;48;2;247;83;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0;48;2;247;83;0m▄\u{1b}[0m\u{1b}[38;2;247;81;0;48;2;247;83;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;83;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;83;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;79;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;78;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m \n \u{1b}[38;2;247;80;0;48;2;247;83;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m\u{1b}[38;2;247;80;0;48;2;247;82;0m▄\u{1b}[0m \u{1b}[38;2;165;43;0m▀\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0m▀\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;80;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;77;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;79;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;232;71;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0m▀\u{1b}[0m\u{1b}[38;2;165;43;0m▀\u{1b}[0m \u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m \u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0m▀\u{1b}[0m \n \u{1b}[38;2;247;78;0m▀\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;78;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;77;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;77;0m▄\u{1b}[0m \u{1b}[38;2;165;43;0m▀\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0m▄\u{1b}[0m \u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;246;76;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;231;69;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;238;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;242;74;0m▀\u{1b}[0m \u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0m▀\u{1b}[0m \u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0m▀\u{1b}[0m \n \u{1b}[38;2;247;76;0m▀\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m \u{1b}[38;2;165;43;0m▀\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0m▄\u{1b}[0m \u{1b}[38;2;242;74;0m▀\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0m▀\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;242;74;0m▄\u{1b}[0m\u{1b}[38;2;237;72;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;0;0;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;20;5;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;0;0;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;229;68;0;48;2;233;70;0m▄\u{1b}[0m\u{1b}[38;2;233;70;0m▀\u{1b}[0m \u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m\u{1b}[38;2;165;43;0m▀\u{1b}[0m \u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m \n \u{1b}[38;2;247;76;0m▀\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0m▄\u{1b}[0m \u{1b}[38;2;165;43;0m▀\u{1b}[0m\u{1b}[38;2;165;43;0m▄\u{1b}[0m \u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;227;68;0;48;2;232;70;0m▄\u{1b}[0m\u{1b}[38;2;227;68;0;48;2;232;70;0m▄\u{1b}[0m\u{1b}[38;2;227;68;0;48;2;232;70;0m▄\u{1b}[0m\u{1b}[38;2;227;68;0;48;2;232;70;0m▄\u{1b}[0m\u{1b}[38;2;227;68;0;48;2;232;70;0m▄\u{1b}[0m\u{1b}[38;2;227;68;0;48;2;232;70;0m▄\u{1b}[0m\u{1b}[38;2;227;68;0;48;2;232;70;0m▄\u{1b}[0m\u{1b}[38;2;227;68;0;48;2;232;70;0m▄\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m\u{1b}[38;2;232;70;0m▀\u{1b}[0m \u{1b}[38;2;225;67;0m▀\u{1b}[0m \u{1b}[38;2;225;67;0m▀\u{1b}[0m\u{1b}[38;2;225;67;0m▀\u{1b}[0m\u{1b}[38;2;220;65;0;48;2;225;67;0m▄\u{1b}[0m\u{1b}[38;2;220;65;0;48;2;225;67;0m▄\u{1b}[0m\u{1b}[38;2;220;65;0;48;2;225;67;0m▄\u{1b}[0m\u{1b}[38;2;220;65;0;48;2;225;67;0m▄\u{1b}[0m\u{1b}[38;2;220;65;0;48;2;225;67;0m▄\u{1b}[0m\u{1b}[38;2;220;65;0;48;2;225;67;0m▄\u{1b}[0m\u{1b}[38;2;220;65;0;48;2;225;67;0m▄\u{1b}[0m\u{1b}[38;2;220;65;0;48;2;225;67;0m▄\u{1b}[0m\u{1b}[38;2;225;67;0m▀\u{1b}[0m \u{1b}[38;2;165;43;0;48;2;165;43;0m▄\u{1b}[0m \u{1b}[38;2;247;76;0;48;2;248;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0m▀\u{1b}[0m \n \u{1b}[38;2;247;76;0m▀\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0m▄\u{1b}[0m \u{1b}[38;2;222;66;0m▀\u{1b}[0m\u{1b}[38;2;222;66;0m▀\u{1b}[0m\u{1b}[38;2;217;63;0;48;2;222;66;0m▄\u{1b}[0m\u{1b}[38;2;217;63;0;48;2;222;66;0m▄\u{1b}[0m\u{1b}[38;2;217;63;0;48;2;222;66;0m▄\u{1b}[0m\u{1b}[38;2;217;63;0;48;2;222;66;0m▄\u{1b}[0m\u{1b}[38;2;217;63;0;48;2;222;66;0m▄\u{1b}[0m\u{1b}[38;2;217;63;0m▄\u{1b}[0m\u{1b}[38;2;217;63;0m▄\u{1b}[0m \u{1b}[38;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;212;61;0;48;2;216;63;0m▄\u{1b}[0m\u{1b}[38;2;212;61;0;48;2;216;63;0m▄\u{1b}[0m\u{1b}[38;2;212;61;0;48;2;216;63;0m▄\u{1b}[0m\u{1b}[38;2;212;61;0;48;2;216;63;0m▄\u{1b}[0m\u{1b}[38;2;212;61;0;48;2;216;63;0m▄\u{1b}[0m\u{1b}[38;2;212;61;0;48;2;216;63;0m▄\u{1b}[0m\u{1b}[38;2;216;63;0m▀\u{1b}[0m \u{1b}[38;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;76;0m▀\u{1b}[0m \n \u{1b}[38;2;247;76;0m▀\u{1b}[0m\u{1b}[38;2;247;76;0;48;2;247;76;0m▄\u{1b}[0m \u{1b}[38;2;212;61;0m▀\u{1b}[0m\u{1b}[38;2;207;59;0;48;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0;48;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0;48;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0;48;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0;48;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0;48;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0;48;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0;48;2;212;61;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0m▄\u{1b}[0m\u{1b}[38;2;207;60;0m▄\u{1b}[0m \u{1b}[38;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;207;59;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;207;59;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;207;59;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;207;59;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;207;59;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;207;59;0m▄\u{1b}[0m\u{1b}[38;2;207;59;0m▀\u{1b}[0m\u{1b}[38;2;207;59;0m▀\u{1b}[0m \u{1b}[38;2;247;76;0m▄\u{1b}[0m\u{1b}[38;2;247;75;0;48;2;247;76;0m▄\u{1b}[0m \n \u{1b}[38;2;247;76;0m▀\u{1b}[0m \u{1b}[38;2;204;58;0m▀\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0m▀\u{1b}[0m \u{1b}[38;2;204;58;0m▀\u{1b}[0m\u{1b}[38;2;204;58;0m▀\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0;48;2;204;58;0m▄\u{1b}[0m\u{1b}[38;2;204;58;0m▀\u{1b}[0m\u{1b}[38;2;204;58;0m▀\u{1b}[0m\u{1b}[38;2;204;58;0m▀\u{1b}[0m \u{1b}[38;2;247;76;0m▀\u{1b}[0m \n Thanks for using Gupax...!\n ( 〃 ω〃)";
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/free.rs b/src/free.rs
index 2f20b2b..706fcbf 100644
--- a/src/free.rs
+++ b/src/free.rs
@@ -8,11 +8,11 @@ use crate::constants::*;
#[inline(never)]
// Clamp the scaling resolution `f32` to a known good `f32`.
pub fn clamp_scale(scale: f32) -> f32 {
- // Make sure it is finite.
- if !scale.is_finite() {
- return APP_DEFAULT_SCALE;
- }
+ // Make sure it is finite.
+ if !scale.is_finite() {
+ return APP_DEFAULT_SCALE;
+ }
- // Clamp between valid range.
- scale.clamp(APP_MIN_SCALE, APP_MAX_SCALE)
-}
\ No newline at end of file
+ // Clamp between valid range.
+ scale.clamp(APP_MIN_SCALE, APP_MAX_SCALE)
+}
diff --git a/src/gupax.rs b/src/gupax.rs
index 5009299..0029b2b 100644
--- a/src/gupax.rs
+++ b/src/gupax.rs
@@ -16,299 +16,521 @@
// along with this program. If not, see .
use crate::State;
+use crate::{constants::*, macros::*, update::*, ErrorState, Restart, Tab};
use egui::{
- TextEdit,
- TextStyle,
- TextStyle::Monospace,
- Checkbox,ProgressBar,Spinner,Button,Label,Slider,
- SelectableLabel,
- RichText,
- Vec2,
-};
-use crate::{
- constants::*,
- update::*,
- ErrorState,
- Restart,
- Tab,
- macros::*,
-};
-use std::{
- thread,
- sync::{Arc,Mutex},
- path::Path,
+ Button, Checkbox, Label, ProgressBar, RichText, SelectableLabel, Slider, Spinner, TextEdit,
+ TextStyle, TextStyle::Monospace, Vec2,
};
use log::*;
-use serde::{Serialize,Deserialize};
+use serde::{Deserialize, Serialize};
+use std::{
+ path::Path,
+ sync::{Arc, Mutex},
+ thread,
+};
//---------------------------------------------------------------------------------------------------- FileWindow
// Struct for writing/reading the path state.
// The opened file picker is started in a new
// thread so main() needs to be in sync.
pub struct FileWindow {
- thread: bool, // Is there already a FileWindow thread?
- picked_p2pool: bool, // Did the user pick a path for p2pool?
- picked_xmrig: bool, // Did the user pick a path for xmrig?
- p2pool_path: String, // The picked p2pool path
- xmrig_path: String, // The picked p2pool path
+ thread: bool, // Is there already a FileWindow thread?
+ picked_p2pool: bool, // Did the user pick a path for p2pool?
+ picked_xmrig: bool, // Did the user pick a path for xmrig?
+ p2pool_path: String, // The picked p2pool path
+ xmrig_path: String, // The picked p2pool path
}
impl FileWindow {
- pub fn new() -> Arc> {
- arc_mut!(Self {
- thread: false,
- picked_p2pool: false,
- picked_xmrig: false,
- p2pool_path: String::new(),
- xmrig_path: String::new(),
- })
- }
+ pub fn new() -> Arc> {
+ arc_mut!(Self {
+ thread: false,
+ picked_p2pool: false,
+ picked_xmrig: false,
+ p2pool_path: String::new(),
+ xmrig_path: String::new(),
+ })
+ }
}
-#[derive(Debug,Clone)]
+#[derive(Debug, Clone)]
pub enum FileType {
- P2pool,
- Xmrig,
+ P2pool,
+ Xmrig,
}
//---------------------------------------------------------------------------------------------------- Ratio Lock
// Enum for the lock ratio in the advanced tab.
-#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
pub enum Ratio {
- Width,
- Height,
- None,
+ Width,
+ Height,
+ None,
}
//---------------------------------------------------------------------------------------------------- Gupax
impl crate::disk::Gupax {
- #[inline(always)] // called once
- pub fn show(
- &mut self,
- og: &Arc>,
- state_path: &Path,
- update: &Arc>,
- file_window: &Arc>,
- error_state: &mut ErrorState,
- restart: &Arc>,
- width: f32,
- height: f32,
- frame: &mut eframe::Frame,
- _ctx: &egui::Context,
- ui: &mut egui::Ui
- ) {
- // Update button + Progress bar
- debug!("Gupax Tab | Rendering [Update] button + progress bar");
- ui.group(|ui| {
- let button = if self.simple { height/5.0 } else { height/15.0 };
- let height = if self.simple { height/5.0 } else { height/10.0 };
- let width = width - 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);
- #[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;
- if updating {
- ui.add_sized([width, height], Spinner::new().size(height));
- } else {
- ui.add_sized([width, height], Label::new("..."));
- }
- ui.add_sized([width, height], ProgressBar::new(lock2!(update,prog).round() / 100.0));
- });
- });
+ #[inline(always)] // called once
+ pub fn show(
+ &mut self,
+ og: &Arc>,
+ state_path: &Path,
+ update: &Arc>,
+ file_window: &Arc>,
+ error_state: &mut ErrorState,
+ restart: &Arc>,
+ width: f32,
+ height: f32,
+ frame: &mut eframe::Frame,
+ _ctx: &egui::Context,
+ ui: &mut egui::Ui,
+ ) {
+ // Update button + Progress bar
+ debug!("Gupax Tab | Rendering [Update] button + progress bar");
+ ui.group(|ui| {
+ let button = if self.simple {
+ height / 5.0
+ } else {
+ height / 15.0
+ };
+ let height = if self.simple {
+ height / 5.0
+ } else {
+ height / 10.0
+ };
+ let width = width - 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);
+ #[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;
+ if updating {
+ ui.add_sized([width, height], Spinner::new().size(height));
+ } else {
+ ui.add_sized([width, height], Label::new("..."));
+ }
+ ui.add_sized(
+ [width, height],
+ ProgressBar::new(lock2!(update, prog).round() / 100.0),
+ );
+ });
+ });
- debug!("Gupax Tab | Rendering bool buttons");
- ui.horizontal(|ui| {
- ui.group(|ui| {
- let width = (width - SPACE*12.0)/6.0;
- let height = if self.simple { height/10.0 } else { height/15.0 };
- ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
- ui.add_sized([width, height], Checkbox::new(&mut self.update_via_tor, "Update via Tor")).on_hover_text(GUPAX_UPDATE_VIA_TOR);
- ui.separator();
- ui.add_sized([width, height], Checkbox::new(&mut self.auto_update, "Auto-Update")).on_hover_text(GUPAX_AUTO_UPDATE);
- ui.separator();
- ui.add_sized([width, height], Checkbox::new(&mut self.auto_p2pool, "Auto-P2Pool")).on_hover_text(GUPAX_AUTO_P2POOL);
- ui.separator();
- ui.add_sized([width, height], Checkbox::new(&mut self.auto_xmrig, "Auto-XMRig")).on_hover_text(GUPAX_AUTO_XMRIG);
- ui.separator();
- ui.add_sized([width, height], Checkbox::new(&mut self.ask_before_quit, "Ask before quit")).on_hover_text(GUPAX_ASK_BEFORE_QUIT);
- ui.separator();
- ui.add_sized([width, height], Checkbox::new(&mut self.save_before_quit, "Save before quit")).on_hover_text(GUPAX_SAVE_BEFORE_QUIT);
- });
- });
+ debug!("Gupax Tab | Rendering bool buttons");
+ ui.horizontal(|ui| {
+ ui.group(|ui| {
+ let width = (width - SPACE * 12.0) / 6.0;
+ let height = if self.simple {
+ height / 10.0
+ } else {
+ height / 15.0
+ };
+ ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
+ ui.add_sized(
+ [width, height],
+ Checkbox::new(&mut self.update_via_tor, "Update via Tor"),
+ )
+ .on_hover_text(GUPAX_UPDATE_VIA_TOR);
+ ui.separator();
+ ui.add_sized(
+ [width, height],
+ Checkbox::new(&mut self.auto_update, "Auto-Update"),
+ )
+ .on_hover_text(GUPAX_AUTO_UPDATE);
+ ui.separator();
+ ui.add_sized(
+ [width, height],
+ Checkbox::new(&mut self.auto_p2pool, "Auto-P2Pool"),
+ )
+ .on_hover_text(GUPAX_AUTO_P2POOL);
+ ui.separator();
+ ui.add_sized(
+ [width, height],
+ Checkbox::new(&mut self.auto_xmrig, "Auto-XMRig"),
+ )
+ .on_hover_text(GUPAX_AUTO_XMRIG);
+ ui.separator();
+ ui.add_sized(
+ [width, height],
+ Checkbox::new(&mut self.ask_before_quit, "Ask before quit"),
+ )
+ .on_hover_text(GUPAX_ASK_BEFORE_QUIT);
+ ui.separator();
+ ui.add_sized(
+ [width, height],
+ Checkbox::new(&mut self.save_before_quit, "Save before quit"),
+ )
+ .on_hover_text(GUPAX_SAVE_BEFORE_QUIT);
+ });
+ });
- if self.simple { return }
+ if self.simple {
+ return;
+ }
- debug!("Gupax Tab | Rendering P2Pool/XMRig path selection");
- // P2Pool/XMRig binary path selection
- let height = height/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("Gupax 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::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::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 = guard.p2pool_path.clone(); guard.picked_p2pool = false; }
- if guard.picked_xmrig { self.xmrig_path = guard.xmrig_path.clone(); guard.picked_xmrig = false; }
- drop(guard);
+ debug!("Gupax Tab | Rendering P2Pool/XMRig path selection");
+ // P2Pool/XMRig binary path selection
+ let height = height / 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("Gupax 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::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::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 = guard.p2pool_path.clone();
+ guard.picked_p2pool = false;
+ }
+ if guard.picked_xmrig {
+ self.xmrig_path = guard.xmrig_path.clone();
+ guard.picked_xmrig = false;
+ }
+ drop(guard);
- let height = ui.available_height()/6.0;
+ let height = ui.available_height() / 6.0;
- // Saved [Tab]
- debug!("Gupax Tab | Rendering [Tab] selector");
- ui.group(|ui| {
- let width = (width/5.0)-(SPACE*1.93);
- 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([width, height], SelectableLabel::new(self.tab == Tab::About, "About")).on_hover_text(GUPAX_TAB_ABOUT).clicked() { self.tab = Tab::About; }
- ui.separator();
- if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Status, "Status")).on_hover_text(GUPAX_TAB_STATUS).clicked() { self.tab = Tab::Status; }
- ui.separator();
- if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Gupax, "Gupax")).on_hover_text(GUPAX_TAB_GUPAX).clicked() { self.tab = Tab::Gupax; }
- ui.separator();
- if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool")).on_hover_text(GUPAX_TAB_P2POOL).clicked() { self.tab = Tab::P2pool; }
- ui.separator();
- if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")).on_hover_text(GUPAX_TAB_XMRIG).clicked() { self.tab = Tab::Xmrig; }
- })});
+ // Saved [Tab]
+ debug!("Gupax Tab | Rendering [Tab] selector");
+ ui.group(|ui| {
+ let width = (width / 5.0) - (SPACE * 1.93);
+ 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(
+ [width, height],
+ SelectableLabel::new(self.tab == Tab::About, "About"),
+ )
+ .on_hover_text(GUPAX_TAB_ABOUT)
+ .clicked()
+ {
+ self.tab = Tab::About;
+ }
+ ui.separator();
+ if ui
+ .add_sized(
+ [width, height],
+ SelectableLabel::new(self.tab == Tab::Status, "Status"),
+ )
+ .on_hover_text(GUPAX_TAB_STATUS)
+ .clicked()
+ {
+ self.tab = Tab::Status;
+ }
+ ui.separator();
+ if ui
+ .add_sized(
+ [width, height],
+ SelectableLabel::new(self.tab == Tab::Gupax, "Gupax"),
+ )
+ .on_hover_text(GUPAX_TAB_GUPAX)
+ .clicked()
+ {
+ self.tab = Tab::Gupax;
+ }
+ ui.separator();
+ if ui
+ .add_sized(
+ [width, height],
+ SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool"),
+ )
+ .on_hover_text(GUPAX_TAB_P2POOL)
+ .clicked()
+ {
+ self.tab = Tab::P2pool;
+ }
+ ui.separator();
+ if ui
+ .add_sized(
+ [width, height],
+ SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig"),
+ )
+ .on_hover_text(GUPAX_TAB_XMRIG)
+ .clicked()
+ {
+ self.tab = Tab::Xmrig;
+ }
+ })
+ });
- // Gupax App resolution sliders
- debug!("Gupax 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 = width/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;
- ui.horizontal(|ui| {
- ui.set_enabled(self.ratio != Ratio::Height);
- ui.add_sized([width, height], Label::new(format!(" Width [{}-{}]:", APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16)));
- ui.add_sized([width, height], 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([width, height], Label::new(format!("Height [{}-{}]:", APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16)));
- ui.add_sized([width, height], 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([width, height], Label::new(format!("Scaling [{APP_MIN_SCALE}..{APP_MAX_SCALE}]:")));
- ui.add_sized([width, height], 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 = (width/4.0)-(SPACE*1.5);
- if ui.add_sized([width, height], SelectableLabel::new(self.ratio == Width, "Lock to width")).on_hover_text(GUPAX_LOCK_WIDTH).clicked() { self.ratio = Width; }
- ui.separator();
- if ui.add_sized([width, height], SelectableLabel::new(self.ratio == Height, "Lock to height")).on_hover_text(GUPAX_LOCK_HEIGHT).clicked() { self.ratio = Height; }
- ui.separator();
- if ui.add_sized([width, height], SelectableLabel::new(self.ratio == None, "No lock")).on_hover_text(GUPAX_NO_LOCK).clicked() { self.ratio = None; }
- if ui.add_sized([width, height], 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));
- }
- })});
- }
+ // Gupax App resolution sliders
+ debug!("Gupax 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 = width / 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;
+ ui.horizontal(|ui| {
+ ui.set_enabled(self.ratio != Ratio::Height);
+ ui.add_sized(
+ [width, height],
+ Label::new(format!(
+ " Width [{}-{}]:",
+ APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16
+ )),
+ );
+ ui.add_sized(
+ [width, height],
+ 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(
+ [width, height],
+ Label::new(format!(
+ "Height [{}-{}]:",
+ APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16
+ )),
+ );
+ ui.add_sized(
+ [width, height],
+ 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(
+ [width, height],
+ Label::new(format!("Scaling [{APP_MIN_SCALE}..{APP_MAX_SCALE}]:")),
+ );
+ ui.add_sized(
+ [width, height],
+ 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 = (width / 4.0) - (SPACE * 1.5);
+ if ui
+ .add_sized(
+ [width, height],
+ SelectableLabel::new(self.ratio == Width, "Lock to width"),
+ )
+ .on_hover_text(GUPAX_LOCK_WIDTH)
+ .clicked()
+ {
+ self.ratio = Width;
+ }
+ ui.separator();
+ if ui
+ .add_sized(
+ [width, height],
+ SelectableLabel::new(self.ratio == Height, "Lock to height"),
+ )
+ .on_hover_text(GUPAX_LOCK_HEIGHT)
+ .clicked()
+ {
+ self.ratio = Height;
+ }
+ ui.separator();
+ if ui
+ .add_sized(
+ [width, height],
+ SelectableLabel::new(self.ratio == None, "No lock"),
+ )
+ .on_hover_text(GUPAX_NO_LOCK)
+ .clicked()
+ {
+ self.ratio = None;
+ }
+ if ui
+ .add_sized([width, height], 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));
+ }
+ })
+ });
+ }
- // Checks if a path is a valid path to a file.
- pub fn path_is_file(path: &str) -> bool {
- let path = path.to_string();
- match crate::disk::into_absolute_path(path) {
- Ok(path) => path.is_file(),
- _ => false,
- }
- }
+ // Checks if a path is a valid path to a file.
+ pub fn path_is_file(path: &str) -> bool {
+ let path = path.to_string();
+ match crate::disk::into_absolute_path(path) {
+ Ok(path) => path.is_file(),
+ _ => false,
+ }
+ }
- #[cold]
- #[inline(never)]
- fn spawn_file_window_thread(file_window: &Arc>, file_type: FileType) {
- use FileType::*;
- let name = match file_type {
- P2pool => "P2Pool",
- Xmrig => "XMRig",
- };
- let file_window = file_window.clone();
- lock!(file_window).thread = true;
- thread::spawn(move|| {
- match rfd::FileDialog::new().set_title(&format!("Select {} Binary for Gupax", name)).pick_file() {
- Some(path) => {
- info!("Gupax | Path selected for {} ... {}", name, path.display());
- match file_type {
- P2pool => { lock!(file_window).p2pool_path = path.display().to_string(); lock!(file_window).picked_p2pool = true; },
- Xmrig => { lock!(file_window).xmrig_path = path.display().to_string(); lock!(file_window).picked_xmrig = true; },
- };
- },
- None => info!("Gupax | No path selected for {}", name),
- };
- lock!(file_window).thread = false;
- });
- }
+ #[cold]
+ #[inline(never)]
+ fn spawn_file_window_thread(file_window: &Arc>, file_type: FileType) {
+ use FileType::*;
+ let name = match file_type {
+ P2pool => "P2Pool",
+ Xmrig => "XMRig",
+ };
+ let file_window = file_window.clone();
+ lock!(file_window).thread = true;
+ thread::spawn(move || {
+ match rfd::FileDialog::new()
+ .set_title(&format!("Select {} Binary for Gupax", name))
+ .pick_file()
+ {
+ Some(path) => {
+ info!("Gupax | Path selected for {} ... {}", name, path.display());
+ match file_type {
+ P2pool => {
+ lock!(file_window).p2pool_path = path.display().to_string();
+ lock!(file_window).picked_p2pool = true;
+ }
+ Xmrig => {
+ lock!(file_window).xmrig_path = path.display().to_string();
+ lock!(file_window).picked_xmrig = true;
+ }
+ };
+ }
+ None => info!("Gupax | No path selected for {}", name),
+ };
+ lock!(file_window).thread = false;
+ });
+ }
}
diff --git a/src/helper.rs b/src/helper.rs
index adc9a61..3ffdb70 100644
--- a/src/helper.rs
+++ b/src/helper.rs
@@ -34,31 +34,20 @@
// piping their stdout/stderr/stdin, accessing their APIs (HTTP + disk files), etc.
//---------------------------------------------------------------------------------------------------- Import
+use crate::regex::{P2POOL_REGEX, XMRIG_REGEX};
+use crate::{constants::*, human::*, macros::*, xmr::*, GupaxP2poolApi, RemoteNode, SudoState};
+use log::*;
+use serde::{Deserialize, Serialize};
use std::{
- sync::{Arc,Mutex},
- path::PathBuf,
- process::Stdio,
- fmt::Write,
- time::*,
- thread,
-};
-use crate::{
- constants::*,
- SudoState,
- human::*,
- GupaxP2poolApi,
- xmr::*,
- macros::*,
- RemoteNode,
+ fmt::Write,
+ path::PathBuf,
+ process::Stdio,
+ sync::{Arc, Mutex},
+ thread,
+ time::*,
};
use sysinfo::SystemExt;
-use serde::{Serialize,Deserialize};
-use sysinfo::{CpuExt,ProcessExt};
-use log::*;
-use crate::regex::{
- P2POOL_REGEX,
- XMRIG_REGEX,
-};
+use sysinfo::{CpuExt, ProcessExt};
//---------------------------------------------------------------------------------------------------- Constants
// The max amount of bytes of process output we are willing to
@@ -74,18 +63,18 @@ const P2POOL_BLOCK_TIME_IN_SECONDS: u64 = 10;
//---------------------------------------------------------------------------------------------------- [Helper] Struct
// A meta struct holding all the data that gets processed in this thread
pub struct Helper {
- pub instant: Instant, // Gupax start as an [Instant]
- pub uptime: HumanTime, // Gupax uptime formatting for humans
- 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 gui_api_p2pool: Arc>, // P2Pool API state (for GUI thread)
- pub gui_api_xmrig: 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 gupax_p2pool_api: Arc>, //
+ pub instant: Instant, // Gupax start as an [Instant]
+ pub uptime: HumanTime, // Gupax uptime formatting for humans
+ 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 gui_api_p2pool: Arc>, // P2Pool API state (for GUI thread)
+ pub gui_api_xmrig: 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 gupax_p2pool_api: Arc>, //
}
// The communication between the data here and the GUI thread goes as follows:
@@ -97,316 +86,400 @@ pub struct Helper {
// on a 1-second interval into the [GUI]'s [Pub*Api] struct, atomically.
//----------------------------------------------------------------------------------------------------
-#[derive(Debug,Clone)]
+#[derive(Debug, Clone)]
pub struct Sys {
- pub gupax_uptime: String,
- pub gupax_cpu_usage: String,
- pub gupax_memory_used_mb: String,
- pub system_cpu_model: String,
- pub system_memory: String,
- pub system_cpu_usage: String,
+ pub gupax_uptime: String,
+ pub gupax_cpu_usage: String,
+ pub gupax_memory_used_mb: String,
+ pub system_cpu_model: String,
+ pub system_memory: String,
+ pub system_cpu_usage: String,
}
impl Sys {
- pub fn new() -> Self {
- Self {
- gupax_uptime: "0 seconds".to_string(),
- gupax_cpu_usage: "???%".to_string(),
- gupax_memory_used_mb: "??? megabytes".to_string(),
- system_cpu_usage: "???%".to_string(),
- system_memory: "???GB / ???GB".to_string(),
- system_cpu_model: "???".to_string(),
- }
- }
+ pub fn new() -> Self {
+ Self {
+ gupax_uptime: "0 seconds".to_string(),
+ gupax_cpu_usage: "???%".to_string(),
+ gupax_memory_used_mb: "??? megabytes".to_string(),
+ system_cpu_usage: "???%".to_string(),
+ system_memory: "???GB / ???GB".to_string(),
+ system_cpu_model: "???".to_string(),
+ }
+ }
+}
+impl Default for Sys {
+ fn default() -> Self {
+ Self::new()
+ }
}
-impl Default for Sys { fn default() -> Self { Self::new() } }
//---------------------------------------------------------------------------------------------------- [Process] Struct
// This holds all the state of a (child) process.
// The main GUI thread will use this to display console text, online state, etc.
#[derive(Debug)]
pub struct Process {
- pub name: ProcessName, // P2Pool or XMRig?
- pub state: ProcessState, // The state of the process (alive, dead, etc)
- pub signal: ProcessSignal, // Did the user click [Start/Stop/Restart]?
- // STDIN Problem:
- // - User can input many many commands in 1 second
- // - The process loop only processes every 1 second
- // - If there is only 1 [String] holding the user input,
- // the user could overwrite their last input before
- // the loop even has a chance to process their last command
- // STDIN Solution:
- // - When the user inputs something, push it to a [Vec]
- // - In the process loop, loop over every [Vec] element and
- // send each one individually to the process stdin
- //
- pub input: Vec,
+ pub name: ProcessName, // P2Pool or XMRig?
+ pub state: ProcessState, // The state of the process (alive, dead, etc)
+ pub signal: ProcessSignal, // Did the user click [Start/Stop/Restart]?
+ // STDIN Problem:
+ // - User can input many many commands in 1 second
+ // - The process loop only processes every 1 second
+ // - If there is only 1 [String] holding the user input,
+ // the user could overwrite their last input before
+ // the loop even has a chance to process their last command
+ // STDIN Solution:
+ // - When the user inputs something, push it to a [Vec]
+ // - In the process loop, loop over every [Vec] element and
+ // send each one individually to the process stdin
+ //
+ pub input: Vec,
- // The below are the handles to the actual child process.
- // [Simple] has no STDIN, but [Advanced] does. A PTY (pseudo-terminal) is
- // required for P2Pool/XMRig to open their STDIN pipe.
-// child: Option>>>, // STDOUT/STDERR is combined automatically thanks to this PTY, nice
-// stdin: Option>, // A handle to the process's MasterPTY/STDIN
+ // The below are the handles to the actual child process.
+ // [Simple] has no STDIN, but [Advanced] does. A PTY (pseudo-terminal) is
+ // required for P2Pool/XMRig to open their STDIN pipe.
+ // child: Option>>>, // STDOUT/STDERR is combined automatically thanks to this PTY, nice
+ // stdin: Option>, // A handle to the process's MasterPTY/STDIN
- // This is the process's private output [String], used by both [Simple] and [Advanced].
- // "parse" contains the output that will be parsed, then tossed out. "pub" will be written to
- // the same as parse, but it will be [swap()]'d by the "helper" thread into the GUIs [String].
- // The "helper" thread synchronizes this swap so that the data in here is moved there
- // roughly once a second. GUI thread never touches this.
- output_parse: Arc>,
- output_pub: Arc>,
+ // This is the process's private output [String], used by both [Simple] and [Advanced].
+ // "parse" contains the output that will be parsed, then tossed out. "pub" will be written to
+ // the same as parse, but it will be [swap()]'d by the "helper" thread into the GUIs [String].
+ // The "helper" thread synchronizes this swap so that the data in here is moved there
+ // roughly once a second. GUI thread never touches this.
+ output_parse: Arc>,
+ output_pub: Arc>,
- // Start time of process.
- start: std::time::Instant,
+ // Start time of process.
+ start: std::time::Instant,
}
//---------------------------------------------------------------------------------------------------- [Process] Impl
impl Process {
- pub fn new(name: ProcessName, _args: String, _path: PathBuf) -> Self {
- Self {
- name,
- state: ProcessState::Dead,
- signal: ProcessSignal::None,
- start: Instant::now(),
-// stdin: Option::None,
-// child: Option::None,
- output_parse: arc_mut!(String::with_capacity(500)),
- output_pub: arc_mut!(String::with_capacity(500)),
- input: vec![String::new()],
- }
- }
+ pub fn new(name: ProcessName, _args: String, _path: PathBuf) -> Self {
+ Self {
+ name,
+ state: ProcessState::Dead,
+ signal: ProcessSignal::None,
+ start: Instant::now(),
+ // stdin: Option::None,
+ // child: Option::None,
+ output_parse: arc_mut!(String::with_capacity(500)),
+ output_pub: arc_mut!(String::with_capacity(500)),
+ input: vec![String::new()],
+ }
+ }
- // Borrow a [&str], return an owned split collection
- #[inline]
- pub fn parse_args(args: &str) -> Vec {
- args.split_whitespace().map(|s| s.to_owned()).collect()
- }
+ // Borrow a [&str], return an owned split collection
+ #[inline]
+ pub fn parse_args(args: &str) -> Vec {
+ args.split_whitespace().map(|s| s.to_owned()).collect()
+ }
- #[inline]
- // Convenience functions
- pub fn is_alive(&self) -> bool {
- self.state == ProcessState::Alive ||
- self.state == ProcessState::Middle ||
- self.state == ProcessState::Syncing ||
- self.state == ProcessState::NotMining
- }
+ #[inline]
+ // Convenience functions
+ pub fn is_alive(&self) -> bool {
+ self.state == ProcessState::Alive
+ || self.state == ProcessState::Middle
+ || self.state == ProcessState::Syncing
+ || self.state == ProcessState::NotMining
+ }
- #[inline]
- pub fn is_waiting(&self) -> bool {
- self.state == ProcessState::Middle || self.state == ProcessState::Waiting
- }
+ #[inline]
+ pub fn is_waiting(&self) -> bool {
+ self.state == ProcessState::Middle || self.state == ProcessState::Waiting
+ }
- #[inline]
- pub fn is_syncing(&self) -> bool {
- self.state == ProcessState::Syncing
- }
+ #[inline]
+ pub fn is_syncing(&self) -> bool {
+ self.state == ProcessState::Syncing
+ }
- #[inline]
- pub fn is_not_mining(&self) -> bool {
- self.state == ProcessState::NotMining
- }
+ #[inline]
+ pub fn is_not_mining(&self) -> bool {
+ self.state == ProcessState::NotMining
+ }
}
//---------------------------------------------------------------------------------------------------- [Process*] Enum
-#[derive(Copy,Clone,Eq,PartialEq,Debug)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ProcessState {
- Alive, // Process is online, GREEN!
- Dead, // Process is dead, BLACK!
- Failed, // Process is dead AND exited with a bad code, RED!
- Middle, // Process is in the middle of something ([re]starting/stopping), YELLOW!
- Waiting, // Process was successfully killed by a restart, and is ready to be started again, YELLOW!
+ Alive, // Process is online, GREEN!
+ Dead, // Process is dead, BLACK!
+ Failed, // Process is dead AND exited with a bad code, RED!
+ Middle, // Process is in the middle of something ([re]starting/stopping), YELLOW!
+ Waiting, // Process was successfully killed by a restart, and is ready to be started again, YELLOW!
- // Only for P2Pool, ORANGE.
- Syncing,
+ // Only for P2Pool, ORANGE.
+ Syncing,
- // Only for XMRig, ORANGE.
- NotMining,
+ // Only for XMRig, ORANGE.
+ NotMining,
}
-impl Default for ProcessState { fn default() -> Self { Self::Dead } }
+impl Default for ProcessState {
+ fn default() -> Self {
+ Self::Dead
+ }
+}
-#[derive(Copy,Clone,Eq,PartialEq,Debug)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ProcessSignal {
- None,
- Start,
- Stop,
- Restart,
+ None,
+ Start,
+ Stop,
+ Restart,
}
-impl Default for ProcessSignal { fn default() -> Self { Self::None } }
+impl Default for ProcessSignal {
+ fn default() -> Self {
+ Self::None
+ }
+}
-#[derive(Copy,Clone,Eq,PartialEq,Debug)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ProcessName {
- P2pool,
- Xmrig,
+ P2pool,
+ Xmrig,
}
-impl std::fmt::Display for ProcessState { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
-impl std::fmt::Display for ProcessSignal { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
-impl std::fmt::Display for ProcessName {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- match *self {
- ProcessName::P2pool => write!(f, "P2Pool"),
- ProcessName::Xmrig => write!(f, "XMRig"),
- }
- }
+impl std::fmt::Display for ProcessState {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{:#?}", self)
+ }
+}
+impl std::fmt::Display for ProcessSignal {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{:#?}", self)
+ }
+}
+impl std::fmt::Display for ProcessName {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match *self {
+ ProcessName::P2pool => write!(f, "P2Pool"),
+ ProcessName::Xmrig => write!(f, "XMRig"),
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- [Helper]
impl Helper {
- //---------------------------------------------------------------------------------------------------- General Functions
- pub fn new(instant: std::time::Instant, pub_sys: Arc>, p2pool: Arc>, xmrig: Arc>, gui_api_p2pool: Arc>, gui_api_xmrig: Arc>, img_p2pool: Arc>, img_xmrig: Arc>, gupax_p2pool_api: Arc>) -> Self {
- Self {
- instant,
- pub_sys,
- uptime: HumanTime::into_human(instant.elapsed()),
- pub_api_p2pool: arc_mut!(PubP2poolApi::new()),
- pub_api_xmrig: arc_mut!(PubXmrigApi::new()),
- // These are created when initializing [App], since it needs a handle to it as well
- p2pool,
- xmrig,
- gui_api_p2pool,
- gui_api_xmrig,
- img_p2pool,
- img_xmrig,
- gupax_p2pool_api,
- }
- }
+ //---------------------------------------------------------------------------------------------------- General Functions
+ pub fn new(
+ instant: std::time::Instant,
+ pub_sys: Arc>,
+ p2pool: Arc>,
+ xmrig: Arc>,
+ gui_api_p2pool: Arc>,
+ gui_api_xmrig: Arc>,
+ img_p2pool: Arc>,
+ img_xmrig: Arc>,
+ gupax_p2pool_api: Arc>,
+ ) -> Self {
+ Self {
+ instant,
+ pub_sys,
+ uptime: HumanTime::into_human(instant.elapsed()),
+ pub_api_p2pool: arc_mut!(PubP2poolApi::new()),
+ pub_api_xmrig: arc_mut!(PubXmrigApi::new()),
+ // These are created when initializing [App], since it needs a handle to it as well
+ p2pool,
+ xmrig,
+ gui_api_p2pool,
+ gui_api_xmrig,
+ img_p2pool,
+ img_xmrig,
+ gupax_p2pool_api,
+ }
+ }
- #[cold]
- #[inline(never)]
- fn read_pty_xmrig(output_parse: Arc>, output_pub: Arc>, reader: Box) {
- use std::io::BufRead;
- let mut stdout = std::io::BufReader::new(reader).lines();
+ #[cold]
+ #[inline(never)]
+ fn read_pty_xmrig(
+ output_parse: Arc>,
+ output_pub: Arc>,
+ reader: Box,
+ ) {
+ 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 PTY Parse | Output error: {}", e); }
- if let Err(e) = writeln!(lock!(output_pub), "{}", line) { error!("XMRig PTY Pub | Output error: {}", e); }
- if i > 20 {
- break;
- } else {
- i += 1;
- }
- }
- drop(i);
+ // 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 PTY Parse | Output error: {}", e);
+ }
+ if let Err(e) = writeln!(lock!(output_pub), "{}", line) {
+ error!("XMRig PTY Pub | Output error: {}", e);
+ }
+ if i > 20 {
+ break;
+ } else {
+ i += 1;
+ }
+ }
+ drop(i);
- while let Some(Ok(line)) = stdout.next() {
-// println!("{}", line); // For debugging.
- if let Err(e) = writeln!(lock!(output_parse), "{}", line) { error!("XMRig PTY Parse | Output error: {}", e); }
- if let Err(e) = writeln!(lock!(output_pub), "{}", line) { error!("XMRig PTY Pub | Output error: {}", e); }
- }
- }
+ while let Some(Ok(line)) = stdout.next() {
+ // println!("{}", line); // For debugging.
+ if let Err(e) = writeln!(lock!(output_parse), "{}", line) {
+ error!("XMRig PTY Parse | Output error: {}", e);
+ }
+ if let Err(e) = writeln!(lock!(output_pub), "{}", line) {
+ error!("XMRig PTY Pub | Output error: {}", e);
+ }
+ }
+ }
- #[cold]
- #[inline(never)]
- fn read_pty_p2pool(output_parse: Arc>, output_pub: Arc>, reader: Box, gupax_p2pool_api: Arc>) {
- use std::io::BufRead;
- let mut stdout = std::io::BufReader::new(reader).lines();
+ #[cold]
+ #[inline(never)]
+ fn read_pty_p2pool(
+ output_parse: Arc>,
+ output_pub: Arc>,
+ reader: Box,
+ gupax_p2pool_api: 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!("P2Pool PTY Parse | Output error: {}", e); }
- if let Err(e) = writeln!(lock!(output_pub), "{}", line) { error!("P2Pool PTY Pub | Output error: {}", e); }
- if i > 20 {
- break;
- } else {
- i += 1;
- }
- }
- drop(i);
+ // 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!("P2Pool PTY Parse | Output error: {}", e);
+ }
+ if let Err(e) = writeln!(lock!(output_pub), "{}", line) {
+ error!("P2Pool PTY Pub | Output error: {}", e);
+ }
+ if i > 20 {
+ break;
+ } else {
+ i += 1;
+ }
+ }
+ drop(i);
- while let Some(Ok(line)) = stdout.next() {
-// println!("{}", line); // For debugging.
- if P2POOL_REGEX.payout.is_match(&line) {
- debug!("P2Pool PTY | Found payout, attempting write: {}", line);
- let (date, atomic_unit, block) = PayoutOrd::parse_raw_payout_line(&line);
- let formatted_log_line = GupaxP2poolApi::format_payout(&date, &atomic_unit, &block);
- GupaxP2poolApi::add_payout(&mut lock!(gupax_p2pool_api), &formatted_log_line, date, atomic_unit, block);
- if let Err(e) = GupaxP2poolApi::write_to_all_files(&lock!(gupax_p2pool_api), &formatted_log_line) { error!("P2Pool PTY GupaxP2poolApi | Write error: {}", e); }
- }
- if let Err(e) = writeln!(lock!(output_parse), "{}", line) { error!("P2Pool PTY Parse | Output error: {}", e); }
- if let Err(e) = writeln!(lock!(output_pub), "{}", line) { error!("P2Pool PTY Pub | Output error: {}", e); }
- }
- }
+ while let Some(Ok(line)) = stdout.next() {
+ // println!("{}", line); // For debugging.
+ if P2POOL_REGEX.payout.is_match(&line) {
+ debug!("P2Pool PTY | Found payout, attempting write: {}", line);
+ let (date, atomic_unit, block) = PayoutOrd::parse_raw_payout_line(&line);
+ let formatted_log_line = GupaxP2poolApi::format_payout(&date, &atomic_unit, &block);
+ GupaxP2poolApi::add_payout(
+ &mut lock!(gupax_p2pool_api),
+ &formatted_log_line,
+ date,
+ atomic_unit,
+ block,
+ );
+ if let Err(e) = GupaxP2poolApi::write_to_all_files(
+ &lock!(gupax_p2pool_api),
+ &formatted_log_line,
+ ) {
+ error!("P2Pool PTY GupaxP2poolApi | Write error: {}", e);
+ }
+ }
+ if let Err(e) = writeln!(lock!(output_parse), "{}", line) {
+ error!("P2Pool PTY Parse | Output error: {}", e);
+ }
+ if let Err(e) = writeln!(lock!(output_pub), "{}", line) {
+ error!("P2Pool PTY Pub | Output error: {}", e);
+ }
+ }
+ }
- // Reset output if larger than max bytes.
- // This will also append a message showing it was reset.
- fn check_reset_gui_output(output: &mut String, name: ProcessName) {
- let len = output.len();
- if len > GUI_OUTPUT_LEEWAY {
- info!("{} Watchdog | Output is nearing {} bytes, resetting!", name, MAX_GUI_OUTPUT_BYTES);
- let text = format!("{}\n{} GUI log is exceeding the maximum: {} bytes!\nI've reset the logs for you!\n{}\n\n\n\n", HORI_CONSOLE, name, MAX_GUI_OUTPUT_BYTES, HORI_CONSOLE);
- output.clear();
- output.push_str(&text);
- debug!("{} Watchdog | Resetting GUI output ... OK", name);
- } else {
- debug!("{} Watchdog | GUI output reset not needed! Current byte length ... {}", name, len);
- }
- }
+ // Reset output if larger than max bytes.
+ // This will also append a message showing it was reset.
+ fn check_reset_gui_output(output: &mut String, name: ProcessName) {
+ let len = output.len();
+ if len > GUI_OUTPUT_LEEWAY {
+ info!(
+ "{} Watchdog | Output is nearing {} bytes, resetting!",
+ name, MAX_GUI_OUTPUT_BYTES
+ );
+ let text = format!("{}\n{} GUI log is exceeding the maximum: {} bytes!\nI've reset the logs for you!\n{}\n\n\n\n", HORI_CONSOLE, name, MAX_GUI_OUTPUT_BYTES, HORI_CONSOLE);
+ output.clear();
+ output.push_str(&text);
+ debug!("{} Watchdog | Resetting GUI output ... OK", name);
+ } else {
+ debug!(
+ "{} Watchdog | GUI output reset not needed! Current byte length ... {}",
+ name, len
+ );
+ }
+ }
- // Read P2Pool/XMRig's API file to a [String].
- fn path_to_string(path: &std::path::PathBuf, name: ProcessName) -> std::result::Result {
- match std::fs::read_to_string(path) {
- Ok(s) => Ok(s),
- Err(e) => { warn!("{} API | [{}] read error: {}", name, path.display(), e); Err(e) },
- }
- }
+ // Read P2Pool/XMRig's API file to a [String].
+ fn path_to_string(
+ path: &std::path::PathBuf,
+ name: ProcessName,
+ ) -> std::result::Result {
+ match std::fs::read_to_string(path) {
+ Ok(s) => Ok(s),
+ Err(e) => {
+ warn!("{} API | [{}] read error: {}", name, path.display(), e);
+ Err(e)
+ }
+ }
+ }
- //---------------------------------------------------------------------------------------------------- P2Pool specific
- #[cold]
- #[inline(never)]
- // Just sets some signals for the watchdog thread to pick up on.
- pub fn stop_p2pool(helper: &Arc>) {
- info!("P2Pool | Attempting to stop...");
- lock2!(helper,p2pool).signal = ProcessSignal::Stop;
- lock2!(helper,p2pool).state = ProcessState::Middle;
- }
+ //---------------------------------------------------------------------------------------------------- P2Pool specific
+ #[cold]
+ #[inline(never)]
+ // Just sets some signals for the watchdog thread to pick up on.
+ pub fn stop_p2pool(helper: &Arc>) {
+ info!("P2Pool | Attempting to stop...");
+ lock2!(helper, p2pool).signal = ProcessSignal::Stop;
+ lock2!(helper, p2pool).state = ProcessState::Middle;
+ }
- #[cold]
- #[inline(never)]
- // The "restart frontend" to a "frontend" function.
- // Basically calls to kill the current p2pool, waits a little, then starts the below function in a a new thread, then exit.
- pub fn restart_p2pool(helper: &Arc>, state: &crate::disk::P2pool, path: &std::path::PathBuf, backup_hosts: Option>) {
- info!("P2Pool | Attempting to restart...");
- lock2!(helper,p2pool).signal = ProcessSignal::Restart;
- lock2!(helper,p2pool).state = ProcessState::Middle;
+ #[cold]
+ #[inline(never)]
+ // The "restart frontend" to a "frontend" function.
+ // Basically calls to kill the current p2pool, waits a little, then starts the below function in a a new thread, then exit.
+ pub fn restart_p2pool(
+ helper: &Arc>,
+ state: &crate::disk::P2pool,
+ path: &std::path::PathBuf,
+ backup_hosts: Option>,
+ ) {
+ info!("P2Pool | Attempting to restart...");
+ lock2!(helper, p2pool).signal = ProcessSignal::Restart;
+ lock2!(helper, p2pool).state = ProcessState::Middle;
- let helper = Arc::clone(helper);
- let state = state.clone();
- let path = path.clone();
- // This thread lives to wait, start p2pool then die.
- thread::spawn(move || {
- while lock2!(helper,p2pool).is_alive() {
- warn!("P2Pool | Want to restart but process is still alive, waiting...");
- sleep!(1000);
- }
- // Ok, process is not alive, start the new one!
- info!("P2Pool | Old process seems dead, starting new one!");
- Self::start_p2pool(&helper, &state, &path, backup_hosts);
- });
- info!("P2Pool | Restart ... OK");
- }
+ let helper = Arc::clone(helper);
+ let state = state.clone();
+ let path = path.clone();
+ // This thread lives to wait, start p2pool then die.
+ thread::spawn(move || {
+ while lock2!(helper, p2pool).is_alive() {
+ warn!("P2Pool | Want to restart but process is still alive, waiting...");
+ sleep!(1000);
+ }
+ // Ok, process is not alive, start the new one!
+ info!("P2Pool | Old process seems dead, starting new one!");
+ Self::start_p2pool(&helper, &state, &path, backup_hosts);
+ });
+ info!("P2Pool | Restart ... OK");
+ }
- #[cold]
- #[inline(never)]
- // The "frontend" function that parses the arguments, and spawns either the [Simple] or [Advanced] P2Pool watchdog thread.
- pub fn start_p2pool(
- helper: &Arc>,
- state: &crate::disk::P2pool,
- path: &std::path::PathBuf,
- backup_hosts: Option>,
- ) {
- lock2!(helper,p2pool).state = ProcessState::Middle;
+ #[cold]
+ #[inline(never)]
+ // The "frontend" function that parses the arguments, and spawns either the [Simple] or [Advanced] P2Pool watchdog thread.
+ pub fn start_p2pool(
+ helper: &Arc>,
+ state: &crate::disk::P2pool,
+ path: &std::path::PathBuf,
+ backup_hosts: Option>,
+ ) {
+ lock2!(helper, p2pool).state = ProcessState::Middle;
- let (args, api_path_local, api_path_network, api_path_pool) = Self::build_p2pool_args_and_mutate_img(helper, state, path, backup_hosts);
+ let (args, api_path_local, api_path_network, api_path_pool) =
+ Self::build_p2pool_args_and_mutate_img(helper, state, path, backup_hosts);
- // Print arguments & user settings to console
- crate::disk::print_dash(&format!(
+ // Print arguments & user settings to console
+ crate::disk::print_dash(&format!(
"P2Pool | Launch arguments: {:#?} | Local API Path: {:#?} | Network API Path: {:#?} | Pool API Path: {:#?}",
args,
api_path_local,
@@ -414,910 +487,1216 @@ impl Helper {
api_path_pool,
));
- // Spawn watchdog thread
- let process = Arc::clone(&lock!(helper).p2pool);
- let gui_api = Arc::clone(&lock!(helper).gui_api_p2pool);
- let pub_api = Arc::clone(&lock!(helper).pub_api_p2pool);
- let gupax_p2pool_api = Arc::clone(&lock!(helper).gupax_p2pool_api);
- let path = path.clone();
- thread::spawn(move || {
- Self::spawn_p2pool_watchdog(process, gui_api, pub_api, args, path, api_path_local, api_path_network, api_path_pool, gupax_p2pool_api);
- });
- }
+ // Spawn watchdog thread
+ let process = Arc::clone(&lock!(helper).p2pool);
+ let gui_api = Arc::clone(&lock!(helper).gui_api_p2pool);
+ let pub_api = Arc::clone(&lock!(helper).pub_api_p2pool);
+ let gupax_p2pool_api = Arc::clone(&lock!(helper).gupax_p2pool_api);
+ let path = path.clone();
+ thread::spawn(move || {
+ Self::spawn_p2pool_watchdog(
+ process,
+ gui_api,
+ pub_api,
+ args,
+ path,
+ api_path_local,
+ api_path_network,
+ api_path_pool,
+ gupax_p2pool_api,
+ );
+ });
+ }
- // Takes in a 95-char Monero address, returns the first and last
- // 6 characters separated with dots like so: [4abcde...abcdef]
- fn head_tail_of_monero_address(address: &str) -> String {
- if address.len() < 95 { return "???".to_string() }
- let head = &address[0..6];
- let tail = &address[89..95];
- head.to_owned() + "..." + tail
- }
+ // Takes in a 95-char Monero address, returns the first and last
+ // 6 characters separated with dots like so: [4abcde...abcdef]
+ fn head_tail_of_monero_address(address: &str) -> String {
+ if address.len() < 95 {
+ return "???".to_string();
+ }
+ let head = &address[0..6];
+ let tail = &address[89..95];
+ head.to_owned() + "..." + tail
+ }
- #[cold]
- #[inline(never)]
- // Takes in some [State/P2pool] and parses it to build the actual command arguments.
- // Returns the [Vec] of actual arguments, and mutates the [ImgP2pool] for the main GUI thread
- // It returns a value... and mutates a deeply nested passed argument... this is some pretty bad code...
- pub fn build_p2pool_args_and_mutate_img(
- helper: &Arc>,
- state: &crate::disk::P2pool,
- path: &std::path::PathBuf,
- backup_hosts: Option>,
- ) -> (Vec, PathBuf, PathBuf, PathBuf) {
- let mut args = Vec::with_capacity(500);
- let path = path.clone();
- let mut api_path = path;
- api_path.pop();
+ #[cold]
+ #[inline(never)]
+ // Takes in some [State/P2pool] and parses it to build the actual command arguments.
+ // Returns the [Vec] of actual arguments, and mutates the [ImgP2pool] for the main GUI thread
+ // It returns a value... and mutates a deeply nested passed argument... this is some pretty bad code...
+ pub fn build_p2pool_args_and_mutate_img(
+ helper: &Arc>,
+ state: &crate::disk::P2pool,
+ path: &std::path::PathBuf,
+ backup_hosts: Option>,
+ ) -> (Vec, PathBuf, PathBuf, PathBuf) {
+ let mut args = Vec::with_capacity(500);
+ let path = path.clone();
+ let mut api_path = path;
+ api_path.pop();
- // [Simple]
- if state.simple {
- // Build the p2pool argument
- let (ip, rpc, zmq) = RemoteNode::get_ip_rpc_zmq(&state.node); // Get: (IP, RPC, ZMQ)
- args.push("--wallet".to_string()); args.push(state.address.clone()); // Wallet address
- args.push("--host".to_string()); args.push(ip.to_string()); // IP Address
- args.push("--rpc-port".to_string()); args.push(rpc.to_string()); // RPC Port
- args.push("--zmq-port".to_string()); args.push(zmq.to_string()); // ZMQ Port
- args.push("--data-api".to_string()); args.push(api_path.display().to_string()); // API Path
- args.push("--local-api".to_string()); // Enable API
- args.push("--no-color".to_string()); // Remove color escape sequences, Gupax terminal can't parse it :(
- args.push("--mini".to_string()); // P2Pool Mini
- args.push("--light-mode".to_string()); // Assume user is not using P2Pool to mine.
+ // [Simple]
+ if state.simple {
+ // Build the p2pool argument
+ let (ip, rpc, zmq) = RemoteNode::get_ip_rpc_zmq(&state.node); // Get: (IP, RPC, ZMQ)
+ args.push("--wallet".to_string());
+ args.push(state.address.clone()); // Wallet address
+ args.push("--host".to_string());
+ args.push(ip.to_string()); // IP Address
+ args.push("--rpc-port".to_string());
+ args.push(rpc.to_string()); // RPC Port
+ args.push("--zmq-port".to_string());
+ args.push(zmq.to_string()); // ZMQ Port
+ args.push("--data-api".to_string());
+ args.push(api_path.display().to_string()); // API Path
+ args.push("--local-api".to_string()); // Enable API
+ args.push("--no-color".to_string()); // Remove color escape sequences, Gupax terminal can't parse it :(
+ args.push("--mini".to_string()); // P2Pool Mini
+ args.push("--light-mode".to_string()); // Assume user is not using P2Pool to mine.
- // Push other nodes if `backup_host`.
- if let Some(nodes) = backup_hosts {
- for node in nodes {
- if (node.ip.as_str(), node.rpc.as_str(), node.zmq.as_str()) != (ip, rpc, zmq) {
- args.push("--host".to_string()); args.push(node.ip.to_string());
- args.push("--rpc-port".to_string()); args.push(node.rpc.to_string());
- args.push("--zmq-port".to_string()); args.push(node.zmq.to_string());
- }
- }
- }
+ // Push other nodes if `backup_host`.
+ if let Some(nodes) = backup_hosts {
+ for node in nodes {
+ if (node.ip.as_str(), node.rpc.as_str(), node.zmq.as_str()) != (ip, rpc, zmq) {
+ args.push("--host".to_string());
+ args.push(node.ip.to_string());
+ args.push("--rpc-port".to_string());
+ args.push(node.rpc.to_string());
+ args.push("--zmq-port".to_string());
+ args.push(node.zmq.to_string());
+ }
+ }
+ }
- *lock2!(helper,img_p2pool) = ImgP2pool {
- mini: "P2Pool Mini".to_string(),
- address: Self::head_tail_of_monero_address(&state.address),
- host: ip.to_string(),
- rpc: rpc.to_string(),
- zmq: zmq.to_string(),
- out_peers: "10".to_string(),
- in_peers: "10".to_string(),
- };
+ *lock2!(helper, img_p2pool) = ImgP2pool {
+ mini: "P2Pool Mini".to_string(),
+ address: Self::head_tail_of_monero_address(&state.address),
+ host: ip.to_string(),
+ rpc: rpc.to_string(),
+ zmq: zmq.to_string(),
+ out_peers: "10".to_string(),
+ in_peers: "10".to_string(),
+ };
- // [Advanced]
- } else {
- // Overriding command arguments
- if !state.arguments.is_empty() {
- // This parses the input and attempts to fill out
- // the [ImgP2pool]... This is pretty bad code...
- let mut last = "";
- let lock = lock!(helper);
- let mut p2pool_image = lock!(lock.img_p2pool);
- let mut mini = false;
- for arg in state.arguments.split_whitespace() {
- match last {
- "--mini" => { mini = true; p2pool_image.mini = "P2Pool Mini".to_string(); },
- "--wallet" => p2pool_image.address = Self::head_tail_of_monero_address(arg),
- "--host" => p2pool_image.host = arg.to_string(),
- "--rpc-port" => p2pool_image.rpc = arg.to_string(),
- "--zmq-port" => p2pool_image.zmq = arg.to_string(),
- "--out-peers" => p2pool_image.out_peers = arg.to_string(),
- "--in-peers" => p2pool_image.in_peers = arg.to_string(),
- "--data-api" => api_path = PathBuf::from(arg),
- _ => (),
- }
- if !mini { p2pool_image.mini = "P2Pool Main".to_string(); }
- let arg = if arg == "localhost" { "127.0.0.1" } else { arg };
- args.push(arg.to_string());
- last = arg;
- }
- // Else, build the argument
- } else {
- let ip = if state.ip == "localhost" { "127.0.0.1" } else { &state.ip };
- args.push("--wallet".to_string()); args.push(state.address.clone()); // Wallet
- args.push("--host".to_string()); args.push(ip.to_string()); // IP
- args.push("--rpc-port".to_string()); args.push(state.rpc.to_string()); // RPC
- args.push("--zmq-port".to_string()); args.push(state.zmq.to_string()); // ZMQ
- args.push("--loglevel".to_string()); args.push(state.log_level.to_string()); // Log Level
- args.push("--out-peers".to_string()); args.push(state.out_peers.to_string()); // Out Peers
- args.push("--in-peers".to_string()); args.push(state.in_peers.to_string()); // In Peers
- args.push("--data-api".to_string()); args.push(api_path.display().to_string()); // API Path
- args.push("--local-api".to_string()); // Enable API
- args.push("--no-color".to_string()); // Remove color escape sequences
- args.push("--light-mode".to_string()); // Assume user is not using P2Pool to mine.
- if state.mini { args.push("--mini".to_string()); }; // Mini
+ // [Advanced]
+ } else {
+ // Overriding command arguments
+ if !state.arguments.is_empty() {
+ // This parses the input and attempts to fill out
+ // the [ImgP2pool]... This is pretty bad code...
+ let mut last = "";
+ let lock = lock!(helper);
+ let mut p2pool_image = lock!(lock.img_p2pool);
+ let mut mini = false;
+ for arg in state.arguments.split_whitespace() {
+ match last {
+ "--mini" => {
+ mini = true;
+ p2pool_image.mini = "P2Pool Mini".to_string();
+ }
+ "--wallet" => p2pool_image.address = Self::head_tail_of_monero_address(arg),
+ "--host" => p2pool_image.host = arg.to_string(),
+ "--rpc-port" => p2pool_image.rpc = arg.to_string(),
+ "--zmq-port" => p2pool_image.zmq = arg.to_string(),
+ "--out-peers" => p2pool_image.out_peers = arg.to_string(),
+ "--in-peers" => p2pool_image.in_peers = arg.to_string(),
+ "--data-api" => api_path = PathBuf::from(arg),
+ _ => (),
+ }
+ if !mini {
+ p2pool_image.mini = "P2Pool Main".to_string();
+ }
+ let arg = if arg == "localhost" { "127.0.0.1" } else { arg };
+ args.push(arg.to_string());
+ last = arg;
+ }
+ // Else, build the argument
+ } else {
+ let ip = if state.ip == "localhost" {
+ "127.0.0.1"
+ } else {
+ &state.ip
+ };
+ args.push("--wallet".to_string());
+ args.push(state.address.clone()); // Wallet
+ args.push("--host".to_string());
+ args.push(ip.to_string()); // IP
+ args.push("--rpc-port".to_string());
+ args.push(state.rpc.to_string()); // RPC
+ args.push("--zmq-port".to_string());
+ args.push(state.zmq.to_string()); // ZMQ
+ args.push("--loglevel".to_string());
+ args.push(state.log_level.to_string()); // Log Level
+ args.push("--out-peers".to_string());
+ args.push(state.out_peers.to_string()); // Out Peers
+ args.push("--in-peers".to_string());
+ args.push(state.in_peers.to_string()); // In Peers
+ args.push("--data-api".to_string());
+ args.push(api_path.display().to_string()); // API Path
+ args.push("--local-api".to_string()); // Enable API
+ args.push("--no-color".to_string()); // Remove color escape sequences
+ args.push("--light-mode".to_string()); // Assume user is not using P2Pool to mine.
+ if state.mini {
+ args.push("--mini".to_string());
+ }; // Mini
- // Push other nodes if `backup_host`.
- if let Some(nodes) = backup_hosts {
- for node in nodes {
- let ip = if node.ip == "localhost" { "127.0.0.1" } else { &node.ip };
- if (node.ip.as_str(), node.rpc.as_str(), node.zmq.as_str()) != (ip, &state.rpc, &state.zmq) {
- args.push("--host".to_string()); args.push(node.ip.to_string());
- args.push("--rpc-port".to_string()); args.push(node.rpc.to_string());
- args.push("--zmq-port".to_string()); args.push(node.zmq.to_string());
- }
- }
- }
+ // Push other nodes if `backup_host`.
+ if let Some(nodes) = backup_hosts {
+ for node in nodes {
+ let ip = if node.ip == "localhost" {
+ "127.0.0.1"
+ } else {
+ &node.ip
+ };
+ if (node.ip.as_str(), node.rpc.as_str(), node.zmq.as_str())
+ != (ip, &state.rpc, &state.zmq)
+ {
+ args.push("--host".to_string());
+ args.push(node.ip.to_string());
+ args.push("--rpc-port".to_string());
+ args.push(node.rpc.to_string());
+ args.push("--zmq-port".to_string());
+ args.push(node.zmq.to_string());
+ }
+ }
+ }
- *lock2!(helper,img_p2pool) = ImgP2pool {
- mini: if state.mini { "P2Pool Mini".to_string() } else { "P2Pool Main".to_string() },
- address: Self::head_tail_of_monero_address(&state.address),
- host: state.selected_ip.to_string(),
- rpc: state.selected_rpc.to_string(),
- zmq: state.selected_zmq.to_string(),
- out_peers: state.out_peers.to_string(),
- in_peers: state.in_peers.to_string(),
- };
- }
- }
- let mut api_path_local = api_path.clone();
- let mut api_path_network = api_path.clone();
- let mut api_path_pool = api_path.clone();
- api_path_local.push(P2POOL_API_PATH_LOCAL);
- api_path_network.push(P2POOL_API_PATH_NETWORK);
- api_path_pool.push(P2POOL_API_PATH_POOL);
- (args, api_path_local, api_path_network, api_path_pool)
- }
+ *lock2!(helper, img_p2pool) = ImgP2pool {
+ mini: if state.mini {
+ "P2Pool Mini".to_string()
+ } else {
+ "P2Pool Main".to_string()
+ },
+ address: Self::head_tail_of_monero_address(&state.address),
+ host: state.selected_ip.to_string(),
+ rpc: state.selected_rpc.to_string(),
+ zmq: state.selected_zmq.to_string(),
+ out_peers: state.out_peers.to_string(),
+ in_peers: state.in_peers.to_string(),
+ };
+ }
+ }
+ let mut api_path_local = api_path.clone();
+ let mut api_path_network = api_path.clone();
+ let mut api_path_pool = api_path.clone();
+ api_path_local.push(P2POOL_API_PATH_LOCAL);
+ api_path_network.push(P2POOL_API_PATH_NETWORK);
+ api_path_pool.push(P2POOL_API_PATH_POOL);
+ (args, api_path_local, api_path_network, api_path_pool)
+ }
- #[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.
- fn spawn_p2pool_watchdog(process: Arc>, gui_api: Arc>, pub_api: Arc>, args: Vec, path: std::path::PathBuf, api_path_local: std::path::PathBuf, api_path_network: std::path::PathBuf, api_path_pool: std::path::PathBuf, gupax_p2pool_api: Arc>) {
- // 1a. Create PTY
- debug!("P2Pool | 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();
- // 1b. Create command
- debug!("P2Pool | Creating command...");
- let mut cmd = portable_pty::CommandBuilder::new(path.as_path());
- cmd.args(args);
- cmd.env("NO_COLOR", "true");
- cmd.cwd(path.as_path().parent().unwrap());
- // 1c. Create child
- debug!("P2Pool | Creating child...");
- let child_pty = arc_mut!(pair.slave.spawn_command(cmd).unwrap());
- drop(pair.slave);
+ #[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.
+ fn spawn_p2pool_watchdog(
+ process: Arc>,
+ gui_api: Arc>,
+ pub_api: Arc>,
+ args: Vec,
+ path: std::path::PathBuf,
+ api_path_local: std::path::PathBuf,
+ api_path_network: std::path::PathBuf,
+ api_path_pool: std::path::PathBuf,
+ gupax_p2pool_api: Arc>,
+ ) {
+ // 1a. Create PTY
+ debug!("P2Pool | 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();
+ // 1b. Create command
+ debug!("P2Pool | Creating command...");
+ let mut cmd = portable_pty::CommandBuilder::new(path.as_path());
+ cmd.args(args);
+ cmd.env("NO_COLOR", "true");
+ cmd.cwd(path.as_path().parent().unwrap());
+ // 1c. Create child
+ debug!("P2Pool | Creating child...");
+ let child_pty = arc_mut!(pair.slave.spawn_command(cmd).unwrap());
+ drop(pair.slave);
// 2. Set process state
- debug!("P2Pool | Setting process state...");
+ debug!("P2Pool | Setting process state...");
let mut lock = lock!(process);
lock.state = ProcessState::Syncing;
lock.signal = ProcessSignal::None;
lock.start = Instant::now();
- let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
- let mut stdin = pair.master.take_writer().unwrap();
- drop(lock);
+ let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
+ let mut stdin = pair.master.take_writer().unwrap();
+ drop(lock);
- // 3. Spawn PTY read thread
- debug!("P2Pool | Spawning PTY read thread...");
- let output_parse = Arc::clone(&lock!(process).output_parse);
- let output_pub = Arc::clone(&lock!(process).output_pub);
- let gupax_p2pool_api = Arc::clone(&gupax_p2pool_api);
- thread::spawn(move || {
- Self::read_pty_p2pool(output_parse, output_pub, reader, gupax_p2pool_api);
- });
- let output_parse = Arc::clone(&lock!(process).output_parse);
- let output_pub = Arc::clone(&lock!(process).output_pub);
+ // 3. Spawn PTY read thread
+ debug!("P2Pool | Spawning PTY read thread...");
+ let output_parse = Arc::clone(&lock!(process).output_parse);
+ let output_pub = Arc::clone(&lock!(process).output_pub);
+ let gupax_p2pool_api = Arc::clone(&gupax_p2pool_api);
+ thread::spawn(move || {
+ Self::read_pty_p2pool(output_parse, output_pub, reader, gupax_p2pool_api);
+ });
+ let output_parse = Arc::clone(&lock!(process).output_parse);
+ let output_pub = Arc::clone(&lock!(process).output_pub);
- debug!("P2Pool | Cleaning old [local] API files...");
- // Attempt to remove stale API file
- match std::fs::remove_file(&api_path_local) {
- Ok(_) => info!("P2Pool | Attempting to remove stale API file ... OK"),
- Err(e) => warn!("P2Pool | Attempting to remove stale API file ... FAIL ... {}", e),
- }
- // Attempt to create a default empty one.
- use std::io::Write;
- if std::fs::File::create(&api_path_local).is_ok() {
- let text = r#"{"hashrate_15m":0,"hashrate_1h":0,"hashrate_24h":0,"shares_found":0,"average_effort":0.0,"current_effort":0.0,"connections":0}"#;
- match std::fs::write(&api_path_local, text) {
- Ok(_) => info!("P2Pool | Creating default empty API file ... OK"),
- Err(e) => warn!("P2Pool | Creating default empty API file ... FAIL ... {}", e),
- }
- }
- let start = lock!(process).start;
+ debug!("P2Pool | Cleaning old [local] API files...");
+ // Attempt to remove stale API file
+ match std::fs::remove_file(&api_path_local) {
+ Ok(_) => info!("P2Pool | Attempting to remove stale API file ... OK"),
+ Err(e) => warn!(
+ "P2Pool | Attempting to remove stale API file ... FAIL ... {}",
+ e
+ ),
+ }
+ // Attempt to create a default empty one.
+ use std::io::Write;
+ if std::fs::File::create(&api_path_local).is_ok() {
+ let text = r#"{"hashrate_15m":0,"hashrate_1h":0,"hashrate_24h":0,"shares_found":0,"average_effort":0.0,"current_effort":0.0,"connections":0}"#;
+ match std::fs::write(&api_path_local, text) {
+ Ok(_) => info!("P2Pool | Creating default empty API file ... OK"),
+ Err(e) => warn!(
+ "P2Pool | Creating default empty API file ... FAIL ... {}",
+ e
+ ),
+ }
+ }
+ let start = lock!(process).start;
- // Reset stats before loop
- *lock!(pub_api) = PubP2poolApi::new();
- *lock!(gui_api) = PubP2poolApi::new();
+ // Reset stats before loop
+ *lock!(pub_api) = PubP2poolApi::new();
+ *lock!(gui_api) = PubP2poolApi::new();
- // 4. Loop as watchdog
- info!("P2Pool | Entering watchdog mode... woof!");
- loop {
- // Set timer
- let now = Instant::now();
- debug!("P2Pool Watchdog | ----------- Start of loop -----------");
- lock!(gui_api).tick += 1;
+ // 4. Loop as watchdog
+ info!("P2Pool | Entering watchdog mode... woof!");
+ loop {
+ // Set timer
+ let now = Instant::now();
+ debug!("P2Pool Watchdog | ----------- Start of loop -----------");
+ lock!(gui_api).tick += 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");
- break
- }
+ // 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");
+ 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");
- 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");
+ 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 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 if logs need resetting
- debug!("P2Pool Watchdog | Attempting GUI log reset check");
- let mut lock = lock!(gui_api);
- Self::check_reset_gui_output(&mut lock.output, ProcessName::P2pool);
- drop(lock);
+ // Check if logs need resetting
+ debug!("P2Pool Watchdog | Attempting GUI log reset check");
+ let mut lock = lock!(gui_api);
+ Self::check_reset_gui_output(&mut lock.output, ProcessName::P2pool);
+ drop(lock);
- // Always update from output
- debug!("P2Pool Watchdog | Starting [update_from_output()]");
- PubP2poolApi::update_from_output(&pub_api, &output_parse, &output_pub, start.elapsed(), &process);
+ // Always update from output
+ debug!("P2Pool Watchdog | Starting [update_from_output()]");
+ PubP2poolApi::update_from_output(
+ &pub_api,
+ &output_parse,
+ &output_pub,
+ start.elapsed(),
+ &process,
+ );
- // Read [local] API
- debug!("P2Pool Watchdog | Attempting [local] API file read");
- if let Ok(string) = Self::path_to_string(&api_path_local, ProcessName::P2pool) {
- // Deserialize
- if let Ok(local_api) = PrivP2poolLocalApi::from_str(&string) {
- // Update the structs.
- PubP2poolApi::update_from_local(&pub_api, local_api);
- }
- }
- // If more than 1 minute has passed, read the other API files.
- if lock!(gui_api).tick >= 60 {
- debug!("P2Pool Watchdog | Attempting [network] & [pool] API file read");
- if let (Ok(network_api), Ok(pool_api)) = (Self::path_to_string(&api_path_network, ProcessName::P2pool), Self::path_to_string(&api_path_pool, ProcessName::P2pool)) {
- if let (Ok(network_api), Ok(pool_api)) = (PrivP2poolNetworkApi::from_str(&network_api), PrivP2poolPoolApi::from_str(&pool_api)) {
- PubP2poolApi::update_from_network_pool(&pub_api, network_api, pool_api);
- lock!(gui_api).tick = 0;
- }
- }
- }
+ // Read [local] API
+ debug!("P2Pool Watchdog | Attempting [local] API file read");
+ if let Ok(string) = Self::path_to_string(&api_path_local, ProcessName::P2pool) {
+ // Deserialize
+ if let Ok(local_api) = PrivP2poolLocalApi::from_str(&string) {
+ // Update the structs.
+ PubP2poolApi::update_from_local(&pub_api, local_api);
+ }
+ }
+ // If more than 1 minute has passed, read the other API files.
+ if lock!(gui_api).tick >= 60 {
+ debug!("P2Pool Watchdog | Attempting [network] & [pool] API file read");
+ if let (Ok(network_api), Ok(pool_api)) = (
+ Self::path_to_string(&api_path_network, ProcessName::P2pool),
+ Self::path_to_string(&api_path_pool, ProcessName::P2pool),
+ ) {
+ if let (Ok(network_api), Ok(pool_api)) = (
+ PrivP2poolNetworkApi::from_str(&network_api),
+ PrivP2poolPoolApi::from_str(&pool_api),
+ ) {
+ PubP2poolApi::update_from_network_pool(&pub_api, network_api, pool_api);
+ lock!(gui_api).tick = 0;
+ }
+ }
+ }
- // 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!("P2Pool Watchdog | END OF LOOP - Tick: [{}/60] - Sleeping for [{}]ms...", lock!(gui_api).tick, sleep);
- sleep!(sleep);
- } else {
- debug!("P2Pool Watchdog | END OF LOOP - Tick: [{}/60] Not sleeping!", lock!(gui_api).tick);
- }
- }
+ // 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!(
+ "P2Pool Watchdog | END OF LOOP - Tick: [{}/60] - Sleeping for [{}]ms...",
+ lock!(gui_api).tick,
+ sleep
+ );
+ sleep!(sleep);
+ } else {
+ debug!(
+ "P2Pool Watchdog | END OF LOOP - Tick: [{}/60] Not sleeping!",
+ lock!(gui_api).tick
+ );
+ }
+ }
- // 5. If loop broke, we must be done here.
- info!("P2Pool Watchdog | Watchdog thread exiting... Goodbye!");
- }
+ // 5. If loop broke, we must be done here.
+ info!("P2Pool Watchdog | Watchdog thread exiting... Goodbye!");
+ }
- //---------------------------------------------------------------------------------------------------- XMRig specific, most functions are very similar to P2Pool's
- #[cold]
- #[inline(never)]
- // If processes are started with [sudo] on macOS, they must also
- // be killed with [sudo] (even if I have a direct handle to it as the
- // parent process...!). This is only needed on macOS, not Linux.
- fn sudo_kill(pid: u32, sudo: &Arc>) -> bool {
- // Spawn [sudo] to execute [kill] on the given [pid]
- let mut child = std::process::Command::new("sudo")
- .args(["--stdin", "kill", "-9", &pid.to_string()])
- .stdin(Stdio::piped())
- .spawn().unwrap();
+ //---------------------------------------------------------------------------------------------------- XMRig specific, most functions are very similar to P2Pool's
+ #[cold]
+ #[inline(never)]
+ // If processes are started with [sudo] on macOS, they must also
+ // be killed with [sudo] (even if I have a direct handle to it as the
+ // parent process...!). This is only needed on macOS, not Linux.
+ fn sudo_kill(pid: u32, sudo: &Arc>) -> bool {
+ // Spawn [sudo] to execute [kill] on the given [pid]
+ let mut child = std::process::Command::new("sudo")
+ .args(["--stdin", "kill", "-9", &pid.to_string()])
+ .stdin(Stdio::piped())
+ .spawn()
+ .unwrap();
- // Write the [sudo] password to STDIN.
- let mut stdin = child.stdin.take().unwrap();
- use std::io::Write;
- if let Err(e) = writeln!(stdin, "{}\n", lock!(sudo).pass) { error!("Sudo Kill | STDIN error: {}", e); }
+ // Write the [sudo] password to STDIN.
+ let mut stdin = child.stdin.take().unwrap();
+ use std::io::Write;
+ if let Err(e) = writeln!(stdin, "{}\n", lock!(sudo).pass) {
+ error!("Sudo Kill | STDIN error: {}", e);
+ }
- // Return exit code of [sudo/kill].
- child.wait().unwrap().success()
- }
+ // Return exit code of [sudo/kill].
+ child.wait().unwrap().success()
+ }
- #[cold]
- #[inline(never)]
- // Just sets some signals for the watchdog thread to pick up on.
- pub fn stop_xmrig(helper: &Arc>) {
- info!("XMRig | Attempting to stop...");
- lock2!(helper,xmrig).signal = ProcessSignal::Stop;
- lock2!(helper,xmrig).state = ProcessState::Middle;
- }
+ #[cold]
+ #[inline(never)]
+ // Just sets some signals for the watchdog thread to pick up on.
+ pub fn stop_xmrig(helper: &Arc>) {
+ info!("XMRig | Attempting to stop...");
+ lock2!(helper, xmrig).signal = ProcessSignal::Stop;
+ lock2!(helper, xmrig).state = ProcessState::Middle;
+ }
- #[cold]
- #[inline(never)]
- // The "restart frontend" to a "frontend" function.
- // Basically calls to kill the current xmrig, waits a little, then starts the below function in a a new thread, then exit.
- pub fn restart_xmrig(helper: &Arc>, state: &crate::disk::Xmrig, path: &std::path::PathBuf, sudo: Arc>) {
- info!("XMRig | Attempting to restart...");
- lock2!(helper,xmrig).signal = ProcessSignal::Restart;
- lock2!(helper,xmrig).state = ProcessState::Middle;
+ #[cold]
+ #[inline(never)]
+ // The "restart frontend" to a "frontend" function.
+ // Basically calls to kill the current xmrig, waits a little, then starts the below function in a a new thread, then exit.
+ pub fn restart_xmrig(
+ helper: &Arc>,
+ state: &crate::disk::Xmrig,
+ path: &std::path::PathBuf,
+ sudo: Arc>,
+ ) {
+ info!("XMRig | Attempting to restart...");
+ lock2!(helper, xmrig).signal = ProcessSignal::Restart;
+ lock2!(helper, xmrig).state = ProcessState::Middle;
- let helper = Arc::clone(helper);
- let state = state.clone();
- let path = path.clone();
- // This thread lives to wait, start xmrig then die.
- thread::spawn(move || {
- while lock2!(helper,xmrig).state != ProcessState::Waiting {
- warn!("XMRig | Want to restart but process is still alive, waiting...");
- sleep!(1000);
- }
- // Ok, process is not alive, start the new one!
- info!("XMRig | Old process seems dead, starting new one!");
- Self::start_xmrig(&helper, &state, &path, sudo);
- });
- info!("XMRig | Restart ... OK");
- }
+ let helper = Arc::clone(helper);
+ let state = state.clone();
+ let path = path.clone();
+ // This thread lives to wait, start xmrig then die.
+ thread::spawn(move || {
+ while lock2!(helper, xmrig).state != ProcessState::Waiting {
+ warn!("XMRig | Want to restart but process is still alive, waiting...");
+ sleep!(1000);
+ }
+ // Ok, process is not alive, start the new one!
+ info!("XMRig | Old process seems dead, starting new one!");
+ Self::start_xmrig(&helper, &state, &path, sudo);
+ });
+ info!("XMRig | Restart ... OK");
+ }
- #[cold]
- #[inline(never)]
- pub fn start_xmrig(helper: &Arc>, state: &crate::disk::Xmrig, path: &std::path::PathBuf, sudo: Arc>) {
- lock2!(helper,xmrig).state = ProcessState::Middle;
+ #[cold]
+ #[inline(never)]
+ pub fn start_xmrig(
+ helper: &Arc>,
+ state: &crate::disk::Xmrig,
+ path: &std::path::PathBuf,
+ sudo: Arc>,
+ ) {
+ lock2!(helper, xmrig).state = ProcessState::Middle;
- let (args, api_ip_port) = Self::build_xmrig_args_and_mutate_img(helper, state, path);
+ let (args, api_ip_port) = Self::build_xmrig_args_and_mutate_img(helper, state, path);
- // Print arguments & user settings to console
- crate::disk::print_dash(&format!("XMRig | Launch arguments: {:#?}", args));
- info!("XMRig | Using path: [{}]", path.display());
+ // Print arguments & user settings to console
+ crate::disk::print_dash(&format!("XMRig | Launch arguments: {:#?}", args));
+ info!("XMRig | Using path: [{}]", path.display());
- // Spawn watchdog thread
- let process = Arc::clone(&lock!(helper).xmrig);
- let gui_api = Arc::clone(&lock!(helper).gui_api_xmrig);
- let pub_api = Arc::clone(&lock!(helper).pub_api_xmrig);
- let path = path.clone();
- thread::spawn(move || {
- Self::spawn_xmrig_watchdog(process, gui_api, pub_api, args, path, sudo, api_ip_port);
- });
- }
+ // Spawn watchdog thread
+ let process = Arc::clone(&lock!(helper).xmrig);
+ let gui_api = Arc::clone(&lock!(helper).gui_api_xmrig);
+ let pub_api = Arc::clone(&lock!(helper).pub_api_xmrig);
+ let path = path.clone();
+ thread::spawn(move || {
+ Self::spawn_xmrig_watchdog(process, gui_api, pub_api, args, path, sudo, api_ip_port);
+ });
+ }
- #[cold]
- #[inline(never)]
- // Takes in some [State/Xmrig] and parses it to build the actual command arguments.
- // Returns the [Vec] of actual arguments, and mutates the [ImgXmrig] for the main GUI thread
- // It returns a value... and mutates a deeply nested passed argument... this is some pretty bad code...
- pub fn build_xmrig_args_and_mutate_img(helper: &Arc>, state: &crate::disk::Xmrig, path: &std::path::PathBuf) -> (Vec, String) {
- let mut args = Vec::with_capacity(500);
- let mut api_ip = String::with_capacity(15);
- let mut api_port = String::with_capacity(5);
- let path = path.clone();
- // The actual binary we're executing is [sudo], technically
- // the XMRig path is just an argument to sudo, so add it.
- // Before that though, add the ["--prompt"] flag and set it
- // to emptyness so that it doesn't show up in the output.
- if cfg!(unix) {
- args.push(r#"--prompt="#.to_string());
- args.push("--".to_string());
- args.push(path.display().to_string());
- }
+ #[cold]
+ #[inline(never)]
+ // Takes in some [State/Xmrig] and parses it to build the actual command arguments.
+ // Returns the [Vec] of actual arguments, and mutates the [ImgXmrig] for the main GUI thread
+ // It returns a value... and mutates a deeply nested passed argument... this is some pretty bad code...
+ pub fn build_xmrig_args_and_mutate_img(
+ helper: &Arc>,
+ state: &crate::disk::Xmrig,
+ path: &std::path::PathBuf,
+ ) -> (Vec, String) {
+ let mut args = Vec::with_capacity(500);
+ let mut api_ip = String::with_capacity(15);
+ let mut api_port = String::with_capacity(5);
+ let path = path.clone();
+ // The actual binary we're executing is [sudo], technically
+ // the XMRig path is just an argument to sudo, so add it.
+ // Before that though, add the ["--prompt"] flag and set it
+ // to emptyness so that it doesn't show up in the output.
+ if cfg!(unix) {
+ args.push(r#"--prompt="#.to_string());
+ args.push("--".to_string());
+ args.push(path.display().to_string());
+ }
- // [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("--url".to_string()); args.push("127.0.0.1:3333".to_string()); // Local P2Pool (the default)
- args.push("--threads".to_string()); args.push(state.current_threads.to_string()); // Threads
- 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("18088".to_string()); // HTTP API Port
- if state.pause != 0 { args.push("--pause-on-active".to_string()); args.push(state.pause.to_string()); } // Pause on active
- *lock2!(helper,img_xmrig) = ImgXmrig {
- threads: state.current_threads.to_string(),
- url: "127.0.0.1:3333 (Local P2Pool)".to_string(),
- };
- api_ip = "127.0.0.1".to_string();
- api_port = "18088".to_string();
+ // [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("--url".to_string());
+ args.push("127.0.0.1:3333".to_string()); // Local P2Pool (the default)
+ args.push("--threads".to_string());
+ args.push(state.current_threads.to_string()); // Threads
+ 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("18088".to_string()); // HTTP API Port
+ if state.pause != 0 {
+ args.push("--pause-on-active".to_string());
+ args.push(state.pause.to_string());
+ } // Pause on active
+ *lock2!(helper, img_xmrig) = ImgXmrig {
+ threads: state.current_threads.to_string(),
+ url: "127.0.0.1:3333 (Local P2Pool)".to_string(),
+ };
+ api_ip = "127.0.0.1".to_string();
+ api_port = "18088".to_string();
- // [Advanced]
- } else {
- // Overriding command arguments
- if !state.arguments.is_empty() {
- // This parses the input and attempts to fill out
- // the [ImgXmrig]... This is pretty bad code...
- let mut last = "";
- let lock = lock!(helper);
- let mut xmrig_image = lock!(lock.img_xmrig);
- for arg in state.arguments.split_whitespace() {
- match last {
- "--threads" => xmrig_image.threads = arg.to_string(),
- "--url" => xmrig_image.url = arg.to_string(),
- "--http-host" => api_ip = if arg == "localhost" { "127.0.0.1".to_string() } else { arg.to_string() },
- "--http-port" => api_port = arg.to_string(),
- _ => (),
- }
- args.push(if arg == "localhost" { "127.0.0.1".to_string() } else { arg.to_string() });
- last = arg;
- }
- // Else, build the argument
- } else {
- // XMRig doesn't understand [localhost]
- let ip = if state.ip == "localhost" || state.ip.is_empty() { "127.0.0.1" } else { &state.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() { "18088".to_string() } else { state.api_port.to_string() };
- let url = format!("{}:{}", ip, state.port); // Combine IP:Port into one string
- args.push("--user".to_string()); args.push(state.address.clone()); // Wallet
- args.push("--threads".to_string()); args.push(state.current_threads.to_string()); // Threads
- args.push("--rig-id".to_string()); args.push(state.rig.to_string()); // Rig ID
- args.push("--url".to_string()); args.push(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
- if state.pause != 0 { args.push("--pause-on-active".to_string()); args.push(state.pause.to_string()); } // Pause on active
- *lock2!(helper,img_xmrig) = ImgXmrig {
- url,
- threads: state.current_threads.to_string(),
- };
- }
- }
- (args, format!("{}:{}", api_ip, api_port))
- }
+ // [Advanced]
+ } else {
+ // Overriding command arguments
+ if !state.arguments.is_empty() {
+ // This parses the input and attempts to fill out
+ // the [ImgXmrig]... This is pretty bad code...
+ let mut last = "";
+ let lock = lock!(helper);
+ let mut xmrig_image = lock!(lock.img_xmrig);
+ for arg in state.arguments.split_whitespace() {
+ match last {
+ "--threads" => xmrig_image.threads = arg.to_string(),
+ "--url" => xmrig_image.url = arg.to_string(),
+ "--http-host" => {
+ api_ip = if arg == "localhost" {
+ "127.0.0.1".to_string()
+ } else {
+ arg.to_string()
+ }
+ }
+ "--http-port" => api_port = arg.to_string(),
+ _ => (),
+ }
+ args.push(if arg == "localhost" {
+ "127.0.0.1".to_string()
+ } else {
+ arg.to_string()
+ });
+ last = arg;
+ }
+ // Else, build the argument
+ } else {
+ // XMRig doesn't understand [localhost]
+ let ip = if state.ip == "localhost" || state.ip.is_empty() {
+ "127.0.0.1"
+ } else {
+ &state.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() {
+ "18088".to_string()
+ } else {
+ state.api_port.to_string()
+ };
+ let url = format!("{}:{}", ip, state.port); // Combine IP:Port into one string
+ args.push("--user".to_string());
+ args.push(state.address.clone()); // Wallet
+ args.push("--threads".to_string());
+ args.push(state.current_threads.to_string()); // Threads
+ args.push("--rig-id".to_string());
+ args.push(state.rig.to_string()); // Rig ID
+ args.push("--url".to_string());
+ args.push(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
+ if state.pause != 0 {
+ args.push("--pause-on-active".to_string());
+ args.push(state.pause.to_string());
+ } // Pause on active
+ *lock2!(helper, img_xmrig) = ImgXmrig {
+ url,
+ threads: state.current_threads.to_string(),
+ };
+ }
+ }
+ (args, format!("{}:{}", api_ip, api_port))
+ }
- // We actually spawn [sudo] on Unix, with XMRig being the argument.
- #[cfg(target_family = "unix")]
- fn create_xmrig_cmd_unix(args: Vec, path: PathBuf) -> portable_pty::CommandBuilder {
- let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new("sudo");
- cmd.args(args);
- cmd.cwd(path.as_path().parent().unwrap());
- cmd
- }
+ // We actually spawn [sudo] on Unix, with XMRig being the argument.
+ #[cfg(target_family = "unix")]
+ fn create_xmrig_cmd_unix(args: Vec, path: PathBuf) -> portable_pty::CommandBuilder {
+ let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new("sudo");
+ cmd.args(args);
+ cmd.cwd(path.as_path().parent().unwrap());
+ cmd
+ }
- // Gupax should be admin on Windows, so just spawn XMRig normally.
- #[cfg(target_os = "windows")]
- fn create_xmrig_cmd_windows(args: Vec, path: PathBuf) -> portable_pty::CommandBuilder {
- let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new(path.clone());
- cmd.args(args);
- cmd.cwd(path.as_path().parent().unwrap());
- cmd
- }
+ // Gupax should be admin on Windows, so just spawn XMRig normally.
+ #[cfg(target_os = "windows")]
+ fn create_xmrig_cmd_windows(args: Vec, path: PathBuf) -> portable_pty::CommandBuilder {
+ let mut cmd = portable_pty::cmdbuilder::CommandBuilder::new(path.clone());
+ cmd.args(args);
+ cmd.cwd(path.as_path().parent().unwrap());
+ cmd
+ }
- #[cold]
- #[inline(never)]
- // The XMRig watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works.
- // This isn't actually async, a tokio runtime is unfortunately needed because [Hyper] is an async library (HTTP API calls)
- #[tokio::main]
- async fn spawn_xmrig_watchdog(process: Arc>, gui_api: Arc>, pub_api: Arc>, args: Vec, path: std::path::PathBuf, sudo: Arc>, mut api_ip_port: String) {
- // 1a. Create PTY
- debug!("XMRig | Creating PTY...");
- let pty = portable_pty::native_pty_system();
- let mut pair = pty.openpty(portable_pty::PtySize {
- rows: 100,
- cols: 1000,
- pixel_width: 0,
- pixel_height: 0,
- }).unwrap();
- // 1b. Create command
- debug!("XMRig | Creating command...");
- #[cfg(target_os = "windows")]
- let cmd = Self::create_xmrig_cmd_windows(args, path);
- #[cfg(target_family = "unix")]
- let cmd = Self::create_xmrig_cmd_unix(args, path);
- // 1c. Create child
- debug!("XMRig | Creating child...");
- let child_pty = arc_mut!(pair.slave.spawn_command(cmd).unwrap());
- drop(pair.slave);
+ #[cold]
+ #[inline(never)]
+ // The XMRig watchdog. Spawns 1 OS thread for reading a PTY (STDOUT+STDERR), and combines the [Child] with a PTY so STDIN actually works.
+ // This isn't actually async, a tokio runtime is unfortunately needed because [Hyper] is an async library (HTTP API calls)
+ #[tokio::main]
+ async fn spawn_xmrig_watchdog(
+ process: Arc>,
+ gui_api: Arc>,
+ pub_api: Arc>,
+ args: Vec,
+ path: std::path::PathBuf,
+ sudo: Arc>,
+ mut api_ip_port: String,
+ ) {
+ // 1a. Create PTY
+ debug!("XMRig | Creating PTY...");
+ let pty = portable_pty::native_pty_system();
+ let mut pair = pty
+ .openpty(portable_pty::PtySize {
+ rows: 100,
+ cols: 1000,
+ pixel_width: 0,
+ pixel_height: 0,
+ })
+ .unwrap();
+ // 1b. Create command
+ debug!("XMRig | Creating command...");
+ #[cfg(target_os = "windows")]
+ let cmd = Self::create_xmrig_cmd_windows(args, path);
+ #[cfg(target_family = "unix")]
+ let cmd = Self::create_xmrig_cmd_unix(args, path);
+ // 1c. Create child
+ debug!("XMRig | Creating child...");
+ let child_pty = arc_mut!(pair.slave.spawn_command(cmd).unwrap());
+ drop(pair.slave);
- let mut stdin = pair.master.take_writer().unwrap();
+ let mut stdin = pair.master.take_writer().unwrap();
- // 2. Input [sudo] pass, wipe, then drop.
- if cfg!(unix) {
- debug!("XMRig | Inputting [sudo] and wiping...");
- // a) Sleep to wait for [sudo]'s non-echo prompt (on Unix).
- // this prevents users pass from showing up in the STDOUT.
- sleep!(3000);
- if let Err(e) = writeln!(stdin, "{}", lock!(sudo).pass) { error!("XMRig | Sudo STDIN error: {}", e); };
- SudoState::wipe(&sudo);
-
- // b) Reset GUI STDOUT just in case.
- debug!("XMRig | Clearing GUI output...");
- lock!(gui_api).output.clear();
- }
+ // 2. Input [sudo] pass, wipe, then drop.
+ if cfg!(unix) {
+ debug!("XMRig | Inputting [sudo] and wiping...");
+ // a) Sleep to wait for [sudo]'s non-echo prompt (on Unix).
+ // this prevents users pass from showing up in the STDOUT.
+ sleep!(3000);
+ if let Err(e) = writeln!(stdin, "{}", lock!(sudo).pass) {
+ error!("XMRig | Sudo STDIN error: {}", e);
+ };
+ SudoState::wipe(&sudo);
+ // b) Reset GUI STDOUT just in case.
+ debug!("XMRig | Clearing GUI output...");
+ lock!(gui_api).output.clear();
+ }
// 3. Set process state
- debug!("XMRig | Setting process state...");
+ debug!("XMRig | Setting process state...");
let mut lock = lock!(process);
lock.state = ProcessState::NotMining;
lock.signal = ProcessSignal::None;
lock.start = Instant::now();
- let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
- drop(lock);
+ let reader = pair.master.try_clone_reader().unwrap(); // Get STDOUT/STDERR before moving the PTY
+ drop(lock);
- // 4. Spawn PTY read thread
- debug!("XMRig | Spawning PTY read thread...");
- let output_parse = Arc::clone(&lock!(process).output_parse);
- let output_pub = Arc::clone(&lock!(process).output_pub);
- thread::spawn(move || {
- Self::read_pty_xmrig(output_parse, output_pub, reader);
- });
- let output_parse = Arc::clone(&lock!(process).output_parse);
- let output_pub = Arc::clone(&lock!(process).output_pub);
+ // 4. Spawn PTY read thread
+ debug!("XMRig | Spawning PTY read thread...");
+ let output_parse = Arc::clone(&lock!(process).output_parse);
+ let output_pub = Arc::clone(&lock!(process).output_pub);
+ thread::spawn(move || {
+ Self::read_pty_xmrig(output_parse, output_pub, reader);
+ });
+ let output_parse = Arc::clone(&lock!(process).output_parse);
+ let output_pub = Arc::clone(&lock!(process).output_pub);
- let client: hyper::Client = hyper::Client::builder().build(hyper::client::HttpConnector::new());
- let start = lock!(process).start;
- let api_uri = {
- if !api_ip_port.ends_with('/') { api_ip_port.push('/'); }
- "http://".to_owned() + &api_ip_port + XMRIG_API_URI
- };
- info!("XMRig | Final API URI: {}", api_uri);
+ let client: hyper::Client =
+ hyper::Client::builder().build(hyper::client::HttpConnector::new());
+ let start = lock!(process).start;
+ let api_uri = {
+ if !api_ip_port.ends_with('/') {
+ api_ip_port.push('/');
+ }
+ "http://".to_owned() + &api_ip_port + XMRIG_API_URI
+ };
+ info!("XMRig | Final API URI: {}", api_uri);
- // Reset stats before loop
- *lock!(pub_api) = PubXmrigApi::new();
- *lock!(gui_api) = PubXmrigApi::new();
+ // Reset stats before loop
+ *lock!(pub_api) = PubXmrigApi::new();
+ *lock!(gui_api) = PubXmrigApi::new();
- // 5. Loop as watchdog
- info!("XMRig | Entering watchdog mode... woof!");
- loop {
- // Set timer
- let now = Instant::now();
- debug!("XMRig Watchdog | ----------- Start of loop -----------");
+ // 5. Loop as watchdog
+ info!("XMRig | Entering watchdog mode... woof!");
+ loop {
+ // Set timer
+ let now = Instant::now();
+ 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 = HumanTime::into_human(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");
- break
- }
+ // 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 = HumanTime::into_human(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");
+ 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 = HumanTime::into_human(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");
- 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 = HumanTime::into_human(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");
+ 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); }
- }
- }
- drop(lock);
+ // 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);
+ }
+ }
+ }
+ drop(lock);
- // 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::Xmrig);
- drop(lock);
+ // 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::Xmrig);
+ drop(lock);
- // Always update from output
- debug!("XMRig Watchdog | Starting [update_from_output()]");
- PubXmrigApi::update_from_output(&pub_api, &output_pub, &output_parse, start.elapsed(), &process);
+ // Always update from output
+ debug!("XMRig Watchdog | Starting [update_from_output()]");
+ PubXmrigApi::update_from_output(
+ &pub_api,
+ &output_pub,
+ &output_parse,
+ start.elapsed(),
+ &process,
+ );
- // Send an HTTP API request
- debug!("XMRig Watchdog | Attempting HTTP API request...");
- if let Ok(priv_api) = PrivXmrigApi::request_xmrig_api(client.clone(), &api_uri).await {
- debug!("XMRig Watchdog | HTTP API request OK, attempting [update_from_priv()]");
- PubXmrigApi::update_from_priv(&pub_api, priv_api);
- } else {
- warn!("XMRig Watchdog | Could not send HTTP API request to: {}", api_uri);
- }
+ // Send an HTTP API request
+ debug!("XMRig Watchdog | Attempting HTTP API request...");
+ if let Ok(priv_api) = PrivXmrigApi::request_xmrig_api(client.clone(), &api_uri).await {
+ debug!("XMRig Watchdog | HTTP API request OK, attempting [update_from_priv()]");
+ PubXmrigApi::update_from_priv(&pub_api, priv_api);
+ } else {
+ warn!(
+ "XMRig Watchdog | Could not send HTTP API request to: {}",
+ api_uri
+ );
+ }
- // 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);
- sleep!(sleep);
- } else {
- debug!("XMRig Watchdog | END OF LOOP - Not sleeping!");
- }
- }
+ // 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
+ );
+ sleep!(sleep);
+ } else {
+ debug!("XMRig Watchdog | END OF LOOP - Not sleeping!");
+ }
+ }
- // 5. If loop broke, we must be done here.
- info!("XMRig Watchdog | Watchdog thread exiting... Goodbye!");
- }
+ // 5. If loop broke, we must be done here.
+ info!("XMRig Watchdog | Watchdog thread exiting... Goodbye!");
+ }
- //---------------------------------------------------------------------------------------------------- The "helper"
- #[inline(always)] // called once
- fn update_pub_sys_from_sysinfo(sysinfo: &sysinfo::System, pub_sys: &mut Sys, pid: &sysinfo::Pid, helper: &Helper, max_threads: usize) {
- let gupax_uptime = helper.uptime.to_string();
- let cpu = &sysinfo.cpus()[0];
- let gupax_cpu_usage = format!("{:.2}%", sysinfo.process(*pid).unwrap().cpu_usage()/(max_threads as f32));
- let gupax_memory_used_mb = HumanNumber::from_u64(sysinfo.process(*pid).unwrap().memory()/1_000_000);
- let gupax_memory_used_mb = format!("{} megabytes", gupax_memory_used_mb);
- let system_cpu_model = format!("{} ({}MHz)", cpu.brand(), cpu.frequency());
- let system_memory = {
- let used = (sysinfo.used_memory() as f64)/1_000_000_000.0;
- let total = (sysinfo.total_memory() as f64)/1_000_000_000.0;
- format!("{:.3} GB / {:.3} GB", used, total)
- };
- let system_cpu_usage = {
- let mut total: f32 = 0.0;
- for cpu in sysinfo.cpus() {
- total += cpu.cpu_usage();
- }
- format!("{:.2}%", total/(max_threads as f32))
- };
- *pub_sys = Sys {
- gupax_uptime,
- gupax_cpu_usage,
- gupax_memory_used_mb,
- system_cpu_usage,
- system_memory,
- system_cpu_model,
- };
- }
+ //---------------------------------------------------------------------------------------------------- The "helper"
+ #[inline(always)] // called once
+ fn update_pub_sys_from_sysinfo(
+ sysinfo: &sysinfo::System,
+ pub_sys: &mut Sys,
+ pid: &sysinfo::Pid,
+ helper: &Helper,
+ max_threads: usize,
+ ) {
+ let gupax_uptime = helper.uptime.to_string();
+ let cpu = &sysinfo.cpus()[0];
+ let gupax_cpu_usage = format!(
+ "{:.2}%",
+ sysinfo.process(*pid).unwrap().cpu_usage() / (max_threads as f32)
+ );
+ let gupax_memory_used_mb =
+ HumanNumber::from_u64(sysinfo.process(*pid).unwrap().memory() / 1_000_000);
+ let gupax_memory_used_mb = format!("{} megabytes", gupax_memory_used_mb);
+ let system_cpu_model = format!("{} ({}MHz)", cpu.brand(), cpu.frequency());
+ let system_memory = {
+ let used = (sysinfo.used_memory() as f64) / 1_000_000_000.0;
+ let total = (sysinfo.total_memory() as f64) / 1_000_000_000.0;
+ format!("{:.3} GB / {:.3} GB", used, total)
+ };
+ let system_cpu_usage = {
+ let mut total: f32 = 0.0;
+ for cpu in sysinfo.cpus() {
+ total += cpu.cpu_usage();
+ }
+ format!("{:.2}%", total / (max_threads as f32))
+ };
+ *pub_sys = Sys {
+ gupax_uptime,
+ gupax_cpu_usage,
+ gupax_memory_used_mb,
+ system_cpu_usage,
+ system_memory,
+ system_cpu_model,
+ };
+ }
- #[cold]
- #[inline(never)]
- // The "helper" thread. Syncs data between threads here and the GUI.
- pub fn spawn_helper(helper: &Arc>, mut sysinfo: sysinfo::System, pid: sysinfo::Pid, max_threads: usize) {
- // The ordering of these locks is _very_ important. They MUST be in sync with how the main GUI thread locks stuff
- // or a deadlock will occur given enough time. They will eventually both want to lock the [Arc] the other
- // thread is already locking. Yes, I figured this out the hard way, hence the vast amount of debug!() messages.
- // Example of different order (BAD!):
- //
- // GUI Main -> locks [p2pool] first
- // Helper -> locks [gui_api_p2pool] first
- // GUI Status Tab -> tries to lock [gui_api_p2pool] -> CAN'T
- // Helper -> tries to lock [p2pool] -> CAN'T
- //
- // These two threads are now in a deadlock because both
- // are trying to access locks the other one already has.
- //
- // The locking order here must be in the same chronological
- // order as the main GUI thread (top to bottom).
+ #[cold]
+ #[inline(never)]
+ // The "helper" thread. Syncs data between threads here and the GUI.
+ pub fn spawn_helper(
+ helper: &Arc>,
+ mut sysinfo: sysinfo::System,
+ pid: sysinfo::Pid,
+ max_threads: usize,
+ ) {
+ // The ordering of these locks is _very_ important. They MUST be in sync with how the main GUI thread locks stuff
+ // or a deadlock will occur given enough time. They will eventually both want to lock the [Arc] the other
+ // thread is already locking. Yes, I figured this out the hard way, hence the vast amount of debug!() messages.
+ // Example of different order (BAD!):
+ //
+ // GUI Main -> locks [p2pool] first
+ // Helper -> locks [gui_api_p2pool] first
+ // GUI Status Tab -> tries to lock [gui_api_p2pool] -> CAN'T
+ // Helper -> tries to lock [p2pool] -> CAN'T
+ //
+ // These two threads are now in a deadlock because both
+ // are trying to access locks the other one already has.
+ //
+ // The locking order here must be in the same chronological
+ // order as the main GUI thread (top to bottom).
- let helper = Arc::clone(helper);
- let lock = lock!(helper);
- let p2pool = Arc::clone(&lock.p2pool);
- let xmrig = Arc::clone(&lock.xmrig);
- 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 pub_api_p2pool = Arc::clone(&lock.pub_api_p2pool);
- let pub_api_xmrig = Arc::clone(&lock.pub_api_xmrig);
- drop(lock);
+ let helper = Arc::clone(helper);
+ let lock = lock!(helper);
+ let p2pool = Arc::clone(&lock.p2pool);
+ let xmrig = Arc::clone(&lock.xmrig);
+ 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 pub_api_p2pool = Arc::clone(&lock.pub_api_p2pool);
+ let pub_api_xmrig = Arc::clone(&lock.pub_api_xmrig);
+ drop(lock);
- let sysinfo_cpu = sysinfo::CpuRefreshKind::everything();
- let sysinfo_processes = sysinfo::ProcessRefreshKind::new().with_cpu();
+ let sysinfo_cpu = sysinfo::CpuRefreshKind::everything();
+ let sysinfo_processes = sysinfo::ProcessRefreshKind::new().with_cpu();
- thread::spawn(move || {
- info!("Helper | Hello from helper thread! Entering loop where I will spend the rest of my days...");
- // Begin loop
- loop {
- // 1. Loop init timestamp
- let start = Instant::now();
- debug!("Helper | ----------- Start of loop -----------");
+ thread::spawn(move || {
+ info!("Helper | Hello from helper thread! Entering loop where I will spend the rest of my days...");
+ // Begin loop
+ loop {
+ // 1. Loop init timestamp
+ let start = Instant::now();
+ debug!("Helper | ----------- Start of loop -----------");
- // Ignore the invasive [debug!()] messages on the right side of the code.
- // The reason why they are there are so that it's extremely easy to track
- // down the culprit of an [Arc] deadlock. I know, they're ugly.
+ // Ignore the invasive [debug!()] messages on the right side of the code.
+ // The reason why they are there are so that it's extremely easy to track
+ // down the culprit of an [Arc] deadlock. I know, they're ugly.
- // 2. Lock... EVERYTHING!
- let mut lock = lock!(helper); debug!("Helper | Locking (1/8) ... [helper]");
- let p2pool = lock!(p2pool); debug!("Helper | Locking (2/8) ... [p2pool]");
- let xmrig = lock!(xmrig); debug!("Helper | Locking (3/8) ... [xmrig]");
- let mut lock_pub_sys = lock!(pub_sys); debug!("Helper | Locking (4/8) ... [pub_sys]");
- let mut gui_api_p2pool = lock!(gui_api_p2pool); debug!("Helper | Locking (5/8) ... [gui_api_p2pool]");
- let mut gui_api_xmrig = lock!(gui_api_xmrig); debug!("Helper | Locking (6/8) ... [gui_api_xmrig]");
- let mut pub_api_p2pool = lock!(pub_api_p2pool); debug!("Helper | Locking (7/8) ... [pub_api_p2pool]");
- let mut pub_api_xmrig = lock!(pub_api_xmrig); debug!("Helper | Locking (8/8) ... [pub_api_xmrig]");
- // Calculate Gupax's uptime always.
- lock.uptime = HumanTime::into_human(lock.instant.elapsed());
- // If [P2Pool] is alive...
- if p2pool.is_alive() {
- debug!("Helper | P2Pool is alive! Running [combine_gui_pub_api()]");
- PubP2poolApi::combine_gui_pub_api(&mut gui_api_p2pool, &mut pub_api_p2pool);
- } else {
- debug!("Helper | P2Pool is dead! Skipping...");
- }
- // If [XMRig] is alive...
- if xmrig.is_alive() {
- debug!("Helper | XMRig is alive! Running [combine_gui_pub_api()]");
- PubXmrigApi::combine_gui_pub_api(&mut gui_api_xmrig, &mut pub_api_xmrig);
- } else {
- debug!("Helper | XMRig is dead! Skipping...");
- }
+ // 2. Lock... EVERYTHING!
+ let mut lock = lock!(helper);
+ debug!("Helper | Locking (1/8) ... [helper]");
+ let p2pool = lock!(p2pool);
+ debug!("Helper | Locking (2/8) ... [p2pool]");
+ let xmrig = lock!(xmrig);
+ debug!("Helper | Locking (3/8) ... [xmrig]");
+ let mut lock_pub_sys = lock!(pub_sys);
+ debug!("Helper | Locking (4/8) ... [pub_sys]");
+ let mut gui_api_p2pool = lock!(gui_api_p2pool);
+ debug!("Helper | Locking (5/8) ... [gui_api_p2pool]");
+ let mut gui_api_xmrig = lock!(gui_api_xmrig);
+ debug!("Helper | Locking (6/8) ... [gui_api_xmrig]");
+ let mut pub_api_p2pool = lock!(pub_api_p2pool);
+ debug!("Helper | Locking (7/8) ... [pub_api_p2pool]");
+ let mut pub_api_xmrig = lock!(pub_api_xmrig);
+ debug!("Helper | Locking (8/8) ... [pub_api_xmrig]");
+ // Calculate Gupax's uptime always.
+ lock.uptime = HumanTime::into_human(lock.instant.elapsed());
+ // If [P2Pool] is alive...
+ if p2pool.is_alive() {
+ debug!("Helper | P2Pool is alive! Running [combine_gui_pub_api()]");
+ PubP2poolApi::combine_gui_pub_api(&mut gui_api_p2pool, &mut pub_api_p2pool);
+ } else {
+ debug!("Helper | P2Pool is dead! Skipping...");
+ }
+ // If [XMRig] is alive...
+ if xmrig.is_alive() {
+ debug!("Helper | XMRig is alive! Running [combine_gui_pub_api()]");
+ PubXmrigApi::combine_gui_pub_api(&mut gui_api_xmrig, &mut pub_api_xmrig);
+ } else {
+ debug!("Helper | XMRig is dead! Skipping...");
+ }
- // 2. Selectively refresh [sysinfo] for only what we need (better performance).
- sysinfo.refresh_cpu_specifics(sysinfo_cpu); debug!("Helper | Sysinfo refresh (1/3) ... [cpu]");
- sysinfo.refresh_processes_specifics(sysinfo_processes); debug!("Helper | Sysinfo refresh (2/3) ... [processes]");
- sysinfo.refresh_memory(); debug!("Helper | Sysinfo refresh (3/3) ... [memory]");
- debug!("Helper | Sysinfo OK, running [update_pub_sys_from_sysinfo()]");
- Self::update_pub_sys_from_sysinfo(&sysinfo, &mut lock_pub_sys, &pid, &lock, max_threads);
+ // 2. Selectively refresh [sysinfo] for only what we need (better performance).
+ sysinfo.refresh_cpu_specifics(sysinfo_cpu);
+ debug!("Helper | Sysinfo refresh (1/3) ... [cpu]");
+ sysinfo.refresh_processes_specifics(sysinfo_processes);
+ debug!("Helper | Sysinfo refresh (2/3) ... [processes]");
+ sysinfo.refresh_memory();
+ debug!("Helper | Sysinfo refresh (3/3) ... [memory]");
+ debug!("Helper | Sysinfo OK, running [update_pub_sys_from_sysinfo()]");
+ Self::update_pub_sys_from_sysinfo(
+ &sysinfo,
+ &mut lock_pub_sys,
+ &pid,
+ &lock,
+ max_threads,
+ );
- // 3. Drop... (almost) EVERYTHING... IN REVERSE!
- drop(lock_pub_sys); debug!("Helper | Unlocking (1/8) ... [pub_sys]");
- drop(xmrig); debug!("Helper | Unlocking (2/8) ... [xmrig]");
- drop(p2pool); debug!("Helper | Unlocking (3/8) ... [p2pool]");
- drop(pub_api_xmrig); debug!("Helper | Unlocking (4/8) ... [pub_api_xmrig]");
- drop(pub_api_p2pool); debug!("Helper | Unlocking (5/8) ... [pub_api_p2pool]");
- drop(gui_api_xmrig); debug!("Helper | Unlocking (6/8) ... [gui_api_xmrig]");
- drop(gui_api_p2pool); debug!("Helper | Unlocking (7/8) ... [gui_api_p2pool]");
- drop(lock); debug!("Helper | Unlocking (8/8) ... [helper]");
+ // 3. Drop... (almost) EVERYTHING... IN REVERSE!
+ drop(lock_pub_sys);
+ debug!("Helper | Unlocking (1/8) ... [pub_sys]");
+ drop(xmrig);
+ debug!("Helper | Unlocking (2/8) ... [xmrig]");
+ drop(p2pool);
+ debug!("Helper | Unlocking (3/8) ... [p2pool]");
+ drop(pub_api_xmrig);
+ debug!("Helper | Unlocking (4/8) ... [pub_api_xmrig]");
+ drop(pub_api_p2pool);
+ debug!("Helper | Unlocking (5/8) ... [pub_api_p2pool]");
+ drop(gui_api_xmrig);
+ debug!("Helper | Unlocking (6/8) ... [gui_api_xmrig]");
+ drop(gui_api_p2pool);
+ debug!("Helper | Unlocking (7/8) ... [gui_api_p2pool]");
+ drop(lock);
+ debug!("Helper | Unlocking (8/8) ... [helper]");
- // 4. Calculate if we should sleep or not.
- // If we should sleep, how long?
- let elapsed = start.elapsed().as_millis();
- if elapsed < 1000 {
- // Casting from u128 to u64 should be safe here, because [elapsed]
- // is less than 1000, meaning it can fit into a u64 easy.
- let sleep = (1000-elapsed) as u64;
- debug!("Helper | END OF LOOP - Sleeping for [{}]ms...", sleep);
- sleep!(sleep);
- } else {
- debug!("Helper | END OF LOOP - Not sleeping!");
- }
+ // 4. Calculate if we should sleep or not.
+ // If we should sleep, how long?
+ let elapsed = start.elapsed().as_millis();
+ if elapsed < 1000 {
+ // Casting from u128 to u64 should be safe here, because [elapsed]
+ // is less than 1000, meaning it can fit into a u64 easy.
+ let sleep = (1000 - elapsed) as u64;
+ debug!("Helper | END OF LOOP - Sleeping for [{}]ms...", sleep);
+ sleep!(sleep);
+ } else {
+ debug!("Helper | END OF LOOP - Not sleeping!");
+ }
- // 5. End loop
- }
- });
- }
+ // 5. End loop
+ }
+ });
+ }
}
//---------------------------------------------------------------------------------------------------- [ImgP2pool]
@@ -1327,424 +1706,450 @@ impl Helper {
// No need for an [Arc] since the Helper thread doesn't need this information.
#[derive(Debug, Clone)]
pub struct ImgP2pool {
- pub mini: String, // Did the user start on the mini-chain?
- pub address: String, // What address is the current p2pool paying out to? (This gets shortened to [4xxxxx...xxxxxx])
- pub host: String, // What monerod are we using?
- pub rpc: String, // What is the RPC port?
- pub zmq: String, // What is the ZMQ port?
- pub out_peers: String, // How many out-peers?
- pub in_peers: String, // How many in-peers?
+ pub mini: String, // Did the user start on the mini-chain?
+ pub address: String, // What address is the current p2pool paying out to? (This gets shortened to [4xxxxx...xxxxxx])
+ pub host: String, // What monerod are we using?
+ pub rpc: String, // What is the RPC port?
+ pub zmq: String, // What is the ZMQ port?
+ pub out_peers: String, // How many out-peers?
+ pub in_peers: String, // How many in-peers?
}
impl Default for ImgP2pool {
- fn default() -> Self {
- Self::new()
- }
+ fn default() -> Self {
+ Self::new()
+ }
}
impl ImgP2pool {
- pub fn new() -> Self {
- Self {
- mini: String::from("???"),
- address: String::from("???"),
- host: String::from("???"),
- rpc: String::from("???"),
- zmq: String::from("???"),
- out_peers: String::from("???"),
- in_peers: String::from("???"),
- }
- }
+ pub fn new() -> Self {
+ Self {
+ mini: String::from("???"),
+ address: String::from("???"),
+ host: String::from("???"),
+ rpc: String::from("???"),
+ zmq: String::from("???"),
+ out_peers: String::from("???"),
+ in_peers: String::from("???"),
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- Public P2Pool API
// Helper/GUI threads both have a copy of this, Helper updates
// the GUI's version on a 1-second interval from the private data.
-#[derive(Debug,Clone,PartialEq)]
+#[derive(Debug, Clone, PartialEq)]
pub struct PubP2poolApi {
- // Output
- pub output: String,
- // Uptime
- pub uptime: HumanTime,
- // These are manually parsed from the STDOUT.
- pub payouts: u128,
- pub payouts_hour: f64,
- pub payouts_day: f64,
- pub payouts_month: f64,
- pub xmr: f64,
- pub xmr_hour: f64,
- pub xmr_day: f64,
- pub xmr_month: f64,
- // Local API
- pub hashrate_15m: HumanNumber,
- pub hashrate_1h: HumanNumber,
- pub hashrate_24h: HumanNumber,
- pub shares_found: HumanNumber,
- pub average_effort: HumanNumber,
- pub current_effort: HumanNumber,
- pub connections: HumanNumber,
- // The API needs a raw ints to go off of and
- // there's not a good way to access it without doing weird
- // [Arc] shenanigans, so some raw ints are stored here.
- pub user_p2pool_hashrate_u64: u64,
- pub p2pool_difficulty_u64: u64,
- pub monero_difficulty_u64: u64,
- pub p2pool_hashrate_u64: u64,
- pub monero_hashrate_u64: u64,
- // Tick. Every loop this gets incremented.
- // At 60, it indicated we should read the below API files.
- pub tick: u8,
- // Network API
- pub monero_difficulty: HumanNumber, // e.g: [15,000,000]
- pub monero_hashrate: HumanNumber, // e.g: [1.000 GH/s]
- pub hash: String, // Current block hash
- pub height: HumanNumber,
- pub reward: AtomicUnit,
- // Pool API
- pub p2pool_difficulty: HumanNumber,
- pub p2pool_hashrate: HumanNumber,
- pub miners: HumanNumber, // Current amount of miners on P2Pool sidechain
- // Mean (calculated in functions, not serialized)
- pub solo_block_mean: HumanTime, // Time it would take the user to find a solo block
- pub p2pool_block_mean: HumanTime, // Time it takes the P2Pool sidechain to find a block
- pub p2pool_share_mean: HumanTime, // Time it would take the user to find a P2Pool share
- // Percent
- pub p2pool_percent: HumanNumber, // Percentage of P2Pool hashrate capture of overall Monero hashrate.
- pub user_p2pool_percent: HumanNumber, // How much percent the user's hashrate accounts for in P2Pool.
- pub user_monero_percent: HumanNumber, // How much percent the user's hashrate accounts for in all of Monero hashrate.
+ // Output
+ pub output: String,
+ // Uptime
+ pub uptime: HumanTime,
+ // These are manually parsed from the STDOUT.
+ pub payouts: u128,
+ pub payouts_hour: f64,
+ pub payouts_day: f64,
+ pub payouts_month: f64,
+ pub xmr: f64,
+ pub xmr_hour: f64,
+ pub xmr_day: f64,
+ pub xmr_month: f64,
+ // Local API
+ pub hashrate_15m: HumanNumber,
+ pub hashrate_1h: HumanNumber,
+ pub hashrate_24h: HumanNumber,
+ pub shares_found: HumanNumber,
+ pub average_effort: HumanNumber,
+ pub current_effort: HumanNumber,
+ pub connections: HumanNumber,
+ // The API needs a raw ints to go off of and
+ // there's not a good way to access it without doing weird
+ // [Arc] shenanigans, so some raw ints are stored here.
+ pub user_p2pool_hashrate_u64: u64,
+ pub p2pool_difficulty_u64: u64,
+ pub monero_difficulty_u64: u64,
+ pub p2pool_hashrate_u64: u64,
+ pub monero_hashrate_u64: u64,
+ // Tick. Every loop this gets incremented.
+ // At 60, it indicated we should read the below API files.
+ pub tick: u8,
+ // Network API
+ pub monero_difficulty: HumanNumber, // e.g: [15,000,000]
+ pub monero_hashrate: HumanNumber, // e.g: [1.000 GH/s]
+ pub hash: String, // Current block hash
+ pub height: HumanNumber,
+ pub reward: AtomicUnit,
+ // Pool API
+ pub p2pool_difficulty: HumanNumber,
+ pub p2pool_hashrate: HumanNumber,
+ pub miners: HumanNumber, // Current amount of miners on P2Pool sidechain
+ // Mean (calculated in functions, not serialized)
+ pub solo_block_mean: HumanTime, // Time it would take the user to find a solo block
+ pub p2pool_block_mean: HumanTime, // Time it takes the P2Pool sidechain to find a block
+ pub p2pool_share_mean: HumanTime, // Time it would take the user to find a P2Pool share
+ // Percent
+ pub p2pool_percent: HumanNumber, // Percentage of P2Pool hashrate capture of overall Monero hashrate.
+ pub user_p2pool_percent: HumanNumber, // How much percent the user's hashrate accounts for in P2Pool.
+ pub user_monero_percent: HumanNumber, // How much percent the user's hashrate accounts for in all of Monero hashrate.
}
impl Default for PubP2poolApi {
- fn default() -> Self {
- Self::new()
- }
+ fn default() -> Self {
+ Self::new()
+ }
}
impl PubP2poolApi {
- pub fn new() -> Self {
- Self {
- output: String::new(),
- uptime: HumanTime::new(),
- payouts: 0,
- payouts_hour: 0.0,
- payouts_day: 0.0,
- payouts_month: 0.0,
- xmr: 0.0,
- xmr_hour: 0.0,
- xmr_day: 0.0,
- xmr_month: 0.0,
- hashrate_15m: HumanNumber::unknown(),
- hashrate_1h: HumanNumber::unknown(),
- hashrate_24h: HumanNumber::unknown(),
- shares_found: HumanNumber::unknown(),
- average_effort: HumanNumber::unknown(),
- current_effort: HumanNumber::unknown(),
- connections: HumanNumber::unknown(),
- tick: 0,
- user_p2pool_hashrate_u64: 0,
- p2pool_difficulty_u64: 0,
- monero_difficulty_u64: 0,
- p2pool_hashrate_u64: 0,
- monero_hashrate_u64: 0,
- monero_difficulty: HumanNumber::unknown(),
- monero_hashrate: HumanNumber::unknown(),
- hash: String::from("???"),
- height: HumanNumber::unknown(),
- reward: AtomicUnit::new(),
- p2pool_difficulty: HumanNumber::unknown(),
- p2pool_hashrate: HumanNumber::unknown(),
- miners: HumanNumber::unknown(),
- solo_block_mean: HumanTime::new(),
- p2pool_block_mean: HumanTime::new(),
- p2pool_share_mean: HumanTime::new(),
- p2pool_percent: HumanNumber::unknown(),
- user_p2pool_percent: HumanNumber::unknown(),
- user_monero_percent: HumanNumber::unknown(),
- }
- }
+ pub fn new() -> Self {
+ Self {
+ output: String::new(),
+ uptime: HumanTime::new(),
+ payouts: 0,
+ payouts_hour: 0.0,
+ payouts_day: 0.0,
+ payouts_month: 0.0,
+ xmr: 0.0,
+ xmr_hour: 0.0,
+ xmr_day: 0.0,
+ xmr_month: 0.0,
+ hashrate_15m: HumanNumber::unknown(),
+ hashrate_1h: HumanNumber::unknown(),
+ hashrate_24h: HumanNumber::unknown(),
+ shares_found: HumanNumber::unknown(),
+ average_effort: HumanNumber::unknown(),
+ current_effort: HumanNumber::unknown(),
+ connections: HumanNumber::unknown(),
+ tick: 0,
+ user_p2pool_hashrate_u64: 0,
+ p2pool_difficulty_u64: 0,
+ monero_difficulty_u64: 0,
+ p2pool_hashrate_u64: 0,
+ monero_hashrate_u64: 0,
+ monero_difficulty: HumanNumber::unknown(),
+ monero_hashrate: HumanNumber::unknown(),
+ hash: String::from("???"),
+ height: HumanNumber::unknown(),
+ reward: AtomicUnit::new(),
+ p2pool_difficulty: HumanNumber::unknown(),
+ p2pool_hashrate: HumanNumber::unknown(),
+ miners: HumanNumber::unknown(),
+ solo_block_mean: HumanTime::new(),
+ p2pool_block_mean: HumanTime::new(),
+ p2pool_share_mean: HumanTime::new(),
+ p2pool_percent: HumanNumber::unknown(),
+ user_p2pool_percent: HumanNumber::unknown(),
+ user_monero_percent: HumanNumber::unknown(),
+ }
+ }
- #[inline]
- // The issue with just doing [gui_api = pub_api] is that values get overwritten.
- // This doesn't matter for any of the values EXCEPT for the output, so we must
- // manually append it instead of overwriting.
- // This is used in the "helper" thread.
- fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) {
- let mut output = std::mem::take(&mut gui_api.output);
- let buf = std::mem::take(&mut pub_api.output);
- if !buf.is_empty() { output.push_str(&buf); }
- *gui_api = Self {
- output,
- tick: std::mem::take(&mut gui_api.tick),
- ..pub_api.clone()
- };
- }
+ #[inline]
+ // The issue with just doing [gui_api = pub_api] is that values get overwritten.
+ // This doesn't matter for any of the values EXCEPT for the output, so we must
+ // manually append it instead of overwriting.
+ // This is used in the "helper" thread.
+ fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) {
+ let mut output = std::mem::take(&mut gui_api.output);
+ let buf = std::mem::take(&mut pub_api.output);
+ if !buf.is_empty() {
+ output.push_str(&buf);
+ }
+ *gui_api = Self {
+ output,
+ tick: std::mem::take(&mut gui_api.tick),
+ ..pub_api.clone()
+ };
+ }
- #[inline]
- // Essentially greps the output for [x.xxxxxxxxxxxx XMR] where x = a number.
- // It sums each match and counts along the way, handling an error by not adding and printing to console.
- fn calc_payouts_and_xmr(output: &str) -> (u128 /* payout count */, f64 /* total xmr */) {
- let iter = P2POOL_REGEX.payout.find_iter(output);
- let mut sum: f64 = 0.0;
- let mut count: u128 = 0;
- for i in iter {
- if let Some(word) = P2POOL_REGEX.payout_float.find(i.as_str()) {
- match word.as_str().parse::() {
- Ok(num) => { sum += num; count += 1; },
- Err(e) => error!("P2Pool | Total XMR sum calculation error: [{}]", e),
- }
- }
- }
- (count, sum)
- }
+ #[inline]
+ // Essentially greps the output for [x.xxxxxxxxxxxx XMR] where x = a number.
+ // It sums each match and counts along the way, handling an error by not adding and printing to console.
+ fn calc_payouts_and_xmr(output: &str) -> (u128 /* payout count */, f64 /* total xmr */) {
+ let iter = P2POOL_REGEX.payout.find_iter(output);
+ let mut sum: f64 = 0.0;
+ let mut count: u128 = 0;
+ for i in iter {
+ if let Some(word) = P2POOL_REGEX.payout_float.find(i.as_str()) {
+ match word.as_str().parse::() {
+ Ok(num) => {
+ sum += num;
+ count += 1;
+ }
+ Err(e) => error!("P2Pool | Total XMR sum calculation error: [{}]", e),
+ }
+ }
+ }
+ (count, sum)
+ }
- // Mutate "watchdog"'s [PubP2poolApi] with data the process output.
- 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);
- if !output_pub.is_empty() {
- lock!(public).output.push_str(&std::mem::take(&mut *output_pub));
- }
+ // Mutate "watchdog"'s [PubP2poolApi] with data the process output.
+ 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);
+ if !output_pub.is_empty() {
+ lock!(public)
+ .output
+ .push_str(&std::mem::take(&mut *output_pub));
+ }
- // 2. Parse the full STDOUT
- let mut output_parse = lock!(output_parse);
- let (payouts_new, xmr_new) = Self::calc_payouts_and_xmr(&output_parse);
- // Check for "SYNCHRONIZED" only if we aren't already.
- if lock!(process).state == ProcessState::Syncing {
- // How many times the word was captured.
- let synchronized_captures = P2POOL_REGEX.synchronized.find_iter(&output_parse).count();
+ // 2. Parse the full STDOUT
+ let mut output_parse = lock!(output_parse);
+ let (payouts_new, xmr_new) = Self::calc_payouts_and_xmr(&output_parse);
+ // Check for "SYNCHRONIZED" only if we aren't already.
+ if lock!(process).state == ProcessState::Syncing {
+ // How many times the word was captured.
+ let synchronized_captures = P2POOL_REGEX.synchronized.find_iter(&output_parse).count();
- // If P2Pool receives shares before syncing, it will start mining on its own sidechain.
- // In this instance, we technically are "synced" on block 1 and P2Pool will print "SYNCHRONIZED"
- // although, that doesn't necessarily mean we're synced on main/mini-chain.
- //
- // So, if we find a `next block = 1`, that means we
- // must look for at least 2 instances of "SYNCHRONIZED",
- // one for the sidechain, one for main/mini.
- if P2POOL_REGEX.next_height_1.is_match(&output_parse) {
- if synchronized_captures > 1 {
- lock!(process).state = ProcessState::Alive;
- }
- } else if synchronized_captures > 0 {
- // if there is no `next block = 1`, fallback to
- // just finding 1 instance of "SYNCHRONIZED".
- lock!(process).state = ProcessState::Alive;
- }
- }
- // 3. Throw away [output_parse]
- output_parse.clear();
- drop(output_parse);
- // 4. Add to current values
- let mut public = lock!(public);
- let (payouts, xmr) = (public.payouts + payouts_new, public.xmr + xmr_new);
+ // If P2Pool receives shares before syncing, it will start mining on its own sidechain.
+ // In this instance, we technically are "synced" on block 1 and P2Pool will print "SYNCHRONIZED"
+ // although, that doesn't necessarily mean we're synced on main/mini-chain.
+ //
+ // So, if we find a `next block = 1`, that means we
+ // must look for at least 2 instances of "SYNCHRONIZED",
+ // one for the sidechain, one for main/mini.
+ if P2POOL_REGEX.next_height_1.is_match(&output_parse) {
+ if synchronized_captures > 1 {
+ lock!(process).state = ProcessState::Alive;
+ }
+ } else if synchronized_captures > 0 {
+ // if there is no `next block = 1`, fallback to
+ // just finding 1 instance of "SYNCHRONIZED".
+ lock!(process).state = ProcessState::Alive;
+ }
+ }
+ // 3. Throw away [output_parse]
+ output_parse.clear();
+ drop(output_parse);
+ // 4. Add to current values
+ let mut public = lock!(public);
+ let (payouts, xmr) = (public.payouts + payouts_new, public.xmr + xmr_new);
- // 5. Calculate hour/day/month given elapsed time
- let elapsed_as_secs_f64 = elapsed.as_secs_f64();
- // Payouts
- let per_sec = (payouts as f64) / elapsed_as_secs_f64;
- let payouts_hour = (per_sec * 60.0) * 60.0;
- let payouts_day = payouts_hour * 24.0;
- let payouts_month = payouts_day * 30.0;
- // Total XMR
- let per_sec = xmr / elapsed_as_secs_f64;
- let xmr_hour = (per_sec * 60.0) * 60.0;
- let xmr_day = xmr_hour * 24.0;
- let xmr_month = xmr_day * 30.0;
+ // 5. Calculate hour/day/month given elapsed time
+ let elapsed_as_secs_f64 = elapsed.as_secs_f64();
+ // Payouts
+ let per_sec = (payouts as f64) / elapsed_as_secs_f64;
+ let payouts_hour = (per_sec * 60.0) * 60.0;
+ let payouts_day = payouts_hour * 24.0;
+ let payouts_month = payouts_day * 30.0;
+ // Total XMR
+ let per_sec = xmr / elapsed_as_secs_f64;
+ let xmr_hour = (per_sec * 60.0) * 60.0;
+ let xmr_day = xmr_hour * 24.0;
+ let xmr_month = xmr_day * 30.0;
- if payouts_new != 0 {
- debug!("P2Pool Watchdog | New [Payout] found in output ... {}", payouts_new);
- debug!("P2Pool Watchdog | Total [Payout] should be ... {}", payouts);
- debug!("P2Pool Watchdog | Correct [Payout per] should be ... [{}/hour, {}/day, {}/month]", payouts_hour, payouts_day, payouts_month);
- }
- if xmr_new != 0.0 {
- debug!("P2Pool Watchdog | New [XMR mined] found in output ... {}", xmr_new);
- debug!("P2Pool Watchdog | Total [XMR mined] should be ... {}", xmr);
- debug!("P2Pool Watchdog | Correct [XMR mined per] should be ... [{}/hour, {}/day, {}/month]", xmr_hour, xmr_day, xmr_month);
- }
+ if payouts_new != 0 {
+ debug!(
+ "P2Pool Watchdog | New [Payout] found in output ... {}",
+ payouts_new
+ );
+ debug!("P2Pool Watchdog | Total [Payout] should be ... {}", payouts);
+ debug!(
+ "P2Pool Watchdog | Correct [Payout per] should be ... [{}/hour, {}/day, {}/month]",
+ payouts_hour, payouts_day, payouts_month
+ );
+ }
+ if xmr_new != 0.0 {
+ debug!(
+ "P2Pool Watchdog | New [XMR mined] found in output ... {}",
+ xmr_new
+ );
+ debug!("P2Pool Watchdog | Total [XMR mined] should be ... {}", xmr);
+ debug!("P2Pool Watchdog | Correct [XMR mined per] should be ... [{}/hour, {}/day, {}/month]", xmr_hour, xmr_day, xmr_month);
+ }
- // 6. Mutate the struct with the new info
- *public = Self {
- uptime: HumanTime::into_human(elapsed),
- payouts,
- xmr,
- payouts_hour,
- payouts_day,
- payouts_month,
- xmr_hour,
- xmr_day,
- xmr_month,
- ..std::mem::take(&mut *public)
- };
- }
+ // 6. Mutate the struct with the new info
+ *public = Self {
+ uptime: HumanTime::into_human(elapsed),
+ payouts,
+ xmr,
+ payouts_hour,
+ payouts_day,
+ payouts_month,
+ xmr_hour,
+ xmr_day,
+ xmr_month,
+ ..std::mem::take(&mut *public)
+ };
+ }
- // Mutate [PubP2poolApi] with data from a [PrivP2poolLocalApi] and the process output.
- fn update_from_local(public: &Arc>, local: PrivP2poolLocalApi) {
- let mut public = lock!(public);
- *public = Self {
- hashrate_15m: HumanNumber::from_u64(local.hashrate_15m),
- hashrate_1h: HumanNumber::from_u64(local.hashrate_1h),
- hashrate_24h: HumanNumber::from_u64(local.hashrate_24h),
- shares_found: HumanNumber::from_u64(local.shares_found),
- average_effort: HumanNumber::to_percent(local.average_effort),
- current_effort: HumanNumber::to_percent(local.current_effort),
- connections: HumanNumber::from_u32(local.connections),
- user_p2pool_hashrate_u64: local.hashrate_1h,
- ..std::mem::take(&mut *public)
- };
- }
+ // Mutate [PubP2poolApi] with data from a [PrivP2poolLocalApi] and the process output.
+ fn update_from_local(public: &Arc>, local: PrivP2poolLocalApi) {
+ let mut public = lock!(public);
+ *public = Self {
+ hashrate_15m: HumanNumber::from_u64(local.hashrate_15m),
+ hashrate_1h: HumanNumber::from_u64(local.hashrate_1h),
+ hashrate_24h: HumanNumber::from_u64(local.hashrate_24h),
+ shares_found: HumanNumber::from_u64(local.shares_found),
+ average_effort: HumanNumber::to_percent(local.average_effort),
+ current_effort: HumanNumber::to_percent(local.current_effort),
+ connections: HumanNumber::from_u32(local.connections),
+ user_p2pool_hashrate_u64: local.hashrate_1h,
+ ..std::mem::take(&mut *public)
+ };
+ }
- // Mutate [PubP2poolApi] with data from a [PrivP2pool(Network|Pool)Api].
- fn update_from_network_pool(public: &Arc>, net: PrivP2poolNetworkApi, pool: PrivP2poolPoolApi) {
- let user_hashrate = lock!(public).user_p2pool_hashrate_u64; // The user's total P2Pool hashrate
- let monero_difficulty = net.difficulty;
- let monero_hashrate = monero_difficulty / MONERO_BLOCK_TIME_IN_SECONDS;
- let p2pool_hashrate = pool.pool_statistics.hashRate;
- let p2pool_difficulty = p2pool_hashrate * P2POOL_BLOCK_TIME_IN_SECONDS;
- // These [0] checks prevent dividing by 0 (it [panic!()]s)
- let p2pool_block_mean;
- let user_p2pool_percent;
- if p2pool_hashrate == 0 {
- p2pool_block_mean = HumanTime::new();
- user_p2pool_percent = HumanNumber::unknown();
- } else {
- p2pool_block_mean = HumanTime::into_human(std::time::Duration::from_secs(monero_difficulty / p2pool_hashrate));
- let f = (user_hashrate as f64 / p2pool_hashrate as f64) * 100.0;
- user_p2pool_percent = HumanNumber::from_f64_to_percent_6_point(f);
- };
- let p2pool_percent;
- let user_monero_percent;
- if monero_hashrate == 0 {
- p2pool_percent = HumanNumber::unknown();
- user_monero_percent = HumanNumber::unknown();
- } else {
- let f = (p2pool_hashrate as f64 / monero_hashrate as f64) * 100.0;
- p2pool_percent = HumanNumber::from_f64_to_percent_6_point(f);
- let f = (user_hashrate as f64 / monero_hashrate as f64) * 100.0;
- user_monero_percent = HumanNumber::from_f64_to_percent_6_point(f);
- };
- let solo_block_mean;
- let p2pool_share_mean;
- if user_hashrate == 0 {
- solo_block_mean = HumanTime::new();
- p2pool_share_mean = HumanTime::new();
- } else {
- solo_block_mean = HumanTime::into_human(std::time::Duration::from_secs(monero_difficulty / user_hashrate));
- p2pool_share_mean = HumanTime::into_human(std::time::Duration::from_secs(p2pool_difficulty / user_hashrate));
- }
- let mut public = lock!(public);
- *public = Self {
- p2pool_difficulty_u64: p2pool_difficulty,
- monero_difficulty_u64: monero_difficulty,
- p2pool_hashrate_u64: p2pool_hashrate,
- monero_hashrate_u64: monero_hashrate,
- monero_difficulty: HumanNumber::from_u64(monero_difficulty),
- monero_hashrate: HumanNumber::from_u64_to_gigahash_3_point(monero_hashrate),
- hash: net.hash,
- height: HumanNumber::from_u32(net.height),
- reward: AtomicUnit::from_u64(net.reward),
- p2pool_difficulty: HumanNumber::from_u64(p2pool_difficulty),
- p2pool_hashrate: HumanNumber::from_u64_to_megahash_3_point(p2pool_hashrate),
- miners: HumanNumber::from_u32(pool.pool_statistics.miners),
- solo_block_mean,
- p2pool_block_mean,
- p2pool_share_mean,
- p2pool_percent,
- user_p2pool_percent,
- user_monero_percent,
- ..std::mem::take(&mut *public)
- };
- }
+ // Mutate [PubP2poolApi] with data from a [PrivP2pool(Network|Pool)Api].
+ fn update_from_network_pool(
+ public: &Arc>,
+ net: PrivP2poolNetworkApi,
+ pool: PrivP2poolPoolApi,
+ ) {
+ let user_hashrate = lock!(public).user_p2pool_hashrate_u64; // The user's total P2Pool hashrate
+ let monero_difficulty = net.difficulty;
+ let monero_hashrate = monero_difficulty / MONERO_BLOCK_TIME_IN_SECONDS;
+ let p2pool_hashrate = pool.pool_statistics.hashRate;
+ let p2pool_difficulty = p2pool_hashrate * P2POOL_BLOCK_TIME_IN_SECONDS;
+ // These [0] checks prevent dividing by 0 (it [panic!()]s)
+ let p2pool_block_mean;
+ let user_p2pool_percent;
+ if p2pool_hashrate == 0 {
+ p2pool_block_mean = HumanTime::new();
+ user_p2pool_percent = HumanNumber::unknown();
+ } else {
+ p2pool_block_mean = HumanTime::into_human(std::time::Duration::from_secs(
+ monero_difficulty / p2pool_hashrate,
+ ));
+ let f = (user_hashrate as f64 / p2pool_hashrate as f64) * 100.0;
+ user_p2pool_percent = HumanNumber::from_f64_to_percent_6_point(f);
+ };
+ let p2pool_percent;
+ let user_monero_percent;
+ if monero_hashrate == 0 {
+ p2pool_percent = HumanNumber::unknown();
+ user_monero_percent = HumanNumber::unknown();
+ } else {
+ let f = (p2pool_hashrate as f64 / monero_hashrate as f64) * 100.0;
+ p2pool_percent = HumanNumber::from_f64_to_percent_6_point(f);
+ let f = (user_hashrate as f64 / monero_hashrate as f64) * 100.0;
+ user_monero_percent = HumanNumber::from_f64_to_percent_6_point(f);
+ };
+ let solo_block_mean;
+ let p2pool_share_mean;
+ if user_hashrate == 0 {
+ solo_block_mean = HumanTime::new();
+ p2pool_share_mean = HumanTime::new();
+ } else {
+ solo_block_mean = HumanTime::into_human(std::time::Duration::from_secs(
+ monero_difficulty / user_hashrate,
+ ));
+ p2pool_share_mean = HumanTime::into_human(std::time::Duration::from_secs(
+ p2pool_difficulty / user_hashrate,
+ ));
+ }
+ let mut public = lock!(public);
+ *public = Self {
+ p2pool_difficulty_u64: p2pool_difficulty,
+ monero_difficulty_u64: monero_difficulty,
+ p2pool_hashrate_u64: p2pool_hashrate,
+ monero_hashrate_u64: monero_hashrate,
+ monero_difficulty: HumanNumber::from_u64(monero_difficulty),
+ monero_hashrate: HumanNumber::from_u64_to_gigahash_3_point(monero_hashrate),
+ hash: net.hash,
+ height: HumanNumber::from_u32(net.height),
+ reward: AtomicUnit::from_u64(net.reward),
+ p2pool_difficulty: HumanNumber::from_u64(p2pool_difficulty),
+ p2pool_hashrate: HumanNumber::from_u64_to_megahash_3_point(p2pool_hashrate),
+ miners: HumanNumber::from_u32(pool.pool_statistics.miners),
+ solo_block_mean,
+ p2pool_block_mean,
+ p2pool_share_mean,
+ p2pool_percent,
+ user_p2pool_percent,
+ user_monero_percent,
+ ..std::mem::take(&mut *public)
+ };
+ }
- #[inline]
- pub fn calculate_share_or_block_time(hashrate: u64, difficulty: u64) -> HumanTime {
- if hashrate == 0 {
- HumanTime::new()
- } else {
- HumanTime::from_u64(difficulty / hashrate)
- }
- }
+ #[inline]
+ pub fn calculate_share_or_block_time(hashrate: u64, difficulty: u64) -> HumanTime {
+ if hashrate == 0 {
+ HumanTime::new()
+ } else {
+ HumanTime::from_u64(difficulty / hashrate)
+ }
+ }
- #[inline]
- pub fn calculate_dominance(my_hashrate: u64, global_hashrate: u64) -> HumanNumber {
- if global_hashrate == 0 {
- HumanNumber::unknown()
- } else {
- let f = (my_hashrate as f64 / global_hashrate as f64) * 100.0;
- HumanNumber::from_f64_to_percent_6_point(f)
- }
- }
+ #[inline]
+ pub fn calculate_dominance(my_hashrate: u64, global_hashrate: u64) -> HumanNumber {
+ if global_hashrate == 0 {
+ HumanNumber::unknown()
+ } else {
+ let f = (my_hashrate as f64 / global_hashrate as f64) * 100.0;
+ HumanNumber::from_f64_to_percent_6_point(f)
+ }
+ }
- pub const fn calculate_tick_bar(&self) -> &'static str {
- // The stars are reduced by one because it takes a frame to render the stats.
- // We want 0 stars at the same time stats are rendered, so it looks a little off here.
- match self.tick {
- 1 => "[ ]",
- 2 => "[* ]",
- 3 => "[** ]",
- 4 => "[*** ]",
- 5 => "[**** ]",
- 6 => "[***** ]",
- 7 => "[****** ]",
- 8 => "[******* ]",
- 9 => "[******** ]",
- 10 => "[********* ]",
- 11 => "[********** ]",
- 12 => "[*********** ]",
- 13 => "[************ ]",
- 14 => "[************* ]",
- 15 => "[************** ]",
- 16 => "[*************** ]",
- 17 => "[**************** ]",
- 18 => "[***************** ]",
- 19 => "[****************** ]",
- 20 => "[******************* ]",
- 21 => "[******************** ]",
- 22 => "[********************* ]",
- 23 => "[********************** ]",
- 24 => "[*********************** ]",
- 25 => "[************************ ]",
- 26 => "[************************* ]",
- 27 => "[************************** ]",
- 28 => "[*************************** ]",
- 29 => "[**************************** ]",
- 30 => "[***************************** ]",
- 31 => "[****************************** ]",
- 32 => "[******************************* ]",
- 33 => "[******************************** ]",
- 34 => "[********************************* ]",
- 35 => "[********************************** ]",
- 36 => "[*********************************** ]",
- 37 => "[************************************ ]",
- 38 => "[************************************* ]",
- 39 => "[************************************** ]",
- 40 => "[*************************************** ]",
- 41 => "[**************************************** ]",
- 42 => "[***************************************** ]",
- 43 => "[****************************************** ]",
- 44 => "[******************************************* ]",
- 45 => "[******************************************** ]",
- 46 => "[********************************************* ]",
- 47 => "[********************************************** ]",
- 48 => "[*********************************************** ]",
- 49 => "[************************************************ ]",
- 50 => "[************************************************* ]",
- 51 => "[************************************************** ]",
- 52 => "[*************************************************** ]",
- 53 => "[**************************************************** ]",
- 54 => "[***************************************************** ]",
- 55 => "[****************************************************** ]",
- 56 => "[******************************************************* ]",
- 57 => "[******************************************************** ]",
- 58 => "[********************************************************* ]",
- 59 => "[********************************************************** ]",
- 60 => "[*********************************************************** ]",
- _ => "[************************************************************]",
- }
- }
+ pub const fn calculate_tick_bar(&self) -> &'static str {
+ // The stars are reduced by one because it takes a frame to render the stats.
+ // We want 0 stars at the same time stats are rendered, so it looks a little off here.
+ match self.tick {
+ 1 => "[ ]",
+ 2 => "[* ]",
+ 3 => "[** ]",
+ 4 => "[*** ]",
+ 5 => "[**** ]",
+ 6 => "[***** ]",
+ 7 => "[****** ]",
+ 8 => "[******* ]",
+ 9 => "[******** ]",
+ 10 => "[********* ]",
+ 11 => "[********** ]",
+ 12 => "[*********** ]",
+ 13 => "[************ ]",
+ 14 => "[************* ]",
+ 15 => "[************** ]",
+ 16 => "[*************** ]",
+ 17 => "[**************** ]",
+ 18 => "[***************** ]",
+ 19 => "[****************** ]",
+ 20 => "[******************* ]",
+ 21 => "[******************** ]",
+ 22 => "[********************* ]",
+ 23 => "[********************** ]",
+ 24 => "[*********************** ]",
+ 25 => "[************************ ]",
+ 26 => "[************************* ]",
+ 27 => "[************************** ]",
+ 28 => "[*************************** ]",
+ 29 => "[**************************** ]",
+ 30 => "[***************************** ]",
+ 31 => "[****************************** ]",
+ 32 => "[******************************* ]",
+ 33 => "[******************************** ]",
+ 34 => "[********************************* ]",
+ 35 => "[********************************** ]",
+ 36 => "[*********************************** ]",
+ 37 => "[************************************ ]",
+ 38 => "[************************************* ]",
+ 39 => "[************************************** ]",
+ 40 => "[*************************************** ]",
+ 41 => "[**************************************** ]",
+ 42 => "[***************************************** ]",
+ 43 => "[****************************************** ]",
+ 44 => "[******************************************* ]",
+ 45 => "[******************************************** ]",
+ 46 => "[********************************************* ]",
+ 47 => "[********************************************** ]",
+ 48 => "[*********************************************** ]",
+ 49 => "[************************************************ ]",
+ 50 => "[************************************************* ]",
+ 51 => "[************************************************** ]",
+ 52 => "[*************************************************** ]",
+ 53 => "[**************************************************** ]",
+ 54 => "[***************************************************** ]",
+ 55 => "[****************************************************** ]",
+ 56 => "[******************************************************* ]",
+ 57 => "[******************************************************** ]",
+ 58 => "[********************************************************* ]",
+ 59 => "[********************************************************** ]",
+ 60 => "[*********************************************************** ]",
+ _ => "[************************************************************]",
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- Private P2Pool "Local" Api
@@ -1752,226 +2157,260 @@ impl PubP2poolApi {
// P2Pool seems to initialize all stats at 0 (or 0.0), so no [Option] wrapper seems needed.
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
struct PrivP2poolLocalApi {
- hashrate_15m: u64,
- hashrate_1h: u64,
- hashrate_24h: u64,
- shares_found: u64,
- average_effort: f32,
- current_effort: f32,
- connections: u32, // This is a `uint32_t` in `p2pool`
+ hashrate_15m: u64,
+ hashrate_1h: u64,
+ hashrate_24h: u64,
+ shares_found: u64,
+ average_effort: f32,
+ current_effort: f32,
+ connections: u32, // This is a `uint32_t` in `p2pool`
}
-impl Default for PrivP2poolLocalApi { fn default() -> Self { Self::new() } }
+impl Default for PrivP2poolLocalApi {
+ fn default() -> Self {
+ Self::new()
+ }
+}
impl PrivP2poolLocalApi {
- fn new() -> Self {
- Self {
- hashrate_15m: 0,
- hashrate_1h: 0,
- hashrate_24h: 0,
- shares_found: 0,
- average_effort: 0.0,
- current_effort: 0.0,
- connections: 0,
- }
- }
+ fn new() -> Self {
+ Self {
+ hashrate_15m: 0,
+ hashrate_1h: 0,
+ hashrate_24h: 0,
+ shares_found: 0,
+ average_effort: 0.0,
+ current_effort: 0.0,
+ connections: 0,
+ }
+ }
- // Deserialize the above [String] into a [PrivP2poolApi]
- fn from_str(string: &str) -> std::result::Result {
- match serde_json::from_str::(string) {
- Ok(a) => Ok(a),
- Err(e) => { warn!("P2Pool Local API | Could not deserialize API data: {}", e); Err(e) },
- }
- }
+ // Deserialize the above [String] into a [PrivP2poolApi]
+ fn from_str(string: &str) -> std::result::Result {
+ match serde_json::from_str::(string) {
+ Ok(a) => Ok(a),
+ Err(e) => {
+ warn!("P2Pool Local API | Could not deserialize API data: {}", e);
+ Err(e)
+ }
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- Private P2Pool "Network" API
// This matches P2Pool's [network/stats] JSON API file.
#[derive(Debug, Serialize, Deserialize, Clone)]
struct PrivP2poolNetworkApi {
- difficulty: u64,
- hash: String,
- height: u32,
- reward: u64,
- timestamp: u32,
+ difficulty: u64,
+ hash: String,
+ height: u32,
+ reward: u64,
+ timestamp: u32,
}
-impl Default for PrivP2poolNetworkApi { fn default() -> Self { Self::new() } }
+impl Default for PrivP2poolNetworkApi {
+ fn default() -> Self {
+ Self::new()
+ }
+}
impl PrivP2poolNetworkApi {
- fn new() -> Self {
- Self {
- difficulty: 0,
- hash: String::from("???"),
- height: 0,
- reward: 0,
- timestamp: 0,
- }
- }
+ fn new() -> Self {
+ Self {
+ difficulty: 0,
+ hash: String::from("???"),
+ height: 0,
+ reward: 0,
+ timestamp: 0,
+ }
+ }
- fn from_str(string: &str) -> std::result::Result {
- match serde_json::from_str::(string) {
- Ok(a) => Ok(a),
- Err(e) => { warn!("P2Pool Network API | Could not deserialize API data: {}", e); Err(e) },
- }
- }
+ fn from_str(string: &str) -> std::result::Result {
+ match serde_json::from_str::(string) {
+ Ok(a) => Ok(a),
+ Err(e) => {
+ warn!("P2Pool Network API | Could not deserialize API data: {}", e);
+ Err(e)
+ }
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- Private P2Pool "Pool" API
// This matches P2Pool's [pool/stats] JSON API file.
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
struct PrivP2poolPoolApi {
- pool_statistics: PoolStatistics,
+ pool_statistics: PoolStatistics,
}
-impl Default for PrivP2poolPoolApi { fn default() -> Self { Self::new() } }
+impl Default for PrivP2poolPoolApi {
+ fn default() -> Self {
+ Self::new()
+ }
+}
impl PrivP2poolPoolApi {
- fn new() -> Self {
- Self {
- pool_statistics: PoolStatistics::new(),
- }
- }
+ fn new() -> Self {
+ Self {
+ pool_statistics: PoolStatistics::new(),
+ }
+ }
- fn from_str(string: &str) -> std::result::Result {
- match serde_json::from_str::(string) {
- Ok(a) => Ok(a),
- Err(e) => { warn!("P2Pool Pool API | Could not deserialize API data: {}", e); Err(e) },
- }
- }
+ fn from_str(string: &str) -> std::result::Result {
+ match serde_json::from_str::(string) {
+ Ok(a) => Ok(a),
+ Err(e) => {
+ warn!("P2Pool Pool API | Could not deserialize API data: {}", e);
+ Err(e)
+ }
+ }
+ }
}
#[allow(non_snake_case)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
struct PoolStatistics {
- hashRate: u64,
- miners: u32,
+ hashRate: u64,
+ miners: u32,
+}
+impl Default for PoolStatistics {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+impl PoolStatistics {
+ fn new() -> Self {
+ Self {
+ hashRate: 0,
+ miners: 0,
+ }
+ }
}
-impl Default for PoolStatistics { fn default() -> Self { Self::new() } }
-impl PoolStatistics { fn new() -> Self { Self { hashRate: 0, miners: 0 } } }
//---------------------------------------------------------------------------------------------------- [ImgXmrig]
#[derive(Debug, Clone)]
pub struct ImgXmrig {
- pub threads: String,
- pub url: String,
+ pub threads: String,
+ pub url: String,
}
impl Default for ImgXmrig {
- fn default() -> Self {
- Self::new()
- }
+ fn default() -> Self {
+ Self::new()
+ }
}
impl ImgXmrig {
- pub fn new() -> Self {
- Self {
- threads: "???".to_string(),
- url: "???".to_string(),
- }
- }
+ pub fn new() -> Self {
+ Self {
+ threads: "???".to_string(),
+ url: "???".to_string(),
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- Public XMRig API
#[derive(Debug, Clone)]
pub struct PubXmrigApi {
- pub output: String,
- pub uptime: HumanTime,
- pub worker_id: String,
- pub resources: HumanNumber,
- pub hashrate: HumanNumber,
- pub diff: HumanNumber,
- pub accepted: HumanNumber,
- pub rejected: HumanNumber,
+ pub output: String,
+ pub uptime: HumanTime,
+ pub worker_id: String,
+ pub resources: HumanNumber,
+ pub hashrate: HumanNumber,
+ pub diff: HumanNumber,
+ pub accepted: HumanNumber,
+ pub rejected: HumanNumber,
- pub hashrate_raw: f32,
+ pub hashrate_raw: f32,
}
impl Default for PubXmrigApi {
- fn default() -> Self {
- Self::new()
- }
+ fn default() -> Self {
+ Self::new()
+ }
}
impl PubXmrigApi {
- pub fn new() -> Self {
- Self {
- output: String::new(),
- uptime: HumanTime::new(),
- worker_id: "???".to_string(),
- resources: HumanNumber::unknown(),
- hashrate: HumanNumber::unknown(),
- diff: HumanNumber::unknown(),
- accepted: HumanNumber::unknown(),
- rejected: HumanNumber::unknown(),
- hashrate_raw: 0.0,
- }
- }
+ pub fn new() -> Self {
+ Self {
+ output: String::new(),
+ uptime: HumanTime::new(),
+ worker_id: "???".to_string(),
+ resources: HumanNumber::unknown(),
+ hashrate: HumanNumber::unknown(),
+ diff: HumanNumber::unknown(),
+ accepted: HumanNumber::unknown(),
+ rejected: HumanNumber::unknown(),
+ hashrate_raw: 0.0,
+ }
+ }
- #[inline]
- fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) {
- let output = std::mem::take(&mut gui_api.output);
- let buf = std::mem::take(&mut pub_api.output);
- *gui_api = Self {
- output,
- ..std::mem::take(pub_api)
- };
- if !buf.is_empty() { gui_api.output.push_str(&buf); }
- }
+ #[inline]
+ fn combine_gui_pub_api(gui_api: &mut Self, pub_api: &mut Self) {
+ let output = std::mem::take(&mut gui_api.output);
+ let buf = std::mem::take(&mut pub_api.output);
+ *gui_api = Self {
+ output,
+ ..std::mem::take(pub_api)
+ };
+ if !buf.is_empty() {
+ gui_api.output.push_str(&buf);
+ }
+ }
- // This combines the buffer from the PTY thread [output_pub]
- // with the actual [PubApiXmrig] output field.
- 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);
+ // This combines the buffer from the PTY thread [output_pub]
+ // with the actual [PubApiXmrig] output field.
+ 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 = HumanTime::into_human(elapsed);
- }
+ {
+ let mut public = lock!(public);
+ if !output_pub.is_empty() {
+ public.output.push_str(&std::mem::take(&mut *output_pub));
+ }
+ // Update uptime
+ public.uptime = HumanTime::into_human(elapsed);
+ }
- // 2. Check for "new job"/"no active...".
- let mut output_parse = lock!(output_parse);
- if XMRIG_REGEX.new_job.is_match(&output_parse) {
- lock!(process).state = ProcessState::Alive;
- } else if XMRIG_REGEX.not_mining.is_match(&output_parse) {
- lock!(process).state = ProcessState::NotMining;
- }
+ // 2. Check for "new job"/"no active...".
+ let mut output_parse = lock!(output_parse);
+ if XMRIG_REGEX.new_job.is_match(&output_parse) {
+ lock!(process).state = ProcessState::Alive;
+ } else if XMRIG_REGEX.not_mining.is_match(&output_parse) {
+ lock!(process).state = ProcessState::NotMining;
+ }
- // 3. Throw away [output_parse]
- output_parse.clear();
- drop(output_parse);
- }
+ // 3. Throw away [output_parse]
+ output_parse.clear();
+ drop(output_parse);
+ }
- // Formats raw private data into ready-to-print human readable version.
- fn update_from_priv(public: &Arc>, private: PrivXmrigApi) {
- let mut public = lock!(public);
- let hashrate_raw = match private.hashrate.total.get(0) {
- Some(Some(h)) => *h,
- _ => 0.0,
- };
+ // Formats raw private data into ready-to-print human readable version.
+ fn update_from_priv(public: &Arc>, private: PrivXmrigApi) {
+ let mut public = lock!(public);
+ let hashrate_raw = match private.hashrate.total.get(0) {
+ Some(Some(h)) => *h,
+ _ => 0.0,
+ };
- *public = Self {
- worker_id: private.worker_id,
- resources: HumanNumber::from_load(private.resources.load_average),
- hashrate: HumanNumber::from_hashrate(private.hashrate.total),
- diff: HumanNumber::from_u128(private.connection.diff),
- accepted: HumanNumber::from_u128(private.connection.accepted),
- rejected: HumanNumber::from_u128(private.connection.rejected),
- hashrate_raw,
- ..std::mem::take(&mut *public)
- }
- }
+ *public = Self {
+ worker_id: private.worker_id,
+ resources: HumanNumber::from_load(private.resources.load_average),
+ hashrate: HumanNumber::from_hashrate(private.hashrate.total),
+ diff: HumanNumber::from_u128(private.connection.diff),
+ accepted: HumanNumber::from_u128(private.connection.accepted),
+ rejected: HumanNumber::from_u128(private.connection.rejected),
+ hashrate_raw,
+ ..std::mem::take(&mut *public)
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- Private XMRig API
@@ -1981,307 +2420,341 @@ impl PubXmrigApi {
// which means some elements need to be wrapped in an [Option] or else serde will [panic!].
#[derive(Debug, Serialize, Deserialize, Clone)]
struct PrivXmrigApi {
- worker_id: String,
- resources: Resources,
- connection: Connection,
- hashrate: Hashrate,
+ worker_id: String,
+ resources: Resources,
+ connection: Connection,
+ hashrate: Hashrate,
}
impl PrivXmrigApi {
- fn new() -> Self {
- Self {
- worker_id: String::new(),
- resources: Resources::new(),
- connection: Connection::new(),
- hashrate: Hashrate::new(),
- }
- }
+ fn new() -> Self {
+ Self {
+ worker_id: String::new(),
+ resources: Resources::new(),
+ connection: Connection::new(),
+ hashrate: Hashrate::new(),
+ }
+ }
- #[inline]
- // Send an HTTP request to XMRig's API, serialize it into [Self] and return it
- async fn request_xmrig_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_millis(500), client.request(request)).await?;
- let body = hyper::body::to_bytes(response?.body_mut()).await?;
- Ok(serde_json::from_slice::(&body)?)
- }
+ #[inline]
+ // Send an HTTP request to XMRig's API, serialize it into [Self] and return it
+ async fn request_xmrig_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_millis(500),
+ client.request(request),
+ )
+ .await?;
+ let body = hyper::body::to_bytes(response?.body_mut()).await?;
+ Ok(serde_json::from_slice::(&body)?)
+ }
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
struct Resources {
- load_average: [Option; 3],
+ load_average: [Option; 3],
}
impl Resources {
- fn new() -> Self {
- Self {
- load_average: [Some(0.0), Some(0.0), Some(0.0)],
- }
- }
+ fn new() -> Self {
+ Self {
+ load_average: [Some(0.0), Some(0.0), Some(0.0)],
+ }
+ }
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Connection {
- diff: u128,
- accepted: u128,
- rejected: u128,
+ diff: u128,
+ accepted: u128,
+ rejected: u128,
}
impl Connection {
- fn new() -> Self {
- Self {
- diff: 0,
- accepted: 0,
- rejected: 0,
- }
- }
+ fn new() -> Self {
+ Self {
+ diff: 0,
+ accepted: 0,
+ rejected: 0,
+ }
+ }
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
struct Hashrate {
- total: [Option; 3],
+ total: [Option; 3],
}
impl Hashrate {
- fn new() -> Self {
- Self {
- total: [Some(0.0), Some(0.0), Some(0.0)],
- }
- }
+ fn new() -> Self {
+ Self {
+ total: [Some(0.0), Some(0.0), Some(0.0)],
+ }
+ }
}
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
- use super::*;
+ use super::*;
- #[test]
- fn reset_gui_output() {
- let max = crate::helper::GUI_OUTPUT_LEEWAY;
- let mut string = String::with_capacity(max);
- for _ in 0..=max {
- string.push('0');
- }
- crate::Helper::check_reset_gui_output(&mut string, crate::ProcessName::P2pool);
- // Some text gets added, so just check for less than 500 bytes.
- assert!(string.len() < 500);
- }
+ #[test]
+ fn reset_gui_output() {
+ let max = crate::helper::GUI_OUTPUT_LEEWAY;
+ let mut string = String::with_capacity(max);
+ for _ in 0..=max {
+ string.push('0');
+ }
+ crate::Helper::check_reset_gui_output(&mut string, crate::ProcessName::P2pool);
+ // Some text gets added, so just check for less than 500 bytes.
+ assert!(string.len() < 500);
+ }
- #[test]
- fn combine_gui_pub_p2pool_api() {
- use crate::helper::PubP2poolApi;
- let mut gui_api = PubP2poolApi::new();
- let mut pub_api = PubP2poolApi::new();
- pub_api.payouts = 1;
- pub_api.payouts_hour = 2.0;
- pub_api.payouts_day = 3.0;
- pub_api.payouts_month = 4.0;
- pub_api.xmr = 1.0;
- pub_api.xmr_hour = 2.0;
- pub_api.xmr_day = 3.0;
- pub_api.xmr_month = 4.0;
- println!("BEFORE - GUI_API: {:#?}\nPUB_API: {:#?}", gui_api, pub_api);
- assert_ne!(gui_api, pub_api);
- PubP2poolApi::combine_gui_pub_api(&mut gui_api, &mut pub_api);
- println!("AFTER - GUI_API: {:#?}\nPUB_API: {:#?}", gui_api, pub_api);
- assert_eq!(gui_api, pub_api);
- pub_api.xmr = 2.0;
- PubP2poolApi::combine_gui_pub_api(&mut gui_api, &mut pub_api);
- assert_eq!(gui_api, pub_api);
- assert_eq!(gui_api.xmr, 2.0);
- assert_eq!(pub_api.xmr, 2.0);
- }
+ #[test]
+ fn combine_gui_pub_p2pool_api() {
+ use crate::helper::PubP2poolApi;
+ let mut gui_api = PubP2poolApi::new();
+ let mut pub_api = PubP2poolApi::new();
+ pub_api.payouts = 1;
+ pub_api.payouts_hour = 2.0;
+ pub_api.payouts_day = 3.0;
+ pub_api.payouts_month = 4.0;
+ pub_api.xmr = 1.0;
+ pub_api.xmr_hour = 2.0;
+ pub_api.xmr_day = 3.0;
+ pub_api.xmr_month = 4.0;
+ println!("BEFORE - GUI_API: {:#?}\nPUB_API: {:#?}", gui_api, pub_api);
+ assert_ne!(gui_api, pub_api);
+ PubP2poolApi::combine_gui_pub_api(&mut gui_api, &mut pub_api);
+ println!("AFTER - GUI_API: {:#?}\nPUB_API: {:#?}", gui_api, pub_api);
+ assert_eq!(gui_api, pub_api);
+ pub_api.xmr = 2.0;
+ PubP2poolApi::combine_gui_pub_api(&mut gui_api, &mut pub_api);
+ assert_eq!(gui_api, pub_api);
+ assert_eq!(gui_api.xmr, 2.0);
+ assert_eq!(pub_api.xmr, 2.0);
+ }
- #[test]
- fn calc_payouts_and_xmr_from_output_p2pool() {
- use crate::helper::{PubP2poolApi};
- use std::sync::{Arc,Mutex};
- let public = Arc::new(Mutex::new(PubP2poolApi::new()));
- let output_parse = Arc::new(Mutex::new(String::from(
- r#"payout of 5.000000000001 XMR in block 1111
+ #[test]
+ fn calc_payouts_and_xmr_from_output_p2pool() {
+ use crate::helper::PubP2poolApi;
+ use std::sync::{Arc, Mutex};
+ let public = Arc::new(Mutex::new(PubP2poolApi::new()));
+ let output_parse = Arc::new(Mutex::new(String::from(
+ r#"payout of 5.000000000001 XMR in block 1111
payout of 5.000000000001 XMR in block 1112
- payout of 5.000000000001 XMR in block 1113"#
- )));
- let output_pub = Arc::new(Mutex::new(String::new()));
- let elapsed = std::time::Duration::from_secs(60);
- let process = Arc::new(Mutex::new(Process::new(ProcessName::P2pool, "".to_string(), PathBuf::new())));
- PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
- let public = public.lock().unwrap();
- println!("{:#?}", public);
- assert_eq!(public.payouts, 3);
- assert_eq!(public.payouts_hour, 180.0);
- assert_eq!(public.payouts_day, 4320.0);
- assert_eq!(public.payouts_month, 129600.0);
- assert_eq!(public.xmr, 15.000000000003);
- assert_eq!(public.xmr_hour, 900.00000000018);
- assert_eq!(public.xmr_day, 21600.00000000432);
- assert_eq!(public.xmr_month, 648000.0000001296);
- }
+ payout of 5.000000000001 XMR in block 1113"#,
+ )));
+ let output_pub = Arc::new(Mutex::new(String::new()));
+ let elapsed = std::time::Duration::from_secs(60);
+ let process = Arc::new(Mutex::new(Process::new(
+ ProcessName::P2pool,
+ "".to_string(),
+ PathBuf::new(),
+ )));
+ PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
+ let public = public.lock().unwrap();
+ println!("{:#?}", public);
+ assert_eq!(public.payouts, 3);
+ assert_eq!(public.payouts_hour, 180.0);
+ assert_eq!(public.payouts_day, 4320.0);
+ assert_eq!(public.payouts_month, 129600.0);
+ assert_eq!(public.xmr, 15.000000000003);
+ assert_eq!(public.xmr_hour, 900.00000000018);
+ assert_eq!(public.xmr_day, 21600.00000000432);
+ assert_eq!(public.xmr_month, 648000.0000001296);
+ }
- #[test]
- fn set_p2pool_synchronized() {
- use crate::helper::{PubP2poolApi};
- use std::sync::{Arc,Mutex};
- let public = Arc::new(Mutex::new(PubP2poolApi::new()));
- let output_parse = Arc::new(Mutex::new(String::from(
- r#"payout of 5.000000000001 XMR in block 1111
+ #[test]
+ fn set_p2pool_synchronized() {
+ use crate::helper::PubP2poolApi;
+ use std::sync::{Arc, Mutex};
+ let public = Arc::new(Mutex::new(PubP2poolApi::new()));
+ let output_parse = Arc::new(Mutex::new(String::from(
+ r#"payout of 5.000000000001 XMR in block 1111
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
- payout of 5.000000000001 XMR in block 1113"#
- )));
- let output_pub = Arc::new(Mutex::new(String::new()));
- let elapsed = std::time::Duration::from_secs(60);
- let process = Arc::new(Mutex::new(Process::new(ProcessName::P2pool, "".to_string(), PathBuf::new())));
+ payout of 5.000000000001 XMR in block 1113"#,
+ )));
+ let output_pub = Arc::new(Mutex::new(String::new()));
+ let elapsed = std::time::Duration::from_secs(60);
+ let process = Arc::new(Mutex::new(Process::new(
+ ProcessName::P2pool,
+ "".to_string(),
+ PathBuf::new(),
+ )));
- // It only gets checked if we're `Syncing`.
- process.lock().unwrap().state = ProcessState::Syncing;
- PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
- println!("{:#?}", process);
- assert!(process.lock().unwrap().state == ProcessState::Alive);
- }
+ // It only gets checked if we're `Syncing`.
+ process.lock().unwrap().state = ProcessState::Syncing;
+ PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
+ println!("{:#?}", process);
+ assert!(process.lock().unwrap().state == ProcessState::Alive);
+ }
- #[test]
- fn p2pool_synchronized_false_positive() {
- use crate::helper::{PubP2poolApi};
- use std::sync::{Arc,Mutex};
- let public = Arc::new(Mutex::new(PubP2poolApi::new()));
+ #[test]
+ fn p2pool_synchronized_false_positive() {
+ use crate::helper::PubP2poolApi;
+ use std::sync::{Arc, Mutex};
+ let public = Arc::new(Mutex::new(PubP2poolApi::new()));
- // The SideChain that is "SYNCHRONIZED" in this output is
- // probably not main/mini, but the sidechain started on height 1,
- // so this should _not_ trigger alive state.
- let output_parse = Arc::new(Mutex::new(String::from(
- r#"payout of 5.000000000001 XMR in block 1111
+ // The SideChain that is "SYNCHRONIZED" in this output is
+ // probably not main/mini, but the sidechain started on height 1,
+ // so this should _not_ trigger alive state.
+ let output_parse = Arc::new(Mutex::new(String::from(
+ r#"payout of 5.000000000001 XMR in block 1111
SideChain new chain tip: next height = 1
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
- payout of 5.000000000001 XMR in block 1113"#
- )));
- let output_pub = Arc::new(Mutex::new(String::new()));
- let elapsed = std::time::Duration::from_secs(60);
- let process = Arc::new(Mutex::new(Process::new(ProcessName::P2pool, "".to_string(), PathBuf::new())));
+ payout of 5.000000000001 XMR in block 1113"#,
+ )));
+ let output_pub = Arc::new(Mutex::new(String::new()));
+ let elapsed = std::time::Duration::from_secs(60);
+ let process = Arc::new(Mutex::new(Process::new(
+ ProcessName::P2pool,
+ "".to_string(),
+ PathBuf::new(),
+ )));
- // It only gets checked if we're `Syncing`.
- process.lock().unwrap().state = ProcessState::Syncing;
- PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
- println!("{:#?}", process);
- assert!(process.lock().unwrap().state == ProcessState::Syncing); // still syncing
- }
+ // It only gets checked if we're `Syncing`.
+ process.lock().unwrap().state = ProcessState::Syncing;
+ PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
+ println!("{:#?}", process);
+ assert!(process.lock().unwrap().state == ProcessState::Syncing); // still syncing
+ }
- #[test]
- fn p2pool_synchronized_double_synchronized() {
- use crate::helper::{PubP2poolApi};
- use std::sync::{Arc,Mutex};
- let public = Arc::new(Mutex::new(PubP2poolApi::new()));
+ #[test]
+ fn p2pool_synchronized_double_synchronized() {
+ use crate::helper::PubP2poolApi;
+ use std::sync::{Arc, Mutex};
+ let public = Arc::new(Mutex::new(PubP2poolApi::new()));
- // The 1st SideChain that is "SYNCHRONIZED" in this output is
- // the sidechain started on height 1, but there is another one
- // which means the real main/mini is probably synced,
- // so this _should_ trigger alive state.
- let output_parse = Arc::new(Mutex::new(String::from(
- r#"payout of 5.000000000001 XMR in block 1111
+ // The 1st SideChain that is "SYNCHRONIZED" in this output is
+ // the sidechain started on height 1, but there is another one
+ // which means the real main/mini is probably synced,
+ // so this _should_ trigger alive state.
+ let output_parse = Arc::new(Mutex::new(String::from(
+ r#"payout of 5.000000000001 XMR in block 1111
SideChain new chain tip: next height = 1
NOTICE 2021-12-27 21:42:17.2008 SideChain SYNCHRONIZED
payout of 5.000000000001 XMR in block 1113
- NOTICE 2021-12-27 21:42:17.2100 SideChain SYNCHRONIZED"#
- )));
- let output_pub = Arc::new(Mutex::new(String::new()));
- let elapsed = std::time::Duration::from_secs(60);
- let process = Arc::new(Mutex::new(Process::new(ProcessName::P2pool, "".to_string(), PathBuf::new())));
+ NOTICE 2021-12-27 21:42:17.2100 SideChain SYNCHRONIZED"#,
+ )));
+ let output_pub = Arc::new(Mutex::new(String::new()));
+ let elapsed = std::time::Duration::from_secs(60);
+ let process = Arc::new(Mutex::new(Process::new(
+ ProcessName::P2pool,
+ "".to_string(),
+ PathBuf::new(),
+ )));
- // It only gets checked if we're `Syncing`.
- process.lock().unwrap().state = ProcessState::Syncing;
- PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
- println!("{:#?}", process);
- assert!(process.lock().unwrap().state == ProcessState::Alive);
- }
+ // It only gets checked if we're `Syncing`.
+ process.lock().unwrap().state = ProcessState::Syncing;
+ PubP2poolApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
+ println!("{:#?}", process);
+ assert!(process.lock().unwrap().state == ProcessState::Alive);
+ }
- #[test]
- fn update_pub_p2pool_from_local_network_pool() {
- use std::sync::{Arc,Mutex};
- use crate::helper::PubP2poolApi;
- use crate::helper::PrivP2poolLocalApi;
- use crate::helper::PrivP2poolNetworkApi;
- use crate::helper::PrivP2poolPoolApi;
- use crate::helper::PoolStatistics;
- let public = Arc::new(Mutex::new(PubP2poolApi::new()));
- let local = PrivP2poolLocalApi {
- hashrate_15m: 10_000,
- hashrate_1h: 20_000,
- hashrate_24h: 30_000,
- shares_found: 1000,
- average_effort: 100.000,
- current_effort: 200.000,
- connections: 1234,
- };
- let network = PrivP2poolNetworkApi {
- difficulty: 300_000_000_000,
- hash: "asdf".to_string(),
- height: 1234,
- reward: 2345,
- timestamp: 3456,
- };
- let pool = PrivP2poolPoolApi {
- pool_statistics: PoolStatistics {
- hashRate: 1_000_000, // 1 MH/s
- miners: 1_000,
- }
- };
- // Update Local
- PubP2poolApi::update_from_local(&public, local);
- let p = public.lock().unwrap();
- println!("AFTER LOCAL: {:#?}", p);
- assert_eq!(p.hashrate_15m.to_string(), "10,000");
- assert_eq!(p.hashrate_1h.to_string(), "20,000");
- assert_eq!(p.hashrate_24h.to_string(), "30,000");
- assert_eq!(p.shares_found.to_string(), "1,000");
- assert_eq!(p.average_effort.to_string(), "100.00%");
- assert_eq!(p.current_effort.to_string(), "200.00%");
- assert_eq!(p.connections.to_string(), "1,234");
- assert_eq!(p.user_p2pool_hashrate_u64, 20000);
- drop(p);
- // Update Network + Pool
- PubP2poolApi::update_from_network_pool(&public, network, pool);
- let p = public.lock().unwrap();
- println!("AFTER NETWORK+POOL: {:#?}", p);
- assert_eq!(p.monero_difficulty.to_string(), "300,000,000,000");
- assert_eq!(p.monero_hashrate.to_string(), "2.500 GH/s");
- assert_eq!(p.hash.to_string(), "asdf");
- assert_eq!(p.height.to_string(), "1,234");
- assert_eq!(p.reward.to_u64(), 2345);
- assert_eq!(p.p2pool_difficulty.to_string(), "10,000,000");
- assert_eq!(p.p2pool_hashrate.to_string(), "1.000 MH/s");
- assert_eq!(p.miners.to_string(), "1,000");
- assert_eq!(p.solo_block_mean.to_string(), "5 months, 21 days, 9 hours, 52 minutes");
- assert_eq!(p.p2pool_block_mean.to_string(), "3 days, 11 hours, 20 minutes");
- assert_eq!(p.p2pool_share_mean.to_string(), "8 minutes, 20 seconds");
- assert_eq!(p.p2pool_percent.to_string(), "0.040000%");
- assert_eq!(p.user_p2pool_percent.to_string(), "2.000000%");
- assert_eq!(p.user_monero_percent.to_string(), "0.000800%");
- drop(p);
- }
+ #[test]
+ fn update_pub_p2pool_from_local_network_pool() {
+ use crate::helper::PoolStatistics;
+ use crate::helper::PrivP2poolLocalApi;
+ use crate::helper::PrivP2poolNetworkApi;
+ use crate::helper::PrivP2poolPoolApi;
+ use crate::helper::PubP2poolApi;
+ use std::sync::{Arc, Mutex};
+ let public = Arc::new(Mutex::new(PubP2poolApi::new()));
+ let local = PrivP2poolLocalApi {
+ hashrate_15m: 10_000,
+ hashrate_1h: 20_000,
+ hashrate_24h: 30_000,
+ shares_found: 1000,
+ average_effort: 100.000,
+ current_effort: 200.000,
+ connections: 1234,
+ };
+ let network = PrivP2poolNetworkApi {
+ difficulty: 300_000_000_000,
+ hash: "asdf".to_string(),
+ height: 1234,
+ reward: 2345,
+ timestamp: 3456,
+ };
+ let pool = PrivP2poolPoolApi {
+ pool_statistics: PoolStatistics {
+ hashRate: 1_000_000, // 1 MH/s
+ miners: 1_000,
+ },
+ };
+ // Update Local
+ PubP2poolApi::update_from_local(&public, local);
+ let p = public.lock().unwrap();
+ println!("AFTER LOCAL: {:#?}", p);
+ assert_eq!(p.hashrate_15m.to_string(), "10,000");
+ assert_eq!(p.hashrate_1h.to_string(), "20,000");
+ assert_eq!(p.hashrate_24h.to_string(), "30,000");
+ assert_eq!(p.shares_found.to_string(), "1,000");
+ assert_eq!(p.average_effort.to_string(), "100.00%");
+ assert_eq!(p.current_effort.to_string(), "200.00%");
+ assert_eq!(p.connections.to_string(), "1,234");
+ assert_eq!(p.user_p2pool_hashrate_u64, 20000);
+ drop(p);
+ // Update Network + Pool
+ PubP2poolApi::update_from_network_pool(&public, network, pool);
+ let p = public.lock().unwrap();
+ println!("AFTER NETWORK+POOL: {:#?}", p);
+ assert_eq!(p.monero_difficulty.to_string(), "300,000,000,000");
+ assert_eq!(p.monero_hashrate.to_string(), "2.500 GH/s");
+ assert_eq!(p.hash.to_string(), "asdf");
+ assert_eq!(p.height.to_string(), "1,234");
+ assert_eq!(p.reward.to_u64(), 2345);
+ assert_eq!(p.p2pool_difficulty.to_string(), "10,000,000");
+ assert_eq!(p.p2pool_hashrate.to_string(), "1.000 MH/s");
+ assert_eq!(p.miners.to_string(), "1,000");
+ assert_eq!(
+ p.solo_block_mean.to_string(),
+ "5 months, 21 days, 9 hours, 52 minutes"
+ );
+ assert_eq!(
+ p.p2pool_block_mean.to_string(),
+ "3 days, 11 hours, 20 minutes"
+ );
+ assert_eq!(p.p2pool_share_mean.to_string(), "8 minutes, 20 seconds");
+ assert_eq!(p.p2pool_percent.to_string(), "0.040000%");
+ assert_eq!(p.user_p2pool_percent.to_string(), "2.000000%");
+ assert_eq!(p.user_monero_percent.to_string(), "0.000800%");
+ drop(p);
+ }
- #[test]
- fn set_xmrig_mining() {
- use crate::helper::PubXmrigApi;
- use std::sync::{Arc,Mutex};
- let public = Arc::new(Mutex::new(PubXmrigApi::new()));
- let output_parse = Arc::new(Mutex::new(String::from("[2022-02-12 12:49:30.311] net no active pools, stop mining")));
- let output_pub = Arc::new(Mutex::new(String::new()));
- let elapsed = std::time::Duration::from_secs(60);
- let process = Arc::new(Mutex::new(Process::new(ProcessName::Xmrig, "".to_string(), PathBuf::new())));
+ #[test]
+ fn set_xmrig_mining() {
+ use crate::helper::PubXmrigApi;
+ use std::sync::{Arc, Mutex};
+ let public = Arc::new(Mutex::new(PubXmrigApi::new()));
+ let output_parse = Arc::new(Mutex::new(String::from(
+ "[2022-02-12 12:49:30.311] net no active pools, stop mining",
+ )));
+ let output_pub = Arc::new(Mutex::new(String::new()));
+ let elapsed = std::time::Duration::from_secs(60);
+ let process = Arc::new(Mutex::new(Process::new(
+ ProcessName::Xmrig,
+ "".to_string(),
+ PathBuf::new(),
+ )));
- process.lock().unwrap().state = ProcessState::Alive;
- PubXmrigApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
- println!("{:#?}", process);
- assert!(process.lock().unwrap().state == ProcessState::NotMining);
+ process.lock().unwrap().state = ProcessState::Alive;
+ PubXmrigApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
+ println!("{:#?}", process);
+ assert!(process.lock().unwrap().state == ProcessState::NotMining);
- let output_parse = Arc::new(Mutex::new(String::from("[2022-02-12 12:49:30.311] net new job from 192.168.2.1:3333 diff 402K algo rx/0 height 2241142 (11 tx)")));
- PubXmrigApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
- assert!(process.lock().unwrap().state == ProcessState::Alive);
- }
+ let output_parse = Arc::new(Mutex::new(String::from("[2022-02-12 12:49:30.311] net new job from 192.168.2.1:3333 diff 402K algo rx/0 height 2241142 (11 tx)")));
+ PubXmrigApi::update_from_output(&public, &output_parse, &output_pub, elapsed, &process);
+ assert!(process.lock().unwrap().state == ProcessState::Alive);
+ }
- #[test]
- fn serde_priv_p2pool_local_api() {
- let data =
- r#"{
+ #[test]
+ fn serde_priv_p2pool_local_api() {
+ let data = r#"{
"hashrate_15m": 12,
"hashrate_1h": 11111,
"hashrate_24h": 468967,
@@ -2292,11 +2765,10 @@ mod test {
"connections": 123,
"incoming_connections": 96
}"#;
- let priv_api = crate::helper::PrivP2poolLocalApi::from_str(data).unwrap();
- let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
- println!("{}", json);
- let data_after_ser =
-r#"{
+ let priv_api = crate::helper::PrivP2poolLocalApi::from_str(data).unwrap();
+ let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
+ println!("{}", json);
+ let data_after_ser = r#"{
"hashrate_15m": 12,
"hashrate_1h": 11111,
"hashrate_24h": 468967,
@@ -2305,37 +2777,34 @@ r#"{
"current_effort": 129.297,
"connections": 123
}"#;
- assert_eq!(data_after_ser, json)
- }
+ assert_eq!(data_after_ser, json)
+ }
- #[test]
- fn serde_priv_p2pool_network_api() {
- let data =
- r#"{
+ #[test]
+ fn serde_priv_p2pool_network_api() {
+ let data = r#"{
"difficulty": 319028180924,
"hash": "22ae1b83d727bb2ff4efc17b485bc47bc8bf5e29a7b3af65baf42213ac70a39b",
"height": 2776576,
"reward": 600499860000,
"timestamp": 1670953659
}"#;
- let priv_api = crate::helper::PrivP2poolNetworkApi::from_str(data).unwrap();
- let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
- println!("{}", json);
- let data_after_ser =
-r#"{
+ let priv_api = crate::helper::PrivP2poolNetworkApi::from_str(data).unwrap();
+ let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
+ println!("{}", json);
+ let data_after_ser = r#"{
"difficulty": 319028180924,
"hash": "22ae1b83d727bb2ff4efc17b485bc47bc8bf5e29a7b3af65baf42213ac70a39b",
"height": 2776576,
"reward": 600499860000,
"timestamp": 1670953659
}"#;
- assert_eq!(data_after_ser, json)
- }
+ assert_eq!(data_after_ser, json)
+ }
- #[test]
- fn serde_priv_p2pool_pool_api() {
- let data =
- r#"{
+ #[test]
+ fn serde_priv_p2pool_pool_api() {
+ let data = r#"{
"pool_list": ["pplns"],
"pool_statistics": {
"hashRate": 10225772,
@@ -2346,23 +2815,21 @@ r#"{
"totalBlocksFound": 4
}
}"#;
- let priv_api = crate::helper::PrivP2poolPoolApi::from_str(data).unwrap();
- let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
- println!("{}", json);
- let data_after_ser =
-r#"{
+ let priv_api = crate::helper::PrivP2poolPoolApi::from_str(data).unwrap();
+ let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
+ println!("{}", json);
+ let data_after_ser = r#"{
"pool_statistics": {
"hashRate": 10225772,
"miners": 713
}
}"#;
- assert_eq!(data_after_ser, json)
- }
+ assert_eq!(data_after_ser, json)
+ }
- #[test]
- fn serde_priv_xmrig_api() {
- let data =
- r#"{
+ #[test]
+ fn serde_priv_xmrig_api() {
+ let data = r#"{
"id": "6226e3sd0cd1a6es",
"worker_id": "hinto",
"uptime": 123,
@@ -2443,12 +2910,11 @@ r#"{
},
"hugepages": true
}"#;
- use crate::helper::PrivXmrigApi;
- let priv_api = serde_json::from_str::(&data).unwrap();
- let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
- println!("{}", json);
- let data_after_ser =
-r#"{
+ use crate::helper::PrivXmrigApi;
+ let priv_api = serde_json::from_str::(&data).unwrap();
+ let json = serde_json::ser::to_string_pretty(&priv_api).unwrap();
+ println!("{}", json);
+ let data_after_ser = r#"{
"worker_id": "hinto",
"resources": {
"load_average": [
@@ -2470,6 +2936,6 @@ r#"{
]
}
}"#;
- assert_eq!(data_after_ser, json)
- }
+ assert_eq!(data_after_ser, json)
+ }
}
diff --git a/src/human.rs b/src/human.rs
index 118b6e2..9179de5 100644
--- a/src/human.rs
+++ b/src/human.rs
@@ -30,69 +30,74 @@ use std::time::Duration;
pub struct HumanTime(Duration);
impl Default for HumanTime {
- fn default() -> Self {
- Self::new()
- }
+ fn default() -> Self {
+ Self::new()
+ }
}
impl HumanTime {
- #[inline]
- pub const fn new() -> HumanTime {
- HumanTime(ZERO_SECONDS)
- }
+ #[inline]
+ pub const fn new() -> HumanTime {
+ HumanTime(ZERO_SECONDS)
+ }
- #[inline]
- pub const fn into_human(d: Duration) -> HumanTime {
- HumanTime(d)
- }
+ #[inline]
+ pub const fn into_human(d: Duration) -> HumanTime {
+ HumanTime(d)
+ }
- #[inline]
- pub const fn from_u64(u: u64) -> HumanTime {
- HumanTime(Duration::from_secs(u))
- }
+ #[inline]
+ pub const fn from_u64(u: u64) -> HumanTime {
+ HumanTime(Duration::from_secs(u))
+ }
- fn plural(f: &mut std::fmt::Formatter, started: &mut bool, name: &str, value: u64) -> std::fmt::Result {
- if value > 0 {
- if *started {
- f.write_str(", ")?;
- }
- write!(f, "{} {}", value, name)?;
- if value > 1 {
- f.write_str("s")?;
- }
- *started = true;
- }
- Ok(())
- }
+ fn plural(
+ f: &mut std::fmt::Formatter,
+ started: &mut bool,
+ name: &str,
+ value: u64,
+ ) -> std::fmt::Result {
+ if value > 0 {
+ if *started {
+ f.write_str(", ")?;
+ }
+ write!(f, "{} {}", value, name)?;
+ if value > 1 {
+ f.write_str("s")?;
+ }
+ *started = true;
+ }
+ Ok(())
+ }
}
impl std::fmt::Display for HumanTime {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- let secs = self.0.as_secs();
- if secs == 0 {
- f.write_str("0 seconds")?;
- return Ok(());
- }
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let secs = self.0.as_secs();
+ if secs == 0 {
+ f.write_str("0 seconds")?;
+ return Ok(());
+ }
- let years = secs / 31_557_600; // 365.25d
- let ydays = secs % 31_557_600;
- let months = ydays / 2_630_016; // 30.44d
- let mdays = ydays % 2_630_016;
- let days = mdays / 86400;
- let day_secs = mdays % 86400;
- let hours = day_secs / 3600;
- let minutes = day_secs % 3600 / 60;
- let seconds = day_secs % 60;
+ let years = secs / 31_557_600; // 365.25d
+ let ydays = secs % 31_557_600;
+ let months = ydays / 2_630_016; // 30.44d
+ let mdays = ydays % 2_630_016;
+ let days = mdays / 86400;
+ let day_secs = mdays % 86400;
+ let hours = day_secs / 3600;
+ let minutes = day_secs % 3600 / 60;
+ let seconds = day_secs % 60;
- let started = &mut false;
- Self::plural(f, started, "year", years)?;
- Self::plural(f, started, "month", months)?;
- Self::plural(f, started, "day", days)?;
- Self::plural(f, started, "hour", hours)?;
- Self::plural(f, started, "minute", minutes)?;
- Self::plural(f, started, "second", seconds)?;
- Ok(())
- }
+ let started = &mut false;
+ Self::plural(f, started, "year", years)?;
+ Self::plural(f, started, "month", months)?;
+ Self::plural(f, started, "day", days)?;
+ Self::plural(f, started, "hour", hours)?;
+ Self::plural(f, started, "minute", minutes)?;
+ Self::plural(f, started, "second", seconds)?;
+ Ok(())
+ }
}
//---------------------------------------------------------------------------------------------------- [HumanNumber]
@@ -107,249 +112,304 @@ impl std::fmt::Display for HumanTime {
pub struct HumanNumber(String);
impl std::fmt::Display for HumanNumber {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- write!(f, "{}", &self.0)
- }
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "{}", &self.0)
+ }
}
impl HumanNumber {
- #[inline]
- pub fn unknown() -> Self {
- Self("???".to_string())
- }
- #[inline]
- pub fn from_str(s: &str) -> Self {
- Self(s.to_string())
- }
- #[inline]
- pub fn to_hashrate(f: f32) -> Self {
- Self(format!("{} H/s", Self::from_f32(f)))
- }
- #[inline]
- pub fn to_percent(f: f32) -> Self {
- if f < 0.01 {
- Self("0%".to_string())
- } else {
- Self(format!("{:.2}%", f))
- }
- }
- #[inline]
- pub fn to_percent_3_point(f: f32) -> Self {
- Self(format!("{:.3}%", f))
- }
- #[inline]
- pub fn to_percent_no_fmt(f: f32) -> Self {
- Self(format!("{}%", f))
- }
- #[inline]
- pub fn from_f64_to_percent_3_point(f: f64) -> Self {
- Self(format!("{:.3}%", f))
- }
- #[inline]
- pub fn from_f64_to_percent_6_point(f: f64) -> Self {
- Self(format!("{:.6}%", f))
- }
- #[inline]
- pub fn from_f64_to_percent_9_point(f: f64) -> Self {
- Self(format!("{:.9}%", f))
- }
- #[inline]
- pub fn from_f64_to_percent_no_fmt(f: f64) -> Self {
- Self(format!("{}%", f))
- }
- #[inline]
- pub fn from_f32(f: f32) -> Self {
- let mut buf = num_format::Buffer::new();
- buf.write_formatted(&(f as u64), &LOCALE);
- Self(buf.as_str().to_string())
- }
- #[inline]
- pub fn from_f64(f: f64) -> Self {
- let mut buf = num_format::Buffer::new();
- buf.write_formatted(&(f as u128), &LOCALE);
- Self(buf.as_str().to_string())
- }
- #[inline]
- pub fn from_u16(u: u16) -> Self {
- let mut buf = num_format::Buffer::new();
- buf.write_formatted(&u, &LOCALE);
- Self(buf.as_str().to_string())
- }
- #[inline]
- pub fn from_u32(u: u32) -> Self {
- let mut buf = num_format::Buffer::new();
- buf.write_formatted(&u, &LOCALE);
- Self(buf.as_str().to_string())
- }
- #[inline]
- pub fn from_u64(u: u64) -> Self {
- let mut buf = num_format::Buffer::new();
- buf.write_formatted(&u, &LOCALE);
- Self(buf.as_str().to_string())
- }
- #[inline]
- pub fn from_u128(u: u128) -> Self {
- let mut buf = num_format::Buffer::new();
- buf.write_formatted(&u, &LOCALE);
- Self(buf.as_str().to_string())
- }
- #[inline]
- pub fn from_hashrate(array: [Option; 3]) -> Self {
- let mut string = "[".to_string();
- let mut buf = num_format::Buffer::new();
+ #[inline]
+ pub fn unknown() -> Self {
+ Self("???".to_string())
+ }
+ #[inline]
+ pub fn from_str(s: &str) -> Self {
+ Self(s.to_string())
+ }
+ #[inline]
+ pub fn to_hashrate(f: f32) -> Self {
+ Self(format!("{} H/s", Self::from_f32(f)))
+ }
+ #[inline]
+ pub fn to_percent(f: f32) -> Self {
+ if f < 0.01 {
+ Self("0%".to_string())
+ } else {
+ Self(format!("{:.2}%", f))
+ }
+ }
+ #[inline]
+ pub fn to_percent_3_point(f: f32) -> Self {
+ Self(format!("{:.3}%", f))
+ }
+ #[inline]
+ pub fn to_percent_no_fmt(f: f32) -> Self {
+ Self(format!("{}%", f))
+ }
+ #[inline]
+ pub fn from_f64_to_percent_3_point(f: f64) -> Self {
+ Self(format!("{:.3}%", f))
+ }
+ #[inline]
+ pub fn from_f64_to_percent_6_point(f: f64) -> Self {
+ Self(format!("{:.6}%", f))
+ }
+ #[inline]
+ pub fn from_f64_to_percent_9_point(f: f64) -> Self {
+ Self(format!("{:.9}%", f))
+ }
+ #[inline]
+ pub fn from_f64_to_percent_no_fmt(f: f64) -> Self {
+ Self(format!("{}%", f))
+ }
+ #[inline]
+ pub fn from_f32(f: f32) -> Self {
+ let mut buf = num_format::Buffer::new();
+ buf.write_formatted(&(f as u64), &LOCALE);
+ Self(buf.as_str().to_string())
+ }
+ #[inline]
+ pub fn from_f64(f: f64) -> Self {
+ let mut buf = num_format::Buffer::new();
+ buf.write_formatted(&(f as u128), &LOCALE);
+ Self(buf.as_str().to_string())
+ }
+ #[inline]
+ pub fn from_u16(u: u16) -> Self {
+ let mut buf = num_format::Buffer::new();
+ buf.write_formatted(&u, &LOCALE);
+ Self(buf.as_str().to_string())
+ }
+ #[inline]
+ pub fn from_u32(u: u32) -> Self {
+ let mut buf = num_format::Buffer::new();
+ buf.write_formatted(&u, &LOCALE);
+ Self(buf.as_str().to_string())
+ }
+ #[inline]
+ pub fn from_u64(u: u64) -> Self {
+ let mut buf = num_format::Buffer::new();
+ buf.write_formatted(&u, &LOCALE);
+ Self(buf.as_str().to_string())
+ }
+ #[inline]
+ pub fn from_u128(u: u128) -> Self {
+ let mut buf = num_format::Buffer::new();
+ buf.write_formatted(&u, &LOCALE);
+ Self(buf.as_str().to_string())
+ }
+ #[inline]
+ pub fn from_hashrate(array: [Option; 3]) -> Self {
+ let mut string = "[".to_string();
+ let mut buf = num_format::Buffer::new();
- let mut n = 0;
- for i in array {
- match i {
- Some(f) => {
- let f = f as u128;
- buf.write_formatted(&f, &LOCALE);
- string.push_str(buf.as_str());
- string.push_str(" H/s");
- },
- None => string.push_str("??? H/s"),
- }
- if n != 2 {
- string.push_str(", ");
- n += 1;
- } else {
- string.push(']');
- break
- }
- }
+ let mut n = 0;
+ for i in array {
+ match i {
+ Some(f) => {
+ let f = f as u128;
+ buf.write_formatted(&f, &LOCALE);
+ string.push_str(buf.as_str());
+ string.push_str(" H/s");
+ }
+ None => string.push_str("??? H/s"),
+ }
+ if n != 2 {
+ string.push_str(", ");
+ n += 1;
+ } else {
+ string.push(']');
+ break;
+ }
+ }
- Self(string)
- }
- #[inline]
- pub fn from_load(array: [Option; 3]) -> Self {
- let mut string = "[".to_string();
- let mut n = 0;
- for i in array {
- match i {
- Some(f) => string.push_str(format!("{:.2}", f).as_str()),
- None => string.push_str("???"),
- }
- if n != 2 {
- string.push_str(", ");
- n += 1;
- } else {
- string.push(']');
- break
- }
- }
- Self(string)
- }
- // [1_000_000] -> [1.000 MH/s]
- #[inline]
- pub fn from_u64_to_megahash_3_point(hash: u64) -> Self {
- let hash = (hash as f64)/1_000_000.0;
- let hash = format!("{:.3} MH/s", hash);
- Self(hash)
- }
- // [1_000_000_000] -> [1.000 GH/s]
- #[inline]
- pub fn from_u64_to_gigahash_3_point(hash: u64) -> Self {
- let hash = (hash as f64)/1_000_000_000.0;
- let hash = format!("{:.3} GH/s", hash);
- Self(hash)
- }
- #[inline]
- pub fn from_f64_12_point(f: f64) -> Self {
- let f = format!("{:.12}", f);
- Self(f)
- }
- #[inline]
- pub fn from_f64_no_fmt(f: f64) -> Self {
- let f = format!("{}", f);
- Self(f)
- }
- #[inline]
- pub fn as_str(&self) -> &str {
- self.0.as_str()
- }
+ Self(string)
+ }
+ #[inline]
+ pub fn from_load(array: [Option; 3]) -> Self {
+ let mut string = "[".to_string();
+ let mut n = 0;
+ for i in array {
+ match i {
+ Some(f) => string.push_str(format!("{:.2}", f).as_str()),
+ None => string.push_str("???"),
+ }
+ if n != 2 {
+ string.push_str(", ");
+ n += 1;
+ } else {
+ string.push(']');
+ break;
+ }
+ }
+ Self(string)
+ }
+ // [1_000_000] -> [1.000 MH/s]
+ #[inline]
+ pub fn from_u64_to_megahash_3_point(hash: u64) -> Self {
+ let hash = (hash as f64) / 1_000_000.0;
+ let hash = format!("{:.3} MH/s", hash);
+ Self(hash)
+ }
+ // [1_000_000_000] -> [1.000 GH/s]
+ #[inline]
+ pub fn from_u64_to_gigahash_3_point(hash: u64) -> Self {
+ let hash = (hash as f64) / 1_000_000_000.0;
+ let hash = format!("{:.3} GH/s", hash);
+ Self(hash)
+ }
+ #[inline]
+ pub fn from_f64_12_point(f: f64) -> Self {
+ let f = format!("{:.12}", f);
+ Self(f)
+ }
+ #[inline]
+ pub fn from_f64_no_fmt(f: f64) -> Self {
+ let f = format!("{}", f);
+ Self(f)
+ }
+ #[inline]
+ pub fn as_str(&self) -> &str {
+ self.0.as_str()
+ }
}
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
- #[test]
- fn human_number() {
- use crate::human::HumanNumber;
- assert!(HumanNumber::to_percent(0.001).to_string() == "0%");
- assert!(HumanNumber::to_percent(12.123123123123).to_string() == "12.12%");
- assert!(HumanNumber::to_percent_3_point(0.001).to_string() == "0.001%");
- assert!(HumanNumber::from_hashrate([Some(123.1), Some(11111.1), None]).to_string() == "[123 H/s, 11,111 H/s, ??? H/s]");
- assert!(HumanNumber::from_hashrate([None, Some(1.123), Some(123123.312)]).to_string() == "[??? H/s, 1 H/s, 123,123 H/s]");
- assert!(HumanNumber::from_load([Some(123.1234), Some(321.321), None]).to_string() == "[123.12, 321.32, ???]");
- assert!(HumanNumber::from_load([None, Some(4321.43), Some(1234.1)]).to_string() == "[???, 4321.43, 1234.10]");
- assert!(HumanNumber::from_f32(123_123.123123123).to_string() == "123,123");
- assert!(HumanNumber::from_f64(123_123_123.123123123123123).to_string() == "123,123,123");
- assert!(HumanNumber::from_u16(1_000).to_string() == "1,000");
- assert!(HumanNumber::from_u16(65_535).to_string() == "65,535");
- assert!(HumanNumber::from_u32(65_536).to_string() == "65,536");
- assert!(HumanNumber::from_u32(100_000).to_string() == "100,000");
- assert!(HumanNumber::from_u32(1_000_000).to_string() == "1,000,000");
- assert!(HumanNumber::from_u32(10_000_000).to_string() == "10,000,000");
- assert!(HumanNumber::from_u32(100_000_000).to_string() == "100,000,000");
- assert!(HumanNumber::from_u32(1_000_000_000).to_string() == "1,000,000,000");
- assert!(HumanNumber::from_u32(4_294_967_295).to_string() == "4,294,967,295");
- assert!(HumanNumber::from_u64(4_294_967_296).to_string() == "4,294,967,296");
- assert!(HumanNumber::from_u64(10_000_000_000).to_string() == "10,000,000,000");
- assert!(HumanNumber::from_u64(100_000_000_000).to_string() == "100,000,000,000");
- assert!(HumanNumber::from_u64(1_000_000_000_000).to_string() == "1,000,000,000,000");
- assert!(HumanNumber::from_u64(10_000_000_000_000).to_string() == "10,000,000,000,000");
- assert!(HumanNumber::from_u64(100_000_000_000_000).to_string() == "100,000,000,000,000");
- assert!(HumanNumber::from_u64(1_000_000_000_000_000).to_string() == "1,000,000,000,000,000");
- assert!(HumanNumber::from_u64(10_000_000_000_000_000).to_string() == "10,000,000,000,000,000");
- assert!(HumanNumber::from_u64(18_446_744_073_709_551_615).to_string() == "18,446,744,073,709,551,615");
- assert!(HumanNumber::from_u128(18_446_744_073_709_551_616).to_string() == "18,446,744,073,709,551,616");
- assert!(HumanNumber::from_u128(100_000_000_000_000_000_000).to_string() == "100,000,000,000,000,000,000");
- assert_eq!(
- HumanNumber::from_u128(340_282_366_920_938_463_463_374_607_431_768_211_455).to_string(),
- "340,282,366,920,938,463,463,374,607,431,768,211,455",
- );
- assert!(HumanNumber::from_u64_to_gigahash_3_point(1_000_000_000).to_string() == "1.000 GH/s");
- }
+ #[test]
+ fn human_number() {
+ use crate::human::HumanNumber;
+ assert!(HumanNumber::to_percent(0.001).to_string() == "0%");
+ assert!(HumanNumber::to_percent(12.123123123123).to_string() == "12.12%");
+ assert!(HumanNumber::to_percent_3_point(0.001).to_string() == "0.001%");
+ assert!(
+ HumanNumber::from_hashrate([Some(123.1), Some(11111.1), None]).to_string()
+ == "[123 H/s, 11,111 H/s, ??? H/s]"
+ );
+ assert!(
+ HumanNumber::from_hashrate([None, Some(1.123), Some(123123.312)]).to_string()
+ == "[??? H/s, 1 H/s, 123,123 H/s]"
+ );
+ assert!(
+ HumanNumber::from_load([Some(123.1234), Some(321.321), None]).to_string()
+ == "[123.12, 321.32, ???]"
+ );
+ assert!(
+ HumanNumber::from_load([None, Some(4321.43), Some(1234.1)]).to_string()
+ == "[???, 4321.43, 1234.10]"
+ );
+ assert!(HumanNumber::from_f32(123_123.123123123).to_string() == "123,123");
+ assert!(HumanNumber::from_f64(123_123_123.123123123123123).to_string() == "123,123,123");
+ assert!(HumanNumber::from_u16(1_000).to_string() == "1,000");
+ assert!(HumanNumber::from_u16(65_535).to_string() == "65,535");
+ assert!(HumanNumber::from_u32(65_536).to_string() == "65,536");
+ assert!(HumanNumber::from_u32(100_000).to_string() == "100,000");
+ assert!(HumanNumber::from_u32(1_000_000).to_string() == "1,000,000");
+ assert!(HumanNumber::from_u32(10_000_000).to_string() == "10,000,000");
+ assert!(HumanNumber::from_u32(100_000_000).to_string() == "100,000,000");
+ assert!(HumanNumber::from_u32(1_000_000_000).to_string() == "1,000,000,000");
+ assert!(HumanNumber::from_u32(4_294_967_295).to_string() == "4,294,967,295");
+ assert!(HumanNumber::from_u64(4_294_967_296).to_string() == "4,294,967,296");
+ assert!(HumanNumber::from_u64(10_000_000_000).to_string() == "10,000,000,000");
+ assert!(HumanNumber::from_u64(100_000_000_000).to_string() == "100,000,000,000");
+ assert!(HumanNumber::from_u64(1_000_000_000_000).to_string() == "1,000,000,000,000");
+ assert!(HumanNumber::from_u64(10_000_000_000_000).to_string() == "10,000,000,000,000");
+ assert!(HumanNumber::from_u64(100_000_000_000_000).to_string() == "100,000,000,000,000");
+ assert!(
+ HumanNumber::from_u64(1_000_000_000_000_000).to_string() == "1,000,000,000,000,000"
+ );
+ assert!(
+ HumanNumber::from_u64(10_000_000_000_000_000).to_string() == "10,000,000,000,000,000"
+ );
+ assert!(
+ HumanNumber::from_u64(18_446_744_073_709_551_615).to_string()
+ == "18,446,744,073,709,551,615"
+ );
+ assert!(
+ HumanNumber::from_u128(18_446_744_073_709_551_616).to_string()
+ == "18,446,744,073,709,551,616"
+ );
+ assert!(
+ HumanNumber::from_u128(100_000_000_000_000_000_000).to_string()
+ == "100,000,000,000,000,000,000"
+ );
+ assert_eq!(
+ HumanNumber::from_u128(340_282_366_920_938_463_463_374_607_431_768_211_455).to_string(),
+ "340,282,366,920,938,463,463,374,607,431,768,211,455",
+ );
+ assert!(
+ HumanNumber::from_u64_to_gigahash_3_point(1_000_000_000).to_string() == "1.000 GH/s"
+ );
+ }
- #[test]
- fn human_time() {
- use crate::human::HumanTime;
- use std::time::Duration;
- assert!(HumanTime::into_human(Duration::from_secs(0)).to_string() == "0 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(1)).to_string() == "1 second");
- assert!(HumanTime::into_human(Duration::from_secs(2)).to_string() == "2 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(59)).to_string() == "59 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(60)).to_string() == "1 minute");
- assert!(HumanTime::into_human(Duration::from_secs(61)).to_string() == "1 minute, 1 second");
- assert!(HumanTime::into_human(Duration::from_secs(62)).to_string() == "1 minute, 2 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(120)).to_string() == "2 minutes");
- assert!(HumanTime::into_human(Duration::from_secs(121)).to_string() == "2 minutes, 1 second");
- assert!(HumanTime::into_human(Duration::from_secs(122)).to_string() == "2 minutes, 2 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(179)).to_string() == "2 minutes, 59 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(3599)).to_string() == "59 minutes, 59 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(3600)).to_string() == "1 hour");
- assert!(HumanTime::into_human(Duration::from_secs(3601)).to_string() == "1 hour, 1 second");
- assert!(HumanTime::into_human(Duration::from_secs(3602)).to_string() == "1 hour, 2 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(3660)).to_string() == "1 hour, 1 minute");
- assert!(HumanTime::into_human(Duration::from_secs(3720)).to_string() == "1 hour, 2 minutes");
- assert!(HumanTime::into_human(Duration::from_secs(86399)).to_string() == "23 hours, 59 minutes, 59 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(86400)).to_string() == "1 day");
- assert!(HumanTime::into_human(Duration::from_secs(86401)).to_string() == "1 day, 1 second");
- assert!(HumanTime::into_human(Duration::from_secs(86402)).to_string() == "1 day, 2 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(86460)).to_string() == "1 day, 1 minute");
- assert!(HumanTime::into_human(Duration::from_secs(86520)).to_string() == "1 day, 2 minutes");
- assert!(HumanTime::into_human(Duration::from_secs(90000)).to_string() == "1 day, 1 hour");
- assert!(HumanTime::into_human(Duration::from_secs(93600)).to_string() == "1 day, 2 hours");
- assert!(HumanTime::into_human(Duration::from_secs(604799)).to_string() == "6 days, 23 hours, 59 minutes, 59 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(604800)).to_string() == "7 days");
- assert!(HumanTime::into_human(Duration::from_secs(2630016)).to_string() == "1 month");
- assert!(HumanTime::into_human(Duration::from_secs(3234815)).to_string() == "1 month, 6 days, 23 hours, 59 minutes, 59 seconds");
- assert!(HumanTime::into_human(Duration::from_secs(5260032)).to_string() == "2 months");
- assert!(HumanTime::into_human(Duration::from_secs(31557600)).to_string() == "1 year");
- assert!(HumanTime::into_human(Duration::from_secs(63115200)).to_string() == "2 years");
- assert_eq!(
- HumanTime::into_human(Duration::from_secs(18446744073709551615)).to_string(),
- "584542046090 years, 7 months, 15 days, 17 hours, 5 minutes, 3 seconds",
- );
- }
+ #[test]
+ fn human_time() {
+ use crate::human::HumanTime;
+ use std::time::Duration;
+ assert!(HumanTime::into_human(Duration::from_secs(0)).to_string() == "0 seconds");
+ assert!(HumanTime::into_human(Duration::from_secs(1)).to_string() == "1 second");
+ assert!(HumanTime::into_human(Duration::from_secs(2)).to_string() == "2 seconds");
+ assert!(HumanTime::into_human(Duration::from_secs(59)).to_string() == "59 seconds");
+ assert!(HumanTime::into_human(Duration::from_secs(60)).to_string() == "1 minute");
+ assert!(HumanTime::into_human(Duration::from_secs(61)).to_string() == "1 minute, 1 second");
+ assert!(
+ HumanTime::into_human(Duration::from_secs(62)).to_string() == "1 minute, 2 seconds"
+ );
+ assert!(HumanTime::into_human(Duration::from_secs(120)).to_string() == "2 minutes");
+ assert!(
+ HumanTime::into_human(Duration::from_secs(121)).to_string() == "2 minutes, 1 second"
+ );
+ assert!(
+ HumanTime::into_human(Duration::from_secs(122)).to_string() == "2 minutes, 2 seconds"
+ );
+ assert!(
+ HumanTime::into_human(Duration::from_secs(179)).to_string() == "2 minutes, 59 seconds"
+ );
+ assert!(
+ HumanTime::into_human(Duration::from_secs(3599)).to_string()
+ == "59 minutes, 59 seconds"
+ );
+ assert!(HumanTime::into_human(Duration::from_secs(3600)).to_string() == "1 hour");
+ assert!(HumanTime::into_human(Duration::from_secs(3601)).to_string() == "1 hour, 1 second");
+ assert!(
+ HumanTime::into_human(Duration::from_secs(3602)).to_string() == "1 hour, 2 seconds"
+ );
+ assert!(HumanTime::into_human(Duration::from_secs(3660)).to_string() == "1 hour, 1 minute");
+ assert!(
+ HumanTime::into_human(Duration::from_secs(3720)).to_string() == "1 hour, 2 minutes"
+ );
+ assert!(
+ HumanTime::into_human(Duration::from_secs(86399)).to_string()
+ == "23 hours, 59 minutes, 59 seconds"
+ );
+ assert!(HumanTime::into_human(Duration::from_secs(86400)).to_string() == "1 day");
+ assert!(HumanTime::into_human(Duration::from_secs(86401)).to_string() == "1 day, 1 second");
+ assert!(
+ HumanTime::into_human(Duration::from_secs(86402)).to_string() == "1 day, 2 seconds"
+ );
+ assert!(HumanTime::into_human(Duration::from_secs(86460)).to_string() == "1 day, 1 minute");
+ assert!(
+ HumanTime::into_human(Duration::from_secs(86520)).to_string() == "1 day, 2 minutes"
+ );
+ assert!(HumanTime::into_human(Duration::from_secs(90000)).to_string() == "1 day, 1 hour");
+ assert!(HumanTime::into_human(Duration::from_secs(93600)).to_string() == "1 day, 2 hours");
+ assert!(
+ HumanTime::into_human(Duration::from_secs(604799)).to_string()
+ == "6 days, 23 hours, 59 minutes, 59 seconds"
+ );
+ assert!(HumanTime::into_human(Duration::from_secs(604800)).to_string() == "7 days");
+ assert!(HumanTime::into_human(Duration::from_secs(2630016)).to_string() == "1 month");
+ assert!(
+ HumanTime::into_human(Duration::from_secs(3234815)).to_string()
+ == "1 month, 6 days, 23 hours, 59 minutes, 59 seconds"
+ );
+ assert!(HumanTime::into_human(Duration::from_secs(5260032)).to_string() == "2 months");
+ assert!(HumanTime::into_human(Duration::from_secs(31557600)).to_string() == "1 year");
+ assert!(HumanTime::into_human(Duration::from_secs(63115200)).to_string() == "2 years");
+ assert_eq!(
+ HumanTime::into_human(Duration::from_secs(18446744073709551615)).to_string(),
+ "584542046090 years, 7 months, 15 days, 17 hours, 5 minutes, 3 seconds",
+ );
+ }
}
diff --git a/src/macros.rs b/src/macros.rs
index 76a9246..3a0e8e4 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -39,83 +39,81 @@
// Locks and unwraps an [Arc]
macro_rules! lock {
- ($arc_mutex:expr) => {
- $arc_mutex.lock().unwrap()
- };
+ ($arc_mutex:expr) => {
+ $arc_mutex.lock().unwrap()
+ };
}
pub(crate) use lock;
// Locks and unwraps a field of a struct, both of them being [Arc]
// Yes, I know this is bad code.
macro_rules! lock2 {
- ($arc_mutex:expr, $arc_mutex_two:ident) => {
- $arc_mutex.lock().unwrap().$arc_mutex_two.lock().unwrap()
- };
+ ($arc_mutex:expr, $arc_mutex_two:ident) => {
+ $arc_mutex.lock().unwrap().$arc_mutex_two.lock().unwrap()
+ };
}
pub(crate) use lock2;
// Creates a new [Arc]
macro_rules! arc_mut {
- ($arc_mutex:expr) => {
- std::sync::Arc::new(std::sync::Mutex::new($arc_mutex))
- };
+ ($arc_mutex:expr) => {
+ std::sync::Arc::new(std::sync::Mutex::new($arc_mutex))
+ };
}
pub(crate) use arc_mut;
// Sleeps a [std::thread] using milliseconds
macro_rules! sleep {
($millis:expr) => {
- std::thread::sleep(std::time::Duration::from_millis($millis))
+ std::thread::sleep(std::time::Duration::from_millis($millis))
};
}
pub(crate) use sleep;
// Flips a [bool] in place
macro_rules! flip {
- ($b:expr) => {
- match $b {
- true|false => $b = !$b,
- }
- };
+ ($b:expr) => {
+ match $b {
+ true | false => $b = !$b,
+ }
+ };
}
pub(crate) use flip;
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
- #[test]
- fn lock() {
- use std::sync::{Arc,Mutex};
- let arc_mutex = Arc::new(Mutex::new(false));
- *lock!(arc_mutex) = true;
- assert!(*lock!(arc_mutex) == true);
- }
+ #[test]
+ fn lock() {
+ use std::sync::{Arc, Mutex};
+ let arc_mutex = Arc::new(Mutex::new(false));
+ *lock!(arc_mutex) = true;
+ assert!(*lock!(arc_mutex) == true);
+ }
- #[test]
- fn lock2() {
- struct Ab {
- a: Arc>,
- }
- use std::sync::{Arc,Mutex};
- let arc_mutex = Arc::new(Mutex::new(
- Ab {
- a: Arc::new(Mutex::new(false)),
- }
- ));
- *lock2!(arc_mutex,a) = true;
- assert!(*lock2!(arc_mutex,a) == true);
- }
+ #[test]
+ fn lock2() {
+ struct Ab {
+ a: Arc