From 9fcb750ddcb5189ddf3e8c695ef95a13cdbc94fc Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Sun, 16 Oct 2022 17:29:24 -0400
Subject: [PATCH] add basic [--flags]

---
 src/constants.rs |  19 +++-
 src/main.rs      | 242 +++++++++++++++++++++++++++++++----------------
 src/node.rs      |  89 +++++++----------
 src/state.rs     |  36 ++++---
 4 files changed, 236 insertions(+), 150 deletions(-)

diff --git a/src/constants.rs b/src/constants.rs
index 977adb5..4cda9a6 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -15,7 +15,7 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-// These are the versions bundled with Gupax.
+pub const GUPAX_VERSION: &'static str = "v0.1.0";
 pub const P2POOL_VERSION: &'static str = "v2.4";
 pub const XMRIG_VERSION: &'static str = "v6.18.0";
 
@@ -23,6 +23,7 @@ pub const BYTES_ICON: &[u8] = include_bytes!("../images/png/icon.png");
 pub const BYTES_BANNER: &[u8] = include_bytes!("../images/png/banner.png");
 pub const P2POOL_BASE_ARGS: &'static str = "";
 pub const XMRIG_BASE_ARGS: &'static str = "--http-host=127.0.0.1 --http-port=18088 --algo=rx/0 --coin=Monero --randomx-cache-qos";
+pub const HORIZONTAL: &'static str = "--------------------------------------------";
 
 // OS specific
 #[cfg(target_os = "windows")]
@@ -66,3 +67,19 @@ pub const XMRIG_NICEHASH: &'static str = "Enable nicehash.com support";
 pub const XMRIG_KEEPALIVE: &'static str = "Send keepalived packet to prevent timeout (needs pool support)";
 pub const XMRIG_THREADS: &'static str = "Number of CPU threads to use for mining";
 pub const XMRIG_PRIORITY: &'static str = "Set process priority (0 idle, 2 normal to 5 highest)";
+
+// CLI argument messages
+pub const ARG_HELP: &'static str =
+r#"USAGE: gupax [--flags]
+
+    -h | --help              Print this help message
+    -v | --version           Print versions
+    -n | --no-startup        Disable auto-update/node connections at startup
+    -r | --reset             Reset all Gupax configuration/state"#;
+pub const ARG_COPYRIGHT: &'static str =
+r#"For more information:
+https://github.com/hinto-janaiyo/gupax
+https://github.com/SChernykh/p2pool
+https://github.com/xmrig/xmrig
+
+Gupax, P2Pool, and XMRig are licensed under GPLv3."#;
diff --git a/src/main.rs b/src/main.rs
index cea0889..f84a2a7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,8 +34,9 @@ use env_logger::{Builder,WriteStyle};
 use std::io::Write;
 use std::process::exit;
 use std::sync::{Arc,Mutex};
-use std::thread;
+use std::{thread,env};
 use std::time::Instant;
+use std::path::PathBuf;
 
 // Modules
 mod constants;
@@ -55,10 +56,10 @@ use {constants::*,node::*,state::*,about::*,status::*,gupax::*,p2pool::*,xmrig::
 pub struct App {
 	// Misc state
 	tab: Tab, // What tab are we on?
-	quit: bool, // Did user click quit button?
-	quit_confirm: bool, // Did user confirm to quit?
-	ping: bool, // Did user click the ping button?
-	ping_prog: Arc<Mutex<bool>>, // Are we in the progress of pinging?
+	quit: bool, // Was the quit button clicked?
+	quit_confirm: bool, // Was the quit confirmed?
+	ping: bool, // Was the ping button clicked?
+	pinging: Arc<Mutex<bool>>, // Is a ping in progress?
 	node: Arc<Mutex<NodeStruct>>, // Data on community nodes
 	// State:
 	// og    = Old state to compare against
@@ -68,42 +69,58 @@ pub struct App {
 	og: State,
 	state: State,
 	diff: bool,
+	// Process/update state:
+	// Doesn't make sense to save this on disk
+	// so it's represented as a bool here.
+	p2pool: bool, // Is p2pool online?
+	xmrig: bool, // Is xmrig online?
+	updating: bool, // Is an update in progress?
+	// State from [--flags]
+	startup: bool,
+	reset: bool,
 	// Static stuff
-	now: Instant,
-	resolution: Vec2,
-	os: &'static str,
-	version: String,
-	name_version: String,
-	banner: RetainedImage,
-
-	// TEMPORARY FIXME
-	p2pool: bool,
-	xmrig: bool,
+	now: Instant, // Internal timer
+	resolution: Vec2, // Frame resolution
+	os: &'static str, // OS
+	version: String, // Gupax version
+	name_version: String, // [Gupax vX.X.X]
+	banner: RetainedImage, // Gupax banner image
 }
 
 impl App {
-	fn new(cc: &eframe::CreationContext<'_>) -> Self {
+	fn cc(cc: &eframe::CreationContext<'_>, app: Self) -> Self {
+		let resolution = cc.integration_info.window_info.size;
+		init_text_styles(&cc.egui_ctx, resolution[0] as f32);
 		Self {
+			resolution,
+			..app
+		}
+	}
+
+	fn default() -> Self {
+		let app = Self {
 			tab: Tab::default(),
 			quit: false,
 			quit_confirm: false,
 			ping: false,
-			ping_prog: Arc::new(Mutex::new(false)),
+			pinging: Arc::new(Mutex::new(false)),
 			node: Arc::new(Mutex::new(NodeStruct::default())),
 			og: State::default(),
 			state: State::default(),
 			diff: false,
+			p2pool: false,
+			xmrig: false,
+			updating: false,
+			startup: true,
+			reset: false,
 			now: Instant::now(),
-			resolution: cc.integration_info.window_info.size,
+			resolution: Vec2::new(1280.0, 720.0),
 			os: OS,
 			version: "v0.0.1".to_string(),
 			name_version: "Gupax v0.0.1".to_string(),
 			banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).expect("oops"),
-
-			// TEMPORARY FIXME
-			p2pool: false,
-			xmrig: false,
-		}
+		};
+		parse_args(app)
 	}
 }
 
@@ -153,6 +170,10 @@ fn init_text_styles(ctx: &egui::Context, width: f32) {
 }
 
 fn init_logger() {
+	#[cfg(debug_assertions)]
+	let filter = LevelFilter::Info;
+	#[cfg(not(debug_assertions))]
+	let filter = LevelFilter::Warn;
 	use env_logger::fmt::Color;
 	Builder::new().format(|buf, record| {
 		let level;
@@ -173,7 +194,7 @@ fn init_logger() {
 			buf.style().set_dimmed(true).value(record.line().unwrap_or(0)),
 			record.args(),
 		)
-	}).filter_level(LevelFilter::Info).write_style(WriteStyle::Always).parse_default_env().format_timestamp_millis().init();
+	}).filter_level(filter).write_style(WriteStyle::Always).parse_default_env().format_timestamp_millis().init();
 	info!("init_logger() ... OK");
 }
 
@@ -195,6 +216,45 @@ fn init_options() -> NativeOptions {
 	options
 }
 
+//---------------------------------------------------------------------------------------------------- Misc functions
+fn into_absolute_path(path: String) -> Result<PathBuf, std::io::Error> {
+	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)
+	}
+}
+
+fn parse_args(mut app: App) -> App {
+	info!("Parsing CLI arguments...");
+	let mut args: Vec<String> = env::args().collect();
+	if args.len() == 1 { info!("No args ... OK"); return app } else { args.remove(0); info!("Args ... {:?}", args); }
+	// [help/version], exit early
+	for arg in &args {
+		match arg.as_str() {
+			"-h"|"--help"    => { println!("{}", ARG_HELP); exit(0); },
+			"-v"|"--version" => {
+				println!("Gupax  | {}\nP2Pool | {}\nXMRig  | {}\n\n{}", GUPAX_VERSION, P2POOL_VERSION, XMRIG_VERSION, ARG_COPYRIGHT);
+				exit(0);
+			},
+			_ => (),
+		}
+	}
+	// Everything else
+	for arg in args {
+		match arg.as_str() {
+			"-n"|"--no-startup" => { info!("Disabling startup..."); app.startup = false; }
+			"-r"|"--reset" => { info!("Resetting state..."); app.reset = true; }
+			_ => { eprintln!("[Gupax error] Invalid option: [{}]\nFor help, use: [--help]", arg); exit(1); },
+		}
+	}
+	app
+}
+
 //---------------------------------------------------------------------------------------------------- [App] frame for [Panic] situations
 struct Panic { error_msg: String, }
 impl Panic {
@@ -240,19 +300,19 @@ impl eframe::App for Panic {
 //---------------------------------------------------------------------------------------------------- Main [App] frame
 fn main() {
 	init_logger();
-//	let toml = match State::get() {
-//		Ok(toml) => toml,
-//		Err(err) => {
-//			error!("{}", err);
-//			let error_msg = err.to_string();
-//			let options = Panic::options();
-//			eframe::run_native("Gupax", options, Box::new(|cc| Box::new(Panic::new(cc, error_msg))),);
-//			exit(1);
-//		},
-//	};
-	let state = State::default();
+	let app = App::default();
 	let options = init_options();
-	eframe::run_native("Gupax", options, Box::new(|cc| Box::new(App::new(cc))),);
+	let toml = match State::get() {
+		Ok(toml) => toml,
+		Err(err) => {
+			error!("{}", err);
+			let error_msg = err.to_string();
+			let options = Panic::options();
+			eframe::run_native("Gupax", options, Box::new(|cc| Box::new(Panic::new(cc, error_msg))),);
+			exit(1);
+		},
+	};
+	eframe::run_native("Gupax", options, Box::new(|cc| Box::new(App::cc(cc, app))),);
 }
 
 impl eframe::App for App {
@@ -262,49 +322,75 @@ impl eframe::App for App {
 	}
 
 	fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
-		init_text_styles(ctx, 1280.0);
+//		init_text_styles(ctx, 1280.0);
 
 		// Close confirmation.
 		if self.quit {
 			egui::CentralPanel::default().show(ctx, |ui| {
+				init_text_styles(ctx, ui.available_width());
 				let width = ui.available_width();
 				let width = width - 10.0;
 				let height = ui.available_height();
-				init_text_styles(ctx, width);
-				ui.add_sized([width, height/2.0], Label::new("Are you sure you want to quit?"));
-				ui.group(|ui| {
-					if ui.add_sized([width, height/10.0], egui::Button::new("Yes")).clicked() {
-						info!("Quit confirmation = yes ... goodbye!");
-						self.quit_confirm = true;
-						frame.close();
-					} else if ui.add_sized([width, height/10.0], egui::Button::new("No")).clicked() {
-						self.quit = false;
+				// Detect processes or update
+				if self.p2pool || self.xmrig {
+					ui.add_sized([width, height/6.0], Label::new("Are you sure you want to quit?"));
+					if self.p2pool { ui.add_sized([width, height/6.0], Label::new("P2Pool is online...!")); }
+					if self.xmrig { ui.add_sized([width, height/6.0], Label::new("XMRig is online...!")); }
+				// Else, just quit
+				} else {
+					if self.state.gupax.save_before_quit {
+						info!("Saving before quit...");
+						match self.state.save() {
+							Err(err) => { error!("{}", err); exit(1); },
+							_ => (),
+						};
 					}
+					info!("No processes or update in progress ... goodbye!");
+					exit(0);
+				}
+				egui::TopBottomPanel::bottom("quit").show(ctx, |ui| {
+					ui.group(|ui| {
+						if ui.add_sized([width, height/8.0], egui::Button::new("Yes")).clicked() {
+							if self.state.gupax.save_before_quit {
+								info!("Saving before quit...");
+								match self.state.save() {
+									Err(err) => { error!("{}", err); exit(1); },
+									_ => (),
+								};
+							}
+							info!("Quit confirmation = yes ... goodbye!");
+							self.quit_confirm = true;
+							exit(0);
+						} else if ui.add_sized([width, height/8.0], egui::Button::new("No")).clicked() {
+							self.quit = false;
+						}
+					});
 				});
 			});
 			return
 		}
 
-		//
+		// If ping was pressed, start thread
 		if self.ping {
 			self.ping = false;
-			self.ping_prog = Arc::new(Mutex::new(true));
+			self.pinging = Arc::new(Mutex::new(true));
 			let node_clone = Arc::clone(&self.node);
-			let prog_clone = Arc::clone(&self.ping_prog);
+			let pinging_clone = Arc::clone(&self.pinging);
 			thread::spawn(move|| {
 				let result = NodeStruct::ping();
-				*node_clone.lock().unwrap() = result.node;
-				*prog_clone.lock().unwrap() = false;
+				*node_clone.lock().unwrap() = result.nodes;
+				*pinging_clone.lock().unwrap() = false;
 			});
 		}
 
-//		if *self.ping_prog.lock().unwrap() {
+		// If ping-ING, display stats
+//		if *self.pinging.lock().unwrap() {
 //			egui::CentralPanel::default().show(ctx, |ui| {
 //				let width = ui.available_width();
 //				let width = width - 10.0;
 //				let height = ui.available_height();
 //				init_text_styles(ctx, width);
-//				ui.add_sized([width, height/2.0], Label::new(format!("In progress: {}", *self.ping_prog.lock().unwrap())));
+//				ui.add_sized([width, height/2.0], Label::new(format!("In progress: {}", *self.pinging.lock().unwrap())));
 //				ui.group(|ui| {
 //					if ui.add_sized([width, height/10.0], egui::Button::new("Yes")).clicked() {
 //						info!("Quit confirmation = yes ... goodbye!");
@@ -324,8 +410,8 @@ impl eframe::App for App {
 			let width = (ui.available_width() - 90.0) / 5.0;
 			let height = ui.available_height() / 10.0;
 		    ui.add_space(4.0);
-			ui.style_mut().override_text_style = Some(Name("Tab".into()));
 			ui.horizontal(|ui| {
+				ui.style_mut().override_text_style = Some(Name("Tab".into()));
 				ui.style_mut().visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100);
 				ui.style_mut().visuals.selection.bg_fill = Color32::from_rgb(255, 120, 120);
 				ui.style_mut().visuals.selection.stroke = Stroke {
@@ -346,7 +432,6 @@ impl eframe::App for App {
 			ui.separator();
 //		});
 
-
 		let height = height / 2.0;
 		// Bottom: app info + state/process buttons
 		egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| {
@@ -359,9 +444,6 @@ impl eframe::App for App {
 					ui.add_sized([width, height], Label::new(self.os));
 					ui.separator();
 					ui.add_sized([width/1.5, height], Label::new("P2Pool"));
-// TODO
-// self.p2pool + self.xmrig
-// This is for process online/offline status
 					if self.p2pool == true {
 						ui.add_sized([width/4.0, height], Label::new(RichText::new("⏺").color(Color32::from_rgb(100, 230, 100))));
 					} else {
@@ -428,31 +510,25 @@ impl eframe::App for App {
 			});
 		});
 
-		// Central: tab contents
-		// Docs say to add central last, don't think it matters here but whatever:
-		// [https://docs.rs/egui/latest/egui/containers/panel/struct.TopBottomPanel.html]
-//        egui::TopBottomPanel::bottom("2").show(ctx, |ui| {
-			ui.style_mut().override_text_style = Some(egui::TextStyle::Body);
-	        match self.tab {
-	            Tab::About => {
-					About::show(self, ctx, ui);
-	            }
-	            Tab::Status => {
-					Status::show(self, ctx, ui);
-	            }
-	            Tab::Gupax => {
-//					Gupax::show(self.state.gupax, ctx, ui);
-					exit(0);
-	            }
-	            Tab::P2pool => {
-					P2pool::show(&mut self.state.p2pool, ctx, ui);
-	            }
-	            Tab::Xmrig => {
-					Xmrig::show(&mut self.state.xmrig, ctx, ui);
-	            }
-	        }
-//        });
-
+		ui.style_mut().override_text_style = Some(egui::TextStyle::Body);
+        match self.tab {
+            Tab::About => {
+				About::show(self, ctx, ui);
+            }
+            Tab::Status => {
+				Status::show(self, ctx, ui);
+            }
+            Tab::Gupax => {
+//				Gupax::show(self.state.gupax, ctx, ui);
+				exit(0);
+            }
+            Tab::P2pool => {
+				P2pool::show(&mut self.state.p2pool, ctx, ui);
+            }
+            Tab::Xmrig => {
+				Xmrig::show(&mut self.state.xmrig, ctx, ui);
+            }
+        }
 		});
 	}
 }
diff --git a/src/node.rs b/src/node.rs
index 9f2145a..85732f5 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -41,6 +41,11 @@ pub const SUPPORTXMR: &'static str = "node.supportxmr.com:18081";
 pub const SUPPORTXMR_IR: &'static str = "node.supportxmr.ir:18081";
 pub const XMRVSBEAST: &'static str = "p2pmd.xmrvsbeast.com:18081";
 
+pub const NODE_IPS: [&'static str; 12] = [
+	C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,MONERUJO,RINO,
+	SELSTA,SETH,SUPPORTXMR,SUPPORTXMR_IR,XMRVSBEAST,
+];
+
 #[derive(Debug)]
 pub struct NodeStruct {
 	c3pool: Data, cake: Data, cake_eu: Data, cake_uk: Data, cake_us: Data, monerujo: Data,
@@ -52,7 +57,7 @@ pub struct Data {
 	pub ms: u128,
 	pub color: Color32,
 	pub id: NodeEnum,
-	pub ip: String,
+	pub ip: &'static str,
 }
 
 #[derive(Copy,Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
@@ -69,39 +74,31 @@ impl std::fmt::Display for NodeEnum {
 
 #[derive(Debug)]
 pub struct PingResult {
-	pub node: NodeStruct,
+	pub nodes: NodeStruct,
 	pub fastest: NodeEnum,
 }
 
+use crate::NodeEnum::*;
 impl NodeStruct {
 	pub fn default() -> Self {
-		use crate::NodeEnum::*;
 		let ms = 0;
 		let color = Color32::GRAY;
 		Self {
-			c3pool:        Data { ms, color, id: C3pool,       ip: C3POOL.to_string(), },
-			cake:          Data { ms, color, id: Cake,         ip: CAKE.to_string(), },
-			cake_eu:       Data { ms, color, id: CakeEu,       ip: CAKE_EU.to_string(), },
-			cake_uk:       Data { ms, color, id: CakeUk,       ip: CAKE_UK.to_string(), },
-			cake_us:       Data { ms, color, id: CakeUs,       ip: CAKE_US.to_string(), },
-			monerujo:      Data { ms, color, id: Monerujo,     ip: MONERUJO.to_string(), },
-			rino:          Data { ms, color, id: Rino,         ip: RINO.to_string(), },
-			selsta:        Data { ms, color, id: Selsta,       ip: SELSTA.to_string(), },
-			seth:          Data { ms, color, id: Seth,         ip: SETH.to_string(), },
-			supportxmr:    Data { ms, color, id: SupportXmr,   ip: SUPPORTXMR.to_string(), },
-			supportxmr_ir: Data { ms, color, id: SupportXmrIr, ip: SUPPORTXMR_IR.to_string(), },
-			xmrvsbeast:    Data { ms, color, id: XmrVsBeast,   ip: XMRVSBEAST.to_string(), },
+			c3pool:        Data { ms, color, id: C3pool,       ip: C3POOL, },
+			cake:          Data { ms, color, id: Cake,         ip: CAKE, },
+			cake_eu:       Data { ms, color, id: CakeEu,       ip: CAKE_EU, },
+			cake_uk:       Data { ms, color, id: CakeUk,       ip: CAKE_UK, },
+			cake_us:       Data { ms, color, id: CakeUs,       ip: CAKE_US, },
+			monerujo:      Data { ms, color, id: Monerujo,     ip: MONERUJO, },
+			rino:          Data { ms, color, id: Rino,         ip: RINO, },
+			selsta:        Data { ms, color, id: Selsta,       ip: SELSTA, },
+			seth:          Data { ms, color, id: Seth,         ip: SETH, },
+			supportxmr:    Data { ms, color, id: SupportXmr,   ip: SUPPORTXMR, },
+			supportxmr_ir: Data { ms, color, id: SupportXmrIr, ip: SUPPORTXMR_IR, },
+			xmrvsbeast:    Data { ms, color, id: XmrVsBeast,   ip: XMRVSBEAST, },
 		}
 	}
 
-	// Return array of all IPs
-	fn array() -> [&'static str; 12] {
-		[
-			C3POOL,CAKE,CAKE_EU,CAKE_UK,CAKE_US,MONERUJO,RINO,
-			SELSTA,SETH,SUPPORTXMR,SUPPORTXMR_IR,XMRVSBEAST,
-		]
-	}
-
 	// This is for pinging the community nodes to
 	// find the fastest/slowest one for the user.
 	// The process:
@@ -123,9 +120,8 @@ impl NodeStruct {
 	// timeout = BLACK
 	// default = GRAY
 	pub fn ping() -> PingResult {
-		use crate::NodeEnum::*;
 		info!("Starting community node pings...");
-		let mut node = NodeStruct::default();
+		let mut nodes = NodeStruct::default();
 		let mut get_info = HashMap::new();
 		get_info.insert("jsonrpc", "2.0");
 		get_info.insert("id", "0");
@@ -134,7 +130,7 @@ impl NodeStruct {
 		let fastest = false;
 		let timeout_sec = Duration::from_millis(5000);
 
-		for ip in Self::array().iter() {
+		for ip in NODE_IPS.iter() {
 			let id = match *ip {
 				C3POOL        => C3pool,
 				CAKE          => Cake,
@@ -182,18 +178,18 @@ impl NodeStruct {
 				color = Color32::LIGHT_GREEN
 			}
 			match id {
-				C3pool       => { node.c3pool.ms = ms; node.c3pool.color = color; },
-				Cake         => { node.cake.ms = ms; node.cake.color = color; },
-				CakeEu       => { node.cake_eu.ms = ms; node.cake_eu.color = color; },
-				CakeUk       => { node.cake_uk.ms = ms; node.cake_uk.color = color; },
-				CakeUs       => { node.cake_us.ms = ms; node.cake_us.color = color; },
-				Monerujo     => { node.monerujo.ms = ms; node.monerujo.color = color; },
-				Rino         => { node.rino.ms = ms; node.rino.color = color; },
-				Selsta       => { node.selsta.ms = ms; node.selsta.color = color; },
-				Seth         => { node.seth.ms = ms; node.seth.color = color; },
-				SupportXmr   => { node.supportxmr.ms = ms; node.supportxmr.color = color; },
-				SupportXmrIr => { node.supportxmr_ir.ms = ms; node.supportxmr_ir.color = color; },
-				XmrVsBeast   => { node.xmrvsbeast.ms = ms; node.xmrvsbeast.color = color; },
+				C3pool       => { nodes.c3pool.ms = ms; nodes.c3pool.color = color; },
+				Cake         => { nodes.cake.ms = ms; nodes.cake.color = color; },
+				CakeEu       => { nodes.cake_eu.ms = ms; nodes.cake_eu.color = color; },
+				CakeUk       => { nodes.cake_uk.ms = ms; nodes.cake_uk.color = color; },
+				CakeUs       => { nodes.cake_us.ms = ms; nodes.cake_us.color = color; },
+				Monerujo     => { nodes.monerujo.ms = ms; nodes.monerujo.color = color; },
+				Rino         => { nodes.rino.ms = ms; nodes.rino.color = color; },
+				Selsta       => { nodes.selsta.ms = ms; nodes.selsta.color = color; },
+				Seth         => { nodes.seth.ms = ms; nodes.seth.color = color; },
+				SupportXmr   => { nodes.supportxmr.ms = ms; nodes.supportxmr.color = color; },
+				SupportXmrIr => { nodes.supportxmr_ir.ms = ms; nodes.supportxmr_ir.color = color; },
+				XmrVsBeast   => { nodes.xmrvsbeast.ms = ms; nodes.xmrvsbeast.color = color; },
 			}
 		}
 		let mut best_ms: u128 = vec[0].0;
@@ -208,22 +204,7 @@ impl NodeStruct {
 		// The values don't update if not printed beforehand,
 		// so the match below on [fastest] gets funky.
 		info!("Fastest node ... {:#?} @ {:#?}ms", fastest, best_ms);
-		let ip = match fastest {
-			C3pool       => C3POOL,
-			Cake         => CAKE,
-			CakeEu       => CAKE_EU,
-			CakeUk       => CAKE_UK,
-			CakeUs       => CAKE_US,
-			Monerujo     => MONERUJO,
-			Rino         => RINO,
-			Selsta       => SELSTA,
-			Seth         => SETH,
-			SupportXmr   => SUPPORTXMR,
-			SupportXmrIr => SUPPORTXMR_IR,
-			XmrVsBeast   => XMRVSBEAST,
-		};
-		info!("Using IP ... {}", ip);
 		info!("Community node ping ... OK");
-		PingResult { node, fastest, }
+		PingResult { nodes, fastest, }
 	}
 }
diff --git a/src/state.rs b/src/state.rs
index 712f384..4a8366e 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -33,6 +33,7 @@ use std::fmt::Display;
 use std::path::{Path,PathBuf};
 use std::result::Result;
 use serde_derive::{Serialize,Deserialize};
+use crate::constants::HORIZONTAL;
 use log::*;
 
 //---------------------------------------------------------------------------------------------------- Impl
@@ -43,6 +44,7 @@ impl State {
 			gupax: Gupax {
 				auto_update: true,
 				ask_before_quit: true,
+				save_before_quit: true,
 				p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
 				xmrig_path: DEFAULT_XMRIG_PATH.to_string(),
 			},
@@ -107,14 +109,14 @@ impl State {
 				Ok(string)
 			},
 			Err(err) => {
-				error!("TOML not found, attempting to create default");
+				warn!("TOML not found, attempting to create default");
 				let default = match toml::ser::to_string(&State::default()) {
 						Ok(o) => { info!("TOML serialization ... OK"); o },
 						Err(e) => { error!("Couldn't serialize default TOML file: {}", e); return Err(TomlError::Serialize(e)) },
 				};
 				fs::write(&path, &default)?;
 				info!("TOML write ... OK");
-				Ok(fs::read_to_string(default)?)
+				Ok(default)
 			},
 		}
 	}
@@ -124,7 +126,9 @@ impl State {
 		match toml::de::from_str(&string) {
 			Ok(toml) => {
 				info!("TOML parse ... OK");
-				eprint!("{}", string);
+				info!("{}", HORIZONTAL);
+				for i in string.lines() { info!("{}", i); }
+				info!("{}", HORIZONTAL);
 				Ok(toml)
 			},
 			Err(err) => { error!("Couldn't parse TOML from string"); Err(TomlError::Deserialize(err)) },
@@ -133,22 +137,25 @@ impl State {
 
 	// Last three functions combined
 	// get_path() -> read_or_create() -> parse()
-//	pub fn get() -> Result<State, TomlError> {
-//		let path = Self::path();
-//	}
+	pub fn get() -> Result<State, TomlError> {
+		Self::parse(Self::read_or_create(Self::get_path()?)?)
+	}
 
-	// Overwrite disk Toml with memory State (save state)
-	pub fn overwrite(state: State, path: PathBuf) -> Result<(), TomlError> {
+	// Save [State] onto disk file [gupax.toml]
+	pub fn save(&self) -> Result<(), TomlError> {
+		let path = Self::get_path()?;
 		info!("Starting TOML overwrite...");
-		let string = match toml::ser::to_string(&state) {
+		let string = match toml::ser::to_string(&self) {
 			Ok(string) => {
 				info!("TOML parse ... OK");
-				eprint!("{}", string);
+				info!("{}", HORIZONTAL);
+				for i in string.lines() { info!("{}", i); }
+				info!("{}", HORIZONTAL);
 				string
 			},
 			Err(err) => { error!("Couldn't parse TOML into string"); return Err(TomlError::Serialize(err)) },
 		};
-		match fs::write(&path, string) {
+		match fs::write(path, string) {
 			Ok(_) => { info!("TOML overwrite ... OK"); Ok(()) },
 			Err(err) => { error!("Couldn't overwrite TOML file"); return Err(TomlError::Io(err)) },
 		}
@@ -176,7 +183,7 @@ impl From<std::io::Error> for TomlError {
 //---------------------------------------------------------------------------------------------------- Const
 const FILENAME: &'static str = "gupax.toml";
 const ERROR: &'static str = "TOML Error";
-	const PATH_ERROR: &'static str = "PATH for state directory could not be not found";
+const PATH_ERROR: &'static str = "PATH for state directory could not be not found";
 #[cfg(target_os = "windows")]
 const DIRECTORY: &'static str = "Gupax";
 #[cfg(target_os = "macos")]
@@ -218,6 +225,7 @@ pub struct State {
 pub struct Gupax {
 	pub auto_update: bool,
 	pub ask_before_quit: bool,
+	pub save_before_quit: bool,
 	pub p2pool_path: String,
 	pub xmrig_path: String,
 }
@@ -234,6 +242,8 @@ pub struct P2pool {
 	pub rpc: u16,
 	pub zmq: u16,
 	pub address: String,
+//	pub config: String,
+//	pub args: String,
 }
 
 #[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
@@ -248,6 +258,8 @@ pub struct Xmrig {
 	pub priority: u8,
 	pub pool: String,
 	pub address: String,
+//	pub config: String,
+//	pub args: String,
 }
 
 #[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]