From c49b7aa9829a0da8071c92f8870a4563d1979278 Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Sat, 3 Dec 2022 13:37:57 -0500
Subject: [PATCH] helper: add functions for p2pool/xmrig UI -> command
 arguments

---
 Cargo.lock       |  22 ++++-----
 README.md        |   4 ++
 src/constants.rs |   4 +-
 src/disk.rs      |  16 ++++---
 src/helper.rs    | 115 ++++++++++++++++++++++++++++++++++++++++++++---
 src/node.rs      |  12 +++++
 src/p2pool.rs    |   2 +-
 src/xmrig.rs     |   8 ++--
 8 files changed, 151 insertions(+), 32 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 052c060..34d69b6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -491,7 +491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5bcf530afb40e45e14440701e5e996d7fd139e84a912a4d83a8d6a0fb3e58663"
 dependencies = [
  "log",
- "nix 0.25.0",
+ "nix 0.25.1",
  "slotmap",
  "thiserror",
  "vec_map",
@@ -2143,9 +2143,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
 
 [[package]]
 name = "libc"
-version = "0.2.137"
+version = "0.2.138"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
 
 [[package]]
 name = "libloading"
@@ -2431,9 +2431,9 @@ dependencies = [
 
 [[package]]
 name = "nix"
-version = "0.24.2"
+version = "0.24.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
 dependencies = [
  "bitflags",
  "cfg-if",
@@ -2443,9 +2443,9 @@ dependencies = [
 
 [[package]]
 name = "nix"
-version = "0.25.0"
+version = "0.25.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
+checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
 dependencies = [
  "autocfg",
  "bitflags",
@@ -3584,7 +3584,7 @@ dependencies = [
  "lazy_static",
  "log",
  "memmap2",
- "nix 0.24.2",
+ "nix 0.24.3",
  "pkg-config",
  "wayland-client",
  "wayland-cursor",
@@ -4781,7 +4781,7 @@ dependencies = [
  "bitflags",
  "downcast-rs",
  "libc",
- "nix 0.24.2",
+ "nix 0.24.3",
  "scoped-tls",
  "wayland-commons",
  "wayland-scanner",
@@ -4794,7 +4794,7 @@ version = "0.29.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
 dependencies = [
- "nix 0.24.2",
+ "nix 0.24.3",
  "once_cell",
  "smallvec",
  "wayland-sys",
@@ -4806,7 +4806,7 @@ version = "0.29.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
 dependencies = [
- "nix 0.24.2",
+ "nix 0.24.3",
  "wayland-client",
  "xcursor",
 ]
diff --git a/README.md b/README.md
index 2987f87..6d57f5e 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,7 @@ https://user-images.githubusercontent.com/101352116/194763334-d8e936c9-a71e-474e
 * A Monero node/wallet
 
 ## Build
+Windows/Linux:
 ```
 cargo build --release
 ```
@@ -120,5 +121,8 @@ On macOS, if you want the binary to have an icon in `Finder`, you must install [
 ```
 cargo bundle --release
 ```
+This bundles Gupax into a `Gupax.app`, the way it comes in the pre-built tars for macOS.
 
 The `build.rs` file in the repo root sets the icon in `File Explorer` for Windows. The taskbar icon & App frame icon (for all OS's) get set at runtime using pre-compiled bytes in [`src/constants.rs`](https://github.com/hinto-janaiyo/gupax/blob/main/src/constants.rs) from [`images`](https://github.com/hinto-janaiyo/gupax/blob/main/images).
+
+The `--release` profile is set to prefer code performance & small binary sizes over compilation speed (see [`Cargo.toml`](https://github.com/hinto-janaiyo/gupax/blob/main/Cargo.toml)). Gupax itself (with all dependencies already built) takes around 1m30s to build (vs 10s on a normal `--release`) with a Ryzen 5950x.
diff --git a/src/constants.rs b/src/constants.rs
index 85ea736..01cf749 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -113,7 +113,7 @@ pub const P2POOL_AUTO_SELECT: &str = "Automatically select the fastest community
 pub const P2POOL_SELECT_FASTEST: &str = "Select the fastest community Monero node";
 pub const P2POOL_PING: &str = "Ping the built-in community 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 for P2Pool mining; wallet addresses are public on P2Pool!";
-pub const P2POOL_COMMAND: &str = "Start P2Pool with these arguments and override all below settings; If the [--data-api] flag is not given, Gupax will append it to the arguments automatically so that the [Status] tab can work";
+pub const P2POOL_ARGUMENTS: &str = "Start P2Pool with these arguments and override all below settings; If the [--data-api] & [--local-api] flag is not given, Gupax will append it to the arguments automatically so that the [Status] tab can work";
 pub const P2POOL_SIMPLE: &str =
 r#"Use simple P2Pool settings:
     - Remote community Monero node
@@ -151,7 +151,7 @@ r#"Use advanced XMRig settings:
 	- TLS setting
 	- Keepalive setting
 	- Custom HTTP API IP/Port"#;
-pub const XMRIG_CONFIG: &str = "Start XMRig with this config file and override all below settings; If the [http-api] options are not set, the [Status] tab will not properly show XMRig stats. If they are set, Gupax will detect which automatically IP/Port you are using";
+pub const XMRIG_ARGUMENTS: &str = "Start XMRig with these arguments and override all below settings; If the [http-api] options are not set, Gupax will append it to the arguments automatically so that the [Status] tab can work";
 pub const XMRIG_ADDRESS: &str = "Specify which Monero address to send payouts to; Must be a valid primary address (starts with 4)";
 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";
diff --git a/src/disk.rs b/src/disk.rs
index cdf26dd..7e22421 100644
--- a/src/disk.rs
+++ b/src/disk.rs
@@ -112,7 +112,7 @@ pub fn read_to_string(file: File, path: &PathBuf) -> Result<String, TomlError> {
 }
 
 // Write str to console with [info!] surrounded by "---"
-pub fn print_toml(toml: &str) {
+pub fn print_dash(toml: &str) {
 	info!("{}", HORIZONTAL);
 	for i in toml.lines() { info!("{}", i); }
 	info!("{}", HORIZONTAL);
@@ -165,7 +165,7 @@ impl State {
 				log_level: 3,
 				node: crate::NodeEnum::C3pool,
 				arguments: String::new(),
-				address: String::with_capacity(95),
+				address: String::with_capacity(96),
 				name: "Local Monero Node".to_string(),
 				ip: "localhost".to_string(),
 				rpc: "18081".to_string(),
@@ -179,8 +179,9 @@ impl State {
 			xmrig: Xmrig {
 				simple: true,
 				pause: 0,
-				config: String::with_capacity(100),
-				address: String::with_capacity(95),
+				simple_rig: String::with_capacity(30),
+				arguments: String::with_capacity(300),
+				address: String::with_capacity(96),
 				name: "Local P2Pool".to_string(),
 				rig: "Gupax".to_string(),
 				ip: "localhost".to_string(),
@@ -210,7 +211,7 @@ impl State {
 		match toml::de::from_str(string) {
 			Ok(state) => {
 				info!("State | Parse ... OK");
-				print_toml(string);
+				print_dash(string);
 				Ok(state)
 			}
 			Err(err) => {
@@ -272,7 +273,7 @@ impl State {
 		let string = match toml::ser::to_string(&self) {
 			Ok(string) => {
 				info!("State | Parse ... OK");
-				print_toml(&string);
+				print_dash(&string);
 				string
 			},
 			Err(err) => { error!("State | Couldn't parse TOML into string ... FAIL"); return Err(TomlError::Serialize(err)) },
@@ -615,7 +616,8 @@ pub struct P2pool {
 pub struct Xmrig {
 	pub simple: bool,
 	pub pause: u8,
-	pub config: String,
+	pub simple_rig: String,
+	pub arguments: String,
 	pub tls: bool,
 	pub keepalive: bool,
 	pub max_threads: usize,
diff --git a/src/helper.rs b/src/helper.rs
index 1e2502c..b11498c 100644
--- a/src/helper.rs
+++ b/src/helper.rs
@@ -323,6 +323,7 @@ impl Hashrate {
 use tokio::io::{BufReader,AsyncBufReadExt};
 
 impl Helper {
+	//---------------------------------------------------------------------------------------------------- General Functions
 	pub fn new(instant: std::time::Instant) -> Self {
 		Self {
 			instant,
@@ -336,12 +337,6 @@ impl Helper {
 		}
 	}
 
-	// Intermediate function that spawns the helper thread.
-	pub fn spawn_helper(helper: &Arc<Mutex<Self>>) {
-		let helper = Arc::clone(helper);
-		thread::spawn(move || { Self::helper(helper); });
-	}
-
 	// The tokio runtime that blocks while async reading both STDOUT/STDERR
 	// Cheaper than spawning 2 OS threads just to read 2 pipes (...right? :D)
 	#[tokio::main]
@@ -364,7 +359,113 @@ impl Helper {
 		tokio::join![stdout_job, stderr_job];
 	}
 
-	// The "helper" loop
+	//---------------------------------------------------------------------------------------------------- P2Pool specific
+	// Intermediate function that parses the arguments, and spawns the P2Pool watchdog thread.
+	pub fn spawn_p2pool(state: &crate::disk::P2pool, api_path: &std::path::Path) {
+		let mut args = Vec::with_capacity(500);
+		// [Simple]
+		if state.simple {
+			// Build the p2pool argument
+			let (ip, rpc, zmq) = crate::node::enum_to_ip_rpc_zmq_tuple(state.node); // Get: (IP, RPC, ZMQ)
+			args.push(format!("--wallet {}", state.address));        // Wallet Address
+			args.push(format!("--host {}", ip));                     // IP Address
+			args.push(format!("--rpc-port {}", rpc));                // RPC Port
+			args.push(format!("--zmq-port {}", zmq));                // ZMQ Port
+			args.push(format!("--data-api {}", api_path.display())); // 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
+
+		// [Advanced]
+		} else {
+			// Overriding command arguments
+			if !state.arguments.is_empty() {
+				for arg in state.arguments.split_whitespace() {
+					args.push(arg.to_string());
+				}
+			// Else, build the argument
+			} else {
+				args.push(state.address.clone());      // Wallet
+				args.push(state.selected_ip.clone());  // IP
+				args.push(state.selected_rpc.clone()); // RPC
+				args.push(state.selected_zmq.clone()); // ZMQ
+				args.push("--local-api".to_string());  // Enable API
+				args.push("--no-color".to_string());   // Remove color escape sequences
+				if state.mini { args.push("--mini".to_string()); };      // Mini
+				args.push(format!("--loglevel {}", state.log_level));    // Log Level
+				args.push(format!("--out-peers {}", state.out_peers));   // Out Peers
+				args.push(format!("--in-peers {}", state.in_peers));     // In Peers
+				args.push(format!("--data-api {}", api_path.display())); // API Path
+			}
+		}
+
+		// Print arguments to console
+		crate::disk::print_dash(&format!("P2Pool | Launch arguments ... {:#?}", args));
+
+		// Spawn watchdog thread
+		thread::spawn(move || {
+			Self::spawn_p2pool_watchdog(args);
+		});
+	}
+
+	// The actual P2Pool watchdog tokio runtime.
+	#[tokio::main]
+	pub async fn spawn_p2pool_watchdog(args: Vec<String>) {
+		// 1. Create command
+		// 2. Spawn STDOUT/STDERR thread
+		// 3. Loop forever as watchdog until process dies
+	}
+
+	//---------------------------------------------------------------------------------------------------- XMRig specific
+	// Intermediate function that parses the arguments, and spawns the XMRig watchdog thread.
+	pub fn spawn_xmrig(state: &crate::disk::Xmrig, api_path: &std::path::Path) {
+		let mut args = Vec::with_capacity(500);
+		if state.simple {
+			let rig_name = if state.simple_rig.is_empty() { GUPAX_VERSION.to_string() } else { state.simple_rig.clone() }; // Rig name
+			args.push(format!("--threads {}", state.current_threads)); // Threads
+			args.push(format!("--user {}", state.simple_rig));         // Rig name
+			args.push(format!("--url 127.0.0.1:3333"));                // Local P2Pool (the default)
+			args.push("--no-color".to_string());                       // No color escape codes
+			if state.pause != 0 { args.push(format!("--pause-on-active {}", state.pause)); } // Pause on active
+		} else {
+			if !state.arguments.is_empty() {
+				for arg in state.arguments.split_whitespace() {
+					args.push(arg.to_string());
+				}
+			} else {
+				args.push(format!("--user {}", state.address.clone()));    // Wallet
+				args.push(format!("--threads {}", state.current_threads)); // Threads
+				args.push(format!("--rig-id {}", state.selected_rig));     // Rig ID
+				args.push(format!("--url {}:{}", state.selected_ip.clone(), state.selected_port.clone())); // IP/Port
+				args.push(format!("--http-host {}", state.api_ip).to_string());   // HTTP API IP
+				args.push(format!("--http-port {}", state.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(format!("--pause-on-active {}", state.pause)); } // Pause on active
+			}
+		}
+		// Print arguments to console
+		crate::disk::print_dash(&format!("XMRig | Launch arguments ... {:#?}", args));
+
+		// Spawn watchdog thread
+		thread::spawn(move || {
+			Self::spawn_xmrig_watchdog(args);
+		});
+	}
+
+	// The actual XMRig watchdog tokio runtime.
+	#[tokio::main]
+	pub async fn spawn_xmrig_watchdog(args: Vec<String>) {
+	}
+
+	//---------------------------------------------------------------------------------------------------- The "helper"
+	// Intermediate function that spawns the helper thread.
+	pub fn spawn_helper(helper: &Arc<Mutex<Self>>) {
+		let helper = Arc::clone(helper);
+		thread::spawn(move || { Self::helper(helper); });
+	}
+
 	// [helper] = Actual Arc
 	// [h]      = Temporary lock that gets dropped
 	// [jobs]   = Vector of async jobs ready to go
diff --git a/src/node.rs b/src/node.rs
index 7de8544..4cebfad 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -143,6 +143,18 @@ pub fn enum_to_ip(node: NodeEnum) -> &'static str {
 	}
 }
 
+// Returns a tuple of (IP, RPC_PORT, ZMQ_PORT)
+pub fn enum_to_ip_rpc_zmq_tuple(node: NodeEnum) -> (&'static str, &'static str, &'static str) {
+	// [.unwrap()] should be safe, IP:PORTs are constants after all.
+	let (ip, rpc) = enum_to_ip(node).rsplit_once(':').unwrap();
+	// Get ZMQ, grr... plowsof is the only 18084 zmq person.
+	let zmq = match node {
+		Plowsof1|Plowsof2 => "18084",
+		_ => "18083",
+	};
+	(ip, rpc, zmq)
+}
+
 // 5000 = 4 max length
 pub fn format_ms(ms: u128) -> String {
 	match ms.to_string().len() {
diff --git a/src/p2pool.rs b/src/p2pool.rs
index 5e07513..37c02f1 100644
--- a/src/p2pool.rs
+++ b/src/p2pool.rs
@@ -66,7 +66,7 @@ egui::Frame::none()
 			let width = (width/10.0) - SPACE;
 			ui.style_mut().override_text_style = Some(Monospace);
 			ui.add_sized([width, text_edit], Label::new("Command arguments:"));
-			ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.arguments), r#"--wallet <...> --host <...>"#)).on_hover_text(P2POOL_COMMAND);
+			ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.arguments), r#"--wallet <...> --host <...>"#)).on_hover_text(P2POOL_ARGUMENTS);
 			self.arguments.truncate(1024);
 		})});
 		ui.set_enabled(self.arguments.is_empty());
diff --git a/src/xmrig.rs b/src/xmrig.rs
index 65f9647..8408bbd 100644
--- a/src/xmrig.rs
+++ b/src/xmrig.rs
@@ -45,11 +45,11 @@ impl Xmrig {
 		ui.group(|ui| { ui.horizontal(|ui| {
 			let width = (width/10.0) - SPACE;
 			ui.style_mut().override_text_style = Some(Monospace);
-			ui.add_sized([width, text_edit], Label::new("Config file:"));
-			ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.config), r#"...config.json"#)).on_hover_text(XMRIG_CONFIG);
-			self.config.truncate(1024);
+			ui.add_sized([width, text_edit], Label::new("Command arguments:"));
+			ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.arguments), r#"--url <...> --user <...> --config <...>"#)).on_hover_text(XMRIG_ARGUMENTS);
+			self.arguments.truncate(1024);
 		})});
-		ui.set_enabled(self.config.is_empty());
+		ui.set_enabled(self.arguments.is_empty());
 	//---------------------------------------------------------------------------------------------------- Address
 		ui.group(|ui| {
 			let width = width - SPACE;