add toml.rs/constants.rs, ask_before_quit screen

This commit is contained in:
hinto-janaiyo 2022-10-13 08:57:50 -04:00
parent d315e5c7cb
commit ab9b98a819
No known key found for this signature in database
GPG key ID: D7483F6CA27D1B1D
7 changed files with 453 additions and 46 deletions

88
Cargo.lock generated
View file

@ -133,6 +133,15 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.11.0"
@ -346,6 +355,15 @@ dependencies = [
"libc",
]
[[package]]
name = "cpufeatures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
@ -427,6 +445,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "cty"
version = "0.2.2"
@ -440,7 +468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
dependencies = [
"byteorder",
"digest",
"digest 0.9.0",
"rand_core 0.5.1",
"subtle",
"zeroize",
@ -490,6 +518,36 @@ dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "dispatch"
version = "0.2.0"
@ -950,15 +1008,21 @@ name = "gupax"
version = "0.1.0"
dependencies = [
"chrono",
"dirs",
"eframe",
"egui",
"egui_extras",
"env_logger",
"hex-literal",
"image",
"log",
"monero",
"num_cpus",
"regex",
"serde",
"serde_derive",
"sha2",
"toml",
]
[[package]]
@ -1699,6 +1763,17 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom 0.2.7",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.6.0"
@ -1820,6 +1895,17 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "sha2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.5",
]
[[package]]
name = "shared_library"
version = "0.1.9"

View file

@ -4,27 +4,33 @@ version = "0.1.0"
edition = "2021"
[dependencies]
egui = "0.19.0"
eframe = "0.19.0"
egui_extras = { version = "0.19.0", features = ["image"] }
image = { version = "0.24.4", features = ["png"] }
num_cpus = "1.13.1"
monero = "0.18.0"
regex = "1.6.0"
log = "0.4.17"
env_logger = "0.9.1"
dirs = "4.0.0"
chrono = "0.4.22"
eframe = "0.19.0"
egui = "0.19.0"
egui_extras = { version = "0.19.0", features = ["image"] }
env_logger = "0.9.1"
hex-literal = "0.3.4"
image = { version = "0.24.4", features = ["png"] }
log = "0.4.17"
monero = "0.18.0"
num_cpus = "1.13.1"
regex = "1.6.0"
serde = "1.0.145"
serde_derive = "1.0.145"
sha2 = "0.10.6"
toml = "0.5.9"
[profile.optimized]
inherits = "release"
strip = "debuginfo"
debug = false
codegen-units = 1
debug-assertions = false
debug = false
incremental = true
inherits = "release"
lto = true
overflow-checks = false
incremental = true
codegen-units = 1
rpath = false
strip = "debuginfo"
[profile.optimized.package."*"]
opt-level = 3

View file

@ -4,4 +4,17 @@ set -e
[[ $PWD = */gupax ]]
RUSTFLAGS="-C target-cpu=native" cargo build --profile optimized && du -hs target/optimized/gupax
if [[ $1 = *all* ]]; then
echo "=== building all ==="
echo "=== windows ==="
cargo build --profile optimized --target x86_64-pc-windows-gnu
# echo "=== macos ==="
# cargo build --profile optimized --target x86_64-apple-darwin
echo "=== linux ==="
cargo build --profile optimized
du -hs target/x86_64-pc-windows-gnu/optimized/gupax target/x86_64-apple-darwin/optimized/gupax target/optimized/gupax
else
echo "=== building linux cpu optimized ==="
RUSTFLAGS="-C target-cpu=native" cargo build --profile optimized
du -hs target/optimized/gupax
fi

49
src/README.md Normal file
View file

@ -0,0 +1,49 @@
# Gupax source files
* [State](#State)
* [Structure](#Structure)
* [Bootstrap](#Bootstrap)
## Structure
| File/Folder | Purpose |
|----------------|---------|
| `about.rs` | Struct/impl for `About` tab
| `constants.rs` | General constants needed in Gupax
| `gupax.rs` | Struct/impl for `Gupax` tab
| `main.rs` | Struct/enum/impl for `App/Tab/State`, init functions, main function
| `p2pool.rs` | Struct/impl for `P2Pool` tab
| `status.rs` | Struct/impl for `Status` tab
| `toml.rs` | Struct/impl for `gupax.toml`, the disk state
| `xmrig.rs` | Struct/impl for `XMRig` tab
## Bootstrap
This is how Gupax works internally when starting up, divided into 3 sections.
1. **INIT**
- Initialize custom console logging with `log`, `env_logger` || *warn!*
- Initialize misc data (structs, text styles, thread count, images, etc) || *panic!*
- Check for admin privilege (for XMRig) || *warn!*
- Attempt to read `gupax.toml` || *warn!*, *initialize config with default options*
- If errors were found, pop-up window
2. **AUTO-UPDATE**
- If `auto_update` == `true`, pop-up auto-updating window || *info!*, *skip auto-update*
- Multi-threaded GitHub API check on Gupax -> P2Pool -> XMRig || *warn!*, *skip auto-update*
- Multi-threaded download if current version != new version || *warn!*, *skip auto-update*
- After download, atomically replace current binaries with new || *warn!*, *skip auto-update*
- Update version metadata || *warn!*, *skip auto-update*
3. **MAIN**
- All data must be initialized at this point, either via `gupax.toml` or default options || *panic!*
- Start `App` frame || *panic!*
- Write state to `gupax.toml` on user clicking `Save` (after checking input for correctness) || *warn!*
- If `ask_before_quit` == `true`, check for running processes, unsaved state, and update connections before quitting
- Kill processes, kill connections, exit
## State
Internal state is saved in the "OS data folder" as `gupax.toml`, using the [TOML](https://github.com/toml-lang/toml) format. If the version can't be parsed (not in the `vX.X.X` or `vX.X` format), the auto-updater will be skipped. [If not found, a default `gupax.toml` file will be created with `Toml::default`.](https://github.com/hinto-janaiyo/gupax/blob/main/src/toml.rs)
| OS | Data Folder | Example |
|----------|----------------------------------------- |-----------------------------------------------------------|
| Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Roaming\Gupax\gupax.toml |
| macOS | `$HOME`/Library/Application Support | /Users/Alice/Library/Application Support/Gupax/gupax.toml |
| Linux | `$XDG_DATA_HOME` or `$HOME`/.local/share | /home/alice/.local/share/gupax/gupax.toml |

View file

@ -16,11 +16,17 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::net::{Ipv4Addr,SocketAddrV4};
use sha2::{Sha256,Digest};
use hex_literal::hex;
use std::{io, fs};
// These are the versions bundled with Gupax.
pub const P2POOL_VERSION: &'static str = "v2.4";
pub const XMRIG_VERSION: &'static str = "v6.18.0";
// Compile-time constants
pub const BYTES_ICON: &[u8] = include_bytes!("../images/png/icon.png");
pub const BYTES_BANNER: &[u8] = include_bytes!("../images/png/banner.png");
pub const P2POOL_BASE_ARGS: &'static str = "--host 127.0.0.1 --rpc-port 18081 --zmq-port 18083 --loglevel 3 --out-peers 10 --in-peers 10";
pub const P2POOL_BASE_ARGS: &'static str = "";
pub const XMRIG_BASE_ARGS: &'static str = "--http-host=127.0.0.1 --http-port=18088 --algo=rx/0 --coin=Monero --randomx-cache-qos";
// OS specific

View file

@ -16,7 +16,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use eframe::{egui,NativeOptions};
use egui::{Vec2,Pos2};
use std::process::exit;
use std::thread;
@ -36,12 +36,13 @@ use std::io::Write;
use std::time::Instant;
mod constants;
mod toml;
mod about;
mod status;
mod gupax;
mod p2pool;
mod xmrig;
use {constants::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*};
use {constants::*,crate::toml::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*};
// The state of the outer [App].
// See the [State] struct for the
@ -60,6 +61,8 @@ pub struct App {
xmrig: bool,
state: State,
og: State,
allowed_to_close: bool,
show_confirmation_dialog: bool,
}
impl App {
@ -79,12 +82,12 @@ impl App {
let resolution = cc.integration_info.window_info.size;
init_text_styles(&cc.egui_ctx, resolution[0] as f32);
let banner = match RetainedImage::from_image_bytes("banner.png", BYTES_BANNER) {
Ok(banner) => { info!("Banner loading OK"); banner },
Ok(banner) => { info!("Banner loading ... OK"); banner },
Err(err) => { error!("{}", err); panic!("{}", err); },
};
let mut state = State::new();
let mut og = State::new();
info!("{:?}", resolution);
info!("Frame resolution ... {:#?}", resolution);
Self {
version,
name_version,
@ -99,6 +102,8 @@ impl App {
xmrig: false,
state,
og,
allowed_to_close: false,
show_confirmation_dialog: false,
}
}
}
@ -169,48 +174,58 @@ fn init_text_styles(ctx: &egui::Context, width: f32) {
// style.spacing.button_padding = Vec2::new(scale/2.0, scale/2.0);
ctx.set_style(style);
ctx.set_pixels_per_point(1.0);
ctx.request_repaint();
}
fn main() {
fn init_logger() {
use env_logger::fmt::Color;
Builder::new().format(|buf, record| {
let level;
let mut style = buf.style();
match record.level() {
Level::Error => { style.set_color(Color::Red); level = "ERROR" },
Level::Warn => { style.set_color(Color::Yellow); level = "WARN " },
Level::Info => { style.set_color(Color::White); level = "INFO " },
Level::Warn => { style.set_color(Color::Yellow); level = "WARN" },
Level::Info => { style.set_color(Color::White); level = "INFO" },
Level::Debug => { style.set_color(Color::Blue); level = "DEBUG" },
Level::Trace => { style.set_color(Color::Magenta); level = "TRACE" },
};
writeln!(
buf,
"| {} | {} | {}:{} | {}",
"[{}] [{}] [{}:{}] {}",
style.set_bold(true).value(level),
buf.style().set_dimmed(true).value(chrono::Local::now().format("%F %T%.3f")),
buf.style().set_dimmed(true).value(record.file().unwrap_or("???")),
buf.style().set_dimmed(true).value(record.line().unwrap_or(0)),
record.args(),
)
}).filter_level(LevelFilter::Info).write_style(WriteStyle::Always).parse_default_env().format_timestamp_millis().try_init();
info!("test");
warn!("test");
error!("test");
debug!("test");
}).filter_level(LevelFilter::Info).write_style(WriteStyle::Always).parse_default_env().format_timestamp_millis().init();
info!("init_logger() ... OK");
}
fn init_options() -> NativeOptions {
let mut options = eframe::NativeOptions::default();
options.min_window_size = Option::from(Vec2::new(1280.0, 720.0));
options.max_window_size = Option::from(Vec2::new(3180.0, 2160.0));
options.initial_window_size = Option::from(Vec2::new(1280.0, 720.0));
options.follow_system_theme = false;
options.default_theme = eframe::Theme::Dark;
let icon = image::load_from_memory(BYTES_ICON).expect("Failed to read ICON bytes").to_rgba8();
let icon = image::load_from_memory(BYTES_ICON).expect("Failed to read icon bytes").to_rgba8();
let (icon_width, icon_height) = icon.dimensions();
options.icon_data = Some(eframe::IconData {
rgba: icon.into_raw(),
width: icon_width,
height: icon_height,
});
info!("init_options() ... OK");
options
}
fn main() {
init_logger();
let options = init_options();
let toml = Toml::get();
info!("Printing gupax.toml...");
eprintln!("{:#?}", toml);
let now = Instant::now();
eframe::run_native(
"Gupax",
@ -220,7 +235,29 @@ fn main() {
}
impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn on_close_event(&mut self) -> bool {
self.show_confirmation_dialog = true;
self.allowed_to_close
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
if self.show_confirmation_dialog {
// Show confirmation dialog:
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("Are you sure you want to quit?"));
ui.group(|ui| {
if ui.add_sized([width, height/10.0], egui::Button::new("Yes")).clicked() {
exit(0);
} else if ui.add_sized([width, height/10.0], egui::Button::new("No")).clicked() {
self.show_confirmation_dialog = false;
}
});
});
return
}
// Top: Tabs
egui::CentralPanel::default().show(ctx, |ui| {
init_text_styles(ctx, ui.available_width());
@ -355,16 +392,3 @@ impl eframe::App for App {
});
}
}
pub trait View {
fn ui(&mut self, ui: &mut egui::Ui);
}
/// Something to view
pub trait Demo {
/// `&'static` so we can also use it as a key to store open/close state.
fn name(&self) -> &'static str;
/// Show windows, etc
fn show(&mut self, ctx: &egui::Context, open: &mut bool);
}

223
src/toml.rs Normal file
View file

@ -0,0 +1,223 @@
// 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:
// Toml/
// ├─ Gupax/
// │ ├─ ...
// ├─ P2pool/
// │ ├─ ...
// ├─ Xmrig/
// │ ├─ ...
// ├─ Version/
// ├─ ...
use std::{fs,env};
use std::fmt::Display;
use std::path::{Path,PathBuf};
use serde_derive::{Serialize,Deserialize};
use log::*;
//---------------------------------------------------------------------------------------------------- Impl
// Since [State] is already used in [main.rs] to represent
// working state, [Toml] is used to disk state.
impl Toml {
pub fn default() -> Self {
use crate::constants::{P2POOL_VERSION,XMRIG_VERSION};
Self {
gupax: Gupax {
auto_update: true,
ask_before_quit: true,
p2pool_path: DEFAULT_P2POOL_PATH.to_string(),
xmrig_path: DEFAULT_XMRIG_PATH.to_string(),
},
p2pool: P2pool {
simple: true,
mini: true,
out_peers: 10,
in_peers: 10,
log_level: 3,
monerod: "localhost".to_string(),
rpc: 18081,
zmq: 18083,
address: "".to_string(),
},
xmrig: Xmrig {
simple: true,
tls: false,
nicehash: false,
keepalive: false,
threads: 1,
priority: 2,
pool: "localhost:3333".to_string(),
address: "".to_string(),
},
version: Version {
p2pool: P2POOL_VERSION.to_string(),
xmrig: XMRIG_VERSION.to_string(),
},
}
}
pub fn get() -> Result<Toml, 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(DIRECTORY);
info!("{}, OS data path ... OK", path.display());
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)?;
// Attempt to read file, create default if not found
path.push(FILENAME);
let file = match fs::read_to_string(&path) {
Ok(file) => file,
Err(err) => {
error!("TOML not found, attempting to create default");
let default = match toml::ser::to_string(&Toml::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");
fs::read_to_string(&path)?
},
};
info!("TOML read ... OK");
// Attempt to parse, return Result
match toml::from_str(&file) {
Ok(file) => { info!("TOML parse ... OK"); Ok(file) },
Err(err) => { error!("Couldn't parse TOML file"); Err(TomlError::Parse(err)) },
}
}
}
impl Display for TomlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use TomlError::*;
match self {
Io(err) => write!(f, "{} | {}", ERROR, err),
Path(err) => write!(f, "{} | {}", ERROR, err),
Parse(err) => write!(f, "{} | {}", ERROR, err),
Serialize(err) => write!(f, "{} | {}", ERROR, err),
}
}
}
impl From<std::io::Error> for TomlError {
fn from(err: std::io::Error) -> Self {
TomlError::Io(err)
}
}
fn main() {
let state = match Toml::get() {
Ok(state) => { println!("OK"); state },
Err(err) => panic!(),
};
}
//---------------------------------------------------------------------------------------------------- Const
const FILENAME: &'static str = "gupax.toml";
const ERROR: &'static str = "TOML Error";
const PATH_ERROR: &'static str = "PATH for state directory could not be not found";
#[cfg(target_os = "windows")]
const DIRECTORY: &'static str = "Gupax";
#[cfg(target_os = "macos")]
const DIRECTORY: &'static str = "Gupax";
#[cfg(target_os = "linux")]
const DIRECTORY: &'static str = "gupax";
#[cfg(target_os = "windows")]
const DEFAULT_P2POOL_PATH: &'static str = r"P2Pool\p2pool.exe";
#[cfg(target_os = "macos")]
const DEFAULT_P2POOL_PATH: &'static str = "P2Pool/p2pool";
#[cfg(target_os = "linux")]
const DEFAULT_P2POOL_PATH: &'static str = "p2pool/p2pool";
#[cfg(target_os = "windows")]
const DEFAULT_XMRIG_PATH: &'static str = r"XMRig\xmrig.exe";
#[cfg(target_os = "macos")]
const DEFAULT_XMRIG_PATH: &'static str = "XMRig/xmrig";
#[cfg(target_os = "linux")]
const DEFAULT_XMRIG_PATH: &'static str = "xmrig/xmrig";
//---------------------------------------------------------------------------------------------------- Error Enum
#[derive(Debug)]
pub enum TomlError {
Io(std::io::Error),
Path(String),
Parse(toml::de::Error),
Serialize(toml::ser::Error),
}
//---------------------------------------------------------------------------------------------------- Structs
#[derive(Debug,Deserialize,Serialize)]
pub struct Toml {
gupax: Gupax,
p2pool: P2pool,
xmrig: Xmrig,
version: Version,
}
#[derive(Debug,Deserialize,Serialize)]
struct Gupax {
auto_update: bool,
ask_before_quit: bool,
p2pool_path: String,
xmrig_path: String,
}
#[derive(Debug,Deserialize,Serialize)]
struct P2pool {
simple: bool,
mini: bool,
out_peers: u8,
in_peers: u8,
log_level: u8,
monerod: String,
rpc: u16,
zmq: u16,
address: String,
}
#[derive(Debug,Deserialize,Serialize)]
struct Xmrig {
simple: bool,
tls: bool,
nicehash: bool,
keepalive: bool,
threads: u16,
priority: u8,
pool: String,
address: String,
}
#[derive(Debug,Deserialize,Serialize)]
struct Version {
p2pool: String,
xmrig: String,
}