p2pool: add [Advanced], add [node.toml] database, add char limit

This commit is contained in:
hinto-janaiyo 2022-11-13 21:56:25 -05:00
parent 3f4124622c
commit 9faf0fc9f5
No known key found for this signature in database
GPG key ID: D7483F6CA27D1B1D
9 changed files with 925 additions and 537 deletions

View file

@ -8,12 +8,12 @@
| File/Folder | Purpose |
|----------------|---------|
| `constants.rs` | General constants needed in Gupax
| `disk.rs` | Code for writing to disk: `state.toml`, `nodes.toml`; This holds the structs for mutable [State]
| `ferris.rs` | Cute crab `--ferris`
| `gupax.rs` | `Gupax` tab
| `main.rs` | `App/Tab/State` + misc functions
| `node.rs` | Community node feature
| `p2pool.rs` | `P2Pool` tab
| `state.rs` | `gupax.toml` config code. This holds the structs representing tabs with mutable state (Gupax/P2Pool/XMRig)
| `status.rs` | `Status` tab
| `update.rs` | Update code for the `Gupax` tab
| `xmrig.rs` | `XMRig` tab

View file

@ -66,13 +66,13 @@ pub const GUPAX_UPDATE_VIA_TOR: &'static str = "Update through the Tor network.
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_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";
pub const GUPAX_PATH_XMRIG: &'static str = "The location of the XMRig binary, both absolute and relative paths are accepted";
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";
// P2Pool
pub const P2POOL_MAIN: &'static str = "Use the P2Pool main-chain. This P2Pool finds shares faster, but has a higher difficulty. Suitable for miners with more than 50kH/s";
pub const P2POOL_MINI: &'static str = "Use the P2Pool mini-chain. This P2Pool finds shares slower, but has a lower difficulty. Suitable for miners with less than 50kH/s";
pub const P2POOL_OUT: &'static str = "How many out-bound peers (you connecting to others) to connect to?";
pub const P2POOL_IN: &'static str = "How many in-bound peers (others connecting to you) to connect to?";
pub const P2POOL_OUT: &'static str = "How many out-bound peers to connect to? (you connecting to others)";
pub const P2POOL_IN: &'static str = "How many in-bound peers to allow? (others connecting to you)";
pub const P2POOL_LOG: &'static str = "Verbosity of the console log";
pub const P2POOL_COMMUNITY: &'static str = "Connect to a community trusted Monero node: This is convenient because you don't have to download the Monero blockchain but it comes at the cost of privacy";
pub const P2POOL_MANUAL: &'static str = "Manually specify your own Monero node settings";
@ -81,6 +81,25 @@ pub const P2POOL_AUTO_SELECT: &'static str = "Automatically select the fastest c
pub const P2POOL_SELECT_FASTEST: &'static str = "Select the fastest community Monero node";
pub const P2POOL_PING: &'static str = "Ping the built-in community Monero nodes";
pub const P2POOL_ADDRESS: &'static str = "You must use a primary Monero address to mine on P2Pool (starts with a 4). It is highly recommended to create a new wallet for P2Pool mining; wallet addresses are public on P2Pool!";
pub const P2POOL_COMMAND: &'static str = "Start P2Pool with these arguments and override all below settings; If the [--data-api] flag is not given, Gupax will append it to the arguments automatically so that the [Status] tab can work";
pub const P2POOL_SIMPLE: &'static str =
r#"Use simple settings:
- Remote community Monero node
- Default P2Pool settings + Mini"#;
pub const P2POOL_ADVANCED: &'static str =
r#"Use advanced settings:
- Overriding command arguments
- Manual node selection
- P2Pool Main/Mini selection
- 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_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";
// XMRig
pub const XMRIG_P2POOL: &'static str = "Mine to your own P2Pool instance (localhost:3333)";
@ -94,13 +113,19 @@ pub const XMRIG_PRIORITY: &'static str = "Set process priority (0 idle, 2 normal
// CLI argument messages
pub const ARG_HELP: &'static str =
r#"USAGE: gupax [--flags]
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
-f | --ferris Print an extremely cute crab"#;
-h | --help Print this help message
-v | --version Print version and build info
-l | --node-list Print the manual node list
-s | --state Print Gupax state
-n | --no-startup Disable all auto-startup settings for this instance
-r | --reset Reset all Gupax state and the manual node list
-f | --ferris Print an extremely cute crab
To view more detailed console debug information, start Gupax with
the environment variable [RUST_LOG] set to a log level like so:
RUST_LOG=(trace|debug|info|warn|error) ./gupax"#;
pub const ARG_COPYRIGHT: &'static str =
r#"Gupax is licensed under GPLv3.
For more information, see link below:

537
src/disk.rs Normal file
View file

@ -0,0 +1,537 @@
// Gupax - GUI Uniting P2Pool And XMRig
//
// Copyright (c) 2022 hinto-janaiyo
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// This handles reading/writing the disk files:
// - [state.toml] -> [App] state
// - [nodes.toml] -> [Manual Nodes] list
// The TOML format is used. This struct hierarchy
// directly translates into the TOML parser:
// State/
// ├─ Gupax/
// │ ├─ ...
// ├─ P2pool/
// │ ├─ ...
// ├─ Xmrig/
// │ ├─ ...
// ├─ Version/
// ├─ ...
use std::{fs,env};
use std::fmt::Display;
use std::path::{Path,PathBuf};
use std::result::Result;
use std::sync::{Arc,Mutex};
use std::collections::HashMap;
use std::fmt::Write;
use serde::{Serialize,Deserialize};
use figment::Figment;
use figment::providers::{Format,Toml};
use crate::constants::*;
use anyhow::Error;
use log::*;
//---------------------------------------------------------------------------------------------------- General functions for all [File]'s
// get_file_path() | Return absolute path to OS data path + filename
// read_to_string() | Convert the file at a given path into a [String]
// 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> {
// 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 name = File::name(&file);
let mut path = match dirs::data_dir() {
Some(mut path) => {
path.push(DIRECTORY);
info!("OS data path ... OK");
path
},
None => { error!("Couldn't get OS PATH for data"); return Err(TomlError::Path(PATH_ERROR.to_string())) },
};
// Create directory
fs::create_dir_all(&path)?;
path.push(name);
info!("{:?} path ... {}", file, path.display());
Ok(path)
}
// Convert a [File] path to a [String]
pub fn read_to_string(file: File, path: &PathBuf) -> Result<String, TomlError> {
match fs::read_to_string(&path) {
Ok(string) => {
info!("{:?} | Read ... OK", file);
Ok(string)
},
Err(err) => {
warn!("{:?} | Read ... FAIL", file);
Err(TomlError::Io(err))
},
}
}
// Write [String] to console with [info!] surrounded by "---"
pub fn print_toml(toml: &String) {
info!("{}", HORIZONTAL);
for i in toml.lines() { info!("{}", i); }
info!("{}", HORIZONTAL);
}
// Turn relative paths into absolute paths
pub fn into_absolute_path(path: String) -> Result<PathBuf, TomlError> {
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)
}
}
//---------------------------------------------------------------------------------------------------- [State] Impl
impl State {
pub fn new() -> Self {
let max_threads = num_cpus::get();
let current_threads;
if max_threads == 1 { current_threads = 1; } else { current_threads = max_threads / 2; }
Self {
gupax: Gupax {
auto_update: true,
auto_node: true,
ask_before_quit: true,
save_before_quit: true,
update_via_tor: true,
p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
xmrig_path: DEFAULT_XMRIG_PATH.to_string(),
absolute_p2pool_path: into_absolute_path(DEFAULT_P2POOL_PATH.to_string()).unwrap(),
absolute_xmrig_path: into_absolute_path(DEFAULT_XMRIG_PATH.to_string()).unwrap(),
},
p2pool: P2pool {
simple: true,
mini: true,
auto_node: true,
auto_select: true,
out_peers: 10,
in_peers: 10,
log_level: 3,
node: crate::NodeEnum::C3pool,
arguments: String::new(),
address: String::with_capacity(95),
name: "Local Monero Node".to_string(),
ip: "localhost".to_string(),
rpc: "18081".to_string(),
zmq: "18083".to_string(),
selected_name: "Local Monero Node".to_string(),
selected_ip: "localhost".to_string(),
selected_rpc: "18081".to_string(),
selected_zmq: "18083".to_string(),
},
xmrig: Xmrig {
simple: true,
tls: false,
nicehash: false,
keepalive: false,
hugepages_jit: true,
current_threads,
max_threads,
priority: 2,
pool: "localhost:3333".to_string(),
address: String::with_capacity(95),
},
version: Arc::new(Mutex::new(Version {
p2pool: Arc::new(Mutex::new(P2POOL_VERSION.to_string())),
xmrig: Arc::new(Mutex::new(XMRIG_VERSION.to_string())),
})),
}
}
// Convert [String] to [State]
pub fn from_string(string: String) -> Result<Self, TomlError> {
match toml::de::from_str(&string) {
Ok(state) => {
info!("State parse ... OK");
print_toml(&string);
Ok(state)
}
Err(err) => {
error!("State | String -> State ... FAIL ... {}", err);
Err(TomlError::Deserialize(err))
},
}
}
// Combination of multiple functions:
// 1. Attempt to read file from path into [String]
// |_ 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> {
// Read
let file = File::State;
let path = get_file_path(file)?;
let string = match read_to_string(file, &path) {
Ok(string) => string,
// Create
_ => {
let new = Self::create_new()?;
read_to_string(file, &path)?
},
};
// Deserialize
Self::from_string(string)
}
// Completely overwrite current [state.toml]
// with a new default version, and return [Self].
pub fn create_new() -> Result<Self, TomlError> {
info!("State | Creating new default...");
let new = Self::new();
let path = get_file_path(File::State)?;
println!("{:#?}", new);
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)) },
};
fs::write(&path, &string)?;
info!("State | Write ... OK");
Ok(new)
}
// Save [State] onto disk file [gupax.toml]
pub fn save(&mut self) -> Result<(), TomlError> {
info!("Saving {:?} to disk...", self);
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");
print_toml(&string);
string
},
Err(err) => { error!("Couldn't parse TOML into string"); 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)) },
}
}
// Take [Self] 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: &Self) -> Result<Self, TomlError> {
info!("Starting TOML merge...");
let old = match toml::ser::to_string(&old) {
Ok(string) => { info!("Old TOML parse ... OK"); string },
Err(err) => { error!("Couldn't parse old TOML into string"); return Err(TomlError::Serialize(err)) },
};
let default = match toml::ser::to_string(&Self::new()) {
Ok(string) => { info!("Default TOML parse ... OK"); string },
Err(err) => { error!("Couldn't parse default TOML into string"); return Err(TomlError::Serialize(err)) },
};
let mut new: Self = match Figment::new().merge(Toml::string(&old)).merge(Toml::string(&default)).extract() {
Ok(new) => { info!("TOML merge ... OK"); new },
Err(err) => { error!("Couldn't merge default + old TOML"); return Err(TomlError::Merge(err)) },
};
// Attempt save
Self::save(&mut new)?;
Ok(new)
}
}
//---------------------------------------------------------------------------------------------------- [Node] Impl
impl Node {
pub fn new() -> Self {
Self {
ip: String::new(),
rpc: "18081".to_string(),
zmq: "18083".to_string(),
}
}
pub fn localhost() -> Self {
Self {
ip: "localhost".to_string(),
rpc: "18081".to_string(),
zmq: "18083".to_string(),
}
}
pub fn new_vec() -> Vec<(String, Self)> {
let mut vec = Vec::new();
vec.push(("Local Monero Node".to_string(), Self::localhost()));
vec
}
// Convert [String] to [Node] Vec
pub fn from_string(string: String) -> Result<Vec<(String, Self)>, TomlError> {
let nodes: HashMap<String, Node> = match toml::de::from_str(&string) {
Ok(map) => {
info!("Node | Parse ... OK");
print_toml(&string);
map
}
Err(err) => {
error!("Node | String parse ... FAIL ... {}", err);
return Err(TomlError::Deserialize(err))
},
};
let size = nodes.keys().len();
let mut vec = Vec::with_capacity(size);
for (key, values) in nodes.iter() {
vec.push((key.clone(), values.clone()));
}
Ok(vec)
}
// Convert [Vec<(String, Self)>] into [String]
// that can be written as a proper TOML file
pub fn into_string(vec: Vec<(String, Self)>) -> String {
let mut toml = String::new();
for (key, value) in vec.iter() {
write!(
toml,
"[\'{}\']\nip = {:#?}\nrpc = {:#?}\nzmq = {:#?}\n",
key,
value.ip,
value.rpc,
value.zmq,
);
}
toml
}
// Combination of multiple functions:
// 1. Attempt to read file from path into [String]
// |_ 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> {
// Read
let file = File::Node;
let path = get_file_path(file)?;
let string = match read_to_string(file, &path) {
Ok(string) => string,
// Create
_ => {
let new = Self::create_new()?;
read_to_string(file, &path)?
},
};
// Deserialize
Self::from_string(string)
}
// Completely overwrite current [node.toml]
// with a new default version, and return [Vec<String, Self>].
pub fn create_new() -> 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::into_string(Self::new_vec());
fs::write(&path, &string)?;
info!("Node | Write ... OK");
Ok(new)
}
// // Save [State] onto disk file [gupax.toml]
// pub fn save(&mut self) -> Result<(), TomlError> {
// info!("Saving {:?} to disk...", self);
// 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");
// print_toml(&string);
// string
// },
// Err(err) => { error!("Couldn't parse TOML into string"); 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)) },
// }
// }
//
// // Take [Self] 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: &Self) -> Result<Self, TomlError> {
// info!("Starting TOML merge...");
// let old = match toml::ser::to_string(&old) {
// Ok(string) => { info!("Old TOML parse ... OK"); string },
// Err(err) => { error!("Couldn't parse old TOML into string"); return Err(TomlError::Serialize(err)) },
// };
// let default = match toml::ser::to_string(&Self::new()) {
// Ok(string) => { info!("Default TOML parse ... OK"); string },
// Err(err) => { error!("Couldn't parse default TOML into string"); return Err(TomlError::Serialize(err)) },
// };
// let mut new: Self = match Figment::new().merge(Toml::string(&old)).merge(Toml::string(&default)).extract() {
// Ok(new) => { info!("TOML merge ... OK"); new },
// Err(err) => { error!("Couldn't merge default + old TOML"); return Err(TomlError::Merge(err)) },
// };
// // Attempt save
// Self::save(&mut new)?;
// Ok(new)
// }
}
//---------------------------------------------------------------------------------------------------- Custom Error [TomlError]
impl Display for TomlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use TomlError::*;
match self {
Io(err) => write!(f, "{}: {} | {}", ERROR, self, err),
Path(err) => write!(f, "{}: {} | {}", ERROR, self, err),
Serialize(err) => write!(f, "{}: {} | {}", ERROR, self, err),
Deserialize(err) => write!(f, "{}: {} | {}", ERROR, self, err),
Merge(err) => write!(f, "{}: {} | {}", ERROR, self, err),
}
}
}
impl From<std::io::Error> for TomlError {
fn from(err: std::io::Error) -> Self {
TomlError::Io(err)
}
}
//---------------------------------------------------------------------------------------------------- Const
// State file
const ERROR: &'static str = "Disk error";
const PATH_ERROR: &'static str = "PATH for state directory could not be not found";
#[cfg(target_os = "windows")]
const DIRECTORY: &'static str = r#"Gupax\"#;
#[cfg(target_os = "macos")]
const DIRECTORY: &'static str = "com.github.hinto-janaiyo.gupax/";
#[cfg(target_os = "linux")]
const DIRECTORY: &'static str = "gupax/";
#[cfg(target_os = "windows")]
pub const DEFAULT_P2POOL_PATH: &'static str = r"P2Pool\p2pool.exe";
#[cfg(target_family = "unix")]
pub const DEFAULT_P2POOL_PATH: &'static str = "p2pool/p2pool";
#[cfg(target_os = "windows")]
pub const DEFAULT_XMRIG_PATH: &'static str = r"XMRig\xmrig.exe";
#[cfg(target_family = "unix")]
pub const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig";
//---------------------------------------------------------------------------------------------------- Error Enum
#[derive(Debug)]
pub enum TomlError {
Io(std::io::Error),
Path(String),
Serialize(toml::ser::Error),
Deserialize(toml::de::Error),
Merge(figment::Error),
}
//---------------------------------------------------------------------------------------------------- [File] Enum (for matching which file)
#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub enum File {
State,
Node,
}
impl File {
fn name(&self) -> &'static str {
match *self {
Self::State => "state.toml",
Self::Node => "node.toml",
}
}
}
//---------------------------------------------------------------------------------------------------- [Node] Struct
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub struct Node {
pub ip: String,
pub rpc: String,
pub zmq: String,
}
//---------------------------------------------------------------------------------------------------- [State] Struct
#[derive(Clone,Debug,Deserialize,Serialize)]
pub struct State {
pub gupax: Gupax,
pub p2pool: P2pool,
pub xmrig: Xmrig,
pub version: Arc<Mutex<Version>>,
}
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub struct Gupax {
pub auto_update: bool,
pub auto_node: bool,
pub ask_before_quit: bool,
pub save_before_quit: bool,
pub update_via_tor: bool,
pub p2pool_path: String,
pub xmrig_path: String,
pub absolute_p2pool_path: PathBuf,
pub absolute_xmrig_path: PathBuf,
}
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub struct P2pool {
pub simple: bool,
pub mini: bool,
pub auto_node: bool,
pub auto_select: bool,
pub out_peers: u16,
pub in_peers: u16,
pub log_level: u8,
pub node: crate::node::NodeEnum,
pub arguments: String,
pub address: String,
pub name: String,
pub ip: String,
pub rpc: String,
pub zmq: String,
pub selected_name: String,
pub selected_ip: String,
pub selected_rpc: String,
pub selected_zmq: 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: usize,
pub current_threads: usize,
pub priority: u8,
pub pool: String,
pub address: String,
}
#[derive(Clone,Debug,Deserialize,Serialize)]
pub struct Version {
pub p2pool: Arc<Mutex<String>>,
pub xmrig: Arc<Mutex<String>>,
}

View file

@ -17,24 +17,29 @@
use std::path::Path;
use crate::{App,State};
use egui::TextStyle::Monospace;
use egui::RichText;
use egui::{
TextStyle::Monospace,
Checkbox,
RichText,
Label,
Color32,
};
use crate::constants::*;
use crate::state::{Gupax,Version};
use crate::disk::{Gupax,Version};
use crate::update::*;
use std::thread;
use std::sync::{Arc,Mutex};
use log::*;
impl Gupax {
pub fn show(state: &mut Gupax, og: &Arc<Mutex<State>>, state_ver: &Arc<Mutex<Version>>, update: &Arc<Mutex<Update>>, 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>>, 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
// because I need to use [ui.set_enabled]s, but I can't
// find a way to use a [ui.xxx()] with [ui.add_sized()].
// I have to pick one. This one seperates them though.
let height = height/6.0;
let height = height/8.0;
let width = width - SPACE;
let updating = *update.lock().unwrap().updating.lock().unwrap();
ui.vertical(|ui| {
@ -87,36 +92,62 @@ impl Gupax {
ui.horizontal(|ui| {
ui.group(|ui| {
let width = (width - SPACE*9.8)/5.0;
let height = height/2.5;
let width = (width - SPACE*7.5)/4.0;
let height = height/8.0;
let mut style = (*ctx.style()).clone();
style.spacing.icon_width_inner = width / 6.0;
style.spacing.icon_width = width / 4.0;
style.spacing.icon_width_inner = width / 8.0;
style.spacing.icon_width = width / 6.0;
style.spacing.icon_spacing = 20.0;
ctx.set_style(style);
let height = height/2.5;
ui.add_sized([width, height], egui::Checkbox::new(&mut state.auto_update, "Auto-update")).on_hover_text(GUPAX_AUTO_UPDATE);
ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_update, "Auto-update")).on_hover_text(GUPAX_AUTO_UPDATE);
ui.separator();
ui.add_sized([width, height], egui::Checkbox::new(&mut state.auto_node, "Auto-node")).on_hover_text(GUPAX_AUTO_NODE);
ui.add_sized([width, height], egui::Checkbox::new(&mut self.update_via_tor, "Update via Tor")).on_hover_text(GUPAX_UPDATE_VIA_TOR);
ui.separator();
ui.add_sized([width, height], egui::Checkbox::new(&mut state.update_via_tor, "Update via Tor")).on_hover_text(GUPAX_UPDATE_VIA_TOR);
ui.add_sized([width, height], egui::Checkbox::new(&mut self.ask_before_quit, "Ask before quit")).on_hover_text(GUPAX_ASK_BEFORE_QUIT);
ui.separator();
ui.add_sized([width, height], egui::Checkbox::new(&mut state.ask_before_quit, "Ask before quit")).on_hover_text(GUPAX_ASK_BEFORE_QUIT);
ui.separator();
ui.add_sized([width, height], egui::Checkbox::new(&mut state.save_before_quit, "Save before quit")).on_hover_text(GUPAX_SAVE_BEFORE_QUIT);
ui.add_sized([width, height], egui::Checkbox::new(&mut self.save_before_quit, "Save before quit")).on_hover_text(GUPAX_SAVE_BEFORE_QUIT);
});
});
ui.add_space(SPACE);
ui.style_mut().override_text_style = Some(Monospace);
let height = height/20.0;
let text_edit = (ui.available_width()/10.0)-SPACE;
ui.horizontal(|ui| {
ui.label("P2Pool binary path:");
if self.p2pool_path.is_empty() {
ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ").color(Color32::LIGHT_GRAY)));
} else {
match crate::disk::into_absolute_path(self.p2pool_path.clone()) {
Ok(path) => {
if path.is_file() {
ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ✔").color(Color32::from_rgb(100, 230, 100))))
} else {
ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(Color32::from_rgb(230, 50, 50))))
}
},
_ => ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(Color32::from_rgb(230, 50, 50)))),
};
}
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
ui.text_edit_singleline(&mut state.p2pool_path).on_hover_text(GUPAX_PATH_P2POOL);
ui.text_edit_singleline(&mut self.p2pool_path).on_hover_text(GUPAX_PATH_P2POOL);
});
ui.horizontal(|ui| {
ui.label("XMRig binary path: ");
if self.xmrig_path.is_empty() {
ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ").color(Color32::LIGHT_GRAY)));
} else {
match crate::disk::into_absolute_path(self.xmrig_path.clone()) {
Ok(path) => {
if path.is_file() {
ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ✔").color(Color32::from_rgb(100, 230, 100))))
} else {
ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(Color32::from_rgb(230, 50, 50))))
}
},
_ => ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(Color32::from_rgb(230, 50, 50)))),
};
}
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
ui.text_edit_singleline(&mut state.xmrig_path).on_hover_text(GUPAX_PATH_XMRIG);
ui.text_edit_singleline(&mut self.xmrig_path).on_hover_text(GUPAX_PATH_XMRIG);
});
}
}

View file

@ -49,14 +49,14 @@ use std::path::PathBuf;
mod ferris;
mod constants;
mod node;
mod state;
mod disk;
mod about;
mod status;
mod gupax;
mod p2pool;
mod xmrig;
mod update;
use {ferris::*,constants::*,node::*,state::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*,update::*};
use {ferris::*,constants::*,node::*,disk::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*,update::*};
//---------------------------------------------------------------------------------------------------- Struct + Impl
// The state of the outer main [App].
@ -74,6 +74,7 @@ pub struct App {
og: Arc<Mutex<State>>, // og = Old state to compare against
state: State, // state = Working state (current settings)
update: Arc<Mutex<Update>>, // State for update data [update.rs]
node_vec: Vec<(String, Node)>, // Manual Node database
diff: bool, // This bool indicates state changes
// Process/update state:
// Doesn't make sense to save this on disk
@ -92,7 +93,7 @@ pub struct App {
version: &'static str, // Gupax version
name_version: String, // [Gupax vX.X.X]
banner: RetainedImage, // Gupax banner image
addr_regex: Regex, // [4.*] Monero Address Regex
regex: Regexes, // Custom Struct holding pre-made [Regex]'s
}
impl App {
@ -113,9 +114,10 @@ impl App {
ping: Arc::new(Mutex::new(Ping::new())),
width: 1280.0,
height: 720.0,
og: Arc::new(Mutex::new(State::default())),
state: State::default(),
og: Arc::new(Mutex::new(State::new())),
state: State::new(),
update: Arc::new(Mutex::new(Update::new(String::new(), PathBuf::new(), PathBuf::new(), true))),
node_vec: Node::new_vec(),
diff: false,
p2pool: false,
xmrig: false,
@ -129,7 +131,7 @@ impl App {
version: GUPAX_VERSION,
name_version: format!("Gupax {}", GUPAX_VERSION),
banner: RetainedImage::from_image_bytes("banner.png", BYTES_BANNER).unwrap(),
addr_regex: Regex::new("^4[A-Za-z1-9]+$").unwrap(),
regex: Regexes::new(),
};
// Apply arg state
let mut app = parse_args(app);
@ -151,6 +153,8 @@ impl App {
};
}
app.state = app.og.lock().unwrap().clone();
// Get node list
app.node_vec = Node::get().unwrap();
// Handle max threads
app.og.lock().unwrap().xmrig.max_threads = num_cpus::get();
let current = app.og.lock().unwrap().xmrig.current_threads;
@ -167,7 +171,7 @@ impl App {
}
}
//---------------------------------------------------------------------------------------------------- Enum + Impl
//---------------------------------------------------------------------------------------------------- [Tab] Enum + Impl
// The tabs inside [App].
#[derive(Clone, Copy, Debug, PartialEq)]
enum Tab {
@ -184,6 +188,28 @@ impl Default for Tab {
}
}
//---------------------------------------------------------------------------------------------------- [Regexes] struct
#[derive(Clone, Debug)]
struct Regexes {
name: Regex,
address: Regex,
ipv4: Regex,
domain: Regex,
port: Regex,
}
impl Regexes {
fn new() -> Self {
Regexes {
name: Regex::new("^[A-Za-z0-9-_]+( [A-Za-z0-9-_]+)*$").unwrap(),
address: Regex::new("^4[A-Za-z1-9]+$").unwrap(), // This still needs to check for (l, I, o, 0)
ipv4: Regex::new(r#"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"#).unwrap(),
domain: Regex::new(r#"^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$"#).unwrap(),
port: Regex::new(r#"^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$"#).unwrap(),
}
}
}
//---------------------------------------------------------------------------------------------------- Init functions
fn init_text_styles(ctx: &egui::Context, width: f32) {
let scale = width / 26.666;
@ -196,6 +222,7 @@ fn init_text_styles(ctx: &egui::Context, width: f32) {
(Heading, FontId::new(scale/1.5, Proportional)),
(Name("Tab".into()), FontId::new(scale*1.2, Proportional)),
(Name("Bottom".into()), FontId::new(scale/2.0, Proportional)),
(Name("MonospaceSmall".into()), FontId::new(scale/2.5, egui::FontFamily::Monospace)),
].into();
// style.visuals.selection.stroke = Stroke { width: 5.0, color: Color32::from_rgb(255, 255, 255) };
// style.spacing.slider_width = scale;
@ -301,7 +328,9 @@ fn init_auto(app: &App) {
}
// [Auto-Ping]
if app.og.lock().unwrap().p2pool.auto_node {
let auto_node = app.og.lock().unwrap().p2pool.auto_node;
let simple = app.og.lock().unwrap().p2pool.simple;
if auto_node && simple {
let ping = Arc::clone(&app.ping);
let og = Arc::clone(&app.og);
thread::spawn(move|| {
@ -323,7 +352,7 @@ fn parse_args(mut app: App) -> App {
match arg.as_str() {
"-h"|"--help" => { println!("{}", ARG_HELP); exit(0); },
"-v"|"--version" => {
println!("Gupax {} (OS: {}, Commit: {})\n\n{}", GUPAX_VERSION, OS_NAME, &COMMIT[..40], ARG_COPYRIGHT);
println!("Gupax {} [OS: {}, Commit: {}]\n\n{}", GUPAX_VERSION, OS_NAME, &COMMIT[..40], ARG_COPYRIGHT);
exit(0);
},
"-f"|"--ferris" => { println!("{}", FERRIS); exit(0); },
@ -333,9 +362,11 @@ fn parse_args(mut app: App) -> App {
// Everything else
for arg in args {
match arg.as_str() {
"-l"|"--node-list" => { info!("Printing node list..."); print_disk_file(File::Node); }
"-s"|"--state" => { info!("Printing state..."); print_disk_file(File::State); }
"-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); },
"-r"|"--reset" => { info!("Resetting state..."); app.reset = true; }
_ => { eprintln!("[Gupax error] Invalid option: [{}]\nFor help, use: [--help]", arg); exit(1); },
}
}
app
@ -373,6 +404,18 @@ pub fn clean_dir() -> Result<(), anyhow::Error> {
Ok(())
}
// 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); },
};
match std::fs::read_to_string(&path) {
Ok(string) => { println!("{}", string); exit(0); },
Err(e) => { error!("{}", e); exit(1); },
}
}
//---------------------------------------------------------------------------------------------------- [App] frame for [Panic] situations
fn panic_main(error: String) {
error!("{}", error);
@ -560,7 +603,7 @@ impl eframe::App for App {
// Top: Tabs
egui::TopBottomPanel::top("top").show(ctx, |ui| {
let width = (self.width - (SPACE*10.0))/5.0;
let height = self.height/10.0;
let height = self.height/12.0;
ui.group(|ui| {
ui.add_space(4.0);
ui.horizontal(|ui| {
@ -584,7 +627,7 @@ impl eframe::App for App {
// Bottom: app info + state/process buttons
egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| {
let height = self.height/18.0;
let height = self.height/20.0;
ui.style_mut().override_text_style = Some(Name("Bottom".into()));
ui.horizontal(|ui| {
ui.group(|ui| {
@ -632,11 +675,11 @@ impl eframe::App for App {
Tab::P2pool => {
ui.group(|ui| {
let width = width / 1.5;
if ui.add_sized([width, height], egui::SelectableLabel::new(!self.state.p2pool.simple, "Advanced")).clicked() {
if ui.add_sized([width, height], egui::SelectableLabel::new(!self.state.p2pool.simple, "Advanced")).on_hover_text(P2POOL_ADVANCED).clicked() {
self.state.p2pool.simple = false;
}
ui.separator();
if ui.add_sized([width, height], egui::SelectableLabel::new(self.state.p2pool.simple, "Simple")).clicked() {
if ui.add_sized([width, height], egui::SelectableLabel::new(self.state.p2pool.simple, "Simple")).on_hover_text(P2POOL_SIMPLE).clicked() {
self.state.p2pool.simple = true;
}
});
@ -725,7 +768,7 @@ impl eframe::App for App {
Gupax::show(&mut self.state.gupax, &self.og, &self.state.version, &self.update, self.width, self.height, ctx, ui);
}
Tab::P2pool => {
P2pool::show(&mut self.state.p2pool, &self.og, self.p2pool, &self.ping, &self.addr_regex, self.width, self.height, ctx, ui);
P2pool::show(&mut self.state.p2pool, &mut self.node_vec, &self.og, self.p2pool, &self.ping, &self.regex, self.width, self.height, ctx, ui);
}
Tab::Xmrig => {
Xmrig::show(&mut self.state.xmrig, self.width, self.height, ctx, ui);

View file

@ -15,33 +15,73 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::App;
use crate::constants::*;
use crate::state::*;
use crate::node::*;
use crate::node::NodeEnum::*;
use crate::{
App,
Regexes,
constants::*,
disk::*,
node::*
};
use egui::{
TextEdit,SelectableLabel,ComboBox,Label,FontId,Button,Color32,RichText,Slider,Checkbox,
TextStyle::*,
FontFamily::Proportional,
TextBuffer,
};
use std::sync::{Arc,Mutex};
use std::thread;
use log::*;
use egui::{TextEdit,SelectableLabel,ComboBox,Label};
use egui::TextStyle::*;
use egui::FontFamily::Proportional;
use egui::{FontId,Button,Color32,RichText};
use regex::Regex;
use log::*;
impl P2pool {
pub fn show(&mut self, og: &Arc<Mutex<State>>, online: bool, ping: &Arc<Mutex<Ping>>, addr_regex: &Regex, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) {
let text_edit = height / 20.0;
// Console
pub fn show(&mut self, node_vec: &mut Vec<(String, Node)>, og: &Arc<Mutex<State>>, online: bool, ping: &Arc<Mutex<Ping>>, regex: &Regexes, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) {
let text_edit = height / 22.0;
//---------------------------------------------------------------------------------------------------- Console
ui.group(|ui| {
let height = height / SPACE;
let width = width - SPACE;
ui.style_mut().override_text_style = Some(Monospace);
ui.add_sized([width, height*3.0], TextEdit::multiline(&mut "".to_string()));
ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut "".to_string()), r#"Type a command (e.g "help" or "status") and press Enter"#));
});
//---------------------------------------------------------------------------------------------------- Args
if ! self.simple {
ui.group(|ui| { ui.horizontal(|ui| {
let width = (width/10.0) - SPACE;
ui.style_mut().override_text_style = Some(Monospace);
ui.add_sized([width, text_edit], Label::new("Command arguments:"));
ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.arguments), r#"--wallet <...> --host <...>"#)).on_hover_text(P2POOL_COMMAND);
self.arguments.truncate(1024);
})});
ui.set_enabled(self.arguments.is_empty());
}
//---------------------------------------------------------------------------------------------------- Address
ui.group(|ui| {
let width = width - SPACE;
ui.spacing_mut().text_edit_width = (width)-(SPACE*3.0);
ui.style_mut().override_text_style = Some(Monospace);
let text;
let color;
let len = format!("{:02}", self.address.len());
if self.address.is_empty() {
text = format!("Monero Address [{}/95] ", len);
color = Color32::LIGHT_GRAY;
} else if self.address.len() == 95 && Regex::is_match(&regex.address, &self.address) && ! self.address.contains("0") && ! self.address.contains("O") && ! self.address.contains("l") {
text = format!("Monero Address [{}/95] ✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!("Monero Address [{}/95] ❌", len);
color = Color32::from_rgb(230, 50, 50);
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4...")).on_hover_text(P2POOL_ADDRESS);
self.address.truncate(95);
});
//---------------------------------------------------------------------------------------------------- Simple
let height = ui.available_height();
// [Simple]
if self.simple {
// [Node]
let height = height / 6.0;
@ -50,7 +90,7 @@ impl P2pool {
ui.vertical(|ui| {
ui.horizontal(|ui| {
// [Ping List]
let id = og.lock().unwrap().p2pool.node;
let id = self.node;
let ip = enum_to_ip(id);
let mut ms = 0;
let mut color = Color32::LIGHT_GRAY;
@ -61,13 +101,13 @@ impl P2pool {
break
}
}
let text = RichText::new(format!("{}ms | {} | {}", ms, id, ip)).color(color);
let text = RichText::new(format!(" {}ms | {} | {}", ms, id, ip)).color(color);
ComboBox::from_id_source("nodes").selected_text(RichText::text_style(text, Monospace)).show_ui(ui, |ui| {
for data in ping.lock().unwrap().nodes.iter() {
let ms = crate::node::format_ms(data.ms);
let id = crate::node::format_enum(data.id);
let text = RichText::text_style(RichText::new(format!("{} | {} | {}", ms, id, data.ip)).color(data.color), Monospace);
ui.selectable_value(&mut og.lock().unwrap().p2pool.node, data.id, text);
let text = RichText::text_style(RichText::new(format!(" {} | {} | {}", ms, id, data.ip)).color(data.color), Monospace);
ui.selectable_value(&mut self.node, data.id, text);
}
});
});
@ -130,131 +170,193 @@ impl P2pool {
ui.add_sized([width, height], egui::Checkbox::new(&mut self.auto_node, "Auto-node")).on_hover_text(P2POOL_AUTO_NODE);
})});
// [Address]
let height = ui.available_height();
ui.horizontal(|ui| {
if self.address.is_empty() {
ui.add_sized([width, text_edit], Label::new(RichText::new("Monero Payout Address ").color(Color32::LIGHT_GRAY)));
} else if self.address.len() == 95 && Regex::is_match(addr_regex, &self.address) {
ui.add_sized([width, text_edit], Label::new(RichText::new("Monero Payout Address ✔").color(Color32::from_rgb(100, 230, 100))));
} else {
ui.add_sized([width, text_edit], Label::new(RichText::new("Monero Payout Address ❌").color(Color32::from_rgb(230, 50, 50))));
}
});
ui.spacing_mut().text_edit_width = (width)-(SPACE*3.0);
ui.style_mut().override_text_style = Some(Monospace);
ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4...")).on_hover_text(P2POOL_ADDRESS);
// [Advanced]
//---------------------------------------------------------------------------------------------------- Advanced
} else {
// TODO:
// ping code
// If ping was pressed, start thread
// if self.ping {
// self.ping = false;
// self.pinging = Arc::new(Mutex::new(true));
// let node_clone = Arc::clone(&self.node);
// let pinging_clone = Arc::clone(&self.pinging);
// thread::spawn(move|| {
// let result = NodeStruct::ping();
// *node_clone.lock().unwrap() = result.nodes;
// *pinging_clone.lock().unwrap() = false;
// });
// }
// 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.pinging.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
// }
let width = width - 30.0;
let mut style = (*ctx.style()).clone();
let height = ui.available_height()/1.2;
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
// [Monero node IP/RPC/ZMQ]
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(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;
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 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 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 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(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(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::Selsta1, SELSTA_1);
});
// );
});
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 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 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 self.zmq.to_string());
});
})});
});
ui.group(|ui| {
let width = width/10.0;
ui.vertical(|ui| {
ui.style_mut().override_text_style = Some(Monospace);
ui.spacing_mut().text_edit_width = width*3.32;
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:02}", self.name.len());
if self.name.is_empty() {
text = format!("Name [ {}/30 ]", len);
color = Color32::LIGHT_GRAY;
incorrect_input = true;
} else if Regex::is_match(&regex.name, &self.name) {
text = format!("Name [ {}/30 ]✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!("Name [ {}/30 ]❌", len);
color = Color32::from_rgb(230, 50, 50);
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.name).on_hover_text(P2POOL_NAME);
self.name.truncate(30);
});
ui.horizontal(|ui| {
let text;
let color;
let len = format!("{:03}", self.ip.len());
if self.ip.is_empty() {
text = format!(" IP [{}/255]", len);
color = Color32::LIGHT_GRAY;
incorrect_input = true;
} else if self.ip == "localhost" || Regex::is_match(&regex.ipv4, &self.ip) || Regex::is_match(&regex.domain, &self.ip) {
text = format!(" IP [{}/255]✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!(" IP [{}/255]❌", len);
color = Color32::from_rgb(230, 50, 50);
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.ip).on_hover_text(P2POOL_NODE_IP);
self.ip.truncate(255);
});
ui.horizontal(|ui| {
let text;
let color;
let len = self.rpc.len();
if self.rpc.is_empty() {
text = format!(" RPC [ {}/5 ]", len);
color = Color32::LIGHT_GRAY;
incorrect_input = true;
} else if Regex::is_match(&regex.port, &self.rpc) {
text = format!(" RPC [ {}/5 ]✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!(" RPC [ {}/5 ]❌", len);
color = Color32::from_rgb(230, 50, 50);
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.rpc).on_hover_text(P2POOL_RPC_PORT);
self.rpc.truncate(5);
});
ui.horizontal(|ui| {
let text;
let color;
let len = self.zmq.len();
if self.zmq.is_empty() {
text = format!(" ZMQ [ {}/5 ]", len);
color = Color32::LIGHT_GRAY;
incorrect_input = true;
} else if Regex::is_match(&regex.port, &self.zmq) {
text = format!(" ZMQ [ {}/5 ]✔", len);
color = Color32::from_rgb(100, 230, 100);
} else {
text = format!(" ZMQ [ {}/5 ]❌", len);
color = Color32::from_rgb(230, 50, 50);
incorrect_input = true;
}
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
ui.text_edit_singleline(&mut self.zmq).on_hover_text(P2POOL_ZMQ_PORT);
self.zmq.truncate(5);
});
});
ui.vertical(|ui| {
let width = ui.available_width();
ui.add_space(1.0);
// [Manual node selection]
ui.spacing_mut().slider_width = width - 8.0;
ui.spacing_mut().icon_width = width / 25.0;
// [Ping List]
let text = RichText::new(format!("{} | {}", self.selected_name, self.selected_ip));
ComboBox::from_id_source("nodes").selected_text(RichText::text_style(text, Monospace)).show_ui(ui, |ui| {
for (name, node) in node_vec.iter() {
let text = RichText::text_style(RichText::new(format!("{}\n IP: {}\n RPC: {}\n ZMQ: {}", name, node.ip, node.rpc, node.zmq)), Monospace);
ui.selectable_value(&mut self.selected_name, name.clone(), text);
// println!("{}", value.ip);
}
});
// [Add] + [Delete]
let node_vec_len = node_vec.len();
ui.horizontal(|ui| {
let mut exists = false;
for (name, _) in node_vec.iter() {
if *name == self.name { exists = true; }
}
ui.set_enabled(!incorrect_input && !exists && node_vec_len < 100);
let text = format!("{}\n Max amount of nodes: 100\n Current amount: [{}/100]", P2POOL_ADD, node_vec_len);
if ui.add_sized([width, text_edit], Button::new("Add")).on_hover_text(text).clicked() {
let node = Node {
ip: self.ip.clone(),
rpc: self.rpc.clone(),
zmq: self.zmq.clone(),
};
node_vec.push((self.name.clone(), node));
}
});
ui.horizontal(|ui| {
ui.set_enabled(node_vec_len > 1);
let text = format!("{}\n Max amount of nodes: 100\n Current amount: [{}/100]", P2POOL_DELETE, node_vec_len);
if ui.add_sized([width, text_edit], Button::new("Delete")).on_hover_text(text).clicked() {
let mut n = 0;
for (name, _) in node_vec.iter() {
if *name == self.selected_name {
self.selected_name = node_vec[n-1].0.clone();
node_vec.remove(n);
break
}
n += 1;
}
}
});
ui.horizontal(|ui| {
ui.set_enabled(!self.name.is_empty() || !self.ip.is_empty() || !self.rpc.is_empty() || !self.zmq.is_empty());
if ui.add_sized([width, text_edit], Button::new("Clear")).on_hover_text(P2POOL_CLEAR).clicked() {
self.name.clear();
self.ip.clear();
self.rpc.clear();
self.zmq.clear();
}
});
});
});
});
ui.add_space(5.0);
// [Main/Mini]
ui.horizontal(|ui| {
ui.spacing_mut().text_edit_width = ui.available_width();
ui.label("Address:");
ui.text_edit_singleline(&mut self.address);
let height = height/3.0;
ui.group(|ui| { ui.horizontal(|ui| {
let width = (width/4.0)-SPACE;
let height = height + 6.0;
if ui.add_sized([width, height], SelectableLabel::new(self.mini == false, "P2Pool Main")).on_hover_text(P2POOL_MAIN).clicked() { self.mini = false; }
if ui.add_sized([width, height], SelectableLabel::new(self.mini == true, "P2Pool Mini")).on_hover_text(P2POOL_MINI).clicked() { self.mini = true; }
})});
// [Out/In Peers] + [Log Level]
ui.group(|ui| { ui.vertical(|ui| {
let text = (ui.available_width()/10.0)-SPACE;
let width = (text*8.0)-SPACE;
let height = height/3.0;
ui.style_mut().spacing.slider_width = width/1.2;
ui.style_mut().spacing.interact_size.y = height;
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
// ui.style_mut().override_text_style = Some(Monospace);
ui.horizontal(|ui| {
ui.add_sized([text, height], Label::new("Out peers [10-450]:"));
ui.add_sized([width, height], Slider::new(&mut self.out_peers, 10..=450)).on_hover_text(P2POOL_OUT);
ui.add_space(ui.available_width()-4.0);
});
ui.horizontal(|ui| {
ui.add_sized([text, height], Label::new(" In peers [10-450]:"));
ui.add_sized([width, height], Slider::new(&mut self.in_peers, 10..=450)).on_hover_text(P2POOL_IN);
});
ui.horizontal(|ui| {
ui.add_sized([text, height], Label::new(" Log level [0-6]:"));
ui.add_sized([width, height], Slider::new(&mut self.log_level, 0..=6)).on_hover_text(P2POOL_LOG);
});
})});
});
}
}
}

View file

@ -1,350 +0,0 @@
// Gupax - GUI Uniting P2Pool And XMRig
//
// Copyright (c) 2022 hinto-janaiyo
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// This handles reading/parsing the state file: [gupax.toml]
// The TOML format is used. This struct hierarchy directly
// translates into the TOML parser:
// State/
// ├─ Gupax/
// │ ├─ ...
// ├─ P2pool/
// │ ├─ ...
// ├─ Xmrig/
// │ ├─ ...
// ├─ Version/
// ├─ ...
use std::{fs,env};
use std::fmt::Display;
use std::path::{Path,PathBuf};
use std::result::Result;
use std::sync::{Arc,Mutex};
use serde::{Serialize,Deserialize};
use figment::Figment;
use figment::providers::{Format,Toml};
use crate::constants::HORIZONTAL;
use log::*;
//---------------------------------------------------------------------------------------------------- Impl
impl State {
pub fn default() -> Self {
use crate::constants::{P2POOL_VERSION,XMRIG_VERSION};
let max_threads = num_cpus::get();
let current_threads;
if max_threads == 1 { current_threads = 1; } else { current_threads = max_threads / 2; }
Self {
gupax: Gupax {
auto_update: true,
auto_node: true,
ask_before_quit: true,
save_before_quit: true,
update_via_tor: true,
p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
xmrig_path: DEFAULT_XMRIG_PATH.to_string(),
absolute_p2pool_path: Self::into_absolute_path(DEFAULT_P2POOL_PATH.to_string()).unwrap(),
absolute_xmrig_path: Self::into_absolute_path(DEFAULT_XMRIG_PATH.to_string()).unwrap(),
},
p2pool: P2pool {
simple: true,
mini: true,
auto_node: true,
auto_select: true,
out_peers: 10,
in_peers: 10,
log_level: 3,
node: crate::NodeEnum::C3pool,
monerod: "localhost".to_string(),
rpc: 18081,
zmq: 18083,
address: "".to_string(),
},
xmrig: Xmrig {
simple: true,
tls: false,
nicehash: false,
keepalive: false,
hugepages_jit: true,
current_threads,
max_threads,
priority: 2,
pool: "localhost:3333".to_string(),
address: "".to_string(),
},
version: Arc::new(Mutex::new(Version {
p2pool: Arc::new(Mutex::new(P2POOL_VERSION.to_string())),
xmrig: Arc::new(Mutex::new(XMRIG_VERSION.to_string())),
})),
}
}
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
// Windows | {FOLDERID_RoamingAppData} | C:\Users\Alice\AppData\Roaming
let mut path = match dirs::data_dir() {
Some(mut path) => {
path.push(STATE_DIRECTORY);
info!("OS data path ... OK");
path
},
None => { error!("Couldn't get OS PATH for data"); return Err(TomlError::Path(PATH_ERROR.to_string())) },
};
// Create directory
fs::create_dir_all(&path)?;
path.push(STATE_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) => {
warn!("TOML not found, attempting to create default");
let default = match toml::ser::to_string(&Self::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(default)
},
}
}
// Attempt to parse from String
// If failed, assume we're working with an old [State]
// and attempt to merge it with a new [State::default()].
pub fn parse(string: String) -> Result<Self, TomlError> {
match toml::de::from_str(&string) {
Ok(toml) => {
info!("TOML parse ... OK");
Self::info(&toml);
Ok(toml)
},
Err(err) => {
warn!("Couldn't parse TOML, assuming old [State], attempting merge...");
Self::merge(&Self::default())
},
}
}
// Last three functions combined
// get_path() -> read_or_create() -> parse()
pub fn get() -> Result<Self, TomlError> {
Self::parse(Self::read_or_create(Self::get_path()?)?)
}
// Completely overwrite current [gupax.toml]
// with a new default version, and return [Self].
pub fn new_default() -> Result<Self, TomlError> {
info!("Creating new default TOML...");
let default = Self::default();
let path = Self::get_path()?;
let string = match toml::ser::to_string(&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, &string)?;
info!("TOML write ... OK");
Ok(default)
}
// Turn relative paths into absolute paths
fn into_absolute_path(path: String) -> Result<PathBuf, TomlError> {
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)
}
}
// Save [State] onto disk file [gupax.toml]
pub fn save(&mut self) -> Result<(), TomlError> {
info!("Saving TOML to disk...");
let path = Self::get_path()?;
// Convert path to absolute
self.gupax.absolute_p2pool_path = Self::into_absolute_path(self.gupax.p2pool_path.clone())?;
self.gupax.absolute_xmrig_path = Self::into_absolute_path(self.gupax.xmrig_path.clone())?;
let string = match toml::ser::to_string(&self) {
Ok(string) => {
info!("TOML parse ... OK");
Self::info(&self);
string
},
Err(err) => { error!("Couldn't parse TOML into string"); 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)) },
}
}
// Take [Self] 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: &Self) -> Result<Self, TomlError> {
info!("Starting TOML merge...");
let old = match toml::ser::to_string(&old) {
Ok(string) => { info!("Old TOML parse ... OK"); string },
Err(err) => { error!("Couldn't parse old TOML into string"); return Err(TomlError::Serialize(err)) },
};
let default = match toml::ser::to_string(&Self::default()) {
Ok(string) => { info!("Default TOML parse ... OK"); string },
Err(err) => { error!("Couldn't parse default TOML into string"); return Err(TomlError::Serialize(err)) },
};
let mut new: Self = match Figment::new().merge(Toml::string(&old)).merge(Toml::string(&default)).extract() {
Ok(new) => { info!("TOML merge ... OK"); new },
Err(err) => { error!("Couldn't merge default + old TOML"); return Err(TomlError::Merge(err)) },
};
// Attempt save
Self::save(&mut new)?;
Ok(new)
}
// Write [Self] to console with
// [info!] surrounded by "---"
pub fn info(&self) -> Result<(), toml::ser::Error> {
info!("{}", HORIZONTAL);
for i in toml::ser::to_string(&self)?.lines() { info!("{}", i); }
info!("{}", HORIZONTAL);
Ok(())
}
}
impl Display for TomlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use TomlError::*;
match self {
Io(err) => write!(f, "{}: Io | {}", ERROR, err),
Path(err) => write!(f, "{}: Path | {}", ERROR, err),
Serialize(err) => write!(f, "{}: Serialize | {}", ERROR, err),
Deserialize(err) => write!(f, "{}: Deserialize | {}", ERROR, err),
Merge(err) => write!(f, "{}: Merge | {}", ERROR, err),
}
}
}
impl From<std::io::Error> for TomlError {
fn from(err: std::io::Error) -> Self {
TomlError::Io(err)
}
}
//---------------------------------------------------------------------------------------------------- Const
// State file
const STATE_FILE: &'static str = "gupax.toml";
const ERROR: &'static str = "TOML Error";
const PATH_ERROR: &'static str = "PATH for state directory could not be not found";
#[cfg(target_os = "windows")]
const STATE_DIRECTORY: &'static str = "Gupax";
#[cfg(target_os = "macos")]
const STATE_DIRECTORY: &'static str = "com.github.hinto-janaiyo.gupax";
#[cfg(target_os = "linux")]
const STATE_DIRECTORY: &'static str = "gupax";
#[cfg(target_os = "windows")]
pub const DEFAULT_P2POOL_PATH: &'static str = r"P2Pool\p2pool.exe";
#[cfg(target_family = "unix")]
pub const DEFAULT_P2POOL_PATH: &'static str = "p2pool/p2pool";
#[cfg(target_os = "windows")]
pub const DEFAULT_XMRIG_PATH: &'static str = r"XMRig\xmrig.exe";
#[cfg(target_family = "unix")]
pub const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig";
//---------------------------------------------------------------------------------------------------- Error Enum
#[derive(Debug)]
pub enum TomlError {
Io(std::io::Error),
Path(String),
Serialize(toml::ser::Error),
Deserialize(toml::de::Error),
Merge(figment::Error),
}
//---------------------------------------------------------------------------------------------------- Structs
#[derive(Clone,Debug,Deserialize,Serialize)]
pub struct State {
pub gupax: Gupax,
pub p2pool: P2pool,
pub xmrig: Xmrig,
pub version: Arc<Mutex<Version>>,
}
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub struct Gupax {
pub auto_update: bool,
pub auto_node: bool,
pub ask_before_quit: bool,
pub save_before_quit: bool,
pub update_via_tor: bool,
pub p2pool_path: String,
pub xmrig_path: String,
pub absolute_p2pool_path: PathBuf,
pub absolute_xmrig_path: PathBuf,
}
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub struct P2pool {
pub simple: bool,
pub mini: bool,
pub auto_node: bool,
pub auto_select: 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,
// pub config: String,
// pub args: 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: usize,
pub current_threads: usize,
pub priority: u8,
pub pool: String,
pub address: String,
// pub config: String,
// pub args: String,
}
#[derive(Clone,Debug,Deserialize,Serialize)]
pub struct Version {
pub p2pool: Arc<Mutex<String>>,
pub xmrig: Arc<Mutex<String>>,
}

View file

@ -30,7 +30,7 @@ use arti_hyper::*;
use arti_hyper::*;
use crate::constants::GUPAX_VERSION;
//use crate::{Name::*,State};
use crate::state::*;
use crate::disk::*;
use crate::update::Name::*;
use hyper::{Client,Body,Request};
use hyper::header::HeaderValue;

View file

@ -21,7 +21,7 @@ use std::str::FromStr;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use num_cpus;
use crate::constants::*;
use crate::state::Xmrig;
use crate::disk::Xmrig;
impl Xmrig {
pub fn show(&mut self, width: f32, height: f32, ctx: &egui::Context, ui: &mut egui::Ui) {