From ed7ddeeda10e113d3462473919a7500d44953801 Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Sat, 15 Oct 2022 15:15:27 -0400
Subject: [PATCH] connect functions with [State/state.rs]

---
 Cargo.lock                |   1 +
 src/README.md             |   4 +-
 src/main.rs               | 267 +++++++++++++++++---------------------
 src/node.rs               |  12 +-
 src/p2pool.rs             | 112 +++++-----------
 src/{toml.rs => state.rs} | 167 ++++++++++++++----------
 src/xmrig.rs              |  71 ++--------
 7 files changed, 273 insertions(+), 361 deletions(-)
 rename src/{toml.rs => state.rs} (63%)

diff --git a/Cargo.lock b/Cargo.lock
index 16b5c20..af76cea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1085,6 +1085,7 @@ dependencies = [
  "monero",
  "num-format",
  "num_cpus",
+ "rand",
  "regex",
  "reqwest",
  "serde",
diff --git a/src/README.md b/src/README.md
index 77d8607..a9d8e87 100644
--- a/src/README.md
+++ b/src/README.md
@@ -12,8 +12,8 @@
 | `main.rs`      | Struct/enum/impl for `App/Tab/State`, init functions, main function
 | `node.rs`      | Struct/impl for Community Nodes
 | `p2pool.rs`    | Struct/impl for `P2Pool` tab
+| `state.rs`     | Struct/impl for `gupax.toml`, the disk state
 | `status.rs`    | Struct/impl for `Status` tab
-| `toml.rs`      | Struct/impl for `gupax.toml`, the disk state
 | `xmrig.rs`     | Struct/impl for `XMRig` tab
 
 ## Bootstrap
@@ -42,7 +42,7 @@ This is how Gupax works internally when starting up, divided into 3 sections.
 	- Kill processes, kill connections, exit
 
 ## State
-Internal state is saved in the "OS data folder" as `gupax.toml`, using the [TOML](https://github.com/toml-lang/toml) format. If the version can't be parsed (not in the `vX.X.X` or `vX.X` format), the auto-updater will be skipped. [If not found, a default `gupax.toml` file will be created with `Toml::default`.](https://github.com/hinto-janaiyo/gupax/blob/main/src/toml.rs) Gupax will `panic!` if `gupax.toml` has IO or parsing issues.
+Internal state is saved in the "OS data folder" as `gupax.toml`, using the [TOML](https://github.com/toml-lang/toml) format. If the version can't be parsed (not in the `vX.X.X` or `vX.X` format), the auto-updater will be skipped. [If not found, a default `gupax.toml` file will be created with `State::default`.](https://github.com/hinto-janaiyo/gupax/blob/main/src/state.rs) Gupax will `panic!` if `gupax.toml` has IO or parsing issues.
 
 | OS       | Data Folder                              | Example                                                   |
 |----------|----------------------------------------- |-----------------------------------------------------------|
diff --git a/src/main.rs b/src/main.rs
index 6e516c2..cea0889 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,135 +16,98 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
-use eframe::{egui,NativeOptions};
-use egui::{Vec2,Pos2};
-use std::process::exit;
-use std::thread;
-use egui::color::Color32;
-use egui::Stroke;
-use egui::FontId;
-use egui::FontFamily::Proportional;
-use egui::TextStyle::{Body,Button,Heading,Monospace,Name,Small};
-use egui::RichText;
-use egui::Label;
-use regex::Regex;
-use egui_extras::RetainedImage;
-use log::*;
-use env_logger::Builder;
-use env_logger::WriteStyle;
-use std::io::Write;
-use std::time::Instant;
-use std::sync::{Arc,Mutex};
 
+//---------------------------------------------------------------------------------------------------- Imports
+// egui/eframe
+use egui::TextStyle::*;
+use egui::color::Color32;
+use egui::FontFamily::Proportional;
+use egui::{FontId,Label,RichText,Stroke,Vec2,Pos2};
+use egui_extras::RetainedImage;
+use eframe::{egui,NativeOptions};
+
+// Logging
+use log::*;
+use env_logger::{Builder,WriteStyle};
+
+// std
+use std::io::Write;
+use std::process::exit;
+use std::sync::{Arc,Mutex};
+use std::thread;
+use std::time::Instant;
+
+// Modules
 mod constants;
 mod node;
-mod toml;
+mod state;
 mod about;
 mod status;
 mod gupax;
 mod p2pool;
 mod xmrig;
-use {constants::*,node::*,crate::toml::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*};
+use {constants::*,node::*,state::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*};
 
-// The state of the outer [App].
-// See the [State] struct for the
-// actual inner state of the settings.
+//---------------------------------------------------------------------------------------------------- Struct + Impl
+// The state of the outer main [App].
+// See the [State] struct in [state.rs] for the
+// actual inner state of the tab settings.
 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?
+	node: Arc<Mutex<NodeStruct>>, // Data on community nodes
+	// State:
+	// og    = Old state to compare against
+	// state = Working state (current settings)
+	// Instead of comparing [og == state] every frame,
+	// the [diff] bool will be the signal for [Reset/Save].
+	og: State,
+	state: State,
+	diff: bool,
+	// Static stuff
+	now: Instant,
+	resolution: Vec2,
+	os: &'static str,
 	version: String,
 	name_version: String,
-	tab: Tab,
-	changed: bool,
-	os: &'static str,
-	current_threads: u16,
-	max_threads: u16,
-	resolution: Vec2,
 	banner: RetainedImage,
+
+	// TEMPORARY FIXME
 	p2pool: bool,
 	xmrig: bool,
-	state: State,
-	og: State,
-	allowed_to_close: bool,
-	show_confirmation_dialog: bool,
-	now: Instant,
-	ping: bool,
-	ping_prog: Arc<Mutex<bool>>,
-	node: Arc<Mutex<NodeStruct>>,
 }
 
 impl App {
 	fn new(cc: &eframe::CreationContext<'_>) -> Self {
-		let version = String::from("v0.0.1");
-		let name_version = String::from("Gupax v0.0.1");
-		let tab = Tab::default();
-		let max_threads = num_cpus::get().try_into().unwrap();
-		let current_threads: u16;
-		let changed = false;
-		let os = OS;
-		if max_threads != 1 {
-			current_threads = max_threads / 2
-		} else {
-			current_threads = 1
-		}
-		let resolution = cc.integration_info.window_info.size;
-		init_text_styles(&cc.egui_ctx, resolution[0] as f32);
-		let banner = match RetainedImage::from_image_bytes("banner.png", BYTES_BANNER) {
-			Ok(banner) => { info!("Banner loading ... OK"); banner },
-			Err(err) => { error!("{}", err); panic!("{}", err); },
-		};
-		let mut state = State::new();
-		let mut og = State::new();
-		info!("Frame resolution ... {:#?}", resolution);
 		Self {
-			version,
-			name_version,
-			tab,
-			current_threads,
-			max_threads,
-			changed,
-			resolution,
-			os,
-			banner,
-			p2pool: false,
-			xmrig: false,
-			state,
-			og,
-			allowed_to_close: false,
-			show_confirmation_dialog: false,
-			now: Instant::now(),
-			node: Arc::new(Mutex::new(NodeStruct::default())),
+			tab: Tab::default(),
+			quit: false,
+			quit_confirm: false,
 			ping: false,
 			ping_prog: Arc::new(Mutex::new(false)),
+			node: Arc::new(Mutex::new(NodeStruct::default())),
+			og: State::default(),
+			state: State::default(),
+			diff: false,
+			now: Instant::now(),
+			resolution: cc.integration_info.window_info.size,
+			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,
 		}
 	}
 }
 
-// Inner state holding all
-// mutable tab structs.
-#[derive(Clone, Debug, Eq, PartialEq)]
-struct State {
-	gupax: Gupax,
-	p2pool: P2pool,
-	xmrig: Xmrig,
-}
-
-impl State {
-	fn new() -> Self {
-		Self {
-			gupax: Gupax::new(),
-			p2pool: P2pool::new(),
-			xmrig: Xmrig::new(),
-		}
-	}
-
-	fn save(new: State) -> Self {
-		Self {
-			gupax: new.gupax,
-			p2pool: new.p2pool,
-			xmrig: new.xmrig,
-		}
-	}
-}
-
+//---------------------------------------------------------------------------------------------------- Enum + Impl
 // The tabs inside [App].
 #[derive(Clone, Copy, Debug, PartialEq)]
 enum Tab {
@@ -154,12 +117,14 @@ enum Tab {
 	P2pool,
 	Xmrig,
 }
+
 impl Default for Tab {
     fn default() -> Self {
         Self::About
     }
 }
 
+//---------------------------------------------------------------------------------------------------- Init functions
 fn init_text_styles(ctx: &egui::Context, width: f32) {
 	let scale = width / 26.666;
 	let mut style = (*ctx.style()).clone();
@@ -230,23 +195,7 @@ fn init_options() -> NativeOptions {
 	options
 }
 
-fn main() {
-	init_logger();
-	let toml = match Toml::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 options = init_options();
-	eframe::run_native("Gupax", options, Box::new(|cc| Box::new(App::new(cc))),);
-}
-
-// [App] frame for Panic situations.
+//---------------------------------------------------------------------------------------------------- [App] frame for [Panic] situations
 struct Panic { error_msg: String, }
 impl Panic {
 	fn options() -> NativeOptions {
@@ -288,15 +237,35 @@ 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 options = init_options();
+	eframe::run_native("Gupax", options, Box::new(|cc| Box::new(App::new(cc))),);
+}
+
 impl eframe::App for App {
 	fn on_close_event(&mut self) -> bool {
-//		self.show_confirmation_dialog = true;
-		self.ping = true;
-		self.allowed_to_close
+		self.quit = true;
+		self.quit_confirm
 	}
+
 	fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
+		init_text_styles(ctx, 1280.0);
+
 		// Close confirmation.
-		if self.show_confirmation_dialog {
+		if self.quit {
 			egui::CentralPanel::default().show(ctx, |ui| {
 				let width = ui.available_width();
 				let width = width - 10.0;
@@ -306,10 +275,10 @@ impl eframe::App for App {
 				ui.group(|ui| {
 					if ui.add_sized([width, height/10.0], egui::Button::new("Yes")).clicked() {
 						info!("Quit confirmation = yes ... goodbye!");
-						exit(0);
+						self.quit_confirm = true;
+						frame.close();
 					} else if ui.add_sized([width, height/10.0], egui::Button::new("No")).clicked() {
-						info!("Quit confirmation = no ... returning!");
-						self.show_confirmation_dialog = false;
+						self.quit = false;
 					}
 				});
 			});
@@ -329,25 +298,25 @@ impl eframe::App for App {
 			});
 		}
 
-		if *self.ping_prog.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.group(|ui| {
-					if ui.add_sized([width, height/10.0], egui::Button::new("Yes")).clicked() {
-						info!("Quit confirmation = yes ... goodbye!");
-						exit(0);
-					} else if ui.add_sized([width, height/10.0], egui::Button::new("No")).clicked() {
-						info!("Quit confirmation = no ... returning!");
-						self.show_confirmation_dialog = false;
-					}
-				});
-			});
-			return
-		}
+//		if *self.ping_prog.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.group(|ui| {
+//					if ui.add_sized([width, height/10.0], egui::Button::new("Yes")).clicked() {
+//						info!("Quit confirmation = yes ... goodbye!");
+//						exit(0);
+//					} else if ui.add_sized([width, height/10.0], egui::Button::new("No")).clicked() {
+//						info!("Quit confirmation = no ... returning!");
+//						self.show_confirmation_dialog = false;
+//					}
+//				});
+//			});
+//			return
+//		}
 
 		// Top: Tabs
 		egui::CentralPanel::default().show(ctx, |ui| {
@@ -390,6 +359,9 @@ 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 {
@@ -469,7 +441,8 @@ impl eframe::App for App {
 					Status::show(self, ctx, ui);
 	            }
 	            Tab::Gupax => {
-					Gupax::show(&mut self.state.gupax, ctx, ui);
+//					Gupax::show(self.state.gupax, ctx, ui);
+					exit(0);
 	            }
 	            Tab::P2pool => {
 					P2pool::show(&mut self.state.p2pool, ctx, ui);
diff --git a/src/node.rs b/src/node.rs
index c5ea977..9f2145a 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -55,12 +55,18 @@ pub struct Data {
 	pub ip: String,
 }
 
-#[derive(Copy,Clone,Debug,Deserialize,Serialize)]
+#[derive(Copy,Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
 pub enum NodeEnum {
 	C3pool, Cake, CakeEu, CakeUk, CakeUs, Monerujo, Rino,
 	Selsta, Seth, SupportXmr, SupportXmrIr, XmrVsBeast,
 }
 
+impl std::fmt::Display for NodeEnum {
+	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+		write!(f, "{:#?}", self)
+	}
+}
+
 #[derive(Debug)]
 pub struct PingResult {
 	pub node: NodeStruct,
@@ -146,7 +152,7 @@ impl NodeStruct {
 			};
 			let mut timeout = false;
 			let mut mid = Duration::new(0, 0);
-			let max = rng::thread_rng().gen_range(1..5);
+			let max = rand::thread_rng().gen_range(1..5);
 			for i in 1..=max {
 				let client = reqwest::blocking::ClientBuilder::new();
 				let client = reqwest::blocking::ClientBuilder::timeout(client, timeout_sec);
@@ -156,7 +162,7 @@ impl NodeStruct {
 				match client.post(http).json(&get_info).send() {
 					Ok(r) => mid += now.elapsed(),
 					Err(err) => {
-						error!("Timeout on [{}: {}] (over 5 seconds)", id, ip);
+						error!("Timeout on [{:#?}: {}] (over 5 seconds)", id, ip);
 						mid += timeout_sec;
 						timeout = true;
 					},
diff --git a/src/p2pool.rs b/src/p2pool.rs
index 2b84a88..5045249 100644
--- a/src/p2pool.rs
+++ b/src/p2pool.rs
@@ -16,77 +16,25 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::App;
-use monero::util::address::Address;
-use std::str::FromStr;
-use std::net::{IpAddr, Ipv4Addr, SocketAddr};
 use crate::constants::*;
+use crate::state::P2pool;
+use crate::node::NodeEnum;
+use crate::node::{RINO,SETH,SELSTA};
 
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum Node {
-	Rino,
-	Seth,
-	Selsta,
-}
-
-impl Node {
-	pub fn ip(self) -> String {
-		match self {
-			Node::Rino => String::from("node.community.rino.io:18081"),
-			Node::Seth => String::from("node.sethforprivacy.com:18089"),
-			Node::Selsta => String::from("selsta1.featherwallet.net:18081"),
-		}
-	}
-	pub fn name(self) -> String {
-		match self {
-			Node::Rino => String::from("Rino"),
-			Node::Seth => String::from("Seth"),
-			Node::Selsta => String::from("Selsta"),
-		}
-	}
-}
-
-// Main data structure for the P2Pool tab
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct P2pool {
-	pub version: String,
-	pub sha256: String,
-	pub manual: bool,
-	pub mini: bool,
-	pub rpc: String,
-	pub zmq: String,
+//	pub simple: bool,
+//	pub mini: bool,
+//	pub out_peers: u8,
+//	pub in_peers: u8,
+//	pub log_level: u8,
+//	pub node: crate::node::NodeEnum,
+//	pub monerod: String,
 //	pub rpc: u16,
 //	pub zmq: u16,
-	pub out_peers: u16,
-	pub in_peers: u16,
-	pub log: u8,
-	pub monerod: String,
-//	pub monerod: std::net::SocketAddr,
-	pub community: Node,
-	pub address: String,
-//	pub address: monero::util::address::Address,
-}
+//	pub address: String,
+
 
 impl P2pool {
-	pub fn new() -> Self {
-		Self {
-			version: String::from("v2.4"),
-			sha256: String::from("asdf"),
-			manual: false,
-			mini: true,
-			rpc: String::from(""),
-			zmq: String::from(""),
-			out_peers: 10,
-			in_peers: 10,
-			log: 3,
-//			monerod: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 18081),
-			monerod: String::from(""),
-			address: String::from(""),
-			community: Node::Rino,
-//			address: Address::from_str("44hintoFpuo3ugKfcqJvh5BmrsTRpnTasJmetKC4VXCt6QDtbHVuixdTtsm6Ptp7Y8haXnJ6j8Gj2dra8CKy5ewz7Vi9CYW").unwrap(),
-		}
-	}
-
-	pub fn show(state: &mut P2pool, ctx: &egui::Context, ui: &mut egui::Ui) {
+	pub fn show(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
 		let height = ui.available_height() / 10.0;
 		let mut width = ui.available_width() - 50.0;
 		ui.group(|ui| {
@@ -100,61 +48,61 @@ impl P2pool {
 		ui.horizontal(|ui| {
 			ui.group(|ui| { ui.vertical(|ui| {
 				ui.group(|ui| { ui.horizontal(|ui| {
-					if ui.add_sized([width/4.0, height/5.0], egui::SelectableLabel::new(state.mini == false, "P2Pool Main")).on_hover_text(P2POOL_MAIN).clicked() { state.mini = false; };
-					if ui.add_sized([width/4.0, height/5.0], egui::SelectableLabel::new(state.mini == true, "P2Pool Mini")).on_hover_text(P2POOL_MINI).clicked() { state.mini = true; };
+					if ui.add_sized([width/4.0, height/5.0], egui::SelectableLabel::new(self.mini == false, "P2Pool Main")).on_hover_text(P2POOL_MAIN).clicked() { self.mini = false; };
+					if ui.add_sized([width/4.0, height/5.0], egui::SelectableLabel::new(self.mini == true, "P2Pool Mini")).on_hover_text(P2POOL_MINI).clicked() { self.mini = true; };
 				})});
 
-				let width = (width/4.0);
+				let width = width/4.0;
 				style.spacing.slider_width = width*1.25;
 				ctx.set_style(style);
 				ui.horizontal(|ui| {
 					ui.add_sized([width/8.0, height/5.0], egui::Label::new("Out peers [10-450]:"));
-					ui.add_sized([width, height/5.0], egui::Slider::new(&mut state.out_peers, 10..=450)).on_hover_text(P2POOL_OUT);
+					ui.add_sized([width, height/5.0], egui::Slider::new(&mut self.out_peers, 10..=450)).on_hover_text(P2POOL_OUT);
 				});
 
 				ui.horizontal(|ui| {
 					ui.add_sized([width/8.0, height/5.0], egui::Label::new("    In peers [10-450]:"));
-					ui.add_sized([width, height/5.0], egui::Slider::new(&mut state.in_peers, 10..=450)).on_hover_text(P2POOL_IN);
+					ui.add_sized([width, height/5.0], egui::Slider::new(&mut self.in_peers, 10..=450)).on_hover_text(P2POOL_IN);
 				});
 
 				ui.horizontal(|ui| {
 					ui.add_sized([width/8.0, height/5.0], egui::Label::new("          Log level [0-6]:"));
-					ui.add_sized([width, height/5.0], egui::Slider::new(&mut state.log, 0..=6)).on_hover_text(P2POOL_LOG);
+					ui.add_sized([width, height/5.0], egui::Slider::new(&mut self.log_level, 0..=6)).on_hover_text(P2POOL_LOG);
 				});
 			})});
 
 		ui.group(|ui| { ui.vertical(|ui| {
 			ui.group(|ui| { ui.horizontal(|ui| {
-				if ui.add_sized([width/4.0, height/5.0], egui::SelectableLabel::new(state.manual == false, "Community Monero Node")).on_hover_text(P2POOL_COMMUNITY).clicked() { state.manual = false; };
-				if ui.add_sized([width/4.0, height/5.0], egui::SelectableLabel::new(state.manual == true, "Manual Monero Node")).on_hover_text(P2POOL_MANUAL).clicked() { state.manual = true; };
+				if ui.add_sized([width/4.0, height/5.0], egui::SelectableLabel::new(self.simple == false, "Community Monero Node")).on_hover_text(P2POOL_COMMUNITY).clicked() { self.simple = false; };
+				if ui.add_sized([width/4.0, height/5.0], egui::SelectableLabel::new(self.simple == true, "Manual Monero Node")).on_hover_text(P2POOL_MANUAL).clicked() { self.simple = true; };
 			})});
 			ui.add_space(8.0);
 			ui.horizontal(|ui| {
 //			ui.add_sized([width/8.0, height/5.0],
-			egui::ComboBox::from_label(Node::name(state.community.clone())).selected_text(Node::ip(state.community.clone())).show_ui(ui, |ui| {
-					ui.selectable_value(&mut state.community, Node::Rino, Node::ip(Node::Rino));
-					ui.selectable_value(&mut state.community, Node::Seth, Node::ip(Node::Seth));
-					ui.selectable_value(&mut state.community, Node::Selsta, Node::ip(Node::Selsta));
+			egui::ComboBox::from_label(self.node.to_string()).selected_text(RINO).show_ui(ui, |ui| {
+					ui.selectable_value(&mut self.node, NodeEnum::Rino, RINO);
+					ui.selectable_value(&mut self.node, NodeEnum::Seth, SETH);
+					ui.selectable_value(&mut self.node, NodeEnum::Selsta, SELSTA);
 			});
 //			);
 			});
 
-			if state.manual == false { ui.set_enabled(false); }
+			if self.simple == false { ui.set_enabled(false); }
 			let width = (width/4.0);
 			ui.horizontal(|ui| {
 				ui.add_sized([width/8.0, height/7.8], egui::Label::new("Monero Node IP:"));
 				ui.spacing_mut().text_edit_width = ui.available_width() - 35.0;
-				ui.text_edit_singleline(&mut state.monerod);
+				ui.text_edit_singleline(&mut self.monerod);
 			});
 			ui.horizontal(|ui| {
 				ui.add_sized([width/8.0, height/7.8], egui::Label::new("Monero Node RPC Port:"));
 				ui.spacing_mut().text_edit_width = ui.available_width() - 35.0;
-				ui.text_edit_singleline(&mut state.rpc);
+				ui.text_edit_singleline(&mut self.rpc.to_string());
 			});
 			ui.horizontal(|ui| {
 				ui.add_sized([width/8.0, height/7.8], egui::Label::new("Monero Node ZMQ Port:"));
 				ui.spacing_mut().text_edit_width = ui.available_width() - 35.0;
-				ui.text_edit_singleline(&mut state.zmq);
+				ui.text_edit_singleline(&mut self.zmq.to_string());
 			});
 
 		})});
@@ -164,7 +112,7 @@ impl P2pool {
 		ui.horizontal(|ui| {
 			ui.spacing_mut().text_edit_width = ui.available_width();
 			ui.label("Address:");
-			ui.text_edit_singleline(&mut state.address);
+			ui.text_edit_singleline(&mut self.address);
 		})});
 	}
 }
diff --git a/src/toml.rs b/src/state.rs
similarity index 63%
rename from src/toml.rs
rename to src/state.rs
index ff19174..712f384 100644
--- a/src/toml.rs
+++ b/src/state.rs
@@ -18,7 +18,7 @@
 // This handles reading/parsing the state file: [gupax.toml]
 // The TOML format is used. This struct hierarchy directly
 // translates into the TOML parser:
-//   Toml/
+//   State/
 //   ├─ Gupax/
 //   │  ├─ ...
 //   ├─ P2pool/
@@ -31,13 +31,12 @@
 use std::{fs,env};
 use std::fmt::Display;
 use std::path::{Path,PathBuf};
+use std::result::Result;
 use serde_derive::{Serialize,Deserialize};
 use log::*;
 
 //---------------------------------------------------------------------------------------------------- Impl
-// Since [State] is already used in [main.rs] to represent
-// working state, [Toml] is used to represent disk state.
-impl Toml {
+impl State {
 	pub fn default() -> Self {
 		use crate::constants::{P2POOL_VERSION,XMRIG_VERSION};
 		Self {
@@ -64,7 +63,9 @@ impl Toml {
 				tls: false,
 				nicehash: false,
 				keepalive: false,
-				threads: 1,
+				hugepages_jit: true,
+				current_threads: 1,
+				max_threads: 1,
 				priority: 2,
 				pool: "localhost:3333".to_string(),
 				address: "".to_string(),
@@ -76,7 +77,7 @@ impl Toml {
 		}
 	}
 
-	pub fn get() -> Result<Toml, TomlError> {
+	pub fn get_path() -> Result<PathBuf, TomlError> {
 		// Get OS data folder
 		// Linux   | $XDG_DATA_HOME or $HOME/.local/share | /home/alice/.local/state
 		// macOS   | $HOME/Library/Application Support    | /Users/Alice/Library/Application Support
@@ -89,36 +90,67 @@ impl Toml {
 			},
 			None => { error!("Couldn't get OS PATH for data"); return Err(TomlError::Path(PATH_ERROR.to_string())) },
 		};
-
 		// Create directory
 		fs::create_dir_all(&path)?;
-
-		// Attempt to read file, create default if not found
 		path.push(FILENAME);
-		let file = match fs::read_to_string(&path) {
-			Ok(file) => file,
+		info!("TOML path ... {}", path.display());
+		Ok(path)
+	}
+
+	// Attempts to read [gupax.toml] or
+	// attempts to create if not found.
+	pub fn read_or_create(path: PathBuf) -> Result<String, TomlError> {
+		// Attempt to read file, create default if not found
+		match fs::read_to_string(&path) {
+			Ok(string) => {
+				info!("TOML read ... OK");
+				Ok(string)
+			},
 			Err(err) => {
 				error!("TOML not found, attempting to create default");
-				let default = match toml::ser::to_string(&Toml::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)?;
+				fs::write(&path, &default)?;
 				info!("TOML write ... OK");
-				fs::read_to_string(&path)?
+				Ok(fs::read_to_string(default)?)
 			},
-		};
-		info!("TOML path ... {}", path.display());
-		info!("TOML read ... OK");
+		}
+	}
 
-		// Attempt to parse, return Result
-		match toml::from_str(&file) {
+	// Attempt to parse from String
+	pub fn parse(string: String) -> Result<State, TomlError> {
+		match toml::de::from_str(&string) {
 			Ok(toml) => {
-			info!("TOML parse ... OK");
-				eprint!("{}", file);
+				info!("TOML parse ... OK");
+				eprint!("{}", string);
 				Ok(toml)
 			},
-			Err(err) => { error!("Couldn't parse TOML file"); Err(TomlError::Parse(err)) },
+			Err(err) => { error!("Couldn't parse TOML from string"); Err(TomlError::Deserialize(err)) },
+		}
+	}
+
+	// Last three functions combined
+	// get_path() -> read_or_create() -> parse()
+//	pub fn get() -> Result<State, TomlError> {
+//		let path = Self::path();
+//	}
+
+	// Overwrite disk Toml with memory State (save state)
+	pub fn overwrite(state: State, path: PathBuf) -> Result<(), TomlError> {
+		info!("Starting TOML overwrite...");
+		let string = match toml::ser::to_string(&state) {
+			Ok(string) => {
+				info!("TOML parse ... OK");
+				eprint!("{}", string);
+				string
+			},
+			Err(err) => { error!("Couldn't parse TOML into string"); return Err(TomlError::Serialize(err)) },
+		};
+		match fs::write(&path, string) {
+			Ok(_) => { info!("TOML overwrite ... OK"); Ok(()) },
+			Err(err) => { error!("Couldn't overwrite TOML file"); return Err(TomlError::Io(err)) },
 		}
 	}
 }
@@ -129,8 +161,8 @@ impl Display for TomlError {
 		match self {
 			Io(err) => write!(f, "{} | {}", ERROR, err),
 			Path(err) => write!(f, "{} | {}", ERROR, err),
-			Parse(err) => write!(f, "{} | {}", ERROR, err),
 			Serialize(err) => write!(f, "{} | {}", ERROR, err),
+			Deserialize(err) => write!(f, "{} | {}", ERROR, err),
 		}
 	}
 }
@@ -141,17 +173,10 @@ impl From<std::io::Error> for TomlError {
 	}
 }
 
-fn main() {
-	let state = match Toml::get() {
-		Ok(state) => { println!("OK"); state },
-		Err(err) => panic!(),
-	};
-}
-
 //---------------------------------------------------------------------------------------------------- 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")]
@@ -176,55 +201,57 @@ const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig";
 pub enum TomlError {
 	Io(std::io::Error),
 	Path(String),
-	Parse(toml::de::Error),
 	Serialize(toml::ser::Error),
+	Deserialize(toml::de::Error),
 }
 
 //---------------------------------------------------------------------------------------------------- Structs
-#[derive(Debug,Deserialize,Serialize)]
-pub struct Toml {
-	gupax: Gupax,
-	p2pool: P2pool,
-	xmrig: Xmrig,
-	version: Version,
+#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+pub struct State {
+	pub gupax: Gupax,
+	pub p2pool: P2pool,
+	pub xmrig: Xmrig,
+	pub version: Version,
 }
 
-#[derive(Debug,Deserialize,Serialize)]
-struct Gupax {
-	auto_update: bool,
-	ask_before_quit: bool,
-	p2pool_path: String,
-	xmrig_path: String,
+#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+pub struct Gupax {
+	pub auto_update: bool,
+	pub ask_before_quit: bool,
+	pub p2pool_path: String,
+	pub xmrig_path: String,
 }
 
-#[derive(Debug,Deserialize,Serialize)]
-struct P2pool {
-	simple: bool,
-	mini: bool,
-	out_peers: u8,
-	in_peers: u8,
-	log_level: u8,
-	node: crate::node::NodeEnum,
-	monerod: String,
-	rpc: u16,
-	zmq: u16,
-	address: String,
+#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+pub struct P2pool {
+	pub simple: bool,
+	pub mini: bool,
+	pub out_peers: u16,
+	pub in_peers: u16,
+	pub log_level: u8,
+	pub node: crate::node::NodeEnum,
+	pub monerod: String,
+	pub rpc: u16,
+	pub zmq: u16,
+	pub address: String,
 }
 
-#[derive(Debug,Deserialize,Serialize)]
-struct Xmrig {
-	simple: bool,
-	tls: bool,
-	nicehash: bool,
-	keepalive: bool,
-	threads: u16,
-	priority: u8,
-	pool: String,
-	address: String,
+#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+pub struct Xmrig {
+	pub simple: bool,
+	pub tls: bool,
+	pub nicehash: bool,
+	pub keepalive: bool,
+	pub hugepages_jit: bool,
+	pub max_threads: u16,
+	pub current_threads: u16,
+	pub priority: u8,
+	pub pool: String,
+	pub address: String,
 }
 
-#[derive(Debug,Deserialize,Serialize)]
-struct Version {
-	p2pool: String,
-	xmrig: String,
+#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
+pub struct Version {
+	pub p2pool: String,
+	pub xmrig: String,
 }
diff --git a/src/xmrig.rs b/src/xmrig.rs
index 0a3c4d9..f84b072 100644
--- a/src/xmrig.rs
+++ b/src/xmrig.rs
@@ -20,54 +20,11 @@ use monero::util::address::Address;
 use std::str::FromStr;
 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
 use num_cpus;
-use crate::State;
 use crate::constants::*;
-
-// Main data structure for the XMRig tab
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Xmrig {
-	pub version: String,
-	pub sha256: String,
-	pub manual: bool,
-	pub tls: bool,
-	pub nicehash: bool,
-	pub keepalive: bool,
-	pub hugepages_jit: bool,
-	pub threads: u16,
-	pub priority: u8,
-	pub pool: String,
-	pub address: String,
-//	pub pool: std::net::SocketAddr,
-//	pub address: monero::util::address::Address,
-	pub max_threads: u16,
-	pub current_threads: u16,
-}
+use crate::state::Xmrig;
 
 impl Xmrig {
-	pub fn new() -> Self {
-		let max_threads = num_cpus::get().try_into().unwrap();
-		let current_threads: u16;
-		if max_threads == 1 { current_threads = 1 } else { current_threads = max_threads/2 }
-		Self {
-			version: String::from("v6.18.0"),
-			sha256: String::from("asdf"),
-			manual: false,
-			tls: false,
-			nicehash: false,
-			keepalive: false,
-			hugepages_jit: true,
-			threads: 16,
-			priority: 2,
-			pool: String::from(""),
-			address: String::from(""),
-//			pool: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 3333),
-//			address: Address::from_str("44hintoFpuo3ugKfcqJvh5BmrsTRpnTasJmetKC4VXCt6QDtbHVuixdTtsm6Ptp7Y8haXnJ6j8Gj2dra8CKy5ewz7Vi9CYW").unwrap(),
-			max_threads,
-			current_threads,
-		}
-	}
-
-	pub fn show(state: &mut Xmrig, ctx: &egui::Context, ui: &mut egui::Ui) {
+	pub fn show(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) {
 		let height = ui.available_height() / 10.0;
 		let mut width = ui.available_width() - 10.0;
 		ui.group(|ui| {
@@ -81,18 +38,18 @@ impl Xmrig {
 		ui.horizontal(|ui| {
 			ui.group(|ui| { ui.vertical(|ui| {
 				ui.group(|ui| { ui.horizontal(|ui| {
-					if ui.add_sized([width/2.0, height/6.0], egui::SelectableLabel::new(state.manual == false, "P2Pool Mode")).on_hover_text(XMRIG_P2POOL).clicked() { state.manual = false; };
-					if ui.add_sized([width/2.0, height/6.0], egui::SelectableLabel::new(state.manual == true, "Manual Mode")).on_hover_text(XMRIG_MANUAL).clicked() { state.manual = true; };
+					if ui.add_sized([width/2.0, height/6.0], egui::SelectableLabel::new(self.simple == false, "P2Pool Mode")).on_hover_text(XMRIG_P2POOL).clicked() { self.simple = false; };
+					if ui.add_sized([width/2.0, height/6.0], egui::SelectableLabel::new(self.simple == true, "Manual Mode")).on_hover_text(XMRIG_MANUAL).clicked() { self.simple = true; };
 				})});
 				ui.group(|ui| { ui.horizontal(|ui| {
 					let width = width - 58.0;
-					ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut state.tls, "TLS Connection")).on_hover_text(XMRIG_TLS);
+					ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut self.tls, "TLS Connection")).on_hover_text(XMRIG_TLS);
 					ui.separator();
-					ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut state.hugepages_jit, "Hugepages JIT")).on_hover_text(XMRIG_HUGEPAGES_JIT);
+					ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut self.hugepages_jit, "Hugepages JIT")).on_hover_text(XMRIG_HUGEPAGES_JIT);
 					ui.separator();
-					ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut state.nicehash, "Nicehash")).on_hover_text(XMRIG_NICEHASH);
+					ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut self.nicehash, "Nicehash")).on_hover_text(XMRIG_NICEHASH);
 					ui.separator();
-					ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut state.keepalive, "Keepalive")).on_hover_text(XMRIG_KEEPALIVE);
+					ui.add_sized([width/4.0, height/6.0], egui::Checkbox::new(&mut self.keepalive, "Keepalive")).on_hover_text(XMRIG_KEEPALIVE);
 				})});
 			})});
 		});
@@ -101,28 +58,28 @@ impl Xmrig {
 			style.spacing.slider_width = ui.available_width()/1.25;
 			ctx.set_style(style);
 			ui.horizontal(|ui| {
-				ui.add_sized([width/8.0, height/8.0], egui::Label::new(format!("Threads [1-{}]:", state.max_threads)));
-				ui.add_sized([width, height/8.0], egui::Slider::new(&mut state.current_threads, 1..=state.max_threads)).on_hover_text(XMRIG_THREADS);
+				ui.add_sized([width/8.0, height/8.0], egui::Label::new(format!("Threads [1-{}]:", self.max_threads)));
+				ui.add_sized([width, height/8.0], egui::Slider::new(&mut self.current_threads, 1..=self.max_threads)).on_hover_text(XMRIG_THREADS);
 			});
 
 			ui.horizontal(|ui| {
 				ui.add_sized([width/8.0, height/8.0], egui::Label::new("CPU Priority [0-5]:"));
-				ui.add_sized([width, height/8.0], egui::Slider::new(&mut state.priority, 0..=5)).on_hover_text(XMRIG_PRIORITY);
+				ui.add_sized([width, height/8.0], egui::Slider::new(&mut self.priority, 0..=5)).on_hover_text(XMRIG_PRIORITY);
 			});
 //		});
 
 //		ui.group(|ui| {
-			if state.manual == false { ui.set_enabled(false); }
+			if self.simple == false { ui.set_enabled(false); }
 			let width = (width/4.0);
 			ui.horizontal(|ui| {
 				ui.add_sized([width/8.0, height/8.0], egui::Label::new("Pool IP:"));
 				ui.spacing_mut().text_edit_width = ui.available_width() - 35.0;
-				ui.text_edit_singleline(&mut state.pool);
+				ui.text_edit_singleline(&mut self.pool);
 			});
 			ui.horizontal(|ui| {
 				ui.add_sized([width/8.0, height/8.0], egui::Label::new("Address:"));
 				ui.spacing_mut().text_edit_width = ui.available_width() - 35.0;
-				ui.text_edit_singleline(&mut state.address);
+				ui.text_edit_singleline(&mut self.address);
 			});
 //		});
 	}