From 15bbe9b8bc8e43e98a141a2b25bbeb973ccd39b7 Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Wed, 30 Nov 2022 17:21:55 -0500
Subject: [PATCH] helper: add initial struct, add [HumanTime] for formatting
 uptime

---
 src/helper.rs | 130 +++++++++++++++++++++++++++++++++++++++++++++-----
 src/main.rs   |  32 ++++++++-----
 2 files changed, 138 insertions(+), 24 deletions(-)

diff --git a/src/helper.rs b/src/helper.rs
index 8312ad5..2d4ccbf 100644
--- a/src/helper.rs
+++ b/src/helper.rs
@@ -39,11 +39,34 @@ use std::{
 	sync::{Arc,Mutex},
 	path::PathBuf,
 	process::Command,
+	time::*,
 	thread,
 };
 use crate::constants::*;
 use log::*;
 
+//---------------------------------------------------------------------------------------------------- [Helper] Struct
+// A meta struct holding all the data that gets processed in this thread
+pub struct Helper {
+	uptime: HumanTime,     // Gupax uptime formatting for humans
+	p2pool: Process,       // P2Pool process state
+	xmrig: Process,        // XMRig process state
+	p2pool_api: P2poolApi, // P2Pool API state
+	xmrig_api: XmrigApi,   // XMRig API state
+}
+
+impl Helper {
+	pub fn new(instant: std::time::Instant) -> Self {
+		Self {
+			uptime: HumanTime::into_human(instant.elapsed()),
+			p2pool: Process::new(ProcessName::P2pool, String::new(), PathBuf::new()),
+			xmrig: Process::new(ProcessName::Xmrig, String::new(), PathBuf::new()),
+			p2pool_api: P2poolApi::new(),
+			xmrig_api: XmrigApi::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.
@@ -51,6 +74,8 @@ pub struct Process {
 	name: ProcessName,     // P2Pool or XMRig?
 	state: ProcessState,   // The state of the process (alive, dead, etc)
 	signal: ProcessSignal, // Did the user click [Start/Stop/Restart]?
+	start: Instant,        // Starttime of process
+	uptime: HumanTime,     // Uptime of process
 	output: String,        // This is the process's stdout + stderr
 	// STDIN Problem:
 	//     - User can input many many commands in 1 second
@@ -68,10 +93,13 @@ pub struct Process {
 //---------------------------------------------------------------------------------------------------- [Process] Impl
 impl Process {
 	pub fn new(name: ProcessName, args: String, path: PathBuf) -> Self {
+		let now = Instant::now();
 		Self {
 			name,
 			state: ProcessState::Dead,
 			signal: ProcessSignal::None,
+			start: now,
+			uptime: HumanTime::into_human(now.elapsed()),
 			// P2Pool log level 1 produces a bit less than 100,000 lines a day.
 			// Assuming each line averages 80 UTF-8 scalars (80 bytes), then this
 			// initial buffer should last around a week (56MB) before resetting.
@@ -108,27 +136,105 @@ pub enum ProcessSignal {
 
 #[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 { write!(f, "{:#?}", self) } }
 
+//---------------------------------------------------------------------------------------------------- [HumanTime]
+// This converts a [std::time::Duration] into something more readable.
+// Used for uptime display purposes: [7 years, 8 months, 15 days, 23 hours, 35 minutes, 1 second]
+// Code taken from [https://docs.rs/humantime/] and edited to remove sub-second time, change spacing and some words.
+use std::time::Duration;
+
+#[derive(Debug, Clone)]
+pub struct HumanTime(Duration);
+
+impl HumanTime {
+	pub fn into_human(d: Duration) -> HumanTime {
+		HumanTime(d)
+	}
+
+	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("0s")?;
+			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 ref mut started = 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(())
+	}
+}
+
+//---------------------------------------------------------------------------------------------------- [P2poolApi]
+pub struct P2poolApi {
+
+}
+
+impl P2poolApi {
+	pub fn new() -> Self {
+		Self {
+		}
+	}
+}
+
+//---------------------------------------------------------------------------------------------------- [XmrigApi]
+pub struct XmrigApi {
+
+}
+
+impl XmrigApi {
+	pub fn new() -> Self {
+		Self {
+		}
+	}
+}
+
 //---------------------------------------------------------------------------------------------------- The "helper" loop
 #[tokio::main]
 pub async fn helper() {
 	thread::spawn(|| { loop {
-	// 1. Check process signals, match, do action (start, kill, read)
-	// 2. Spawn P2Pool API task
-	// 3. Spawn XMRig HTTP API task
-	// 4. 
-	// 5.
-	// 6.
-	// 7.
-	// 8.
-	// 9.
-	// 10.
+	// 1. Spawn child processes (if signal found)
+	// 2. Create stdout pipe thread (if new child process)
+	// 3. Send stdin (if signal found)
+	// 4. Kill child process (if signal found)
+	// 4. Collect P2Pool API task (if alive)
+	// 5. Collect XMRig HTTP API task (if alive)
+	// 6. Execute all async tasks
+	// 7. Set Gupax/P2Pool/XMRig uptime
 	}});
 }
diff --git a/src/main.rs b/src/main.rs
index 3362b70..52cbfc1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -61,7 +61,7 @@ mod p2pool;
 mod xmrig;
 mod update;
 mod helper;
-use {ferris::*,constants::*,node::*,disk::*,status::*,update::*,gupax::*};
+use {ferris::*,constants::*,node::*,disk::*,status::*,update::*,gupax::*,helper::*};
 
 //---------------------------------------------------------------------------------------------------- Struct + Impl
 // The state of the outer main [App].
@@ -98,13 +98,17 @@ pub struct App {
 	// indicate if an error message needs to be displayed
 	// (it takes up the whole screen with [error_msg] and buttons for ok/quit/etc)
 	error_state: ErrorState,
-	// Process State:
-	// This holds everything related to the
-	// child processes when starting P2Pool/XMRig.
+	// Helper State:
+	// This holds everything related to the data
+	// processed by the "helper thread", including
+	helper: Arc<Mutex<Helper>>,
+
+// Fix-me.
+// These shouldn't exist
+// Just for debugging.
 	p2pool: bool,
 	xmrig: bool,
-//	p2pool: Arc<Mutex<Process>>,
-//	xmrig: Arc<Mutex<Process>>,
+
 	// State from [--flags]
 	no_startup: bool,
 	// Static stuff
@@ -133,7 +137,7 @@ impl App {
 		}
 	}
 
-	fn new() -> Self {
+	fn new(now: Instant) -> Self {
 		info!("Initializing App Struct...");
 		let mut app = Self {
 			tab: Tab::default(),
@@ -153,12 +157,17 @@ impl App {
 			restart: Arc::new(Mutex::new(Restart::No)),
 			diff: false,
 			error_state: ErrorState::new(),
+			helper: Arc::new(Mutex::new(Helper::new(now))),
+
+// TODO
+// these p2pool/xmrig bools are here for debugging purposes
+// they represent the online/offline status.
+// fix this later when [Helper] is integrated.
+
 			p2pool: false,
 			xmrig: false,
-//			p2pool: Arc::new(Mutex::new(Process::new())),
-//			xmrig: Arc::new(Mutex::new(Process::new())),
 			no_startup: false,
-			now: Instant::now(),
+			now,
 			exe: String::new(),
 			dir: String::new(),
 			resolution: Vec2::new(APP_DEFAULT_HEIGHT, APP_DEFAULT_WIDTH),
@@ -664,8 +673,7 @@ fn print_disk_file(path: &PathBuf) {
 fn main() {
 	let now = Instant::now();
 	init_logger(now);
-	let mut app = App::new();
-	app.now = now;
+	let mut app = App::new(now);
 	init_auto(&mut app);
 	let initial_window_size = match app.state.gupax.simple {
 		true  => Some(Vec2::new(app.state.gupax.selected_width as f32, app.state.gupax.selected_height as f32)),