v1.1.0: Merge 'status' branch

This commit is contained in:
hinto-janaiyo 2023-01-03 12:21:22 -05:00
commit 1301d1a283
No known key found for this signature in database
GPG key ID: B1C5A64B80691E45
22 changed files with 2925 additions and 899 deletions

View file

@ -1,4 +1,24 @@
# v1.0.1
# v1.1.0
## Updates
* **Status:** Added P2Pool submenu:
- Total payouts across all time
- Total XMR mined across all time
- Formatted log lines of ALL payouts (date, amount, block) with sorting options
- Automatic/Manual calculator for average share/block time
- P2Pool/Monero stats for difficulty/hashrate/dominance
* **Status:** Added more process stats:
- P2Pool: Current Monero node IP/RPC/ZMQ
- P2Pool: Current Sidechain
- P2Pool: Current Monero address
- XMRig: Current Pool IP
- XMRig: Current thread usage
* **Key Shortcut:** Added two shortcuts:
- `C | Left Submenu`
- `V | Left Submenu`
* **Command Line:** Added two flags:
- `--payouts Print the P2Pool payout log, payout count, and total XMR mined`
- `--reset-payouts Reset the permanent P2Pool stats that appear in the [Status] tab`
## Fixes
* **macOS:** Added warning (and solution) if `Gupax/P2Pool/XMRig` were quarantined by [`Gatekeeper`](https://support.apple.com/en-us/HT202491)
* **P2Pool/XMRig:** Added a red `Start` button on errors (bad PATH, invalid file, etc) and a solution in the tooltip
@ -7,7 +27,7 @@
* Miscellaneous UI changes and fixes
## Bundled Versions
* [`P2Pool v2.6`](https://github.com/SChernykh/p2pool/releases/tag/v2.6)
* [`P2Pool v2.7`](https://github.com/SChernykh/p2pool/releases/tag/v2.7)
* [`XMRig v6.18.1`](https://github.com/xmrig/xmrig/releases/tag/v6.18.1)

34
Cargo.lock generated
View file

@ -449,9 +449,9 @@ dependencies = [
[[package]]
name = "calloop"
version = "0.10.4"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19457a0da465234abd76134a5c2a910c14bd3c5558463e4396ab9a37a328e465"
checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192"
dependencies = [
"log",
"nix 0.25.1",
@ -1798,7 +1798,7 @@ dependencies = [
[[package]]
name = "gupax"
version = "1.0.1"
version = "1.1.0"
dependencies = [
"anyhow",
"arti-client",
@ -1841,9 +1841,9 @@ dependencies = [
[[package]]
name = "half"
version = "2.1.0"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad6a9459c9c30b177b925162351f97e7d967c7ea8bab3b8352805327daf45554"
checksum = "6c467d36af040b7b2681f5fddd27427f6da8d3d072f575a265e181d2f8e8d157"
dependencies = [
"crunchy",
]
@ -2497,9 +2497,9 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "nom"
version = "7.1.1"
version = "7.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c"
dependencies = [
"memchr",
"minimal-lexical",
@ -2665,9 +2665,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "opaque-debug"
@ -3420,18 +3420,18 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]]
name = "serde"
version = "1.0.151"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.151"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
@ -3440,9 +3440,9 @@ dependencies = [
[[package]]
name = "serde_ignored"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51212eb6171778353d78ef5860fdffe29ac17f03a69375d2dc14fbb5754d54a4"
checksum = "94eb4a4087ba8bdf14a9208ac44fddbf55c01a6195f7edfc511ddaff6cae45a6"
dependencies = [
"serde",
]
@ -3832,9 +3832,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.27.1"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccb297c0afb439440834b4bcf02c5c9da8ec2e808e70f36b0d8e815ff403bd24"
checksum = "17351d0e9eb8841897b14e9669378f3c69fb57779cc04f8ca9a9d512edfb2563"
dependencies = [
"cfg-if",
"core-foundation-sys",

View file

@ -1,6 +1,6 @@
[package]
name = "gupax"
version = "1.0.1"
version = "1.1.0"
authors = ["hinto-janaiyo <hinto.janaiyo@protonmail.com>"]
description = "GUI for P2Pool+XMRig"
documentation = "https://github.com/hinto-janaiyo/gupax"

View file

@ -7,6 +7,7 @@ Gupax is a (Windows|macOS|Linux) GUI for mining [**Monero**](https://github.com/
* [What is Monero/P2Pool/XMRig/Gupax?](#what-is-monero-p2pool-xmrig-and-gupax)
* [How-To](#How-To)
* [Simple](#Simple)
- [Status](#Status)
- [Gupax](#Gupax)
- [P2Pool](#P2Pool)
- [XMRig](#XMRig)
@ -20,6 +21,7 @@ Gupax is a (Windows|macOS|Linux) GUI for mining [**Monero**](https://github.com/
- [Logs](#Logs)
- [Disk](#Disk)
- [Swapping P2Pool/XMRig](#Swapping-P2PoolXMRig)
- [Status](#Status-1)
- [Gupax](#Gupax-1)
- [P2Pool](#P2Pool-1)
- [XMRig](#XMRig-1)
@ -105,8 +107,23 @@ The `Gupax/P2Pool/XMRig` tabs have two versions, `Simple` & `Advanced`.
`Simple` is for a minimal & working out-of-the-box configuration.
### Status
This tab has 2 submenus that show some info about running processes and P2Pool-specific stats.
**Processes:**
![processes.png](https://github.com/hinto-janaiyo/gupax/blob/main/images/processes.png)
**P2Pool:**
This submenu shows:
- ***Permanent*** stats on all your payouts received via P2Pool & Gupax
- Payout sorting options
- Share/block time calculator
![payouts.png](https://github.com/hinto-janaiyo/gupax/blob/main/images/payouts.png)
### Gupax
In this tab, there is the updater and general Gupax settings.
This tab has the updater and general Gupax settings.
If `Check for updates` is pressed, Gupax will update your `Gupax/P2Pool/XMRig` (if needed) using the [GitHub API](#where-are-updates-downloaded-from).
@ -194,11 +211,13 @@ USAGE: ./gupax [--flag]
--version Print version and build info
--state Print Gupax state
--nodes Print the manual node list
--no-startup Disable all auto-startup settings for this instance
--payouts Print the P2Pool payout log, payout count, and total XMR mined
--no-startup Disable all auto-startup settings for this instance (auto-update, auto-ping, etc)
--reset-state Reset all Gupax state (your settings)
--reset-nodes Reset the manual node list in the [P2Pool] tab
--reset-pools Reset the manual pool list in the [XMRig] tab
--reset-all Reset the state, the manual node list, and the manual pool list
--reset-payouts Reset the permanent P2Pool stats that appear in the [Status] tab
--reset-all Reset the state, manual node list, manual pool list, and P2Pool stats
--ferris Print an extremely cute crab
```
@ -207,7 +226,7 @@ By default, Gupax has `auto-update` & `auto-ping` enabled. This can only be turn
---
### Key Shortcuts
The letter keys (Z/X/S/R) will only work if nothing is in focus, i.e, you _are not_ editing a text box.
The letter keys (Z/X/C/V/S/R) will only work if nothing is in focus, i.e, you _are not_ editing a text box.
An ALT+F4 will also trigger the exit confirm screen (if enabled).
```
@ -218,8 +237,10 @@ An ALT+F4 will also trigger the exit confirm screen (if enabled).
| Escape | Quit screen |
| Up | Start/Restart |
| Down | Stop |
| Z | Switch to Left Tab |
| X | Switch to Right Tab |
| Z | Left Tab |
| X | Right Tab |
| C | Left Submenu |
| V | Right Submenu |
| S | Save |
| R | Reset |
*---------------------------------------*
@ -235,7 +256,7 @@ This can be changed by dragging the corner of the window itself or by using the
If you have changed your OS's pixel scaling, you may need to resize Gupax to see all UI correctly.
The minimum window size is: `640x480`
The maximum window size is: `2560x1920`
The maximum window size is: `3840x2160`
Fullscreen mode can also be entered by pressing `F11`.
---
@ -278,6 +299,7 @@ The current files saved to disk:
* `state.toml` Gupax state/settings
* `node.toml` The manual node database used for P2Pool advanced
* `pool.toml` The manual pool database used for XMRig advanced
* `p2pool/` The Gupax-P2Pool API files
---
@ -342,6 +364,49 @@ gupax/
---
### Status
The `[P2Pool]` submenu in this tab reads/writes to a file-based API in a folder called `p2pool` located in the [Gupax OS data directory:](#Disk)
| File | Purpose | Specific Data Type |
|----------|---------------------------------------------------------|--------------------|
| `log` | Formatted payout lines extracted from P2Pool | Basically a `String` that needs to be parsed correctly
| `payout` | The total amount of payouts received via P2Pool & Gupax | `u64`
| `xmr` | The total amount of XMR mined via P2Pool & Gupax | Atomic Units represented with a `u64`
The `log` file contains formatted payout lines extracted from your P2Pool:
```
<DATE> <TIME> | <12_FLOATING_POINT> XMR | Block <HUMAN_READABLE_BLOCK>
```
e.g:
```
2023-01-01 00:00:00.0000 | 0.600000000000 XMR | Block 2,123,123
```
The `payout` file is just a simple count of how many payouts have been received.
The `xmr` file is the sum of XMR mined represented as [Atomic Units](https://web.getmonero.org/resources/moneropedia/atomic-units.html). The equation to represent this as normal XMR is:
```rust
XMR_ATOMIC_UNITS / 1,000,000,000,000
```
e.g:
```rust
600,000,000,000 / 1,000,000,000,000 = 0.6 XMR
```
Gupax interacts with these files in two ways:
- Reading the files when initially starting
- Appending/overwriting new data to the files upon P2Pool payout
These files shouldn't be written to manually or Gupax will report wrong data.
If you want to reset these stats, you can run:
```bash
./gupax --reset-payouts
```
or just manually delete the `p2pool` folder in the Gupax OS data directory.
---
### Gupax
Along with the updater and settings mentioned in [Simple](#simple), `Gupax Advanced` allows you to change:
- The PATH of where Gupax looks for P2Pool/XMRig
@ -480,7 +545,7 @@ You need [`cargo`](https://www.rust-lang.org/learn/get-started), Rust's build to
The `--release` profile in Gupax is set to prefer code performance & small binary sizes over compilation speed (see [`Cargo.toml`](https://github.com/hinto-janaiyo/gupax/blob/main/Cargo.toml)). Gupax itself (with all dependencies already built) takes around 1m30s to build (vs 10s on a normal `--release`) with a Ryzen 5950x.
There are `21` unit tests throughout the codebase files, you should probably run:
There are `38` unit tests throughout the codebase files, you should probably run:
```
cargo test
```

BIN
images/payouts.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

BIN
images/processes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

View file

@ -4,6 +4,7 @@
* [Bootstrap](#Bootstrap)
* [Scale](#Scale)
* [Naming Scheme](#naming-scheme)
* [Mining Stat Reference](#mining-stat-reference)
* [Sudo](#Sudo)
* [Why does Gupax need to be Admin? (on Windows)](#why-does-gupax-need-to-be-admin-on-windows)
- [The issue](#the-issue)
@ -17,17 +18,20 @@
## Structure
| File/Folder | Purpose |
|--------------|---------|
| constants.rs | General constants needed in Gupax
| constants.rs | General constants used in Gupax
| disk.rs | Code for writing to disk: `state.toml/node.toml/pool.toml`; This holds the structs for the [State] struct
| ferris.rs | Cute crab bytes
| gupax.rs | `Gupax` tab
| helper.rs | The "helper" thread that runs for the entire duration Gupax is alive. All the processing that needs to be done without blocking the main GUI thread runs here, including everything related to handling P2Pool/XMRig
| macros.rs | General `macros!()` used in Gupax
| main.rs | The main `App` struct that holds all data + misc data/functions
| node.rs | Community node ping code for the `P2Pool` simple tab
| p2pool.rs | `P2Pool` tab
| regex.rs | General regexes used in Gupax
| status.rs | `Status` tab
| sudo.rs | Code for handling `sudo` escalation for XMRig on Unix
| update.rs | Update code for the `Gupax` tab
| xmr.rs | Code for handling actual XMR, `AtomicUnit` & `PayoutOrd`
| xmrig.rs | `XMRig` tab
## Thread Model
@ -124,6 +128,31 @@ Exceptions (there are always exceptions...):
- XMRig separates the hash and signature
- P2Pool hashes are in UPPERCASE
## Mining Stat Reference
Some pseudo JSON for constants/equations needed for generating mining stats. They're here for easy reference, I was never good at math :)
```
block_time_in_seconds: {
P2POOL_BLOCK_TIME: 10,
MONERO_BLOCK_TIME: 120,
}
difficulty: {
P2POOL_DIFFICULTY: (current_p2pool_hashrate * P2POOL_BLOCK_TIME),
MONERO_DIFFICULTY: (current_monero_hashrate * MONERO_BLOCK_TIME),
}
hashrate_per_second: {
P2POOL_HASHRATE: (P2POOL_DIFFICULTY / P2POOL_BLOCK_TIME),
MONERO_HASHRATE: (MONERO_DIFFICULTY / MONERO_BLOCK_TIME),
}
mean_in_seconds: {
P2POOL_BLOCK_MEAN: (MONERO_DIFF / P2POOL_HASHRATE),
MY_SOLO_BLOCK_MEAN: (MONERO_DIFF / my_hashrate),
MY_P2POOL_SHARE_MEAN: (P2POOL_DIFF / my_hashrate),
}
```
## Sudo
Unlike Windows, Unix (macOS/Linux) has a userland program that handles all the dirty details of privilege escalation: `sudo`.
@ -208,7 +237,7 @@ This was the solution I would have gone with, but alas, the abstracted `Command`
---
### Windows vs Unix
Unix (macOS/Linux) has have a super nice, easy, friendly, not-completely-garbage userland program called: `sudo`. It is so extremely simple to use `sudo` as a sort of wrapper around XMRig since `sudo` isn't completely backwards and actually has valuable flags! No legacy `Administrator`, no UAC prompt, no shells within shells, no low-level system APIs, no messing with the user Registry.
Unix (macOS/Linux) has a super nice, easy, friendly, not-completely-garbage userland program called: `sudo`. It is so extremely simple to use `sudo` as a sort of wrapper around XMRig since `sudo` isn't completely backwards and actually has valuable flags! No legacy `Administrator`, no UAC prompt, no shells within shells, no low-level system APIs, no messing with the user Registry.
You get the user's password, you input it to `sudo` with `--stdin` and you execute XMRig with it. Simple, easy, nice. (Don't forget to zero the password memory, though).

View file

@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub const GUPAX_VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); // e.g: Gupax v1.0.0
pub const P2POOL_VERSION: &str = "v2.6";
pub const GUPAX_VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); // e.g: v1.0.0
pub const P2POOL_VERSION: &str = "v2.7";
pub const XMRIG_VERSION: &str = "v6.18.1";
pub const COMMIT: &str = include_str!("../.git/refs/heads/main");
// e.g: Gupax_v1_0_0
@ -51,7 +51,6 @@ pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon@2x.png");
pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon.png");
pub const BYTES_BANNER: &[u8] = include_bytes!("../images/banner.png");
pub const HORIZONTAL: &str = "--------------------------------------------";
// The text to separate my "process stopped, here's stats" text from the process output in the console.
pub const HORI_CONSOLE: &str = "---------------------------------------------------------------------------------------------------------------------------";
// Keyboard shortcuts
@ -63,16 +62,26 @@ r#"*---------------------------------------*
| Escape | Quit screen |
| Up | Start/Restart |
| Down | Stop |
| Z | Switch to Left Tab |
| X | Switch to Right Tab |
| Z | Left Tab |
| X | Right Tab |
| C | Left Submenu |
| V | Right Submenu |
| S | Save |
| R | Reset |
*---------------------------------------*"#;
// P2Pool & XMRig default API stuff
#[cfg(target_os = "windows")]
pub const P2POOL_API_PATH: &str = r"local\stats"; // The default relative FS path of P2Pool's local API
pub const P2POOL_API_PATH_LOCAL: &str = r"local\stats";
#[cfg(target_os = "windows")]
pub const P2POOL_API_PATH_NETWORK: &str = r"network\stats";
#[cfg(target_os = "windows")]
pub const P2POOL_API_PATH_POOL: &str = r"pool\stats";
#[cfg(target_family = "unix")]
pub const P2POOL_API_PATH: &str = "local/stats";
pub const P2POOL_API_PATH_LOCAL: &str = "local/stats";
#[cfg(target_family = "unix")]
pub const P2POOL_API_PATH_NETWORK: &str = "network/stats";
#[cfg(target_family = "unix")]
pub const P2POOL_API_PATH_POOL: &str = "pool/stats";
pub const XMRIG_API_URI: &str = "1/summary"; // The default relative URI of XMRig's API
// Process state tooltips (online, offline, etc)
@ -106,9 +115,6 @@ pub const DARK_GRAY: egui::Color32 = egui::Color32::from_rgb(18, 18, 18);
// [Duration] constants
pub const SECOND: std::time::Duration = std::time::Duration::from_secs(1);
pub const ZERO_SECONDS: std::time::Duration = std::time::Duration::from_secs(0);
pub const MILLI_900: std::time::Duration = std::time::Duration::from_millis(900);
pub const TOKIO_SECOND: tokio::time::Duration = std::time::Duration::from_secs(1);
// The explaination given to the user on why XMRig needs sudo.
pub const XMRIG_ADMIN_REASON: &str =
@ -148,8 +154,8 @@ pub const STATUS_GUPAX_SYSTEM_MEMORY: &str = "How much memory your entire system
pub const STATUS_GUPAX_SYSTEM_CPU_MODEL: &str = "The detected model of your system's CPU and its current frequency";
//--
pub const STATUS_P2POOL_UPTIME: &str = "How long P2Pool has been online";
pub const STATUS_P2POOL_PAYOUTS: &str = "The total amount of payouts received and an extrapolated estimate of how many you will receive. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!";
pub const STATUS_P2POOL_XMR: &str = "The total amount of XMR mined via P2Pool and an extrapolated estimate of how many you will mine in the future. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!";
pub const STATUS_P2POOL_PAYOUTS: &str = "The total amount of payouts received in this instance of P2Pool and an extrapolated estimate of how many you will receive. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!";
pub const STATUS_P2POOL_XMR: &str = "The total amount of XMR mined in this instance of P2Pool and an extrapolated estimate of how many you will mine in the future. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!";
pub const STATUS_P2POOL_HASHRATE: &str = "The total amount of hashrate your P2Pool has pointed at it in 15 minute, 1 hour, and 24 hour averages";
pub const STATUS_P2POOL_SHARES: &str = "The total amount of shares found on P2Pool";
pub const STATUS_P2POOL_EFFORT: &str = "The average amount of effort needed to find a share, and the current effort";
@ -165,6 +171,35 @@ pub const STATUS_XMRIG_DIFFICULTY: &str = "The current difficulty of the job XMR
pub const STATUS_XMRIG_SHARES: &str = "The amount of accepted and rejected shares";
pub const STATUS_XMRIG_POOL: &str = "The pool XMRig is currently mining to";
pub const STATUS_XMRIG_THREADS: &str = "The amount of threads XMRig is currently using";
// Status Submenus
pub const STATUS_SUBMENU_PROCESSES: &str = "View the status of process related data for [Gupax|P2Pool|XMRig]";
pub const STATUS_SUBMENU_P2POOL: &str = "View P2Pool specific data";
//-- P2Pool
pub const STATUS_SUBMENU_PAYOUT: &str = "The total amount of payouts received via P2Pool across all time. This includes all payouts you have ever received using Gupax and P2Pool.";
pub const STATUS_SUBMENU_XMR: &str = "The total of XMR mined via P2Pool across all time. This includes all the XMR you have ever mined using Gupax and P2Pool.";
pub const STATUS_SUBMENU_LATEST: &str = "Sort the payouts from latest to oldest";
pub const STATUS_SUBMENU_OLDEST: &str = "Sort the payouts from oldest to latest";
pub const STATUS_SUBMENU_BIGGEST: &str = "Sort the payouts from biggest to smallest";
pub const STATUS_SUBMENU_SMALLEST: &str = "Sort the payouts from smallest to biggest";
pub const STATUS_SUBMENU_AUTOMATIC: &str = "Automatically calculate share/block time with your current P2Pool 1 hour average hashrate";
pub const STATUS_SUBMENU_MANUAL: &str = "Manually input a hashrate to calculate share/block time with current P2Pool/Monero network stats";
pub const STATUS_SUBMENU_HASH: &str = "Use [Hash] as the hashrate metric";
pub const STATUS_SUBMENU_KILO: &str = "Use [Kilo] as the hashrate metric (1,000x hash)";
pub const STATUS_SUBMENU_MEGA: &str = "Use [Mega] as the hashrate metric (1,000,000x hash)";
pub const STATUS_SUBMENU_GIGA: &str = "Use [Giga] as the hashrate metric (1,000,000,000x hash)";
pub const STATUS_SUBMENU_P2POOL_BLOCK_MEAN: &str = "The average time it takes for P2Pool to find a block";
pub const STATUS_SUBMENU_YOUR_P2POOL_HASHRATE: &str = "Your 1 hour average hashrate on P2Pool";
pub const STATUS_SUBMENU_P2POOL_SHARE_MEAN: &str = "The average time it takes for your hashrate to find a share on P2Pool";
pub const STATUS_SUBMENU_SOLO_BLOCK_MEAN: &str = "The average time it would take for your hashrate to find a block solo mining Monero";
pub const STATUS_SUBMENU_MONERO_DIFFICULTY: &str = "The current Monero network's difficulty (how many hashes it will take on average to find a block)";
pub const STATUS_SUBMENU_MONERO_HASHRATE: &str = "The current Monero network's hashrate";
pub const STATUS_SUBMENU_P2POOL_DIFFICULTY: &str = "The current P2Pool network's difficulty (how many hashes it will take on average to find a share)";
pub const STATUS_SUBMENU_P2POOL_HASHRATE: &str = "The current P2Pool network's hashrate";
pub const STATUS_SUBMENU_P2POOL_MINERS: &str = "The current amount of miners on P2Pool";
pub const STATUS_SUBMENU_P2POOL_DOMINANCE: &str = "The percent of hashrate P2Pool accounts for in the entire Monero network";
pub const STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE: &str = "The percent of hashrate you account for in P2Pool";
pub const STATUS_SUBMENU_YOUR_MONERO_DOMINANCE: &str = "The percent of hashrate you account for in the entire Monero network";
pub const STATUS_SUBMENU_PROGRESS_BAR: &str = "The next time Gupax will update P2Pool stats. Each [*] is 900ms (updates roughly every 54 seconds)";
// Gupax
pub const GUPAX_UPDATE: &str = "Check for updates on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically";
@ -174,7 +209,7 @@ pub const GUPAX_UP_TO_DATE: &str = "Gupax is up-to-date";
#[cfg(not(target_os = "macos"))]
pub const GUPAX_UPDATE_VIA_TOR: &str = "Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required";
#[cfg(target_os = "macos")] // Arti library has issues on macOS
pub const GUPAX_UPDATE_VIA_TOR: &'static str = "WARNING: This option is unstable on macOS. Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required";
pub const GUPAX_UPDATE_VIA_TOR: &str = "WARNING: This option is unstable on macOS. Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required";
pub const GUPAX_ASK_BEFORE_QUIT: &str = "Ask before quitting Gupax";
pub const GUPAX_SAVE_BEFORE_QUIT: &str = "Automatically save any changed settings before quitting";
pub const GUPAX_AUTO_P2POOL: &str = "Automatically start P2Pool on Gupax startup. If you are using [P2Pool Simple], this will NOT wait for your [Auto-Ping] to finish, it will start P2Pool on the pool you already have selected. This option will fail if your P2Pool settings aren't valid!";
@ -280,6 +315,7 @@ pub const XMRIG_NAME: &str = "Add a unique name to identify this pool; Only [A-Z
pub const XMRIG_IP: &str = "Specify the pool IP to connect to with XMRig; It must be a valid IPv4 address or a valid domain name; Max length = 255 characters";
pub const XMRIG_PORT: &str = "Specify the port of the pool; [1-65535]";
pub const XMRIG_RIG: &str = "Add an optional rig ID. This will be the name shown on the pool; Only [A-Za-z0-9-_] and spaces allowed; Max length = 30 characters";
#[cfg(not(target_os = "linux"))]
pub const XMRIG_PAUSE: &str = "THIS SETTING IS DISABLED IF SET TO [0]. Pause mining if user is active, resume after";
pub const XMRIG_API_IP: &str = "Specify which IP to bind to for XMRig's HTTP API; If empty: [localhost/127.0.0.1]";
pub const XMRIG_API_PORT: &str = "Specify which port to bind to for XMRig's HTTP API; If empty: [18088]";
@ -299,11 +335,13 @@ r#"USAGE: ./gupax [--flag]
--version Print version and build info
--state Print Gupax state
--nodes Print the manual node list
--no-startup Disable all auto-startup settings for this instance
--payouts Print the P2Pool payout log, payout count, and total XMR mined
--no-startup Disable all auto-startup settings for this instance (auto-update, auto-ping, etc)
--reset-state Reset all Gupax state (your settings)
--reset-nodes Reset the manual node list in the [P2Pool] tab
--reset-pools Reset the manual pool list in the [XMRig] tab
--reset-all Reset the state, the manual node list, and the manual pool list
--reset-payouts Reset the permanent P2Pool stats that appear in the [Status] tab
--reset-all Reset the state, manual node list, manual pool list, and P2Pool stats
--ferris Print an extremely cute crab
To view more detailed console debug information, start Gupax with

View file

@ -42,11 +42,16 @@ use serde::{Serialize,Deserialize};
use figment::Figment;
use figment::providers::{Format,Toml};
use crate::{
human::*,
constants::*,
gupax::Ratio,
Tab,
xmr::*,
macros::*,
};
use log::*;
#[cfg(target_family = "unix")]
use std::os::unix::fs::PermissionsExt;
//---------------------------------------------------------------------------------------------------- Const
// State file
@ -59,6 +64,30 @@ const DIRECTORY: &str = "Gupax/";
#[cfg(target_os = "linux")]
const DIRECTORY: &str = "gupax/";
// File names
pub const STATE_TOML: &str = "state.toml";
pub const NODE_TOML: &str = "node.toml";
pub const POOL_TOML: &str = "pool.toml";
// P2Pool API
// Lives within the Gupax OS data directory.
// ~/.local/share/gupax/p2pool/
// ├─ payout_log // Raw log lines of payouts received
// ├─ payout // Single [u64] representing total payouts
// ├─ xmr // Single [u64] representing total XMR mined in atomic units
#[cfg(target_os = "windows")]
pub const GUPAX_P2POOL_API_DIRECTORY: &str = r"p2pool\";
#[cfg(target_family = "unix")]
pub const GUPAX_P2POOL_API_DIRECTORY: &str = "p2pool/";
pub const GUPAX_P2POOL_API_LOG: &str = "log";
pub const GUPAX_P2POOL_API_PAYOUT: &str = "payout";
pub const GUPAX_P2POOL_API_XMR: &str = "xmr";
pub const GUPAX_P2POOL_API_FILE_ARRAY: [&str; 3] = [
GUPAX_P2POOL_API_LOG,
GUPAX_P2POOL_API_PAYOUT,
GUPAX_P2POOL_API_XMR,
];
#[cfg(target_os = "windows")]
pub const DEFAULT_P2POOL_PATH: &str = r"P2Pool\p2pool.exe";
#[cfg(target_os = "macos")]
@ -96,17 +125,55 @@ pub fn get_gupax_data_path() -> Result<PathBuf, TomlError> {
path.push(DIRECTORY);
info!("OS | Data path ... {}", path.display());
create_gupax_dir(&path)?;
let mut gupax_p2pool_dir = path.clone();
gupax_p2pool_dir.push(GUPAX_P2POOL_API_DIRECTORY);
create_gupax_p2pool_dir(&gupax_p2pool_dir)?;
Ok(path)
},
None => { error!("OS | Data path ... FAIL"); Err(TomlError::Path(PATH_ERROR.to_string())) },
}
}
pub fn set_unix_750_perms(path: &PathBuf) -> Result<(), TomlError> {
#[cfg(target_os = "windows")]
return Ok(());
#[cfg(target_family = "unix")]
match fs::set_permissions(path, fs::Permissions::from_mode(0o750)) {
Ok(_) => { info!("OS | Unix 750 permissions on path [{}] ... OK", path.display()); Ok(()) },
Err(e) => { error!("OS | Unix 750 permissions on path [{}] ... FAIL ... {}", path.display(), e); Err(TomlError::Io(e)) },
}
}
pub fn set_unix_660_perms(path: &PathBuf) -> Result<(), TomlError> {
#[cfg(target_os = "windows")]
return Ok(());
#[cfg(target_family = "unix")]
match fs::set_permissions(path, fs::Permissions::from_mode(0o660)) {
Ok(_) => { info!("OS | Unix 660 permissions on path [{}] ... OK", path.display()); Ok(()) },
Err(e) => { error!("OS | Unix 660 permissions on path [{}] ... FAIL ... {}", path.display(), e); Err(TomlError::Io(e)) },
}
}
pub fn get_gupax_p2pool_path(os_data_path: &PathBuf) -> PathBuf {
let mut gupax_p2pool_dir = os_data_path.clone();
gupax_p2pool_dir.push(GUPAX_P2POOL_API_DIRECTORY);
gupax_p2pool_dir
}
pub fn create_gupax_dir(path: &PathBuf) -> Result<(), TomlError> {
// Create directory
// Create Gupax directory
match fs::create_dir_all(path) {
Ok(_) => { info!("OS | Create data path ... OK"); Ok(()) },
Err(e) => { error!("OS | Create data path ... FAIL ... {}", e); Err(TomlError::Io(e)) },
Ok(_) => info!("OS | Create data path ... OK"),
Err(e) => { error!("OS | Create data path ... FAIL ... {}", e); return Err(TomlError::Io(e)) },
}
set_unix_750_perms(path)
}
pub fn create_gupax_p2pool_dir(path: &PathBuf) -> Result<(), TomlError> {
// Create Gupax directory
match fs::create_dir_all(path) {
Ok(_) => { info!("OS | Create Gupax-P2Pool API path [{}] ... OK", path.display()); Ok(()) },
Err(e) => { error!("OS | Create Gupax-P2Pool API path [{}] ... FAIL ... {}", path.display(), e); Err(TomlError::Io(e)) },
}
}
@ -131,13 +198,6 @@ pub fn print_dash(toml: &str) {
info!("{}", HORIZONTAL);
}
// Write str to console with [debug!] surrounded by "---"
pub fn print_dash_debug(toml: &str) {
info!("{}", HORIZONTAL);
for i in toml.lines() { debug!("{}", i); }
info!("{}", HORIZONTAL);
}
// Turn relative paths into absolute paths
pub fn into_absolute_path(path: String) -> Result<PathBuf, TomlError> {
let path = PathBuf::from(path);
@ -163,10 +223,11 @@ impl State {
let max_threads = num_cpus::get();
let current_threads = if max_threads == 1 { 1 } else { max_threads / 2 };
Self {
status: Status::default(),
gupax: Gupax::default(),
p2pool: P2pool::default(),
xmrig: Xmrig::with_threads(max_threads, current_threads),
version: Arc::new(Mutex::new(Version::default())),
version: arc_mut!(Version::default()),
}
}
@ -517,6 +578,195 @@ impl Pool {
}
}
//---------------------------------------------------------------------------------------------------- Gupax-P2Pool API
#[derive(Clone,Debug)]
pub struct GupaxP2poolApi {
pub log: String, // Log file only containing full payout lines
pub log_rev: String, // Same as above but reversed based off lines
pub payout: HumanNumber, // Human-friendly display of payout count
pub payout_u64: u64, // [u64] version of above
pub payout_ord: PayoutOrd, // Ordered Vec of payouts, see [PayoutOrd]
pub payout_low: String, // A pre-allocated/computed [String] of the above Vec from low payout to high
pub payout_high: String, // Same as above but high -> low
pub xmr: AtomicUnit, // XMR stored as atomic units
pub path_log: PathBuf, // Path to [log]
pub path_payout: PathBuf, // Path to [payout]
pub path_xmr: PathBuf, // Path to [xmr]
}
impl Default for GupaxP2poolApi { fn default() -> Self { Self::new() } }
impl GupaxP2poolApi {
//---------------------------------------------------------------------------------------------------- Init, these pretty much only get called once
pub fn new() -> Self {
Self {
log: String::new(),
log_rev: String::new(),
payout: HumanNumber::unknown(),
payout_u64: 0,
payout_ord: PayoutOrd::new(),
payout_low: String::new(),
payout_high: String::new(),
xmr: AtomicUnit::new(),
path_xmr: PathBuf::new(),
path_payout: PathBuf::new(),
path_log: PathBuf::new(),
}
}
pub fn fill_paths(&mut self, gupax_p2pool_dir: &PathBuf) {
let mut path_log = gupax_p2pool_dir.clone();
let mut path_payout = gupax_p2pool_dir.clone();
let mut path_xmr = gupax_p2pool_dir.clone();
path_log.push(GUPAX_P2POOL_API_LOG);
path_payout.push(GUPAX_P2POOL_API_PAYOUT);
path_xmr.push(GUPAX_P2POOL_API_XMR);
*self = Self {
path_log,
path_payout,
path_xmr,
..std::mem::take(self)
};
}
pub fn create_all_files(gupax_p2pool_dir: &PathBuf) -> Result<(), TomlError> {
use std::io::Write;
for file in GUPAX_P2POOL_API_FILE_ARRAY {
let mut path = gupax_p2pool_dir.clone();
path.push(file);
if path.exists() {
info!("GupaxP2poolApi | [{}] already exists, skipping...", path.display());
continue
}
match std::fs::File::create(&path) {
Ok(mut f) => {
match file {
GUPAX_P2POOL_API_PAYOUT|GUPAX_P2POOL_API_XMR => writeln!(f, "0")?,
_ => (),
}
info!("GupaxP2poolApi | [{}] create ... OK", path.display());
},
Err(e) => { warn!("GupaxP2poolApi | [{}] create ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) },
}
}
Ok(())
}
pub fn read_all_files_and_update(&mut self) -> Result<(), TomlError> {
let payout_u64 = match read_to_string(File::Payout, &self.path_payout)?.trim().parse::<u64>() {
Ok(o) => o,
Err(e) => { warn!("GupaxP2poolApi | [payout] parse error: {}", e); return Err(TomlError::Parse("payout")) }
};
let xmr = match read_to_string(File::Xmr, &self.path_xmr)?.trim().parse::<u64>() {
Ok(o) => AtomicUnit::from_u64(o),
Err(e) => { warn!("GupaxP2poolApi | [xmr] parse error: {}", e); return Err(TomlError::Parse("xmr")) }
};
let payout = HumanNumber::from_u64(payout_u64);
let log = read_to_string(File::Log, &self.path_log)?;
self.payout_ord.update_from_payout_log(&log);
self.update_payout_strings();
*self = Self {
log,
payout,
payout_u64,
xmr,
..std::mem::take(self)
};
self.update_log_rev();
Ok(())
}
// Completely delete the [p2pool] folder and create defaults.
pub fn create_new(path: &PathBuf) -> Result<(), TomlError> {
info!("GupaxP2poolApi | Deleting old folder at [{}]...", path.display());
std::fs::remove_dir_all(&path)?;
info!("GupaxP2poolApi | Creating new default folder at [{}]...", path.display());
create_gupax_p2pool_dir(&path)?;
Self::create_all_files(&path)?;
Ok(())
}
//---------------------------------------------------------------------------------------------------- Live, functions that actually update/write live stats
pub fn update_log_rev(&mut self) {
let mut log_rev = String::with_capacity(self.log.len());
for line in self.log.lines().rev() {
log_rev.push_str(line);
log_rev.push('\n');
}
self.log_rev = log_rev;
}
pub fn format_payout(date: &str, atomic_unit: &AtomicUnit, block: &HumanNumber) -> String {
format!("{} | {} XMR | Block {}", date, atomic_unit, block)
}
pub fn append_log(&mut self, formatted_log_line: &str) {
self.log.push_str(formatted_log_line);
self.log.push('\n');
}
pub fn append_head_log_rev(&mut self, formatted_log_line: &str) {
self.log_rev = format!("{}\n{}", formatted_log_line, self.log_rev);
}
pub fn update_payout_low(&mut self) {
self.payout_ord.sort_payout_low_to_high();
self.payout_low = self.payout_ord.to_string();
}
pub fn update_payout_high(&mut self) {
self.payout_ord.sort_payout_high_to_low();
self.payout_high = self.payout_ord.to_string();
}
pub fn update_payout_strings(&mut self) {
self.update_payout_low();
self.update_payout_high();
}
// Takes the (date, atomic_unit, block) and updates [self] and the [PayoutOrd]
pub fn add_payout(&mut self, formatted_log_line: &str, date: String, atomic_unit: AtomicUnit, block: HumanNumber) {
self.append_log(formatted_log_line);
self.append_head_log_rev(formatted_log_line);
self.payout_u64 += 1;
self.payout = HumanNumber::from_u64(self.payout_u64);
self.xmr = self.xmr.add_self(atomic_unit);
self.payout_ord.push(date, atomic_unit, block);
self.update_payout_strings();
}
pub fn write_to_all_files(&self, formatted_log_line: &str) -> Result<(), TomlError> {
Self::disk_overwrite(&self.payout_u64.to_string(), &self.path_payout)?;
Self::disk_overwrite(&self.xmr.to_string(), &self.path_xmr)?;
Self::disk_append(formatted_log_line, &self.path_log)?;
Ok(())
}
pub fn disk_append(formatted_log_line: &str, path: &PathBuf) -> Result<(), TomlError> {
use std::io::Write;
let mut file = match fs::OpenOptions::new().append(true).create(true).open(path) {
Ok(f) => f,
Err(e) => { error!("GupaxP2poolApi | Append [{}] ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) },
};
match writeln!(file, "{}", formatted_log_line) {
Ok(_) => { debug!("GupaxP2poolApi | Append [{}] ... OK", path.display()); Ok(()) },
Err(e) => { error!("GupaxP2poolApi | Append [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) },
}
}
pub fn disk_overwrite(string: &str, path: &PathBuf) -> Result<(), TomlError> {
use std::io::Write;
let mut file = match fs::OpenOptions::new().write(true).truncate(true).create(true).open(path) {
Ok(f) => f,
Err(e) => { error!("GupaxP2poolApi | Overwrite [{}] ... FAIL: {}", path.display(), e); return Err(TomlError::Io(e)) },
};
match writeln!(file, "{}", string) {
Ok(_) => { debug!("GupaxP2poolApi | Overwrite [{}] ... OK", path.display()); Ok(()) },
Err(e) => { error!("GupaxP2poolApi | Overwrite [{}] ... FAIL: {}", path.display(), e); Err(TomlError::Io(e)) },
}
}
}
//---------------------------------------------------------------------------------------------------- Custom Error [TomlError]
#[derive(Debug)]
pub enum TomlError {
@ -559,9 +809,138 @@ impl From<std::fmt::Error> for TomlError {
//---------------------------------------------------------------------------------------------------- [File] Enum (for matching which file)
#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub enum File {
State, // state.toml -> Gupax state
Node, // node.toml -> P2Pool manual node selector
Pool, // pool.toml -> XMRig manual pool selector
// State files
State, // state.toml | Gupax state
Node, // node.toml | P2Pool manual node selector
Pool, // pool.toml | XMRig manual pool selector
// Gupax-P2Pool API
Log, // log | Raw log lines of P2Pool payouts received
Payout, // payout | Single [u64] representing total payouts
Xmr, // xmr | Single [u64] representing total XMR mined in atomic units
}
//---------------------------------------------------------------------------------------------------- [Submenu] enum for [Status] tab
#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub enum Submenu {
Processes,
P2pool,
}
impl Default for Submenu {
fn default() -> Self {
Self::Processes
}
}
impl Display for Submenu {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Submenu::*;
match self {
P2pool => write!(f, "P2Pool"),
_ => write!(f, "{:?}", self),
}
}
}
//---------------------------------------------------------------------------------------------------- [PayoutView] enum for [Status/P2Pool] tab
// The enum buttons for selecting which "view" to sort the payout log in.
#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub enum PayoutView {
Latest, // Shows the most recent logs first
Oldest, // Shows the oldest logs first
Biggest, // Shows highest to lowest payouts
Smallest, // Shows lowest to highest payouts
}
impl PayoutView {
fn new() -> Self {
Self::Latest
}
}
impl Default for PayoutView {
fn default() -> Self {
Self::new()
}
}
impl Display for PayoutView {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
//---------------------------------------------------------------------------------------------------- [Hash] enum for [Status/P2Pool]
#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub enum Hash {
Hash,
Kilo,
Mega,
Giga,
}
impl Default for Hash {
fn default() -> Self {
Self::Hash
}
}
impl Hash {
pub fn convert_to_hash(f: f64, from: Self) -> f64 {
match from {
Self::Hash => f,
Self::Kilo => f * 1_000.0,
Self::Mega => f * 1_000_000.0,
Self::Giga => f * 1_000_000_000.0,
}
}
pub fn convert(f: f64, og: Self, new: Self) -> f64 {
match og {
Self::Hash => {
match new {
Self::Hash => f,
Self::Kilo => f / 1_000.0,
Self::Mega => f / 1_000_000.0,
Self::Giga => f / 1_000_000_000.0,
}
},
Self::Kilo => {
match new {
Self::Hash => f * 1_000.0,
Self::Kilo => f,
Self::Mega => f / 1_000.0,
Self::Giga => f / 1_000_000.0,
}
},
Self::Mega => {
match new {
Self::Hash => f * 1_000_000.0,
Self::Kilo => f * 1_000.0,
Self::Mega => f,
Self::Giga => f / 1_000.0,
}
},
Self::Giga => {
match new {
Self::Hash => f * 1_000_000_000.0,
Self::Kilo => f * 1_000_000.0,
Self::Mega => f * 1_000.0,
Self::Giga => f,
}
},
}
}
}
impl Display for Hash {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Hash::Hash => write!(f, "Hash"),
_ => write!(f, "{:?}hash", self),
}
}
}
//---------------------------------------------------------------------------------------------------- [Node] Struct
@ -583,18 +962,30 @@ pub struct Pool {
//---------------------------------------------------------------------------------------------------- [State] Struct
#[derive(Clone,Debug,Deserialize,Serialize)]
pub struct State {
pub status: Status,
pub gupax: Gupax,
pub p2pool: P2pool,
pub xmrig: Xmrig,
pub version: Arc<Mutex<Version>>,
}
#[derive(Clone,PartialEq,Debug,Deserialize,Serialize)]
pub struct Status {
pub submenu: Submenu,
pub payout_view: PayoutView,
pub monero_enabled: bool,
pub manual_hash: bool,
pub hashrate: f64,
pub hash_metric: Hash,
}
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
pub struct Gupax {
pub simple: bool,
pub auto_update: bool,
pub auto_p2pool: bool,
pub auto_xmrig: bool,
// pub auto_monero: bool,
pub ask_before_quit: bool,
pub save_before_quit: bool,
pub update_via_tor: bool,
@ -663,6 +1054,19 @@ pub struct Version {
}
//---------------------------------------------------------------------------------------------------- [State] Defaults
impl Default for Status {
fn default() -> Self {
Self {
submenu: Submenu::default(),
payout_view: PayoutView::default(),
monero_enabled: false,
manual_hash: false,
hashrate: 1.0,
hash_metric: Hash::default(),
}
}
}
impl Default for Gupax {
fn default() -> Self {
Self {
@ -687,6 +1091,7 @@ impl Default for Gupax {
}
}
}
impl Default for P2pool {
fn default() -> Self {
Self {
@ -800,6 +1205,14 @@ mod test {
tab = "About"
ratio = "Width"
[status]
submenu = "P2pool"
payout_view = "Oldest"
monero_enabled = true
manual_hash = false
hashrate = 1241.23
hash_metric = "Hash"
[p2pool]
simple = true
mini = true
@ -979,4 +1392,64 @@ mod test {
assert!(!merged_state.contains("SETTING_THAT_DOESNT_EXIST_ANYMORE"));
assert!(merged_state.contains("44hintoFpuo3ugKfcqJvh5BmrsTRpnTasJmetKC4VXCt6QDtbHVuixdTtsm6Ptp7Y8haXnJ6j8Gj2dra8CKy5ewz7Vi9CYW"));
}
#[test]
fn create_and_serde_gupax_p2pool_api() {
use crate::disk::GupaxP2poolApi;
use crate::regex::P2poolRegex;
use crate::xmr::PayoutOrd;
use crate::xmr::AtomicUnit;
// Get API dir, fill paths.
let mut api = GupaxP2poolApi::new();
let mut path = crate::disk::get_gupax_data_path().unwrap();
path.push(crate::disk::GUPAX_P2POOL_API_DIRECTORY);
GupaxP2poolApi::fill_paths(&mut api, &path);
println!("{:#?}", api);
// Create, write some fake data.
GupaxP2poolApi::create_all_files(&path).unwrap();
api.log = "NOTICE 2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816".to_string();
api.payout_u64 = 1;
api.xmr = AtomicUnit::from_u64(2);
let (date, atomic_unit, block) = PayoutOrd::parse_raw_payout_line(&api.log, &P2poolRegex::new());
let formatted_log_line = GupaxP2poolApi::format_payout(&date, &atomic_unit, &block);
GupaxP2poolApi::write_to_all_files(&api, &formatted_log_line).unwrap();
println!("AFTER WRITE: {:#?}", api);
// Read
GupaxP2poolApi::read_all_files_and_update(&mut api).unwrap();
println!("AFTER READ: {:#?}", api);
// Assert that the file read mutated the internal struct correctly.
assert_eq!(api.payout_u64, 1);
assert_eq!(api.xmr.to_u64(), 2);
assert!(!api.payout_ord.is_empty());
assert!(api.log.contains("2022-01-27 01:30:23.1377 | 0.000000000001 XMR | Block 2,642,816"));
}
#[test]
fn convert_hash() {
use crate::disk::Hash;
let hash = 1.0;
assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Hash), 1.0);
assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Kilo), 0.001);
assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Mega), 0.000_001);
assert_eq!(Hash::convert(hash, Hash::Hash, Hash::Giga), 0.000_000_001);
let hash = 1.0;
assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Hash), 1_000.0);
assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Kilo), 1.0);
assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Mega), 0.001);
assert_eq!(Hash::convert(hash, Hash::Kilo, Hash::Giga), 0.000_001);
let hash = 1.0;
assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Hash), 1_000_000.0);
assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Kilo), 1_000.0);
assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Mega), 1.0);
assert_eq!(Hash::convert(hash, Hash::Mega, Hash::Giga), 0.001);
let hash = 1.0;
assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Hash), 1_000_000_000.0);
assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Kilo), 1_000_000.0);
assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Mega), 1_000.0);
assert_eq!(Hash::convert(hash, Hash::Giga, Hash::Giga), 1.0);
}
}

View file

@ -27,11 +27,11 @@ use egui::{
};
use crate::{
constants::*,
disk::Gupax,
update::*,
ErrorState,
Restart,
Tab,
macros::*,
};
use std::{
thread,
@ -55,13 +55,13 @@ pub struct FileWindow {
impl FileWindow {
pub fn new() -> Arc<Mutex<Self>> {
Arc::new(Mutex::new(Self {
arc_mut!(Self {
thread: false,
picked_p2pool: false,
picked_xmrig: false,
p2pool_path: String::new(),
xmrig_path: String::new(),
}))
})
}
}
@ -81,7 +81,7 @@ pub enum Ratio {
}
//---------------------------------------------------------------------------------------------------- Gupax
impl Gupax {
impl crate::disk::Gupax {
pub fn show(&mut self, og: &Arc<Mutex<State>>, state_path: &Path, update: &Arc<Mutex<Update>>, file_window: &Arc<Mutex<FileWindow>>, error_state: &mut ErrorState, restart: &Arc<Mutex<Restart>>, width: f32, height: f32, frame: &mut eframe::Frame, _ctx: &egui::Context, ui: &mut egui::Ui) {
// Update button + Progress bar
debug!("Gupax Tab | Rendering [Update] button + progress bar");
@ -89,7 +89,7 @@ impl Gupax {
let button = if self.simple { height/5.0 } else { height/15.0 };
let height = if self.simple { height/5.0 } else { height/10.0 };
let width = width - SPACE;
let updating = *update.lock().unwrap().updating.lock().unwrap();
let updating = *lock2!(update,updating);
ui.vertical(|ui| {
// If [Gupax] is being built for a Linux distro,
// disable built-in updating completely.
@ -106,8 +106,8 @@ impl Gupax {
});
ui.vertical(|ui| {
ui.set_enabled(updating);
let prog = *update.lock().unwrap().prog.lock().unwrap();
let msg = format!("{}\n{}{}", *update.lock().unwrap().msg.lock().unwrap(), prog, "%");
let prog = *lock2!(update,prog);
let msg = format!("{}\n{}{}", *lock2!(update,msg), prog, "%");
ui.add_sized([width, height*1.4], Label::new(RichText::text_style(RichText::new(msg), Monospace)));
let height = height/2.0;
if updating {
@ -115,7 +115,7 @@ impl Gupax {
} else {
ui.add_sized([width, height], Label::new("..."));
}
ui.add_sized([width, height], ProgressBar::new(update.lock().unwrap().prog.lock().unwrap().round() / 100.0));
ui.add_sized([width, height], ProgressBar::new(lock2!(update,prog).round() / 100.0));
});
});
@ -160,7 +160,7 @@ impl Gupax {
ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN))).on_hover_text(P2POOL_PATH_OK);
}
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
ui.set_enabled(!file_window.lock().unwrap().thread);
ui.set_enabled(!lock!(file_window).thread);
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Self::spawn_file_window_thread(file_window, FileType::P2pool);
}
@ -177,14 +177,14 @@ impl Gupax {
ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ✔").color(GREEN))).on_hover_text(XMRIG_PATH_OK);
}
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
ui.set_enabled(!file_window.lock().unwrap().thread);
ui.set_enabled(!lock!(file_window).thread);
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
Self::spawn_file_window_thread(file_window, FileType::Xmrig);
}
ui.add_sized([ui.available_width(), height], TextEdit::singleline(&mut self.xmrig_path)).on_hover_text(GUPAX_PATH_XMRIG);
});
});
let mut guard = file_window.lock().unwrap();
let mut guard = lock!(file_window);
if guard.picked_p2pool { self.p2pool_path = guard.p2pool_path.clone(); guard.picked_p2pool = false; }
if guard.picked_xmrig { self.xmrig_path = guard.xmrig_path.clone(); guard.picked_xmrig = false; }
drop(guard);
@ -276,19 +276,19 @@ impl Gupax {
Xmrig => "XMRig",
};
let file_window = file_window.clone();
file_window.lock().unwrap().thread = true;
lock!(file_window).thread = true;
thread::spawn(move|| {
match rfd::FileDialog::new().set_title(&format!("Select {} Binary for Gupax", name)).pick_file() {
Some(path) => {
info!("Gupax | Path selected for {} ... {}", name, path.display());
match file_type {
P2pool => { file_window.lock().unwrap().p2pool_path = path.display().to_string(); file_window.lock().unwrap().picked_p2pool = true; },
Xmrig => { file_window.lock().unwrap().xmrig_path = path.display().to_string(); file_window.lock().unwrap().picked_xmrig = true; },
P2pool => { lock!(file_window).p2pool_path = path.display().to_string(); lock!(file_window).picked_p2pool = true; },
Xmrig => { lock!(file_window).xmrig_path = path.display().to_string(); lock!(file_window).picked_xmrig = true; },
};
},
None => info!("Gupax | No path selected for {}", name),
};
file_window.lock().unwrap().thread = false;
lock!(file_window).thread = false;
});
}
}

File diff suppressed because it is too large Load diff

326
src/human.rs Normal file
View file

@ -0,0 +1,326 @@
// 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/>.
//---------------------------------------------------------------------------------------------------- Constants
// The locale numbers are formatting in is English, which looks like: [1,000]
pub const LOCALE: num_format::Locale = num_format::Locale::en;
pub const ZERO_SECONDS: std::time::Duration = std::time::Duration::from_secs(0);
//---------------------------------------------------------------------------------------------------- [HumanTime]
// This converts a [std::time::Duration] into something more readable.
// Used for uptime display purposes: [7 years, 8 months, 15 days, 23 hours, 35 minutes, 1 second]
// Code taken from [https://docs.rs/humantime/] and edited to remove sub-second time, change spacing and some words.
use std::time::Duration;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct HumanTime(Duration);
impl Default for HumanTime {
fn default() -> Self {
Self::new()
}
}
impl HumanTime {
pub const fn new() -> HumanTime {
HumanTime(ZERO_SECONDS)
}
pub const fn into_human(d: Duration) -> HumanTime {
HumanTime(d)
}
pub const fn from_u64(u: u64) -> HumanTime {
HumanTime(Duration::from_secs(u))
}
fn plural(f: &mut std::fmt::Formatter, started: &mut bool, name: &str, value: u64) -> std::fmt::Result {
if value > 0 {
if *started {
f.write_str(", ")?;
}
write!(f, "{} {}", value, name)?;
if value > 1 {
f.write_str("s")?;
}
*started = true;
}
Ok(())
}
}
impl std::fmt::Display for HumanTime {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let secs = self.0.as_secs();
if secs == 0 {
f.write_str("0 seconds")?;
return Ok(());
}
let years = secs / 31_557_600; // 365.25d
let ydays = secs % 31_557_600;
let months = ydays / 2_630_016; // 30.44d
let mdays = ydays % 2_630_016;
let days = mdays / 86400;
let day_secs = mdays % 86400;
let hours = day_secs / 3600;
let minutes = day_secs % 3600 / 60;
let seconds = day_secs % 60;
let started = &mut false;
Self::plural(f, started, "year", years)?;
Self::plural(f, started, "month", months)?;
Self::plural(f, started, "day", days)?;
Self::plural(f, started, "hour", hours)?;
Self::plural(f, started, "minute", minutes)?;
Self::plural(f, started, "second", seconds)?;
Ok(())
}
}
//---------------------------------------------------------------------------------------------------- [HumanNumber]
// Human readable numbers.
// Float | [1234.57] -> [1,234] | Casts as u64/u128, adds comma
// Unsigned | [1234567] -> [1,234,567] | Adds comma
// Percent | [99.123] -> [99.12%] | Truncates to 2 after dot, adds percent
// Percent | [0.001] -> [0%] | Rounds down, removes redundant zeros
// Hashrate | [123.0, 311.2, null] -> [123, 311, ???] | Casts, replaces null with [???]
// CPU Load | [12.0, 11.4, null] -> [12.0, 11.4, ???] | No change, just into [String] form
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct HumanNumber(String);
impl std::fmt::Display for HumanNumber {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", &self.0)
}
}
impl HumanNumber {
pub fn unknown() -> Self {
Self("???".to_string())
}
pub fn from_str(s: &str) -> Self {
Self(s.to_string())
}
pub fn to_percent(f: f32) -> Self {
if f < 0.01 {
Self("0%".to_string())
} else {
Self(format!("{:.2}%", f))
}
}
pub fn to_percent_3_point(f: f32) -> Self {
Self(format!("{:.3}%", f))
}
pub fn to_percent_no_fmt(f: f32) -> Self {
Self(format!("{}%", f))
}
pub fn from_f64_to_percent_3_point(f: f64) -> Self {
Self(format!("{:.3}%", f))
}
pub fn from_f64_to_percent_6_point(f: f64) -> Self {
Self(format!("{:.6}%", f))
}
pub fn from_f64_to_percent_9_point(f: f64) -> Self {
Self(format!("{:.9}%", f))
}
pub fn from_f64_to_percent_no_fmt(f: f64) -> Self {
Self(format!("{}%", f))
}
pub fn from_f32(f: f32) -> Self {
let mut buf = num_format::Buffer::new();
buf.write_formatted(&(f as u64), &LOCALE);
Self(buf.as_str().to_string())
}
pub fn from_f64(f: f64) -> Self {
let mut buf = num_format::Buffer::new();
buf.write_formatted(&(f as u128), &LOCALE);
Self(buf.as_str().to_string())
}
pub fn from_u16(u: u16) -> Self {
let mut buf = num_format::Buffer::new();
buf.write_formatted(&u, &LOCALE);
Self(buf.as_str().to_string())
}
pub fn from_u32(u: u32) -> Self {
let mut buf = num_format::Buffer::new();
buf.write_formatted(&u, &LOCALE);
Self(buf.as_str().to_string())
}
pub fn from_u64(u: u64) -> Self {
let mut buf = num_format::Buffer::new();
buf.write_formatted(&u, &LOCALE);
Self(buf.as_str().to_string())
}
pub fn from_u128(u: u128) -> Self {
let mut buf = num_format::Buffer::new();
buf.write_formatted(&u, &LOCALE);
Self(buf.as_str().to_string())
}
pub fn from_hashrate(array: [Option<f32>; 3]) -> Self {
let mut string = "[".to_string();
let mut buf = num_format::Buffer::new();
let mut n = 0;
for i in array {
match i {
Some(f) => {
let f = f as u128;
buf.write_formatted(&f, &LOCALE);
string.push_str(buf.as_str());
string.push_str(" H/s");
},
None => string.push_str("??? H/s"),
}
if n != 2 {
string.push_str(", ");
n += 1;
} else {
string.push(']');
break
}
}
Self(string)
}
pub fn from_load(array: [Option<f32>; 3]) -> Self {
let mut string = "[".to_string();
let mut n = 0;
for i in array {
match i {
Some(f) => string.push_str(format!("{:.2}", f).as_str()),
None => string.push_str("???"),
}
if n != 2 {
string.push_str(", ");
n += 1;
} else {
string.push(']');
break
}
}
Self(string)
}
// [1_000_000] -> [1.000 MH/s]
pub fn from_u64_to_megahash_3_point(hash: u64) -> Self {
let hash = (hash as f64)/1_000_000.0;
let hash = format!("{:.3} MH/s", hash);
Self(hash)
}
// [1_000_000_000] -> [1.000 GH/s]
pub fn from_u64_to_gigahash_3_point(hash: u64) -> Self {
let hash = (hash as f64)/1_000_000_000.0;
let hash = format!("{:.3} GH/s", hash);
Self(hash)
}
pub fn from_f64_12_point(f: f64) -> Self {
let f = format!("{:.12}", f);
Self(f)
}
pub fn from_f64_no_fmt(f: f64) -> Self {
let f = format!("{}", f);
Self(f)
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
#[test]
fn human_number() {
use crate::human::HumanNumber;
assert!(HumanNumber::to_percent(0.001).to_string() == "0%");
assert!(HumanNumber::to_percent(12.123123123123).to_string() == "12.12%");
assert!(HumanNumber::to_percent_3_point(0.001).to_string() == "0.001%");
assert!(HumanNumber::from_hashrate([Some(123.1), Some(11111.1), None]).to_string() == "[123 H/s, 11,111 H/s, ??? H/s]");
assert!(HumanNumber::from_hashrate([None, Some(1.123), Some(123123.312)]).to_string() == "[??? H/s, 1 H/s, 123,123 H/s]");
assert!(HumanNumber::from_load([Some(123.1234), Some(321.321), None]).to_string() == "[123.12, 321.32, ???]");
assert!(HumanNumber::from_load([None, Some(4321.43), Some(1234.1)]).to_string() == "[???, 4321.43, 1234.10]");
assert!(HumanNumber::from_f32(123_123.123123123).to_string() == "123,123");
assert!(HumanNumber::from_f64(123_123_123.123123123123123).to_string() == "123,123,123");
assert!(HumanNumber::from_u16(1_000).to_string() == "1,000");
assert!(HumanNumber::from_u16(65_535).to_string() == "65,535");
assert!(HumanNumber::from_u32(65_536).to_string() == "65,536");
assert!(HumanNumber::from_u32(100_000).to_string() == "100,000");
assert!(HumanNumber::from_u32(1_000_000).to_string() == "1,000,000");
assert!(HumanNumber::from_u32(10_000_000).to_string() == "10,000,000");
assert!(HumanNumber::from_u32(100_000_000).to_string() == "100,000,000");
assert!(HumanNumber::from_u32(1_000_000_000).to_string() == "1,000,000,000");
assert!(HumanNumber::from_u32(4_294_967_295).to_string() == "4,294,967,295");
assert!(HumanNumber::from_u64(4_294_967_296).to_string() == "4,294,967,296");
assert!(HumanNumber::from_u64(10_000_000_000).to_string() == "10,000,000,000");
assert!(HumanNumber::from_u64(100_000_000_000).to_string() == "100,000,000,000");
assert!(HumanNumber::from_u64(1_000_000_000_000).to_string() == "1,000,000,000,000");
assert!(HumanNumber::from_u64(10_000_000_000_000).to_string() == "10,000,000,000,000");
assert!(HumanNumber::from_u64(100_000_000_000_000).to_string() == "100,000,000,000,000");
assert!(HumanNumber::from_u64(1_000_000_000_000_000).to_string() == "1,000,000,000,000,000");
assert!(HumanNumber::from_u64(10_000_000_000_000_000).to_string() == "10,000,000,000,000,000");
assert!(HumanNumber::from_u64(18_446_744_073_709_551_615).to_string() == "18,446,744,073,709,551,615");
assert!(HumanNumber::from_u128(18_446_744_073_709_551_616).to_string() == "18,446,744,073,709,551,616");
assert!(HumanNumber::from_u128(100_000_000_000_000_000_000).to_string() == "100,000,000,000,000,000,000");
assert_eq!(
HumanNumber::from_u128(340_282_366_920_938_463_463_374_607_431_768_211_455).to_string(),
"340,282,366,920,938,463,463,374,607,431,768,211,455",
);
assert!(HumanNumber::from_u64_to_gigahash_3_point(1_000_000_000).to_string() == "1.000 GH/s");
}
#[test]
fn human_time() {
use crate::human::HumanTime;
use std::time::Duration;
assert!(HumanTime::into_human(Duration::from_secs(0)).to_string() == "0 seconds");
assert!(HumanTime::into_human(Duration::from_secs(1)).to_string() == "1 second");
assert!(HumanTime::into_human(Duration::from_secs(2)).to_string() == "2 seconds");
assert!(HumanTime::into_human(Duration::from_secs(59)).to_string() == "59 seconds");
assert!(HumanTime::into_human(Duration::from_secs(60)).to_string() == "1 minute");
assert!(HumanTime::into_human(Duration::from_secs(61)).to_string() == "1 minute, 1 second");
assert!(HumanTime::into_human(Duration::from_secs(62)).to_string() == "1 minute, 2 seconds");
assert!(HumanTime::into_human(Duration::from_secs(120)).to_string() == "2 minutes");
assert!(HumanTime::into_human(Duration::from_secs(121)).to_string() == "2 minutes, 1 second");
assert!(HumanTime::into_human(Duration::from_secs(122)).to_string() == "2 minutes, 2 seconds");
assert!(HumanTime::into_human(Duration::from_secs(179)).to_string() == "2 minutes, 59 seconds");
assert!(HumanTime::into_human(Duration::from_secs(3599)).to_string() == "59 minutes, 59 seconds");
assert!(HumanTime::into_human(Duration::from_secs(3600)).to_string() == "1 hour");
assert!(HumanTime::into_human(Duration::from_secs(3601)).to_string() == "1 hour, 1 second");
assert!(HumanTime::into_human(Duration::from_secs(3602)).to_string() == "1 hour, 2 seconds");
assert!(HumanTime::into_human(Duration::from_secs(3660)).to_string() == "1 hour, 1 minute");
assert!(HumanTime::into_human(Duration::from_secs(3720)).to_string() == "1 hour, 2 minutes");
assert!(HumanTime::into_human(Duration::from_secs(86399)).to_string() == "23 hours, 59 minutes, 59 seconds");
assert!(HumanTime::into_human(Duration::from_secs(86400)).to_string() == "1 day");
assert!(HumanTime::into_human(Duration::from_secs(86401)).to_string() == "1 day, 1 second");
assert!(HumanTime::into_human(Duration::from_secs(86402)).to_string() == "1 day, 2 seconds");
assert!(HumanTime::into_human(Duration::from_secs(86460)).to_string() == "1 day, 1 minute");
assert!(HumanTime::into_human(Duration::from_secs(86520)).to_string() == "1 day, 2 minutes");
assert!(HumanTime::into_human(Duration::from_secs(90000)).to_string() == "1 day, 1 hour");
assert!(HumanTime::into_human(Duration::from_secs(93600)).to_string() == "1 day, 2 hours");
assert!(HumanTime::into_human(Duration::from_secs(604799)).to_string() == "6 days, 23 hours, 59 minutes, 59 seconds");
assert!(HumanTime::into_human(Duration::from_secs(604800)).to_string() == "7 days");
assert!(HumanTime::into_human(Duration::from_secs(2630016)).to_string() == "1 month");
assert!(HumanTime::into_human(Duration::from_secs(3234815)).to_string() == "1 month, 6 days, 23 hours, 59 minutes, 59 seconds");
assert!(HumanTime::into_human(Duration::from_secs(5260032)).to_string() == "2 months");
assert!(HumanTime::into_human(Duration::from_secs(31557600)).to_string() == "1 year");
assert!(HumanTime::into_human(Duration::from_secs(63115200)).to_string() == "2 years");
assert_eq!(
HumanTime::into_human(Duration::from_secs(18446744073709551615)).to_string(),
"584542046090 years, 7 months, 15 days, 17 hours, 5 minutes, 3 seconds",
);
}
}

121
src/macros.rs Normal file
View file

@ -0,0 +1,121 @@
// 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/>.
// These are general QoL macros, nothing too scary, I promise.
//
// | MACRO | PURPOSE | EQUIVALENT CODE |
// |---------|-----------------------------------------------|------------------------------------------------------------|
// | lock | Lock an [Arc<Mutex>] | a.lock().unwrap() |
// | lock2 | Lock a field inside a struct, both Arc<Mutex> | a.lock().unwrap().b.lock().unwrap() |
// | arc_mut | Create a new [Arc<Mutex>] | std::sync::Arc::new(std::sync::Mutex::new(my_value)) |
// | sleep | Sleep the current thread for x milliseconds | std::thread::sleep(std::time::Duration::from_millis(1000)) |
// | flip | Flip a bool in place | my_bool = !my_bool |
//
// Hopefully the long ass code on the right justifies usage of macros :D
//
// [lock2!()] works like this: "lock2!(my_first, my_second)"
// and expects it be a [Struct]-[field] relationship, e.g:
//
// let my_first = Arc::new(Mutex::new(Struct {
// my_second: Arc::new(Mutex::new(true)),
// }));
// lock2!(my_first, my_second);
//
// The equivalent code is: "my_first.lock().unwrap().my_second.lock().unwrap()" (see? this is long as hell)
// Locks and unwraps an [Arc<Mutex<T>]
macro_rules! lock {
($arc_mutex:expr) => {
$arc_mutex.lock().unwrap()
};
}
pub(crate) use lock;
// Locks and unwraps a field of a struct, both of them being [Arc<Mutex>]
// Yes, I know this is bad code.
macro_rules! lock2 {
($arc_mutex:expr, $arc_mutex_two:ident) => {
$arc_mutex.lock().unwrap().$arc_mutex_two.lock().unwrap()
};
}
pub(crate) use lock2;
// Creates a new [Arc<Mutex<T>]
macro_rules! arc_mut {
($arc_mutex:expr) => {
std::sync::Arc::new(std::sync::Mutex::new($arc_mutex))
};
}
pub(crate) use arc_mut;
// Sleeps a [std::thread] using milliseconds
macro_rules! sleep {
($millis:expr) => {
std::thread::sleep(std::time::Duration::from_millis($millis))
};
}
pub(crate) use sleep;
// Flips a [bool] in place
macro_rules! flip {
($b:expr) => {
match $b {
true|false => $b = !$b,
}
};
}
pub(crate) use flip;
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
#[test]
fn lock() {
use std::sync::{Arc,Mutex};
let arc_mutex = Arc::new(Mutex::new(false));
*lock!(arc_mutex) = true;
assert!(*lock!(arc_mutex) == true);
}
#[test]
fn lock2() {
struct Ab {
a: Arc<Mutex<bool>>,
}
use std::sync::{Arc,Mutex};
let arc_mutex = Arc::new(Mutex::new(
Ab {
a: Arc::new(Mutex::new(false)),
}
));
*lock2!(arc_mutex,a) = true;
assert!(*lock2!(arc_mutex,a) == true);
}
#[test]
fn arc_mut() {
let a = arc_mut!(false);
assert!(*lock!(a) == false);
}
#[test]
fn flip() {
let mut b = true;
flip!(b);
assert!(b == false);
}
}

View file

@ -37,7 +37,7 @@ use eframe::{egui,NativeOptions};
use log::*;
use env_logger::{Builder,WriteStyle};
// Regex
use regex::Regex;
use ::regex::Regex;
// Serde
use serde::{Serialize,Deserialize};
// std
@ -62,7 +62,11 @@ mod p2pool;
mod xmrig;
mod update;
mod helper;
use {ferris::*,constants::*,node::*,disk::*,status::*,update::*,gupax::*,helper::*};
mod human;
mod regex;
mod xmr;
mod macros;
use {macros::*,crate::regex::*,ferris::*,constants::*,node::*,disk::*,update::*,gupax::*,helper::*};
// Sudo (dummy values for Windows)
mod sudo;
@ -128,6 +132,12 @@ pub struct App {
sudo: Arc<Mutex<SudoState>>, // This is just a dummy struct on [Windows].
// State from [--flags]
no_startup: bool,
// Gupax-P2Pool API
// Gupax's P2Pool API (e.g: ~/.local/share/gupax/p2pool/)
// This is a file-based API that contains data for permanent stats.
// The below struct holds everything needed for it, the paths, the
// actual stats, and all the functions needed to mutate them.
gupax_p2pool_api: Arc<Mutex<GupaxP2poolApi>>,
// Static stuff
pid: sysinfo::Pid, // Gupax's PID
max_threads: usize, // Max amount of detected system threads
@ -137,7 +147,8 @@ pub struct App {
resolution: Vec2, // Frame resolution
os: &'static str, // OS
admin: bool, // Are we admin? (for Windows)
os_data_path: PathBuf, // OS data path (e.g: ~/.local/share/gupax)
os_data_path: PathBuf, // OS data path (e.g: ~/.local/share/gupax/)
gupax_p2pool_api_path: PathBuf, // Gupax-P2Pool API path (e.g: ~/.local/share/gupax/p2pool/)
state_path: PathBuf, // State file path
node_path: PathBuf, // Node file path
pool_path: PathBuf, // Pool file path
@ -166,12 +177,12 @@ impl App {
fn new(now: Instant) -> Self {
info!("Initializing App Struct...");
debug!("App Init | P2Pool & XMRig processes...");
let p2pool = Arc::new(Mutex::new(Process::new(ProcessName::P2pool, String::new(), PathBuf::new())));
let xmrig = Arc::new(Mutex::new(Process::new(ProcessName::Xmrig, String::new(), PathBuf::new())));
let p2pool_api = Arc::new(Mutex::new(PubP2poolApi::new()));
let xmrig_api = Arc::new(Mutex::new(PubXmrigApi::new()));
let p2pool_img = Arc::new(Mutex::new(ImgP2pool::new()));
let xmrig_img = Arc::new(Mutex::new(ImgXmrig::new()));
let p2pool = arc_mut!(Process::new(ProcessName::P2pool, String::new(), PathBuf::new()));
let xmrig = arc_mut!(Process::new(ProcessName::Xmrig, String::new(), PathBuf::new()));
let p2pool_api = arc_mut!(PubP2poolApi::new());
let xmrig_api = arc_mut!(PubXmrigApi::new());
let p2pool_img = arc_mut!(ImgP2pool::new());
let xmrig_img = arc_mut!(ImgXmrig::new());
debug!("App Init | Sysinfo...");
// We give this to the [Helper] thread.
@ -185,27 +196,27 @@ impl App {
Ok(pid) => pid,
Err(e) => { error!("App Init | Failed to get sysinfo PID: {}", e); exit(1) }
};
let pub_sys = Arc::new(Mutex::new(Sys::new()));
let pub_sys = arc_mut!(Sys::new());
debug!("App Init | The rest of the [App]...");
let mut app = Self {
tab: Tab::default(),
ping: Arc::new(Mutex::new(Ping::new())),
ping: arc_mut!(Ping::new()),
width: APP_DEFAULT_WIDTH,
height: APP_DEFAULT_HEIGHT,
must_resize: false,
og: Arc::new(Mutex::new(State::new())),
og: arc_mut!(State::new()),
state: State::new(),
update: Arc::new(Mutex::new(Update::new(String::new(), PathBuf::new(), PathBuf::new(), true))),
update: arc_mut!(Update::new(String::new(), PathBuf::new(), PathBuf::new(), true)),
file_window: FileWindow::new(),
og_node_vec: Node::new_vec(),
node_vec: Node::new_vec(),
og_pool_vec: Pool::new_vec(),
pool_vec: Pool::new_vec(),
restart: Arc::new(Mutex::new(Restart::No)),
restart: arc_mut!(Restart::No),
diff: false,
error_state: ErrorState::new(),
helper: Arc::new(Mutex::new(Helper::new(now, pub_sys.clone(), p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone()))),
helper: arc_mut!(Helper::new(now, pub_sys.clone(), p2pool.clone(), xmrig.clone(), p2pool_api.clone(), xmrig_api.clone(), p2pool_img.clone(), xmrig_img.clone(), arc_mut!(GupaxP2poolApi::new()))),
p2pool,
xmrig,
p2pool_api,
@ -214,10 +225,11 @@ impl App {
xmrig_img,
p2pool_stdin: String::with_capacity(10),
xmrig_stdin: String::with_capacity(10),
sudo: Arc::new(Mutex::new(SudoState::new())),
sudo: arc_mut!(SudoState::new()),
resizing: false,
alpha: 0,
no_startup: false,
gupax_p2pool_api: arc_mut!(GupaxP2poolApi::new()),
pub_sys,
pid,
max_threads: num_cpus::get(),
@ -228,6 +240,7 @@ impl App {
resolution: Vec2::new(APP_DEFAULT_HEIGHT, APP_DEFAULT_WIDTH),
os: OS,
os_data_path: PathBuf::new(),
gupax_p2pool_api_path: PathBuf::new(),
state_path: PathBuf::new(),
node_path: PathBuf::new(),
pool_path: PathBuf::new(),
@ -258,11 +271,14 @@ impl App {
debug!("App Init | Setting TOML path...");
// Set [*.toml] path
app.state_path = app.os_data_path.clone();
app.state_path.push("state.toml");
app.state_path.push(STATE_TOML);
app.node_path = app.os_data_path.clone();
app.node_path.push("node.toml");
app.node_path.push(NODE_TOML);
app.pool_path = app.os_data_path.clone();
app.pool_path.push("pool.toml");
app.pool_path.push(POOL_TOML);
// Set GupaxP2poolApi path
app.gupax_p2pool_api_path = crate::disk::get_gupax_p2pool_path(&app.os_data_path);
lock!(app.gupax_p2pool_api).fill_paths(&app.gupax_p2pool_api_path);
// Apply arg state
// It's not safe to [--reset] if any of the previous variables
@ -289,7 +305,7 @@ impl App {
State::new()
},
};
app.og = Arc::new(Mutex::new(app.state.clone()));
app.og = arc_mut!(app.state.clone());
// Read node list
debug!("App Init | Reading node list...");
app.node_vec = match Node::get(&app.node_path) {
@ -333,9 +349,51 @@ impl App {
debug!("Pool Vec:");
debug!("{:#?}", app.pool_vec);
//----------------------------------------------------------------------------------------------------
// Read [GupaxP2poolApi] disk files
let mut gupax_p2pool_api = lock!(app.gupax_p2pool_api);
match GupaxP2poolApi::create_all_files(&app.gupax_p2pool_api_path) {
Ok(_) => debug!("App Init | Creating Gupax-P2Pool API files ... OK"),
Err(err) => {
error!("GupaxP2poolApi ... {}", err);
match err {
Io(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Path(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Serialize(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Deserialize(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Format(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Merge(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
Parse(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
};
},
}
debug!("App Init | Reading Gupax-P2Pool API files...");
match gupax_p2pool_api.read_all_files_and_update() {
Ok(_) => {
info!(
"GupaxP2poolApi ... Payouts: {} | XMR (atomic-units): {}",
gupax_p2pool_api.payout,
gupax_p2pool_api.xmr,
);
},
Err(err) => {
error!("GupaxP2poolApi ... {}", err);
match err {
Io(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Path(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Serialize(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Deserialize(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Format(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
Merge(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Error, ErrorButtons::ResetState),
Parse(e) => app.error_state.set(format!("GupaxP2poolApi: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
};
},
};
drop(gupax_p2pool_api);
lock!(app.helper).gupax_p2pool_api = Arc::clone(&app.gupax_p2pool_api);
//----------------------------------------------------------------------------------------------------
let mut og = app.og.lock().unwrap(); // Lock [og]
let mut og = lock!(app.og); // Lock [og]
// Handle max threads
debug!("App Init | Handling max thread overflow...");
og.xmrig.max_threads = app.max_threads;
@ -380,13 +438,13 @@ impl App {
let p2pool_path = og.gupax.absolute_p2pool_path.clone();
let xmrig_path = og.gupax.absolute_xmrig_path.clone();
let tor = og.gupax.update_via_tor;
app.update = Arc::new(Mutex::new(Update::new(app.exe.clone(), p2pool_path, xmrig_path, tor)));
app.update = arc_mut!(Update::new(app.exe.clone(), p2pool_path, xmrig_path, tor));
debug!("App Init | Setting state Gupax version...");
// Set state version as compiled in version
og.version.lock().unwrap().gupax = GUPAX_VERSION.to_string();
app.state.version.lock().unwrap().gupax = GUPAX_VERSION.to_string();
lock!(og.version).gupax = GUPAX_VERSION.to_string();
lock!(app.state.version).gupax = GUPAX_VERSION.to_string();
debug!("App Init | Setting saved [Tab]...");
// Set saved [Tab]
@ -562,33 +620,6 @@ impl Images {
}
}
//---------------------------------------------------------------------------------------------------- [Regexes] struct
#[derive(Clone, Debug)]
pub struct Regexes {
pub name: Regex,
pub address: Regex,
pub ipv4: Regex,
pub domain: Regex,
pub 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(),
}
}
// Check if a Monero address is correct.
pub fn addr_ok(&self, address: &str) -> bool {
address.len() == 95 && Regex::is_match(&self.address, address) && !address.contains('0') && !address.contains('O') && !address.contains('l')
}
}
//---------------------------------------------------------------------------------------------------- [Pressed] enum
// These represent the keys pressed during the frame.
// I could use egui's [Key] but there is no option for
@ -605,6 +636,8 @@ enum KeyPressed {
Esc,
Z,
X,
C,
V,
S,
R,
D,
@ -639,6 +672,12 @@ impl KeyPressed {
fn is_d(&self) -> bool {
*self == Self::D
}
fn is_c(&self) -> bool {
*self == Self::C
}
fn is_v(&self) -> bool {
*self == Self::V
}
fn is_none(&self) -> bool {
*self == Self::None
}
@ -761,7 +800,7 @@ fn init_auto(app: &mut App) {
} else if cfg!(windows) {
Helper::start_xmrig(&app.helper, &app.state.xmrig, &app.state.gupax.absolute_xmrig_path, Arc::clone(&app.sudo));
} else {
app.sudo.lock().unwrap().signal = ProcessSignal::Start;
lock!(app.sudo).signal = ProcessSignal::Start;
app.error_state.ask_sudo(&app.sudo);
}
} else {
@ -791,7 +830,14 @@ fn reset_pools(path: &PathBuf) -> Result<(), TomlError> {
}
}
fn reset(path: &PathBuf, state: &PathBuf, node: &PathBuf, pool: &PathBuf) {
fn reset_gupax_p2pool_api(path: &PathBuf) -> Result<(), TomlError> {
match GupaxP2poolApi::create_new(path) {
Ok(_) => { info!("Resetting GupaxP2poolApi ... OK"); Ok(()) },
Err(e) => { error!("Resetting GupaxP2poolApi folder ... FAIL ... {}", e); Err(e) },
}
}
fn reset(path: &PathBuf, state: &PathBuf, node: &PathBuf, pool: &PathBuf, gupax_p2pool_api: &PathBuf) {
let mut code = 0;
// Attempt to remove directory first
match std::fs::remove_dir_all(path) {
@ -815,6 +861,10 @@ fn reset(path: &PathBuf, state: &PathBuf, node: &PathBuf, pool: &PathBuf) {
Ok(_) => (),
Err(_) => code = 1,
}
match reset_gupax_p2pool_api(gupax_p2pool_api) {
Ok(_) => (),
Err(_) => code = 1,
}
match code {
0 => println!("\nGupax reset ... OK"),
_ => eprintln!("\nGupax reset ... FAIL"),
@ -849,12 +899,14 @@ fn parse_args<S: Into<String>>(mut app: App, panic: S) -> App {
// Everything else
for arg in args {
match arg.as_str() {
"--state" => { info!("Printing state..."); print_disk_file(&app.state_path); }
"--nodes" => { info!("Printing node list..."); print_disk_file(&app.node_path); }
"--state" => { info!("Printing state..."); print_disk_file(&app.state_path); },
"--nodes" => { info!("Printing node list..."); print_disk_file(&app.node_path); },
"--payouts" => { info!("Printing payouts...\n"); print_gupax_p2pool_api(&app.gupax_p2pool_api); },
"--reset-state" => if let Ok(()) = reset_state(&app.state_path) { println!("\nState reset ... OK"); exit(0); } else { eprintln!("\nState reset ... FAIL"); exit(1) },
"--reset-nodes" => if let Ok(()) = reset_nodes(&app.node_path) { println!("\nNode reset ... OK"); exit(0) } else { eprintln!("\nNode reset ... FAIL"); exit(1) },
"--reset-pools" => if let Ok(()) = reset_pools(&app.pool_path) { println!("\nPool reset ... OK"); exit(0) } else { eprintln!("\nPool reset ... FAIL"); exit(1) },
"--reset-all" => reset(&app.os_data_path, &app.state_path, &app.node_path, &app.pool_path),
"--reset-payouts" => if let Ok(()) = reset_gupax_p2pool_api(&app.gupax_p2pool_api_path) { println!("\nGupaxP2poolApi reset ... OK"); exit(0) } else { eprintln!("\nGupaxP2poolApi reset ... FAIL"); exit(1) },
"--reset-all" => reset(&app.os_data_path, &app.state_path, &app.node_path, &app.pool_path, &app.gupax_p2pool_api_path),
"--no-startup" => app.no_startup = true,
_ => { eprintln!("\n[Gupax error] Invalid option: [{}]\nFor help, use: [--help]", arg); exit(1); },
}
@ -904,6 +956,33 @@ fn print_disk_file(path: &PathBuf) {
}
}
// Prints the GupaxP2PoolApi files.
fn print_gupax_p2pool_api(gupax_p2pool_api: &Arc<Mutex<GupaxP2poolApi>>) {
let api = lock!(gupax_p2pool_api);
let log = match std::fs::read_to_string(&api.path_log) {
Ok(string) => string,
Err(e) => { error!("{}", e); exit(1); },
};
let payout = match std::fs::read_to_string(&api.path_payout) {
Ok(string) => string,
Err(e) => { error!("{}", e); exit(1); },
};
let xmr = match std::fs::read_to_string(&api.path_xmr) {
Ok(string) => string,
Err(e) => { error!("{}", e); exit(1); },
};
let xmr = match xmr.trim().parse::<u64>() {
Ok(o) => crate::xmr::AtomicUnit::from_u64(o),
Err(e) => { warn!("GupaxP2poolApi | [xmr] parse error: {}", e); exit(1); }
};
println!("{}\nTotal payouts | {}\nTotal XMR | {} ({} Atomic Units)", log, payout.trim(), xmr, xmr.to_u64());
exit(0);
}
// Prints the GupaxP2PoolApi [xmr] file in AtomicUnits and floating point.
fn print_xmr_file(path: &PathBuf) {
}
//---------------------------------------------------------------------------------------------------- Main [App] frame
fn main() {
let now = Instant::now();
@ -923,7 +1002,7 @@ fn main() {
Ok(_) => info!("Temporary folder cleanup ... OK"),
Err(e) => warn!("Could not cleanup [gupax_tmp] folders: {}", e),
}
info!("Init ... DONE");
info!("/*************************************/ Init ... OK /*************************************/");
eframe::run_native(&app.name_version.clone(), options, Box::new(|cc| Box::new(App::cc(cc, app))),);
}
@ -962,6 +1041,10 @@ impl eframe::App for App {
KeyPressed::Z
} else if input.consume_key(Modifiers::NONE, Key::X) {
KeyPressed::X
} else if input.consume_key(Modifiers::NONE, Key::C) {
KeyPressed::C
} else if input.consume_key(Modifiers::NONE, Key::V) {
KeyPressed::V
} else if input.consume_key(Modifiers::NONE, Key::ArrowUp) {
KeyPressed::Up
} else if input.consume_key(Modifiers::NONE, Key::ArrowDown) {
@ -1005,6 +1088,34 @@ impl eframe::App for App {
Tab::P2pool => self.tab = Tab::Xmrig,
Tab::Xmrig => self.tab = Tab::About,
};
// Change Submenu LEFT
} else if key.is_c() && !wants_input {
match self.tab {
Tab::Status => {
match self.state.status.submenu {
Submenu::Processes => self.state.status.submenu = Submenu::P2pool,
Submenu::P2pool => self.state.status.submenu = Submenu::Processes,
}
},
Tab::Gupax => flip!(self.state.gupax.simple),
Tab::P2pool => flip!(self.state.p2pool.simple),
Tab::Xmrig => flip!(self.state.xmrig.simple),
_ => (),
};
// Change Submenu RIGHT
} else if key.is_v() && !wants_input {
match self.tab {
Tab::Status => {
match self.state.status.submenu {
Submenu::Processes => self.state.status.submenu = Submenu::P2pool,
Submenu::P2pool => self.state.status.submenu = Submenu::Processes,
}
},
Tab::Gupax => flip!(self.state.gupax.simple),
Tab::P2pool => flip!(self.state.p2pool.simple),
Tab::Xmrig => flip!(self.state.xmrig.simple),
_ => (),
};
}
// Refresh AT LEAST once a second
@ -1016,13 +1127,13 @@ impl eframe::App for App {
// might as well check only once here to save
// on a bunch of [.lock().unwrap()]s.
debug!("App | Locking and collecting P2Pool state...");
let p2pool = self.p2pool.lock().unwrap();
let p2pool = lock!(self.p2pool);
let p2pool_is_alive = p2pool.is_alive();
let p2pool_is_waiting = p2pool.is_waiting();
let p2pool_state = p2pool.state;
drop(p2pool);
debug!("App | Locking and collecting XMRig state...");
let xmrig = self.xmrig.lock().unwrap();
let xmrig = lock!(self.xmrig);
let xmrig_is_alive = xmrig.is_alive();
let xmrig_is_waiting = xmrig.is_waiting();
let xmrig_state = xmrig.state;
@ -1113,7 +1224,7 @@ impl eframe::App for App {
match self.error_state.buttons {
StayQuit => {
let mut text = "".to_string();
if *self.update.lock().unwrap().updating.lock().unwrap() { text = format!("{}\nUpdate is in progress...! Quitting may cause file corruption!", text); }
if *lock2!(self.update,updating) { text = format!("{}\nUpdate is in progress...! Quitting may cause file corruption!", text); }
if p2pool_is_alive { text = format!("{}\nP2Pool is online...!", text); }
if xmrig_is_alive { text = format!("{}\nXMRig is online...!", text); }
ui.add_sized([width, height], Label::new("--- Are you sure you want to quit? ---"));
@ -1193,7 +1304,7 @@ impl eframe::App for App {
match State::get(&self.state_path) {
Ok(s) => {
self.state = s;
self.og = Arc::new(Mutex::new(self.state.clone()));
self.og = arc_mut!(self.state.clone());
self.error_state.set("State read OK", ErrorFerris::Happy, ErrorButtons::Okay);
},
Err(e) => self.error_state.set(format!("State read fail: {}", e), ErrorFerris::Panic, ErrorButtons::Quit),
@ -1225,7 +1336,7 @@ impl eframe::App for App {
ErrorButtons::Sudo => {
let sudo_width = width/10.0;
let height = ui.available_height()/4.0;
let mut sudo = self.sudo.lock().unwrap();
let mut sudo = lock!(self.sudo);
let hide = sudo.hide;
ui.style_mut().override_text_style = Some(Monospace);
if sudo.testing {
@ -1248,7 +1359,7 @@ impl eframe::App for App {
}
}
let color = if hide { BLACK } else { BRIGHT_YELLOW };
if ui.add_sized([box_width, height], Button::new(RichText::new("👁").color(color))).on_hover_text(PASSWORD_HIDE).clicked() { sudo.hide = !sudo.hide; }
if ui.add_sized([box_width, height], Button::new(RichText::new("👁").color(color))).on_hover_text(PASSWORD_HIDE).clicked() { flip!(sudo.hide); }
});
if (key.is_esc() && !sudo.testing) || ui.add_sized([width, height*4.0], Button::new("Leave")).on_hover_text(PASSWORD_LEAVE).clicked() { self.error_state.reset(); };
// If [test_sudo()] finished, reset error state.
@ -1269,8 +1380,9 @@ impl eframe::App for App {
// contains Arc<Mutex>'s that cannot be compared easily.
// They don't need to be compared anyway.
debug!("App | Checking diff between [og] & [state]");
let og = self.og.lock().unwrap();
if og.gupax != self.state.gupax ||
let og = lock!(self.og);
if og.status != self.state.status ||
og.gupax != self.state.gupax ||
og.p2pool != self.state.p2pool ||
og.xmrig != self.state.xmrig ||
self.og_node_vec != self.node_vec ||
@ -1316,7 +1428,7 @@ impl eframe::App for App {
let width = ((self.width/2.0)/4.0)-(SPACE*2.0);
// [Gupax Version]
// Is yellow if the user updated and should (but isn't required to) restart.
match *self.restart.lock().unwrap() {
match *lock!(self.restart) {
Restart::Yes => ui.add_sized([width, height], Label::new(RichText::new(&self.name_version).color(YELLOW))).on_hover_text(GUPAX_SHOULD_RESTART),
_ => ui.add_sized([width, height], Label::new(&self.name_version)).on_hover_text(GUPAX_UP_TO_DATE),
};
@ -1353,15 +1465,13 @@ impl eframe::App for App {
// [Save/Reset]
ui.with_layout(Layout::right_to_left(Align::RIGHT), |ui| {
let width = match self.tab {
Tab::Gupax => (ui.available_width()/2.0)-(SPACE*3.0),
_ => (ui.available_width()/3.0)-(SPACE*3.0),
};
let width = (ui.available_width()/3.0)-(SPACE*3.0);
ui.group(|ui| {
ui.set_enabled(self.diff);
let width = width / 2.0;
if key.is_r() && !wants_input && self.diff || ui.add_sized([width, height], Button::new("Reset")).on_hover_text("Reset changes").clicked() {
let og = self.og.lock().unwrap().clone();
let og = lock!(self.og).clone();
self.state.status = og.status;
self.state.gupax = og.gupax;
self.state.p2pool = og.p2pool;
self.state.xmrig = og.xmrig;
@ -1371,7 +1481,8 @@ impl eframe::App for App {
if key.is_s() && !wants_input && self.diff || ui.add_sized([width, height], Button::new("Save")).on_hover_text("Save changes").clicked() {
match State::save(&mut self.state, &self.state_path) {
Ok(_) => {
let mut og = self.og.lock().unwrap();
let mut og = lock!(self.og);
og.status = self.state.status.clone();
og.gupax = self.state.gupax.clone();
og.p2pool = self.state.p2pool.clone();
og.xmrig = self.state.xmrig.clone();
@ -1393,9 +1504,21 @@ impl eframe::App for App {
// [Simple/Advanced] + [Start/Stop/Restart]
match self.tab {
Tab::Status => {
ui.group(|ui| {
let width = (ui.available_width() / 2.0)-10.5;
if ui.add_sized([width, height], SelectableLabel::new(self.state.status.submenu == Submenu::P2pool, "P2Pool")).on_hover_text(STATUS_SUBMENU_P2POOL).clicked() {
self.state.status.submenu = Submenu::P2pool;
}
ui.separator();
if ui.add_sized([width, height], SelectableLabel::new(self.state.status.submenu == Submenu::Processes, "Processes")).on_hover_text(STATUS_SUBMENU_PROCESSES).clicked() {
self.state.status.submenu = Submenu::Processes;
}
});
},
Tab::Gupax => {
ui.group(|ui| {
let width = width / 2.0;
let width = (ui.available_width() / 2.0)-10.5;
if ui.add_sized([width, height], SelectableLabel::new(!self.state.gupax.simple, "Advanced")).on_hover_text(GUPAX_ADVANCED).clicked() {
self.state.gupax.simple = false;
}
@ -1426,6 +1549,8 @@ impl eframe::App for App {
});
} else if p2pool_is_alive {
if key.is_up() && !wants_input || ui.add_sized([width, height], Button::new("")).on_hover_text("Restart P2Pool").clicked() {
lock!(self.og).update_absolute_path();
self.state.update_absolute_path();
Helper::restart_p2pool(&self.helper, &self.state.p2pool, &self.state.gupax.absolute_p2pool_path);
}
if key.is_down() && !wants_input || ui.add_sized([width, height], Button::new("")).on_hover_text("Stop P2Pool").clicked() {
@ -1455,8 +1580,8 @@ impl eframe::App for App {
ui.set_enabled(ui_enabled);
let color = if ui_enabled { GREEN } else { RED };
if (ui_enabled && key.is_up() && !wants_input) || ui.add_sized([width, height], Button::new(RichText::new("").color(color))).on_hover_text("Start P2Pool").on_disabled_hover_text(text).clicked() {
self.og.lock().unwrap().update_absolute_path();
self.state.update_absolute_path(); // The above checks make sure this can unwrap safely, probably should handle it though.
lock!(self.og).update_absolute_path();
self.state.update_absolute_path();
Helper::start_p2pool(&self.helper, &self.state.p2pool, &self.state.gupax.absolute_p2pool_path);
}
}
@ -1483,16 +1608,18 @@ impl eframe::App for App {
});
} else if xmrig_is_alive {
if key.is_up() && !wants_input || ui.add_sized([width, height], Button::new("")).on_hover_text("Restart XMRig").clicked() {
lock!(self.og).update_absolute_path();
self.state.update_absolute_path();
if cfg!(windows) {
Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo));
} else {
self.sudo.lock().unwrap().signal = ProcessSignal::Restart;
lock!(self.sudo).signal = ProcessSignal::Restart;
self.error_state.ask_sudo(&self.sudo);
}
}
if key.is_down() && !wants_input || ui.add_sized([width, height], Button::new("")).on_hover_text("Stop XMRig").clicked() {
if cfg!(target_os = "macos") {
self.sudo.lock().unwrap().signal = ProcessSignal::Stop;
lock!(self.sudo).signal = ProcessSignal::Stop;
self.error_state.ask_sudo(&self.sudo);
} else {
Helper::stop_xmrig(&self.helper);
@ -1518,12 +1645,12 @@ impl eframe::App for App {
ui.set_enabled(ui_enabled);
let color = if ui_enabled { GREEN } else { RED };
if (ui_enabled && key.is_up() && !wants_input) || ui.add_sized([width, height], Button::new(RichText::new("").color(color))).on_hover_text("Start XMRig").on_disabled_hover_text(text).clicked() {
self.og.lock().unwrap().update_absolute_path();
self.state.update_absolute_path(); // The above checks make sure this can unwrap safely, probably should handle it though.
lock!(self.og).update_absolute_path();
self.state.update_absolute_path();
if cfg!(windows) {
Helper::start_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo));
} else if cfg!(unix) {
self.sudo.lock().unwrap().signal = ProcessSignal::Start;
lock!(self.sudo).signal = ProcessSignal::Start;
self.error_state.ask_sudo(&self.sudo);
}
}
@ -1553,6 +1680,8 @@ impl eframe::App for App {
let distro = true;
#[cfg(not(feature = "distro"))]
let distro = false;
let p2pool_gui_len = lock!(self.p2pool_api).output.len();
let xmrig_gui_len = lock!(self.xmrig_api).output.len();
let debug_info = format!(
"Gupax version: {}\n
Bundled P2Pool version: {}\n
@ -1581,6 +1710,8 @@ XMRig console byte length: {}\n
{:#?}\n
------------------------------------------ XMRIG IMAGE ------------------------------------------
{:#?}\n
------------------------------------------ GUPAX-P2POOL API ------------------------------------------
{:#?}\n
------------------------------------------ WORKING STATE ------------------------------------------
{:#?}\n
------------------------------------------ ORIGINAL STATE ------------------------------------------
@ -1589,10 +1720,10 @@ XMRig console byte length: {}\n
P2POOL_VERSION,
XMRIG_VERSION,
self.now.elapsed().as_secs_f32(),
self.state.gupax.selected_width as u16,
self.state.gupax.selected_height as u16,
self.width as u16,
self.height as u16,
self.state.gupax.selected_width,
self.state.gupax.selected_height,
self.width,
self.height,
OS_NAME,
self.max_threads,
self.pid,
@ -1608,12 +1739,13 @@ XMRig console byte length: {}\n
self.exe,
self.state.gupax.absolute_p2pool_path.display(),
self.state.gupax.absolute_xmrig_path.display(),
self.p2pool_api.lock().unwrap().output.len(),
self.xmrig_api.lock().unwrap().output.len(),
self.p2pool_img.lock().unwrap(),
self.xmrig_img.lock().unwrap(),
p2pool_gui_len,
xmrig_gui_len,
lock!(self.p2pool_img),
lock!(self.xmrig_img),
lock!(self.gupax_p2pool_api),
self.state,
self.og.lock().unwrap(),
lock!(self.og),
);
self.error_state.set(debug_info, ErrorFerris::Cute, ErrorButtons::Debug);
}
@ -1646,19 +1778,19 @@ XMRig console byte length: {}\n
}
Tab::Status => {
debug!("App | Entering [Status] Tab");
Status::show(&self.pub_sys, &self.p2pool_api, &self.xmrig_api, &self.p2pool_img, &self.xmrig_img, p2pool_is_alive, xmrig_is_alive, self.max_threads, self.width, self.height, ctx, ui);
crate::disk::Status::show(&mut self.state.status, &self.pub_sys, &self.p2pool_api, &self.xmrig_api, &self.p2pool_img, &self.xmrig_img, p2pool_is_alive, xmrig_is_alive, self.max_threads, &self.gupax_p2pool_api, self.width, self.height, ctx, ui);
}
Tab::Gupax => {
debug!("App | Entering [Gupax] Tab");
Gupax::show(&mut self.state.gupax, &self.og, &self.state_path, &self.update, &self.file_window, &mut self.error_state, &self.restart, self.width, self.height, frame, ctx, ui);
crate::disk::Gupax::show(&mut self.state.gupax, &self.og, &self.state_path, &self.update, &self.file_window, &mut self.error_state, &self.restart, self.width, self.height, frame, ctx, ui);
}
Tab::P2pool => {
debug!("App | Entering [P2Pool] Tab");
P2pool::show(&mut self.state.p2pool, &mut self.node_vec, &self.og, &self.ping, &self.regex, &self.p2pool, &self.p2pool_api, &mut self.p2pool_stdin, self.width, self.height, ctx, ui);
crate::disk::P2pool::show(&mut self.state.p2pool, &mut self.node_vec, &self.og, &self.ping, &self.regex, &self.p2pool, &self.p2pool_api, &mut self.p2pool_stdin, self.width, self.height, ctx, ui);
}
Tab::Xmrig => {
debug!("App | Entering [XMRig] Tab");
Xmrig::show(&mut self.state.xmrig, &mut self.pool_vec, &self.regex, &self.xmrig, &self.xmrig_api, &mut self.xmrig_stdin, self.width, self.height, ctx, ui);
crate::disk::Xmrig::show(&mut self.state.xmrig, &mut self.pool_vec, &self.regex, &self.xmrig, &self.xmrig_api, &mut self.xmrig_stdin, self.width, self.height, ctx, ui);
}
}
});

View file

@ -17,6 +17,7 @@
use crate::{
constants::*,
macros::*,
};
use serde::{Serialize,Deserialize};
use rand::{thread_rng, Rng};
@ -310,19 +311,19 @@ impl Ping {
match Self::ping(&ping) {
Ok(msg) => {
info!("Ping ... OK");
ping.lock().unwrap().msg = msg;
ping.lock().unwrap().pinged = true;
ping.lock().unwrap().auto_selected = false;
ping.lock().unwrap().prog = 100.0;
lock!(ping).msg = msg;
lock!(ping).pinged = true;
lock!(ping).auto_selected = false;
lock!(ping).prog = 100.0;
},
Err(err) => {
error!("Ping ... FAIL ... {}", err);
ping.lock().unwrap().pinged = false;
ping.lock().unwrap().msg = err.to_string();
lock!(ping).pinged = false;
lock!(ping).msg = err.to_string();
},
}
info!("Ping ... Took [{}] seconds...", now.elapsed().as_secs_f32());
ping.lock().unwrap().pinging = false;
lock!(ping).pinging = false;
});
}
@ -347,13 +348,13 @@ impl Ping {
pub async fn ping(ping: &Arc<Mutex<Self>>) -> Result<String, anyhow::Error> {
// Start ping
let ping = Arc::clone(ping);
ping.lock().unwrap().pinging = true;
ping.lock().unwrap().prog = 0.0;
lock!(ping).pinging = true;
lock!(ping).prog = 0.0;
let percent = (100.0 / (COMMUNITY_NODE_LENGTH as f32)).floor();
// Create HTTP client
let info = "Creating HTTP Client".to_string();
ping.lock().unwrap().msg = info;
lock!(ping).msg = info;
let client: Client<HttpConnector> = Client::builder()
.build(HttpConnector::new());
@ -361,7 +362,7 @@ impl Ping {
let rand_user_agent = crate::Pkg::get_user_agent();
// Handle vector
let mut handles = Vec::with_capacity(COMMUNITY_NODE_LENGTH);
let node_vec = Arc::new(Mutex::new(Vec::with_capacity(COMMUNITY_NODE_LENGTH)));
let node_vec = arc_mut!(Vec::with_capacity(COMMUNITY_NODE_LENGTH));
for ip in NODE_IPS {
let client = client.clone();
@ -381,12 +382,12 @@ impl Ping {
handle.await?;
}
let node_vec = std::mem::take(&mut *node_vec.lock().unwrap());
let node_vec = std::mem::take(&mut *lock!(node_vec));
let fastest_info = format!("Fastest node: {}ms ... {} @ {}", node_vec[0].ms, node_vec[0].id, node_vec[0].ip);
let info = "Cleaning up connections".to_string();
info!("Ping | {}...", info);
let mut ping = ping.lock().unwrap();
let mut ping = lock!(ping);
ping.fastest = node_vec[0].id;
ping.nodes = node_vec;
ping.msg = info;
@ -421,11 +422,11 @@ impl Ping {
} else {
color = BLACK;
}
let mut ping = ping.lock().unwrap();
let mut ping = lock!(ping);
ping.msg = info;
ping.prog += percent;
drop(ping);
node_vec.lock().unwrap().push(NodeData { id: ip_to_enum(ip), ip, ms, color, });
lock!(node_vec).push(NodeData { id: ip_to_enum(ip), ip, ms, color, });
}
}

View file

@ -21,6 +21,7 @@ use crate::{
disk::*,
node::*,
helper::*,
macros::*,
};
use egui::{
TextEdit,SelectableLabel,ComboBox,Label,Button,
@ -31,7 +32,7 @@ use std::sync::{Arc,Mutex};
use regex::Regex;
use log::*;
impl P2pool {
impl crate::disk::P2pool {
pub fn show(&mut self, node_vec: &mut Vec<(String, Node)>, _og: &Arc<Mutex<State>>, ping: &Arc<Mutex<Ping>>, regex: &Regexes, process: &Arc<Mutex<Process>>, api: &Arc<Mutex<PubP2poolApi>>, buffer: &mut String, width: f32, height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) {
let text_edit = height / 25.0;
//---------------------------------------------------------------------------------------------------- [Simple] Console
@ -44,7 +45,7 @@ impl P2pool {
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
egui::ScrollArea::vertical().stick_to_bottom(true).max_width(width).max_height(height).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
ui.add_sized([width, height], TextEdit::multiline(&mut api.lock().unwrap().output.as_str()));
ui.add_sized([width, height], TextEdit::multiline(&mut lock!(api).output.as_str()));
});
});
//---------------------------------------------------------------------------------------------------- [Advanced] Console
@ -55,7 +56,7 @@ impl P2pool {
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
egui::ScrollArea::vertical().stick_to_bottom(true).max_width(width).max_height(height).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
ui.add_sized([width, height], TextEdit::multiline(&mut api.lock().unwrap().output.as_str()));
ui.add_sized([width, height], TextEdit::multiline(&mut lock!(api).output.as_str()));
});
});
ui.separator();
@ -64,7 +65,7 @@ impl P2pool {
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
response.request_focus(); // Get focus back
let buffer = std::mem::take(buffer); // Take buffer
let mut process = process.lock().unwrap(); // Lock
let mut process = lock!(process); // Lock
if process.is_alive() { process.input.push(buffer); } // Push only if alive
}
}
@ -122,7 +123,7 @@ impl P2pool {
// Two atomic bools = enough to represent this data
debug!("P2Pool Tab | Running [auto-select] check");
if self.auto_select {
let mut ping = ping.lock().unwrap();
let mut ping = lock!(ping);
// If we haven't auto_selected yet, auto-select and turn it off
if ping.pinged && !ping.auto_selected {
self.node = ping.fastest;
@ -139,8 +140,8 @@ impl P2pool {
let ip = enum_to_ip(id);
let mut ms = 0;
let mut color = Color32::LIGHT_GRAY;
if ping.lock().unwrap().pinged {
for data in ping.lock().unwrap().nodes.iter() {
if lock!(ping).pinged {
for data in lock!(ping).nodes.iter() {
if data.id == id {
ms = data.ms;
color = data.color;
@ -151,7 +152,7 @@ impl P2pool {
debug!("P2Pool Tab | Rendering [ComboBox] of Community Nodes");
let text = RichText::new(format!("{}ms | {} | {}", ms, id, ip)).color(color);
ComboBox::from_id_source("community_nodes").selected_text(RichText::text_style(text, Monospace)).show_ui(ui, |ui| {
for data in ping.lock().unwrap().nodes.iter() {
for data in lock!(ping).nodes.iter() {
let ms = crate::node::format_ms(data.ms);
let id = crate::node::format_enum(data.id);
let text = RichText::text_style(RichText::new(format!("{} | {} | {}", ms, id, data.ip)).color(data.color), Monospace);
@ -170,18 +171,18 @@ impl P2pool {
self.node = NodeEnum::get_random(&self.node);
}
// [Select fastest node]
if ui.add_sized([width, height], Button::new("Select fastest node")).on_hover_text(P2POOL_SELECT_FASTEST).clicked() && ping.lock().unwrap().pinged {
self.node = ping.lock().unwrap().fastest;
if ui.add_sized([width, height], Button::new("Select fastest node")).on_hover_text(P2POOL_SELECT_FASTEST).clicked() && lock!(ping).pinged {
self.node = lock!(ping).fastest;
}
// [Ping Button]
ui.add_enabled_ui(!ping.lock().unwrap().pinging, |ui| {
ui.add_enabled_ui(!lock!(ping).pinging, |ui| {
if ui.add_sized([width, height], Button::new("Ping community nodes")).on_hover_text(P2POOL_PING).clicked() {
Ping::spawn_thread(ping);
}
});
// [Last <-]
if ui.add_sized([width, height], Button::new("⬅ Last")).on_hover_text(P2POOL_SELECT_LAST).clicked() {
let ping = ping.lock().unwrap();
let ping = lock!(ping);
match ping.pinged {
true => self.node = NodeEnum::get_last_from_ping(&self.node, &ping.nodes),
false => self.node = NodeEnum::get_last(&self.node),
@ -190,7 +191,7 @@ impl P2pool {
}
// [Next ->]
if ui.add_sized([width, height], Button::new("Next ➡")).on_hover_text(P2POOL_SELECT_NEXT).clicked() {
let ping = ping.lock().unwrap();
let ping = lock!(ping);
match ping.pinged {
true => self.node = NodeEnum::get_next_from_ping(&self.node, &ping.nodes),
false => self.node = NodeEnum::get_next(&self.node),
@ -201,10 +202,10 @@ impl P2pool {
ui.vertical(|ui| {
let height = height / 2.0;
let pinging = ping.lock().unwrap().pinging;
let pinging = lock!(ping).pinging;
ui.set_enabled(pinging);
let prog = ping.lock().unwrap().prog.round();
let msg = RichText::text_style(RichText::new(format!("{} ... {}%", ping.lock().unwrap().msg, prog)), Monospace);
let prog = lock!(ping).prog.round();
let msg = RichText::text_style(RichText::new(format!("{} ... {}%", lock!(ping).msg, prog)), Monospace);
let height = height / 1.25;
ui.add_space(5.0);
ui.add_sized([width, height], Label::new(msg));

128
src/regex.rs Normal file
View file

@ -0,0 +1,128 @@
// 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/>.
// Some regexes used throughout Gupax.
use regex::Regex;
//---------------------------------------------------------------------------------------------------- [Regexes] struct
// General purpose Regexes, mostly used in the GUI.
#[derive(Clone, Debug)]
pub struct Regexes {
pub name: Regex,
pub address: Regex,
pub ipv4: Regex,
pub domain: Regex,
pub port: Regex,
}
impl Regexes {
pub 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(),
}
}
// Check if a Monero address is correct.
// This actually only checks for length & Base58, and doesn't do any checksum validation
// (the last few bytes of a Monero address are a Keccak hash checksum) so some invalid addresses can trick this function.
pub fn addr_ok(&self, address: &str) -> bool {
address.len() == 95 && Regex::is_match(&self.address, address) && !address.contains('0') && !address.contains('O') && !address.contains('l')
}
}
//---------------------------------------------------------------------------------------------------- [P2poolRegex]
// Meant for parsing the output of P2Pool and finding payouts and total XMR found.
// Why Regex instead of the standard library?
// 1. I'm already using Regex
// 2. It's insanely faster
//
// The following STDLIB implementation takes [0.003~] seconds to find all matches given a [String] with 30k lines:
// let mut n = 0;
// for line in P2POOL_OUTPUT.lines() {
// if line.contains("payout of [0-9].[0-9]+ XMR") { n += 1; }
// }
//
// This regex function takes [0.0003~] seconds (10x faster):
// let regex = Regex::new("payout of [0-9].[0-9]+ XMR").unwrap();
// let n = regex.find_iter(P2POOL_OUTPUT).count();
//
// Both are nominally fast enough where it doesn't matter too much but meh, why not use regex.
#[derive(Clone,Debug)]
pub struct P2poolRegex {
pub date: regex::Regex,
pub payout: regex::Regex,
pub payout_float: regex::Regex,
pub block: regex::Regex,
pub block_int: regex::Regex,
pub block_comma: regex::Regex,
}
impl P2poolRegex {
pub fn new() -> Self {
Self {
date: regex::Regex::new("[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+.[0-9]+").unwrap(),
payout: regex::Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(), // Assumes 12 digits after the dot.
payout_float: regex::Regex::new("[0-9].[0-9]{12}").unwrap(), // Assumes 12 digits after the dot.
block: regex::Regex::new("block [0-9]{7}").unwrap(), // Monero blocks will be 7 digits for... the next 10,379 years
block_int: regex::Regex::new("[0-9]{7}").unwrap(),
block_comma: regex::Regex::new("[0-9],[0-9]{3},[0-9]{3}").unwrap(),
}
}
}
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
#[test]
fn build_regexes() {
use regex::Regex;
let r = crate::Regexes::new();
assert!(Regex::is_match(&r.name, "_this_ is... a n-a-m-e."));
assert!(Regex::is_match(&r.address, "44hintoFpuo3ugKfcqJvh5BmrsTRpnTasJmetKC4VXCt6QDtbHVuixdTtsm6Ptp7Y8haXnJ6j8Gj2dra8CKy5ewz7Vi9CYW"));
assert!(Regex::is_match(&r.ipv4, "192.168.1.2"));
assert!(Regex::is_match(&r.ipv4, "127.0.0.1"));
assert!(Regex::is_match(&r.domain, "my.node.com"));
assert!(Regex::is_match(&r.domain, "my.monero-node123.net"));
assert!(Regex::is_match(&r.domain, "www.my-node.org"));
assert!(Regex::is_match(&r.domain, "www.my-monero-node123.io"));
for i in 1..=65535 {
assert!(Regex::is_match(&r.port, &i.to_string()));
}
assert!(!Regex::is_match(&r.port, "0"));
assert!(!Regex::is_match(&r.port, "65536"));
}
#[test]
fn build_p2pool_regex() {
use regex::Regex;
let r = crate::P2poolRegex::new();
let text = "NOTICE 2022-11-11 11:11:11.1111 P2Pool You received a payout of 0.111111111111 XMR in block 1111111";
let text2 = "2022-11-11 11:11:11.1111 | 0.111111111111 XMR | Block 1,111,111";
assert_eq!(r.payout.find(text).unwrap().as_str(), "payout of 0.111111111111 XMR");
assert_eq!(r.payout_float.find(text).unwrap().as_str(), "0.111111111111");
assert_eq!(r.date.find(text).unwrap().as_str(), "2022-11-11 11:11:11.1111");
assert_eq!(r.block.find(text).unwrap().as_str(), "block 1111111");
assert_eq!(r.block_int.find(text).unwrap().as_str(), "1111111");
assert_eq!(r.block_comma.find(text2).unwrap().as_str(), "1,111,111");
}
}

View file

@ -22,6 +22,12 @@ use crate::{
ImgXmrig,
constants::*,
Sys,
Hash,
Submenu,
macros::*,
GupaxP2poolApi,
PayoutView,
human::HumanNumber,
};
use std::sync::{Arc,Mutex};
use log::*;
@ -29,14 +35,15 @@ use egui::{
Label,RichText,TextStyle,
TextStyle::Monospace,
TextStyle::Name,
TextEdit,
SelectableLabel,
Slider,
};
// Main data structure for the Status tab
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Status {}
impl Status {
pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_api: &Arc<Mutex<PubXmrigApi>>, p2pool_img: &Arc<Mutex<ImgP2pool>>, xmrig_img: &Arc<Mutex<ImgXmrig>>, p2pool_alive: bool, xmrig_alive: bool, max_threads: usize, width: f32, height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) {
impl crate::disk::Status {
pub fn show(&mut self, sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_api: &Arc<Mutex<PubXmrigApi>>, p2pool_img: &Arc<Mutex<ImgP2pool>>, xmrig_img: &Arc<Mutex<ImgXmrig>>, p2pool_alive: bool, xmrig_alive: bool, max_threads: usize, gupax_p2pool_api: &Arc<Mutex<GupaxP2poolApi>>, width: f32, height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) {
//---------------------------------------------------------------------------------------------------- [Processes]
if self.submenu == Submenu::Processes {
let width = (width/3.0)-(SPACE*1.666);
let min_height = height/1.1;
let height = height/25.0;
@ -47,7 +54,7 @@ pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_
ui.set_min_height(min_height);
ui.add_sized([width, height], Label::new(RichText::new("[Gupax]").color(LIGHT_GRAY).text_style(TextStyle::Name("MonospaceLarge".into())))).on_hover_text("Gupax is online");
ui.style_mut().override_text_style = Some(Monospace);
let sys = sys.lock().unwrap();
let sys = lock!(sys);
ui.add_sized([width, height], Label::new(RichText::new("Uptime").underline().color(BONE))).on_hover_text(STATUS_GUPAX_UPTIME);
ui.add_sized([width, height], Label::new(sys.gupax_uptime.to_string()));
ui.add_sized([width, height], Label::new(RichText::new("Gupax CPU").underline().color(BONE))).on_hover_text(STATUS_GUPAX_CPU_USAGE);
@ -70,7 +77,7 @@ pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_
ui.add_sized([width, height], Label::new(RichText::new("[P2Pool]").color(LIGHT_GRAY).text_style(TextStyle::Name("MonospaceLarge".into())))).on_hover_text("P2Pool is online").on_disabled_hover_text("P2Pool is offline");
let height = height/1.4;
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
let api = p2pool_api.lock().unwrap();
let api = lock!(p2pool_api);
ui.add_sized([width, height], Label::new(RichText::new("Uptime").underline().color(BONE))).on_hover_text(STATUS_P2POOL_UPTIME);
ui.add_sized([width, height], Label::new(format!("{}", api.uptime)));
ui.add_sized([width, height], Label::new(RichText::new("Shares Found").underline().color(BONE))).on_hover_text(STATUS_P2POOL_SHARES);
@ -87,7 +94,7 @@ pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_
ui.add_sized([width, height], Label::new(format!("{}", api.connections)));
ui.add_sized([width, height], Label::new(RichText::new("Effort").underline().color(BONE))).on_hover_text(STATUS_P2POOL_EFFORT);
ui.add_sized([width, height], Label::new(format!("[Average: {}] [Current: {}]", api.average_effort, api.current_effort)));
let img = p2pool_img.lock().unwrap();
let img = lock!(p2pool_img);
ui.add_sized([width, height], Label::new(RichText::new("Monero Node").underline().color(BONE))).on_hover_text(STATUS_P2POOL_MONERO_NODE);
ui.add_sized([width, height], Label::new(format!("[IP: {}]\n[RPC: {}] [ZMQ: {}]", &img.host, &img.rpc, &img.zmq)));
ui.add_sized([width, height], Label::new(RichText::new("Sidechain").underline().color(BONE))).on_hover_text(STATUS_P2POOL_POOL);
@ -104,7 +111,7 @@ pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_
ui.set_min_height(min_height);
ui.add_sized([width, height], Label::new(RichText::new("[XMRig]").color(LIGHT_GRAY).text_style(TextStyle::Name("MonospaceLarge".into())))).on_hover_text("XMRig is online").on_disabled_hover_text("XMRig is offline");
ui.style_mut().override_text_style = Some(Monospace);
let api = xmrig_api.lock().unwrap();
let api = lock!(xmrig_api);
ui.add_sized([width, height], Label::new(RichText::new("Uptime").underline().color(BONE))).on_hover_text(STATUS_XMRIG_UPTIME);
ui.add_sized([width, height], Label::new(format!("{}", api.uptime)));
ui.add_sized([width, height], Label::new(RichText::new("CPU Load Averages").underline().color(BONE))).on_hover_text(STATUS_XMRIG_CPU);
@ -116,11 +123,151 @@ pub fn show(sys: &Arc<Mutex<Sys>>, p2pool_api: &Arc<Mutex<PubP2poolApi>>, xmrig_
ui.add_sized([width, height], Label::new(RichText::new("Shares").underline().color(BONE))).on_hover_text(STATUS_XMRIG_SHARES);
ui.add_sized([width, height], Label::new(format!("[Accepted: {}] [Rejected: {}]", api.accepted, api.rejected)));
ui.add_sized([width, height], Label::new(RichText::new("Pool").underline().color(BONE))).on_hover_text(STATUS_XMRIG_POOL);
ui.add_sized([width, height], Label::new(&xmrig_img.lock().unwrap().url));
ui.add_sized([width, height], Label::new(&lock!(xmrig_img).url));
ui.add_sized([width, height], Label::new(RichText::new("Threads").underline().color(BONE))).on_hover_text(STATUS_XMRIG_THREADS);
ui.add_sized([width, height], Label::new(format!("{}/{}", &xmrig_img.lock().unwrap().threads, max_threads)));
ui.add_sized([width, height], Label::new(format!("{}/{}", &lock!(xmrig_img).threads, max_threads)));
drop(api);
})});
});
//---------------------------------------------------------------------------------------------------- [P2Pool]
} else if self.submenu == Submenu::P2pool {
let api = lock!(gupax_p2pool_api);
let text = height / 25.0;
let log = height / 2.8;
ui.style_mut().override_text_style = Some(Monospace);
// Payout Text + PayoutView buttons
ui.group(|ui| {
ui.horizontal(|ui| {
let width = (width/3.0)-(SPACE*4.0);
ui.add_sized([width, text], Label::new(RichText::new(format!("Total Payouts: {}", api.payout)).underline().color(LIGHT_GRAY))).on_hover_text(STATUS_SUBMENU_PAYOUT);
ui.separator();
ui.add_sized([width, text], Label::new(RichText::new(format!("Total XMR: {}", api.xmr)).underline().color(LIGHT_GRAY))).on_hover_text(STATUS_SUBMENU_XMR);
let width = width / 4.0;
ui.separator();
if ui.add_sized([width, text], SelectableLabel::new(self.payout_view == PayoutView::Latest, "Latest")).on_hover_text(STATUS_SUBMENU_LATEST).clicked() {
self.payout_view = PayoutView::Latest;
}
ui.separator();
if ui.add_sized([width, text], SelectableLabel::new(self.payout_view == PayoutView::Oldest, "Oldest")).on_hover_text(STATUS_SUBMENU_OLDEST).clicked() {
self.payout_view = PayoutView::Oldest;
}
ui.separator();
if ui.add_sized([width, text], SelectableLabel::new(self.payout_view == PayoutView::Biggest, "Biggest")).on_hover_text(STATUS_SUBMENU_BIGGEST).clicked() {
self.payout_view = PayoutView::Biggest;
}
ui.separator();
if ui.add_sized([width, text], SelectableLabel::new(self.payout_view == PayoutView::Smallest, "Smallest")).on_hover_text(STATUS_SUBMENU_SMALLEST).clicked() {
self.payout_view = PayoutView::Smallest;
}
});
ui.separator();
// Actual logs
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
egui::ScrollArea::vertical().stick_to_bottom(self.payout_view == PayoutView::Oldest).max_width(width).max_height(log).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
ui.style_mut().override_text_style = Some(Name("MonospaceLarge".into()));
match self.payout_view {
PayoutView::Latest => ui.add_sized([width, log], TextEdit::multiline(&mut api.log_rev.as_str())),
PayoutView::Oldest => ui.add_sized([width, log], TextEdit::multiline(&mut api.log.as_str())),
PayoutView::Biggest => ui.add_sized([width, log], TextEdit::multiline(&mut api.payout_high.as_str())),
PayoutView::Smallest => ui.add_sized([width, log], TextEdit::multiline(&mut api.payout_low.as_str())),
};
});
});
});
drop(api);
// Payout/Share Calculator
ui.style_mut().override_text_style = Some(Monospace);
let button = (width/20.0)-(SPACE*1.666);
ui.group(|ui| { ui.horizontal(|ui| {
ui.set_min_width(width-SPACE);
if ui.add_sized([button*2.0, text], SelectableLabel::new(!self.manual_hash, "Automatic")).on_hover_text(STATUS_SUBMENU_AUTOMATIC).clicked() {self.manual_hash = false; }
ui.separator();
if ui.add_sized([button*2.0, text], SelectableLabel::new(self.manual_hash, "Manual")).on_hover_text(STATUS_SUBMENU_MANUAL).clicked() { self.manual_hash = true; }
ui.separator();
ui.set_enabled(self.manual_hash);
if ui.add_sized([button, text], SelectableLabel::new(self.hash_metric == Hash::Hash, "Hash")).on_hover_text(STATUS_SUBMENU_HASH).clicked() { self.hash_metric = Hash::Hash; }
ui.separator();
if ui.add_sized([button, text], SelectableLabel::new(self.hash_metric == Hash::Kilo, "Kilo")).on_hover_text(STATUS_SUBMENU_KILO).clicked() { self.hash_metric = Hash::Kilo; }
ui.separator();
if ui.add_sized([button, text], SelectableLabel::new(self.hash_metric == Hash::Mega, "Mega")).on_hover_text(STATUS_SUBMENU_MEGA).clicked() { self.hash_metric = Hash::Mega; }
ui.separator();
if ui.add_sized([button, text], SelectableLabel::new(self.hash_metric == Hash::Giga, "Giga")).on_hover_text(STATUS_SUBMENU_GIGA).clicked() { self.hash_metric = Hash::Giga; }
ui.separator();
ui.spacing_mut().slider_width = button*11.5;
ui.add_sized([button*14.0, text], Slider::new(&mut self.hashrate, 1.0..=1_000.0));
})});
// Actual stats
ui.set_enabled(p2pool_alive);
let text = height / 25.0;
let width = (width/3.0)-(SPACE*1.666);
let min_height = ui.available_height()/1.3;
let api = lock!(p2pool_api);
ui.horizontal(|ui| {
ui.group(|ui| { ui.vertical(|ui| {
ui.set_min_height(min_height);
ui.add_sized([width, text], Label::new(RichText::new("Monero Difficulty").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_MONERO_DIFFICULTY);
ui.add_sized([width, text], Label::new(api.monero_difficulty.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("Monero Hashrate").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_MONERO_HASHRATE);
ui.add_sized([width, text], Label::new(api.monero_hashrate.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("P2Pool Difficulty").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_DIFFICULTY);
ui.add_sized([width, text], Label::new(api.p2pool_difficulty.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("P2Pool Hashrate").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_HASHRATE);
ui.add_sized([width, text], Label::new(api.p2pool_hashrate.as_str()));
})});
ui.group(|ui| { ui.vertical(|ui| {
ui.set_min_height(min_height);
if self.manual_hash {
let hashrate = Hash::convert_to_hash(self.hashrate, self.hash_metric) as u64;
let p2pool_share_mean = PubP2poolApi::calculate_share_or_block_time(hashrate, api.p2pool_difficulty_u64);
let solo_block_mean = PubP2poolApi::calculate_share_or_block_time(hashrate, api.monero_difficulty_u64);
ui.add_sized([width, text], Label::new(RichText::new("Manually Inputted Hashrate").underline().color(BONE)));
ui.add_sized([width, text], Label::new(format!("{} H/s", HumanNumber::from_u64(hashrate))));
ui.add_sized([width, text], Label::new(RichText::new("P2Pool Block Mean").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN);
ui.add_sized([width, text], Label::new(api.p2pool_block_mean.to_string()));
ui.add_sized([width, text], Label::new(RichText::new("Your P2Pool Share Mean").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN);
ui.add_sized([width, text], Label::new(p2pool_share_mean.to_string()));
ui.add_sized([width, text], Label::new(RichText::new("Your Solo Block Mean").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN);
ui.add_sized([width, text], Label::new(solo_block_mean.to_string()));
} else {
ui.add_sized([width, text], Label::new(RichText::new("Your P2Pool Hashrate").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_HASHRATE);
ui.add_sized([width, text], Label::new(format!("{} H/s", api.hashrate_1h)));
ui.add_sized([width, text], Label::new(RichText::new("P2Pool Block Mean").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_BLOCK_MEAN);
ui.add_sized([width, text], Label::new(api.p2pool_block_mean.to_string()));
ui.add_sized([width, text], Label::new(RichText::new("Your P2Pool Share Mean").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_SHARE_MEAN);
ui.add_sized([width, text], Label::new(api.p2pool_share_mean.to_string()));
ui.add_sized([width, text], Label::new(RichText::new("Your Solo Block Mean").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_SOLO_BLOCK_MEAN);
ui.add_sized([width, text], Label::new(api.solo_block_mean.to_string()));
}
})});
ui.group(|ui| { ui.vertical(|ui| {
ui.set_min_height(min_height);
if self.manual_hash {
let hashrate = Hash::convert_to_hash(self.hashrate, self.hash_metric) as u64;
let user_p2pool_percent = PubP2poolApi::calculate_dominance(hashrate, api.p2pool_hashrate_u64);
let user_monero_percent = PubP2poolApi::calculate_dominance(hashrate, api.monero_hashrate_u64);
ui.add_sized([width, text], Label::new(RichText::new("P2Pool Miners").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_MINERS);
ui.add_sized([width, text], Label::new(api.miners.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("P2Pool Dominance").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_DOMINANCE);
ui.add_sized([width, text], Label::new(api.p2pool_percent.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("Your P2Pool Dominance").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE);
ui.add_sized([width, text], Label::new(user_p2pool_percent.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("Your Monero Dominance").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_YOUR_MONERO_DOMINANCE);
ui.add_sized([width, text], Label::new(user_monero_percent.as_str()));
} else {
ui.add_sized([width, text], Label::new(RichText::new("P2Pool Miners").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_MINERS);
ui.add_sized([width, text], Label::new(api.miners.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("P2Pool Dominance").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_P2POOL_DOMINANCE);
ui.add_sized([width, text], Label::new(api.p2pool_percent.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("Your P2Pool Dominance").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_YOUR_P2POOL_DOMINANCE);
ui.add_sized([width, text], Label::new(api.user_p2pool_percent.as_str()));
ui.add_sized([width, text], Label::new(RichText::new("Your Monero Dominance").underline().color(BONE))).on_hover_text(STATUS_SUBMENU_YOUR_MONERO_DOMINANCE);
ui.add_sized([width, text], Label::new(api.user_monero_percent.as_str()));
}
})});
});
// Tick bar
ui.add_sized([ui.available_width(), text], Label::new(api.calculate_tick_bar())).on_hover_text(STATUS_SUBMENU_PROGRESS_BAR);
drop(api);
}
}
}

View file

@ -32,6 +32,7 @@ use crate::{
disk::Xmrig,
ProcessSignal,
constants::*,
macros::*,
};
use log::*;
@ -81,7 +82,7 @@ impl SudoState {
// Resets the state.
pub fn reset(state: &Arc<Mutex<Self>>) {
Self::wipe(state);
let mut state = state.lock().unwrap();
let mut state = lock!(state);
state.testing = false;
state.success = false;
// state.signal = ProcessSignal::None;
@ -92,7 +93,7 @@ impl SudoState {
pub fn wipe(state: &Arc<Mutex<Self>>) {
let mut new = String::with_capacity(256);
// new is now == old, and vice-versa.
std::mem::swap(&mut new, &mut state.lock().unwrap().pass);
std::mem::swap(&mut new, &mut lock!(state).pass);
// we're wiping & dropping the old pass here.
new.zeroize();
std::mem::drop(new);
@ -108,7 +109,7 @@ impl SudoState {
let path = path.clone();
thread::spawn(move || {
// Set to testing
state.lock().unwrap().testing = true;
lock!(state).testing = true;
// Make sure sudo timestamp is reset
let reset = Command::new("sudo")
@ -122,8 +123,8 @@ impl SudoState {
Err(e) => {
error!("Sudo | Couldn't reset timestamp: {}", e);
Self::wipe(&state);
state.lock().unwrap().msg = format!("Sudo error: {}", e);
state.lock().unwrap().testing = false;
lock!(state).msg = format!("Sudo error: {}", e);
lock!(state).testing = false;
return
},
}
@ -139,7 +140,7 @@ impl SudoState {
// Write pass to STDIN
let mut stdin = sudo.stdin.take().unwrap();
stdin.write_all(state.lock().unwrap().pass.as_bytes()).unwrap();
stdin.write_all(lock!(state).pass.as_bytes()).unwrap();
drop(stdin);
// Sudo re-prompts and will hang.
@ -149,7 +150,7 @@ impl SudoState {
match sudo.try_wait() {
Ok(Some(code)) => if code.success() {
info!("Sudo | Password ... OK!");
state.lock().unwrap().success = true;
lock!(state).success = true;
break
},
Ok(None) => {
@ -159,25 +160,25 @@ impl SudoState {
Err(e) => {
error!("Sudo | Couldn't reset timestamp: {}", e);
Self::wipe(&state);
state.lock().unwrap().msg = format!("Sudo error: {}", e);
state.lock().unwrap().testing = false;
lock!(state).msg = format!("Sudo error: {}", e);
lock!(state).testing = false;
return
},
}
}
if let Err(e) = sudo.kill() { warn!("Sudo | Kill error (it probably already exited): {}", e); }
if state.lock().unwrap().success {
match state.lock().unwrap().signal {
if lock!(state).success {
match lock!(state).signal {
ProcessSignal::Restart => crate::helper::Helper::restart_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
ProcessSignal::Stop => crate::helper::Helper::stop_xmrig(&helper),
_ => crate::helper::Helper::start_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
}
} else {
state.lock().unwrap().msg = "Incorrect password! (or sudo timeout)".to_string();
lock!(state).msg = "Incorrect password! (or sudo timeout)".to_string();
Self::wipe(&state);
}
state.lock().unwrap().signal = ProcessSignal::None;
state.lock().unwrap().testing = false;
lock!(state).signal = ProcessSignal::None;
lock!(state).testing = false;
});
}
}

View file

@ -33,6 +33,7 @@ use crate::{
update::Name::*,
ErrorState,ErrorFerris,ErrorButtons,
Restart,
macros::*,
};
use hyper::{
Client,Body,Request,
@ -265,9 +266,9 @@ impl Update {
path_p2pool: path_p2pool.display().to_string(),
path_xmrig: path_xmrig.display().to_string(),
tmp_dir: "".to_string(),
updating: Arc::new(Mutex::new(false)),
prog: Arc::new(Mutex::new(0.0)),
msg: Arc::new(Mutex::new(MSG_NONE.to_string())),
updating: arc_mut!(false),
prog: arc_mut!(0.0),
msg: arc_mut!(MSG_NONE.to_string()),
tor,
}
}
@ -392,13 +393,13 @@ impl Update {
return;
}
update.lock().unwrap().path_p2pool = p2pool_path.display().to_string();
update.lock().unwrap().path_xmrig = xmrig_path.display().to_string();
update.lock().unwrap().tor = gupax.update_via_tor;
lock!(update).path_p2pool = p2pool_path.display().to_string();
lock!(update).path_xmrig = xmrig_path.display().to_string();
lock!(update).tor = gupax.update_via_tor;
// Clone before thread spawn
let og = Arc::clone(og);
let state_ver = Arc::clone(&og.lock().unwrap().version);
let state_ver = Arc::clone(&lock!(og).version);
let state_path = state_path.to_path_buf();
let update = Arc::clone(update);
let restart = Arc::clone(restart);
@ -407,23 +408,23 @@ impl Update {
match Update::start(update.clone(), og.clone(), state_ver.clone(), restart) {
Ok(_) => {
info!("Update | Saving state...");
let original_version = og.lock().unwrap().version.clone();
og.lock().unwrap().version = state_ver;
match State::save(&mut og.lock().unwrap(), &state_path) {
let original_version = lock!(og).version.clone();
lock!(og).version = state_ver;
match State::save(&mut lock!(og), &state_path) {
Ok(_) => info!("Update ... OK"),
Err(e) => {
warn!("Update | Saving state ... FAIL: {}", e);
og.lock().unwrap().version = original_version;
*update.lock().unwrap().msg.lock().unwrap() = "Saving new versions into state failed".to_string();
lock!(og).version = original_version;
*lock2!(update,msg) = "Saving new versions into state failed".to_string();
},
};
}
Err(e) => {
info!("Update ... FAIL: {}", e);
*update.lock().unwrap().msg.lock().unwrap() = format!("{} | {}", MSG_FAILED, e);
*lock2!(update,msg) = format!("{} | {}", MSG_FAILED, e);
},
};
*update.lock().unwrap().updating.lock().unwrap() = false;
*lock2!(update,updating) = false;
});
}
@ -442,19 +443,19 @@ impl Update {
return Err(anyhow!("This is the [Linux distro] version of Gupax, updates are disabled"));
//---------------------------------------------------------------------------------------------------- Init
*update.lock().unwrap().updating.lock().unwrap() = true;
*lock2!(update,updating) = true;
// Set timer
let now = std::time::Instant::now();
// Set progress bar
*update.lock().unwrap().msg.lock().unwrap() = MSG_START.to_string();
*update.lock().unwrap().prog.lock().unwrap() = 0.0;
*lock2!(update,msg) = MSG_START.to_string();
*lock2!(update,prog) = 0.0;
info!("Update | {}", INIT);
// Get temporary directory
let msg = MSG_TMP.to_string();
info!("Update | {}", msg);
*update.lock().unwrap().msg.lock().unwrap() = msg;
*lock2!(update,msg) = msg;
let tmp_dir = Self::get_tmp_dir()?;
std::fs::create_dir(&tmp_dir)?;
@ -467,27 +468,27 @@ impl Update {
// Generate fake user-agent
let user_agent = Pkg::get_user_agent();
*update.lock().unwrap().prog.lock().unwrap() = 5.0;
*lock2!(update,prog) = 5.0;
// Create Tor/HTTPS client
let lock = update.lock().unwrap();
let lock = lock!(update);
let tor = lock.tor;
if tor {
let msg = MSG_TOR.to_string();
info!("Update | {}", msg);
*lock.msg.lock().unwrap() = msg;
*lock!(lock.msg) = msg;
} else {
let msg = MSG_HTTPS.to_string();
info!("Update | {}", msg);
*lock.msg.lock().unwrap() = msg;
*lock!(lock.msg) = msg;
}
drop(lock);
let mut client = Self::get_client(tor)?;
*update.lock().unwrap().prog.lock().unwrap() += 5.0;
info!("Update | Init ... OK ... {}%", update.lock().unwrap().prog.lock().unwrap());
*lock2!(update,prog) += 5.0;
info!("Update | Init ... OK ... {}%", lock2!(update,prog));
//---------------------------------------------------------------------------------------------------- Metadata
*update.lock().unwrap().msg.lock().unwrap() = MSG_METADATA.to_string();
*lock2!(update,msg) = MSG_METADATA.to_string();
info!("Update | {}", METADATA);
let mut vec2 = vec![];
// Loop process:
@ -502,7 +503,7 @@ impl Update {
// function itself but for some reason, it was getting skipped over,
// so the [new_ver] check is now here, in the outer scope.
for i in 1..=3 {
if i > 1 { *update.lock().unwrap().msg.lock().unwrap() = format!("{} [{}/3]", MSG_METADATA_RETRY, i); }
if i > 1 { *lock2!(update,msg) = format!("{} [{}/3]", MSG_METADATA_RETRY, i); }
let mut handles: Vec<JoinHandle<Result<(), anyhow::Error>>> = vec![];
for pkg in vec.iter() {
// Clone data before sending to async
@ -528,13 +529,13 @@ impl Update {
// Check for empty version
let mut indexes = vec![];
for (index, pkg) in vec.iter().enumerate() {
if pkg.new_ver.lock().unwrap().is_empty() {
if lock!(pkg.new_ver).is_empty() {
warn!("Update | {} failed, attempt [{}/3]...", pkg.name, i+1);
} else {
indexes.push(index);
vec2.push(pkg.clone());
*update.lock().unwrap().prog.lock().unwrap() += 10.0;
info!("Update | {} {} ... OK", pkg.name, pkg.new_ver.lock().unwrap());
*lock2!(update,prog) += 10.0;
info!("Update | {} {} ... OK", pkg.name, lock!(pkg.new_ver));
}
}
// Order indexes from biggest to smallest
@ -553,19 +554,19 @@ impl Update {
}
}
if vec.is_empty() {
info!("Update | Metadata ... OK ... {}%", update.lock().unwrap().prog.lock().unwrap());
info!("Update | Metadata ... OK ... {}%", lock2!(update,prog));
} else {
error!("Update | Metadata ... FAIL");
return Err(anyhow!("Metadata fetch failed"))
}
//---------------------------------------------------------------------------------------------------- Compare
*update.lock().unwrap().msg.lock().unwrap() = MSG_COMPARE.to_string();
*lock2!(update,msg) = MSG_COMPARE.to_string();
info!("Update | {}", COMPARE);
let mut vec3 = vec![];
let mut new_pkgs = vec![];
for pkg in vec2.iter() {
let new_ver = pkg.new_ver.lock().unwrap().clone();
let new_ver = lock!(pkg.new_ver).clone();
let diff;
let old_ver;
let name;
@ -575,17 +576,17 @@ impl Update {
// that gets updated during an update. This prevents the updater always thinking
// there's a new Gupax update since the user didnt restart and is still technically
// using the old version (even though the underlying binary was updated).
old_ver = state_ver.lock().unwrap().gupax.clone();
old_ver = lock!(state_ver).gupax.clone();
diff = old_ver != new_ver && GUPAX_VERSION != new_ver;
name = "Gupax";
}
P2pool => {
old_ver = state_ver.lock().unwrap().p2pool.clone();
old_ver = lock!(state_ver).p2pool.clone();
diff = old_ver != new_ver;
name = "P2Pool";
}
Xmrig => {
old_ver = state_ver.lock().unwrap().xmrig.clone();
old_ver = lock!(state_ver).xmrig.clone();
diff = old_ver != new_ver;
name = "XMRig";
}
@ -598,32 +599,32 @@ impl Update {
info!("Update | {} {} == {} ... SKIPPING", pkg.name, old_ver, new_ver);
}
}
*update.lock().unwrap().prog.lock().unwrap() += 5.0;
info!("Update | Compare ... OK ... {}%", update.lock().unwrap().prog.lock().unwrap());
*lock2!(update,prog) += 5.0;
info!("Update | Compare ... OK ... {}%", lock2!(update,prog));
// Return if 0 (all packages up-to-date)
// Get amount of packages to divide up the percentage increases
let pkg_amount = vec3.len() as f32;
if pkg_amount == 0.0 {
info!("Update | All packages up-to-date ... RETURNING");
*update.lock().unwrap().prog.lock().unwrap() = 100.0;
*update.lock().unwrap().msg.lock().unwrap() = MSG_UP_TO_DATE.to_string();
*lock2!(update,prog) = 100.0;
*lock2!(update,msg) = MSG_UP_TO_DATE.to_string();
return Ok(())
}
let new_pkgs: String = new_pkgs.concat();
//---------------------------------------------------------------------------------------------------- Download
*update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_DOWNLOAD, new_pkgs);
*lock2!(update,msg) = format!("{}{}", MSG_DOWNLOAD, new_pkgs);
info!("Update | {}", DOWNLOAD);
let mut vec4 = vec![];
for i in 1..=3 {
if i > 1 { *update.lock().unwrap().msg.lock().unwrap() = format!("{} [{}/3]{}", MSG_DOWNLOAD_RETRY, i, new_pkgs); }
if i > 1 { *lock2!(update,msg) = format!("{} [{}/3]{}", MSG_DOWNLOAD_RETRY, i, new_pkgs); }
let mut handles: Vec<JoinHandle<Result<(), anyhow::Error>>> = vec![];
for pkg in vec3.iter() {
// Clone data before async
let bytes = Arc::clone(&pkg.bytes);
let client = client.clone();
let version = pkg.new_ver.lock().unwrap();
let version = lock!(pkg.new_ver);
// Download link = PREFIX + Version (found at runtime) + SUFFIX + Version + EXT
// Example: https://github.com/hinto-janaiyo/gupax/releases/download/v0.0.1/gupax-v0.0.1-linux-x64-standalone
// XMRig doesn't have a [v], so slice it out
@ -647,12 +648,12 @@ impl Update {
// Check for empty bytes
let mut indexes = vec![];
for (index, pkg) in vec3.iter().enumerate() {
if pkg.bytes.lock().unwrap().is_empty() {
if lock!(pkg.bytes).is_empty() {
warn!("Update | {} failed, attempt [{}/3]...", pkg.name, i);
} else {
indexes.push(index);
vec4.push(pkg.clone());
*update.lock().unwrap().prog.lock().unwrap() += (30.0 / pkg_amount).round();
*lock2!(update,prog) += (30.0 / pkg_amount).round();
info!("Update | {} ... OK", pkg.name);
}
}
@ -666,14 +667,14 @@ impl Update {
if vec3.is_empty() { break }
}
if vec3.is_empty() {
info!("Update | Download ... OK ... {}%", *update.lock().unwrap().prog.lock().unwrap());
info!("Update | Download ... OK ... {}%", *lock2!(update,prog));
} else {
error!("Update | Download ... FAIL");
return Err(anyhow!("Download failed"))
}
//---------------------------------------------------------------------------------------------------- Extract
*update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_EXTRACT, new_pkgs);
*lock2!(update,msg) = format!("{}{}", MSG_EXTRACT, new_pkgs);
info!("Update | {}", EXTRACT);
for pkg in vec4.iter() {
let tmp = match pkg.name {
@ -681,20 +682,20 @@ impl Update {
_ => tmp_dir.to_owned() + &pkg.name.to_string(),
};
#[cfg(target_os = "windows")]
ZipArchive::extract(&mut ZipArchive::new(std::io::Cursor::new(pkg.bytes.lock().unwrap().as_ref()))?, tmp)?;
ZipArchive::extract(&mut ZipArchive::new(std::io::Cursor::new(lock!(pkg.bytes).as_ref()))?, tmp)?;
#[cfg(target_family = "unix")]
tar::Archive::new(flate2::read::GzDecoder::new(pkg.bytes.lock().unwrap().as_ref())).unpack(tmp)?;
*update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round();
tar::Archive::new(flate2::read::GzDecoder::new(lock!(pkg.bytes).as_ref())).unpack(tmp)?;
*lock2!(update,prog) += (5.0 / pkg_amount).round();
info!("Update | {} ... OK", pkg.name);
}
info!("Update | Extract ... OK ... {}%", *update.lock().unwrap().prog.lock().unwrap());
info!("Update | Extract ... OK ... {}%", *lock2!(update,prog));
//---------------------------------------------------------------------------------------------------- Upgrade
// 1. Walk directories
// 2. If basename matches known binary name, start
// 3. Rename tmp path into current path
// 4. Update [State/Version]
*update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_UPGRADE, new_pkgs);
*lock2!(update,msg) = format!("{}{}", MSG_UPGRADE, new_pkgs);
info!("Update | {}", UPGRADE);
// If this bool doesn't get set, something has gone wrong because
// we _didn't_ find a binary even though we downloaded it.
@ -715,9 +716,9 @@ impl Update {
_ => Xmrig,
};
let path = match name {
Gupax => update.lock().unwrap().path_gupax.clone(),
P2pool => update.lock().unwrap().path_p2pool.clone(),
Xmrig => update.lock().unwrap().path_xmrig.clone(),
Gupax => lock!(update).path_gupax.clone(),
P2pool => lock!(update).path_p2pool.clone(),
Xmrig => lock!(update).path_xmrig.clone(),
};
let path = Path::new(&path);
// Unix can replace running binaries no problem (they're loaded into memory)
@ -745,14 +746,14 @@ impl Update {
// Update [State] version
match name {
Gupax => {
state_ver.lock().unwrap().gupax = Pkg::get_new_pkg_version(Gupax, &vec4)?;
lock!(state_ver).gupax = Pkg::get_new_pkg_version(Gupax, &vec4)?;
// If we're updating Gupax, set the [Restart] state so that the user knows to restart
*restart.lock().unwrap() = Restart::Yes;
*lock!(restart) = Restart::Yes;
},
P2pool => state_ver.lock().unwrap().p2pool = Pkg::get_new_pkg_version(P2pool, &vec4)?,
Xmrig => state_ver.lock().unwrap().xmrig = Pkg::get_new_pkg_version(Xmrig, &vec4)?,
P2pool => lock!(state_ver).p2pool = Pkg::get_new_pkg_version(P2pool, &vec4)?,
Xmrig => lock!(state_ver).xmrig = Pkg::get_new_pkg_version(Xmrig, &vec4)?,
};
*update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round();
*lock2!(update,prog) += (5.0 / pkg_amount).round();
},
_ => (),
}
@ -768,11 +769,11 @@ impl Update {
let seconds = now.elapsed().as_secs();
info!("Update | Seconds elapsed ... [{}s]", seconds);
match seconds {
0 => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took 0 seconds... What...?!{}", MSG_SUCCESS, new_pkgs),
1 => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took 1 second... Wow!{}", MSG_SUCCESS, new_pkgs),
_ => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took {} seconds.{}", MSG_SUCCESS, seconds, new_pkgs),
0 => *lock2!(update,msg) = format!("{}! Took 0 seconds... What...?!{}", MSG_SUCCESS, new_pkgs),
1 => *lock2!(update,msg) = format!("{}! Took 1 second... Wow!{}", MSG_SUCCESS, new_pkgs),
_ => *lock2!(update,msg) = format!("{}! Took {} seconds.{}", MSG_SUCCESS, seconds, new_pkgs),
}
*update.lock().unwrap().prog.lock().unwrap() = 100.0;
*lock2!(update,prog) = 100.0;
Ok(())
}
}
@ -823,8 +824,8 @@ impl Pkg {
link_prefix,
link_suffix,
link_extension,
bytes: Arc::new(Mutex::new(bytes::Bytes::new())),
new_ver: Arc::new(Mutex::new(String::new())),
bytes: arc_mut!(bytes::Bytes::new()),
new_ver: arc_mut!(String::new()),
}
}
@ -855,7 +856,7 @@ impl Pkg {
let mut response = client.request(request).await?;
let body = hyper::body::to_bytes(response.body_mut()).await?;
let body: TagName = serde_json::from_slice(&body)?;
*new_ver.lock().unwrap() = body.tag_name;
*lock!(new_ver) = body.tag_name;
Ok(())
}
@ -873,7 +874,7 @@ impl Pkg {
response = client.request(request).await?;
}
let body = hyper::body::to_bytes(response.into_body()).await?;
*bytes.lock().unwrap() = body;
*lock!(bytes) = body;
Ok(())
}
@ -882,7 +883,7 @@ impl Pkg {
fn get_new_pkg_version(name: Name, vec: &[&Pkg]) -> Result<String, Error> {
for pkg in vec.iter() {
if pkg.name == name {
return Ok(pkg.new_ver.lock().unwrap().to_string())
return Ok(lock!(pkg.new_ver).to_string())
}
}
Err(anyhow!("Couldn't find new_pkg_version"))

411
src/xmr.rs Normal file
View file

@ -0,0 +1,411 @@
// 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 file is for handling actual XMR integers/floats using [AtomicUnit] & [PayoutOrd]
// AtomicUnit is just a wrapper around a [u64] implementing common XMR Atomic Unit functions.
// PayoutOrd is a wrapper around a [Vec] for sorting P2Pool payouts with this type signature:
// "Vec<(String, AtomicUnit, HumanNumber)>"
// These represent:
// "(DATE, ATOMIC_UNIT, MONERO_BLOCK)"
use crate::{
human::*,
P2poolRegex,
};
use log::*;
//---------------------------------------------------------------------------------------------------- XMR AtomicUnit
// After I initially wrote this struct, I forgot why I even needed it.
// I get the XMR received as a float, I display it as a float and it wouldn't be
// too bad if I wrote it to disk as a float, but then I realized [.cmp()] doesn't
// work on [f64] and also that Rust makes sorting floats a pain so instead of deleting
// this code and making some float sorter, I might as well use it.
// [u64] can hold max: 18_446_744_073_709_551_615 which equals to 18,446,744,073 XMR (18 billion).
// Given the constant XMR tail emission of (0.3 per minute|18 per hour|432 per day|157,680 per year)
// this would take: 116,976~ years to overflow.
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
pub struct AtomicUnit(u64);
impl AtomicUnit {
pub const fn new() -> Self {
Self(0)
}
pub const fn from_u64(u: u64) -> Self {
Self(u)
}
pub const fn add_u64(self, u: u64) -> Self {
Self(self.0 + u)
}
pub const fn add_self(self, atomic_unit: Self) -> Self {
Self(self.0 + atomic_unit.0)
}
pub const fn to_u64(self) -> u64 {
self.0
}
pub fn to_string(self) -> String {
self.0.to_string()
}
pub fn sum_vec(vec: &Vec<Self>) -> Self {
let mut sum = 0;
for int in vec {
sum += int.0;
}
Self(sum)
}
pub fn from_f64(f: f64) -> Self {
Self((f * 1_000_000_000_000.0) as u64)
}
pub fn to_f64(&self) -> f64 {
self.0 as f64 / 1_000_000_000_000.0
}
pub fn to_human_number_12_point(&self) -> HumanNumber {
let f = self.0 as f64 / 1_000_000_000_000.0;
HumanNumber::from_f64_12_point(f)
}
pub fn to_human_number_no_fmt(&self) -> HumanNumber {
let f = self.0 as f64 / 1_000_000_000_000.0;
HumanNumber::from_f64_no_fmt(f)
}
}
// Displays AtomicUnit as a real XMR floating point.
impl std::fmt::Display for AtomicUnit {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", Self::to_human_number_12_point(self))
}
}
//---------------------------------------------------------------------------------------------------- [PayoutOrd]
// This is the struct for ordering P2Pool payout lines into a structured and ordered vector of elements.
// The structure goes as follows:
// "Vec<(String, AtomicUnit, HumanNumber)>"
// Which displays as:
// "2022-08-17 12:16:11.8662" | 0.002382256231 XMR | Block 2573821
//
// [0] = DATE
// [1] = XMR IN ATOMIC-UNITS
// [2] = MONERO BLOCK
#[derive(Debug,Clone)]
pub struct PayoutOrd(Vec<(String, AtomicUnit, HumanNumber)>);
impl PayoutOrd {
pub fn new() -> Self {
Self(vec![(String::from("????-??-?? ??:??:??.????"), AtomicUnit::new(), HumanNumber::unknown())])
}
pub const fn from_vec(vec: Vec<(String, AtomicUnit, HumanNumber)>) -> Self {
Self(vec)
}
pub fn is_same(a: &Self, b: &Self) -> bool {
if a.0.is_empty() && b.0.is_empty() { return true }
if a.0.len() != b.0.len() { return false }
let mut n = 0;
for (date, atomic_unit, block) in &a.0 {
if *date != b.0[n].0 { return false }
if *atomic_unit != b.0[n].1 { return false }
if *block != b.0[n].2 { return false }
n += 1;
}
true
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
// Expected input: "NOTICE 2022-01-27 01:30:23.1377 P2Pool You received a payout of 0.000000000001 XMR in block 2642816"
pub fn parse_raw_payout_line(line: &str, regex: &P2poolRegex) -> (String, AtomicUnit, HumanNumber) {
// Date
let date = match regex.date.find(line) {
Some(date) => date.as_str().to_string(),
None => { error!("P2Pool | Date parse error: [{}]", line); "????-??-?? ??:??:??.????".to_string() },
};
// AtomicUnit
let atomic_unit = if let Some(word) = regex.payout.find(line) {
if let Some(word) = regex.payout_float.find(word.as_str()) {
match word.as_str().parse::<f64>() {
Ok(au) => AtomicUnit::from_f64(au),
Err(e) => { error!("P2Pool | AtomicUnit parse error: [{}] on [{}]", e, line); AtomicUnit::new() },
}
} else {
error!("P2Pool | AtomicUnit parse error: [{}]", line);
AtomicUnit::new()
}
} else {
error!("P2Pool | AtomicUnit parse error: [{}]", line);
AtomicUnit::new()
};
// Block
let block = if let Some(word) = regex.block.find(line) {
if let Some(word) = regex.block_int.find(word.as_str()) {
match word.as_str().parse::<u64>() {
Ok(b) => HumanNumber::from_u64(b),
Err(e) => { error!("P2Pool | Block parse error: [{}] on [{}]", e, line); HumanNumber::unknown() },
}
} else {
error!("P2Pool | Block parse error: [{}]", line);
HumanNumber::unknown()
}
} else {
error!("P2Pool | Block parse error: [{}]", line);
HumanNumber::unknown()
};
(date, atomic_unit, block)
}
// Expected input: "2022-01-27 01:30:23.1377 | 0.000000000001 XMR | Block 2,642,816"
pub fn parse_formatted_payout_line(line: &str, regex: &P2poolRegex) -> (String, AtomicUnit, HumanNumber) {
// Date
let date = match regex.date.find(line) {
Some(date) => date.as_str().to_string(),
None => { error!("P2Pool | Date parse error: [{}]", line); "????-??-?? ??:??:??.????".to_string() },
};
// AtomicUnit
let atomic_unit = if let Some(word) = regex.payout_float.find(line) {
match word.as_str().parse::<f64>() {
Ok(au) => AtomicUnit::from_f64(au),
Err(e) => { error!("P2Pool | AtomicUnit parse error: [{}] on [{}]", e, line); AtomicUnit::new() },
}
} else {
error!("P2Pool | AtomicUnit parse error: [{}]", line);
AtomicUnit::new()
};
// Block
let block = match regex.block_comma.find(line) {
Some(b) => HumanNumber::from_str(b.as_str()),
None => { error!("P2Pool | Block parse error: [{}]", line); HumanNumber::unknown() },
};
(date, atomic_unit, block)
}
// Takes in input of ONLY P2Pool payout logs and converts it into a usable [PayoutOrd]
// It expects formatted log lines like this: "2022-04-11 00:20:17.2571 | 0.001371623621 XMR | Block 2,562,511"
// For efficiency reasons, I'd like to know the byte size
// we should allocate for the vector so we aren't adding every loop.
// Given a log [str], the equation for how many bytes the final vec will be is:
// (BYTES_OF_DATE + BYTES OF XMR + BYTES OF BLOCK) + (SPACES, PIPES, MISC WORDS) * amount_of_lines
// The first three are more or less constants (monero block 10m is in 10,379 years...): [23, 14, 7] (sum: 44)
// Spaces, pipes, commas and words (XMR, Block): [19]
// Add 7 more bytes for wrapper type overhead and it's an even [70] bytes per line.
pub fn update_from_payout_log(&mut self, log: &str) {
let regex = P2poolRegex::new();
let amount_of_lines = log.lines().count();
let mut vec: Vec<(String, AtomicUnit, HumanNumber)> = Vec::with_capacity(70 * amount_of_lines);
for line in log.lines() {
debug!("PayoutOrg | Parsing line: [{}]", line);
vec.push(Self::parse_formatted_payout_line(line, &regex));
}
*self = Self(vec);
}
// Takes the wrapper types, and pushes to existing [Self]
pub fn push(&mut self, date: String, atomic_unit: AtomicUnit, block: HumanNumber) {
self.0.push((date, atomic_unit, block));
}
// Takes the raw components (no wrapper types), convert them and pushes to existing [Self]
pub fn push_raw(&mut self, date: &str, atomic_unit: u64, block: u64) {
let atomic_unit = AtomicUnit(atomic_unit);
let block = HumanNumber::from_u64(block);
self.0.push((date.to_string(), atomic_unit, block));
}
pub fn atomic_unit_sum(&self) -> AtomicUnit {
let mut sum: u64 = 0;
for (_, atomic_unit, _) in &self.0 {
sum += atomic_unit.to_u64();
}
AtomicUnit::from_u64(sum)
}
// Sort [Self] from highest payout to lowest
pub fn sort_payout_high_to_low(&mut self) {
// This is a little confusing because wrapper types are basically 1 element tuples so:
// self.0 = The [Vec] within [PayoutOrd]
// b.1.0 = [b] is [(String, AtomicUnit, HumanNumber)], [.1] is the [AtomicUnit] inside it, [.0] is the [u64] inside that
// a.1.0 = Same deal, but we compare it with the previous value (b)
self.0.sort_by(|a, b| b.1.0.cmp(&a.1.0));
}
// These sorting functions take around [0.0035~] seconds on a Ryzen 5950x
// given a Vec filled with 1_000_000 elements, not bad.
pub fn sort_payout_low_to_high(&mut self) {
self.0.sort_by(|a, b| a.1.0.cmp(&b.1.0));
}
// Returns a reversed [Iter] of the [PayoutOrd]
// This is obviously faster than actually reordering the Vec.
pub fn rev_iter(&self) -> std::iter::Rev<std::slice::Iter<'_, (String, AtomicUnit, HumanNumber)>> {
self.0.iter().rev()
}
// Recent <-> Oldest relies on the line order.
// The raw log lines will be shown instead of this struct.
}
impl Default for PayoutOrd { fn default() -> Self { Self::new() } }
impl std::fmt::Display for PayoutOrd {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
for i in &self.0 {
writeln!(f, "{} | {} XMR | Block {}", i.0, i.1, i.2)?;
}
Ok(())
}
}
//---------------------------------------------------------------------------------------------------- TESTS
#[cfg(test)]
mod test {
#[test]
fn update_p2pool_payout_log() {
use crate::xmr::PayoutOrd;
let log =
r#"2021-12-21 01:01:01.1111 | 0.001000000000 XMR | Block 1,234,567
2021-12-21 02:01:01.1111 | 0.002000000000 XMR | Block 2,345,678
2021-12-21 03:01:01.1111 | 0.003000000000 XMR | Block 3,456,789
"#;
let mut payout_ord = PayoutOrd::new();
println!("BEFORE: {}", payout_ord);
PayoutOrd::update_from_payout_log(&mut payout_ord, log);
println!("AFTER: {}", payout_ord);
assert_eq!(payout_ord.to_string(), log);
}
#[test]
fn push_to_payout_ord() {
use crate::xmr::PayoutOrd;
use crate::xmr::AtomicUnit;
use crate::human::HumanNumber;
let mut payout_ord = PayoutOrd::from_vec(vec![]);
let should_be = "2022-09-08 18:42:55.4636 | 0.000000000001 XMR | Block 2,654,321\n";
println!("BEFORE: {:#?}", payout_ord);
payout_ord.push_raw("2022-09-08 18:42:55.4636", 1, 2654321);
println!("AFTER: {}", payout_ord);
println!("SHOULD_BE: {}", should_be);
assert_eq!(payout_ord.to_string(), should_be);
}
#[test]
fn sum_payout_ord_atomic_unit() {
use crate::xmr::PayoutOrd;
use crate::xmr::AtomicUnit;
use crate::human::HumanNumber;
let mut payout_ord = PayoutOrd::from_vec(vec![
("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u64(1), HumanNumber::from_u64(2654321)),
("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(1), HumanNumber::from_u64(2654322)),
("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(1), HumanNumber::from_u64(2654323)),
]);
println!("OG: {:#?}", payout_ord);
let sum = PayoutOrd::atomic_unit_sum(&payout_ord);
println!("SUM: {}", sum.to_u64());
assert_eq!(sum.to_u64(), 3);
}
#[test]
fn sort_p2pool_payout_ord() {
use crate::xmr::PayoutOrd;
use crate::xmr::AtomicUnit;
use crate::human::HumanNumber;
let mut payout_ord = PayoutOrd::from_vec(vec![
("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u64(1000000000), HumanNumber::from_u64(2654321)),
("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(2000000000), HumanNumber::from_u64(2654322)),
("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(3000000000), HumanNumber::from_u64(2654323)),
]);
println!("OG: {:#?}", payout_ord);
// High to Low
PayoutOrd::sort_payout_high_to_low(&mut payout_ord);
println!("AFTER PAYOUT HIGH TO LOW: {:#?}", payout_ord);
let should_be =
r#"2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323
2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322
2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321
"#;
println!("SHOULD_BE:\n{}", should_be);
println!("IS:\n{}", payout_ord);
assert_eq!(payout_ord.to_string(), should_be);
// Low to High
PayoutOrd::sort_payout_low_to_high(&mut payout_ord);
println!("AFTER PAYOUT LOW TO HIGH: {:#?}", payout_ord);
let should_be =
r#"2022-09-08 18:42:55.4636 | 0.001000000000 XMR | Block 2,654,321
2022-09-09 16:18:26.7582 | 0.002000000000 XMR | Block 2,654,322
2022-09-10 11:15:21.1272 | 0.003000000000 XMR | Block 2,654,323
"#;
println!("SHOULD_BE:\n{}", should_be);
println!("IS:\n{}", payout_ord);
assert_eq!(payout_ord.to_string(), should_be);
}
#[test]
fn payout_ord_is_same() {
use crate::xmr::PayoutOrd;
use crate::xmr::AtomicUnit;
use crate::human::HumanNumber;
let mut payout_ord = PayoutOrd::from_vec(vec![
("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u64(1000000000), HumanNumber::from_u64(2654321)),
("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(2000000000), HumanNumber::from_u64(2654322)),
("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(3000000000), HumanNumber::from_u64(2654323)),
]);
let payout_ord_2 = payout_ord.clone();
println!("1: {:#?}", payout_ord);
println!("2: {:#?}", payout_ord);
assert!(PayoutOrd::is_same(&payout_ord, &payout_ord_2) == true);
payout_ord.push_raw("2022-09-08 18:42:55.4636", 1000000000, 2654321);
println!("1: {:#?}", payout_ord);
println!("2: {:#?}", payout_ord);
assert!(PayoutOrd::is_same(&payout_ord, &payout_ord_2) == false);
}
#[test]
fn view_reverse_payout_ord() {
use crate::xmr::PayoutOrd;
use crate::xmr::AtomicUnit;
use crate::human::HumanNumber;
let mut payout_ord = PayoutOrd::from_vec(vec![
("2022-09-08 18:42:55.4636".to_string(), AtomicUnit::from_u64(1000000000), HumanNumber::from_u64(2654321)),
("2022-09-09 16:18:26.7582".to_string(), AtomicUnit::from_u64(2000000000), HumanNumber::from_u64(2654322)),
("2022-09-10 11:15:21.1272".to_string(), AtomicUnit::from_u64(3000000000), HumanNumber::from_u64(2654323)),
]);
println!("OG: {:#?}", payout_ord);
for (_, atomic_unit, _) in payout_ord.rev_iter() {
if atomic_unit.to_u64() == 3000000000 {
break
} else {
println!("expected: 3000000000, found: {}", atomic_unit);
panic!("not reversed");
}
}
}
}

View file

@ -21,6 +21,7 @@ use crate::{
disk::*,
Process,
PubXmrigApi,
macros::*,
};
use egui::{
TextEdit,SelectableLabel,ComboBox,Label,Button,RichText,Slider,Checkbox,
@ -32,7 +33,7 @@ use std::{
use regex::Regex;
use log::*;
impl Xmrig {
impl crate::disk::Xmrig {
pub fn show(&mut self, pool_vec: &mut Vec<(String, Pool)>, regex: &Regexes, process: &Arc<Mutex<Process>>, api: &Arc<Mutex<PubXmrigApi>>, buffer: &mut String, width: f32, height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) {
let text_edit = height / 25.0;
//---------------------------------------------------------------------------------------------------- [Simple] Console
@ -45,7 +46,7 @@ impl Xmrig {
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
egui::ScrollArea::vertical().stick_to_bottom(true).max_width(width).max_height(height).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
ui.add_sized([width, height], TextEdit::multiline(&mut api.lock().unwrap().output.as_str()));
ui.add_sized([width, height], TextEdit::multiline(&mut lock!(api).output.as_str()));
});
});
//---------------------------------------------------------------------------------------------------- [Advanced] Console
@ -56,7 +57,7 @@ impl Xmrig {
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
egui::ScrollArea::vertical().stick_to_bottom(true).max_width(width).max_height(height).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
ui.add_sized([width, height], TextEdit::multiline(&mut api.lock().unwrap().output.as_str()));
ui.add_sized([width, height], TextEdit::multiline(&mut lock!(api).output.as_str()));
});
});
ui.separator();
@ -65,7 +66,7 @@ impl Xmrig {
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
response.request_focus(); // Get focus back
let buffer = std::mem::take(buffer); // Take buffer
let mut process = process.lock().unwrap(); // Lock
let mut process = lock!(process); // Lock
if process.is_alive() { process.input.push(buffer); } // Push only if alive
}
}