From 3222693c357f6ea15911a74efa9c5a6c51edfbef Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Mon, 28 Nov 2022 12:05:09 -0500
Subject: [PATCH] command: implement basic data structures, functions

This adds the basic wireframe of how processes will be handled.
The data/funcs in [command.rs] will be the API the main GUI thread
uses to talk to child processes. The process thread will loop
every 1 second to read/write the necessary data (stdout, stdin),
and handle signals from the GUI thread (kill, restart, etc).
---
 src/command.rs   | 111 +++++++++++++++++++++++++++++++++++++++++++++++
 src/constants.rs |   3 ++
 src/main.rs      |   1 +
 src/p2pool.rs    |   3 +-
 4 files changed, 117 insertions(+), 1 deletion(-)

diff --git a/src/command.rs b/src/command.rs
index 7e46dde..ca271a9 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -14,3 +14,114 @@
 //
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+// This file handles all things related to child processes (P2Pool/XMRig).
+// The main GUI thread will interface with the [Arc<Mutex<...>>] data found
+// here, e.g: User clicks [Start P2Pool] -> Init p2pool thread here.
+
+//---------------------------------------------------------------------------------------------------- Import
+use std::{
+	sync::{Arc,Mutex},
+	path::PathBuf,
+	process::Command,
+	thread,
+};
+use crate::constants::*;
+use log::*;
+
+//---------------------------------------------------------------------------------------------------- [Process] Struct
+// This holds all the state of a (child) process.
+// The actual process thread runs in a 1 second loop, reading/writing to this struct.
+// The main GUI thread will use this to display console text, online state, etc.
+pub struct Process {
+	name: ProcessName,     // P2Pool or XMRig?
+	online: bool,          // Is the process alive?
+	args: String,          // A single [String] containing the arguments
+	path: PathBuf,         // The absolute path to the process binary
+	signal: ProcessSignal, // Did the user click [Stop/Restart]?
+	output: String,        // This is the process's stdout + stderr
+	// 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
+	input: Vec<String>,
+}
+
+//---------------------------------------------------------------------------------------------------- [Process] Impl
+impl Process {
+	pub fn new(name: ProcessName, args: String, path: PathBuf) -> Self {
+		Self {
+			name,
+			online: false,
+			args,
+			path,
+			signal: ProcessSignal::None,
+			output: String::new(),
+			input: vec![String::new()],
+		}
+	}
+
+	// Borrow a [&str], return an owned split collection
+	pub fn parse_args(args: &str) -> Vec<String> {
+		args.split_whitespace().map(|s| s.to_owned()).collect()
+	}
+
+	pub fn spawn(process: &Arc<Mutex<Self>>, name: ProcessName) {
+		// Setup
+		let process = Arc::clone(process);
+		let args = Self::parse_args(&process.lock().unwrap().args);
+		info!("{} | Spawning initial thread", name);
+		info!("{} | Arguments: {:?}", name, args);
+
+		// Spawn thread
+		thread::spawn(move || {
+			// Create & spawn child
+			let mut child = Command::new(&process.lock().unwrap().path)
+				.args(args)
+				.stdout(std::process::Stdio::piped())
+				.spawn().unwrap();
+
+			// 1-second loop, reading and writing data to relevent struct
+			loop {
+				let process = process.lock().unwrap(); // Get lock
+				// If user sent a signal, handle it
+				match process.signal {
+					ProcessSignal::None => {},
+					_ => { child.kill(); break; },
+				};
+//				println!("{:?}", String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap());
+				thread::sleep(SECOND);
+			}
+
+			// End of thread, must mean process is offline
+			process.lock().unwrap().online = false;
+		});
+	}
+}
+
+//---------------------------------------------------------------------------------------------------- [ProcessSignal] Enum
+#[derive(Copy,Clone,Eq,PartialEq,Debug)]
+pub enum ProcessSignal {
+	None,
+	Stop,
+	Restart,
+}
+
+//---------------------------------------------------------------------------------------------------- [ProcessName] Enum
+#[derive(Copy,Clone,Eq,PartialEq,Debug)]
+pub enum ProcessName {
+	P2Pool,
+	XMRig,
+}
+
+impl std::fmt::Display for ProcessName {
+	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+		write!(f, "{:#?}", self)
+	}
+}
diff --git a/src/constants.rs b/src/constants.rs
index 62c4c26..3b41597 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -52,6 +52,9 @@ pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100);
 pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY;
 pub const BLACK: egui::Color32 = egui::Color32::BLACK;
 
+// [Duration] constants
+pub const SECOND: std::time::Duration = std::time::Duration::from_secs(1);
+
 // OS specific
 #[cfg(target_os = "windows")]
 pub const OS: &'static str = " Windows";
diff --git a/src/main.rs b/src/main.rs
index ec28943..45864d5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -60,6 +60,7 @@ mod gupax;
 mod p2pool;
 mod xmrig;
 mod update;
+mod command;
 use {ferris::*,constants::*,node::*,disk::*,status::*,update::*,gupax::*};
 
 //---------------------------------------------------------------------------------------------------- Struct + Impl
diff --git a/src/p2pool.rs b/src/p2pool.rs
index cdc758c..ab7f5e9 100644
--- a/src/p2pool.rs
+++ b/src/p2pool.rs
@@ -19,7 +19,8 @@ use crate::{
 	Regexes,
 	constants::*,
 	disk::*,
-	node::*
+	node::*,
+	command::*,
 };
 use egui::{
 	TextEdit,SelectableLabel,ComboBox,Label,Button,