From cb2db2e8ef3bc6bf679837cc14726728d25c735d Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Wed, 16 Nov 2022 14:07:27 -0500
Subject: [PATCH] main: implement [ErrorState] checks in main()

---
 src/constants.rs |  10 +--
 src/disk.rs      |  25 ++++--
 src/main.rs      | 196 +++++++++++++++++++++--------------------------
 src/p2pool.rs    |   3 +-
 4 files changed, 113 insertions(+), 121 deletions(-)

diff --git a/src/constants.rs b/src/constants.rs
index d43877d..5bff82b 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -60,11 +60,11 @@ pub const HUGEPAGES_1GB: bool = true;
 
 // Tooltips
 // Gupax
-pub const GUPAX_UPDATE: &'static str = "Check for update on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically";
+pub const GUPAX_UPDATE: &'static str = "Check for updates on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically";
 pub const GUPAX_AUTO_UPDATE: &'static str = "Automatically check for updates at startup";
 pub const GUPAX_UPDATE_VIA_TOR: &'static str = "Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required";
 pub const GUPAX_AUTO_NODE: &'static str = "Automatically ping the community Monero nodes and select the fastest at startup for P2Pool";
-pub const GUPAX_ASK_BEFORE_QUIT: &'static str = "Ask before quitting if processes are still alive or if an update is in progress";
+pub const GUPAX_ASK_BEFORE_QUIT: &'static str = "Ask before quitting Gupax";
 pub const GUPAX_SAVE_BEFORE_QUIT: &'static str = "Automatically save any changed settings before quitting";
 pub const GUPAX_PATH_P2POOL: &'static str = "The location of the P2Pool binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path";
 pub const GUPAX_PATH_XMRIG: &'static str = "The location of the XMRig binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path";
@@ -94,9 +94,9 @@ r#"Use advanced settings:
     - Out/In peer setting
     - Log level setting"#;
 pub const P2POOL_NAME: &'static str = "Add a unique name to identify this node; Only [A-Za-z0-9-_] and spaces allowed; Max length = 30 characters";
-pub const P2POOL_NODE_IP: &'static str = "Specify the Monero Node IP to connect to with P2Pool; Max length = 255 characters";
-pub const P2POOL_RPC_PORT: &'static str = "Specify the RPC port of the Monero node; [0-65535]";
-pub const P2POOL_ZMQ_PORT: &'static str = "Specify the ZMQ port of the Monero node; [0-65535]";
+pub const P2POOL_NODE_IP: &'static str = "Specify the Monero Node IP to connect to with P2Pool; It must be a valid IPv4 address or a valid domain name; Max length = 255 characters";
+pub const P2POOL_RPC_PORT: &'static str = "Specify the RPC port of the Monero node; [1-65535]";
+pub const P2POOL_ZMQ_PORT: &'static str = "Specify the ZMQ port of the Monero node; [1-65535]";
 pub const P2POOL_ADD: &'static str = "Add the current values to the list";
 pub const P2POOL_DELETE: &'static str = "Delete the currently selected node";
 pub const P2POOL_CLEAR: &'static str = "Clear all current values";
diff --git a/src/disk.rs b/src/disk.rs
index 1473d2e..b4e6615 100644
--- a/src/disk.rs
+++ b/src/disk.rs
@@ -50,11 +50,24 @@ use log::*;
 // create_new()         | Write a default TOML Struct into the appropriate file (in OS data path)
 // into_absolute_path() | Convert relative -> absolute path
 
-pub fn get_file_path(file: File) -> Result<PathBuf, TomlError> {
+pub fn get_os_data_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
 	// Windows | {FOLDERID_RoamingAppData}            | C:\Users\Alice\AppData\Roaming
+	let mut path = match dirs::data_dir() {
+		Some(mut path) => {
+			info!("OS | Data path ... OK");
+			path
+		},
+		None => { error!("OS | Data path ... FAIL"); return Err(TomlError::Path(PATH_ERROR.to_string())) },
+	};
+	// Create directory
+	fs::create_dir_all(&path)?;
+	Ok(path)
+}
+
+pub fn get_file_path(file: File) -> Result<PathBuf, TomlError> {
 	let name = File::name(&file);
 
 	let mut path = match dirs::data_dir() {
@@ -217,22 +230,22 @@ impl State {
 
 	// Save [State] onto disk file [gupax.toml]
 	pub fn save(&mut self) -> Result<(), TomlError> {
-		info!("Saving {:?} to disk...", self);
+		info!("State | Saving to disk...");
 		let path = get_file_path(File::State)?;
 		// Convert path to absolute
 		self.gupax.absolute_p2pool_path = into_absolute_path(self.gupax.p2pool_path.clone())?;
 		self.gupax.absolute_xmrig_path = into_absolute_path(self.gupax.xmrig_path.clone())?;
 		let string = match toml::ser::to_string(&self) {
 			Ok(string) => {
-				info!("TOML parse ... OK");
+				info!("State | Parse ... OK");
 				print_toml(&string);
 				string
 			},
-			Err(err) => { error!("Couldn't parse TOML into string"); return Err(TomlError::Serialize(err)) },
+			Err(err) => { error!("State | Couldn't parse TOML into string ... FAIL"); return Err(TomlError::Serialize(err)) },
 		};
 		match fs::write(path, string) {
-			Ok(_) => { info!("TOML save ... OK"); Ok(()) },
-			Err(err) => { error!("Couldn't overwrite TOML file"); return Err(TomlError::Io(err)) },
+			Ok(_) => { info!("State | Save ... OK"); Ok(()) },
+			Err(err) => { error!("State | Couldn't overwrite TOML file ... FAIL"); return Err(TomlError::Io(err)) },
 		}
 	}
 
diff --git a/src/main.rs b/src/main.rs
index 9a76822..5f2913b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -64,8 +64,7 @@ use {ferris::*,constants::*,node::*,disk::*,status::*,gupax::*,p2pool::*,xmrig::
 pub struct App {
 	// Misc state
 	tab: Tab, // What tab are we on?
-	quit: bool, // Was the quit button clicked?
-	quit_confirm: bool, // Was the quit confirmed?
+//	quit: bool, // Was the quit confirmed?
 	ping: Arc<Mutex<Ping>>, // Ping data found in [node.rs]
 	width: f32, // Top-level width
 	height: f32, // Top-level height
@@ -95,9 +94,10 @@ pub struct App {
 	dir: String, // Directory [Gupax] binary is in
 	resolution: Vec2, // Frame resolution
 	os: &'static str, // OS
+	os_data_path: PathBuf, // OS data path (e.g: ~/.local/share/gupax)
 	version: &'static str, // Gupax version
 	name_version: String, // [Gupax vX.X.X]
-	image: Images, // Custom Struct holding pre-compiled bytes of [Images]
+	img: Images, // Custom Struct holding pre-compiled bytes of [Images]
 	regex: Regexes, // Custom Struct holding pre-made [Regex]'s
 }
 
@@ -114,8 +114,7 @@ impl App {
 	fn new() -> Self {
 		let app = Self {
 			tab: Tab::default(),
-			quit: false,
-			quit_confirm: false,
+//			quit: false,
 			ping: Arc::new(Mutex::new(Ping::new())),
 			width: 1280.0,
 			height: 720.0,
@@ -135,9 +134,10 @@ impl App {
 			dir: "".to_string(),
 			resolution: Vec2::new(1280.0, 720.0),
 			os: OS,
+			os_data_path: PathBuf::new(),
 			version: GUPAX_VERSION,
 			name_version: format!("Gupax {}", GUPAX_VERSION),
-			image: Images::new(),
+			img: Images::new(),
 			regex: Regexes::new(),
 		};
 		// Apply arg state
@@ -152,6 +152,11 @@ impl App {
 			Ok(dir) => dir,
 			Err(err) => { panic_main(err.to_string()); exit(1); },
 		};
+		// Get OS data path
+		app.os_data_path = match get_os_data_path() {
+			Ok(dir) => dir,
+			Err(err) => { panic_main(err.to_string()); exit(1); },
+		};
 		// Read disk state if no [--reset] arg
 		if app.reset == false {
 			app.og = match State::get() {
@@ -200,8 +205,8 @@ impl Default for Tab {
 //---------------------------------------------------------------------------------------------------- [ErrorState] struct
 pub struct ErrorState {
 	error: bool, // Is there an error?
-	msg: String, // What message to display?
-	image: RetainedImage, // Which ferris to display?
+	msg: &'static str, // What message to display?
+	ferris: ErrorFerris, // Which ferris to display?
 	buttons: ErrorButtons, // Which buttons to display?
 }
 
@@ -209,27 +214,39 @@ impl ErrorState {
 	pub fn new() -> Self {
 		Self {
 			error: false,
-			msg: String::new(),
-			image: RetainedImage::from_image_bytes("banner.png", FERRIS_ERROR).unwrap(),
+			msg: "Unknown Error",
+			ferris: ErrorFerris::Oops,
 			buttons: ErrorButtons::Okay,
 		}
 	}
 
 	// Convenience function to set the [App] error state
-	pub fn set(mut self, new: Self) {
-		self = new;
+	pub fn set(&mut self, error: bool, msg: &'static str, ferris: ErrorFerris, buttons: ErrorButtons) {
+		*self = Self {
+			error,
+			msg,
+			ferris,
+			buttons,
+		};
 	}
 }
 
 //---------------------------------------------------------------------------------------------------- [ErrorButtons] enum
 #[derive(Clone, Copy, Debug, PartialEq)]
-enum ErrorButtons {
+pub enum ErrorButtons {
 	YesNo,
-	YesQuit,
+	StayQuit,
 	Okay,
 	Quit,
 }
 
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ErrorFerris {
+	Oops,
+	Error,
+	Panic,
+}
+
 //---------------------------------------------------------------------------------------------------- [Images] struct
 struct Images {
 	banner: RetainedImage,
@@ -242,9 +259,9 @@ impl Images {
 	fn new() -> Self {
 		Self {
 			banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).unwrap(),
-			oops: RetainedImage::from_image_bytes("banner.png", FERRIS_OOPS).unwrap(),
-			error: RetainedImage::from_image_bytes("banner.png", FERRIS_ERROR).unwrap(),
-			panic: RetainedImage::from_image_bytes("banner.png", FERRIS_PANIC).unwrap(),
+			oops: RetainedImage::from_image_bytes("oops.png", FERRIS_OOPS).unwrap(),
+			error: RetainedImage::from_image_bytes("error.png", FERRIS_ERROR).unwrap(),
+			panic: RetainedImage::from_image_bytes("panic.png", FERRIS_PANIC).unwrap(),
 		}
 	}
 }
@@ -566,15 +583,23 @@ fn main() {
 	let mut app = App::new();
 	app.now = now;
 	init_auto(&app);
-	info!("Initialization DONE ... {} seconds", now.elapsed().as_secs_f32());
+	info!("Init ... DONE ... Took [{}] seconds", now.elapsed().as_secs_f32());
 	eframe::run_native(&app.name_version.clone(), options, Box::new(|cc| Box::new(App::cc(cc, app))),);
 }
 
 impl eframe::App for App {
+//	pub fn new() -> Self {
+//		Self {
+//			error: false,
+//			msg: String::new(),
+//			image: RetainedImage::from_image_bytes("banner.png", FERRIS_ERROR).unwrap(),
+//			buttons: ErrorButtons::Okay,
+//		}
+//	}
 	fn on_close_event(&mut self) -> bool {
-		self.quit = true;
-		if self.og.lock().unwrap().gupax.ask_before_quit {
-			self.quit_confirm
+		if self.state.gupax.ask_before_quit {
+			self.error_state.set(true, "", ErrorFerris::Oops, ErrorButtons::StayQuit);
+			false
 		} else {
 			true
 		}
@@ -608,23 +633,49 @@ impl eframe::App for App {
 				let width = self.width;
 				let height = self.height/4.0;
 				ui.style_mut().override_text_style = Some(Name("MonospaceLarge".into()));
-				self.error_state.image.show_max_size(ui, Vec2::new(width, height));
 
-				ui.add_sized([width, height], Label::new("--- Gupax has encountered an error ---"));
-				ui.add_sized([width, height], Label::new(&self.error_state.msg));
+				// Display ferris
+				use ErrorFerris::*;
+				let ferris = match self.error_state.ferris {
+					Oops => &self.img.oops,
+					Error => &self.img.error,
+					Panic => &self.img.panic,
+				};
+				ferris.show_max_size(ui, Vec2::new(width, height));
+
+				// Error/Quit screen
+				match self.error_state.buttons {
+					StayQuit => {
+						let mut text = "--- Are you sure you want to quit? ---".to_string();
+						if *self.update.lock().unwrap().updating.lock().unwrap() { text = format!("{}\nUpdate is in progress...!", text); }
+						if self.p2pool { text = format!("{}\nP2Pool is online...!", text); }
+						if self.xmrig { text = format!("{}\nXMRig is online...!", text); }
+						ui.add_sized([width, height], Label::new(text))
+					},
+					_ => ui.add_sized([width, height], Label::new("--- Gupax has encountered an error! ---")),
+				};
+				ui.add_sized([width, height], Label::new(self.error_state.msg));
 				use ErrorButtons::*;
 				let height = ui.available_height();
+
+				// Capture [Esc] key
+				let esc = ctx.input_mut().consume_key(Modifiers::NONE, Key::Escape);
+
 				match self.error_state.buttons {
 					YesNo   => {
 						if ui.add_sized([width, height/2.0], egui::Button::new("Yes")).clicked() { self.error_state = ErrorState::new(); }
-						if ui.add_sized([width, height/2.0], egui::Button::new("No")).clicked() { exit(1); }
+						// If [Esc] was pressed, assume [No]
+				        if esc || ui.add_sized([width, height/2.0], egui::Button::new("No")).clicked() { exit(0); }
 					},
-					YesQuit => {
-						if ui.add_sized([width, height/2.0], egui::Button::new("Okay")).clicked() { self.error_state = ErrorState::new(); }
-						if ui.add_sized([width, height/2.0], egui::Button::new("Quit")).clicked() { exit(1); }
+					StayQuit => {
+						// If [Esc] was pressed, assume [Stay]
+				        if esc || ui.add_sized([width, height/2.0], egui::Button::new("Stay")).clicked() {
+							self.error_state = ErrorState::new();
+						}
+						if ui.add_sized([width, height/2.0], egui::Button::new("Quit")).clicked() { exit(0); }
 					},
-					Okay    => if ui.add_sized([width, height], egui::Button::new("Okay")).clicked() { self.error_state = ErrorState::new(); },
-					Quit    => if ui.add_sized([width, height], egui::Button::new("Quit")).clicked() { exit(1); },
+					Okay => if esc || ui.add_sized([width, height], egui::Button::new("Okay")).clicked() { self.error_state = ErrorState::new(); },
+					Quit => if ui.add_sized([width, height], egui::Button::new("Quit")).clicked() { exit(0); },
 				}
 			})});
 			return
@@ -647,80 +698,6 @@ impl eframe::App for App {
 		}
 		drop(og);
 
-		// Close confirmation.
-		if self.quit {
-			// If [ask_before_quit == true]
-			if self.og.lock().unwrap().gupax.ask_before_quit {
-				egui::TopBottomPanel::bottom("quit").show(ctx, |ui| {
-					let width = self.width;
-					let height = self.height/8.0;
-					ui.group(|ui| {
-						if ui.add_sized([width, height], egui::Button::new("Yes")).clicked() {
-							if self.og.lock().unwrap().gupax.save_before_quit {
-								if self.diff {
-									info!("Saving before quit...");
-									match self.state.save() {
-										Err(err) => { error!("{}", err); exit(1); },
-										_ => (),
-									};
-								} else {
-									info!("No changed detected, not saving...");
-								}
-							}
-							info!("Quit confirmation = yes ... goodbye!");
-							exit(0);
-						} else if ui.add_sized([width, height], egui::Button::new("No")).clicked() {
-							self.quit = false;
-						}
-					});
-				});
-				egui::CentralPanel::default().show(ctx, |ui| {
-					let width = self.width;
-					let height = ui.available_height();
-					let ten = height/10.0;
-					// Detect processes or update
-					ui.add_space(ten);
-					if *self.update.lock().unwrap().updating.lock().unwrap() || self.p2pool || self.xmrig {
-						ui.add_sized([width, height/4.0], Label::new("Are you sure you want to quit?"));
-						if *self.update.lock().unwrap().updating.lock().unwrap() { ui.add_sized([width, ten], Label::new("Update is in progress...!")); }
-						if self.p2pool { ui.add_sized([width, ten], Label::new("P2Pool is online...!")); }
-						if self.xmrig { ui.add_sized([width, ten], Label::new("XMRig is online...!")); }
-					// Else, just quit
-					} else {
-						if self.og.lock().unwrap().gupax.save_before_quit {
-							if self.diff {
-								info!("Saving before quit...");
-								match self.state.save() {
-									Err(err) => { error!("{}", err); exit(1); },
-									_ => (),
-								};
-							} else {
-								info!("No changed detected, not saving...");
-							}
-						}
-						info!("No processes or update in progress ... goodbye!");
-						exit(0);
-					}
-				});
-			// Else, quit (save if [save_before_quit == true]
-			} else {
-				if self.og.lock().unwrap().gupax.save_before_quit {
-					if self.diff {
-						info!("Saving before quit...");
-						match self.state.save() {
-							Err(err) => { error!("{}", err); exit(1); },
-							_ => (),
-						};
-					} else {
-						info!("No changed detected, not saving...");
-					}
-				}
-				info!("Quit confirmation = yes ... goodbye!");
-				exit(0);
-			}
-			return
-		}
-
 		// Top: Tabs
 		egui::TopBottomPanel::top("top").show(ctx, |ui| {
 			let width = (self.width - (SPACE*10.0))/5.0;
@@ -728,10 +705,11 @@ impl eframe::App for App {
 			ui.group(|ui| {
 			    ui.add_space(4.0);
 				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 { width: 5.0, color: Color32::from_rgb(255, 255, 255) };
+					let style = ui.style_mut();
+					style.override_text_style = Some(Name("Tab".into()));
+					style.visuals.widgets.inactive.fg_stroke.color = Color32::from_rgb(100, 100, 100);
+					style.visuals.selection.bg_fill = Color32::from_rgb(255, 120, 120);
+					style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) };
 					if ui.add_sized([width, height], egui::SelectableLabel::new(self.tab == Tab::About, "About")).clicked() { self.tab = Tab::About; }
 					ui.separator();
 					if ui.add_sized([width, height], egui::SelectableLabel::new(self.tab == Tab::Status, "Status")).clicked() { self.tab = Tab::Status; }
@@ -868,7 +846,7 @@ impl eframe::App for App {
 					ui.add_space(10.0);
 					ui.vertical_centered(|ui| {
 						// Display [Gupax] banner at max, 1/4 the available length
-						self.image.banner.show_max_size(ui, Vec2::new(self.width, self.height/4.0));
+						self.img.banner.show_max_size(ui, Vec2::new(self.width, self.height/4.0));
 						ui.label("Gupax is a cross-platform GUI for mining");
 						ui.hyperlink_to("[Monero]", "https://www.github.com/monero-project/monero");
 						ui.label("on the decentralized");
diff --git a/src/p2pool.rs b/src/p2pool.rs
index 391e2ab..cf22cfb 100644
--- a/src/p2pool.rs
+++ b/src/p2pool.rs
@@ -298,6 +298,7 @@ impl P2pool {
 						zmq: self.zmq.clone(),
 					};
 					node_vec.push((self.name.clone(), node));
+					info!("Node | Added [index: {}, name: \"{}\", ip: \"{}\", rpc: {}, zmq: {}]", node_vec_len+1, self.name, self.ip, self.rpc, self.zmq);
 				}
 			});
 			ui.horizontal(|ui| {
@@ -314,7 +315,7 @@ impl P2pool {
 								_ => { self.selected_name = node_vec[n-1].0.clone(); self.selected_index = n as u16; },
 							};
 							node_vec.remove(n);
-							info!("Node | Removed index [{}. {}]", n+1, self.selected_name);
+							info!("Node | Deleted [index: {}, name: \"{}\", ip: \"{}\", rpc: {}, zmq: {}]", n+1, self.selected_name, self.selected_ip, self.selected_rpc, self.selected_zmq);
 							break
 						}
 						n += 1;