From 26eecd4b257dbaef68c13e31ea765820abbcd7a6 Mon Sep 17 00:00:00 2001
From: hinto-janaiyo <hinto.janaiyo@protonmail.com>
Date: Sun, 20 Nov 2022 13:31:00 -0500
Subject: [PATCH] main/disk: get OS data path only once, pass ref for
 [state/node]

---
 src/disk.rs  |  70 ++++++++------------
 src/gupax.rs |  12 ++--
 src/main.rs  | 176 +++++++++++++++++++++++++++++++--------------------
 3 files changed, 140 insertions(+), 118 deletions(-)

diff --git a/src/disk.rs b/src/disk.rs
index 28ef127..b266304 100644
--- a/src/disk.rs
+++ b/src/disk.rs
@@ -50,39 +50,25 @@ 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_os_data_path() -> Result<PathBuf, TomlError> {
+pub fn get_gupax_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 path = match dirs::data_dir() {
-		Some(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() {
+	// Linux   | $XDG_DATA_HOME or $HOME/.local/share/gupax  | /home/alice/.local/state/gupax
+	// macOS   | $HOME/Library/Application Support/Gupax     | /Users/Alice/Library/Application Support/Gupax
+	// Windows | {FOLDERID_RoamingAppData}\Gupax             | C:\Users\Alice\AppData\Roaming\Gupax
+	match dirs::data_dir() {
 		Some(mut path) => {
 			path.push(DIRECTORY);
-			info!("OS | Data path ... OK");
-			path
+			info!("OS | Data path ... OK ... {}", path.display());
+			Ok(path)
 		},
-		None => { error!("Couldn't get OS PATH for data"); return Err(TomlError::Path(PATH_ERROR.to_string())) },
-	};
+		None => { error!("OS | Data path ... FAIL"); Err(TomlError::Path(PATH_ERROR.to_string())) },
+	}
+}
+
+pub fn create_gupax_dir(path: PathBuf) -> Result<(), TomlError> {
 	// Create directory
 	fs::create_dir_all(&path)?;
-	path.push(name);
-	info!("{:?} | Path ... {}", file, path.display());
-	Ok(path)
+	Ok(())
 }
 
 // Convert a [File] path to a [String]
@@ -198,15 +184,14 @@ impl State {
 	//      |_ Create a default file if not found
 	//   2. Deserialize [String] into a proper [Struct]
 	//      |_ Attempt to merge if deserialization fails
-	pub fn get() -> Result<Self, TomlError> {
+	pub fn get(path: &PathBuf) -> Result<Self, TomlError> {
 		// Read
 		let file = File::State;
-		let path = get_file_path(file)?;
 		let string = match read_to_string(file, &path) {
 			Ok(string) => string,
 			// Create
 			_ => {
-				Self::create_new()?;
+				Self::create_new(path)?;
 				match read_to_string(file, &path) {
 					Ok(s) => s,
 					Err(e) => return Err(e),
@@ -218,17 +203,16 @@ impl State {
 			Ok(s) => Ok(s),
 			Err(e) => {
 				warn!("State | Attempting merge...");
-				Self::merge(string)
+				Self::merge(string, path)
 			},
 		}
 	}
 
 	// Completely overwrite current [state.toml]
 	// with a new default version, and return [Self].
-	pub fn create_new() -> Result<Self, TomlError> {
+	pub fn create_new(path: &PathBuf) -> Result<Self, TomlError> {
 		info!("State | Creating new default...");
 		let new = Self::new();
-		let path = get_file_path(File::State)?;
 		let string = match toml::ser::to_string(&new) {
 				Ok(o) => { info!("State | Serialization ... OK"); o },
 				Err(e) => { error!("State | Couldn't serialize default file: {}", e); return Err(TomlError::Serialize(e)) },
@@ -239,9 +223,8 @@ impl State {
 	}
 
 	// Save [State] onto disk file [gupax.toml]
-	pub fn save(&mut self) -> Result<(), TomlError> {
+	pub fn save(&mut self, path: &PathBuf) -> Result<(), TomlError> {
 		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())?;
@@ -262,7 +245,7 @@ impl State {
 	// Take [String] as input, merge it with whatever the current [default] is,
 	// leaving behind old keys+values and updating [default] with old valid ones.
 	// Automatically overwrite current file.
-	pub fn merge(old: String) -> Result<Self, TomlError> {
+	pub fn merge(old: String, path: &PathBuf) -> Result<Self, TomlError> {
 		let default = match toml::ser::to_string(&Self::new()) {
 			Ok(string) => { info!("State | Default TOML parse ... OK"); string },
 			Err(err) => { error!("State | Couldn't parse default TOML into string"); return Err(TomlError::Serialize(err)) },
@@ -272,7 +255,7 @@ impl State {
 			Err(err) => { error!("State | Couldn't merge default + old TOML"); return Err(TomlError::Merge(err)) },
 		};
 		// Attempt save
-		Self::save(&mut new)?;
+		Self::save(&mut new, path)?;
 		Ok(new)
 	}
 }
@@ -348,15 +331,14 @@ impl Node {
 	//      |_ Create a default file if not found
 	//   2. Deserialize [String] into a proper [Struct]
 	//      |_ Attempt to merge if deserialization fails
-	pub fn get() -> Result<Vec<(String, Self)>, TomlError> {
+	pub fn get(path: &PathBuf) -> Result<Vec<(String, Self)>, TomlError> {
 		// Read
 		let file = File::Node;
-		let path = get_file_path(file)?;
 		let string = match read_to_string(file, &path) {
 			Ok(string) => string,
 			// Create
 			_ => {
-				Self::create_new()?;
+				Self::create_new(path)?;
 				read_to_string(file, &path)?
 			},
 		};
@@ -366,10 +348,9 @@ impl Node {
 
 	// Completely overwrite current [node.toml]
 	// with a new default version, and return [Vec<String, Self>].
-	pub fn create_new() -> Result<Vec<(String, Self)>, TomlError> {
+	pub fn create_new(path: &PathBuf) -> Result<Vec<(String, Self)>, TomlError> {
 		info!("Node | Creating new default...");
 		let new = Self::new_vec();
-		let path = get_file_path(File::Node)?;
 		let string = Self::to_string(&Self::new_vec());
 		fs::write(&path, &string)?;
 		info!("Node | Write ... OK");
@@ -377,9 +358,8 @@ impl Node {
 	}
 
 	// Save [Node] onto disk file [node.toml]
-	pub fn save(vec: &Vec<(String, Self)>) -> Result<(), TomlError> {
+	pub fn save(vec: &Vec<(String, Self)>, path: &PathBuf) -> Result<(), TomlError> {
 		info!("Node | Saving to disk...");
-		let path = get_file_path(File::Node)?;
 		let string = Self::to_string(vec);
 		match fs::write(path, string) {
 			Ok(_) => { info!("TOML save ... OK"); Ok(()) },
@@ -439,7 +419,7 @@ const PATH_ERROR: &'static str = "PATH for state directory could not be not foun
 #[cfg(target_os = "windows")]
 const DIRECTORY: &'static str = r#"Gupax\"#;
 #[cfg(target_os = "macos")]
-const DIRECTORY: &'static str = "com.github.hinto-janaiyo.gupax/";
+const DIRECTORY: &'static str = "Gupax/";
 #[cfg(target_os = "linux")]
 const DIRECTORY: &'static str = "gupax/";
 
diff --git a/src/gupax.rs b/src/gupax.rs
index 66734f5..779e2cb 100644
--- a/src/gupax.rs
+++ b/src/gupax.rs
@@ -25,8 +25,11 @@ use egui::{
 use crate::constants::*;
 use crate::disk::{Gupax,Version};
 use crate::update::*;
-use std::thread;
-use std::sync::{Arc,Mutex};
+use std::{
+	thread,
+	sync::{Arc,Mutex},
+	path::PathBuf,
+};
 use log::*;
 
 //---------------------------------------------------------------------------------------------------- FileWindow
@@ -55,7 +58,7 @@ impl FileWindow {
 
 //---------------------------------------------------------------------------------------------------- Gupax
 impl Gupax {
-	pub fn show(&mut self, og: &Arc<Mutex<State>>, state_ver: &Arc<Mutex<Version>>, update: &Arc<Mutex<Update>>, file_window: &Arc<Mutex<FileWindow>>, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) {
+	pub fn show(&mut self, og: &Arc<Mutex<State>>, state_ver: &Arc<Mutex<Version>>, update: &Arc<Mutex<Update>>, file_window: &Arc<Mutex<FileWindow>>, state_path: &PathBuf, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) {
 		// Update button + Progress bar
 		ui.group(|ui| {
 				// These are in unnecessary [ui.vertical()]'s
@@ -75,6 +78,7 @@ impl Gupax {
 						let state_ver = Arc::clone(&state_ver);
 						let update = Arc::clone(&update);
 						let update_thread = Arc::clone(&update);
+						let state_path = state_path.clone();
 						thread::spawn(move|| {
 							info!("Spawning update thread...");
 							match Update::start(update_thread, og.clone(), state_ver.clone()) {
@@ -84,7 +88,7 @@ impl Gupax {
 								},
 								_ => {
 									info!("Update | Saving state...");
-									match State::save(&mut og.lock().unwrap()) {
+									match State::save(&mut og.lock().unwrap(), &state_path) {
 										Ok(_) => info!("Update ... OK"),
 										Err(e) => {
 											warn!("Update | Saving state ... FAIL ... {}", e);
diff --git a/src/main.rs b/src/main.rs
index f779404..7243b91 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,12 +37,14 @@ use env_logger::{Builder,WriteStyle};
 use regex::Regex;
 
 // std
-use std::io::Write;
-use std::process::exit;
-use std::sync::{Arc,Mutex};
-use std::{thread,env};
-use std::time::Instant;
-use std::path::PathBuf;
+use std::{
+	io::Write,
+	process::exit,
+	sync::{Arc,Mutex},
+	{thread,env},
+	time::Instant,
+	path::PathBuf,
+};
 
 // Modules
 mod ferris;
@@ -93,6 +95,8 @@ pub struct App {
 	resolution: Vec2, // Frame resolution
 	os: &'static str, // OS
 	os_data_path: PathBuf, // OS data path (e.g: ~/.local/share/gupax)
+	state_path: PathBuf, // State file path
+	node_path: PathBuf, // Node file path
 	version: &'static str, // Gupax version
 	name_version: String, // [Gupax vX.X.X]
 	img: Images, // Custom Struct holding pre-compiled bytes of [Images]
@@ -111,7 +115,7 @@ impl App {
 
 	fn new() -> Self {
 		info!("Initializing App Struct...");
-		let app = Self {
+		let mut app = Self {
 			tab: Tab::default(),
 			ping: Arc::new(Mutex::new(Ping::new())),
 			width: 1280.0,
@@ -128,68 +132,82 @@ impl App {
 			xmrig: false,
 			no_startup: false,
 			now: Instant::now(),
-			exe: "".to_string(),
-			dir: "".to_string(),
+			exe: String::new(),
+			dir: String::new(),
 			resolution: Vec2::new(1280.0, 720.0),
 			os: OS,
 			os_data_path: PathBuf::new(),
+			state_path: PathBuf::new(),
+			node_path: PathBuf::new(),
 			version: GUPAX_VERSION,
 			name_version: format!("Gupax {}", GUPAX_VERSION),
 			img: Images::new(),
 			regex: Regexes::new(),
 		};
-		// Apply arg state
-		let mut app = parse_args(app);
+		//---------------------------------------------------------------------------------------------------- App init data that *could* panic
+		let mut panic = String::new();
 		// Get exe path
 		app.exe = match get_exe() {
 			Ok(exe) => exe,
-			Err(err) => { panic_main(err.to_string()); exit(1); },
+			Err(e) => { panic = format!("get_exe(): {}", e); app.error_state.set(panic.clone(), ErrorFerris::Panic, ErrorButtons::Quit); String::new() },
 		};
 		// Get exe directory path
 		app.dir = match get_exe_dir() {
 			Ok(dir) => dir,
-			Err(err) => { panic_main(err.to_string()); exit(1); },
+			Err(e) => { panic = format!("get_exe_dir(): {}", e); app.error_state.set(panic.clone(), ErrorFerris::Panic, ErrorButtons::Quit); String::new() },
 		};
 		// Get OS data path
-		app.os_data_path = match get_os_data_path() {
+		app.os_data_path = match get_gupax_data_path() {
 			Ok(dir) => dir,
-			Err(err) => { panic_main(err.to_string()); exit(1); },
+			Err(e) => { panic = format!("get_os_data_path(): {}", e); app.error_state.set(panic.clone(), ErrorFerris::Panic, ErrorButtons::Quit); PathBuf::new() },
 		};
+
+		// Set [state.toml/node.toml] path
+		app.state_path = app.os_data_path.clone();
+		app.state_path.push("state.toml");
+		app.node_path = app.os_data_path.clone();
+		app.node_path.push("node.toml");
+
+		// Apply arg state
+		// It's not safe to [--reset] if any of the previous variables
+		// are unset (null path), so make sure we just abort if the [panic] String contains something.
+		let mut app = parse_args(app, panic);
+
 		// Read disk state
 		use TomlError::*;
-		app.state = match State::get() {
+		app.state = match State::get(&app.state_path) {
 			Ok(toml) => toml,
 			Err(err) => {
 				error!("State ... {}", err);
 				match err {
-					Io(e) => app.error_state.set(true, format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
-					Path(e) => app.error_state.set(true, format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
-					Serialize(e) => app.error_state.set(true, format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
-					Deserialize(e) => app.error_state.set(true, format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
-					Merge(e) => app.error_state.set(true, format!("State file: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
+					Io(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Path(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Serialize(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Deserialize(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Merge(e) => app.error_state.set(format!("State file: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
 				};
 				State::new()
 			},
 		};
 		app.og = Arc::new(Mutex::new(app.state.clone()));
 		// Read node list
-		app.og_node_vec = match Node::get() {
+		app.og_node_vec = match Node::get(&app.node_path) {
 			Ok(toml) => toml,
 			Err(err) => {
 				error!("Node ... {}", err);
 				match err {
-					Io(e) => app.error_state.set(true, format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
-					Path(e) => app.error_state.set(true, format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
-					Serialize(e) => app.error_state.set(true, format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
-					Deserialize(e) => app.error_state.set(true, format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
-					Merge(e) => app.error_state.set(true, format!("Node list: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
+					Io(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Path(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Serialize(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Deserialize(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+					Merge(e) => app.error_state.set(format!("Node list: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
 				};
 				Node::new_vec()
 			},
 		};
 		app.node_vec = app.og_node_vec.clone();
 
-
+		//----------------------------------------------------------------------------------------------------
 		let mut og = app.og.lock().unwrap(); // Lock [og]
 		// Handle max threads
 		og.xmrig.max_threads = num_cpus::get();
@@ -259,10 +277,20 @@ impl ErrorState {
 		}
 	}
 
-	// Convenience function to set the [App] error state
-	pub fn set(&mut self, error: bool, msg: impl Into<String>, ferris: ErrorFerris, buttons: ErrorButtons) {
+	// Convenience function to enable the [App] error state
+	pub fn set(&mut self, msg: impl Into<String>, ferris: ErrorFerris, buttons: ErrorButtons) {
+		if self.error {
+			// If a panic error is already set, return
+			if self.ferris == ErrorFerris::Panic { return }
+			// If we shouldn't be overriding the current error, return
+			match self.buttons {
+				ErrorButtons::YesNo => (), // Not important
+				ErrorButtons::Okay => (), // Not important
+				_ => return, // Overwrite, Quits, etc
+			}
+		}
 		*self = Self {
-			error,
+			error: true,
 			msg: msg.into(),
 			ferris,
 			buttons,
@@ -433,6 +461,7 @@ fn init_auto(app: &App) {
 		let state_ver = Arc::clone(&app.state.version);
 		let update = Arc::clone(&app.update);
 		let update_thread = Arc::clone(&app.update);
+		let state_path = app.state_path.clone();
 		thread::spawn(move|| {
 			info!("Spawning update thread...");
 			match Update::start(update_thread, og.clone(), state_ver.clone()) {
@@ -442,7 +471,7 @@ fn init_auto(app: &App) {
 				},
 				_ => {
 					info!("Update | Saving state...");
-					match State::save(&mut og.lock().unwrap()) {
+					match State::save(&mut og.lock().unwrap(), &state_path) {
 						Ok(_) => info!("Update ... OK"),
 						Err(e) => {
 							warn!("Update | Saving state ... FAIL ... {}", e);
@@ -472,41 +501,47 @@ fn init_auto(app: &App) {
 	}
 }
 
-fn reset_state() -> Result<(), TomlError> {
+fn reset_state(path: &PathBuf) -> Result<(), TomlError> {
 	info!("Resetting [state.toml]...");
-	match State::create_new() {
+	match State::create_new(path) {
 		Ok(_)  => { info!("Resetting [state.toml] ... OK"); Ok(()) },
 		Err(e) => { error!("Resetting [state.toml] ... FAIL ... {}", e); Err(e) },
 	}
 }
 
-fn reset_nodes() -> Result<(), TomlError> {
+fn reset_nodes(path: &PathBuf) -> Result<(), TomlError> {
 	info!("Resetting [node.toml]...");
-	match Node::create_new() {
+	match Node::create_new(path) {
 		Ok(_)  => { info!("Resetting [node.toml] ... OK"); Ok(()) },
 		Err(e) => { error!("Resetting [node.toml] ... FAIL ... {}", e); Err(e) },
 	}
 }
 
-fn reset() {
+fn reset(path: &PathBuf, state: &PathBuf, node: &PathBuf) {
 	let mut code = 0;
-	match reset_state() {
+	// Attempt to remove directory first
+	info!("OS data path ... {}", path.display());
+	match std::fs::remove_dir_all(path) {
+		Ok(_) => info!("Removing OS data path ... OK"),
+		Err(e) => { error!("Removing OS data path ... FAIL ... {}", e); code = 1; },
+	}
+	match reset_state(state) {
 		Ok(_) => (),
 		Err(_) => code = 1,
 	}
-	match reset_nodes() {
+	match reset_nodes(node) {
 		Ok(_) => (),
 		Err(_) => code = 1,
 	}
 	match code {
-		0 => println!("\nGupax files were reset successfully."),
-		_ => println!("\nGupax files reset FAILED."),
+		0 => println!("\nGupax reset ... OK"),
+		_ => println!("\nGupax reset ... FAIL"),
 	}
 	exit(code);
 }
 
 //---------------------------------------------------------------------------------------------------- Misc functions
-fn parse_args(mut app: App) -> App {
+fn parse_args<S: Into<String>>(mut app: App, panic: S) -> 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); }
@@ -522,14 +557,21 @@ fn parse_args(mut app: App) -> App {
 			_ => (),
 		}
 	}
+	// Abort on panic
+	let panic = panic.into();
+	if ! panic.is_empty() {
+		info!("[Gupax error] {}", panic);
+		exit(1);
+	}
+
 	// Everything else
 	for arg in args {
 		match arg.as_str() {
-			"--nodes"       => { info!("Printing node list..."); print_disk_file(File::Node); }
-			"--state"       => { info!("Printing state..."); print_disk_file(File::State); }
-			"--reset-state" => if let Ok(()) = reset_state() { exit(0) } else { exit(1) },
-			"--reset-nodes" => if let Ok(()) = reset_nodes() { exit(0) } else { exit(1) },
-			"--reset-all"   => reset(),
+			"--state"       => { info!("Printing state..."); print_disk_file(&app.state_path); }
+			"--nodes"       => { info!("Printing node list..."); print_disk_file(&app.node_path); }
+			"--reset-state" => if let Ok(()) = reset_state(&app.state_path) { exit(0) } else { exit(1) },
+			"--reset-nodes" => if let Ok(()) = reset_nodes(&app.node_path) { exit(0) } else { exit(1) },
+			"--reset-all"   => reset(&app.os_data_path, &app.state_path, &app.node_path),
 			"--no-startup"  => app.no_startup = true,
 			_               => { eprintln!("[Gupax error] Invalid option: [{}]\nFor help, use: [--help]", arg); exit(1); },
 		}
@@ -541,7 +583,7 @@ fn parse_args(mut app: App) -> App {
 pub fn get_exe() -> Result<String, std::io::Error> {
 	match std::env::current_exe() {
 		Ok(path) => { Ok(path.display().to_string()) },
-		Err(err) => { error!("Couldn't get exe basepath PATH"); return Err(err) },
+		Err(err) => { error!("Couldn't get absolute Gupax PATH"); return Err(err) },
 	}
 }
 
@@ -572,11 +614,7 @@ pub fn clean_dir() -> Result<(), anyhow::Error> {
 }
 
 // Print disk files to console
-fn print_disk_file(file: File) {
-	let path = match get_file_path(file) {
-		Ok(path) => path,
-		Err(e) => { error!("{}", e); exit(1); },
-	};
+fn print_disk_file(path: &PathBuf) {
 	match std::fs::read_to_string(&path) {
 		Ok(string) => { print!("{}", string); exit(0); },
 		Err(e) => { error!("{}", e); exit(1); },
@@ -652,7 +690,7 @@ fn main() {
 impl eframe::App for App {
 	fn on_close_event(&mut self) -> bool {
 		if self.state.gupax.ask_before_quit {
-			self.error_state.set(true, "", ErrorFerris::Oops, ErrorButtons::StayQuit);
+			self.error_state.set("", ErrorFerris::Oops, ErrorButtons::StayQuit);
 			false
 		} else {
 			true
@@ -749,36 +787,36 @@ impl eframe::App for App {
 					// [Yes/No] buttons
 					ResetState => {
 						if ui.add_sized([width, height/2.0], egui::Button::new("Yes")).clicked() {
-							match reset_state() {
+							match reset_state(&self.state_path) {
 								Ok(_)  => {
-									match State::get() {
+									match State::get(&self.state_path) {
 										Ok(s) => {
 											self.state = s;
 											self.og = Arc::new(Mutex::new(self.state.clone()));
-											self.error_state.set(true, "State read OK", ErrorFerris::Happy, ErrorButtons::Okay);
+											self.error_state.set("State read OK", ErrorFerris::Happy, ErrorButtons::Okay);
 										},
-										Err(e) => self.error_state.set(true, format!("State read fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+										Err(e) => self.error_state.set(format!("State read fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
 									}
 								},
-								Err(e) => self.error_state.set(true, format!("State reset fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+								Err(e) => self.error_state.set(format!("State reset fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
 							};
 						}
 				        if esc || ui.add_sized([width, height/2.0], egui::Button::new("No")).clicked() { self.error_state = ErrorState::new() }
 					},
 					ResetNode => {
 						if ui.add_sized([width, height/2.0], egui::Button::new("Yes")).clicked() {
-							match reset_nodes() {
+							match reset_nodes(&self.node_path) {
 								Ok(_)  => {
-									match Node::get() {
+									match Node::get(&self.node_path) {
 										Ok(s) => {
 											self.node_vec = s;
 											self.og_node_vec = self.node_vec.clone();
-											self.error_state.set(true, "Node read OK", ErrorFerris::Happy, ErrorButtons::Okay);
+											self.error_state.set("Node read OK", ErrorFerris::Happy, ErrorButtons::Okay);
 										},
-										Err(e) => self.error_state.set(true, format!("Node read fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+										Err(e) => self.error_state.set(format!("Node read fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
 									}
 								},
-								Err(e) => self.error_state.set(true, format!("Node reset fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
+								Err(e) => self.error_state.set(format!("Node reset fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
 							};
 						}
 				        if esc || ui.add_sized([width, height/2.0], egui::Button::new("No")).clicked() { self.error_state = ErrorState::new() }
@@ -867,7 +905,7 @@ impl eframe::App for App {
 						self.node_vec = self.og_node_vec.clone();
 					}
 					if ui.add_sized([width, height], egui::Button::new("Save")).on_hover_text("Save changes").clicked() {
-						match self.state.save() {
+						match State::save(&mut self.state, &self.state_path) {
 							Ok(_) => {
 								let mut og = self.og.lock().unwrap();
 								og.gupax = self.state.gupax.clone();
@@ -875,12 +913,12 @@ impl eframe::App for App {
 								og.xmrig = self.state.xmrig.clone();
 							},
 							Err(e) => {
-								self.error_state.set(true, format!("State file: {}", e), ErrorFerris::Error, ErrorButtons::Okay);
+								self.error_state.set(format!("State file: {}", e), ErrorFerris::Error, ErrorButtons::Okay);
 							},
 						};
-						match Node::save(&self.og_node_vec) {
+						match Node::save(&self.og_node_vec, &self.node_path) {
 							Ok(_) => self.og_node_vec = self.node_vec.clone(),
-							Err(e) => self.error_state.set(true, format!("Node list: {}", e), ErrorFerris::Error, ErrorButtons::Okay),
+							Err(e) => self.error_state.set(format!("Node list: {}", e), ErrorFerris::Error, ErrorButtons::Okay),
 						};
 					}
 				});
@@ -979,7 +1017,7 @@ impl eframe::App for App {
 					Status::show(self, self.width, self.height, ctx, ui);
 				}
 				Tab::Gupax => {
-					Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, &self.file_window, self.width, self.height, ctx, ui);
+					Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, &self.file_window, &self.state_path, self.width, self.height, ctx, ui);
 				}
 				Tab::P2pool => {
 					P2pool::show(&mut self.state.p2pool, &mut self.node_vec, &self.og, self.p2pool, &self.ping, &self.regex, self.width, self.height, ctx, ui);