mirror of
https://github.com/Cyrix126/gupaxx.git
synced 2024-11-17 07:47:35 +00:00
p2pool: add [Advanced], add [node.toml] database, add char limit
This commit is contained in:
parent
3f4124622c
commit
9faf0fc9f5
9 changed files with 925 additions and 537 deletions
|
@ -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
|
||||
|
|
|
@ -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"#;
|
||||
-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
537
src/disk.rs
Normal 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>>,
|
||||
}
|
71
src/gupax.rs
71
src/gupax.rs
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
71
src/main.rs
71
src/main.rs
|
@ -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,6 +362,8 @@ 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); },
|
||||
|
@ -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);
|
||||
|
|
374
src/p2pool.rs
374
src/p2pool.rs
|
@ -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(®ex.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;
|
||||
|
@ -67,7 +107,7 @@ impl P2pool {
|
|||
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);
|
||||
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))));
|
||||
//---------------------------------------------------------------------------------------------------- Advanced
|
||||
} 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]
|
||||
} 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| {
|
||||
ui.spacing_mut().text_edit_width = ui.available_width();
|
||||
ui.label("Address:");
|
||||
ui.text_edit_singleline(&mut self.address);
|
||||
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(®ex.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(®ex.ipv4, &self.ip) || Regex::is_match(®ex.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(®ex.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(®ex.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| {
|
||||
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);
|
||||
});
|
||||
})});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
350
src/state.rs
350
src/state.rs
|
@ -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>>,
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue