mirror of
https://github.com/Cyrix126/gupaxx.git
synced 2024-12-22 14:49:21 +00:00
state.rs: add State::merge()
This commit is contained in:
parent
6a1db35c10
commit
5e65d07470
8 changed files with 353 additions and 66 deletions
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -89,6 +89,15 @@ version = "0.7.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "atomic"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic_refcell"
|
||||
version = "0.1.8"
|
||||
|
@ -765,6 +774,19 @@ dependencies = [
|
|||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "figment"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"serde",
|
||||
"toml",
|
||||
"uncased",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-hash"
|
||||
version = "0.7.0"
|
||||
|
@ -1079,12 +1101,14 @@ dependencies = [
|
|||
"egui",
|
||||
"egui_extras",
|
||||
"env_logger",
|
||||
"figment",
|
||||
"hex-literal",
|
||||
"image",
|
||||
"log",
|
||||
"monero",
|
||||
"num-format",
|
||||
"num_cpus",
|
||||
"openssl",
|
||||
"rand",
|
||||
"regex",
|
||||
"reqwest",
|
||||
|
@ -1800,6 +1824,15 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "111.22.0+1.1.1q"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.76"
|
||||
|
@ -1809,6 +1842,7 @@ dependencies = [
|
|||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
@ -2620,6 +2654,15 @@ version = "1.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
|
|
|
@ -10,6 +10,7 @@ eframe = "0.19.0"
|
|||
egui = "0.19.0"
|
||||
egui_extras = { version = "0.19.0", features = ["image"] }
|
||||
env_logger = "0.9.1"
|
||||
figment = { version = "0.10.8", features = ["toml"] }
|
||||
hex-literal = "0.3.4"
|
||||
image = { version = "0.24.4", features = ["png"] }
|
||||
log = "0.4.17"
|
||||
|
@ -23,6 +24,7 @@ serde = "1.0.145"
|
|||
serde_derive = "1.0.145"
|
||||
sha2 = "0.10.6"
|
||||
toml = "0.5.9"
|
||||
openssl = { version = "*", features = ["vendored"] }
|
||||
|
||||
[profile.optimized]
|
||||
codegen-units = 1
|
||||
|
|
|
@ -26,6 +26,11 @@ 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";
|
||||
pub const HORIZONTAL: &'static str = "--------------------------------------------";
|
||||
|
||||
// Update data
|
||||
pub const GITHUB_METADATA_GUPAX: &'static str = "https://api.github.com/repos/hinto-janaiyo/gupax/releases/latest";
|
||||
pub const GITHUB_METADATA_P2POOL: &'static str = "https://api.github.com/repos/SChernykh/p2pool/releases/latest";
|
||||
pub const GITHUB_METADATA_XMRIG: &'static str = "https://api.github.com/repos/xmrig/xmrig/releases/latest";
|
||||
|
||||
// OS specific
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const OS: &'static str = " Windows";
|
||||
|
@ -80,7 +85,8 @@ 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"#;
|
||||
-r | --reset Reset all Gupax configuration/state
|
||||
-f | --ferris Print an extremely cute crab"#;
|
||||
pub const ARG_COPYRIGHT: &'static str =
|
||||
r#"Gupax, P2Pool, and XMRig are licensed under GPLv3.
|
||||
For more information, see here:
|
||||
|
|
81
src/ferris.rs
Normal file
81
src/ferris.rs
Normal file
File diff suppressed because one or more lines are too long
52
src/main.rs
52
src/main.rs
|
@ -41,6 +41,7 @@ use std::time::Instant;
|
|||
use std::path::PathBuf;
|
||||
|
||||
// Modules
|
||||
mod ferris;
|
||||
mod constants;
|
||||
mod node;
|
||||
mod state;
|
||||
|
@ -49,7 +50,7 @@ mod status;
|
|||
mod gupax;
|
||||
mod p2pool;
|
||||
mod xmrig;
|
||||
use {constants::*,node::*,state::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*};
|
||||
use {ferris::*,constants::*,node::*,state::*,about::*,status::*,gupax::*,p2pool::*,xmrig::*};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Struct + Impl
|
||||
// The state of the outer main [App].
|
||||
|
@ -72,13 +73,13 @@ pub struct App {
|
|||
// the [diff] bool will be the signal for [Reset/Save].
|
||||
og: State,
|
||||
state: State,
|
||||
// update: Update, // State for update data [update.rs]
|
||||
diff: bool,
|
||||
// Process/update state:
|
||||
// Doesn't make sense to save this on disk
|
||||
// so it's represented as a bool here.
|
||||
p2pool: bool, // Is p2pool online?
|
||||
xmrig: bool, // Is xmrig online?
|
||||
updating: bool, // Is an update in progress?
|
||||
// State from [--flags]
|
||||
startup: bool,
|
||||
reset: bool,
|
||||
|
@ -113,10 +114,10 @@ impl App {
|
|||
node: Arc::new(Mutex::new(NodeStruct::default())),
|
||||
og: State::default(),
|
||||
state: State::default(),
|
||||
// update: Update::default(),
|
||||
diff: false,
|
||||
p2pool: false,
|
||||
xmrig: false,
|
||||
updating: false,
|
||||
startup: true,
|
||||
reset: false,
|
||||
now: Instant::now(),
|
||||
|
@ -242,18 +243,6 @@ fn init_options() -> NativeOptions {
|
|||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Misc functions
|
||||
fn into_absolute_path(path: String) -> Result<PathBuf, std::io::Error> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_args(mut app: App) -> App {
|
||||
info!("Parsing CLI arguments...");
|
||||
let mut args: Vec<String> = env::args().collect();
|
||||
|
@ -266,6 +255,7 @@ fn parse_args(mut app: App) -> App {
|
|||
println!("Gupax | {}\nP2Pool | {}\nXMRig | {}\n\nOS: [{}], Commit: [{}]\n\n{}", GUPAX_VERSION, P2POOL_VERSION, XMRIG_VERSION, OS_NAME, &COMMIT[..40], ARG_COPYRIGHT);
|
||||
exit(0);
|
||||
},
|
||||
"-f"|"--ferris" => { println!("{}", FERRIS); exit(0); },
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -349,20 +339,25 @@ impl eframe::App for App {
|
|||
|
||||
// Close confirmation.
|
||||
if self.quit {
|
||||
// If [ask_before_quit == true]
|
||||
if self.state.gupax.ask_before_quit {
|
||||
egui::TopBottomPanel::bottom("quit").show(ctx, |ui| {
|
||||
let width = self.width;
|
||||
let height = self.height/8.0;
|
||||
ui.group(|ui| {
|
||||
if ui.add_sized([width, height], egui::Button::new("Yes")).clicked() {
|
||||
if self.state.gupax.save_before_quit {
|
||||
if self.diff {
|
||||
info!("Saving before quit...");
|
||||
match self.state.save() {
|
||||
Err(err) => { error!("{}", err); exit(1); },
|
||||
_ => (),
|
||||
};
|
||||
} else {
|
||||
info!("No changed detected, not saving...");
|
||||
}
|
||||
}
|
||||
info!("Quit confirmation = yes ... goodbye!");
|
||||
self.quit_confirm = true;
|
||||
exit(0);
|
||||
} else if ui.add_sized([width, height], egui::Button::new("No")).clicked() {
|
||||
self.quit = false;
|
||||
|
@ -375,24 +370,45 @@ impl eframe::App for App {
|
|||
let ten = height/10.0;
|
||||
// Detect processes or update
|
||||
ui.add_space(ten);
|
||||
if self.p2pool || self.xmrig || self.updating {
|
||||
// || self.update.updating
|
||||
if self.p2pool || self.xmrig {
|
||||
ui.add_sized([width, height/4.0], Label::new("Are you sure you want to quit?"));
|
||||
if self.updating { ui.add_sized([width, ten], Label::new("Update is in progress...!")); }
|
||||
// if self.update.updating { ui.add_sized([width, ten], Label::new("Update is in progress...!")); }
|
||||
if self.p2pool { ui.add_sized([width, ten], Label::new("P2Pool is online...!")); }
|
||||
if self.xmrig { ui.add_sized([width, ten], Label::new("XMRig is online...!")); }
|
||||
// Else, just quit
|
||||
} else {
|
||||
if self.state.gupax.save_before_quit {
|
||||
if self.diff {
|
||||
info!("Saving before quit...");
|
||||
match self.state.save() {
|
||||
Err(err) => { error!("{}", err); exit(1); },
|
||||
_ => (),
|
||||
};
|
||||
} else {
|
||||
info!("No changed detected, not saving...");
|
||||
}
|
||||
}
|
||||
info!("No processes or update in progress ... goodbye!");
|
||||
exit(0);
|
||||
}
|
||||
});
|
||||
// Else, quit (save if [save_before_quit == true]
|
||||
} else {
|
||||
if self.state.gupax.save_before_quit {
|
||||
if self.diff {
|
||||
info!("Saving before quit...");
|
||||
match self.state.save() {
|
||||
Err(err) => { error!("{}", err); exit(1); },
|
||||
_ => (),
|
||||
};
|
||||
} else {
|
||||
info!("No changed detected, not saving...");
|
||||
}
|
||||
}
|
||||
info!("Quit confirmation = yes ... goodbye!");
|
||||
exit(0);
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
104
src/state.rs
104
src/state.rs
|
@ -33,6 +33,8 @@ use std::fmt::Display;
|
|||
use std::path::{Path,PathBuf};
|
||||
use std::result::Result;
|
||||
use serde_derive::{Serialize,Deserialize};
|
||||
use figment::Figment;
|
||||
use figment::providers::{Format,Toml};
|
||||
use crate::constants::HORIZONTAL;
|
||||
use log::*;
|
||||
|
||||
|
@ -51,6 +53,8 @@ impl State {
|
|||
save_before_quit: 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,
|
||||
|
@ -114,7 +118,7 @@ impl State {
|
|||
},
|
||||
Err(err) => {
|
||||
warn!("TOML not found, attempting to create default");
|
||||
let default = match toml::ser::to_string(&State::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)) },
|
||||
};
|
||||
|
@ -126,35 +130,67 @@ impl State {
|
|||
}
|
||||
|
||||
// Attempt to parse from String
|
||||
pub fn parse(string: String) -> Result<State, TomlError> {
|
||||
// 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");
|
||||
info!("{}", HORIZONTAL);
|
||||
for i in string.lines() { info!("{}", i); }
|
||||
info!("{}", HORIZONTAL);
|
||||
Self::info(&toml);
|
||||
Ok(toml)
|
||||
},
|
||||
Err(err) => { error!("Couldn't parse TOML from string"); Err(TomlError::Deserialize(err)) },
|
||||
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<State, TomlError> {
|
||||
pub fn get() -> Result<Self, TomlError> {
|
||||
Self::parse(Self::read_or_create(Self::get_path()?)?)
|
||||
}
|
||||
|
||||
// Save [State] onto disk file [gupax.toml]
|
||||
pub fn save(&self) -> Result<(), TomlError> {
|
||||
// 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!("Starting TOML overwrite...");
|
||||
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");
|
||||
info!("{}", HORIZONTAL);
|
||||
for i in string.lines() { info!("{}", i); }
|
||||
info!("{}", HORIZONTAL);
|
||||
Self::info(&self);
|
||||
string
|
||||
},
|
||||
Err(err) => { error!("Couldn't parse TOML into string"); return Err(TomlError::Serialize(err)) },
|
||||
|
@ -164,16 +200,49 @@ impl State {
|
|||
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
|
||||
info!("Attempting to save to disk...");
|
||||
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, "{} | {}", ERROR, err),
|
||||
Path(err) => write!(f, "{} | {}", ERROR, err),
|
||||
Serialize(err) => write!(f, "{} | {}", ERROR, err),
|
||||
Deserialize(err) => write!(f, "{} | {}", ERROR, err),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,6 +283,7 @@ pub enum TomlError {
|
|||
Path(String),
|
||||
Serialize(toml::ser::Error),
|
||||
Deserialize(toml::de::Error),
|
||||
Merge(figment::Error),
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Structs
|
||||
|
@ -233,6 +303,8 @@ pub struct Gupax {
|
|||
pub save_before_quit: 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)]
|
||||
|
|
67
src/update.rs
Normal file
67
src/update.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
// 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/>.
|
||||
|
||||
use crate::State;
|
||||
|
||||
struct Update {
|
||||
new_gupax: String,
|
||||
new_p2pool: String,
|
||||
new_xmrig: String,
|
||||
path_gupax: String,
|
||||
path_p2pool: String,
|
||||
path_xmrig: String,
|
||||
updating: Arc<Mutex<bool>> // Is the update in progress?
|
||||
update_prog: u8, // Not an [f32] because [Eq] doesn't work
|
||||
}
|
||||
|
||||
impl Update {
|
||||
fn new(path_p2pool: String, path_xmrig: String) -> Result<Self, Error> {
|
||||
let path_gupax = std::env::current_exe()?;
|
||||
Self {
|
||||
new_gupax: "?".to_string(),
|
||||
new_p2pool: "?".to_string(),
|
||||
new_xmrig: "?".to_string(),
|
||||
path_gupax,
|
||||
path_p2pool,
|
||||
path_xmrig,
|
||||
updating: Arc::new(Mutex::new(false)),
|
||||
update_prog: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(state: &mut State) -> Result((), Error) {
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct TagName {
|
||||
tag_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum Error {
|
||||
Io(std::io::Error),
|
||||
Serialize(toml::ser::Error),
|
||||
Deserialize(toml::de::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum Package {
|
||||
Gupax,
|
||||
P2pool,
|
||||
Xmrig,
|
||||
}
|
||||
|
Loading…
Reference in a new issue