mirror of
https://github.com/Cyrix126/gupaxx.git
synced 2024-12-22 22:59:27 +00:00
v1.1.0: Merge 'status' branch
This commit is contained in:
commit
1301d1a283
22 changed files with 2925 additions and 899 deletions
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -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
|
## Fixes
|
||||||
* **macOS:** Added warning (and solution) if `Gupax/P2Pool/XMRig` were quarantined by [`Gatekeeper`](https://support.apple.com/en-us/HT202491)
|
* **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
|
* **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
|
* Miscellaneous UI changes and fixes
|
||||||
|
|
||||||
## Bundled Versions
|
## 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)
|
* [`XMRig v6.18.1`](https://github.com/xmrig/xmrig/releases/tag/v6.18.1)
|
||||||
|
|
||||||
|
|
||||||
|
|
34
Cargo.lock
generated
34
Cargo.lock
generated
|
@ -449,9 +449,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "calloop"
|
name = "calloop"
|
||||||
version = "0.10.4"
|
version = "0.10.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19457a0da465234abd76134a5c2a910c14bd3c5558463e4396ab9a37a328e465"
|
checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"nix 0.25.1",
|
"nix 0.25.1",
|
||||||
|
@ -1798,7 +1798,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gupax"
|
name = "gupax"
|
||||||
version = "1.0.1"
|
version = "1.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arti-client",
|
"arti-client",
|
||||||
|
@ -1841,9 +1841,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.1.0"
|
version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad6a9459c9c30b177b925162351f97e7d967c7ea8bab3b8352805327daf45554"
|
checksum = "6c467d36af040b7b2681f5fddd27427f6da8d3d072f575a265e181d2f8e8d157"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crunchy",
|
"crunchy",
|
||||||
]
|
]
|
||||||
|
@ -2497,9 +2497,9 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.1"
|
version = "7.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
|
@ -2665,9 +2665,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.16.0"
|
version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
|
@ -3420,18 +3420,18 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.151"
|
version = "1.0.152"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
|
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.151"
|
version = "1.0.152"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
|
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3440,9 +3440,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_ignored"
|
name = "serde_ignored"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51212eb6171778353d78ef5860fdffe29ac17f03a69375d2dc14fbb5754d54a4"
|
checksum = "94eb4a4087ba8bdf14a9208ac44fddbf55c01a6195f7edfc511ddaff6cae45a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -3832,9 +3832,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.27.1"
|
version = "0.27.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccb297c0afb439440834b4bcf02c5c9da8ec2e808e70f36b0d8e815ff403bd24"
|
checksum = "17351d0e9eb8841897b14e9669378f3c69fb57779cc04f8ca9a9d512edfb2563"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gupax"
|
name = "gupax"
|
||||||
version = "1.0.1"
|
version = "1.1.0"
|
||||||
authors = ["hinto-janaiyo <hinto.janaiyo@protonmail.com>"]
|
authors = ["hinto-janaiyo <hinto.janaiyo@protonmail.com>"]
|
||||||
description = "GUI for P2Pool+XMRig"
|
description = "GUI for P2Pool+XMRig"
|
||||||
documentation = "https://github.com/hinto-janaiyo/gupax"
|
documentation = "https://github.com/hinto-janaiyo/gupax"
|
||||||
|
|
97
README.md
97
README.md
|
@ -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)
|
* [What is Monero/P2Pool/XMRig/Gupax?](#what-is-monero-p2pool-xmrig-and-gupax)
|
||||||
* [How-To](#How-To)
|
* [How-To](#How-To)
|
||||||
* [Simple](#Simple)
|
* [Simple](#Simple)
|
||||||
|
- [Status](#Status)
|
||||||
- [Gupax](#Gupax)
|
- [Gupax](#Gupax)
|
||||||
- [P2Pool](#P2Pool)
|
- [P2Pool](#P2Pool)
|
||||||
- [XMRig](#XMRig)
|
- [XMRig](#XMRig)
|
||||||
|
@ -20,6 +21,7 @@ Gupax is a (Windows|macOS|Linux) GUI for mining [**Monero**](https://github.com/
|
||||||
- [Logs](#Logs)
|
- [Logs](#Logs)
|
||||||
- [Disk](#Disk)
|
- [Disk](#Disk)
|
||||||
- [Swapping P2Pool/XMRig](#Swapping-P2PoolXMRig)
|
- [Swapping P2Pool/XMRig](#Swapping-P2PoolXMRig)
|
||||||
|
- [Status](#Status-1)
|
||||||
- [Gupax](#Gupax-1)
|
- [Gupax](#Gupax-1)
|
||||||
- [P2Pool](#P2Pool-1)
|
- [P2Pool](#P2Pool-1)
|
||||||
- [XMRig](#XMRig-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.
|
`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
|
### 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).
|
If `Check for updates` is pressed, Gupax will update your `Gupax/P2Pool/XMRig` (if needed) using the [GitHub API](#where-are-updates-downloaded-from).
|
||||||
|
|
||||||
|
@ -190,16 +207,18 @@ Gupax comes with some command line options:
|
||||||
```
|
```
|
||||||
USAGE: ./gupax [--flag]
|
USAGE: ./gupax [--flag]
|
||||||
|
|
||||||
--help Print this help message
|
--help Print this help message
|
||||||
--version Print version and build info
|
--version Print version and build info
|
||||||
--state Print Gupax state
|
--state Print Gupax state
|
||||||
--nodes Print the manual node list
|
--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
|
||||||
--reset-state Reset all Gupax state (your settings)
|
--no-startup Disable all auto-startup settings for this instance (auto-update, auto-ping, etc)
|
||||||
--reset-nodes Reset the manual node list in the [P2Pool] tab
|
--reset-state Reset all Gupax state (your settings)
|
||||||
--reset-pools Reset the manual pool list in the [XMRig] tab
|
--reset-nodes Reset the manual node list in the [P2Pool] tab
|
||||||
--reset-all Reset the state, the manual node list, and the manual pool list
|
--reset-pools Reset the manual pool list in the [XMRig] tab
|
||||||
--ferris Print an extremely cute crab
|
--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
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, Gupax has `auto-update` & `auto-ping` enabled. This can only be turned off in the GUI which causes a chicken-and-egg problem. To get around this, start Gupax with `--no-startup`. This will disable all `auto` features for that instance.
|
By default, Gupax has `auto-update` & `auto-ping` enabled. This can only be turned off in the GUI which causes a chicken-and-egg problem. To get around this, start Gupax with `--no-startup`. This will disable all `auto` features for that instance.
|
||||||
|
@ -207,7 +226,7 @@ By default, Gupax has `auto-update` & `auto-ping` enabled. This can only be turn
|
||||||
---
|
---
|
||||||
|
|
||||||
### Key Shortcuts
|
### 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).
|
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 |
|
| Escape | Quit screen |
|
||||||
| Up | Start/Restart |
|
| Up | Start/Restart |
|
||||||
| Down | Stop |
|
| Down | Stop |
|
||||||
| Z | Switch to Left Tab |
|
| Z | Left Tab |
|
||||||
| X | Switch to Right Tab |
|
| X | Right Tab |
|
||||||
|
| C | Left Submenu |
|
||||||
|
| V | Right Submenu |
|
||||||
| S | Save |
|
| S | Save |
|
||||||
| R | Reset |
|
| 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.
|
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 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`.
|
Fullscreen mode can also be entered by pressing `F11`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -278,6 +299,7 @@ The current files saved to disk:
|
||||||
* `state.toml` Gupax state/settings
|
* `state.toml` Gupax state/settings
|
||||||
* `node.toml` The manual node database used for P2Pool advanced
|
* `node.toml` The manual node database used for P2Pool advanced
|
||||||
* `pool.toml` The manual pool database used for XMRig 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
|
### Gupax
|
||||||
Along with the updater and settings mentioned in [Simple](#simple), `Gupax Advanced` allows you to change:
|
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
|
- 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.
|
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
|
cargo test
|
||||||
```
|
```
|
||||||
|
|
BIN
images/payouts.png
Normal file
BIN
images/payouts.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 281 KiB |
BIN
images/processes.png
Normal file
BIN
images/processes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 237 KiB |
|
@ -4,6 +4,7 @@
|
||||||
* [Bootstrap](#Bootstrap)
|
* [Bootstrap](#Bootstrap)
|
||||||
* [Scale](#Scale)
|
* [Scale](#Scale)
|
||||||
* [Naming Scheme](#naming-scheme)
|
* [Naming Scheme](#naming-scheme)
|
||||||
|
* [Mining Stat Reference](#mining-stat-reference)
|
||||||
* [Sudo](#Sudo)
|
* [Sudo](#Sudo)
|
||||||
* [Why does Gupax need to be Admin? (on Windows)](#why-does-gupax-need-to-be-admin-on-windows)
|
* [Why does Gupax need to be Admin? (on Windows)](#why-does-gupax-need-to-be-admin-on-windows)
|
||||||
- [The issue](#the-issue)
|
- [The issue](#the-issue)
|
||||||
|
@ -17,17 +18,20 @@
|
||||||
## Structure
|
## Structure
|
||||||
| File/Folder | Purpose |
|
| 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
|
| 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
|
| ferris.rs | Cute crab bytes
|
||||||
| gupax.rs | `Gupax` tab
|
| 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
|
| 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
|
| main.rs | The main `App` struct that holds all data + misc data/functions
|
||||||
| node.rs | Community node ping code for the `P2Pool` simple tab
|
| node.rs | Community node ping code for the `P2Pool` simple tab
|
||||||
| p2pool.rs | `P2Pool` tab
|
| p2pool.rs | `P2Pool` tab
|
||||||
|
| regex.rs | General regexes used in Gupax
|
||||||
| status.rs | `Status` tab
|
| status.rs | `Status` tab
|
||||||
| sudo.rs | Code for handling `sudo` escalation for XMRig on Unix
|
| sudo.rs | Code for handling `sudo` escalation for XMRig on Unix
|
||||||
| update.rs | Update code for the `Gupax` tab
|
| update.rs | Update code for the `Gupax` tab
|
||||||
|
| xmr.rs | Code for handling actual XMR, `AtomicUnit` & `PayoutOrd`
|
||||||
| xmrig.rs | `XMRig` tab
|
| xmrig.rs | `XMRig` tab
|
||||||
|
|
||||||
## Thread Model
|
## Thread Model
|
||||||
|
@ -124,6 +128,31 @@ Exceptions (there are always exceptions...):
|
||||||
- XMRig separates the hash and signature
|
- XMRig separates the hash and signature
|
||||||
- P2Pool hashes are in UPPERCASE
|
- 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
|
## Sudo
|
||||||
Unlike Windows, Unix (macOS/Linux) has a userland program that handles all the dirty details of privilege escalation: `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
|
### 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).
|
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).
|
||||||
|
|
||||||
|
|
258
src/constants.rs
258
src/constants.rs
|
@ -15,10 +15,10 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// 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 GUPAX_VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); // e.g: v1.0.0
|
||||||
pub const P2POOL_VERSION: &str = "v2.6";
|
pub const P2POOL_VERSION: &str = "v2.7";
|
||||||
pub const XMRIG_VERSION: &str = "v6.18.1";
|
pub const XMRIG_VERSION: &str = "v6.18.1";
|
||||||
pub const COMMIT: &str = include_str!("../.git/refs/heads/main");
|
pub const COMMIT: &str = include_str!("../.git/refs/heads/main");
|
||||||
// e.g: Gupax_v1_0_0
|
// e.g: Gupax_v1_0_0
|
||||||
// Would have been [Gupax_v1.0.0] but P2Pool truncates everything after [.]
|
// Would have been [Gupax_v1.0.0] but P2Pool truncates everything after [.]
|
||||||
pub const GUPAX_VERSION_UNDERSCORE: &str = concat!(
|
pub const GUPAX_VERSION_UNDERSCORE: &str = concat!(
|
||||||
|
@ -31,12 +31,12 @@ pub const GUPAX_VERSION_UNDERSCORE: &str = concat!(
|
||||||
);
|
);
|
||||||
|
|
||||||
// App frame resolution, [4:3] aspect ratio, [1.33:1]
|
// App frame resolution, [4:3] aspect ratio, [1.33:1]
|
||||||
pub const APP_MIN_WIDTH: f32 = 640.0;
|
pub const APP_MIN_WIDTH: f32 = 640.0;
|
||||||
pub const APP_MIN_HEIGHT: f32 = 480.0;
|
pub const APP_MIN_HEIGHT: f32 = 480.0;
|
||||||
pub const APP_MAX_WIDTH: f32 = 3840.0;
|
pub const APP_MAX_WIDTH: f32 = 3840.0;
|
||||||
pub const APP_MAX_HEIGHT: f32 = 2160.0;
|
pub const APP_MAX_HEIGHT: f32 = 2160.0;
|
||||||
// Default, 1280x960
|
// Default, 1280x960
|
||||||
pub const APP_DEFAULT_WIDTH: f32 = 1280.0;
|
pub const APP_DEFAULT_WIDTH: f32 = 1280.0;
|
||||||
pub const APP_DEFAULT_HEIGHT: f32 = 960.0;
|
pub const APP_DEFAULT_HEIGHT: f32 = 960.0;
|
||||||
|
|
||||||
// Constants specific for Linux distro packaging of Gupax
|
// Constants specific for Linux distro packaging of Gupax
|
||||||
|
@ -46,12 +46,11 @@ r#"This [Gupax] was compiled for use as a Linux distro package. Built-in updates
|
||||||
|
|
||||||
// Use macOS shaped icon for macOS
|
// Use macOS shaped icon for macOS
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon@2x.png");
|
pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon@2x.png");
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon.png");
|
pub const BYTES_ICON: &[u8] = include_bytes!("../images/icons/icon.png");
|
||||||
pub const BYTES_BANNER: &[u8] = include_bytes!("../images/banner.png");
|
pub const BYTES_BANNER: &[u8] = include_bytes!("../images/banner.png");
|
||||||
pub const HORIZONTAL: &str = "--------------------------------------------";
|
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 = "---------------------------------------------------------------------------------------------------------------------------";
|
pub const HORI_CONSOLE: &str = "---------------------------------------------------------------------------------------------------------------------------";
|
||||||
|
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
|
@ -63,17 +62,27 @@ r#"*---------------------------------------*
|
||||||
| Escape | Quit screen |
|
| Escape | Quit screen |
|
||||||
| Up | Start/Restart |
|
| Up | Start/Restart |
|
||||||
| Down | Stop |
|
| Down | Stop |
|
||||||
| Z | Switch to Left Tab |
|
| Z | Left Tab |
|
||||||
| X | Switch to Right Tab |
|
| X | Right Tab |
|
||||||
|
| C | Left Submenu |
|
||||||
|
| V | Right Submenu |
|
||||||
| S | Save |
|
| S | Save |
|
||||||
| R | Reset |
|
| R | Reset |
|
||||||
*---------------------------------------*"#;
|
*---------------------------------------*"#;
|
||||||
// P2Pool & XMRig default API stuff
|
// P2Pool & XMRig default API stuff
|
||||||
#[cfg(target_os = "windows")]
|
#[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")]
|
#[cfg(target_family = "unix")]
|
||||||
pub const P2POOL_API_PATH: &str = "local/stats";
|
pub const P2POOL_API_PATH_LOCAL: &str = "local/stats";
|
||||||
pub const XMRIG_API_URI: &str = "1/summary"; // The default relative URI of XMRig's API
|
#[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)
|
// Process state tooltips (online, offline, etc)
|
||||||
pub const P2POOL_ALIVE: &str = "P2Pool is online";
|
pub const P2POOL_ALIVE: &str = "P2Pool is online";
|
||||||
|
@ -93,31 +102,28 @@ pub const XMRIG_MIDDLE: &str = "XMRig is in the middle of (re)starting/stopping"
|
||||||
pub const SPACE: f32 = 10.0;
|
pub const SPACE: f32 = 10.0;
|
||||||
|
|
||||||
// Some colors
|
// Some colors
|
||||||
pub const RED: egui::Color32 = egui::Color32::from_rgb(230, 50, 50);
|
pub const RED: egui::Color32 = egui::Color32::from_rgb(230, 50, 50);
|
||||||
pub const GREEN: egui::Color32 = egui::Color32::from_rgb(100, 230, 100);
|
pub const GREEN: egui::Color32 = egui::Color32::from_rgb(100, 230, 100);
|
||||||
pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100);
|
pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100);
|
||||||
pub const BRIGHT_YELLOW: egui::Color32 = egui::Color32::from_rgb(250, 250, 100);
|
pub const BRIGHT_YELLOW: egui::Color32 = egui::Color32::from_rgb(250, 250, 100);
|
||||||
pub const BONE: egui::Color32 = egui::Color32::from_rgb(190, 190, 190); // In between LIGHT_GRAY <-> GRAY
|
pub const BONE: egui::Color32 = egui::Color32::from_rgb(190, 190, 190); // In between LIGHT_GRAY <-> GRAY
|
||||||
pub const WHITE: egui::Color32 = egui::Color32::WHITE;
|
pub const WHITE: egui::Color32 = egui::Color32::WHITE;
|
||||||
pub const GRAY: egui::Color32 = egui::Color32::GRAY;
|
pub const GRAY: egui::Color32 = egui::Color32::GRAY;
|
||||||
pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY;
|
pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY;
|
||||||
pub const BLACK: egui::Color32 = egui::Color32::BLACK;
|
pub const BLACK: egui::Color32 = egui::Color32::BLACK;
|
||||||
pub const DARK_GRAY: egui::Color32 = egui::Color32::from_rgb(18, 18, 18);
|
pub const DARK_GRAY: egui::Color32 = egui::Color32::from_rgb(18, 18, 18);
|
||||||
|
|
||||||
// [Duration] constants
|
// [Duration] constants
|
||||||
pub const SECOND: std::time::Duration = std::time::Duration::from_secs(1);
|
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.
|
// The explaination given to the user on why XMRig needs sudo.
|
||||||
pub const XMRIG_ADMIN_REASON: &str =
|
pub const XMRIG_ADMIN_REASON: &str =
|
||||||
r#"The large hashrate difference between XMRig and other miners like Monero and P2Pool's built-in miners is mostly due to XMRig configuring CPU MSRs and setting up hugepages. Other miners like Monero or P2Pool's built-in miner do not do this. It can be done manually but it isn't recommended since XMRig does this for you automatically, but only if it has the proper admin privileges."#;
|
r#"The large hashrate difference between XMRig and other miners like Monero and P2Pool's built-in miners is mostly due to XMRig configuring CPU MSRs and setting up hugepages. Other miners like Monero or P2Pool's built-in miner do not do this. It can be done manually but it isn't recommended since XMRig does this for you automatically, but only if it has the proper admin privileges."#;
|
||||||
// Password buttons
|
// Password buttons
|
||||||
pub const PASSWORD_TEXT: &str = "Enter sudo/admin password...";
|
pub const PASSWORD_TEXT: &str = "Enter sudo/admin password...";
|
||||||
pub const PASSWORD_LEAVE: &str = "Return to the previous screen";
|
pub const PASSWORD_LEAVE: &str = "Return to the previous screen";
|
||||||
pub const PASSWORD_ENTER: &str = "Attempt with the current password";
|
pub const PASSWORD_ENTER: &str = "Attempt with the current password";
|
||||||
pub const PASSWORD_HIDE: &str = "Toggle hiding/showing the password";
|
pub const PASSWORD_HIDE: &str = "Toggle hiding/showing the password";
|
||||||
|
|
||||||
|
|
||||||
// OS specific
|
// OS specific
|
||||||
|
@ -140,58 +146,87 @@ pub const OS_NAME: &str = "Linux";
|
||||||
|
|
||||||
// Tooltips
|
// Tooltips
|
||||||
// Status
|
// Status
|
||||||
pub const STATUS_GUPAX_UPTIME: &str = "How long Gupax has been online";
|
pub const STATUS_GUPAX_UPTIME: &str = "How long Gupax has been online";
|
||||||
pub const STATUS_GUPAX_CPU_USAGE: &str = "How much CPU Gupax is currently using. This accounts for all your threads (it is out of 100%)";
|
pub const STATUS_GUPAX_CPU_USAGE: &str = "How much CPU Gupax is currently using. This accounts for all your threads (it is out of 100%)";
|
||||||
pub const STATUS_GUPAX_MEMORY_USAGE: &str = "How much memory Gupax is currently using in Megabytes";
|
pub const STATUS_GUPAX_MEMORY_USAGE: &str = "How much memory Gupax is currently using in Megabytes";
|
||||||
pub const STATUS_GUPAX_SYSTEM_CPU_USAGE: &str = "How much CPU your entire system is currently using. This accounts for all your threads (it is out of 100%)";
|
pub const STATUS_GUPAX_SYSTEM_CPU_USAGE: &str = "How much CPU your entire system is currently using. This accounts for all your threads (it is out of 100%)";
|
||||||
pub const STATUS_GUPAX_SYSTEM_MEMORY: &str = "How much memory your entire system has (including swap) and is currently using in Gigabytes";
|
pub const STATUS_GUPAX_SYSTEM_MEMORY: &str = "How much memory your entire system has (including swap) and is currently using in Gigabytes";
|
||||||
pub const STATUS_GUPAX_SYSTEM_CPU_MODEL: &str = "The detected model of your system's CPU and its current frequency";
|
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_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_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 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_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_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_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";
|
pub const STATUS_P2POOL_EFFORT: &str = "The average amount of effort needed to find a share, and the current effort";
|
||||||
pub const STATUS_P2POOL_CONNECTIONS: &str = "The total amount of miner connections on this P2Pool";
|
pub const STATUS_P2POOL_CONNECTIONS: &str = "The total amount of miner connections on this P2Pool";
|
||||||
pub const STATUS_P2POOL_MONERO_NODE: &str = "The Monero node being used by P2Pool";
|
pub const STATUS_P2POOL_MONERO_NODE: &str = "The Monero node being used by P2Pool";
|
||||||
pub const STATUS_P2POOL_POOL: &str = "The P2Pool sidechain you're currently connected to";
|
pub const STATUS_P2POOL_POOL: &str = "The P2Pool sidechain you're currently connected to";
|
||||||
pub const STATUS_P2POOL_ADDRESS: &str = "The Monero address P2Pool will send payouts to";
|
pub const STATUS_P2POOL_ADDRESS: &str = "The Monero address P2Pool will send payouts to";
|
||||||
//--
|
//--
|
||||||
pub const STATUS_XMRIG_UPTIME: &str = "How long XMRig has been online";
|
pub const STATUS_XMRIG_UPTIME: &str = "How long XMRig has been online";
|
||||||
pub const STATUS_XMRIG_CPU: &str = "The average CPU load of XMRig. [1.0] represents 1 thread is maxed out, e.g: If you have 8 threads, [4.0] means half your threads are maxed out.";
|
pub const STATUS_XMRIG_CPU: &str = "The average CPU load of XMRig. [1.0] represents 1 thread is maxed out, e.g: If you have 8 threads, [4.0] means half your threads are maxed out.";
|
||||||
pub const STATUS_XMRIG_HASHRATE: &str = "The average hashrate of XMRig";
|
pub const STATUS_XMRIG_HASHRATE: &str = "The average hashrate of XMRig";
|
||||||
pub const STATUS_XMRIG_DIFFICULTY: &str = "The current difficulty of the job XMRig is working on";
|
pub const STATUS_XMRIG_DIFFICULTY: &str = "The current difficulty of the job XMRig is working on";
|
||||||
pub const STATUS_XMRIG_SHARES: &str = "The amount of accepted and rejected shares";
|
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_POOL: &str = "The pool XMRig is currently mining to";
|
||||||
pub const STATUS_XMRIG_THREADS: &str = "The amount of threads XMRig is currently using";
|
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
|
// Gupax
|
||||||
pub const GUPAX_UPDATE: &str = "Check for updates on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically";
|
pub const GUPAX_UPDATE: &str = "Check for updates on Gupax, P2Pool, and XMRig via GitHub's API and upgrade automatically";
|
||||||
pub const GUPAX_AUTO_UPDATE: &str = "Automatically check for updates at startup";
|
pub const GUPAX_AUTO_UPDATE: &str = "Automatically check for updates at startup";
|
||||||
pub const GUPAX_SHOULD_RESTART: &str = "Gupax was updated. A restart is recommended but not required";
|
pub const GUPAX_SHOULD_RESTART: &str = "Gupax was updated. A restart is recommended but not required";
|
||||||
pub const GUPAX_UP_TO_DATE: &str = "Gupax is up-to-date";
|
pub const GUPAX_UP_TO_DATE: &str = "Gupax is up-to-date";
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[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";
|
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
|
#[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_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_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!";
|
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!";
|
||||||
pub const GUPAX_AUTO_XMRIG: &str = "Automatically start XMRig on Gupax startup. This option will fail if your XMRig settings aren't valid!";
|
pub const GUPAX_AUTO_XMRIG: &str = "Automatically start XMRig on Gupax startup. This option will fail if your XMRig settings aren't valid!";
|
||||||
pub const GUPAX_ADJUST: &str = "Adjust and set the width/height of the Gupax window";
|
pub const GUPAX_ADJUST: &str = "Adjust and set the width/height of the Gupax window";
|
||||||
pub const GUPAX_WIDTH: &str = "Set the width of the Gupax window";
|
pub const GUPAX_WIDTH: &str = "Set the width of the Gupax window";
|
||||||
pub const GUPAX_HEIGHT: &str = "Set the height of the Gupax window";
|
pub const GUPAX_HEIGHT: &str = "Set the height of the Gupax window";
|
||||||
pub const GUPAX_LOCK_WIDTH: &str = "Automatically match the HEIGHT against the WIDTH in a 4:3 ratio";
|
pub const GUPAX_LOCK_WIDTH: &str = "Automatically match the HEIGHT against the WIDTH in a 4:3 ratio";
|
||||||
pub const GUPAX_LOCK_HEIGHT: &str = "Automatically match the WIDTH against the HEIGHT in a 4:3 ratio";
|
pub const GUPAX_LOCK_HEIGHT: &str = "Automatically match the WIDTH against the HEIGHT in a 4:3 ratio";
|
||||||
pub const GUPAX_NO_LOCK: &str = "Allow individual selection of width and height";
|
pub const GUPAX_NO_LOCK: &str = "Allow individual selection of width and height";
|
||||||
pub const GUPAX_SET: &str = "Set the width/height of the Gupax window to the current values";
|
pub const GUPAX_SET: &str = "Set the width/height of the Gupax window to the current values";
|
||||||
pub const GUPAX_TAB: &str = "Set the default tab Gupax starts on";
|
pub const GUPAX_TAB: &str = "Set the default tab Gupax starts on";
|
||||||
pub const GUPAX_TAB_ABOUT: &str = "Set the tab Gupax starts on to: About";
|
pub const GUPAX_TAB_ABOUT: &str = "Set the tab Gupax starts on to: About";
|
||||||
pub const GUPAX_TAB_STATUS: &str = "Set the tab Gupax starts on to: Status";
|
pub const GUPAX_TAB_STATUS: &str = "Set the tab Gupax starts on to: Status";
|
||||||
pub const GUPAX_TAB_GUPAX: &str = "Set the tab Gupax starts on to: Gupax";
|
pub const GUPAX_TAB_GUPAX: &str = "Set the tab Gupax starts on to: Gupax";
|
||||||
pub const GUPAX_TAB_P2POOL: &str = "Set the tab Gupax starts on to: P2Pool";
|
pub const GUPAX_TAB_P2POOL: &str = "Set the tab Gupax starts on to: P2Pool";
|
||||||
pub const GUPAX_TAB_XMRIG: &str = "Set the tab Gupax starts on to: XMRig";
|
pub const GUPAX_TAB_XMRIG: &str = "Set the tab Gupax starts on to: XMRig";
|
||||||
|
|
||||||
pub const GUPAX_SIMPLE: &str =
|
pub const GUPAX_SIMPLE: &str =
|
||||||
r#"Use simple Gupax settings:
|
r#"Use simple Gupax settings:
|
||||||
|
@ -209,19 +244,19 @@ pub const GUPAX_PATH_P2POOL: &str = "The location of the P2Pool binary: Both abs
|
||||||
pub const GUPAX_PATH_XMRIG: &str = "The location of the XMRig binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path";
|
pub const GUPAX_PATH_XMRIG: &str = "The location of the XMRig binary: Both absolute and relative paths are accepted; A red [X] will appear if there is no file found at the given path";
|
||||||
|
|
||||||
// P2Pool
|
// P2Pool
|
||||||
pub const P2POOL_MAIN: &str = "Use the P2Pool main-chain. This P2Pool finds blocks faster, but has a higher difficulty. Suitable for miners with more than 50kH/s";
|
pub const P2POOL_MAIN: &str = "Use the P2Pool main-chain. This P2Pool finds blocks faster, but has a higher difficulty. Suitable for miners with more than 50kH/s";
|
||||||
pub const P2POOL_MINI: &str = "Use the P2Pool mini-chain. This P2Pool finds blocks slower, but has a lower difficulty. Suitable for miners with less than 50kH/s";
|
pub const P2POOL_MINI: &str = "Use the P2Pool mini-chain. This P2Pool finds blocks slower, but has a lower difficulty. Suitable for miners with less than 50kH/s";
|
||||||
pub const P2POOL_OUT: &str = "How many out-bound peers to connect to? (you connecting to others)";
|
pub const P2POOL_OUT: &str = "How many out-bound peers to connect to? (you connecting to others)";
|
||||||
pub const P2POOL_IN: &str = "How many in-bound peers to allow? (others connecting to you)";
|
pub const P2POOL_IN: &str = "How many in-bound peers to allow? (others connecting to you)";
|
||||||
pub const P2POOL_LOG: &str = "Verbosity of the console log";
|
pub const P2POOL_LOG: &str = "Verbosity of the console log";
|
||||||
pub const P2POOL_AUTO_NODE: &str = "Automatically ping the community Monero nodes at Gupax startup";
|
pub const P2POOL_AUTO_NODE: &str = "Automatically ping the community Monero nodes at Gupax startup";
|
||||||
pub const P2POOL_AUTO_SELECT: &str = "Automatically select the fastest community Monero node after pinging";
|
pub const P2POOL_AUTO_SELECT: &str = "Automatically select the fastest community Monero node after pinging";
|
||||||
pub const P2POOL_SELECT_FASTEST: &str = "Select the fastest community Monero node";
|
pub const P2POOL_SELECT_FASTEST: &str = "Select the fastest community Monero node";
|
||||||
pub const P2POOL_SELECT_RANDOM: &str = "Select a random community Monero node";
|
pub const P2POOL_SELECT_RANDOM: &str = "Select a random community Monero node";
|
||||||
pub const P2POOL_SELECT_LAST: &str = "Select the previous community Monero node";
|
pub const P2POOL_SELECT_LAST: &str = "Select the previous community Monero node";
|
||||||
pub const P2POOL_SELECT_NEXT: &str = "Select the next community Monero node";
|
pub const P2POOL_SELECT_NEXT: &str = "Select the next community Monero node";
|
||||||
pub const P2POOL_PING: &str = "Ping the built-in community Monero nodes";
|
pub const P2POOL_PING: &str = "Ping the built-in community Monero nodes";
|
||||||
pub const P2POOL_ADDRESS: &str = "You must use a primary Monero address to mine on P2Pool (starts with a 4). It is highly recommended to create a new wallet since addresses are public on P2Pool!";
|
pub const P2POOL_ADDRESS: &str = "You must use a primary Monero address to mine on P2Pool (starts with a 4). It is highly recommended to create a new wallet since addresses are public on P2Pool!";
|
||||||
pub const P2POOL_INPUT: &str = "Send a command to P2Pool";
|
pub const P2POOL_INPUT: &str = "Send a command to P2Pool";
|
||||||
pub const P2POOL_ARGUMENTS: &str =
|
pub const P2POOL_ARGUMENTS: &str =
|
||||||
r#"WARNING: Use [--no-color] and make sure to set [--data-api <PATH>] & [--local-api] so that the [Status] tab can work!
|
r#"WARNING: Use [--no-color] and make sure to set [--data-api <PATH>] & [--local-api] so that the [Status] tab can work!
|
||||||
|
@ -249,10 +284,10 @@ pub const P2POOL_PATH_OK: &str = "P2Pool was found at the given PATH";
|
||||||
pub const P2POOL_PATH_EMPTY: &str = "P2Pool PATH is empty! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where P2Pool is located.";
|
pub const P2POOL_PATH_EMPTY: &str = "P2Pool PATH is empty! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where P2Pool is located.";
|
||||||
|
|
||||||
// Node/Pool list
|
// Node/Pool list
|
||||||
pub const LIST_ADD: &str = "Add the current values to the list";
|
pub const LIST_ADD: &str = "Add the current values to the list";
|
||||||
pub const LIST_SAVE: &str = "Save the current values to the already existing entry";
|
pub const LIST_SAVE: &str = "Save the current values to the already existing entry";
|
||||||
pub const LIST_DELETE: &str = "Delete the currently selected entry";
|
pub const LIST_DELETE: &str = "Delete the currently selected entry";
|
||||||
pub const LIST_CLEAR: &str = "Clear all current values";
|
pub const LIST_CLEAR: &str = "Clear all current values";
|
||||||
|
|
||||||
// XMRig
|
// XMRig
|
||||||
pub const XMRIG_SIMPLE: &str =
|
pub const XMRIG_SIMPLE: &str =
|
||||||
|
@ -275,36 +310,39 @@ pub const XMRIG_ARGUMENTS: &str =
|
||||||
r#"WARNING: Use [--no-color] and make sure to set [--http-host <IP>] & [--http-port <PORT>] so that the [Status] tab can work!
|
r#"WARNING: Use [--no-color] and make sure to set [--http-host <IP>] & [--http-port <PORT>] so that the [Status] tab can work!
|
||||||
|
|
||||||
Start XMRig with these arguments and override all below settings"#;
|
Start XMRig with these arguments and override all below settings"#;
|
||||||
pub const XMRIG_ADDRESS: &str = "Specify which Monero address to payout to. This does nothing if mining to P2Pool since the address being payed out to will be the one P2Pool started with. This doubles as a rig identifier for P2Pool and some pools.";
|
pub const XMRIG_ADDRESS: &str = "Specify which Monero address to payout to. This does nothing if mining to P2Pool since the address being payed out to will be the one P2Pool started with. This doubles as a rig identifier for P2Pool and some pools.";
|
||||||
pub const XMRIG_NAME: &str = "Add a unique name to identify this pool; Only [A-Za-z0-9-_.] and spaces allowed; Max length = 30 characters";
|
pub const XMRIG_NAME: &str = "Add a unique name to identify this pool; Only [A-Za-z0-9-_.] and spaces allowed; Max length = 30 characters";
|
||||||
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_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_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";
|
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";
|
||||||
pub const XMRIG_PAUSE: &str = "THIS SETTING IS DISABLED IF SET TO [0]. Pause mining if user is active, resume after";
|
#[cfg(not(target_os = "linux"))]
|
||||||
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_PAUSE: &str = "THIS SETTING IS DISABLED IF SET TO [0]. Pause mining if user is active, resume after";
|
||||||
pub const XMRIG_API_PORT: &str = "Specify which port to bind to for XMRig's HTTP API; If empty: [18088]";
|
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_TLS: &str = "Enable SSL/TLS connections (needs pool support)";
|
pub const XMRIG_API_PORT: &str = "Specify which port to bind to for XMRig's HTTP API; If empty: [18088]";
|
||||||
pub const XMRIG_KEEPALIVE: &str = "Send keepalive packets to prevent timeout (needs pool support)";
|
pub const XMRIG_TLS: &str = "Enable SSL/TLS connections (needs pool support)";
|
||||||
pub const XMRIG_THREADS: &str = "Number of CPU threads to use for mining";
|
pub const XMRIG_KEEPALIVE: &str = "Send keepalive packets to prevent timeout (needs pool support)";
|
||||||
pub const XMRIG_PATH_NOT_FILE: &str = "XMRig binary not found at the given PATH in the Gupax tab! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
|
pub const XMRIG_THREADS: &str = "Number of CPU threads to use for mining";
|
||||||
|
pub const XMRIG_PATH_NOT_FILE: &str = "XMRig binary not found at the given PATH in the Gupax tab! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
|
||||||
pub const XMRIG_PATH_NOT_VALID: &str = "XMRig binary at the given PATH in the Gupax tab doesn't look like XMRig! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
|
pub const XMRIG_PATH_NOT_VALID: &str = "XMRig binary at the given PATH in the Gupax tab doesn't look like XMRig! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
|
||||||
pub const XMRIG_PATH_OK: &str = "XMRig was found at the given PATH";
|
pub const XMRIG_PATH_OK: &str = "XMRig was found at the given PATH";
|
||||||
pub const XMRIG_PATH_EMPTY: &str = "XMRig PATH is empty! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
|
pub const XMRIG_PATH_EMPTY: &str = "XMRig PATH is empty! To fix: goto the [Gupax Advanced] tab, select [Open] and specify where XMRig is located.";
|
||||||
|
|
||||||
// CLI argument messages
|
// CLI argument messages
|
||||||
pub const ARG_HELP: &str =
|
pub const ARG_HELP: &str =
|
||||||
r#"USAGE: ./gupax [--flag]
|
r#"USAGE: ./gupax [--flag]
|
||||||
|
|
||||||
--help Print this help message
|
--help Print this help message
|
||||||
--version Print version and build info
|
--version Print version and build info
|
||||||
--state Print Gupax state
|
--state Print Gupax state
|
||||||
--nodes Print the manual node list
|
--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
|
||||||
--reset-state Reset all Gupax state (your settings)
|
--no-startup Disable all auto-startup settings for this instance (auto-update, auto-ping, etc)
|
||||||
--reset-nodes Reset the manual node list in the [P2Pool] tab
|
--reset-state Reset all Gupax state (your settings)
|
||||||
--reset-pools Reset the manual pool list in the [XMRig] tab
|
--reset-nodes Reset the manual node list in the [P2Pool] tab
|
||||||
--reset-all Reset the state, the manual node list, and the manual pool list
|
--reset-pools Reset the manual pool list in the [XMRig] tab
|
||||||
--ferris Print an extremely cute crab
|
--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
|
To view more detailed console debug information, start Gupax with
|
||||||
the environment variable [RUST_LOG] set to a log level like so:
|
the environment variable [RUST_LOG] set to a log level like so:
|
||||||
|
|
511
src/disk.rs
511
src/disk.rs
|
@ -42,11 +42,16 @@ use serde::{Serialize,Deserialize};
|
||||||
use figment::Figment;
|
use figment::Figment;
|
||||||
use figment::providers::{Format,Toml};
|
use figment::providers::{Format,Toml};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
human::*,
|
||||||
constants::*,
|
constants::*,
|
||||||
gupax::Ratio,
|
gupax::Ratio,
|
||||||
Tab,
|
Tab,
|
||||||
|
xmr::*,
|
||||||
|
macros::*,
|
||||||
};
|
};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Const
|
//---------------------------------------------------------------------------------------------------- Const
|
||||||
// State file
|
// State file
|
||||||
|
@ -59,6 +64,30 @@ const DIRECTORY: &str = "Gupax/";
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
const DIRECTORY: &str = "gupax/";
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
pub const DEFAULT_P2POOL_PATH: &str = r"P2Pool\p2pool.exe";
|
pub const DEFAULT_P2POOL_PATH: &str = r"P2Pool\p2pool.exe";
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
@ -96,17 +125,55 @@ pub fn get_gupax_data_path() -> Result<PathBuf, TomlError> {
|
||||||
path.push(DIRECTORY);
|
path.push(DIRECTORY);
|
||||||
info!("OS | Data path ... {}", path.display());
|
info!("OS | Data path ... {}", path.display());
|
||||||
create_gupax_dir(&path)?;
|
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)
|
Ok(path)
|
||||||
},
|
},
|
||||||
None => { error!("OS | Data path ... FAIL"); Err(TomlError::Path(PATH_ERROR.to_string())) },
|
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> {
|
pub fn create_gupax_dir(path: &PathBuf) -> Result<(), TomlError> {
|
||||||
// Create directory
|
// Create Gupax directory
|
||||||
match fs::create_dir_all(path) {
|
match fs::create_dir_all(path) {
|
||||||
Ok(_) => { info!("OS | Create data path ... OK"); Ok(()) },
|
Ok(_) => info!("OS | Create data path ... OK"),
|
||||||
Err(e) => { error!("OS | Create data path ... FAIL ... {}", e); Err(TomlError::Io(e)) },
|
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);
|
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
|
// Turn relative paths into absolute paths
|
||||||
pub fn into_absolute_path(path: String) -> Result<PathBuf, TomlError> {
|
pub fn into_absolute_path(path: String) -> Result<PathBuf, TomlError> {
|
||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
|
@ -163,18 +223,19 @@ impl State {
|
||||||
let max_threads = num_cpus::get();
|
let max_threads = num_cpus::get();
|
||||||
let current_threads = if max_threads == 1 { 1 } else { max_threads / 2 };
|
let current_threads = if max_threads == 1 { 1 } else { max_threads / 2 };
|
||||||
Self {
|
Self {
|
||||||
|
status: Status::default(),
|
||||||
gupax: Gupax::default(),
|
gupax: Gupax::default(),
|
||||||
p2pool: P2pool::default(),
|
p2pool: P2pool::default(),
|
||||||
xmrig: Xmrig::with_threads(max_threads, current_threads),
|
xmrig: Xmrig::with_threads(max_threads, current_threads),
|
||||||
version: Arc::new(Mutex::new(Version::default())),
|
version: arc_mut!(Version::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_absolute_path(&mut self) -> Result<(), TomlError> {
|
pub fn update_absolute_path(&mut self) -> Result<(), TomlError> {
|
||||||
self.gupax.absolute_p2pool_path = into_absolute_path(self.gupax.p2pool_path.clone())?;
|
self.gupax.absolute_p2pool_path = into_absolute_path(self.gupax.p2pool_path.clone())?;
|
||||||
self.gupax.absolute_xmrig_path = into_absolute_path(self.gupax.xmrig_path.clone())?;
|
self.gupax.absolute_xmrig_path = into_absolute_path(self.gupax.xmrig_path.clone())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert [&str] to [State]
|
// Convert [&str] to [State]
|
||||||
pub fn from_str(string: &str) -> Result<Self, TomlError> {
|
pub fn from_str(string: &str) -> Result<Self, TomlError> {
|
||||||
|
@ -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]
|
//---------------------------------------------------------------------------------------------------- Custom Error [TomlError]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TomlError {
|
pub enum TomlError {
|
||||||
|
@ -559,9 +809,138 @@ impl From<std::fmt::Error> for TomlError {
|
||||||
//---------------------------------------------------------------------------------------------------- [File] Enum (for matching which file)
|
//---------------------------------------------------------------------------------------------------- [File] Enum (for matching which file)
|
||||||
#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
|
#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
|
||||||
pub enum File {
|
pub enum File {
|
||||||
State, // state.toml -> Gupax state
|
// State files
|
||||||
Node, // node.toml -> P2Pool manual node selector
|
State, // state.toml | Gupax state
|
||||||
Pool, // pool.toml -> XMRig manual pool selector
|
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
|
//---------------------------------------------------------------------------------------------------- [Node] Struct
|
||||||
|
@ -583,18 +962,30 @@ pub struct Pool {
|
||||||
//---------------------------------------------------------------------------------------------------- [State] Struct
|
//---------------------------------------------------------------------------------------------------- [State] Struct
|
||||||
#[derive(Clone,Debug,Deserialize,Serialize)]
|
#[derive(Clone,Debug,Deserialize,Serialize)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
pub status: Status,
|
||||||
pub gupax: Gupax,
|
pub gupax: Gupax,
|
||||||
pub p2pool: P2pool,
|
pub p2pool: P2pool,
|
||||||
pub xmrig: Xmrig,
|
pub xmrig: Xmrig,
|
||||||
pub version: Arc<Mutex<Version>>,
|
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)]
|
#[derive(Clone,Eq,PartialEq,Debug,Deserialize,Serialize)]
|
||||||
pub struct Gupax {
|
pub struct Gupax {
|
||||||
pub simple: bool,
|
pub simple: bool,
|
||||||
pub auto_update: bool,
|
pub auto_update: bool,
|
||||||
pub auto_p2pool: bool,
|
pub auto_p2pool: bool,
|
||||||
pub auto_xmrig: bool,
|
pub auto_xmrig: bool,
|
||||||
|
// pub auto_monero: bool,
|
||||||
pub ask_before_quit: bool,
|
pub ask_before_quit: bool,
|
||||||
pub save_before_quit: bool,
|
pub save_before_quit: bool,
|
||||||
pub update_via_tor: bool,
|
pub update_via_tor: bool,
|
||||||
|
@ -663,6 +1054,19 @@ pub struct Version {
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- [State] Defaults
|
//---------------------------------------------------------------------------------------------------- [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 {
|
impl Default for Gupax {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -687,6 +1091,7 @@ impl Default for Gupax {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for P2pool {
|
impl Default for P2pool {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -800,6 +1205,14 @@ mod test {
|
||||||
tab = "About"
|
tab = "About"
|
||||||
ratio = "Width"
|
ratio = "Width"
|
||||||
|
|
||||||
|
[status]
|
||||||
|
submenu = "P2pool"
|
||||||
|
payout_view = "Oldest"
|
||||||
|
monero_enabled = true
|
||||||
|
manual_hash = false
|
||||||
|
hashrate = 1241.23
|
||||||
|
hash_metric = "Hash"
|
||||||
|
|
||||||
[p2pool]
|
[p2pool]
|
||||||
simple = true
|
simple = true
|
||||||
mini = true
|
mini = true
|
||||||
|
@ -979,4 +1392,64 @@ mod test {
|
||||||
assert!(!merged_state.contains("SETTING_THAT_DOESNT_EXIST_ANYMORE"));
|
assert!(!merged_state.contains("SETTING_THAT_DOESNT_EXIST_ANYMORE"));
|
||||||
assert!(merged_state.contains("44hintoFpuo3ugKfcqJvh5BmrsTRpnTasJmetKC4VXCt6QDtbHVuixdTtsm6Ptp7Y8haXnJ6j8Gj2dra8CKy5ewz7Vi9CYW"));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
30
src/gupax.rs
30
src/gupax.rs
|
@ -27,11 +27,11 @@ use egui::{
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::*,
|
constants::*,
|
||||||
disk::Gupax,
|
|
||||||
update::*,
|
update::*,
|
||||||
ErrorState,
|
ErrorState,
|
||||||
Restart,
|
Restart,
|
||||||
Tab,
|
Tab,
|
||||||
|
macros::*,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
thread,
|
thread,
|
||||||
|
@ -55,13 +55,13 @@ pub struct FileWindow {
|
||||||
|
|
||||||
impl FileWindow {
|
impl FileWindow {
|
||||||
pub fn new() -> Arc<Mutex<Self>> {
|
pub fn new() -> Arc<Mutex<Self>> {
|
||||||
Arc::new(Mutex::new(Self {
|
arc_mut!(Self {
|
||||||
thread: false,
|
thread: false,
|
||||||
picked_p2pool: false,
|
picked_p2pool: false,
|
||||||
picked_xmrig: false,
|
picked_xmrig: false,
|
||||||
p2pool_path: String::new(),
|
p2pool_path: String::new(),
|
||||||
xmrig_path: String::new(),
|
xmrig_path: String::new(),
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ pub enum Ratio {
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Gupax
|
//---------------------------------------------------------------------------------------------------- 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) {
|
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
|
// Update button + Progress bar
|
||||||
debug!("Gupax Tab | Rendering [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 button = if self.simple { height/5.0 } else { height/15.0 };
|
||||||
let height = if self.simple { height/5.0 } else { height/10.0 };
|
let height = if self.simple { height/5.0 } else { height/10.0 };
|
||||||
let width = width - SPACE;
|
let width = width - SPACE;
|
||||||
let updating = *update.lock().unwrap().updating.lock().unwrap();
|
let updating = *lock2!(update,updating);
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
// If [Gupax] is being built for a Linux distro,
|
// If [Gupax] is being built for a Linux distro,
|
||||||
// disable built-in updating completely.
|
// disable built-in updating completely.
|
||||||
|
@ -106,8 +106,8 @@ impl Gupax {
|
||||||
});
|
});
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.set_enabled(updating);
|
ui.set_enabled(updating);
|
||||||
let prog = *update.lock().unwrap().prog.lock().unwrap();
|
let prog = *lock2!(update,prog);
|
||||||
let msg = format!("{}\n{}{}", *update.lock().unwrap().msg.lock().unwrap(), 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)));
|
ui.add_sized([width, height*1.4], Label::new(RichText::text_style(RichText::new(msg), Monospace)));
|
||||||
let height = height/2.0;
|
let height = height/2.0;
|
||||||
if updating {
|
if updating {
|
||||||
|
@ -115,7 +115,7 @@ impl Gupax {
|
||||||
} else {
|
} else {
|
||||||
ui.add_sized([width, height], Label::new("..."));
|
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.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.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() {
|
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
|
||||||
Self::spawn_file_window_thread(file_window, FileType::P2pool);
|
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.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.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() {
|
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
|
||||||
Self::spawn_file_window_thread(file_window, FileType::Xmrig);
|
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);
|
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_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; }
|
if guard.picked_xmrig { self.xmrig_path = guard.xmrig_path.clone(); guard.picked_xmrig = false; }
|
||||||
drop(guard);
|
drop(guard);
|
||||||
|
@ -276,19 +276,19 @@ impl Gupax {
|
||||||
Xmrig => "XMRig",
|
Xmrig => "XMRig",
|
||||||
};
|
};
|
||||||
let file_window = file_window.clone();
|
let file_window = file_window.clone();
|
||||||
file_window.lock().unwrap().thread = true;
|
lock!(file_window).thread = true;
|
||||||
thread::spawn(move|| {
|
thread::spawn(move|| {
|
||||||
match rfd::FileDialog::new().set_title(&format!("Select {} Binary for Gupax", name)).pick_file() {
|
match rfd::FileDialog::new().set_title(&format!("Select {} Binary for Gupax", name)).pick_file() {
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
info!("Gupax | Path selected for {} ... {}", name, path.display());
|
info!("Gupax | Path selected for {} ... {}", name, path.display());
|
||||||
match file_type {
|
match file_type {
|
||||||
P2pool => { file_window.lock().unwrap().p2pool_path = path.display().to_string(); file_window.lock().unwrap().picked_p2pool = true; },
|
P2pool => { lock!(file_window).p2pool_path = path.display().to_string(); lock!(file_window).picked_p2pool = true; },
|
||||||
Xmrig => { file_window.lock().unwrap().xmrig_path = path.display().to_string(); file_window.lock().unwrap().picked_xmrig = 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),
|
None => info!("Gupax | No path selected for {}", name),
|
||||||
};
|
};
|
||||||
file_window.lock().unwrap().thread = false;
|
lock!(file_window).thread = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1111
src/helper.rs
1111
src/helper.rs
File diff suppressed because it is too large
Load diff
326
src/human.rs
Normal file
326
src/human.rs
Normal 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
121
src/macros.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
328
src/main.rs
328
src/main.rs
|
@ -37,7 +37,7 @@ use eframe::{egui,NativeOptions};
|
||||||
use log::*;
|
use log::*;
|
||||||
use env_logger::{Builder,WriteStyle};
|
use env_logger::{Builder,WriteStyle};
|
||||||
// Regex
|
// Regex
|
||||||
use regex::Regex;
|
use ::regex::Regex;
|
||||||
// Serde
|
// Serde
|
||||||
use serde::{Serialize,Deserialize};
|
use serde::{Serialize,Deserialize};
|
||||||
// std
|
// std
|
||||||
|
@ -62,7 +62,11 @@ mod p2pool;
|
||||||
mod xmrig;
|
mod xmrig;
|
||||||
mod update;
|
mod update;
|
||||||
mod helper;
|
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)
|
// Sudo (dummy values for Windows)
|
||||||
mod sudo;
|
mod sudo;
|
||||||
|
@ -128,6 +132,12 @@ pub struct App {
|
||||||
sudo: Arc<Mutex<SudoState>>, // This is just a dummy struct on [Windows].
|
sudo: Arc<Mutex<SudoState>>, // This is just a dummy struct on [Windows].
|
||||||
// State from [--flags]
|
// State from [--flags]
|
||||||
no_startup: bool,
|
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
|
// Static stuff
|
||||||
pid: sysinfo::Pid, // Gupax's PID
|
pid: sysinfo::Pid, // Gupax's PID
|
||||||
max_threads: usize, // Max amount of detected system threads
|
max_threads: usize, // Max amount of detected system threads
|
||||||
|
@ -137,7 +147,8 @@ pub struct App {
|
||||||
resolution: Vec2, // Frame resolution
|
resolution: Vec2, // Frame resolution
|
||||||
os: &'static str, // OS
|
os: &'static str, // OS
|
||||||
admin: bool, // Are we admin? (for Windows)
|
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
|
state_path: PathBuf, // State file path
|
||||||
node_path: PathBuf, // Node file path
|
node_path: PathBuf, // Node file path
|
||||||
pool_path: PathBuf, // Pool file path
|
pool_path: PathBuf, // Pool file path
|
||||||
|
@ -166,12 +177,12 @@ impl App {
|
||||||
fn new(now: Instant) -> Self {
|
fn new(now: Instant) -> Self {
|
||||||
info!("Initializing App Struct...");
|
info!("Initializing App Struct...");
|
||||||
debug!("App Init | P2Pool & XMRig processes...");
|
debug!("App Init | P2Pool & XMRig processes...");
|
||||||
let p2pool = Arc::new(Mutex::new(Process::new(ProcessName::P2pool, String::new(), PathBuf::new())));
|
let p2pool = arc_mut!(Process::new(ProcessName::P2pool, String::new(), PathBuf::new()));
|
||||||
let xmrig = Arc::new(Mutex::new(Process::new(ProcessName::Xmrig, String::new(), PathBuf::new())));
|
let xmrig = arc_mut!(Process::new(ProcessName::Xmrig, String::new(), PathBuf::new()));
|
||||||
let p2pool_api = Arc::new(Mutex::new(PubP2poolApi::new()));
|
let p2pool_api = arc_mut!(PubP2poolApi::new());
|
||||||
let xmrig_api = Arc::new(Mutex::new(PubXmrigApi::new()));
|
let xmrig_api = arc_mut!(PubXmrigApi::new());
|
||||||
let p2pool_img = Arc::new(Mutex::new(ImgP2pool::new()));
|
let p2pool_img = arc_mut!(ImgP2pool::new());
|
||||||
let xmrig_img = Arc::new(Mutex::new(ImgXmrig::new()));
|
let xmrig_img = arc_mut!(ImgXmrig::new());
|
||||||
|
|
||||||
debug!("App Init | Sysinfo...");
|
debug!("App Init | Sysinfo...");
|
||||||
// We give this to the [Helper] thread.
|
// We give this to the [Helper] thread.
|
||||||
|
@ -185,27 +196,27 @@ impl App {
|
||||||
Ok(pid) => pid,
|
Ok(pid) => pid,
|
||||||
Err(e) => { error!("App Init | Failed to get sysinfo PID: {}", e); exit(1) }
|
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]...");
|
debug!("App Init | The rest of the [App]...");
|
||||||
let mut app = Self {
|
let mut app = Self {
|
||||||
tab: Tab::default(),
|
tab: Tab::default(),
|
||||||
ping: Arc::new(Mutex::new(Ping::new())),
|
ping: arc_mut!(Ping::new()),
|
||||||
width: APP_DEFAULT_WIDTH,
|
width: APP_DEFAULT_WIDTH,
|
||||||
height: APP_DEFAULT_HEIGHT,
|
height: APP_DEFAULT_HEIGHT,
|
||||||
must_resize: false,
|
must_resize: false,
|
||||||
og: Arc::new(Mutex::new(State::new())),
|
og: arc_mut!(State::new()),
|
||||||
state: 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(),
|
file_window: FileWindow::new(),
|
||||||
og_node_vec: Node::new_vec(),
|
og_node_vec: Node::new_vec(),
|
||||||
node_vec: Node::new_vec(),
|
node_vec: Node::new_vec(),
|
||||||
og_pool_vec: Pool::new_vec(),
|
og_pool_vec: Pool::new_vec(),
|
||||||
pool_vec: Pool::new_vec(),
|
pool_vec: Pool::new_vec(),
|
||||||
restart: Arc::new(Mutex::new(Restart::No)),
|
restart: arc_mut!(Restart::No),
|
||||||
diff: false,
|
diff: false,
|
||||||
error_state: ErrorState::new(),
|
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,
|
p2pool,
|
||||||
xmrig,
|
xmrig,
|
||||||
p2pool_api,
|
p2pool_api,
|
||||||
|
@ -214,10 +225,11 @@ impl App {
|
||||||
xmrig_img,
|
xmrig_img,
|
||||||
p2pool_stdin: String::with_capacity(10),
|
p2pool_stdin: String::with_capacity(10),
|
||||||
xmrig_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,
|
resizing: false,
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
no_startup: false,
|
no_startup: false,
|
||||||
|
gupax_p2pool_api: arc_mut!(GupaxP2poolApi::new()),
|
||||||
pub_sys,
|
pub_sys,
|
||||||
pid,
|
pid,
|
||||||
max_threads: num_cpus::get(),
|
max_threads: num_cpus::get(),
|
||||||
|
@ -228,6 +240,7 @@ impl App {
|
||||||
resolution: Vec2::new(APP_DEFAULT_HEIGHT, APP_DEFAULT_WIDTH),
|
resolution: Vec2::new(APP_DEFAULT_HEIGHT, APP_DEFAULT_WIDTH),
|
||||||
os: OS,
|
os: OS,
|
||||||
os_data_path: PathBuf::new(),
|
os_data_path: PathBuf::new(),
|
||||||
|
gupax_p2pool_api_path: PathBuf::new(),
|
||||||
state_path: PathBuf::new(),
|
state_path: PathBuf::new(),
|
||||||
node_path: PathBuf::new(),
|
node_path: PathBuf::new(),
|
||||||
pool_path: PathBuf::new(),
|
pool_path: PathBuf::new(),
|
||||||
|
@ -258,11 +271,14 @@ impl App {
|
||||||
debug!("App Init | Setting TOML path...");
|
debug!("App Init | Setting TOML path...");
|
||||||
// Set [*.toml] path
|
// Set [*.toml] path
|
||||||
app.state_path = app.os_data_path.clone();
|
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 = 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 = 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
|
// Apply arg state
|
||||||
// It's not safe to [--reset] if any of the previous variables
|
// It's not safe to [--reset] if any of the previous variables
|
||||||
|
@ -289,7 +305,7 @@ impl App {
|
||||||
State::new()
|
State::new()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
app.og = Arc::new(Mutex::new(app.state.clone()));
|
app.og = arc_mut!(app.state.clone());
|
||||||
// Read node list
|
// Read node list
|
||||||
debug!("App Init | Reading node list...");
|
debug!("App Init | Reading node list...");
|
||||||
app.node_vec = match Node::get(&app.node_path) {
|
app.node_vec = match Node::get(&app.node_path) {
|
||||||
|
@ -333,9 +349,51 @@ impl App {
|
||||||
debug!("Pool Vec:");
|
debug!("Pool Vec:");
|
||||||
debug!("{:#?}", app.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
|
// Handle max threads
|
||||||
debug!("App Init | Handling max thread overflow...");
|
debug!("App Init | Handling max thread overflow...");
|
||||||
og.xmrig.max_threads = app.max_threads;
|
og.xmrig.max_threads = app.max_threads;
|
||||||
|
@ -380,13 +438,13 @@ impl App {
|
||||||
let p2pool_path = og.gupax.absolute_p2pool_path.clone();
|
let p2pool_path = og.gupax.absolute_p2pool_path.clone();
|
||||||
let xmrig_path = og.gupax.absolute_xmrig_path.clone();
|
let xmrig_path = og.gupax.absolute_xmrig_path.clone();
|
||||||
let tor = og.gupax.update_via_tor;
|
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...");
|
debug!("App Init | Setting state Gupax version...");
|
||||||
// Set state version as compiled in version
|
// Set state version as compiled in version
|
||||||
og.version.lock().unwrap().gupax = GUPAX_VERSION.to_string();
|
lock!(og.version).gupax = GUPAX_VERSION.to_string();
|
||||||
app.state.version.lock().unwrap().gupax = GUPAX_VERSION.to_string();
|
lock!(app.state.version).gupax = GUPAX_VERSION.to_string();
|
||||||
|
|
||||||
debug!("App Init | Setting saved [Tab]...");
|
debug!("App Init | Setting saved [Tab]...");
|
||||||
// Set 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
|
//---------------------------------------------------------------------------------------------------- [Pressed] enum
|
||||||
// These represent the keys pressed during the frame.
|
// These represent the keys pressed during the frame.
|
||||||
// I could use egui's [Key] but there is no option for
|
// I could use egui's [Key] but there is no option for
|
||||||
|
@ -605,6 +636,8 @@ enum KeyPressed {
|
||||||
Esc,
|
Esc,
|
||||||
Z,
|
Z,
|
||||||
X,
|
X,
|
||||||
|
C,
|
||||||
|
V,
|
||||||
S,
|
S,
|
||||||
R,
|
R,
|
||||||
D,
|
D,
|
||||||
|
@ -639,6 +672,12 @@ impl KeyPressed {
|
||||||
fn is_d(&self) -> bool {
|
fn is_d(&self) -> bool {
|
||||||
*self == Self::D
|
*self == Self::D
|
||||||
}
|
}
|
||||||
|
fn is_c(&self) -> bool {
|
||||||
|
*self == Self::C
|
||||||
|
}
|
||||||
|
fn is_v(&self) -> bool {
|
||||||
|
*self == Self::V
|
||||||
|
}
|
||||||
fn is_none(&self) -> bool {
|
fn is_none(&self) -> bool {
|
||||||
*self == Self::None
|
*self == Self::None
|
||||||
}
|
}
|
||||||
|
@ -761,7 +800,7 @@ fn init_auto(app: &mut App) {
|
||||||
} else if cfg!(windows) {
|
} else if cfg!(windows) {
|
||||||
Helper::start_xmrig(&app.helper, &app.state.xmrig, &app.state.gupax.absolute_xmrig_path, Arc::clone(&app.sudo));
|
Helper::start_xmrig(&app.helper, &app.state.xmrig, &app.state.gupax.absolute_xmrig_path, Arc::clone(&app.sudo));
|
||||||
} else {
|
} else {
|
||||||
app.sudo.lock().unwrap().signal = ProcessSignal::Start;
|
lock!(app.sudo).signal = ProcessSignal::Start;
|
||||||
app.error_state.ask_sudo(&app.sudo);
|
app.error_state.ask_sudo(&app.sudo);
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
let mut code = 0;
|
||||||
// Attempt to remove directory first
|
// Attempt to remove directory first
|
||||||
match std::fs::remove_dir_all(path) {
|
match std::fs::remove_dir_all(path) {
|
||||||
|
@ -815,6 +861,10 @@ fn reset(path: &PathBuf, state: &PathBuf, node: &PathBuf, pool: &PathBuf) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(_) => code = 1,
|
Err(_) => code = 1,
|
||||||
}
|
}
|
||||||
|
match reset_gupax_p2pool_api(gupax_p2pool_api) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => code = 1,
|
||||||
|
}
|
||||||
match code {
|
match code {
|
||||||
0 => println!("\nGupax reset ... OK"),
|
0 => println!("\nGupax reset ... OK"),
|
||||||
_ => eprintln!("\nGupax reset ... FAIL"),
|
_ => eprintln!("\nGupax reset ... FAIL"),
|
||||||
|
@ -849,14 +899,16 @@ fn parse_args<S: Into<String>>(mut app: App, panic: S) -> App {
|
||||||
// Everything else
|
// Everything else
|
||||||
for arg in args {
|
for arg in args {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"--state" => { info!("Printing state..."); print_disk_file(&app.state_path); }
|
"--state" => { info!("Printing state..."); print_disk_file(&app.state_path); },
|
||||||
"--nodes" => { info!("Printing node list..."); print_disk_file(&app.node_path); }
|
"--nodes" => { info!("Printing node list..."); print_disk_file(&app.node_path); },
|
||||||
"--reset-state" => if let Ok(()) = reset_state(&app.state_path) { println!("\nState reset ... OK"); exit(0); } else { eprintln!("\nState reset ... FAIL"); exit(1) },
|
"--payouts" => { info!("Printing payouts...\n"); print_gupax_p2pool_api(&app.gupax_p2pool_api); },
|
||||||
"--reset-nodes" => if let Ok(()) = reset_nodes(&app.node_path) { println!("\nNode reset ... OK"); exit(0) } else { eprintln!("\nNode reset ... FAIL"); exit(1) },
|
"--reset-state" => if let Ok(()) = reset_state(&app.state_path) { println!("\nState reset ... OK"); exit(0); } else { eprintln!("\nState 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-nodes" => if let Ok(()) = reset_nodes(&app.node_path) { println!("\nNode reset ... OK"); exit(0) } else { eprintln!("\nNode reset ... FAIL"); exit(1) },
|
||||||
"--reset-all" => reset(&app.os_data_path, &app.state_path, &app.node_path, &app.pool_path),
|
"--reset-pools" => if let Ok(()) = reset_pools(&app.pool_path) { println!("\nPool reset ... OK"); exit(0) } else { eprintln!("\nPool reset ... FAIL"); exit(1) },
|
||||||
"--no-startup" => app.no_startup = true,
|
"--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) },
|
||||||
_ => { eprintln!("\n[Gupax error] Invalid option: [{}]\nFor help, use: [--help]", arg); 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); },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app
|
app
|
||||||
|
@ -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
|
//---------------------------------------------------------------------------------------------------- Main [App] frame
|
||||||
fn main() {
|
fn main() {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
@ -923,7 +1002,7 @@ fn main() {
|
||||||
Ok(_) => info!("Temporary folder cleanup ... OK"),
|
Ok(_) => info!("Temporary folder cleanup ... OK"),
|
||||||
Err(e) => warn!("Could not cleanup [gupax_tmp] folders: {}", e),
|
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))),);
|
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
|
KeyPressed::Z
|
||||||
} else if input.consume_key(Modifiers::NONE, Key::X) {
|
} else if input.consume_key(Modifiers::NONE, Key::X) {
|
||||||
KeyPressed::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) {
|
} else if input.consume_key(Modifiers::NONE, Key::ArrowUp) {
|
||||||
KeyPressed::Up
|
KeyPressed::Up
|
||||||
} else if input.consume_key(Modifiers::NONE, Key::ArrowDown) {
|
} 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::P2pool => self.tab = Tab::Xmrig,
|
||||||
Tab::Xmrig => self.tab = Tab::About,
|
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
|
// Refresh AT LEAST once a second
|
||||||
|
@ -1016,13 +1127,13 @@ impl eframe::App for App {
|
||||||
// might as well check only once here to save
|
// might as well check only once here to save
|
||||||
// on a bunch of [.lock().unwrap()]s.
|
// on a bunch of [.lock().unwrap()]s.
|
||||||
debug!("App | Locking and collecting P2Pool state...");
|
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_alive = p2pool.is_alive();
|
||||||
let p2pool_is_waiting = p2pool.is_waiting();
|
let p2pool_is_waiting = p2pool.is_waiting();
|
||||||
let p2pool_state = p2pool.state;
|
let p2pool_state = p2pool.state;
|
||||||
drop(p2pool);
|
drop(p2pool);
|
||||||
debug!("App | Locking and collecting XMRig state...");
|
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_alive = xmrig.is_alive();
|
||||||
let xmrig_is_waiting = xmrig.is_waiting();
|
let xmrig_is_waiting = xmrig.is_waiting();
|
||||||
let xmrig_state = xmrig.state;
|
let xmrig_state = xmrig.state;
|
||||||
|
@ -1113,7 +1224,7 @@ impl eframe::App for App {
|
||||||
match self.error_state.buttons {
|
match self.error_state.buttons {
|
||||||
StayQuit => {
|
StayQuit => {
|
||||||
let mut text = "".to_string();
|
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 p2pool_is_alive { text = format!("{}\nP2Pool is online...!", text); }
|
||||||
if xmrig_is_alive { text = format!("{}\nXMRig 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? ---"));
|
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) {
|
match State::get(&self.state_path) {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
self.state = 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);
|
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),
|
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 => {
|
ErrorButtons::Sudo => {
|
||||||
let sudo_width = width/10.0;
|
let sudo_width = width/10.0;
|
||||||
let height = ui.available_height()/4.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;
|
let hide = sudo.hide;
|
||||||
ui.style_mut().override_text_style = Some(Monospace);
|
ui.style_mut().override_text_style = Some(Monospace);
|
||||||
if sudo.testing {
|
if sudo.testing {
|
||||||
|
@ -1248,7 +1359,7 @@ impl eframe::App for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let color = if hide { BLACK } else { BRIGHT_YELLOW };
|
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 (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.
|
// 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.
|
// contains Arc<Mutex>'s that cannot be compared easily.
|
||||||
// They don't need to be compared anyway.
|
// They don't need to be compared anyway.
|
||||||
debug!("App | Checking diff between [og] & [state]");
|
debug!("App | Checking diff between [og] & [state]");
|
||||||
let og = self.og.lock().unwrap();
|
let og = lock!(self.og);
|
||||||
if og.gupax != self.state.gupax ||
|
if og.status != self.state.status ||
|
||||||
|
og.gupax != self.state.gupax ||
|
||||||
og.p2pool != self.state.p2pool ||
|
og.p2pool != self.state.p2pool ||
|
||||||
og.xmrig != self.state.xmrig ||
|
og.xmrig != self.state.xmrig ||
|
||||||
self.og_node_vec != self.node_vec ||
|
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);
|
let width = ((self.width/2.0)/4.0)-(SPACE*2.0);
|
||||||
// [Gupax Version]
|
// [Gupax Version]
|
||||||
// Is yellow if the user updated and should (but isn't required to) restart.
|
// 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),
|
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),
|
_ => 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]
|
// [Save/Reset]
|
||||||
ui.with_layout(Layout::right_to_left(Align::RIGHT), |ui| {
|
ui.with_layout(Layout::right_to_left(Align::RIGHT), |ui| {
|
||||||
let width = match self.tab {
|
let width = (ui.available_width()/3.0)-(SPACE*3.0);
|
||||||
Tab::Gupax => (ui.available_width()/2.0)-(SPACE*3.0),
|
|
||||||
_ => (ui.available_width()/3.0)-(SPACE*3.0),
|
|
||||||
};
|
|
||||||
ui.group(|ui| {
|
ui.group(|ui| {
|
||||||
ui.set_enabled(self.diff);
|
ui.set_enabled(self.diff);
|
||||||
let width = width / 2.0;
|
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() {
|
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.gupax = og.gupax;
|
||||||
self.state.p2pool = og.p2pool;
|
self.state.p2pool = og.p2pool;
|
||||||
self.state.xmrig = og.xmrig;
|
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() {
|
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) {
|
match State::save(&mut self.state, &self.state_path) {
|
||||||
Ok(_) => {
|
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.gupax = self.state.gupax.clone();
|
||||||
og.p2pool = self.state.p2pool.clone();
|
og.p2pool = self.state.p2pool.clone();
|
||||||
og.xmrig = self.state.xmrig.clone();
|
og.xmrig = self.state.xmrig.clone();
|
||||||
|
@ -1393,9 +1504,21 @@ impl eframe::App for App {
|
||||||
|
|
||||||
// [Simple/Advanced] + [Start/Stop/Restart]
|
// [Simple/Advanced] + [Start/Stop/Restart]
|
||||||
match self.tab {
|
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 => {
|
Tab::Gupax => {
|
||||||
ui.group(|ui| {
|
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() {
|
if ui.add_sized([width, height], SelectableLabel::new(!self.state.gupax.simple, "Advanced")).on_hover_text(GUPAX_ADVANCED).clicked() {
|
||||||
self.state.gupax.simple = false;
|
self.state.gupax.simple = false;
|
||||||
}
|
}
|
||||||
|
@ -1426,6 +1549,8 @@ impl eframe::App for App {
|
||||||
});
|
});
|
||||||
} else if p2pool_is_alive {
|
} else if p2pool_is_alive {
|
||||||
if key.is_up() && !wants_input || ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() {
|
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);
|
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() {
|
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);
|
ui.set_enabled(ui_enabled);
|
||||||
let color = if ui_enabled { GREEN } else { RED };
|
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() {
|
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();
|
lock!(self.og).update_absolute_path();
|
||||||
self.state.update_absolute_path(); // The above checks make sure this can unwrap safely, probably should handle it though.
|
self.state.update_absolute_path();
|
||||||
Helper::start_p2pool(&self.helper, &self.state.p2pool, &self.state.gupax.absolute_p2pool_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 {
|
} else if xmrig_is_alive {
|
||||||
if key.is_up() && !wants_input || ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart XMRig").clicked() {
|
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) {
|
if cfg!(windows) {
|
||||||
Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo));
|
Helper::restart_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo));
|
||||||
} else {
|
} else {
|
||||||
self.sudo.lock().unwrap().signal = ProcessSignal::Restart;
|
lock!(self.sudo).signal = ProcessSignal::Restart;
|
||||||
self.error_state.ask_sudo(&self.sudo);
|
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 key.is_down() && !wants_input || ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop XMRig").clicked() {
|
||||||
if cfg!(target_os = "macos") {
|
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);
|
self.error_state.ask_sudo(&self.sudo);
|
||||||
} else {
|
} else {
|
||||||
Helper::stop_xmrig(&self.helper);
|
Helper::stop_xmrig(&self.helper);
|
||||||
|
@ -1518,12 +1645,12 @@ impl eframe::App for App {
|
||||||
ui.set_enabled(ui_enabled);
|
ui.set_enabled(ui_enabled);
|
||||||
let color = if ui_enabled { GREEN } else { RED };
|
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() {
|
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();
|
lock!(self.og).update_absolute_path();
|
||||||
self.state.update_absolute_path(); // The above checks make sure this can unwrap safely, probably should handle it though.
|
self.state.update_absolute_path();
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
Helper::start_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo));
|
Helper::start_xmrig(&self.helper, &self.state.xmrig, &self.state.gupax.absolute_xmrig_path, Arc::clone(&self.sudo));
|
||||||
} else if cfg!(unix) {
|
} else if cfg!(unix) {
|
||||||
self.sudo.lock().unwrap().signal = ProcessSignal::Start;
|
lock!(self.sudo).signal = ProcessSignal::Start;
|
||||||
self.error_state.ask_sudo(&self.sudo);
|
self.error_state.ask_sudo(&self.sudo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1553,6 +1680,8 @@ impl eframe::App for App {
|
||||||
let distro = true;
|
let distro = true;
|
||||||
#[cfg(not(feature = "distro"))]
|
#[cfg(not(feature = "distro"))]
|
||||||
let distro = false;
|
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!(
|
let debug_info = format!(
|
||||||
"Gupax version: {}\n
|
"Gupax version: {}\n
|
||||||
Bundled P2Pool version: {}\n
|
Bundled P2Pool version: {}\n
|
||||||
|
@ -1581,6 +1710,8 @@ XMRig console byte length: {}\n
|
||||||
{:#?}\n
|
{:#?}\n
|
||||||
------------------------------------------ XMRIG IMAGE ------------------------------------------
|
------------------------------------------ XMRIG IMAGE ------------------------------------------
|
||||||
{:#?}\n
|
{:#?}\n
|
||||||
|
------------------------------------------ GUPAX-P2POOL API ------------------------------------------
|
||||||
|
{:#?}\n
|
||||||
------------------------------------------ WORKING STATE ------------------------------------------
|
------------------------------------------ WORKING STATE ------------------------------------------
|
||||||
{:#?}\n
|
{:#?}\n
|
||||||
------------------------------------------ ORIGINAL STATE ------------------------------------------
|
------------------------------------------ ORIGINAL STATE ------------------------------------------
|
||||||
|
@ -1589,10 +1720,10 @@ XMRig console byte length: {}\n
|
||||||
P2POOL_VERSION,
|
P2POOL_VERSION,
|
||||||
XMRIG_VERSION,
|
XMRIG_VERSION,
|
||||||
self.now.elapsed().as_secs_f32(),
|
self.now.elapsed().as_secs_f32(),
|
||||||
self.state.gupax.selected_width as u16,
|
self.state.gupax.selected_width,
|
||||||
self.state.gupax.selected_height as u16,
|
self.state.gupax.selected_height,
|
||||||
self.width as u16,
|
self.width,
|
||||||
self.height as u16,
|
self.height,
|
||||||
OS_NAME,
|
OS_NAME,
|
||||||
self.max_threads,
|
self.max_threads,
|
||||||
self.pid,
|
self.pid,
|
||||||
|
@ -1608,12 +1739,13 @@ XMRig console byte length: {}\n
|
||||||
self.exe,
|
self.exe,
|
||||||
self.state.gupax.absolute_p2pool_path.display(),
|
self.state.gupax.absolute_p2pool_path.display(),
|
||||||
self.state.gupax.absolute_xmrig_path.display(),
|
self.state.gupax.absolute_xmrig_path.display(),
|
||||||
self.p2pool_api.lock().unwrap().output.len(),
|
p2pool_gui_len,
|
||||||
self.xmrig_api.lock().unwrap().output.len(),
|
xmrig_gui_len,
|
||||||
self.p2pool_img.lock().unwrap(),
|
lock!(self.p2pool_img),
|
||||||
self.xmrig_img.lock().unwrap(),
|
lock!(self.xmrig_img),
|
||||||
|
lock!(self.gupax_p2pool_api),
|
||||||
self.state,
|
self.state,
|
||||||
self.og.lock().unwrap(),
|
lock!(self.og),
|
||||||
);
|
);
|
||||||
self.error_state.set(debug_info, ErrorFerris::Cute, ErrorButtons::Debug);
|
self.error_state.set(debug_info, ErrorFerris::Cute, ErrorButtons::Debug);
|
||||||
}
|
}
|
||||||
|
@ -1646,19 +1778,19 @@ XMRig console byte length: {}\n
|
||||||
}
|
}
|
||||||
Tab::Status => {
|
Tab::Status => {
|
||||||
debug!("App | Entering [Status] Tab");
|
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 => {
|
Tab::Gupax => {
|
||||||
debug!("App | Entering [Gupax] Tab");
|
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 => {
|
Tab::P2pool => {
|
||||||
debug!("App | Entering [P2Pool] Tab");
|
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 => {
|
Tab::Xmrig => {
|
||||||
debug!("App | Entering [XMRig] Tab");
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
31
src/node.rs
31
src/node.rs
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::*,
|
constants::*,
|
||||||
|
macros::*,
|
||||||
};
|
};
|
||||||
use serde::{Serialize,Deserialize};
|
use serde::{Serialize,Deserialize};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
@ -310,19 +311,19 @@ impl Ping {
|
||||||
match Self::ping(&ping) {
|
match Self::ping(&ping) {
|
||||||
Ok(msg) => {
|
Ok(msg) => {
|
||||||
info!("Ping ... OK");
|
info!("Ping ... OK");
|
||||||
ping.lock().unwrap().msg = msg;
|
lock!(ping).msg = msg;
|
||||||
ping.lock().unwrap().pinged = true;
|
lock!(ping).pinged = true;
|
||||||
ping.lock().unwrap().auto_selected = false;
|
lock!(ping).auto_selected = false;
|
||||||
ping.lock().unwrap().prog = 100.0;
|
lock!(ping).prog = 100.0;
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Ping ... FAIL ... {}", err);
|
error!("Ping ... FAIL ... {}", err);
|
||||||
ping.lock().unwrap().pinged = false;
|
lock!(ping).pinged = false;
|
||||||
ping.lock().unwrap().msg = err.to_string();
|
lock!(ping).msg = err.to_string();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
info!("Ping ... Took [{}] seconds...", now.elapsed().as_secs_f32());
|
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> {
|
pub async fn ping(ping: &Arc<Mutex<Self>>) -> Result<String, anyhow::Error> {
|
||||||
// Start ping
|
// Start ping
|
||||||
let ping = Arc::clone(ping);
|
let ping = Arc::clone(ping);
|
||||||
ping.lock().unwrap().pinging = true;
|
lock!(ping).pinging = true;
|
||||||
ping.lock().unwrap().prog = 0.0;
|
lock!(ping).prog = 0.0;
|
||||||
let percent = (100.0 / (COMMUNITY_NODE_LENGTH as f32)).floor();
|
let percent = (100.0 / (COMMUNITY_NODE_LENGTH as f32)).floor();
|
||||||
|
|
||||||
// Create HTTP client
|
// Create HTTP client
|
||||||
let info = "Creating HTTP Client".to_string();
|
let info = "Creating HTTP Client".to_string();
|
||||||
ping.lock().unwrap().msg = info;
|
lock!(ping).msg = info;
|
||||||
let client: Client<HttpConnector> = Client::builder()
|
let client: Client<HttpConnector> = Client::builder()
|
||||||
.build(HttpConnector::new());
|
.build(HttpConnector::new());
|
||||||
|
|
||||||
|
@ -361,7 +362,7 @@ impl Ping {
|
||||||
let rand_user_agent = crate::Pkg::get_user_agent();
|
let rand_user_agent = crate::Pkg::get_user_agent();
|
||||||
// Handle vector
|
// Handle vector
|
||||||
let mut handles = Vec::with_capacity(COMMUNITY_NODE_LENGTH);
|
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 {
|
for ip in NODE_IPS {
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
|
@ -381,12 +382,12 @@ impl Ping {
|
||||||
handle.await?;
|
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 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();
|
let info = "Cleaning up connections".to_string();
|
||||||
info!("Ping | {}...", info);
|
info!("Ping | {}...", info);
|
||||||
let mut ping = ping.lock().unwrap();
|
let mut ping = lock!(ping);
|
||||||
ping.fastest = node_vec[0].id;
|
ping.fastest = node_vec[0].id;
|
||||||
ping.nodes = node_vec;
|
ping.nodes = node_vec;
|
||||||
ping.msg = info;
|
ping.msg = info;
|
||||||
|
@ -421,11 +422,11 @@ impl Ping {
|
||||||
} else {
|
} else {
|
||||||
color = BLACK;
|
color = BLACK;
|
||||||
}
|
}
|
||||||
let mut ping = ping.lock().unwrap();
|
let mut ping = lock!(ping);
|
||||||
ping.msg = info;
|
ping.msg = info;
|
||||||
ping.prog += percent;
|
ping.prog += percent;
|
||||||
drop(ping);
|
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, });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ use crate::{
|
||||||
disk::*,
|
disk::*,
|
||||||
node::*,
|
node::*,
|
||||||
helper::*,
|
helper::*,
|
||||||
|
macros::*,
|
||||||
};
|
};
|
||||||
use egui::{
|
use egui::{
|
||||||
TextEdit,SelectableLabel,ComboBox,Label,Button,
|
TextEdit,SelectableLabel,ComboBox,Label,Button,
|
||||||
|
@ -31,7 +32,7 @@ use std::sync::{Arc,Mutex};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use log::*;
|
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) {
|
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;
|
let text_edit = height / 25.0;
|
||||||
//---------------------------------------------------------------------------------------------------- [Simple] Console
|
//---------------------------------------------------------------------------------------------------- [Simple] Console
|
||||||
|
@ -44,7 +45,7 @@ impl P2pool {
|
||||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
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, _| {
|
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
|
//---------------------------------------------------------------------------------------------------- [Advanced] Console
|
||||||
|
@ -55,7 +56,7 @@ impl P2pool {
|
||||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
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, _| {
|
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();
|
ui.separator();
|
||||||
|
@ -64,7 +65,7 @@ impl P2pool {
|
||||||
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
|
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
|
||||||
response.request_focus(); // Get focus back
|
response.request_focus(); // Get focus back
|
||||||
let buffer = std::mem::take(buffer); // Take buffer
|
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
|
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
|
// Two atomic bools = enough to represent this data
|
||||||
debug!("P2Pool Tab | Running [auto-select] check");
|
debug!("P2Pool Tab | Running [auto-select] check");
|
||||||
if self.auto_select {
|
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 we haven't auto_selected yet, auto-select and turn it off
|
||||||
if ping.pinged && !ping.auto_selected {
|
if ping.pinged && !ping.auto_selected {
|
||||||
self.node = ping.fastest;
|
self.node = ping.fastest;
|
||||||
|
@ -139,8 +140,8 @@ impl P2pool {
|
||||||
let ip = enum_to_ip(id);
|
let ip = enum_to_ip(id);
|
||||||
let mut ms = 0;
|
let mut ms = 0;
|
||||||
let mut color = Color32::LIGHT_GRAY;
|
let mut color = Color32::LIGHT_GRAY;
|
||||||
if ping.lock().unwrap().pinged {
|
if lock!(ping).pinged {
|
||||||
for data in ping.lock().unwrap().nodes.iter() {
|
for data in lock!(ping).nodes.iter() {
|
||||||
if data.id == id {
|
if data.id == id {
|
||||||
ms = data.ms;
|
ms = data.ms;
|
||||||
color = data.color;
|
color = data.color;
|
||||||
|
@ -151,7 +152,7 @@ impl P2pool {
|
||||||
debug!("P2Pool Tab | Rendering [ComboBox] of Community Nodes");
|
debug!("P2Pool Tab | Rendering [ComboBox] of Community Nodes");
|
||||||
let text = RichText::new(format!(" ⏺ {}ms | {} | {}", ms, id, ip)).color(color);
|
let text = RichText::new(format!(" ⏺ {}ms | {} | {}", ms, id, ip)).color(color);
|
||||||
ComboBox::from_id_source("community_nodes").selected_text(RichText::text_style(text, Monospace)).show_ui(ui, |ui| {
|
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 ms = crate::node::format_ms(data.ms);
|
||||||
let id = crate::node::format_enum(data.id);
|
let id = crate::node::format_enum(data.id);
|
||||||
let text = RichText::text_style(RichText::new(format!(" ⏺ {} | {} | {}", ms, id, data.ip)).color(data.color), Monospace);
|
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);
|
self.node = NodeEnum::get_random(&self.node);
|
||||||
}
|
}
|
||||||
// [Select fastest 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 {
|
if ui.add_sized([width, height], Button::new("Select fastest node")).on_hover_text(P2POOL_SELECT_FASTEST).clicked() && lock!(ping).pinged {
|
||||||
self.node = ping.lock().unwrap().fastest;
|
self.node = lock!(ping).fastest;
|
||||||
}
|
}
|
||||||
// [Ping Button]
|
// [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() {
|
if ui.add_sized([width, height], Button::new("Ping community nodes")).on_hover_text(P2POOL_PING).clicked() {
|
||||||
Ping::spawn_thread(ping);
|
Ping::spawn_thread(ping);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// [Last <-]
|
// [Last <-]
|
||||||
if ui.add_sized([width, height], Button::new("⬅ Last")).on_hover_text(P2POOL_SELECT_LAST).clicked() {
|
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 {
|
match ping.pinged {
|
||||||
true => self.node = NodeEnum::get_last_from_ping(&self.node, &ping.nodes),
|
true => self.node = NodeEnum::get_last_from_ping(&self.node, &ping.nodes),
|
||||||
false => self.node = NodeEnum::get_last(&self.node),
|
false => self.node = NodeEnum::get_last(&self.node),
|
||||||
|
@ -190,7 +191,7 @@ impl P2pool {
|
||||||
}
|
}
|
||||||
// [Next ->]
|
// [Next ->]
|
||||||
if ui.add_sized([width, height], Button::new("Next ➡")).on_hover_text(P2POOL_SELECT_NEXT).clicked() {
|
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 {
|
match ping.pinged {
|
||||||
true => self.node = NodeEnum::get_next_from_ping(&self.node, &ping.nodes),
|
true => self.node = NodeEnum::get_next_from_ping(&self.node, &ping.nodes),
|
||||||
false => self.node = NodeEnum::get_next(&self.node),
|
false => self.node = NodeEnum::get_next(&self.node),
|
||||||
|
@ -201,10 +202,10 @@ impl P2pool {
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
let height = height / 2.0;
|
let height = height / 2.0;
|
||||||
let pinging = ping.lock().unwrap().pinging;
|
let pinging = lock!(ping).pinging;
|
||||||
ui.set_enabled(pinging);
|
ui.set_enabled(pinging);
|
||||||
let prog = ping.lock().unwrap().prog.round();
|
let prog = lock!(ping).prog.round();
|
||||||
let msg = RichText::text_style(RichText::new(format!("{} ... {}%", ping.lock().unwrap().msg, prog)), Monospace);
|
let msg = RichText::text_style(RichText::new(format!("{} ... {}%", lock!(ping).msg, prog)), Monospace);
|
||||||
let height = height / 1.25;
|
let height = height / 1.25;
|
||||||
ui.add_space(5.0);
|
ui.add_space(5.0);
|
||||||
ui.add_sized([width, height], Label::new(msg));
|
ui.add_sized([width, height], Label::new(msg));
|
||||||
|
|
128
src/regex.rs
Normal file
128
src/regex.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
171
src/status.rs
171
src/status.rs
|
@ -22,6 +22,12 @@ use crate::{
|
||||||
ImgXmrig,
|
ImgXmrig,
|
||||||
constants::*,
|
constants::*,
|
||||||
Sys,
|
Sys,
|
||||||
|
Hash,
|
||||||
|
Submenu,
|
||||||
|
macros::*,
|
||||||
|
GupaxP2poolApi,
|
||||||
|
PayoutView,
|
||||||
|
human::HumanNumber,
|
||||||
};
|
};
|
||||||
use std::sync::{Arc,Mutex};
|
use std::sync::{Arc,Mutex};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
@ -29,14 +35,15 @@ use egui::{
|
||||||
Label,RichText,TextStyle,
|
Label,RichText,TextStyle,
|
||||||
TextStyle::Monospace,
|
TextStyle::Monospace,
|
||||||
TextStyle::Name,
|
TextStyle::Name,
|
||||||
|
TextEdit,
|
||||||
|
SelectableLabel,
|
||||||
|
Slider,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Main data structure for the Status tab
|
impl crate::disk::Status {
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
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) {
|
||||||
pub struct Status {}
|
//---------------------------------------------------------------------------------------------------- [Processes]
|
||||||
|
if self.submenu == Submenu::Processes {
|
||||||
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) {
|
|
||||||
let width = (width/3.0)-(SPACE*1.666);
|
let width = (width/3.0)-(SPACE*1.666);
|
||||||
let min_height = height/1.1;
|
let min_height = height/1.1;
|
||||||
let height = height/25.0;
|
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.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.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);
|
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(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(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);
|
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");
|
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;
|
let height = height/1.4;
|
||||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
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(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(format!("{}", api.uptime)));
|
||||||
ui.add_sized([width, height], Label::new(RichText::new("Shares Found").underline().color(BONE))).on_hover_text(STATUS_P2POOL_SHARES);
|
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(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(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)));
|
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(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(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);
|
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.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.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);
|
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(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(format!("{}", api.uptime)));
|
||||||
ui.add_sized([width, height], Label::new(RichText::new("CPU Load Averages").underline().color(BONE))).on_hover_text(STATUS_XMRIG_CPU);
|
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(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(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(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(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);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
src/sudo.rs
29
src/sudo.rs
|
@ -32,6 +32,7 @@ use crate::{
|
||||||
disk::Xmrig,
|
disk::Xmrig,
|
||||||
ProcessSignal,
|
ProcessSignal,
|
||||||
constants::*,
|
constants::*,
|
||||||
|
macros::*,
|
||||||
};
|
};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ impl SudoState {
|
||||||
// Resets the state.
|
// Resets the state.
|
||||||
pub fn reset(state: &Arc<Mutex<Self>>) {
|
pub fn reset(state: &Arc<Mutex<Self>>) {
|
||||||
Self::wipe(state);
|
Self::wipe(state);
|
||||||
let mut state = state.lock().unwrap();
|
let mut state = lock!(state);
|
||||||
state.testing = false;
|
state.testing = false;
|
||||||
state.success = false;
|
state.success = false;
|
||||||
// state.signal = ProcessSignal::None;
|
// state.signal = ProcessSignal::None;
|
||||||
|
@ -92,7 +93,7 @@ impl SudoState {
|
||||||
pub fn wipe(state: &Arc<Mutex<Self>>) {
|
pub fn wipe(state: &Arc<Mutex<Self>>) {
|
||||||
let mut new = String::with_capacity(256);
|
let mut new = String::with_capacity(256);
|
||||||
// new is now == old, and vice-versa.
|
// 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.
|
// we're wiping & dropping the old pass here.
|
||||||
new.zeroize();
|
new.zeroize();
|
||||||
std::mem::drop(new);
|
std::mem::drop(new);
|
||||||
|
@ -108,7 +109,7 @@ impl SudoState {
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
// Set to testing
|
// Set to testing
|
||||||
state.lock().unwrap().testing = true;
|
lock!(state).testing = true;
|
||||||
|
|
||||||
// Make sure sudo timestamp is reset
|
// Make sure sudo timestamp is reset
|
||||||
let reset = Command::new("sudo")
|
let reset = Command::new("sudo")
|
||||||
|
@ -122,8 +123,8 @@ impl SudoState {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Sudo | Couldn't reset timestamp: {}", e);
|
error!("Sudo | Couldn't reset timestamp: {}", e);
|
||||||
Self::wipe(&state);
|
Self::wipe(&state);
|
||||||
state.lock().unwrap().msg = format!("Sudo error: {}", e);
|
lock!(state).msg = format!("Sudo error: {}", e);
|
||||||
state.lock().unwrap().testing = false;
|
lock!(state).testing = false;
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -139,7 +140,7 @@ impl SudoState {
|
||||||
|
|
||||||
// Write pass to STDIN
|
// Write pass to STDIN
|
||||||
let mut stdin = sudo.stdin.take().unwrap();
|
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);
|
drop(stdin);
|
||||||
|
|
||||||
// Sudo re-prompts and will hang.
|
// Sudo re-prompts and will hang.
|
||||||
|
@ -149,7 +150,7 @@ impl SudoState {
|
||||||
match sudo.try_wait() {
|
match sudo.try_wait() {
|
||||||
Ok(Some(code)) => if code.success() {
|
Ok(Some(code)) => if code.success() {
|
||||||
info!("Sudo | Password ... OK!");
|
info!("Sudo | Password ... OK!");
|
||||||
state.lock().unwrap().success = true;
|
lock!(state).success = true;
|
||||||
break
|
break
|
||||||
},
|
},
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
|
@ -159,25 +160,25 @@ impl SudoState {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Sudo | Couldn't reset timestamp: {}", e);
|
error!("Sudo | Couldn't reset timestamp: {}", e);
|
||||||
Self::wipe(&state);
|
Self::wipe(&state);
|
||||||
state.lock().unwrap().msg = format!("Sudo error: {}", e);
|
lock!(state).msg = format!("Sudo error: {}", e);
|
||||||
state.lock().unwrap().testing = false;
|
lock!(state).testing = false;
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Err(e) = sudo.kill() { warn!("Sudo | Kill error (it probably already exited): {}", e); }
|
if let Err(e) = sudo.kill() { warn!("Sudo | Kill error (it probably already exited): {}", e); }
|
||||||
if state.lock().unwrap().success {
|
if lock!(state).success {
|
||||||
match state.lock().unwrap().signal {
|
match lock!(state).signal {
|
||||||
ProcessSignal::Restart => crate::helper::Helper::restart_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
|
ProcessSignal::Restart => crate::helper::Helper::restart_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
|
||||||
ProcessSignal::Stop => crate::helper::Helper::stop_xmrig(&helper),
|
ProcessSignal::Stop => crate::helper::Helper::stop_xmrig(&helper),
|
||||||
_ => crate::helper::Helper::start_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
|
_ => crate::helper::Helper::start_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
Self::wipe(&state);
|
||||||
}
|
}
|
||||||
state.lock().unwrap().signal = ProcessSignal::None;
|
lock!(state).signal = ProcessSignal::None;
|
||||||
state.lock().unwrap().testing = false;
|
lock!(state).testing = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
137
src/update.rs
137
src/update.rs
|
@ -33,6 +33,7 @@ use crate::{
|
||||||
update::Name::*,
|
update::Name::*,
|
||||||
ErrorState,ErrorFerris,ErrorButtons,
|
ErrorState,ErrorFerris,ErrorButtons,
|
||||||
Restart,
|
Restart,
|
||||||
|
macros::*,
|
||||||
};
|
};
|
||||||
use hyper::{
|
use hyper::{
|
||||||
Client,Body,Request,
|
Client,Body,Request,
|
||||||
|
@ -265,9 +266,9 @@ impl Update {
|
||||||
path_p2pool: path_p2pool.display().to_string(),
|
path_p2pool: path_p2pool.display().to_string(),
|
||||||
path_xmrig: path_xmrig.display().to_string(),
|
path_xmrig: path_xmrig.display().to_string(),
|
||||||
tmp_dir: "".to_string(),
|
tmp_dir: "".to_string(),
|
||||||
updating: Arc::new(Mutex::new(false)),
|
updating: arc_mut!(false),
|
||||||
prog: Arc::new(Mutex::new(0.0)),
|
prog: arc_mut!(0.0),
|
||||||
msg: Arc::new(Mutex::new(MSG_NONE.to_string())),
|
msg: arc_mut!(MSG_NONE.to_string()),
|
||||||
tor,
|
tor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,13 +393,13 @@ impl Update {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
update.lock().unwrap().path_p2pool = p2pool_path.display().to_string();
|
lock!(update).path_p2pool = p2pool_path.display().to_string();
|
||||||
update.lock().unwrap().path_xmrig = xmrig_path.display().to_string();
|
lock!(update).path_xmrig = xmrig_path.display().to_string();
|
||||||
update.lock().unwrap().tor = gupax.update_via_tor;
|
lock!(update).tor = gupax.update_via_tor;
|
||||||
|
|
||||||
// Clone before thread spawn
|
// Clone before thread spawn
|
||||||
let og = Arc::clone(og);
|
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 state_path = state_path.to_path_buf();
|
||||||
let update = Arc::clone(update);
|
let update = Arc::clone(update);
|
||||||
let restart = Arc::clone(restart);
|
let restart = Arc::clone(restart);
|
||||||
|
@ -407,23 +408,23 @@ impl Update {
|
||||||
match Update::start(update.clone(), og.clone(), state_ver.clone(), restart) {
|
match Update::start(update.clone(), og.clone(), state_ver.clone(), restart) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Update | Saving state...");
|
info!("Update | Saving state...");
|
||||||
let original_version = og.lock().unwrap().version.clone();
|
let original_version = lock!(og).version.clone();
|
||||||
og.lock().unwrap().version = state_ver;
|
lock!(og).version = state_ver;
|
||||||
match State::save(&mut og.lock().unwrap(), &state_path) {
|
match State::save(&mut lock!(og), &state_path) {
|
||||||
Ok(_) => info!("Update ... OK"),
|
Ok(_) => info!("Update ... OK"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Update | Saving state ... FAIL: {}", e);
|
warn!("Update | Saving state ... FAIL: {}", e);
|
||||||
og.lock().unwrap().version = original_version;
|
lock!(og).version = original_version;
|
||||||
*update.lock().unwrap().msg.lock().unwrap() = "Saving new versions into state failed".to_string();
|
*lock2!(update,msg) = "Saving new versions into state failed".to_string();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("Update ... FAIL: {}", 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"));
|
return Err(anyhow!("This is the [Linux distro] version of Gupax, updates are disabled"));
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Init
|
//---------------------------------------------------------------------------------------------------- Init
|
||||||
*update.lock().unwrap().updating.lock().unwrap() = true;
|
*lock2!(update,updating) = true;
|
||||||
// Set timer
|
// Set timer
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
// Set progress bar
|
// Set progress bar
|
||||||
*update.lock().unwrap().msg.lock().unwrap() = MSG_START.to_string();
|
*lock2!(update,msg) = MSG_START.to_string();
|
||||||
*update.lock().unwrap().prog.lock().unwrap() = 0.0;
|
*lock2!(update,prog) = 0.0;
|
||||||
info!("Update | {}", INIT);
|
info!("Update | {}", INIT);
|
||||||
|
|
||||||
// Get temporary directory
|
// Get temporary directory
|
||||||
let msg = MSG_TMP.to_string();
|
let msg = MSG_TMP.to_string();
|
||||||
info!("Update | {}", msg);
|
info!("Update | {}", msg);
|
||||||
*update.lock().unwrap().msg.lock().unwrap() = msg;
|
*lock2!(update,msg) = msg;
|
||||||
let tmp_dir = Self::get_tmp_dir()?;
|
let tmp_dir = Self::get_tmp_dir()?;
|
||||||
std::fs::create_dir(&tmp_dir)?;
|
std::fs::create_dir(&tmp_dir)?;
|
||||||
|
|
||||||
|
@ -467,27 +468,27 @@ impl Update {
|
||||||
|
|
||||||
// Generate fake user-agent
|
// Generate fake user-agent
|
||||||
let user_agent = Pkg::get_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
|
// Create Tor/HTTPS client
|
||||||
let lock = update.lock().unwrap();
|
let lock = lock!(update);
|
||||||
let tor = lock.tor;
|
let tor = lock.tor;
|
||||||
if tor {
|
if tor {
|
||||||
let msg = MSG_TOR.to_string();
|
let msg = MSG_TOR.to_string();
|
||||||
info!("Update | {}", msg);
|
info!("Update | {}", msg);
|
||||||
*lock.msg.lock().unwrap() = msg;
|
*lock!(lock.msg) = msg;
|
||||||
} else {
|
} else {
|
||||||
let msg = MSG_HTTPS.to_string();
|
let msg = MSG_HTTPS.to_string();
|
||||||
info!("Update | {}", msg);
|
info!("Update | {}", msg);
|
||||||
*lock.msg.lock().unwrap() = msg;
|
*lock!(lock.msg) = msg;
|
||||||
}
|
}
|
||||||
drop(lock);
|
drop(lock);
|
||||||
let mut client = Self::get_client(tor)?;
|
let mut client = Self::get_client(tor)?;
|
||||||
*update.lock().unwrap().prog.lock().unwrap() += 5.0;
|
*lock2!(update,prog) += 5.0;
|
||||||
info!("Update | Init ... OK ... {}%", update.lock().unwrap().prog.lock().unwrap());
|
info!("Update | Init ... OK ... {}%", lock2!(update,prog));
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Metadata
|
//---------------------------------------------------------------------------------------------------- Metadata
|
||||||
*update.lock().unwrap().msg.lock().unwrap() = MSG_METADATA.to_string();
|
*lock2!(update,msg) = MSG_METADATA.to_string();
|
||||||
info!("Update | {}", METADATA);
|
info!("Update | {}", METADATA);
|
||||||
let mut vec2 = vec![];
|
let mut vec2 = vec![];
|
||||||
// Loop process:
|
// Loop process:
|
||||||
|
@ -502,7 +503,7 @@ impl Update {
|
||||||
// function itself but for some reason, it was getting skipped over,
|
// function itself but for some reason, it was getting skipped over,
|
||||||
// so the [new_ver] check is now here, in the outer scope.
|
// so the [new_ver] check is now here, in the outer scope.
|
||||||
for i in 1..=3 {
|
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![];
|
let mut handles: Vec<JoinHandle<Result<(), anyhow::Error>>> = vec![];
|
||||||
for pkg in vec.iter() {
|
for pkg in vec.iter() {
|
||||||
// Clone data before sending to async
|
// Clone data before sending to async
|
||||||
|
@ -528,13 +529,13 @@ impl Update {
|
||||||
// Check for empty version
|
// Check for empty version
|
||||||
let mut indexes = vec![];
|
let mut indexes = vec![];
|
||||||
for (index, pkg) in vec.iter().enumerate() {
|
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);
|
warn!("Update | {} failed, attempt [{}/3]...", pkg.name, i+1);
|
||||||
} else {
|
} else {
|
||||||
indexes.push(index);
|
indexes.push(index);
|
||||||
vec2.push(pkg.clone());
|
vec2.push(pkg.clone());
|
||||||
*update.lock().unwrap().prog.lock().unwrap() += 10.0;
|
*lock2!(update,prog) += 10.0;
|
||||||
info!("Update | {} {} ... OK", pkg.name, pkg.new_ver.lock().unwrap());
|
info!("Update | {} {} ... OK", pkg.name, lock!(pkg.new_ver));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Order indexes from biggest to smallest
|
// Order indexes from biggest to smallest
|
||||||
|
@ -553,19 +554,19 @@ impl Update {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if vec.is_empty() {
|
if vec.is_empty() {
|
||||||
info!("Update | Metadata ... OK ... {}%", update.lock().unwrap().prog.lock().unwrap());
|
info!("Update | Metadata ... OK ... {}%", lock2!(update,prog));
|
||||||
} else {
|
} else {
|
||||||
error!("Update | Metadata ... FAIL");
|
error!("Update | Metadata ... FAIL");
|
||||||
return Err(anyhow!("Metadata fetch failed"))
|
return Err(anyhow!("Metadata fetch failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Compare
|
//---------------------------------------------------------------------------------------------------- Compare
|
||||||
*update.lock().unwrap().msg.lock().unwrap() = MSG_COMPARE.to_string();
|
*lock2!(update,msg) = MSG_COMPARE.to_string();
|
||||||
info!("Update | {}", COMPARE);
|
info!("Update | {}", COMPARE);
|
||||||
let mut vec3 = vec![];
|
let mut vec3 = vec![];
|
||||||
let mut new_pkgs = vec![];
|
let mut new_pkgs = vec![];
|
||||||
for pkg in vec2.iter() {
|
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 diff;
|
||||||
let old_ver;
|
let old_ver;
|
||||||
let name;
|
let name;
|
||||||
|
@ -575,17 +576,17 @@ impl Update {
|
||||||
// that gets updated during an update. This prevents the updater always thinking
|
// 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
|
// 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).
|
// 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;
|
diff = old_ver != new_ver && GUPAX_VERSION != new_ver;
|
||||||
name = "Gupax";
|
name = "Gupax";
|
||||||
}
|
}
|
||||||
P2pool => {
|
P2pool => {
|
||||||
old_ver = state_ver.lock().unwrap().p2pool.clone();
|
old_ver = lock!(state_ver).p2pool.clone();
|
||||||
diff = old_ver != new_ver;
|
diff = old_ver != new_ver;
|
||||||
name = "P2Pool";
|
name = "P2Pool";
|
||||||
}
|
}
|
||||||
Xmrig => {
|
Xmrig => {
|
||||||
old_ver = state_ver.lock().unwrap().xmrig.clone();
|
old_ver = lock!(state_ver).xmrig.clone();
|
||||||
diff = old_ver != new_ver;
|
diff = old_ver != new_ver;
|
||||||
name = "XMRig";
|
name = "XMRig";
|
||||||
}
|
}
|
||||||
|
@ -598,32 +599,32 @@ impl Update {
|
||||||
info!("Update | {} {} == {} ... SKIPPING", pkg.name, old_ver, new_ver);
|
info!("Update | {} {} == {} ... SKIPPING", pkg.name, old_ver, new_ver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*update.lock().unwrap().prog.lock().unwrap() += 5.0;
|
*lock2!(update,prog) += 5.0;
|
||||||
info!("Update | Compare ... OK ... {}%", update.lock().unwrap().prog.lock().unwrap());
|
info!("Update | Compare ... OK ... {}%", lock2!(update,prog));
|
||||||
|
|
||||||
// Return if 0 (all packages up-to-date)
|
// Return if 0 (all packages up-to-date)
|
||||||
// Get amount of packages to divide up the percentage increases
|
// Get amount of packages to divide up the percentage increases
|
||||||
let pkg_amount = vec3.len() as f32;
|
let pkg_amount = vec3.len() as f32;
|
||||||
if pkg_amount == 0.0 {
|
if pkg_amount == 0.0 {
|
||||||
info!("Update | All packages up-to-date ... RETURNING");
|
info!("Update | All packages up-to-date ... RETURNING");
|
||||||
*update.lock().unwrap().prog.lock().unwrap() = 100.0;
|
*lock2!(update,prog) = 100.0;
|
||||||
*update.lock().unwrap().msg.lock().unwrap() = MSG_UP_TO_DATE.to_string();
|
*lock2!(update,msg) = MSG_UP_TO_DATE.to_string();
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
let new_pkgs: String = new_pkgs.concat();
|
let new_pkgs: String = new_pkgs.concat();
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Download
|
//---------------------------------------------------------------------------------------------------- Download
|
||||||
*update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_DOWNLOAD, new_pkgs);
|
*lock2!(update,msg) = format!("{}{}", MSG_DOWNLOAD, new_pkgs);
|
||||||
info!("Update | {}", DOWNLOAD);
|
info!("Update | {}", DOWNLOAD);
|
||||||
let mut vec4 = vec![];
|
let mut vec4 = vec![];
|
||||||
for i in 1..=3 {
|
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![];
|
let mut handles: Vec<JoinHandle<Result<(), anyhow::Error>>> = vec![];
|
||||||
for pkg in vec3.iter() {
|
for pkg in vec3.iter() {
|
||||||
// Clone data before async
|
// Clone data before async
|
||||||
let bytes = Arc::clone(&pkg.bytes);
|
let bytes = Arc::clone(&pkg.bytes);
|
||||||
let client = client.clone();
|
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
|
// 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
|
// 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
|
// XMRig doesn't have a [v], so slice it out
|
||||||
|
@ -647,12 +648,12 @@ impl Update {
|
||||||
// Check for empty bytes
|
// Check for empty bytes
|
||||||
let mut indexes = vec![];
|
let mut indexes = vec![];
|
||||||
for (index, pkg) in vec3.iter().enumerate() {
|
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);
|
warn!("Update | {} failed, attempt [{}/3]...", pkg.name, i);
|
||||||
} else {
|
} else {
|
||||||
indexes.push(index);
|
indexes.push(index);
|
||||||
vec4.push(pkg.clone());
|
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);
|
info!("Update | {} ... OK", pkg.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -666,14 +667,14 @@ impl Update {
|
||||||
if vec3.is_empty() { break }
|
if vec3.is_empty() { break }
|
||||||
}
|
}
|
||||||
if vec3.is_empty() {
|
if vec3.is_empty() {
|
||||||
info!("Update | Download ... OK ... {}%", *update.lock().unwrap().prog.lock().unwrap());
|
info!("Update | Download ... OK ... {}%", *lock2!(update,prog));
|
||||||
} else {
|
} else {
|
||||||
error!("Update | Download ... FAIL");
|
error!("Update | Download ... FAIL");
|
||||||
return Err(anyhow!("Download failed"))
|
return Err(anyhow!("Download failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Extract
|
//---------------------------------------------------------------------------------------------------- Extract
|
||||||
*update.lock().unwrap().msg.lock().unwrap() = format!("{}{}", MSG_EXTRACT, new_pkgs);
|
*lock2!(update,msg) = format!("{}{}", MSG_EXTRACT, new_pkgs);
|
||||||
info!("Update | {}", EXTRACT);
|
info!("Update | {}", EXTRACT);
|
||||||
for pkg in vec4.iter() {
|
for pkg in vec4.iter() {
|
||||||
let tmp = match pkg.name {
|
let tmp = match pkg.name {
|
||||||
|
@ -681,20 +682,20 @@ impl Update {
|
||||||
_ => tmp_dir.to_owned() + &pkg.name.to_string(),
|
_ => tmp_dir.to_owned() + &pkg.name.to_string(),
|
||||||
};
|
};
|
||||||
#[cfg(target_os = "windows")]
|
#[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")]
|
#[cfg(target_family = "unix")]
|
||||||
tar::Archive::new(flate2::read::GzDecoder::new(pkg.bytes.lock().unwrap().as_ref())).unpack(tmp)?;
|
tar::Archive::new(flate2::read::GzDecoder::new(lock!(pkg.bytes).as_ref())).unpack(tmp)?;
|
||||||
*update.lock().unwrap().prog.lock().unwrap() += (5.0 / pkg_amount).round();
|
*lock2!(update,prog) += (5.0 / pkg_amount).round();
|
||||||
info!("Update | {} ... OK", pkg.name);
|
info!("Update | {} ... OK", pkg.name);
|
||||||
}
|
}
|
||||||
info!("Update | Extract ... OK ... {}%", *update.lock().unwrap().prog.lock().unwrap());
|
info!("Update | Extract ... OK ... {}%", *lock2!(update,prog));
|
||||||
|
|
||||||
//---------------------------------------------------------------------------------------------------- Upgrade
|
//---------------------------------------------------------------------------------------------------- Upgrade
|
||||||
// 1. Walk directories
|
// 1. Walk directories
|
||||||
// 2. If basename matches known binary name, start
|
// 2. If basename matches known binary name, start
|
||||||
// 3. Rename tmp path into current path
|
// 3. Rename tmp path into current path
|
||||||
// 4. Update [State/Version]
|
// 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);
|
info!("Update | {}", UPGRADE);
|
||||||
// If this bool doesn't get set, something has gone wrong because
|
// If this bool doesn't get set, something has gone wrong because
|
||||||
// we _didn't_ find a binary even though we downloaded it.
|
// we _didn't_ find a binary even though we downloaded it.
|
||||||
|
@ -715,9 +716,9 @@ impl Update {
|
||||||
_ => Xmrig,
|
_ => Xmrig,
|
||||||
};
|
};
|
||||||
let path = match name {
|
let path = match name {
|
||||||
Gupax => update.lock().unwrap().path_gupax.clone(),
|
Gupax => lock!(update).path_gupax.clone(),
|
||||||
P2pool => update.lock().unwrap().path_p2pool.clone(),
|
P2pool => lock!(update).path_p2pool.clone(),
|
||||||
Xmrig => update.lock().unwrap().path_xmrig.clone(),
|
Xmrig => lock!(update).path_xmrig.clone(),
|
||||||
};
|
};
|
||||||
let path = Path::new(&path);
|
let path = Path::new(&path);
|
||||||
// Unix can replace running binaries no problem (they're loaded into memory)
|
// Unix can replace running binaries no problem (they're loaded into memory)
|
||||||
|
@ -745,14 +746,14 @@ impl Update {
|
||||||
// Update [State] version
|
// Update [State] version
|
||||||
match name {
|
match name {
|
||||||
Gupax => {
|
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
|
// 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)?,
|
P2pool => lock!(state_ver).p2pool = Pkg::get_new_pkg_version(P2pool, &vec4)?,
|
||||||
Xmrig => state_ver.lock().unwrap().xmrig = Pkg::get_new_pkg_version(Xmrig, &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();
|
let seconds = now.elapsed().as_secs();
|
||||||
info!("Update | Seconds elapsed ... [{}s]", seconds);
|
info!("Update | Seconds elapsed ... [{}s]", seconds);
|
||||||
match seconds {
|
match seconds {
|
||||||
0 => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took 0 seconds... What...?!{}", MSG_SUCCESS, new_pkgs),
|
0 => *lock2!(update,msg) = 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),
|
1 => *lock2!(update,msg) = format!("{}! Took 1 second... Wow!{}", MSG_SUCCESS, new_pkgs),
|
||||||
_ => *update.lock().unwrap().msg.lock().unwrap() = format!("{}! Took {} seconds.{}", MSG_SUCCESS, seconds, 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -823,8 +824,8 @@ impl Pkg {
|
||||||
link_prefix,
|
link_prefix,
|
||||||
link_suffix,
|
link_suffix,
|
||||||
link_extension,
|
link_extension,
|
||||||
bytes: Arc::new(Mutex::new(bytes::Bytes::new())),
|
bytes: arc_mut!(bytes::Bytes::new()),
|
||||||
new_ver: Arc::new(Mutex::new(String::new())),
|
new_ver: arc_mut!(String::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -855,7 +856,7 @@ impl Pkg {
|
||||||
let mut response = client.request(request).await?;
|
let mut response = client.request(request).await?;
|
||||||
let body = hyper::body::to_bytes(response.body_mut()).await?;
|
let body = hyper::body::to_bytes(response.body_mut()).await?;
|
||||||
let body: TagName = serde_json::from_slice(&body)?;
|
let body: TagName = serde_json::from_slice(&body)?;
|
||||||
*new_ver.lock().unwrap() = body.tag_name;
|
*lock!(new_ver) = body.tag_name;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -873,7 +874,7 @@ impl Pkg {
|
||||||
response = client.request(request).await?;
|
response = client.request(request).await?;
|
||||||
}
|
}
|
||||||
let body = hyper::body::to_bytes(response.into_body()).await?;
|
let body = hyper::body::to_bytes(response.into_body()).await?;
|
||||||
*bytes.lock().unwrap() = body;
|
*lock!(bytes) = body;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,7 +883,7 @@ impl Pkg {
|
||||||
fn get_new_pkg_version(name: Name, vec: &[&Pkg]) -> Result<String, Error> {
|
fn get_new_pkg_version(name: Name, vec: &[&Pkg]) -> Result<String, Error> {
|
||||||
for pkg in vec.iter() {
|
for pkg in vec.iter() {
|
||||||
if pkg.name == name {
|
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"))
|
Err(anyhow!("Couldn't find new_pkg_version"))
|
||||||
|
|
411
src/xmr.rs
Normal file
411
src/xmr.rs
Normal 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, ®ex));
|
||||||
|
}
|
||||||
|
*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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ use crate::{
|
||||||
disk::*,
|
disk::*,
|
||||||
Process,
|
Process,
|
||||||
PubXmrigApi,
|
PubXmrigApi,
|
||||||
|
macros::*,
|
||||||
};
|
};
|
||||||
use egui::{
|
use egui::{
|
||||||
TextEdit,SelectableLabel,ComboBox,Label,Button,RichText,Slider,Checkbox,
|
TextEdit,SelectableLabel,ComboBox,Label,Button,RichText,Slider,Checkbox,
|
||||||
|
@ -32,7 +33,7 @@ use std::{
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use log::*;
|
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) {
|
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;
|
let text_edit = height / 25.0;
|
||||||
//---------------------------------------------------------------------------------------------------- [Simple] Console
|
//---------------------------------------------------------------------------------------------------- [Simple] Console
|
||||||
|
@ -45,7 +46,7 @@ impl Xmrig {
|
||||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
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, _| {
|
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
|
//---------------------------------------------------------------------------------------------------- [Advanced] Console
|
||||||
|
@ -56,7 +57,7 @@ impl Xmrig {
|
||||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
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, _| {
|
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();
|
ui.separator();
|
||||||
|
@ -65,7 +66,7 @@ impl Xmrig {
|
||||||
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
|
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
|
||||||
response.request_focus(); // Get focus back
|
response.request_focus(); // Get focus back
|
||||||
let buffer = std::mem::take(buffer); // Take buffer
|
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
|
if process.is_alive() { process.input.push(buffer); } // Push only if alive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue