mirror of
https://github.com/hinto-janai/gupax.git
synced 2025-04-20 13:08:12 +00:00
Compare commits
72 commits
Author | SHA1 | Date | |
---|---|---|---|
|
45a1507d0d | ||
|
23888af364 | ||
|
f3d29c99de | ||
|
ab9912fccc | ||
|
6465a3e30b | ||
|
77d7f5003a | ||
|
a8334ea0ae | ||
|
1cc10b9a7b | ||
|
ad6da37601 | ||
|
9f45a3abcc | ||
|
81a780e1d5 | ||
|
a1a82f844a | ||
|
d203c94c7a | ||
|
bffcea376b | ||
|
cce8e03fd4 | ||
|
144fdde052 | ||
|
abaaf72e4d | ||
|
1810e135a2 | ||
|
730a7d2274 | ||
|
6ad66d16fb | ||
|
ba386eaad2 | ||
|
f6f37d9d36 | ||
|
2205610890 | ||
|
7672901bbb | ||
|
56cee0da0a | ||
|
13089db5ca | ||
|
921fca8ed6 | ||
|
7345f9b124 | ||
|
1436a02457 | ||
|
bb1612e9e1 | ||
|
09807aec8b | ||
|
4eb3c8e6ce | ||
|
20f7c7412e | ||
|
22f11ce7d8 | ||
|
78156a59d2 | ||
|
364429ca68 | ||
|
5699fe6702 | ||
|
2953b1f854 | ||
|
3f790a9253 | ||
|
9028dbd822 | ||
|
63f5d69dff | ||
|
d27a1cad74 | ||
|
7024c461ba | ||
|
3b7181e9c0 | ||
|
9df40a79bf | ||
|
a51234d353 | ||
|
62865f7c41 | ||
|
4f177ce951 | ||
|
023b370cdf | ||
|
582f915977 | ||
|
430cf7d805 | ||
|
de070a3986 | ||
|
3d3941081e | ||
|
86c6b9791c | ||
|
e95a85dbd0 | ||
|
1c831712af | ||
|
4c1e317042 | ||
|
54e009a157 | ||
|
d9b4106970 | ||
|
8fe06865e0 | ||
|
0d63429dfe | ||
|
ab8322c2d0 | ||
|
ca881539f1 | ||
|
039aed1679 | ||
|
27cc46d615 | ||
|
8919e2dd57 | ||
|
91e5cddb8b | ||
|
864e8ea394 | ||
|
4d93670c82 | ||
|
43e2e1b8a1 | ||
|
b2ce128daa | ||
|
77d0a7e422 |
40 changed files with 23042 additions and 16216 deletions
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,14 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Request a feature
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Feature request**
|
||||
Describe the feature you're requesting.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request.
|
7
.github/workflows/cache.yml
vendored
7
.github/workflows/cache.yml
vendored
|
@ -1,9 +1,10 @@
|
|||
# Forces `gupax.io` to cache stuff every day.
|
||||
# Forces `gupax.io` to cache.
|
||||
|
||||
name: Cache `gupax.io`
|
||||
on:
|
||||
schedule:
|
||||
- cron: "* 0 * * *"
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-2019, macos-11, ubuntu-20.04]
|
||||
os: [windows-2019, macos-14, ubuntu-20.04]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
@ -39,6 +39,7 @@ jobs:
|
|||
sudo apt install -y libgtk-3-dev
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
cargo install cargo-bundle
|
||||
rustup target install x86_64-apple-darwin
|
||||
rustup target install aarch64-apple-darwin
|
||||
fi
|
||||
shell: bash
|
||||
|
@ -49,9 +50,9 @@ jobs:
|
|||
- name: Build
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "macOS" ]; then
|
||||
cargo bundle --release
|
||||
cargo bundle --release --target x86_64-apple-darwin
|
||||
cargo bundle --release --target aarch64-apple-darwin
|
||||
mv target/release/bundle/osx/Gupax.app Gupax-macos-x64.app
|
||||
mv target/x86_64-apple-darwin/release/bundle/osx/Gupax.app Gupax-macos-x64.app
|
||||
mv target/aarch64-apple-darwin/release/bundle/osx/Gupax.app Gupax-macos-arm64.app
|
||||
tar -cf macos.tar Gupax-macos-arm64.app Gupax-macos-x64.app
|
||||
elif [ "$RUNNER_OS" == "Linux" ]; then
|
||||
|
|
2
.github/workflows/download.yml
vendored
2
.github/workflows/download.yml
vendored
|
@ -3,8 +3,6 @@
|
|||
|
||||
name: gupax.io
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
|
2
.github/workflows/ping.yml
vendored
2
.github/workflows/ping.yml
vendored
|
@ -3,8 +3,6 @@
|
|||
|
||||
name: Remote Node Ping
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 4 * * *"
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
|
|
105
CHANGELOG.md
105
CHANGELOG.md
|
@ -1,3 +1,108 @@
|
|||
# v1.3.10
|
||||
## Changes
|
||||
* [Remote Node](https://github.com/hinto-janai/gupax#remote-monero-nodes) changes:
|
||||
- Removed `monero1.heitechsoft.com`
|
||||
- Removed `node.cryptocano.de`
|
||||
- Removed `fbx.tranbert.com`
|
||||
- Removed `home.allantaylor.kiwi`
|
||||
- Removed `sf.xmr.support`
|
||||
* Text/visual changes
|
||||
|
||||
|
||||
## Bundled Versions
|
||||
* [`P2Pool v4.2`](https://github.com/SChernykh/p2pool/releases/tag/v4.2)
|
||||
* [`XMRig v6.22.2`](https://github.com/xmrig/xmrig/releases/tag/v6.22.2)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# v1.3.9
|
||||
## Changes
|
||||
* [Remote Node](https://github.com/hinto-janai/gupax#remote-monero-nodes) changes:
|
||||
- Removed `xmrnode.facspro.net` (thanks @SChernykh [#94](https://github.com/hinto-janai/gupax/issues/94))
|
||||
|
||||
## Bundled Versions
|
||||
* [`P2Pool v4.0`](https://github.com/SChernykh/p2pool/releases/tag/v4.0)
|
||||
* [`XMRig v6.21.3`](https://github.com/xmrig/xmrig/releases/tag/v6.21.3)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# v1.3.8
|
||||
## Changes
|
||||
* [Remote Node](https://github.com/hinto-janai/gupax#remote-monero-nodes) changes:
|
||||
- Removed `xmr{1,2,3}.rs.me` (thanks @MattJGH [#79](https://github.com/hinto-janai/gupax/issues/79#issuecomment-2127273487))
|
||||
|
||||
## Fixes
|
||||
- Visual bug on Windows (thanks @Cyrix126)
|
||||
|
||||
## Bundled Versions
|
||||
* [`P2Pool v3.10`](https://github.com/SChernykh/p2pool/releases/tag/v3.10)
|
||||
* [`XMRig v6.21.3`](https://github.com/xmrig/xmrig/releases/tag/v6.21.3)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# v1.3.7
|
||||
## Fixes
|
||||
- Not starting on Windows if CPU does not support `>= OpenGL 3.3` (thanks @Cyrix126 [#78](https://github.com/hinto-janai/gupax/issues/78))
|
||||
|
||||
## Bundled Versions
|
||||
* [`P2Pool v3.10`](https://github.com/SChernykh/p2pool/releases/tag/v3.10)
|
||||
* [`XMRig v6.21.3`](https://github.com/xmrig/xmrig/releases/tag/v6.21.3)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# v1.3.6
|
||||
## Changes
|
||||
* [Remote Node](https://github.com/hinto-janai/gupax#remote-monero-nodes) changes:
|
||||
- Added `xmr{1,2,3}.rs.me` (thanks @SChernykh [#80](https://github.com/hinto-janai/gupax/issues/79))
|
||||
- Removed `moneronode.ddns.net` (thanks @dukethorion [#83](https://github.com/hinto-janai/gupax/issues/83))
|
||||
- Removed `bunkernet.ddns.net`
|
||||
- Removed `ru.poiuty.com`
|
||||
|
||||
## Fixes
|
||||
- Non-responding nodes not being sorted by ping speed
|
||||
|
||||
## Bundled Versions
|
||||
* [`P2Pool v3.10`](https://github.com/SChernykh/p2pool/releases/tag/v3.10)
|
||||
* [`XMRig v6.21.1`](https://github.com/xmrig/xmrig/releases/tag/v6.21.1)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# v1.3.5
|
||||
## Fixes
|
||||
* Fix flickering `0s` XMRig uptime (thanks @Tomoyukiryu & @Burner8 [#77](https://github.com/hinto-janai/gupax/pull/77))
|
||||
|
||||
## Bundled Versions
|
||||
* [`P2Pool v3.10`](https://github.com/SChernykh/p2pool/releases/tag/v3.10)
|
||||
* [`XMRig v6.21.0`](https://github.com/xmrig/xmrig/releases/tag/v6.21.0)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# v1.3.4
|
||||
## Fixes
|
||||
* Domain parsing is more relaxed, allows subdomains with longer TLDs (thanks @soupslurpr [#67](https://github.com/hinto-janai/gupax/pull/67))
|
||||
* ANSI escape sequences in Windows P2Pool/XMRig terminal output ([#71](https://github.com/hinto-janai/gupax/pull/71))
|
||||
* P2Pool appearing green (synchronized) on false-positives ([#75](https://github.com/hinto-janai/gupax/pull/75))
|
||||
|
||||
## Bundled Versions
|
||||
* [`P2Pool v3.10`](https://github.com/SChernykh/p2pool/releases/tag/v3.10)
|
||||
* [`XMRig v6.21.0`](https://github.com/xmrig/xmrig/releases/tag/v6.21.0)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# v1.3.3
|
||||
## Changes
|
||||
* Crashes will now create a file on disk with debug information ([#59](https://github.com/hinto-janai/gupax/pull/59))
|
||||
|
|
5858
Cargo.lock
generated
5858
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
266
Cargo.toml
266
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gupax"
|
||||
version = "1.3.3"
|
||||
version = "1.3.10"
|
||||
authors = ["hinto-janai <hinto.janai@protonmail.com>"]
|
||||
description = "GUI for P2Pool+XMRig"
|
||||
documentation = "https://github.com/hinto-janai/gupax"
|
||||
|
@ -26,17 +26,23 @@ default = []
|
|||
distro = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.71"
|
||||
arti-client = { version = "0.9.0", features = ["static"] }
|
||||
arti-hyper = "0.9.0"
|
||||
anyhow = "1.0.83"
|
||||
arti-client = { version = "0.18.0", features = ["static"] }
|
||||
arti-hyper = "0.18.0"
|
||||
benri = "0.1.12"
|
||||
bytes = "1.4.0"
|
||||
bytes = "1.6.0"
|
||||
dirs = "5.0.1"
|
||||
#--------------------------------------------------------------------------------
|
||||
egui = "0.19.0"
|
||||
egui_extras = { version = "0.19.0", features = ["image"] }
|
||||
egui = "0.27.2"
|
||||
egui_extras = { version = "0.27.2", features = ["image"] }
|
||||
## 2023-12-28: https://github.com/hinto-janai/gupax/issues/68
|
||||
##
|
||||
## 2024-03-18: Both `glow` and `wgpu` seem to crash:
|
||||
## <https://github.com/hinto-janai/gupax/issues/84>
|
||||
## `wgpu` seems to crash on less computers though so...
|
||||
eframe = { version = "0.27.2", features = ["wgpu"] }
|
||||
|
||||
## Update 2023-Feb-06: The below gets fixed by using the [wgpu] backend instead of [glow]
|
||||
## 2023-02-06: The below gets fixed by using the [wgpu] backend instead of [glow]
|
||||
## It also fixes crashes on CPU-based graphics. Only used for Windows.
|
||||
## Using [wgpu] actually crashes macOS (fixed in 0.20.x though).
|
||||
|
||||
|
@ -51,35 +57,34 @@ egui_extras = { version = "0.19.0", features = ["image"] }
|
|||
#egui_extras = { path = "external/egui/crates/egui_extras", features = ["image"] }
|
||||
#--------------------------------------------------------------------------------
|
||||
env_logger = "0.10.0"
|
||||
figment = { version = "0.10.9", features = ["toml"] }
|
||||
figment = { version = "0.10.18", features = ["toml"] }
|
||||
hyper = "0.14.26"
|
||||
hyper-tls = "0.5.0"
|
||||
image = { version = "0.24.6", features = ["png"] }
|
||||
log = "0.4.18"
|
||||
image = { version = "0.25.1", features = ["png"] }
|
||||
log = "0.4.21"
|
||||
num-format = { version = "0.4.4", default-features = false }
|
||||
once_cell = "1.17.2"
|
||||
once_cell = "1.19.0"
|
||||
portable-pty = "0.8.1"
|
||||
rand = "0.8.5"
|
||||
regex = { version = "1.8.3", default-features = false, features = ["perf"] }
|
||||
rfd = "0.11.4"
|
||||
serde = { version = "1.0.163", features = ["rc", "derive"] }
|
||||
regex = { version = "1.10.4", default-features = false, features = ["perf"] }
|
||||
rfd = "0.14.1"
|
||||
serde = { version = "1.0.201", features = ["rc", "derive"] }
|
||||
serde_json = "1.0"
|
||||
sysinfo = { version = "0.29.0", default-features = false }
|
||||
tls-api = "0.9.0"
|
||||
tokio = { version = "1.21.2", features = ["rt", "time", "macros", "process"] }
|
||||
toml = { version = "0.7.4", features = ["preserve_order"] }
|
||||
tor-rtcompat = "0.9.0"
|
||||
walkdir = "2.3.3"
|
||||
zeroize = "1.6.0"
|
||||
strsim = "0.10.0"
|
||||
tor-rtcompat = "0.18.0"
|
||||
walkdir = "2.5.0"
|
||||
zeroize = "1.7.0"
|
||||
strsim = "0.11.1"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
|
||||
# Unix dependencies
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
tar = "0.4.38"
|
||||
tar = "0.4.40"
|
||||
flate2 = "1.0"
|
||||
sudo = "0.6.0"
|
||||
## [glow] backend for macOS/Linux.
|
||||
eframe = { version = "0.19.0", default-features = false, features = ["glow"] }
|
||||
|
||||
# macOS
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
|
@ -94,17 +99,16 @@ tls-api-openssl = "0.9.0"
|
|||
openssl = { version = "0.10", features = ["vendored"] }
|
||||
# We don't even use `xz` in `flate2` but this gets dynamically
|
||||
# linked as well which causes problems, so statically link it.
|
||||
lzma-sys = { version = "0.1.20", features = ["static"] }
|
||||
lzma-sys = { version = "0.1", features = ["static"] }
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
tls-api-native-tls = "0.9.0"
|
||||
## [wgpu] backend
|
||||
|
||||
# Windows dependencies
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
zip = "0.6.6"
|
||||
zip = "1"
|
||||
is_elevated = "0.1.2"
|
||||
eframe = { version = "0.19.0", default-features = false, features = ["wgpu"] }
|
||||
wgpu = { version = "0.19.4", features = ["angle"] }
|
||||
|
||||
# For Windows build (icon)
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
|
@ -117,3 +121,213 @@ name = "Gupax"
|
|||
identifier = "com.github.hinto-janai.gupax"
|
||||
icon = ["images/icons/icon@2x.png"]
|
||||
category = "public.app-category.utilities"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
borrow_as_ptr = "deny"
|
||||
case_sensitive_file_extension_comparisons = "deny"
|
||||
cast_lossless = "deny"
|
||||
cast_ptr_alignment = "deny"
|
||||
checked_conversions = "deny"
|
||||
cloned_instead_of_copied = "deny"
|
||||
const_is_empty = "deny"
|
||||
doc_lazy_continuation = "deny"
|
||||
doc_link_with_quotes = "deny"
|
||||
duplicated_attributes = "deny"
|
||||
empty_enum = "deny"
|
||||
enum_glob_use = "deny"
|
||||
expl_impl_clone_on_copy = "deny"
|
||||
explicit_into_iter_loop = "deny"
|
||||
filter_map_next = "deny"
|
||||
flat_map_option = "deny"
|
||||
from_iter_instead_of_collect = "deny"
|
||||
if_not_else = "deny"
|
||||
ignored_unit_patterns = "deny"
|
||||
inconsistent_struct_constructor = "deny"
|
||||
index_refutable_slice = "deny"
|
||||
inefficient_to_string = "deny"
|
||||
invalid_upcast_comparisons = "deny"
|
||||
iter_filter_is_ok = "deny"
|
||||
iter_filter_is_some = "deny"
|
||||
implicit_clone = "deny"
|
||||
legacy_numeric_constants = "deny"
|
||||
manual_c_str_literals = "deny"
|
||||
manual_pattern_char_comparison = "deny"
|
||||
manual_instant_elapsed = "deny"
|
||||
manual_inspect = "deny"
|
||||
manual_is_variant_and = "deny"
|
||||
manual_let_else = "deny"
|
||||
manual_ok_or = "deny"
|
||||
manual_string_new = "deny"
|
||||
manual_unwrap_or_default = "deny"
|
||||
map_unwrap_or = "deny"
|
||||
match_bool = "deny"
|
||||
match_same_arms = "deny"
|
||||
match_wildcard_for_single_variants = "deny"
|
||||
mismatching_type_param_order = "deny"
|
||||
missing_transmute_annotations = "deny"
|
||||
mut_mut = "deny"
|
||||
needless_bitwise_bool = "deny"
|
||||
needless_character_iteration = "deny"
|
||||
needless_continue = "deny"
|
||||
needless_for_each = "deny"
|
||||
needless_maybe_sized = "deny"
|
||||
needless_raw_string_hashes = "deny"
|
||||
no_effect_underscore_binding = "deny"
|
||||
no_mangle_with_rust_abi = "deny"
|
||||
option_as_ref_cloned = "deny"
|
||||
option_option = "deny"
|
||||
ptr_as_ptr = "deny"
|
||||
ptr_cast_constness = "deny"
|
||||
pub_underscore_fields = "deny"
|
||||
redundant_closure_for_method_calls = "deny"
|
||||
ref_as_ptr = "deny"
|
||||
ref_option_ref = "deny"
|
||||
same_functions_in_if_condition = "deny"
|
||||
semicolon_if_nothing_returned = "deny"
|
||||
trivially_copy_pass_by_ref = "deny"
|
||||
uninlined_format_args = "deny"
|
||||
unnecessary_join = "deny"
|
||||
unnested_or_patterns = "deny"
|
||||
unused_async = "deny"
|
||||
unused_self = "deny"
|
||||
used_underscore_binding = "deny"
|
||||
zero_sized_map_values = "deny"
|
||||
as_ptr_cast_mut = "deny"
|
||||
clear_with_drain = "deny"
|
||||
collection_is_never_read = "deny"
|
||||
debug_assert_with_mut_call = "deny"
|
||||
derive_partial_eq_without_eq = "deny"
|
||||
empty_line_after_doc_comments = "deny"
|
||||
empty_line_after_outer_attr = "deny"
|
||||
equatable_if_let = "deny"
|
||||
iter_on_empty_collections = "deny"
|
||||
iter_on_single_items = "deny"
|
||||
iter_with_drain = "deny"
|
||||
needless_collect = "deny"
|
||||
needless_pass_by_ref_mut = "deny"
|
||||
negative_feature_names = "deny"
|
||||
non_send_fields_in_send_ty = "deny"
|
||||
nonstandard_macro_braces = "deny"
|
||||
path_buf_push_overwrite = "deny"
|
||||
read_zero_byte_vec = "deny"
|
||||
redundant_clone = "deny"
|
||||
redundant_feature_names = "deny"
|
||||
trailing_empty_array = "deny"
|
||||
trait_duplication_in_bounds = "deny"
|
||||
type_repetition_in_bounds = "deny"
|
||||
uninhabited_references = "deny"
|
||||
unnecessary_struct_initialization = "deny"
|
||||
unused_peekable = "deny"
|
||||
unused_rounding = "deny"
|
||||
use_self = "deny"
|
||||
useless_let_if_seq = "deny"
|
||||
wildcard_dependencies = "deny"
|
||||
unseparated_literal_suffix = "deny"
|
||||
unnecessary_safety_doc = "deny"
|
||||
unnecessary_safety_comment = "deny"
|
||||
unnecessary_self_imports = "deny"
|
||||
string_to_string = "deny"
|
||||
rest_pat_in_fully_bound_structs = "deny"
|
||||
redundant_type_annotations = "deny"
|
||||
infinite_loop = "deny"
|
||||
zero_repeat_side_effects = "deny"
|
||||
|
||||
cast_possible_truncation = "deny"
|
||||
cast_possible_wrap = "deny"
|
||||
cast_precision_loss = "deny"
|
||||
cast_sign_loss = "deny"
|
||||
copy_iterator = "deny"
|
||||
doc_markdown = "deny"
|
||||
explicit_deref_methods = "deny"
|
||||
explicit_iter_loop = "deny"
|
||||
float_cmp = "deny"
|
||||
fn_params_excessive_bools = "deny"
|
||||
into_iter_without_iter = "deny"
|
||||
iter_without_into_iter = "deny"
|
||||
iter_not_returning_iterator = "deny"
|
||||
large_digit_groups = "deny"
|
||||
large_types_passed_by_value = "deny"
|
||||
manual_assert = "deny"
|
||||
maybe_infinite_iter = "deny"
|
||||
missing_fields_in_debug = "deny"
|
||||
needless_pass_by_value = "deny"
|
||||
range_minus_one = "deny"
|
||||
range_plus_one = "deny"
|
||||
redundant_else = "deny"
|
||||
ref_binding_to_reference = "deny"
|
||||
return_self_not_must_use = "deny"
|
||||
single_match_else = "deny"
|
||||
string_add_assign = "deny"
|
||||
transmute_ptr_to_ptr = "deny"
|
||||
unchecked_duration_subtraction = "deny"
|
||||
unnecessary_box_returns = "deny"
|
||||
unnecessary_wraps = "deny"
|
||||
branches_sharing_code = "deny"
|
||||
fallible_impl_from = "deny"
|
||||
missing_const_for_fn = "deny"
|
||||
significant_drop_in_scrutinee = "deny"
|
||||
significant_drop_tightening = "deny"
|
||||
try_err = "deny"
|
||||
lossy_float_literal = "deny"
|
||||
let_underscore_must_use = "deny"
|
||||
iter_over_hash_type = "deny"
|
||||
get_unwrap = "deny"
|
||||
error_impl_error = "deny"
|
||||
empty_structs_with_brackets = "deny"
|
||||
empty_enum_variants_with_brackets = "deny"
|
||||
empty_drop = "deny"
|
||||
clone_on_ref_ptr = "deny"
|
||||
upper_case_acronyms = "deny"
|
||||
allow_attributes = "deny"
|
||||
|
||||
# inline_always = "deny"
|
||||
# large_futures = "deny"
|
||||
# large_stack_arrays = "deny"
|
||||
# linkedlist = "deny"
|
||||
# missing_errors_doc = "deny"
|
||||
# missing_panics_doc = "deny"
|
||||
# should_panic_without_expect = "deny"
|
||||
# similar_names = "deny"
|
||||
# too_many_lines = "deny"
|
||||
# unreadable_literal = "deny"
|
||||
# wildcard_imports = "deny"
|
||||
# allow_attributes_without_reason = "deny"
|
||||
# missing_assert_message = "deny"
|
||||
# missing_docs_in_private_items = "deny"
|
||||
undocumented_unsafe_blocks = "deny"
|
||||
# multiple_unsafe_ops_per_block = "deny"
|
||||
# single_char_lifetime_names = "deny"
|
||||
# wildcard_enum_match_arm = "deny"
|
||||
|
||||
[workspace.lints.rust]
|
||||
future_incompatible = { level = "deny", priority = -1 }
|
||||
nonstandard_style = { level = "deny", priority = -1 }
|
||||
absolute_paths_not_starting_with_crate = "deny"
|
||||
explicit_outlives_requirements = "deny"
|
||||
keyword_idents_2018 = "deny"
|
||||
keyword_idents_2024 = "deny"
|
||||
missing_abi = "deny"
|
||||
non_ascii_idents = "deny"
|
||||
non_local_definitions = "deny"
|
||||
redundant_lifetimes = "deny"
|
||||
single_use_lifetimes = "deny"
|
||||
trivial_casts = "deny"
|
||||
trivial_numeric_casts = "deny"
|
||||
unsafe_op_in_unsafe_fn = "deny"
|
||||
unused_crate_dependencies = "deny"
|
||||
unused_import_braces = "deny"
|
||||
unused_lifetimes = "deny"
|
||||
unused_macro_rules = "deny"
|
||||
ambiguous_glob_imports = "deny"
|
||||
unused_unsafe = "deny"
|
||||
|
||||
let_underscore = { level = "deny", priority = -1 }
|
||||
unreachable_pub = "deny"
|
||||
unused_qualifications = "deny"
|
||||
variant_size_differences = "deny"
|
||||
non_camel_case_types = "deny"
|
||||
|
||||
# unused_results = "deny"
|
||||
# non_exhaustive_omitted_patterns = "deny"
|
||||
# missing_docs = "deny"
|
||||
# missing_copy_implementations = "deny"
|
||||
|
|
200
README.md
200
README.md
|
@ -3,9 +3,9 @@
|
|||
|
||||
Gupax is a GUI for mining [**Monero**](https://github.com/monero-project/monero) on [**P2Pool**](https://github.com/SChernykh/p2pool), using [**XMRig**](https://github.com/xmrig/xmrig).
|
||||
|
||||
**To see a 3-minute video guide on how to set-up Gupax: [click here.](#Guide)**
|
||||
To see a 3-minute video guide on how to set-up Gupax: [click here.](#Guide)
|
||||
|
||||
[](https://github.com/hinto-janai/gupax/actions/workflows/ci.yml) [](https://github.com/hinto-janai/gupax/actions/workflows/download.yml) [](https://github.com/hinto-janai/gupax/actions/workflows/ping.yml)
|
||||
[](https://github.com/hinto-janai/gupax/actions/workflows/ci.yml) [](https://github.com/hinto-janai/gupax/actions/workflows/download.yml)
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -19,11 +19,10 @@ Gupax is a GUI for mining [**Monero**](https://github.com/monero-project/monero)
|
|||
- [XMRig](#XMRig)
|
||||
* [Advanced](#Advanced)
|
||||
- [Verifying](#Verifying)
|
||||
- [Running a Local Monero Node](#running-a-local-monero-node)
|
||||
- [Running a Local Monero node](#running-a-local-monero-node)
|
||||
- [Command Line](#Command-Line)
|
||||
- [Key Shortcuts](#Key-Shortcuts)
|
||||
- [Resolution](#Resolution)
|
||||
- [Tor/Arti](#TorArti)
|
||||
- [Tor](#Tor)
|
||||
- [Logs](#Logs)
|
||||
- [Disk](#Disk)
|
||||
- [Swapping P2Pool/XMRig](#Swapping-P2PoolXMRig)
|
||||
|
@ -32,7 +31,7 @@ Gupax is a GUI for mining [**Monero**](https://github.com/monero-project/monero)
|
|||
- [P2Pool](#P2Pool-1)
|
||||
- [XMRig](#XMRig-1)
|
||||
* [Connections](#Connections)
|
||||
* [Remote Monero Nodes](#remote-monero-nodes)
|
||||
* [Remote Monero nodes](#remote-monero-nodes)
|
||||
* [Build](#Build)
|
||||
- [General Info](#General-Info)
|
||||
- [Linux](#Linux)
|
||||
|
@ -41,6 +40,8 @@ Gupax is a GUI for mining [**Monero**](https://github.com/monero-project/monero)
|
|||
- [Windows](#Windows)
|
||||
* [License](#License)
|
||||
* [FAQ](#FAQ)
|
||||
- [System requirements](#system-requirements)
|
||||
- [Gupax does not start](#gupax-does-not-start)
|
||||
- [Where are updates downloaded from?](#where-are-updates-downloaded-from)
|
||||
- [P2Pool connection errors](#p2pool-connection-errors)
|
||||
- [Can I quit mid-update?](#can-i-quit-mid-update)
|
||||
|
@ -52,7 +53,7 @@ Gupax is a GUI for mining [**Monero**](https://github.com/monero-project/monero)
|
|||
## What is Monero/P2Pool/XMRig/Gupax?
|
||||
[**`Monero`**](https://getmonero.org) is a secure, private, and untraceable cryptocurrency.
|
||||
|
||||
[Monero GUI](https://github.com/monero-project/monero-gui) allows you to run a Monero node (among other things).
|
||||
[Monero GUI](https://github.com/monero-project/monero-gui) allows you to run a Monero node, among other things.
|
||||
|
||||
---
|
||||
|
||||
|
@ -67,17 +68,17 @@ P2Pool combines the best of solo mining and traditional pool mining:
|
|||
* **It's decentralized:** There's no central server that can be shutdown or pool admin that controls your hashrate
|
||||
* **It's permissionless:** It's peer-to-peer so there's no one to decide who can and cannot mine on the pool
|
||||
* **It's trustless:** Funds are never in custody, all pool blocks pay out to miners directly and immediately
|
||||
* **0% transaction fee, 0 payout fee, immediate ~0.0003 XMR minimum payout**
|
||||
* **0% transaction fee, 0 payout fee, immediate ~0.00027 XMR minimum payout**
|
||||
|
||||
---
|
||||
|
||||
[**`XMRig`**](https://github.com/xmrig/xmrig) is an optimized miner that can mine Monero.
|
||||
|
||||
Both Monero and P2Pool have built in miners but XMRig is quite faster than both of them. Due to issues like [anti-virus flagging](https://github.com/monero-project/monero-gui/pull/3829#issuecomment-1018191461), it is not feasible to integrate XMRig directly into Monero.
|
||||
Both Monero and P2Pool have built in miners but XMRig is faster than both of them. Due to issues like [anti-virus flagging](https://github.com/monero-project/monero-gui/pull/3829#issuecomment-1018191461), it is not feasible to integrate XMRig directly into Monero.
|
||||
|
||||
---
|
||||
|
||||
[**`Gupax`**](https://github.com/hinto-janai/gupax) is a GUI that helps manage P2Pool & XMRig (both originally CLI-only).
|
||||
[**`Gupax`**](https://github.com/hinto-janai/gupax) is a GUI that helps manage P2Pool & XMRig which are both CLI-only.
|
||||
|
||||
<img src="images/local.png" align="left" width="50%"/>
|
||||
|
||||
|
@ -95,7 +96,7 @@ Both Monero and P2Pool have built in miners but XMRig is quite faster than both
|
|||
|
||||
<img src="images/remote.png" align="left" width="50%"/>
|
||||
|
||||
By default, Gupax will use a [Remote Monero Node](#remote-monero-nodes) so you don't have to run [your own Monero node](#running-a-local-monero-node) to start mining on P2Pool.
|
||||
By default, Gupax will use a [Remote Monero node](#remote-monero-nodes) so you don't have to run [your own Monero node](#running-a-local-monero-node) to start mining on P2Pool.
|
||||
|
||||
<br clear="left"/>
|
||||
|
||||
|
@ -108,7 +109,7 @@ https://user-images.githubusercontent.com/101352116/207978455-6ffdc0cc-204c-4594
|
|||
2. Extract
|
||||
3. Launch Gupax
|
||||
4. Input your Monero address in the `P2Pool` tab
|
||||
5. Select a [`Remote Monero Node`](#remote-monero-nodes) (or run your own local [Monero Node](#running-a-local-monero-node))
|
||||
5. Select a [`Remote Monero node`](#remote-monero-nodes) (or run your own local [Monero node](#running-a-local-monero-node))
|
||||
6. Start P2Pool
|
||||
7. Start XMRig
|
||||
|
||||
|
@ -165,8 +166,8 @@ 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).
|
||||
|
||||
Below that, there are some general Gupax settings:
|
||||
| Setting | What does it do? |
|
||||
|--------------------|-------------------|
|
||||
| Setting | What it does |
|
||||
|--------------------|--------------|
|
||||
| `Update via Tor` | Causes updates to be fetched via the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required
|
||||
| `Auto-Update` | Gupax will automatically check for updates at startup
|
||||
| `Auto-P2Pool` | Gupax will automatically start P2Pool at startup
|
||||
|
@ -177,7 +178,7 @@ Below that, there are some general Gupax settings:
|
|||
---
|
||||
|
||||
### P2Pool
|
||||
P2Pool Simple allows you to ping & connect to a [Remote Monero Node](#remote-monero-nodes) and start your own local P2Pool instance on the `Mini` sidechain.
|
||||
P2Pool Simple allows you to ping & connect to a [Remote Monero node](#remote-monero-nodes) and start your own local P2Pool instance on the `Mini` sidechain.
|
||||
|
||||
To start P2Pool, first input the Monero address you'd like to receive payouts from. 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!
|
||||
|
||||
|
@ -202,30 +203,27 @@ XMRig Simple will always mine to your own local P2Pool (`127.0.0.1:3333`), if yo
|
|||
### Verifying
|
||||
It is recommended to verify the hash and PGP signature of the download before using Gupax.
|
||||
|
||||
Download the [`SHA256SUMS`](https://github.com/hinto-janai/gupax/releases/latest) file, download and import my [`PGP key`](https://github.com/hinto-janai/gupax/blob/main/pgp/hinto-janai.asc), and verify:
|
||||
Download the [`SHA256SUMS`](https://github.com/hinto-janai/gupax/releases/latest) file, download and import this [`PGP key`](https://github.com/hinto-janai/gupax/blob/main/pgp/hinto-janai.asc), and verify:
|
||||
```bash
|
||||
sha256sum -c SHA256SUMS
|
||||
gpg --import hinto-janai.asc
|
||||
gpg --verify SHA256SUMS
|
||||
```
|
||||
|
||||
Q: How can I be sure the P2Pool/XMRig bundled with Gupax hasn't been tampered with?
|
||||
A: Verify the hash.
|
||||
|
||||
You can always compare the hash of the `P2Pool/XMRig` binaries bundled with Gupax with the hashes of the binaries found here:
|
||||
You can compare the hash of the `P2Pool/XMRig` binaries bundled with Gupax with the hashes of the binaries found here:
|
||||
- https://github.com/SChernykh/p2pool/releases
|
||||
- https://github.com/xmrig/xmrig/releases
|
||||
|
||||
Make sure the _version_ you are comparing against is correct, and make sure you are comparing the _binary_ to the _binary_, not the `tar/zip`. If they match, you can be sure they are the exact same. Verifying the PGP signature is also recommended:
|
||||
Make sure the _version_ you are comparing against is correct, and make sure you are comparing the _binary_ to the _binary_, not the `tar/zip`. If they match, they are the exact same. Verifying the PGP signature is also recommended:
|
||||
- P2Pool - [`SChernykh.asc`](https://github.com/monero-project/gitian.sigs/blob/master/gitian-pubkeys/SChernykh.asc)
|
||||
- XMRig - [`xmrig.asc`](https://github.com/xmrig/xmrig/blob/master/doc/gpg_keys/xmrig.asc)
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Running a Local Monero Node
|
||||
Running and using your own local Monero node improves privacy and security. It also means you won't be depending on one of the [Remote Monero Nodes](#remote-monero-nodes) provided by Gupax. This comes at the cost of downloading and syncing Monero's blockchain yourself (currently `155GB`).
|
||||
### Running a Local Monero node
|
||||
Running and using your own local Monero node improves privacy and security. It also means you won't be depending on one of the [Remote Monero nodes](#remote-monero-nodes) provided by Gupax. This comes at the cost of downloading and syncing Monero's blockchain yourself.
|
||||
|
||||
If you'd like to run and use your own local Monero node for P2Pool, follow these steps:
|
||||
To run and use your own local Monero node for P2Pool, follow these steps:
|
||||
|
||||
<div align="center">
|
||||
<img src="images/local_node.png" width="66%"/>
|
||||
|
@ -235,11 +233,10 @@ If you'd like to run and use your own local Monero node for P2Pool, follow these
|
|||
3. Enable `Local node`
|
||||
4. Enter `--zmq-pub=tcp://127.0.0.1:18083` into `Daemon startup flags`
|
||||
5. [(Optionally)](https://github.com/SChernykh/p2pool#windows) enter `--disable-dns-checkpoints --enable-dns-blocklist` into `Daemon startup flags`
|
||||
6. Start and fully sync node
|
||||
|
||||
</div>
|
||||
|
||||
After syncing the blockchain, you will now have your own Monero node.
|
||||
|
||||
The 4th step enables `ZMQ`, which is extra Monero node functionality that is needed for P2Pool to work correctly.
|
||||
|
||||
The 5th step:
|
||||
|
@ -247,14 +244,10 @@ The 5th step:
|
|||
- `--disable-dns-checkpoints` avoids periodical lag when DNS is updated (it's not needed when mining)
|
||||
- `--enable-dns-blocklist` bans known bad nodes
|
||||
|
||||
[For more detailed information on configuring a Monero node, click here.](https://monerodocs.org)
|
||||
|
||||
---
|
||||
|
||||
### Command Line
|
||||
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. To get around this, start Gupax with `--no-startup`. This will disable all `auto` features for that instance.
|
||||
```
|
||||
USAGE: ./gupax [--flag]
|
||||
|
||||
|
@ -274,9 +267,6 @@ USAGE: ./gupax [--flag]
|
|||
---
|
||||
|
||||
### Key Shortcuts
|
||||
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).
|
||||
```
|
||||
*---------------------------------------*
|
||||
| Key shortcuts |
|
||||
|
@ -296,23 +286,10 @@ An ALT+F4 will also trigger the exit confirm screen (if enabled).
|
|||
|
||||
---
|
||||
|
||||
### Resolution
|
||||
The default resolution of Gupax is `1280x960` which is a `4:3` aspect ratio.
|
||||
|
||||
This can be changed by dragging the corner of the window itself or by using the resolution sliders in the `Gupax Advanced` tab. After a resolution change, Gupax will fade-in/out of black and will take a second to resize all the UI elements to scale correctly to the new resolution.
|
||||
|
||||
If you have changed your OS's pixel scaling, you may need to resize Gupax to see all UI correctly.
|
||||
|
||||
The minimum window size is: `640x480`
|
||||
The maximum window size is: `3840x2160`
|
||||
Fullscreen mode can also be entered by pressing `F11`.
|
||||
|
||||
---
|
||||
|
||||
### Tor/Arti
|
||||
### Tor
|
||||
By default, Gupax updates via Tor. In particular, it uses [`Arti`](https://gitlab.torproject.org/tpo/core/arti), the official Rust implementation of Tor.
|
||||
|
||||
Instead of bootstrapping onto the Tor network every time, Arti saves state/cache about the Tor network (circuits, guards, etc) for later reuse onto the disk:
|
||||
Arti saves state/cache about the Tor network (circuits, guards, etc) for later reuse onto the disk:
|
||||
|
||||
State:
|
||||
| OS | Data Folder |
|
||||
|
@ -352,10 +329,8 @@ The current files saved to disk:
|
|||
---
|
||||
|
||||
### Logs
|
||||
Gupax has console logs that show with increasing detail, what exactly it is is doing.
|
||||
|
||||
There are multiple log filter levels but by default, `INFO` and above are enabled.
|
||||
To view more detailed console debug information, start Gupax with the environment variable `RUST_LOG` set to a log level like so:
|
||||
To view more detailed console debug information, start Gupax with the environment variable `RUST_LOG` set to a log level:
|
||||
```bash
|
||||
RUST_LOG=(trace|debug|info|warn|error) ./gupax
|
||||
```
|
||||
|
@ -364,12 +339,11 @@ For example:
|
|||
RUST_LOG=debug ./gupax
|
||||
```
|
||||
|
||||
In general:
|
||||
- `ERROR` means something has gone wrong and that something will probably break
|
||||
- `WARN` means something has gone wrong, but things will be fine
|
||||
- `INFO` logs are general info about what Gupax (the GUI thread) is currently doing
|
||||
- `DEBUG` logs are much more verbose and include what EVERY thread is doing (not just the main GUI thread)
|
||||
- `TRACE` logs are insanely verbose and shows very low-level logs
|
||||
- `ERROR`: has gone wrong and that something will probably break
|
||||
- `WARN`: something has gone wrong, but things will be fine
|
||||
- `INFO`: general info about what Gupax (the GUI thread) is currently doing
|
||||
- `DEBUG`: much more verbose and include what EVERY thread is doing (not just the main GUI thread)
|
||||
- `TRACE`: insanely verbose and shows very low-level logs
|
||||
|
||||
---
|
||||
|
||||
|
@ -461,13 +435,13 @@ Along with the updater and settings mentioned in [Simple](#simple), `Gupax Advan
|
|||
- The selected tab on startup
|
||||
- Gupax's resolution
|
||||
|
||||
**Warning:** Gupax will use your custom PATH/binary and will replace them if you use `Check for updates` in the `[Gupax]` tab. There are sanity checks in place, however. Your PATH MUST end in a value that _appears_ correct or else the updater will refuse to start:
|
||||
**Warning:** Gupax will use your custom PATH/binary and will replace them if you use `Check for updates` in the `[Gupax]` tab. Your PATH must end in a value that appears correct or else the updater will refuse to start:
|
||||
| Binary | Accepted values | Good PATH | Bad PATH |
|
||||
|----------|----------------------------------|-----------------|----------|
|
||||
| `P2Pool` | `P2POOL, P2Pool, P2pool, p2pool` | `P2pool/p2pool` | `Documents/my_really_important_file`
|
||||
| `P2Pool` | `P2POOL, P2Pool, P2pool, p2pool` | `P2pool/p2pool` | `Documents/important_file`
|
||||
| `XMRig` | `XMRIG, XMRig, Xmrig, xmrig` | `XMRig/XMRig` | `Desktop/`
|
||||
|
||||
If using Windows, the PATH _must_ end with `.exe`.
|
||||
If using Windows, the PATH must end with `.exe`.
|
||||
|
||||
---
|
||||
|
||||
|
@ -486,9 +460,9 @@ The manual node list allows you save and connect up-to 1000 custom Monero nodes:
|
|||
| Data Field | Purpose | Limits | Max Length |
|
||||
|------------|---------------------------------------------------------------|--------------------------------------------------------|----------------|
|
||||
| `Name` | A unique name to identify this node (only for Gupax purposes) | Only `[A-Za-z0-9-_.]` and spaces allowed | 30 characters |
|
||||
| `IP` | The Monero Node IP to connect to with P2Pool | It must be a valid IPv4 address or a valid domain name | 255 characters |
|
||||
| `RPC` | The RPC port of the Monero node | `[1-65535]` | 5 characters |
|
||||
| `ZMQ` | The ZMQ port of the Monero node | `[1-65535]` | 5 characters |
|
||||
| `IP` | The Monero node IP to connect to with P2Pool | It must be a valid IPv4 address or a valid domain name | 255 characters |
|
||||
| `RPC` | The RPC port of the Monero node | `[1-65535]` | 5 characters |
|
||||
| `ZMQ` | The ZMQ port of the Monero node | `[1-65535]` | 5 characters |
|
||||
|
||||
The `Main/Mini` selector allows you to change which P2Pool sidechain you mine on:
|
||||
| P2Pool Sidechain | Description | Use-case |
|
||||
|
@ -512,7 +486,6 @@ The remaining sliders control miscellaneous settings:
|
|||
| `In peers` | How many in-bound peers P2Pool will allow to connect to you | `10` | `10..450` |
|
||||
| `Log level` | Verbosity of the P2Pool console log | `3` | `0..6` |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### XMRig
|
||||
|
@ -535,7 +508,7 @@ The manual pool list allows you save and connect up-to 1000 custom Pools (regard
|
|||
|------------|---------------------------------------------------------------|--------------------------------------------------------|----------------|
|
||||
| `Name` | A unique name to identify this pool (only for Gupax purposes) | Only `[A-Za-z0-9-_.]` and spaces allowed | 30 characters |
|
||||
| `IP` | The pool IP to connect to with XMRig | It must be a valid IPv4 address or a valid domain name | 255 characters |
|
||||
| `Port` | The port of the pool | `[1-65535]` | 5 characters |
|
||||
| `Port` | The port of the pool | `[1-65535]` | 5 characters |
|
||||
| `Rig` | An optional rig ID; This will be the name shown on the pool | Only `[A-Za-z0-9-_]` and spaces allowed | 30 characters |
|
||||
|
||||
The HTTP API textboxes allow you to change to IP/Port XMRig's HTTP API opens up on:
|
||||
|
@ -556,49 +529,32 @@ For transparency, here's all the connections Gupax makes:
|
|||
| Domain | Why | When | Where |
|
||||
|--------------------|-------------------------------------------------------|------|-------|
|
||||
| https://github.com | Fetching metadata information on packages + download | `[Gupax]` tab -> `Check for updates` | [`update.rs`](https://github.com/hinto-janai/gupax/blob/main/src/update.rs) |
|
||||
| Remote Monero Nodes | Connecting to with P2Pool, measuring ping latency | `[P2Pool Simple]` tab | [`node.rs`](https://github.com/hinto-janai/gupax/blob/main/src/node.rs) |
|
||||
| Remote Monero nodes | Connecting to with P2Pool, measuring ping latency | `[P2Pool Simple]` tab | [`node.rs`](https://github.com/hinto-janai/gupax/blob/main/src/node.rs) |
|
||||
| DNS | DNS connections will usually be handled by your OS (or whatever custom DNS setup you have). If using Tor, DNS requests for updates [*should*](https://tpo.pages.torproject.net/core/doc/rust/arti/) be routed through the Tor network automatically | All of the above | All of the above |
|
||||
|
||||
## Remote Monero Nodes
|
||||
## Remote Monero nodes
|
||||
These are the remote nodes used by Gupax in the `[P2Pool Simple]` tab.
|
||||
|
||||
The nodes with the most consistent uptime are used.
|
||||
|
||||
| IP/Domain | Location | RPC Port | ZMQ Port |
|
||||
|----------------------------------|-----------------------------------|----------|-------------|
|
||||
| monero.10z.com.ar | 🇦🇷 AR - Buenos Aires F.D. | 18089 | 18084 |
|
||||
| monero1.heitechsoft.com | 🇨🇦 CA - Ontario | 18081 | 18084 |
|
||||
| node.monerodevs.org | 🇨🇦 CA - Quebec | 18089 | 18084 |
|
||||
| p2pmd.xmrvsbeast.com | 🇩🇪 DE - Hesse | 18081 | 18083 |
|
||||
| node.cryptocano.de | 🇩🇪 DE - Lower Saxony | 18089 | 18083 |
|
||||
| fbx.tranbert.com | 🇫🇷 FR - Île-de-France | 18089 | 18084 |
|
||||
| node2.monerodevs.org | 🇫🇷 FR - Occitanie | 18089 | 18084 |
|
||||
| p2pool.uk | 🇬🇧 GB - England | 18089 | 18084 |
|
||||
| home.allantaylor.kiwi | 🇳🇿 NZ - Canterbury | 18089 | 18083 |
|
||||
| ru.poiuty.com | 🇷🇺 RU - Kuzbass | 18081 | 18084 |
|
||||
| xmr.support | 🇺🇸 US - California | 18081 | 18083 |
|
||||
| sf.xmr.support | 🇺🇸 US - California | 18081 | 18083 |
|
||||
| xmrbandwagon.hopto.org | 🇺🇸 US - Colorado | 18081 | 18084 |
|
||||
| xmr.spotlightsound.com | 🇺🇸 US - Kansas | 18081 | 18084 |
|
||||
| xmrnode.facspro.net | 🇺🇸 US - Nebraska | 18089 | 18084 |
|
||||
| moneronode.ddns.net | 🇺🇸 US - Pennsylvania | 18089 | 18084 |
|
||||
| node.richfowler.net | 🇺🇸 US - Pennsylvania | 18089 | 18084 |
|
||||
| bunkernet.ddns.net | 🇿🇦 ZA - Western Cape | 18089 | 18084 |
|
||||
| IP/Domain | Location | RPC Port | ZMQ Port |
|
||||
|-------------------------|-------------------|----------|----------|
|
||||
| monero.10z.com.ar | 🇦🇷 Argentina | 18089 | 18084 |
|
||||
| node.monerodevs.org | 🇨🇦 Canada | 18089 | 18084 |
|
||||
| p2pmd.xmrvsbeast.com | 🇩🇪 Germany | 18081 | 18083 |
|
||||
| node2.monerodevs.org | 🇫🇷 France | 18089 | 18084 |
|
||||
| p2pool.uk | 🇬🇧 United Kingdom | 18089 | 18084 |
|
||||
| xmr.support | 🇺🇸 United States | 18081 | 18083 |
|
||||
| xmrbandwagon.hopto.org | 🇺🇸 United States | 18081 | 18084 |
|
||||
| xmr.spotlightsound.com | 🇺🇸 United States | 18081 | 18084 |
|
||||
| node.richfowler.net | 🇺🇸 United States | 18089 | 18084 |
|
||||
|
||||
## Build
|
||||
### General Info
|
||||
You need [`cargo`](https://www.rust-lang.org/learn/get-started), Rust's build tool and package manager.
|
||||
|
||||
There are `41` unit tests, you should probably run:
|
||||
```
|
||||
cargo test
|
||||
```
|
||||
before attempting a full build.
|
||||
|
||||
---
|
||||
|
||||
### Linux
|
||||
The pre-compiled Linux binaries are built on Debian 11, you'll need these packages to build:
|
||||
The pre-compiled Linux binaries are built on Ubuntu 20.04, you'll need these packages to build:
|
||||
```
|
||||
sudo apt install build-essential cmake libgtk-3-dev
|
||||
```
|
||||
|
@ -671,6 +627,34 @@ The GUI library Gupax uses is [egui](https://github.com/emilk/egui). It is licen
|
|||
[Gupax](https://github.com/hinto-janai/gupax/blob/master/LICENSE), [P2Pool](https://github.com/SChernykh/p2pool/blob/master/LICENSE), and [XMRig](https://github.com/xmrig/xmrig/blob/master/LICENSE) are licensed under the GNU General Public License v3.0.
|
||||
|
||||
## FAQ
|
||||
### System requirements
|
||||
Gupax may not run on machines with:
|
||||
- a deprecated OS (Windows 7, Ubuntu 18.04, etc)
|
||||
- an older CPU (<2010)
|
||||
|
||||
Anything more recent than this should be okay.
|
||||
|
||||
### Gupax does not start
|
||||
|
||||
#### Windows
|
||||
Gupax may have issues with machines with older CPUs (~14 years old, <3.3 OpenGL) on Windows.
|
||||
|
||||
Gupax may also be flagged by antivirus, adding an exception to the Gupax folder should fix this.
|
||||
|
||||
#### macOS
|
||||
Gupax may fail to start on macOS due to Apple's security features. Apps downloaded from the internet may be flagged as 'unsafe' if they lack proper notarization by Apple.
|
||||
|
||||
As a workaround you can remove the quarantine flag on the .app file by running the following command in your terminal:
|
||||
|
||||
```bash
|
||||
xattr -d com.apple.quarantine Gupax.app
|
||||
```
|
||||
|
||||
#### Linux ARM
|
||||
Gupax only supports ARM for macOS, not Linux.
|
||||
|
||||
Linux ARM is not supported as `XMRig` does not provide official binaries. This means machines like Raspberry Pis will not work.
|
||||
|
||||
### Where are updates downloaded from?
|
||||
The latest versions are downloaded using GitHub's API.
|
||||
* Gupax [`https://github.com/hinto-janai/gupax`](https://github.com/hinto-janai/gupax)
|
||||
|
@ -681,37 +665,29 @@ GitHub's API blocks request that do not have an HTTP `User-Agent` header.
|
|||
|
||||
[Gupax uses a random recent version of a `Wget/Curl` user-agent.](https://github.com/hinto-janai/gupax/blob/2c5bd0d7f6a39415353769427d60c0ca57f29710/src/update.rs#L178)
|
||||
|
||||
---
|
||||
|
||||
### P2Pool connection errors
|
||||
**TL;DR: Run & use your own Monero Node.**
|
||||
**TL;DR: Run & use your own Monero node.**
|
||||
|
||||
If you are using the [default P2Pool settings](#P2Pool) then you are using a [Remote Monero Node](#remote-monero-nodes). Using a remote node is convenient but comes at the cost of privacy and reliability. You may encounter connections issues with these nodes that look like this:
|
||||
If you are using the [default P2Pool settings](#P2Pool) then you are using a [Remote Monero node](#remote-monero-nodes). Using a remote node is convenient but comes at the cost of privacy and reliability. You may encounter connections issues with these nodes that look like this:
|
||||
```
|
||||
2023-01-05 12:27:37.7962 P2PServer peer 23.233.96.72:37888 is ahead on mainchain (height 2792939, your height 2792936). Is your monerod stuck or lagging?
|
||||
```
|
||||
To fix this you can select a different remote node, or better yet: [Run your own local Monero Node](#running-a-local-monero-node).
|
||||
To fix this you can select a different remote node, or better yet: [Run your own local Monero node](#running-a-local-monero-node).
|
||||
|
||||
Running and using your own local Monero node improves privacy and ensures your connection is as stable as your own internet connection. This comes at the cost of downloading and syncing Monero's blockchain yourself (currently 155GB). If you have the disk space, consider using the [P2Pool Advanced](#p2pool-1) tab and connecting to your own Monero node.
|
||||
|
||||
For a simple guide, see the [Running a Local Monero Node](#running-a-local-monero-node) section.
|
||||
|
||||
---
|
||||
For a simple guide, see the [Running a Local Monero node](#running-a-local-monero-node) section.
|
||||
|
||||
### Can I quit mid-update?
|
||||
If you started an update, you should let it finish. If the update has been stuck for a *long* time, quitting Gupax is probably okay. The worst that can happen is that your `Gupax/P2Pool/XMRig` binaries may be moved/deleted. Those can be easily redownloaded. Your actual `Gupax` user data (settings, custom nodes, pools, etc) is never touched.
|
||||
|
||||
Although Gupax uses a temporary folder (`gupax_update_[A-Za-z0-9]`) to store temporary downloaded files, there aren't measures in place to revert an upgrade once the file swapping has actually started. If you quit Gupax anytime before the `Upgrading packages` phase (after metadata, download, extraction), you will technically be safe but this is not recommended as it is risky, especially since these updates can be very fast.
|
||||
|
||||
---
|
||||
|
||||
### Bundled vs Standalone
|
||||
`Bundled` Gupax comes with the latest version of P2Pool/XMRig already in the `zip/tar`.
|
||||
|
||||
`Standalone` only contains the Gupax executable.
|
||||
|
||||
---
|
||||
|
||||
### How much memory does Gupax use?
|
||||
Gupax itself uses around 100-400 megabytes of memory.
|
||||
|
||||
|
@ -719,12 +695,8 @@ Gupax also holds up to [500,000 bytes](https://github.com/hinto-janai/gupax/blob
|
|||
|
||||
Memory usage should *never* be above 500~ megabytes. If you see Gupax using more than this, please send a bug report.
|
||||
|
||||
---
|
||||
|
||||
### How is sudo handled? (on macOS/Linux)
|
||||
[See here for more info.](https://github.com/hinto-janai/gupax/tree/main/src#sudo)
|
||||
|
||||
---
|
||||
|
||||
### Why does Gupax need to be Admin? (on Windows)
|
||||
[See here for more info.](https://github.com/hinto-janai/gupax/tree/main/src#why-does-gupax-need-to-be-admin-on-windows)
|
||||
|
|
42
build.rs
42
build.rs
|
@ -5,16 +5,17 @@
|
|||
// pre-compiled bytes using [include_bytes!()] on the images in [images/].
|
||||
#[cfg(windows)]
|
||||
fn main() -> std::io::Result<()> {
|
||||
set_commit_env();
|
||||
set_commit_env();
|
||||
|
||||
static_vcruntime::metabuild();
|
||||
let mut res = winres::WindowsResource::new();
|
||||
// This sets the icon.
|
||||
res.set_icon("images/icons/icon.ico");
|
||||
// This sets the [Run as Administrator] metadata flag for Windows.
|
||||
// Why do I do this?: [https://github.com/hinto-janai/gupax/tree/main/src#why-does-gupax-need-to-be-admin-on-windows]
|
||||
// TL;DR: Because Windows.
|
||||
res.set_manifest(r#"
|
||||
static_vcruntime::metabuild();
|
||||
let mut res = winres::WindowsResource::new();
|
||||
// This sets the icon.
|
||||
res.set_icon("images/icons/icon.ico");
|
||||
// This sets the [Run as Administrator] metadata flag for Windows.
|
||||
// Why do I do this?: [https://github.com/hinto-janai/gupax/tree/main/src#why-does-gupax-need-to-be-admin-on-windows]
|
||||
// TL;DR: Because Windows.
|
||||
res.set_manifest(
|
||||
r#"
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
|
@ -24,27 +25,28 @@ fn main() -> std::io::Result<()> {
|
|||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
"#);
|
||||
res.compile()
|
||||
"#,
|
||||
);
|
||||
res.compile()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn main() {
|
||||
set_commit_env();
|
||||
set_commit_env();
|
||||
}
|
||||
|
||||
// Set the current git commit to the env var [COMMIT].
|
||||
fn set_commit_env() {
|
||||
println!("cargo:rerun-if-changed=.git/refs/heads/");
|
||||
println!("cargo:rerun-if-changed=.git/refs/heads/");
|
||||
|
||||
let output = std::process::Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.unwrap();
|
||||
let output = std::process::Command::new("git")
|
||||
.args(["rev-parse", "HEAD"])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let commit = String::from_utf8(output.stdout).unwrap();
|
||||
let commit = String::from_utf8(output.stdout).unwrap();
|
||||
|
||||
assert!(commit.len() >= 40);
|
||||
assert!(commit.len() >= 40);
|
||||
|
||||
println!("cargo:rustc-env=COMMIT={commit}");
|
||||
println!("cargo:rustc-env=COMMIT={commit}");
|
||||
}
|
||||
|
|
2
external/egui
vendored
2
external/egui
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 62b4d427c01201898914594a9d00d1576bc23432
|
||||
Subproject commit 9cf535bd50b2602b0bce45c718d164bae2b4ed77
|
Binary file not shown.
Before ![]() (image error) Size: 33 KiB |
Binary file not shown.
Before ![]() (image error) Size: 64 KiB |
Binary file not shown.
Before ![]() (image error) Size: 35 KiB |
Binary file not shown.
Before ![]() (image error) Size: 51 KiB |
Binary file not shown.
Before ![]() (image error) Size: 71 KiB |
Binary file not shown.
Before ![]() (image error) Size: 47 KiB |
Binary file not shown.
Before ![]() (image error) Size: 214 KiB |
|
@ -21,7 +21,6 @@
|
|||
| cpu.json | [XMRig benchmark data in JSON](https://github.com/hinto-janai/xmrig-benchmarks)
|
||||
| constants.rs | General constants used in Gupax
|
||||
| disk.rs | Code for writing to disk: `state.toml/node.toml/pool.toml`; This holds the structs for the [State] struct
|
||||
| ferris.rs | Cute crab bytes
|
||||
| gupax.rs | `Gupax` tab
|
||||
| helper.rs | The "helper" thread that runs for the entire duration Gupax is alive. All the processing that needs to be done without blocking the main GUI thread runs here, including everything related to handling P2Pool/XMRig
|
||||
| human.rs | Code for displaying human readable numbers & time
|
||||
|
|
553
src/constants.rs
553
src/constants.rs
|
@ -15,51 +15,49 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub const GUPAX_VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); // e.g: v1.0.0
|
||||
pub const P2POOL_VERSION: &str = "v3.9";
|
||||
pub const XMRIG_VERSION: &str = "v6.21.0";
|
||||
pub const COMMIT: &str = env!("COMMIT"); // set in build.rs
|
||||
// e.g: Gupax_v1_0_0
|
||||
// Would have been [Gupax_v1.0.0] but P2Pool truncates everything after [.]
|
||||
pub const GUPAX_VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION")); // e.g: v1.0.0
|
||||
pub const P2POOL_VERSION: &str = "v4.2";
|
||||
pub const XMRIG_VERSION: &str = "v6.22.2";
|
||||
pub const COMMIT: &str = env!("COMMIT"); // set in build.rs
|
||||
// e.g: Gupax_v1_0_0
|
||||
// Would have been [Gupax_v1.0.0] but P2Pool truncates everything after [.]
|
||||
pub const GUPAX_VERSION_UNDERSCORE: &str = concat!(
|
||||
"Gupax_v",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
"_",
|
||||
env!("CARGO_PKG_VERSION_MINOR"),
|
||||
"_",
|
||||
env!("CARGO_PKG_VERSION_PATCH"),
|
||||
"Gupax_v",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
"_",
|
||||
env!("CARGO_PKG_VERSION_MINOR"),
|
||||
"_",
|
||||
env!("CARGO_PKG_VERSION_PATCH"),
|
||||
);
|
||||
|
||||
// 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_MAX_WIDTH: f32 = 3840.0;
|
||||
pub const APP_MAX_WIDTH: f32 = 3840.0;
|
||||
pub const APP_MAX_HEIGHT: f32 = 2160.0;
|
||||
// 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;
|
||||
// App resolution scaling
|
||||
pub const APP_MIN_SCALE: f32 = 0.1;
|
||||
pub const APP_MIN_SCALE: f32 = 0.1;
|
||||
pub const APP_MAX_SCALE: f32 = 2.0;
|
||||
pub const APP_DEFAULT_SCALE: f32 = 1.0;
|
||||
|
||||
// Constants specific for Linux distro packaging of Gupax
|
||||
#[cfg(feature = "distro")]
|
||||
pub const DISTRO_NO_UPDATE: &str =
|
||||
r#"This [Gupax] was compiled for use as a Linux distro package. Built-in updates are disabled. The below settings [Update-via-Tor] & [Auto-Update] will not do anything. Please use your package manager to update [Gupax/P2Pool/XMRig]."#;
|
||||
pub const DISTRO_NO_UPDATE: &str = r#"This [Gupax] was compiled for use as a Linux distro package. Built-in updates are disabled. The below settings [Update-via-Tor] & [Auto-Update] will not do anything. Please use your package manager to update [Gupax/P2Pool/XMRig]."#;
|
||||
|
||||
// Use macOS shaped icon for 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"))]
|
||||
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 HORIZONTAL: &str = "--------------------------------------------";
|
||||
pub const HORIZONTAL: &str = "--------------------------------------------";
|
||||
pub const HORI_CONSOLE: &str = "---------------------------------------------------------------------------------------------------------------------------";
|
||||
|
||||
// Keyboard shortcuts
|
||||
pub const KEYBOARD_SHORTCUTS: &str =
|
||||
r#"*---------------------------------------*
|
||||
pub const KEYBOARD_SHORTCUTS: &str = r#"*---------------------------------------*
|
||||
| Key shortcuts |
|
||||
|---------------------------------------|
|
||||
| F11 | Fullscreen |
|
||||
|
@ -75,28 +73,29 @@ r#"*---------------------------------------*
|
|||
*---------------------------------------*"#;
|
||||
// P2Pool & XMRig default API stuff
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const P2POOL_API_PATH_LOCAL: &str = r"local\stratum";
|
||||
pub const P2POOL_API_PATH_LOCAL: &str = r"local\stratum";
|
||||
#[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";
|
||||
pub const P2POOL_API_PATH_POOL: &str = r"pool\stats";
|
||||
#[cfg(target_family = "unix")]
|
||||
pub const P2POOL_API_PATH_LOCAL: &str = "local/stratum";
|
||||
pub const P2POOL_API_PATH_LOCAL: &str = "local/stratum";
|
||||
#[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
|
||||
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)
|
||||
pub const P2POOL_ALIVE: &str = "P2Pool is online and fully synchronized";
|
||||
pub const P2POOL_DEAD: &str = "P2Pool is offline";
|
||||
pub const P2POOL_FAILED: &str = "P2Pool is offline and failed when exiting";
|
||||
pub const P2POOL_MIDDLE: &str = "P2Pool is in the middle of (re)starting/stopping";
|
||||
pub const P2POOL_SYNCING: &str = "P2Pool is still syncing. This indicator will turn GREEN when P2Pool is ready";
|
||||
pub const P2POOL_ALIVE: &str = "P2Pool is online and fully synchronized";
|
||||
pub const P2POOL_DEAD: &str = "P2Pool is offline";
|
||||
pub const P2POOL_FAILED: &str = "P2Pool is offline and failed when exiting";
|
||||
pub const P2POOL_MIDDLE: &str = "P2Pool is in the middle of (re)starting/stopping";
|
||||
pub const P2POOL_SYNCING: &str =
|
||||
"P2Pool is still syncing. This indicator will turn GREEN when P2Pool is ready";
|
||||
|
||||
pub const XMRIG_ALIVE: &str = "XMRig is online and mining";
|
||||
pub const XMRIG_DEAD: &str = "XMRig is offline";
|
||||
pub const XMRIG_ALIVE: &str = "XMRig is online and mining";
|
||||
pub const XMRIG_DEAD: &str = "XMRig is offline";
|
||||
pub const XMRIG_FAILED: &str = "XMRig is offline and failed when exiting";
|
||||
pub const XMRIG_MIDDLE: &str = "XMRig is in the middle of (re)starting/stopping";
|
||||
pub const XMRIG_NOT_MINING: &str = "XMRig is online, but not mining to any pool";
|
||||
|
@ -108,31 +107,29 @@ pub const XMRIG_NOT_MINING: &str = "XMRig is online, but not mining to any pool"
|
|||
pub const SPACE: f32 = 10.0;
|
||||
|
||||
// Some colors
|
||||
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 BLUE: egui::Color32 = egui::Color32::from_rgb(100, 175, 255);
|
||||
pub const ORANGE: egui::Color32 = egui::Color32::from_rgb(255, 120, 40);
|
||||
pub const YELLOW: egui::Color32 = egui::Color32::from_rgb(230, 230, 100);
|
||||
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 BLUE: egui::Color32 = egui::Color32::from_rgb(100, 175, 255);
|
||||
pub const ORANGE: egui::Color32 = egui::Color32::from_rgb(255, 120, 40);
|
||||
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 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 GRAY: egui::Color32 = egui::Color32::GRAY;
|
||||
pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY;
|
||||
pub const BLACK: egui::Color32 = egui::Color32::BLACK;
|
||||
pub const DARK_GRAY: egui::Color32 = egui::Color32::from_gray(13);
|
||||
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 GRAY: egui::Color32 = egui::Color32::GRAY;
|
||||
pub const LIGHT_GRAY: egui::Color32 = egui::Color32::LIGHT_GRAY;
|
||||
pub const BLACK: egui::Color32 = egui::Color32::BLACK;
|
||||
pub const DARK_GRAY: egui::Color32 = egui::Color32::from_gray(13);
|
||||
|
||||
// [Duration] constants
|
||||
pub const SECOND: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
|
||||
// The explanation given to the user on why XMRig needs sudo.
|
||||
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."#;
|
||||
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."#;
|
||||
// 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_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
|
||||
#[cfg(target_os = "windows")]
|
||||
|
@ -154,111 +151,138 @@ pub const OS_NAME: &str = "Linux";
|
|||
|
||||
// Tooltips
|
||||
// Status
|
||||
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_MEMORY_USAGE: &str = "How much memory Gupax is currently using in Megabytes";
|
||||
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_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_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_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_P2POOL_UPTIME: &str = "How long P2Pool has been online";
|
||||
pub const STATUS_P2POOL_PAYOUTS: &str = "The total amount of payouts received in this instance of P2Pool and an extrapolated estimate of how many you will receive. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!";
|
||||
pub const STATUS_P2POOL_XMR: &str = "The total amount of XMR mined in this instance of P2Pool and an extrapolated estimate of how many you will mine in the future. Warning: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time!";
|
||||
pub const STATUS_P2POOL_UPTIME: &str = "How long P2Pool has been online";
|
||||
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.
|
||||
|
||||
Note: 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.
|
||||
|
||||
Note: these stats will be quite inaccurate if your P2Pool hasn't been running for a long time.";
|
||||
pub const STATUS_P2POOL_HASHRATE: &str = "The total amount of hashrate your P2Pool has pointed at it in 15 minute, 1 hour, and 24 hour averages";
|
||||
pub const STATUS_P2POOL_SHARES: &str = "The total amount of shares found on P2Pool";
|
||||
pub const STATUS_P2POOL_EFFORT: &str = "The average amount of effort needed to find a share, and the current effort";
|
||||
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_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_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_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_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_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_SHARES: &str = "The amount of accepted and rejected shares";
|
||||
pub const STATUS_XMRIG_POOL: &str = "The pool XMRig is currently mining to";
|
||||
pub const STATUS_XMRIG_THREADS: &str = "The amount of threads XMRig is currently using";
|
||||
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_SHARES: &str = "The amount of accepted and rejected shares";
|
||||
pub const STATUS_XMRIG_POOL: &str = "The pool XMRig is currently mining to";
|
||||
pub const STATUS_XMRIG_THREADS: &str = "The amount of threads XMRig is currently using";
|
||||
// Status Submenus
|
||||
pub const STATUS_SUBMENU_PROCESSES: &str = "View the status of process related data for [Gupax|P2Pool|XMRig]";
|
||||
pub const STATUS_SUBMENU_P2POOL: &str = "View P2Pool specific data";
|
||||
pub const STATUS_SUBMENU_HASHRATE: &str = "Compare your CPU hashrate with others";
|
||||
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";
|
||||
pub const STATUS_SUBMENU_HASHRATE: &str = "Compare your CPU hashrate with others";
|
||||
//-- 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_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_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_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_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)";
|
||||
//-- Benchmarks
|
||||
pub const STATUS_SUBMENU_YOUR_CPU: &str = "The CPU detected by Gupax";
|
||||
pub const STATUS_SUBMENU_YOUR_BENCHMARKS: &str = "How many benchmarks your CPU has had uploaded to [https://xmrig.com/benchmark] ";
|
||||
pub const STATUS_SUBMENU_YOUR_RANK: &str = "Your CPU's rank out of all CPUs listed on [https://xmrig.com/benchmark] (higher is better)";
|
||||
pub const STATUS_SUBMENU_YOUR_HIGH: &str = "The highest hashrate recorded for your CPU on [https://xmrig.com/benchmark]";
|
||||
pub const STATUS_SUBMENU_YOUR_AVERAGE: &str = "The average hashrate of your CPU based off the data at [https://xmrig.com/benchmark]";
|
||||
pub const STATUS_SUBMENU_YOUR_LOW: &str = "The lowest hashrate recorded for your CPU on [https://xmrig.com/benchmark]";
|
||||
pub const STATUS_SUBMENU_YOUR_CPU: &str = "The CPU detected by Gupax";
|
||||
pub const STATUS_SUBMENU_YOUR_BENCHMARKS: &str =
|
||||
"How many benchmarks your CPU has had uploaded to [https://xmrig.com/benchmark] ";
|
||||
pub const STATUS_SUBMENU_YOUR_RANK: &str =
|
||||
"Your CPU's rank out of all CPUs listed on [https://xmrig.com/benchmark] (higher is better)";
|
||||
pub const STATUS_SUBMENU_YOUR_HIGH: &str =
|
||||
"The highest hashrate recorded for your CPU on [https://xmrig.com/benchmark]";
|
||||
pub const STATUS_SUBMENU_YOUR_AVERAGE: &str =
|
||||
"The average hashrate of your CPU based off the data at [https://xmrig.com/benchmark]";
|
||||
pub const STATUS_SUBMENU_YOUR_LOW: &str =
|
||||
"The lowest hashrate recorded for your CPU on [https://xmrig.com/benchmark]";
|
||||
pub const STATUS_SUBMENU_OTHER_CPUS: &str = "A list of ALL the recorded CPU benchmarks. The CPUs most similar to yours are listed first. All this data is taken from [https://xmrig.com/benchmark].";
|
||||
pub const STATUS_SUBMENU_OTHER_CPU: &str = "The CPU name";
|
||||
pub const STATUS_SUBMENU_OTHER_CPU: &str = "The CPU name";
|
||||
pub const STATUS_SUBMENU_OTHER_RELATIVE: &str = "The relative hashrate power compared to the fastest recorded CPU, which is current: [AMD EPYC 7T83 64-Core Processor]";
|
||||
pub const STATUS_SUBMENU_OTHER_HIGH: &str = "Highest hashrate record";
|
||||
pub const STATUS_SUBMENU_OTHER_AVERAGE: &str = "Average hashrate";
|
||||
pub const STATUS_SUBMENU_OTHER_LOW: &str = "Lowest hashrate record";
|
||||
pub const STATUS_SUBMENU_OTHER_RANK: &str = "The rank of this CPU out of [1567] (lower is better)";
|
||||
pub const STATUS_SUBMENU_OTHER_BENCHMARKS: &str = "How many benchmarks this CPU has had posted to [https://xmrig.com/benchmark]";
|
||||
pub const STATUS_SUBMENU_OTHER_HIGH: &str = "Highest hashrate record";
|
||||
pub const STATUS_SUBMENU_OTHER_AVERAGE: &str = "Average hashrate";
|
||||
pub const STATUS_SUBMENU_OTHER_LOW: &str = "Lowest hashrate record";
|
||||
pub const STATUS_SUBMENU_OTHER_RANK: &str = "The rank of this CPU out of [1567] (lower is better)";
|
||||
pub const STATUS_SUBMENU_OTHER_BENCHMARKS: &str =
|
||||
"How many benchmarks this CPU has had posted to [https://xmrig.com/benchmark]";
|
||||
|
||||
// Gupax
|
||||
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_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_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_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";
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub const GUPAX_UPDATE_VIA_TOR: &str = "Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required";
|
||||
#[cfg(target_os = "macos")] // Arti library has issues on macOS
|
||||
pub const GUPAX_UPDATE_VIA_TOR: &str = "WARNING: This option is unstable on macOS. Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required";
|
||||
pub const GUPAX_ASK_BEFORE_QUIT: &str = "Ask before quitting Gupax";
|
||||
pub const GUPAX_SAVE_BEFORE_QUIT: &str = "Automatically save any changed settings before quitting";
|
||||
pub const GUPAX_AUTO_P2POOL: &str = "Automatically start P2Pool on Gupax startup. If you are using [P2Pool Simple], this will NOT wait for your [Auto-Ping] to finish, it will start P2Pool on the pool you already have selected. This option will fail if your P2Pool settings aren't valid!";
|
||||
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_WIDTH: &str = "Set the width of the Gupax window";
|
||||
pub const GUPAX_HEIGHT: &str = "Set the height of the Gupax window";
|
||||
pub const GUPAX_SCALE: &str = "Set the resolution scaling of the Gupax window (resize window to re-apply scaling)";
|
||||
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_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_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_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_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_UPDATE_VIA_TOR: &str = "Update through the Tor network. Tor is embedded within Gupax; a Tor system proxy is not required.
|
||||
|
||||
pub const GUPAX_SIMPLE: &str =
|
||||
r#"Use simple Gupax settings:
|
||||
Note: This option is unstable on macOS.";
|
||||
pub const GUPAX_ASK_BEFORE_QUIT: &str = "Ask before quitting Gupax";
|
||||
pub const GUPAX_SAVE_BEFORE_QUIT: &str = "Automatically save any changed settings before quitting";
|
||||
pub const GUPAX_AUTO_P2POOL: &str = "Automatically start P2Pool on Gupax startup. If you are using [P2Pool Simple], this will NOT wait for your [Auto-Ping] to finish, it will start P2Pool on the pool you already have selected. This option will fail if your P2Pool settings aren't valid.";
|
||||
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_WIDTH: &str = "Set the width of the Gupax window";
|
||||
pub const GUPAX_HEIGHT: &str = "Set the height of the Gupax window";
|
||||
pub const GUPAX_SCALE: &str =
|
||||
"Set the resolution scaling of the Gupax window (resize window to re-apply scaling)";
|
||||
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_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_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_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_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_SIMPLE: &str = r#"Use simple Gupax settings:
|
||||
- Update button
|
||||
- Basic toggles"#;
|
||||
pub const GUPAX_ADVANCED: &str =
|
||||
r#"Use advanced Gupax settings:
|
||||
pub const GUPAX_ADVANCED: &str = r#"Use advanced Gupax settings:
|
||||
- Update button
|
||||
- Basic toggles
|
||||
- P2Pool/XMRig binary path selector
|
||||
|
@ -272,45 +296,42 @@ pub const GUPAX_PATH_XMRIG: &str = "The location of the XMRig binary: Both absol
|
|||
// 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_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_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_AUTO_NODE: &str = "Automatically ping the remote Monero nodes at Gupax startup";
|
||||
pub const P2POOL_AUTO_SELECT: &str = "Automatically select the fastest remote Monero node after pinging";
|
||||
pub const P2POOL_BACKUP_HOST_SIMPLE: &str =
|
||||
r#"Automatically switch to the other nodes listed if the current one is down.
|
||||
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_LOG: &str = "Verbosity of the console log";
|
||||
pub const P2POOL_AUTO_NODE: &str = "Automatically ping the remote Monero nodes at Gupax startup";
|
||||
pub const P2POOL_AUTO_SELECT: &str =
|
||||
"Automatically select the fastest remote Monero node after pinging";
|
||||
pub const P2POOL_BACKUP_HOST_SIMPLE: &str = r#"Automatically switch to the other nodes listed if the current one is down.
|
||||
|
||||
Note: you must ping the remote nodes or this feature will default to only using the currently selected node."#;
|
||||
pub const P2POOL_BACKUP_HOST_ADVANCED: &str = "Automatically switch to the other nodes in your list if the current one is down.";
|
||||
pub const P2POOL_SELECT_FASTEST: &str = "Select the fastest remote Monero node";
|
||||
pub const P2POOL_SELECT_RANDOM: &str = "Select a random remote Monero node";
|
||||
pub const P2POOL_SELECT_LAST: &str = "Select the previous remote Monero node";
|
||||
pub const P2POOL_SELECT_NEXT: &str = "Select the next remote Monero node";
|
||||
pub const P2POOL_PING: &str = "Ping the built-in remote 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_COMMUNITY_NODE_WARNING: &str =
|
||||
r#"TL;DR: Run & use your own Monero Node.
|
||||
pub const P2POOL_BACKUP_HOST_ADVANCED: &str =
|
||||
"Automatically switch to the other nodes in your list if the current one is down.";
|
||||
pub const P2POOL_SELECT_FASTEST: &str = "Select the fastest remote Monero node";
|
||||
pub const P2POOL_SELECT_RANDOM: &str = "Select a random remote Monero node";
|
||||
pub const P2POOL_SELECT_LAST: &str = "Select the previous remote Monero node";
|
||||
pub const P2POOL_SELECT_NEXT: &str = "Select the next remote Monero node";
|
||||
pub const P2POOL_PING: &str = "Ping the built-in remote 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_COMMUNITY_NODE_WARNING: &str = r#"--- Run and use your own Monero node ---
|
||||
|
||||
Using a Remote Monero Node is convenient but comes at the cost of privacy and reliability.
|
||||
Using a remote Monero node is convenient but comes at the cost of privacy and reliability.
|
||||
|
||||
You may encounter connection issues with remote nodes which may cause mining performance loss! Late info from laggy nodes will cause your mining jobs to start later than they should.
|
||||
You may encounter connection issues with remote nodes which may cause mining performance loss. Late info from remote nodes may cause mining jobs to start later than they should.
|
||||
|
||||
Running and using your own local Monero node improves privacy and ensures your connection is as stable as your own internet connection. This comes at the cost of downloading and syncing Monero's blockchain yourself (currently 170GB). If you have the disk space, consider using the [Advanced] tab and connecting to your own Monero node.
|
||||
Running and using your own local Monero node improves privacy and ensures your connection is as stable as your own internet connection. This comes at the cost of downloading and syncing Monero's blockchain. If you have the disk space, consider using the [Advanced] tab and connecting to your own Monero node.
|
||||
|
||||
For a simple guide, see the [Running a Local Monero Node] section on Gupax's GitHub by clicking this message."#;
|
||||
For a simple guide, see the [Running a Local Monero Node] documentation by clicking this message."#;
|
||||
|
||||
pub const P2POOL_INPUT: &str = "Send a command to P2Pool";
|
||||
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!
|
||||
pub const P2POOL_ARGUMENTS: &str = r#"Note: [--no-color] & [--data-api <PATH>] & [--local-api] must be set so that the [Status] tab can work!
|
||||
|
||||
Start P2Pool with these arguments and override all below settings"#;
|
||||
pub const P2POOL_SIMPLE: &str =
|
||||
r#"Use simple P2Pool settings:
|
||||
pub const P2POOL_SIMPLE: &str = r#"Use simple P2Pool settings:
|
||||
- Remote remote Monero node
|
||||
- Default P2Pool settings + Mini
|
||||
- Backup host setting"#;
|
||||
pub const P2POOL_ADVANCED: &str =
|
||||
r#"Use advanced P2Pool settings:
|
||||
pub const P2POOL_ADVANCED: &str = r#"Use advanced P2Pool settings:
|
||||
- Terminal input
|
||||
- Overriding command arguments
|
||||
- Manual node list
|
||||
|
@ -328,19 +349,17 @@ 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.";
|
||||
|
||||
// Node/Pool 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_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_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
|
||||
pub const XMRIG_SIMPLE: &str =
|
||||
r#"Use simple XMRig settings:
|
||||
pub const XMRIG_SIMPLE: &str = r#"Use simple XMRig settings:
|
||||
- Mine to local P2Pool (localhost:3333)
|
||||
- CPU thread slider
|
||||
- HTTP API @ localhost:18088"#;
|
||||
pub const XMRIG_ADVANCED: &str =
|
||||
r#"Use advanced XMRig settings:
|
||||
pub const XMRIG_ADVANCED: &str = r#"Use advanced XMRig settings:
|
||||
- Terminal input
|
||||
- Overriding command arguments
|
||||
- Custom payout address
|
||||
|
@ -350,30 +369,31 @@ r#"Use advanced XMRig settings:
|
|||
- TLS setting
|
||||
- Keepalive setting"#;
|
||||
pub const XMRIG_INPUT: &str = "Send a command to XMRig";
|
||||
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!
|
||||
pub const XMRIG_ARGUMENTS: &str = r#"Note: [--no-color] & [--http-host <IP>] & [--http-port <PORT>] must be setso that the [Status] tab can work!
|
||||
|
||||
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 paid 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_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";
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub const XMRIG_PAUSE: &str = "THIS SETTING IS DISABLED IF SET TO [0]. Pause mining if user is active, resume after";
|
||||
pub const XMRIG_API_IP: &str = "Specify which IP to bind to for XMRig's HTTP API; If empty: [localhost/127.0.0.1]";
|
||||
pub const XMRIG_API_PORT: &str = "Specify which port to bind to for XMRig's HTTP API; If empty: [18088]";
|
||||
pub const XMRIG_TLS: &str = "Enable SSL/TLS connections (needs pool support)";
|
||||
pub const XMRIG_KEEPALIVE: &str = "Send keepalive packets to prevent timeout (needs pool support)";
|
||||
pub const XMRIG_THREADS: &str = "Number of CPU threads to use for mining";
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub const XMRIG_PAUSE: &str =
|
||||
"THIS SETTING IS DISABLED IF SET TO [0]. Pause mining if user is active, resume after";
|
||||
pub const XMRIG_API_IP: &str =
|
||||
"Specify which IP to bind to for XMRig's HTTP API; If empty: [localhost/127.0.0.1]";
|
||||
pub const XMRIG_API_PORT: &str =
|
||||
"Specify which port to bind to for XMRig's HTTP API; If empty: [18088]";
|
||||
pub const XMRIG_TLS: &str = "Enable SSL/TLS connections (needs pool support)";
|
||||
pub const XMRIG_KEEPALIVE: &str = "Send keepalive packets to prevent timeout (needs pool support)";
|
||||
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_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.";
|
||||
|
||||
// CLI argument messages
|
||||
pub const ARG_HELP: &str =
|
||||
r#"USAGE: ./gupax [--flag]
|
||||
pub const ARG_HELP: &str = r#"USAGE: ./gupax [--flag]
|
||||
|
||||
--help Print this help message
|
||||
--version Print version and build info
|
||||
|
@ -390,29 +410,16 @@ r#"USAGE: ./gupax [--flag]
|
|||
To view more detailed console debug information, start Gupax with
|
||||
the environment variable [RUST_LOG] set to a log level like so:
|
||||
RUST_LOG=(trace|debug|info|warn|error) ./gupax"#;
|
||||
pub const ARG_COPYRIGHT: &str =
|
||||
r#"Gupax is licensed under GPLv3.
|
||||
pub const ARG_COPYRIGHT: &str = r#"Gupax is licensed under GPLv3.
|
||||
For more information, see link below:
|
||||
<https://github.com/hinto-janai/gupax>"#;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Visuals
|
||||
use egui::epaint::{
|
||||
Rounding,
|
||||
Shadow,
|
||||
Stroke
|
||||
};
|
||||
use egui::epaint::{Rounding, Stroke};
|
||||
|
||||
use egui::{
|
||||
Color32,
|
||||
Visuals,
|
||||
style::Spacing,
|
||||
};
|
||||
use egui::{Color32, Visuals};
|
||||
|
||||
use egui::style::{
|
||||
Selection,
|
||||
Widgets,
|
||||
WidgetVisuals,
|
||||
};
|
||||
use egui::style::{Selection, WidgetVisuals, Widgets};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub const ACCENT_COLOR: Color32 = Color32::from_rgb(200, 100, 100);
|
||||
|
@ -420,88 +427,100 @@ pub const BG: Color32 = Color32::from_gray(20);
|
|||
|
||||
// This is based off [`Visuals::dark()`].
|
||||
pub static VISUALS: Lazy<Visuals> = Lazy::new(|| {
|
||||
let selection = Selection {
|
||||
bg_fill: ACCENT_COLOR,
|
||||
stroke: Stroke::new(1.0, Color32::from_gray(255)),
|
||||
};
|
||||
let selection = Selection {
|
||||
bg_fill: ACCENT_COLOR,
|
||||
stroke: Stroke::new(1.0, Color32::from_gray(255)),
|
||||
};
|
||||
|
||||
let widgets = Widgets {
|
||||
noninteractive: WidgetVisuals {
|
||||
bg_fill: BG,
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(50),
|
||||
bg_stroke: Default::default(),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(80),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
|
||||
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 1.0,
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(55),
|
||||
bg_stroke: Stroke::new(1.0, Color32::WHITE),
|
||||
fg_stroke: Stroke::new(2.0, Color32::WHITE),
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 1.0,
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(27),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 0.0,
|
||||
},
|
||||
};
|
||||
// Based off default dark() mode.
|
||||
// https://docs.rs/egui/0.24.1/src/egui/style.rs.html#1210
|
||||
let widgets = Widgets {
|
||||
noninteractive: WidgetVisuals {
|
||||
bg_fill: BG,
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 0.0,
|
||||
weak_bg_fill: BG,
|
||||
},
|
||||
inactive: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(50),
|
||||
bg_stroke: Default::default(),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 0.0,
|
||||
weak_bg_fill: Color32::from_gray(50),
|
||||
},
|
||||
hovered: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(80),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
|
||||
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 1.0,
|
||||
weak_bg_fill: Color32::from_gray(80),
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(55),
|
||||
bg_stroke: Stroke::new(1.0, Color32::WHITE),
|
||||
fg_stroke: Stroke::new(2.0, Color32::WHITE),
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 1.0,
|
||||
weak_bg_fill: Color32::from_gray(120),
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
bg_fill: Color32::from_gray(27),
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
|
||||
rounding: Rounding::same(10.0),
|
||||
expansion: 0.0,
|
||||
weak_bg_fill: Color32::from_gray(120),
|
||||
},
|
||||
};
|
||||
|
||||
// https://docs.rs/egui/0.24.1/src/egui/style.rs.html#1113
|
||||
Visuals {
|
||||
dark_mode: true,
|
||||
override_text_color: None,
|
||||
widgets,
|
||||
selection,
|
||||
hyperlink_color: Color32::from_rgb(90, 170, 255),
|
||||
faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
|
||||
extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background
|
||||
code_bg_color: Color32::from_gray(64),
|
||||
warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
|
||||
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
||||
window_rounding: Rounding::same(6.0),
|
||||
window_shadow: Shadow::big_dark(),
|
||||
popup_shadow: Shadow::small_dark(),
|
||||
resize_corner_size: 12.0,
|
||||
text_cursor_width: 2.0,
|
||||
text_cursor_preview: false,
|
||||
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
|
||||
button_frame: true,
|
||||
collapsing_header_frame: false,
|
||||
}
|
||||
widgets,
|
||||
selection,
|
||||
hyperlink_color: Color32::from_rgb(90, 170, 255),
|
||||
faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so
|
||||
extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background
|
||||
code_bg_color: Color32::from_gray(64),
|
||||
warn_fg_color: Color32::from_rgb(255, 143, 0), // orange
|
||||
error_fg_color: Color32::from_rgb(255, 0, 0), // red
|
||||
window_rounding: Rounding::same(6.0),
|
||||
// window_shadow: Shadow::big_dark(),
|
||||
// popup_shadow: Shadow::small_dark(),
|
||||
..Visuals::dark()
|
||||
}
|
||||
});
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- TESTS
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn gupax_version_is_semver() {
|
||||
assert_eq!(crate::GUPAX_VERSION.len(), 6);
|
||||
}
|
||||
#[test]
|
||||
fn gupax_version_is_semver() {
|
||||
let len = crate::GUPAX_VERSION.len();
|
||||
println!("{len}");
|
||||
assert!(len == 6 || len == 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_app_ratio_is_4_by_3() {
|
||||
assert_eq!(format!("{:.3}", crate::APP_MIN_WIDTH/crate::APP_MIN_HEIGHT), "1.333");
|
||||
assert_eq!(format!("{:.3}", crate::APP_DEFAULT_WIDTH/crate::APP_DEFAULT_HEIGHT), "1.333");
|
||||
}
|
||||
#[test]
|
||||
fn default_app_ratio_is_4_by_3() {
|
||||
assert_eq!(
|
||||
format!("{:.3}", crate::APP_MIN_WIDTH / crate::APP_MIN_HEIGHT),
|
||||
"1.333"
|
||||
);
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{:.3}",
|
||||
crate::APP_DEFAULT_WIDTH / crate::APP_DEFAULT_HEIGHT
|
||||
),
|
||||
"1.333"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn git_commit_eq_or_gt_40_chars() {
|
||||
assert!(crate::COMMIT.len() >= 40);
|
||||
}
|
||||
#[test]
|
||||
fn git_commit_eq_or_gt_40_chars() {
|
||||
assert!(crate::COMMIT.len() >= 40);
|
||||
}
|
||||
}
|
||||
|
|
14667
src/cpu.json
14667
src/cpu.json
File diff suppressed because it is too large
Load diff
2165
src/disk.rs
2165
src/disk.rs
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
17
src/free.rs
17
src/free.rs
|
@ -4,14 +4,15 @@
|
|||
use crate::constants::*;
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
#[inline]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
// Clamp the scaling resolution `f32` to a known good `f32`.
|
||||
pub fn clamp_scale(scale: f32) -> f32 {
|
||||
// Make sure it is finite.
|
||||
if !scale.is_finite() {
|
||||
return APP_DEFAULT_SCALE;
|
||||
}
|
||||
// Make sure it is finite.
|
||||
if !scale.is_finite() {
|
||||
return APP_DEFAULT_SCALE;
|
||||
}
|
||||
|
||||
// Clamp between valid range.
|
||||
scale.clamp(APP_MIN_SCALE, APP_MAX_SCALE)
|
||||
}
|
||||
// Clamp between valid range.
|
||||
scale.clamp(APP_MIN_SCALE, APP_MAX_SCALE)
|
||||
}
|
||||
|
|
732
src/gupax.rs
732
src/gupax.rs
|
@ -16,283 +16,521 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::State;
|
||||
use crate::{constants::*, macros::*, update::*, ErrorState, Restart, Tab};
|
||||
use egui::{
|
||||
TextEdit,
|
||||
TextStyle,
|
||||
TextStyle::Monospace,
|
||||
Checkbox,ProgressBar,Spinner,Button,Label,Slider,
|
||||
SelectableLabel,
|
||||
RichText,
|
||||
Vec2,
|
||||
};
|
||||
use crate::{
|
||||
constants::*,
|
||||
update::*,
|
||||
ErrorState,
|
||||
Restart,
|
||||
Tab,
|
||||
macros::*,
|
||||
};
|
||||
use std::{
|
||||
thread,
|
||||
sync::{Arc,Mutex},
|
||||
path::Path,
|
||||
Button, Checkbox, Label, ProgressBar, RichText, SelectableLabel, Slider, Spinner, TextEdit,
|
||||
Vec2,
|
||||
};
|
||||
use log::*;
|
||||
use serde::{Serialize,Deserialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- FileWindow
|
||||
// Struct for writing/reading the path state.
|
||||
// The opened file picker is started in a new
|
||||
// thread so main() needs to be in sync.
|
||||
pub struct FileWindow {
|
||||
thread: bool, // Is there already a FileWindow thread?
|
||||
picked_p2pool: bool, // Did the user pick a path for p2pool?
|
||||
picked_xmrig: bool, // Did the user pick a path for xmrig?
|
||||
p2pool_path: String, // The picked p2pool path
|
||||
xmrig_path: String, // The picked p2pool path
|
||||
thread: bool, // Is there already a FileWindow thread?
|
||||
picked_p2pool: bool, // Did the user pick a path for p2pool?
|
||||
picked_xmrig: bool, // Did the user pick a path for xmrig?
|
||||
p2pool_path: String, // The picked p2pool path
|
||||
xmrig_path: String, // The picked p2pool path
|
||||
}
|
||||
|
||||
impl FileWindow {
|
||||
pub fn new() -> Arc<Mutex<Self>> {
|
||||
arc_mut!(Self {
|
||||
thread: false,
|
||||
picked_p2pool: false,
|
||||
picked_xmrig: false,
|
||||
p2pool_path: String::new(),
|
||||
xmrig_path: String::new(),
|
||||
})
|
||||
}
|
||||
pub fn new() -> Arc<Mutex<Self>> {
|
||||
arc_mut!(Self {
|
||||
thread: false,
|
||||
picked_p2pool: false,
|
||||
picked_xmrig: false,
|
||||
p2pool_path: String::new(),
|
||||
xmrig_path: String::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FileType {
|
||||
P2pool,
|
||||
Xmrig,
|
||||
P2pool,
|
||||
Xmrig,
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Ratio Lock
|
||||
// Enum for the lock ratio in the advanced tab.
|
||||
#[derive(Clone,Copy,Eq,PartialEq,Debug,Deserialize,Serialize)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)]
|
||||
pub enum Ratio {
|
||||
Width,
|
||||
Height,
|
||||
None,
|
||||
Width,
|
||||
Height,
|
||||
None,
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Gupax
|
||||
impl crate::disk::Gupax {
|
||||
#[inline(always)]
|
||||
pub fn show(&mut self, og: &Arc<Mutex<State>>, state_path: &Path, update: &Arc<Mutex<Update>>, file_window: &Arc<Mutex<FileWindow>>, error_state: &mut ErrorState, restart: &Arc<Mutex<Restart>>, width: f32, height: f32, frame: &mut eframe::Frame, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
||||
// Update button + Progress bar
|
||||
debug!("Gupax Tab | Rendering [Update] button + progress bar");
|
||||
ui.group(|ui| {
|
||||
let button = if self.simple { height/5.0 } else { height/15.0 };
|
||||
let height = if self.simple { height/5.0 } else { height/10.0 };
|
||||
let width = width - SPACE;
|
||||
let updating = *lock2!(update,updating);
|
||||
ui.vertical(|ui| {
|
||||
// If [Gupax] is being built for a Linux distro,
|
||||
// disable built-in updating completely.
|
||||
#[cfg(feature = "distro")]
|
||||
ui.set_enabled(false);
|
||||
#[cfg(feature = "distro")]
|
||||
ui.add_sized([width, button], Button::new("Updates are disabled")).on_disabled_hover_text(DISTRO_NO_UPDATE);
|
||||
#[cfg(not(feature = "distro"))]
|
||||
ui.set_enabled(!updating);
|
||||
#[cfg(not(feature = "distro"))]
|
||||
if ui.add_sized([width, button], Button::new("Check for updates")).on_hover_text(GUPAX_UPDATE).clicked() {
|
||||
Update::spawn_thread(og, self, state_path, update, error_state, restart);
|
||||
}
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.set_enabled(updating);
|
||||
let prog = *lock2!(update,prog);
|
||||
let msg = format!("{}\n{}{}", *lock2!(update,msg), prog, "%");
|
||||
ui.add_sized([width, height*1.4], Label::new(RichText::new(msg)));
|
||||
let height = height/2.0;
|
||||
if updating {
|
||||
ui.add_sized([width, height], Spinner::new().size(height));
|
||||
} else {
|
||||
ui.add_sized([width, height], Label::new("..."));
|
||||
}
|
||||
ui.add_sized([width, height], ProgressBar::new(lock2!(update,prog).round() / 100.0));
|
||||
});
|
||||
});
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn show(
|
||||
&mut self,
|
||||
og: &Arc<Mutex<State>>,
|
||||
state_path: &Path,
|
||||
update: &Arc<Mutex<Update>>,
|
||||
file_window: &Arc<Mutex<FileWindow>>,
|
||||
error_state: &mut ErrorState,
|
||||
restart: &Arc<Mutex<Restart>>,
|
||||
width: f32,
|
||||
height: f32,
|
||||
_frame: &mut eframe::Frame,
|
||||
_ctx: &egui::Context,
|
||||
ui: &mut egui::Ui,
|
||||
) {
|
||||
// Update button + Progress bar
|
||||
debug!("Gupax Tab | Rendering [Update] button + progress bar");
|
||||
ui.group(|ui| {
|
||||
let button = if self.simple {
|
||||
height / 5.0
|
||||
} else {
|
||||
height / 15.0
|
||||
};
|
||||
let height = if self.simple {
|
||||
height / 5.0
|
||||
} else {
|
||||
height / 10.0
|
||||
};
|
||||
let width = width - SPACE;
|
||||
let updating = *lock2!(update, updating);
|
||||
ui.vertical(|ui| {
|
||||
// If [Gupax] is being built for a Linux distro,
|
||||
// disable built-in updating completely.
|
||||
#[cfg(feature = "distro")]
|
||||
ui.set_enabled(false);
|
||||
#[cfg(feature = "distro")]
|
||||
ui.add_sized([width, button], Button::new("Updates are disabled"))
|
||||
.on_disabled_hover_text(DISTRO_NO_UPDATE);
|
||||
#[cfg(not(feature = "distro"))]
|
||||
ui.set_enabled(!updating);
|
||||
#[cfg(not(feature = "distro"))]
|
||||
if ui
|
||||
.add_sized([width, button], Button::new("Check for updates"))
|
||||
.on_hover_text(GUPAX_UPDATE)
|
||||
.clicked()
|
||||
{
|
||||
Update::spawn_thread(og, self, state_path, update, error_state, restart);
|
||||
}
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.set_enabled(updating);
|
||||
let prog = *lock2!(update, prog);
|
||||
let msg = format!("{}\n{}{}", *lock2!(update, msg), prog, "%");
|
||||
ui.add_sized([width, height * 1.4], Label::new(RichText::new(msg)));
|
||||
let height = height / 2.0;
|
||||
if updating {
|
||||
ui.add_sized([width, height], Spinner::new().size(height));
|
||||
} else {
|
||||
ui.add_sized([width, height], Label::new("..."));
|
||||
}
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
ProgressBar::new(lock2!(update, prog).round() / 100.0),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
debug!("Gupax Tab | Rendering bool buttons");
|
||||
ui.horizontal(|ui| {
|
||||
ui.group(|ui| {
|
||||
let width = (width - SPACE*12.0)/6.0;
|
||||
let height = if self.simple { height/10.0 } else { height/15.0 };
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.update_via_tor, "Update via Tor")).on_hover_text(GUPAX_UPDATE_VIA_TOR);
|
||||
ui.separator();
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.auto_update, "Auto-Update")).on_hover_text(GUPAX_AUTO_UPDATE);
|
||||
ui.separator();
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.auto_p2pool, "Auto-P2Pool")).on_hover_text(GUPAX_AUTO_P2POOL);
|
||||
ui.separator();
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.auto_xmrig, "Auto-XMRig")).on_hover_text(GUPAX_AUTO_XMRIG);
|
||||
ui.separator();
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.ask_before_quit, "Ask before quit")).on_hover_text(GUPAX_ASK_BEFORE_QUIT);
|
||||
ui.separator();
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.save_before_quit, "Save before quit")).on_hover_text(GUPAX_SAVE_BEFORE_QUIT);
|
||||
});
|
||||
});
|
||||
debug!("Gupax Tab | Rendering bool buttons");
|
||||
ui.horizontal(|ui| {
|
||||
ui.group(|ui| {
|
||||
let width = (width - SPACE * 12.0) / 6.0;
|
||||
let height = if self.simple {
|
||||
height / 10.0
|
||||
} else {
|
||||
height / 15.0
|
||||
};
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.update_via_tor, "Update via Tor"),
|
||||
)
|
||||
.on_hover_text(GUPAX_UPDATE_VIA_TOR);
|
||||
ui.separator();
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.auto_update, "Auto-Update"),
|
||||
)
|
||||
.on_hover_text(GUPAX_AUTO_UPDATE);
|
||||
ui.separator();
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.auto_p2pool, "Auto-P2Pool"),
|
||||
)
|
||||
.on_hover_text(GUPAX_AUTO_P2POOL);
|
||||
ui.separator();
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.auto_xmrig, "Auto-XMRig"),
|
||||
)
|
||||
.on_hover_text(GUPAX_AUTO_XMRIG);
|
||||
ui.separator();
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.ask_before_quit, "Ask before quit"),
|
||||
)
|
||||
.on_hover_text(GUPAX_ASK_BEFORE_QUIT);
|
||||
ui.separator();
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.save_before_quit, "Save before quit"),
|
||||
)
|
||||
.on_hover_text(GUPAX_SAVE_BEFORE_QUIT);
|
||||
});
|
||||
});
|
||||
|
||||
if self.simple { return }
|
||||
if self.simple {
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("Gupax Tab | Rendering P2Pool/XMRig path selection");
|
||||
// P2Pool/XMRig binary path selection
|
||||
let height = height/28.0;
|
||||
let text_edit = (ui.available_width()/10.0)-SPACE;
|
||||
ui.group(|ui| {
|
||||
ui.add_sized([ui.available_width(), height/2.0], Label::new(RichText::new("P2Pool/XMRig PATHs").underline().color(LIGHT_GRAY))).on_hover_text("Gupax is online");
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if self.p2pool_path.is_empty() {
|
||||
ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ➖").color(LIGHT_GRAY))).on_hover_text(P2POOL_PATH_EMPTY);
|
||||
} else if !Self::path_is_file(&self.p2pool_path) {
|
||||
ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(RED))).on_hover_text(P2POOL_PATH_NOT_FILE);
|
||||
} else if !crate::update::check_p2pool_path(&self.p2pool_path) {
|
||||
ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ❌").color(RED))).on_hover_text(P2POOL_PATH_NOT_VALID);
|
||||
} else {
|
||||
ui.add_sized([text_edit, height], Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN))).on_hover_text(P2POOL_PATH_OK);
|
||||
}
|
||||
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
|
||||
ui.set_enabled(!lock!(file_window).thread);
|
||||
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
|
||||
Self::spawn_file_window_thread(file_window, FileType::P2pool);
|
||||
}
|
||||
ui.add_sized([ui.available_width(), height], TextEdit::singleline(&mut self.p2pool_path)).on_hover_text(GUPAX_PATH_P2POOL);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
if self.xmrig_path.is_empty() {
|
||||
ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ➖").color(LIGHT_GRAY))).on_hover_text(XMRIG_PATH_EMPTY);
|
||||
} else if !Self::path_is_file(&self.xmrig_path) {
|
||||
ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(RED))).on_hover_text(XMRIG_PATH_NOT_FILE);
|
||||
} else if !crate::update::check_xmrig_path(&self.xmrig_path) {
|
||||
ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ❌").color(RED))).on_hover_text(XMRIG_PATH_NOT_VALID);
|
||||
} else {
|
||||
ui.add_sized([text_edit, height], Label::new(RichText::new(" XMRig Binary Path ✔").color(GREEN))).on_hover_text(XMRIG_PATH_OK);
|
||||
}
|
||||
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
|
||||
ui.set_enabled(!lock!(file_window).thread);
|
||||
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
|
||||
Self::spawn_file_window_thread(file_window, FileType::Xmrig);
|
||||
}
|
||||
ui.add_sized([ui.available_width(), height], TextEdit::singleline(&mut self.xmrig_path)).on_hover_text(GUPAX_PATH_XMRIG);
|
||||
});
|
||||
});
|
||||
let mut guard = lock!(file_window);
|
||||
if guard.picked_p2pool { self.p2pool_path = guard.p2pool_path.clone(); guard.picked_p2pool = false; }
|
||||
if guard.picked_xmrig { self.xmrig_path = guard.xmrig_path.clone(); guard.picked_xmrig = false; }
|
||||
drop(guard);
|
||||
debug!("Gupax Tab | Rendering P2Pool/XMRig path selection");
|
||||
// P2Pool/XMRig binary path selection
|
||||
let height = height / 28.0;
|
||||
let text_edit = (ui.available_width() / 10.0) - SPACE;
|
||||
ui.group(|ui| {
|
||||
ui.add_sized(
|
||||
[ui.available_width(), height / 2.0],
|
||||
Label::new(
|
||||
RichText::new("P2Pool/XMRig PATHs")
|
||||
.underline()
|
||||
.color(LIGHT_GRAY),
|
||||
),
|
||||
)
|
||||
.on_hover_text("Gupax is online");
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if self.p2pool_path.is_empty() {
|
||||
ui.add_sized(
|
||||
[text_edit, height],
|
||||
Label::new(RichText::new("P2Pool Binary Path ➖").color(LIGHT_GRAY)),
|
||||
)
|
||||
.on_hover_text(P2POOL_PATH_EMPTY);
|
||||
} else if !Self::path_is_file(&self.p2pool_path) {
|
||||
ui.add_sized(
|
||||
[text_edit, height],
|
||||
Label::new(RichText::new("P2Pool Binary Path ❌").color(RED)),
|
||||
)
|
||||
.on_hover_text(P2POOL_PATH_NOT_FILE);
|
||||
} else if !crate::update::check_p2pool_path(&self.p2pool_path) {
|
||||
ui.add_sized(
|
||||
[text_edit, height],
|
||||
Label::new(RichText::new("P2Pool Binary Path ❌").color(RED)),
|
||||
)
|
||||
.on_hover_text(P2POOL_PATH_NOT_VALID);
|
||||
} else {
|
||||
ui.add_sized(
|
||||
[text_edit, height],
|
||||
Label::new(RichText::new("P2Pool Binary Path ✔").color(GREEN)),
|
||||
)
|
||||
.on_hover_text(P2POOL_PATH_OK);
|
||||
}
|
||||
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
|
||||
ui.set_enabled(!lock!(file_window).thread);
|
||||
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
|
||||
Self::spawn_file_window_thread(file_window, FileType::P2pool);
|
||||
}
|
||||
ui.add_sized(
|
||||
[ui.available_width(), height],
|
||||
TextEdit::singleline(&mut self.p2pool_path),
|
||||
)
|
||||
.on_hover_text(GUPAX_PATH_P2POOL);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
if self.xmrig_path.is_empty() {
|
||||
ui.add_sized(
|
||||
[text_edit, height],
|
||||
Label::new(RichText::new(" XMRig Binary Path ➖").color(LIGHT_GRAY)),
|
||||
)
|
||||
.on_hover_text(XMRIG_PATH_EMPTY);
|
||||
} else if !Self::path_is_file(&self.xmrig_path) {
|
||||
ui.add_sized(
|
||||
[text_edit, height],
|
||||
Label::new(RichText::new(" XMRig Binary Path ❌").color(RED)),
|
||||
)
|
||||
.on_hover_text(XMRIG_PATH_NOT_FILE);
|
||||
} else if !crate::update::check_xmrig_path(&self.xmrig_path) {
|
||||
ui.add_sized(
|
||||
[text_edit, height],
|
||||
Label::new(RichText::new(" XMRig Binary Path ❌").color(RED)),
|
||||
)
|
||||
.on_hover_text(XMRIG_PATH_NOT_VALID);
|
||||
} else {
|
||||
ui.add_sized(
|
||||
[text_edit, height],
|
||||
Label::new(RichText::new(" XMRig Binary Path ✔").color(GREEN)),
|
||||
)
|
||||
.on_hover_text(XMRIG_PATH_OK);
|
||||
}
|
||||
ui.spacing_mut().text_edit_width = ui.available_width() - SPACE;
|
||||
ui.set_enabled(!lock!(file_window).thread);
|
||||
if ui.button("Open").on_hover_text(GUPAX_SELECT).clicked() {
|
||||
Self::spawn_file_window_thread(file_window, FileType::Xmrig);
|
||||
}
|
||||
ui.add_sized(
|
||||
[ui.available_width(), height],
|
||||
TextEdit::singleline(&mut self.xmrig_path),
|
||||
)
|
||||
.on_hover_text(GUPAX_PATH_XMRIG);
|
||||
});
|
||||
});
|
||||
let mut guard = lock!(file_window);
|
||||
if guard.picked_p2pool {
|
||||
self.p2pool_path = guard.p2pool_path.clone();
|
||||
guard.picked_p2pool = false;
|
||||
}
|
||||
if guard.picked_xmrig {
|
||||
self.xmrig_path = guard.xmrig_path.clone();
|
||||
guard.picked_xmrig = false;
|
||||
}
|
||||
drop(guard);
|
||||
|
||||
let height = ui.available_height()/6.0;
|
||||
let height = ui.available_height() / 6.0;
|
||||
|
||||
// Saved [Tab]
|
||||
debug!("Gupax Tab | Rendering [Tab] selector");
|
||||
ui.group(|ui| {
|
||||
let width = (width/5.0)-(SPACE*1.93);
|
||||
ui.add_sized([ui.available_width(), height/2.0], Label::new(RichText::new("Default Tab").underline().color(LIGHT_GRAY))).on_hover_text(GUPAX_TAB);
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::About, "About")).on_hover_text(GUPAX_TAB_ABOUT).clicked() { self.tab = Tab::About; }
|
||||
ui.separator();
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Status, "Status")).on_hover_text(GUPAX_TAB_STATUS).clicked() { self.tab = Tab::Status; }
|
||||
ui.separator();
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Gupax, "Gupax")).on_hover_text(GUPAX_TAB_GUPAX).clicked() { self.tab = Tab::Gupax; }
|
||||
ui.separator();
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool")).on_hover_text(GUPAX_TAB_P2POOL).clicked() { self.tab = Tab::P2pool; }
|
||||
ui.separator();
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig")).on_hover_text(GUPAX_TAB_XMRIG).clicked() { self.tab = Tab::Xmrig; }
|
||||
})});
|
||||
// Saved [Tab]
|
||||
debug!("Gupax Tab | Rendering [Tab] selector");
|
||||
ui.group(|ui| {
|
||||
let width = (width / 5.0) - (SPACE * 1.93);
|
||||
ui.add_sized(
|
||||
[ui.available_width(), height / 2.0],
|
||||
Label::new(RichText::new("Default Tab").underline().color(LIGHT_GRAY)),
|
||||
)
|
||||
.on_hover_text(GUPAX_TAB);
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.tab == Tab::About, "About"),
|
||||
)
|
||||
.on_hover_text(GUPAX_TAB_ABOUT)
|
||||
.clicked()
|
||||
{
|
||||
self.tab = Tab::About;
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.tab == Tab::Status, "Status"),
|
||||
)
|
||||
.on_hover_text(GUPAX_TAB_STATUS)
|
||||
.clicked()
|
||||
{
|
||||
self.tab = Tab::Status;
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.tab == Tab::Gupax, "Gupax"),
|
||||
)
|
||||
.on_hover_text(GUPAX_TAB_GUPAX)
|
||||
.clicked()
|
||||
{
|
||||
self.tab = Tab::Gupax;
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.tab == Tab::P2pool, "P2Pool"),
|
||||
)
|
||||
.on_hover_text(GUPAX_TAB_P2POOL)
|
||||
.clicked()
|
||||
{
|
||||
self.tab = Tab::P2pool;
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.tab == Tab::Xmrig, "XMRig"),
|
||||
)
|
||||
.on_hover_text(GUPAX_TAB_XMRIG)
|
||||
.clicked()
|
||||
{
|
||||
self.tab = Tab::Xmrig;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Gupax App resolution sliders
|
||||
debug!("Gupax Tab | Rendering resolution sliders");
|
||||
ui.group(|ui| {
|
||||
ui.add_sized([ui.available_width(), height/2.0], Label::new(RichText::new("Width/Height Adjust").underline().color(LIGHT_GRAY))).on_hover_text(GUPAX_ADJUST);
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
let width = width/10.0;
|
||||
ui.spacing_mut().icon_width = width / 25.0;
|
||||
ui.spacing_mut().slider_width = width*7.6;
|
||||
match self.ratio {
|
||||
Ratio::None => (),
|
||||
Ratio::Width => {
|
||||
let width = self.selected_width as f64;
|
||||
let height = (width / 1.333).round();
|
||||
self.selected_height = height as u16;
|
||||
},
|
||||
Ratio::Height => {
|
||||
let height = self.selected_height as f64;
|
||||
let width = (height * 1.333).round();
|
||||
self.selected_width = width as u16;
|
||||
},
|
||||
}
|
||||
let height = height/3.5;
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_enabled(self.ratio != Ratio::Height);
|
||||
ui.add_sized([width, height], Label::new(format!(" Width [{}-{}]:", APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16)));
|
||||
ui.add_sized([width, height], Slider::new(&mut self.selected_width, APP_MIN_WIDTH as u16..=APP_MAX_WIDTH as u16)).on_hover_text(GUPAX_WIDTH);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_enabled(self.ratio != Ratio::Width);
|
||||
ui.add_sized([width, height], Label::new(format!("Height [{}-{}]:", APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16)));
|
||||
ui.add_sized([width, height], Slider::new(&mut self.selected_height, APP_MIN_HEIGHT as u16..=APP_MAX_HEIGHT as u16)).on_hover_text(GUPAX_HEIGHT);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([width, height], Label::new(format!("Scaling [{APP_MIN_SCALE}..{APP_MAX_SCALE}]:")));
|
||||
ui.add_sized([width, height], Slider::new(&mut self.selected_scale, APP_MIN_SCALE..=APP_MAX_SCALE).step_by(0.1)).on_hover_text(GUPAX_SCALE);
|
||||
});
|
||||
});
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Button);
|
||||
ui.separator();
|
||||
// Width/Height locks
|
||||
ui.horizontal(|ui| {
|
||||
use Ratio::*;
|
||||
let width = (width/4.0)-(SPACE*1.5);
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.ratio == Width, "Lock to width")).on_hover_text(GUPAX_LOCK_WIDTH).clicked() { self.ratio = Width; }
|
||||
ui.separator();
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.ratio == Height, "Lock to height")).on_hover_text(GUPAX_LOCK_HEIGHT).clicked() { self.ratio = Height; }
|
||||
ui.separator();
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.ratio == None, "No lock")).on_hover_text(GUPAX_NO_LOCK).clicked() { self.ratio = None; }
|
||||
if ui.add_sized([width, height], Button::new("Set")).on_hover_text(GUPAX_SET).clicked() {
|
||||
frame.set_window_size(Vec2::new(self.selected_width as f32, self.selected_height as f32));
|
||||
}
|
||||
})});
|
||||
}
|
||||
// Gupax App resolution sliders
|
||||
debug!("Gupax Tab | Rendering resolution sliders");
|
||||
ui.group(|ui| {
|
||||
ui.add_sized(
|
||||
[ui.available_width(), height / 2.0],
|
||||
Label::new(
|
||||
RichText::new("Width/Height Adjust")
|
||||
.underline()
|
||||
.color(LIGHT_GRAY),
|
||||
),
|
||||
)
|
||||
.on_hover_text(GUPAX_ADJUST);
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
let width = width / 10.0;
|
||||
ui.spacing_mut().icon_width = width / 25.0;
|
||||
ui.spacing_mut().slider_width = width * 7.6;
|
||||
match self.ratio {
|
||||
Ratio::None => (),
|
||||
Ratio::Width => {
|
||||
let width = self.selected_width as f64;
|
||||
let height = (width / 1.333).round();
|
||||
self.selected_height = height as u16;
|
||||
}
|
||||
Ratio::Height => {
|
||||
let height = self.selected_height as f64;
|
||||
let width = (height * 1.333).round();
|
||||
self.selected_width = width as u16;
|
||||
}
|
||||
}
|
||||
let height = height / 3.5;
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_enabled(self.ratio != Ratio::Height);
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Label::new(format!(
|
||||
" Width [{}-{}]:",
|
||||
APP_MIN_WIDTH as u16, APP_MAX_WIDTH as u16
|
||||
)),
|
||||
);
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Slider::new(
|
||||
&mut self.selected_width,
|
||||
APP_MIN_WIDTH as u16..=APP_MAX_WIDTH as u16,
|
||||
),
|
||||
)
|
||||
.on_hover_text(GUPAX_WIDTH);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_enabled(self.ratio != Ratio::Width);
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Label::new(format!(
|
||||
"Height [{}-{}]:",
|
||||
APP_MIN_HEIGHT as u16, APP_MAX_HEIGHT as u16
|
||||
)),
|
||||
);
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Slider::new(
|
||||
&mut self.selected_height,
|
||||
APP_MIN_HEIGHT as u16..=APP_MAX_HEIGHT as u16,
|
||||
),
|
||||
)
|
||||
.on_hover_text(GUPAX_HEIGHT);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Label::new(format!("Scaling [{APP_MIN_SCALE}..{APP_MAX_SCALE}]:")),
|
||||
);
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Slider::new(&mut self.selected_scale, APP_MIN_SCALE..=APP_MAX_SCALE)
|
||||
.step_by(0.1),
|
||||
)
|
||||
.on_hover_text(GUPAX_SCALE);
|
||||
});
|
||||
});
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Button);
|
||||
ui.separator();
|
||||
// Width/Height locks
|
||||
ui.horizontal(|ui| {
|
||||
use Ratio::*;
|
||||
let width = (width / 4.0) - (SPACE * 1.5);
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.ratio == Width, "Lock to width"),
|
||||
)
|
||||
.on_hover_text(GUPAX_LOCK_WIDTH)
|
||||
.clicked()
|
||||
{
|
||||
self.ratio = Width;
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.ratio == Height, "Lock to height"),
|
||||
)
|
||||
.on_hover_text(GUPAX_LOCK_HEIGHT)
|
||||
.clicked()
|
||||
{
|
||||
self.ratio = Height;
|
||||
}
|
||||
ui.separator();
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.ratio == None, "No lock"),
|
||||
)
|
||||
.on_hover_text(GUPAX_NO_LOCK)
|
||||
.clicked()
|
||||
{
|
||||
self.ratio = None;
|
||||
}
|
||||
if ui
|
||||
.add_sized([width, height], Button::new("Set"))
|
||||
.on_hover_text(GUPAX_SET)
|
||||
.clicked()
|
||||
{
|
||||
let size = Vec2::new(self.selected_width as f32, self.selected_height as f32);
|
||||
ui.ctx()
|
||||
.send_viewport_cmd(egui::viewport::ViewportCommand::InnerSize(size));
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Checks if a path is a valid path to a file.
|
||||
pub fn path_is_file(path: &str) -> bool {
|
||||
let path = path.to_string();
|
||||
match crate::disk::into_absolute_path(path) {
|
||||
Ok(path) => path.is_file(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
// Checks if a path is a valid path to a file.
|
||||
pub fn path_is_file(path: &str) -> bool {
|
||||
let path = path.to_string();
|
||||
match crate::disk::into_absolute_path(path) {
|
||||
Ok(path) => path.is_file(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_file_window_thread(file_window: &Arc<Mutex<FileWindow>>, file_type: FileType) {
|
||||
use FileType::*;
|
||||
let name = match file_type {
|
||||
P2pool => "P2Pool",
|
||||
Xmrig => "XMRig",
|
||||
};
|
||||
let file_window = file_window.clone();
|
||||
lock!(file_window).thread = true;
|
||||
thread::spawn(move|| {
|
||||
match rfd::FileDialog::new().set_title(&format!("Select {} Binary for Gupax", name)).pick_file() {
|
||||
Some(path) => {
|
||||
info!("Gupax | Path selected for {} ... {}", name, path.display());
|
||||
match file_type {
|
||||
P2pool => { lock!(file_window).p2pool_path = path.display().to_string(); lock!(file_window).picked_p2pool = true; },
|
||||
Xmrig => { lock!(file_window).xmrig_path = path.display().to_string(); lock!(file_window).picked_xmrig = true; },
|
||||
};
|
||||
},
|
||||
None => info!("Gupax | No path selected for {}", name),
|
||||
};
|
||||
lock!(file_window).thread = false;
|
||||
});
|
||||
}
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn spawn_file_window_thread(file_window: &Arc<Mutex<FileWindow>>, file_type: FileType) {
|
||||
use FileType::*;
|
||||
let name = match file_type {
|
||||
P2pool => "P2Pool",
|
||||
Xmrig => "XMRig",
|
||||
};
|
||||
let file_window = file_window.clone();
|
||||
lock!(file_window).thread = true;
|
||||
thread::spawn(move || {
|
||||
match rfd::FileDialog::new()
|
||||
.set_title(format!("Select {} Binary for Gupax", name))
|
||||
.pick_file()
|
||||
{
|
||||
Some(path) => {
|
||||
info!("Gupax | Path selected for {} ... {}", name, path.display());
|
||||
match file_type {
|
||||
P2pool => {
|
||||
lock!(file_window).p2pool_path = path.display().to_string();
|
||||
lock!(file_window).picked_p2pool = true;
|
||||
}
|
||||
Xmrig => {
|
||||
lock!(file_window).xmrig_path = path.display().to_string();
|
||||
lock!(file_window).picked_xmrig = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
None => info!("Gupax | No path selected for {}", name),
|
||||
};
|
||||
lock!(file_window).thread = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
4264
src/helper.rs
4264
src/helper.rs
File diff suppressed because it is too large
Load diff
633
src/human.rs
633
src/human.rs
|
@ -30,70 +30,74 @@ use std::time::Duration;
|
|||
pub struct HumanTime(Duration);
|
||||
|
||||
impl Default for HumanTime {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl HumanTime {
|
||||
#[inline(always)]
|
||||
pub const fn new() -> HumanTime {
|
||||
HumanTime(ZERO_SECONDS)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn new() -> HumanTime {
|
||||
HumanTime(ZERO_SECONDS)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn into_human(d: Duration) -> HumanTime {
|
||||
HumanTime(d)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn into_human(d: Duration) -> HumanTime {
|
||||
HumanTime(d)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_u64(u: u64) -> HumanTime {
|
||||
HumanTime(Duration::from_secs(u))
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_u64(u: u64) -> HumanTime {
|
||||
HumanTime(Duration::from_secs(u))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
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(())
|
||||
}
|
||||
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(());
|
||||
}
|
||||
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 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(())
|
||||
}
|
||||
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]
|
||||
|
@ -108,249 +112,304 @@ impl std::fmt::Display for HumanTime {
|
|||
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)
|
||||
}
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl HumanNumber {
|
||||
#[inline(always)]
|
||||
pub fn unknown() -> Self {
|
||||
Self("???".to_string())
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_hashrate(f: f32) -> Self {
|
||||
Self(format!("{} H/s", Self::from_f32(f)))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_percent(f: f32) -> Self {
|
||||
if f < 0.01 {
|
||||
Self("0%".to_string())
|
||||
} else {
|
||||
Self(format!("{:.2}%", f))
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_percent_3_point(f: f32) -> Self {
|
||||
Self(format!("{:.3}%", f))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_percent_no_fmt(f: f32) -> Self {
|
||||
Self(format!("{}%", f))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_f64_to_percent_3_point(f: f64) -> Self {
|
||||
Self(format!("{:.3}%", f))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_f64_to_percent_6_point(f: f64) -> Self {
|
||||
Self(format!("{:.6}%", f))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_f64_to_percent_9_point(f: f64) -> Self {
|
||||
Self(format!("{:.9}%", f))
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_f64_to_percent_no_fmt(f: f64) -> Self {
|
||||
Self(format!("{}%", f))
|
||||
}
|
||||
#[inline(always)]
|
||||
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())
|
||||
}
|
||||
#[inline(always)]
|
||||
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())
|
||||
}
|
||||
#[inline(always)]
|
||||
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())
|
||||
}
|
||||
#[inline(always)]
|
||||
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())
|
||||
}
|
||||
#[inline(always)]
|
||||
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())
|
||||
}
|
||||
#[inline(always)]
|
||||
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())
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_hashrate(array: [Option<f32>; 3]) -> Self {
|
||||
let mut string = "[".to_string();
|
||||
let mut buf = num_format::Buffer::new();
|
||||
#[inline]
|
||||
pub fn unknown() -> Self {
|
||||
Self("???".to_string())
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
Self(s.to_string())
|
||||
}
|
||||
#[inline]
|
||||
pub fn to_hashrate(f: f32) -> Self {
|
||||
Self(format!("{} H/s", Self::from_f32(f)))
|
||||
}
|
||||
#[inline]
|
||||
pub fn to_percent(f: f32) -> Self {
|
||||
if f < 0.01 {
|
||||
Self("0%".to_string())
|
||||
} else {
|
||||
Self(format!("{:.2}%", f))
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn to_percent_3_point(f: f32) -> Self {
|
||||
Self(format!("{:.3}%", f))
|
||||
}
|
||||
#[inline]
|
||||
pub fn to_percent_no_fmt(f: f32) -> Self {
|
||||
Self(format!("{}%", f))
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_f64_to_percent_3_point(f: f64) -> Self {
|
||||
Self(format!("{:.3}%", f))
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_f64_to_percent_6_point(f: f64) -> Self {
|
||||
Self(format!("{:.6}%", f))
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_f64_to_percent_9_point(f: f64) -> Self {
|
||||
Self(format!("{:.9}%", f))
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_f64_to_percent_no_fmt(f: f64) -> Self {
|
||||
Self(format!("{}%", f))
|
||||
}
|
||||
#[inline]
|
||||
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())
|
||||
}
|
||||
#[inline]
|
||||
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())
|
||||
}
|
||||
#[inline]
|
||||
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())
|
||||
}
|
||||
#[inline]
|
||||
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())
|
||||
}
|
||||
#[inline]
|
||||
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())
|
||||
}
|
||||
#[inline]
|
||||
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())
|
||||
}
|
||||
#[inline]
|
||||
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
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
#[inline(always)]
|
||||
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]
|
||||
#[inline(always)]
|
||||
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]
|
||||
#[inline(always)]
|
||||
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)
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_f64_12_point(f: f64) -> Self {
|
||||
let f = format!("{:.12}", f);
|
||||
Self(f)
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_f64_no_fmt(f: f64) -> Self {
|
||||
let f = format!("{}", f);
|
||||
Self(f)
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
Self(string)
|
||||
}
|
||||
#[inline]
|
||||
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]
|
||||
#[inline]
|
||||
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]
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_f64_12_point(f: f64) -> Self {
|
||||
let f = format!("{:.12}", f);
|
||||
Self(f)
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_f64_no_fmt(f: f64) -> Self {
|
||||
let f = format!("{}", f);
|
||||
Self(f)
|
||||
}
|
||||
#[inline]
|
||||
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_number() {
|
||||
use crate::human::HumanNumber;
|
||||
assert!(HumanNumber::to_percent(0.001).to_string() == "0%");
|
||||
assert!(HumanNumber::to_percent(12.123_123).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(123_123.31)]).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.125).to_string() == "123,123");
|
||||
assert!(HumanNumber::from_f64(123_123_123.123_123_12).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",
|
||||
);
|
||||
}
|
||||
#[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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,83 +39,81 @@
|
|||
|
||||
// Locks and unwraps an [Arc<Mutex<T>]
|
||||
macro_rules! lock {
|
||||
($arc_mutex:expr) => {
|
||||
$arc_mutex.lock().unwrap()
|
||||
};
|
||||
($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()
|
||||
};
|
||||
($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))
|
||||
};
|
||||
($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))
|
||||
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,
|
||||
}
|
||||
};
|
||||
($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 lock() {
|
||||
use std::sync::{Arc, Mutex};
|
||||
let arc_mutex = Arc::new(Mutex::new(false));
|
||||
*lock!(arc_mutex) = true;
|
||||
assert!(*lock!(arc_mutex));
|
||||
}
|
||||
|
||||
#[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 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));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arc_mut() {
|
||||
let a = arc_mut!(false);
|
||||
assert!(*lock!(a) == false);
|
||||
}
|
||||
#[test]
|
||||
fn arc_mut() {
|
||||
let a = arc_mut!(false);
|
||||
assert!(!(*lock!(a)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip() {
|
||||
let mut b = true;
|
||||
flip!(b);
|
||||
assert!(b == false);
|
||||
}
|
||||
#[test]
|
||||
fn flip() {
|
||||
let mut b = true;
|
||||
flip!(b);
|
||||
assert!(!b);
|
||||
}
|
||||
}
|
||||
|
|
3464
src/main.rs
3464
src/main.rs
File diff suppressed because it is too large
Load diff
892
src/node.rs
892
src/node.rs
File diff suppressed because it is too large
Load diff
646
src/p2pool.rs
646
src/p2pool.rs
|
@ -15,234 +15,325 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
Regexes,
|
||||
constants::*,
|
||||
disk::*,
|
||||
node::*,
|
||||
helper::*,
|
||||
macros::*,
|
||||
};
|
||||
use crate::regex::REGEXES;
|
||||
use crate::{constants::*, disk::*, helper::*, macros::*, node::*, Regexes};
|
||||
use egui::{
|
||||
TextEdit,SelectableLabel,ComboBox,Label,Button,
|
||||
Color32,RichText,Slider,Checkbox,ProgressBar,Spinner,
|
||||
TextStyle::*,Hyperlink
|
||||
Button, Checkbox, Color32, ComboBox, Hyperlink, Label, ProgressBar, RichText, SelectableLabel,
|
||||
Slider, Spinner, TextEdit, TextStyle::*,
|
||||
};
|
||||
use std::sync::{Arc,Mutex};
|
||||
use regex::Regex;
|
||||
use log::*;
|
||||
use crate::regex::{
|
||||
REGEXES,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
impl crate::disk::P2pool {
|
||||
#[inline(always)]
|
||||
pub fn show(&mut self, node_vec: &mut Vec<(String, Node)>, _og: &Arc<Mutex<State>>, ping: &Arc<Mutex<Ping>>, process: &Arc<Mutex<Process>>, api: &Arc<Mutex<PubP2poolApi>>, buffer: &mut String, width: f32, height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
||||
let text_edit = height / 25.0;
|
||||
//---------------------------------------------------------------------------------------------------- [Simple] Console
|
||||
debug!("P2Pool Tab | Rendering [Console]");
|
||||
ui.group(|ui| {
|
||||
if self.simple {
|
||||
let height = height / 2.8;
|
||||
let width = width - SPACE;
|
||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
egui::ScrollArea::vertical().stick_to_bottom(true).max_width(width).max_height(height).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
|
||||
ui.add_sized([width, height], TextEdit::multiline(&mut lock!(api).output.as_str()));
|
||||
});
|
||||
});
|
||||
//---------------------------------------------------------------------------------------------------- [Advanced] Console
|
||||
} else {
|
||||
let height = height / 2.8;
|
||||
let width = width - SPACE;
|
||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
egui::ScrollArea::vertical().stick_to_bottom(true).max_width(width).max_height(height).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
|
||||
ui.add_sized([width, height], TextEdit::multiline(&mut lock!(api).output.as_str()));
|
||||
});
|
||||
});
|
||||
ui.separator();
|
||||
let response = ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(buffer), r#"Type a command (e.g "help" or "status") and press Enter"#)).on_hover_text(P2POOL_INPUT);
|
||||
// If the user pressed enter, dump buffer contents into the process STDIN
|
||||
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
|
||||
response.request_focus(); // Get focus back
|
||||
let buffer = std::mem::take(buffer); // Take buffer
|
||||
let mut process = lock!(process); // Lock
|
||||
if process.is_alive() { process.input.push(buffer); } // Push only if alive
|
||||
}
|
||||
}
|
||||
});
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn show(
|
||||
&mut self,
|
||||
node_vec: &mut Vec<(String, Node)>,
|
||||
_og: &Arc<Mutex<State>>,
|
||||
ping: &Arc<Mutex<Ping>>,
|
||||
process: &Arc<Mutex<Process>>,
|
||||
api: &Arc<Mutex<PubP2poolApi>>,
|
||||
buffer: &mut String,
|
||||
width: f32,
|
||||
height: f32,
|
||||
_ctx: &egui::Context,
|
||||
ui: &mut egui::Ui,
|
||||
) {
|
||||
let text_edit = height / 25.0;
|
||||
//---------------------------------------------------------------------------------------------------- [Simple] Console
|
||||
debug!("P2Pool Tab | Rendering [Console]");
|
||||
ui.group(|ui| {
|
||||
if self.simple {
|
||||
let height = height / 2.8;
|
||||
let width = width - SPACE;
|
||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
egui::ScrollArea::vertical()
|
||||
.stick_to_bottom(true)
|
||||
.max_width(width)
|
||||
.max_height(height)
|
||||
.auto_shrink([false; 2])
|
||||
.show_viewport(ui, |ui, _| {
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
TextEdit::multiline(&mut lock!(api).output.as_str()),
|
||||
);
|
||||
});
|
||||
});
|
||||
//---------------------------------------------------------------------------------------------------- [Advanced] Console
|
||||
} else {
|
||||
let height = height / 2.8;
|
||||
let width = width - SPACE;
|
||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
egui::ScrollArea::vertical()
|
||||
.stick_to_bottom(true)
|
||||
.max_width(width)
|
||||
.max_height(height)
|
||||
.auto_shrink([false; 2])
|
||||
.show_viewport(ui, |ui, _| {
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
TextEdit::multiline(&mut lock!(api).output.as_str()),
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.separator();
|
||||
let response = ui
|
||||
.add_sized(
|
||||
[width, text_edit],
|
||||
TextEdit::hint_text(
|
||||
TextEdit::singleline(buffer),
|
||||
r#"Type a command (e.g "help" or "status") and press Enter"#,
|
||||
),
|
||||
)
|
||||
.on_hover_text(P2POOL_INPUT);
|
||||
// If the user pressed enter, dump buffer contents into the process STDIN
|
||||
if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
|
||||
response.request_focus(); // Get focus back
|
||||
let buffer = std::mem::take(buffer); // Take buffer
|
||||
let mut process = lock!(process); // Lock
|
||||
if process.is_alive() {
|
||||
process.input.push(buffer);
|
||||
} // Push only if alive
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Args
|
||||
if !self.simple {
|
||||
debug!("P2Pool Tab | Rendering [Arguments]");
|
||||
ui.group(|ui| { ui.horizontal(|ui| {
|
||||
let width = (width/10.0) - SPACE;
|
||||
ui.add_sized([width, text_edit], Label::new("Command arguments:"));
|
||||
ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.arguments), r#"--wallet <...> --host <...>"#)).on_hover_text(P2POOL_ARGUMENTS);
|
||||
self.arguments.truncate(1024);
|
||||
})});
|
||||
ui.set_enabled(self.arguments.is_empty());
|
||||
}
|
||||
//---------------------------------------------------------------------------------------------------- Args
|
||||
if !self.simple {
|
||||
debug!("P2Pool Tab | Rendering [Arguments]");
|
||||
ui.group(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let width = (width / 10.0) - SPACE;
|
||||
ui.add_sized([width, text_edit], Label::new("Command arguments:"));
|
||||
ui.add_sized(
|
||||
[ui.available_width(), text_edit],
|
||||
TextEdit::hint_text(
|
||||
TextEdit::singleline(&mut self.arguments),
|
||||
r#"--wallet <...> --host <...>"#,
|
||||
),
|
||||
)
|
||||
.on_hover_text(P2POOL_ARGUMENTS);
|
||||
self.arguments.truncate(1024);
|
||||
})
|
||||
});
|
||||
ui.set_enabled(self.arguments.is_empty());
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Address
|
||||
debug!("P2Pool Tab | Rendering [Address]");
|
||||
ui.group(|ui| {
|
||||
let width = width - SPACE;
|
||||
ui.spacing_mut().text_edit_width = (width)-(SPACE*3.0);
|
||||
let text;
|
||||
let color;
|
||||
let len = format!("{:02}", self.address.len());
|
||||
if self.address.is_empty() {
|
||||
text = format!("Monero Address [{}/95] ➖", len);
|
||||
color = Color32::LIGHT_GRAY;
|
||||
} else if Regexes::addr_ok(&self.address) {
|
||||
text = format!("Monero Address [{}/95] ✔", len);
|
||||
color = Color32::from_rgb(100, 230, 100);
|
||||
} else {
|
||||
text = format!("Monero Address [{}/95] ❌", len);
|
||||
color = Color32::from_rgb(230, 50, 50);
|
||||
}
|
||||
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
|
||||
ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4...")).on_hover_text(P2POOL_ADDRESS);
|
||||
self.address.truncate(95);
|
||||
});
|
||||
//---------------------------------------------------------------------------------------------------- Address
|
||||
debug!("P2Pool Tab | Rendering [Address]");
|
||||
ui.group(|ui| {
|
||||
let width = width - SPACE;
|
||||
ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0);
|
||||
let text;
|
||||
let color;
|
||||
let len = format!("{:02}", self.address.len());
|
||||
if self.address.is_empty() {
|
||||
text = format!("Monero Address [{}/95] ➖", len);
|
||||
color = Color32::LIGHT_GRAY;
|
||||
} else if Regexes::addr_ok(&self.address) {
|
||||
text = format!("Monero Address [{}/95] ✔", len);
|
||||
color = Color32::from_rgb(100, 230, 100);
|
||||
} else {
|
||||
text = format!("Monero Address [{}/95] ❌", len);
|
||||
color = Color32::from_rgb(230, 50, 50);
|
||||
}
|
||||
ui.add_sized(
|
||||
[width, text_edit],
|
||||
Label::new(RichText::new(text).color(color)),
|
||||
);
|
||||
ui.add_sized(
|
||||
[width, text_edit],
|
||||
TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4..."),
|
||||
)
|
||||
.on_hover_text(P2POOL_ADDRESS);
|
||||
self.address.truncate(95);
|
||||
});
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Simple
|
||||
let height = ui.available_height();
|
||||
if self.simple {
|
||||
// [Node]
|
||||
let height = height / 6.5;
|
||||
ui.spacing_mut().slider_width = width - 8.0;
|
||||
ui.spacing_mut().icon_width = width / 25.0;
|
||||
//---------------------------------------------------------------------------------------------------- Simple
|
||||
let height = ui.available_height();
|
||||
if self.simple {
|
||||
// [Node]
|
||||
let height = height / 6.5;
|
||||
ui.spacing_mut().slider_width = width - 8.0;
|
||||
ui.spacing_mut().icon_width = width / 25.0;
|
||||
|
||||
// [Auto-select] if we haven't already.
|
||||
// Using [Arc<Mutex<Ping>>] as an intermediary here
|
||||
// saves me the hassle of wrapping [state: State] completely
|
||||
// and [.lock().unwrap()]ing it everywhere.
|
||||
// Two atomic bools = enough to represent this data
|
||||
debug!("P2Pool Tab | Running [auto-select] check");
|
||||
if self.auto_select {
|
||||
let mut ping = lock!(ping);
|
||||
// If we haven't auto_selected yet, auto-select and turn it off
|
||||
if ping.pinged && !ping.auto_selected {
|
||||
self.node = ping.fastest.to_string();
|
||||
ping.auto_selected = true;
|
||||
}
|
||||
drop(ping);
|
||||
}
|
||||
// [Auto-select] if we haven't already.
|
||||
// Using [Arc<Mutex<Ping>>] as an intermediary here
|
||||
// saves me the hassle of wrapping [state: State] completely
|
||||
// and [.lock().unwrap()]ing it everywhere.
|
||||
// Two atomic bools = enough to represent this data
|
||||
debug!("P2Pool Tab | Running [auto-select] check");
|
||||
if self.auto_select {
|
||||
let mut ping = lock!(ping);
|
||||
// If we haven't auto_selected yet, auto-select and turn it off
|
||||
if ping.pinged && !ping.auto_selected {
|
||||
self.node = ping.fastest.to_string();
|
||||
ping.auto_selected = true;
|
||||
}
|
||||
drop(ping);
|
||||
}
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
debug!("P2Pool Tab | Rendering [Ping List]");
|
||||
// [Ping List]
|
||||
let mut ms = 0;
|
||||
let mut color = Color32::LIGHT_GRAY;
|
||||
if lock!(ping).pinged {
|
||||
for data in lock!(ping).nodes.iter() {
|
||||
if data.ip == self.node {
|
||||
ms = data.ms;
|
||||
color = data.color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("P2Pool Tab | Rendering [ComboBox] of Remote Nodes");
|
||||
let ip_location = crate::node::format_ip_location(&self.node, false);
|
||||
let text = RichText::new(format!(" ⏺ {}ms | {}", ms, ip_location)).color(color);
|
||||
ComboBox::from_id_source("remote_nodes")
|
||||
.selected_text(text)
|
||||
.width(width)
|
||||
.show_ui(ui, |ui| {
|
||||
for data in lock!(ping).nodes.iter() {
|
||||
let ms = crate::node::format_ms(data.ms);
|
||||
let ip_location = crate::node::format_ip_location(data.ip, true);
|
||||
let text = RichText::new(format!(" ⏺ {} | {}", ms, ip_location))
|
||||
.color(data.color);
|
||||
ui.selectable_value(&mut self.node, data.ip.to_string(), text);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
debug!("P2Pool Tab | Rendering [Ping List]");
|
||||
// [Ping List]
|
||||
let mut ms = 0;
|
||||
let mut color = Color32::LIGHT_GRAY;
|
||||
if lock!(ping).pinged {
|
||||
for data in lock!(ping).nodes.iter() {
|
||||
if data.ip == self.node {
|
||||
ms = data.ms;
|
||||
color = data.color;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("P2Pool Tab | Rendering [ComboBox] of Remote Nodes");
|
||||
let ip_location = crate::node::format_ip_location(&self.node, false);
|
||||
let text = RichText::new(format!(" ⏺ {}ms | {}", ms, ip_location)).color(color);
|
||||
ComboBox::from_id_source("remote_nodes").selected_text(text).show_ui(ui, |ui| {
|
||||
for data in lock!(ping).nodes.iter() {
|
||||
let ms = crate::node::format_ms(data.ms);
|
||||
let ip_location = crate::node::format_ip_location(data.ip, true);
|
||||
let text = RichText::new(format!(" ⏺ {} | {}", ms, ip_location)).color(data.color);
|
||||
ui.selectable_value(&mut self.node, data.ip.to_string(), text);
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(5.0);
|
||||
|
||||
ui.add_space(5.0);
|
||||
debug!("P2Pool Tab | Rendering [Select fastest ... Ping] buttons");
|
||||
ui.horizontal(|ui| {
|
||||
let width = (width / 5.0) - 6.0;
|
||||
// [Select random node]
|
||||
if ui
|
||||
.add_sized([width, height], Button::new("Select random node"))
|
||||
.on_hover_text(P2POOL_SELECT_RANDOM)
|
||||
.clicked()
|
||||
{
|
||||
self.node = RemoteNode::get_random(&self.node);
|
||||
}
|
||||
// [Select fastest node]
|
||||
if ui
|
||||
.add_sized([width, height], Button::new("Select fastest node"))
|
||||
.on_hover_text(P2POOL_SELECT_FASTEST)
|
||||
.clicked()
|
||||
&& lock!(ping).pinged
|
||||
{
|
||||
self.node = lock!(ping).fastest.to_string();
|
||||
}
|
||||
// [Ping Button]
|
||||
ui.add_enabled_ui(!lock!(ping).pinging, |ui| {
|
||||
if ui
|
||||
.add_sized([width, height], Button::new("Ping remote nodes"))
|
||||
.on_hover_text(P2POOL_PING)
|
||||
.clicked()
|
||||
{
|
||||
Ping::spawn_thread(ping);
|
||||
}
|
||||
});
|
||||
// [Last <-]
|
||||
if ui
|
||||
.add_sized([width, height], Button::new("⬅ Last"))
|
||||
.on_hover_text(P2POOL_SELECT_LAST)
|
||||
.clicked()
|
||||
{
|
||||
let ping = lock!(ping);
|
||||
match ping.pinged {
|
||||
true => {
|
||||
self.node = RemoteNode::get_last_from_ping(&self.node, &ping.nodes)
|
||||
}
|
||||
false => self.node = RemoteNode::get_last(&self.node),
|
||||
}
|
||||
drop(ping);
|
||||
}
|
||||
// [Next ->]
|
||||
if ui
|
||||
.add_sized([width, height], Button::new("Next ➡"))
|
||||
.on_hover_text(P2POOL_SELECT_NEXT)
|
||||
.clicked()
|
||||
{
|
||||
let ping = lock!(ping);
|
||||
match ping.pinged {
|
||||
true => {
|
||||
self.node = RemoteNode::get_next_from_ping(&self.node, &ping.nodes)
|
||||
}
|
||||
false => self.node = RemoteNode::get_next(&self.node),
|
||||
}
|
||||
drop(ping);
|
||||
}
|
||||
});
|
||||
|
||||
debug!("P2Pool Tab | Rendering [Select fastest ... Ping] buttons");
|
||||
ui.horizontal(|ui| {
|
||||
let width = (width/5.0)-6.0;
|
||||
// [Select random node]
|
||||
if ui.add_sized([width, height], Button::new("Select random node")).on_hover_text(P2POOL_SELECT_RANDOM).clicked() {
|
||||
self.node = RemoteNode::get_random(&self.node);
|
||||
}
|
||||
// [Select fastest node]
|
||||
if ui.add_sized([width, height], Button::new("Select fastest node")).on_hover_text(P2POOL_SELECT_FASTEST).clicked() && lock!(ping).pinged {
|
||||
self.node = lock!(ping).fastest.to_string();
|
||||
}
|
||||
// [Ping Button]
|
||||
ui.add_enabled_ui(!lock!(ping).pinging, |ui| {
|
||||
if ui.add_sized([width, height], Button::new("Ping remote nodes")).on_hover_text(P2POOL_PING).clicked() {
|
||||
Ping::spawn_thread(ping);
|
||||
}
|
||||
});
|
||||
// [Last <-]
|
||||
if ui.add_sized([width, height], Button::new("⬅ Last")).on_hover_text(P2POOL_SELECT_LAST).clicked() {
|
||||
let ping = lock!(ping);
|
||||
match ping.pinged {
|
||||
true => self.node = RemoteNode::get_last_from_ping(&self.node, &ping.nodes),
|
||||
false => self.node = RemoteNode::get_last(&self.node),
|
||||
}
|
||||
drop(ping);
|
||||
}
|
||||
// [Next ->]
|
||||
if ui.add_sized([width, height], Button::new("Next ➡")).on_hover_text(P2POOL_SELECT_NEXT).clicked() {
|
||||
let ping = lock!(ping);
|
||||
match ping.pinged {
|
||||
true => self.node = RemoteNode::get_next_from_ping(&self.node, &ping.nodes),
|
||||
false => self.node = RemoteNode::get_next(&self.node),
|
||||
}
|
||||
drop(ping);
|
||||
}
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
let height = height / 2.0;
|
||||
let pinging = lock!(ping).pinging;
|
||||
ui.set_enabled(pinging);
|
||||
let prog = lock!(ping).prog.round();
|
||||
let msg = RichText::new(format!("{} ... {}%", lock!(ping).msg, prog));
|
||||
let height = height / 1.25;
|
||||
ui.add_space(5.0);
|
||||
ui.add_sized([width, height], Label::new(msg));
|
||||
ui.add_space(5.0);
|
||||
if pinging {
|
||||
ui.add_sized([width, height], Spinner::new().size(height));
|
||||
} else {
|
||||
ui.add_sized([width, height], Label::new("..."));
|
||||
}
|
||||
ui.add_sized([width, height], ProgressBar::new(prog.round() / 100.0));
|
||||
ui.add_space(5.0);
|
||||
});
|
||||
});
|
||||
|
||||
ui.vertical(|ui| {
|
||||
let height = height / 2.0;
|
||||
let pinging = lock!(ping).pinging;
|
||||
ui.set_enabled(pinging);
|
||||
let prog = lock!(ping).prog.round();
|
||||
let msg = RichText::new(format!("{} ... {}%", lock!(ping).msg, prog));
|
||||
let height = height / 1.25;
|
||||
ui.add_space(5.0);
|
||||
ui.add_sized([width, height], Label::new(msg));
|
||||
ui.add_space(5.0);
|
||||
if pinging {
|
||||
ui.add_sized([width, height], Spinner::new().size(height));
|
||||
} else {
|
||||
ui.add_sized([width, height], Label::new("..."));
|
||||
}
|
||||
ui.add_sized([width, height], ProgressBar::new(prog.round()/100.0));
|
||||
ui.add_space(5.0);
|
||||
});
|
||||
});
|
||||
debug!("P2Pool Tab | Rendering [Auto-*] buttons");
|
||||
ui.group(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let width = (width / 3.0) - (SPACE * 1.75);
|
||||
// [Auto-node]
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.auto_select, "Auto-select"),
|
||||
)
|
||||
.on_hover_text(P2POOL_AUTO_SELECT);
|
||||
ui.separator();
|
||||
// [Auto-node]
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.auto_ping, "Auto-ping"),
|
||||
)
|
||||
.on_hover_text(P2POOL_AUTO_NODE);
|
||||
ui.separator();
|
||||
// [Backup host]
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.backup_host, "Backup host"),
|
||||
)
|
||||
.on_hover_text(P2POOL_BACKUP_HOST_SIMPLE);
|
||||
})
|
||||
});
|
||||
|
||||
debug!("P2Pool Tab | Rendering [Auto-*] buttons");
|
||||
ui.group(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let width = (width/3.0)-(SPACE*1.75);
|
||||
// [Auto-node]
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.auto_select, "Auto-select")).on_hover_text(P2POOL_AUTO_SELECT);
|
||||
ui.separator();
|
||||
// [Auto-node]
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.auto_ping, "Auto-ping")).on_hover_text(P2POOL_AUTO_NODE);
|
||||
ui.separator();
|
||||
// [Backup host]
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.backup_host, "Backup host")).on_hover_text(P2POOL_BACKUP_HOST_SIMPLE);
|
||||
})});
|
||||
debug!("P2Pool Tab | Rendering warning text");
|
||||
ui.add_sized(
|
||||
[width, height / 2.0],
|
||||
Hyperlink::from_label_and_url(
|
||||
"Warning: It is recommended to use your own Monero node.",
|
||||
"https://github.com/hinto-janai/gupax#running-a-local-monero-node",
|
||||
),
|
||||
)
|
||||
.on_hover_text(P2POOL_COMMUNITY_NODE_WARNING);
|
||||
|
||||
debug!("P2Pool Tab | Rendering warning text");
|
||||
ui.add_sized([width, height/2.0], Hyperlink::from_label_and_url("WARNING: It is recommended to run/use your own Monero Node (hover for details)", "https://github.com/hinto-janai/gupax#running-a-local-monero-node")).on_hover_text(P2POOL_COMMUNITY_NODE_WARNING);
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Advanced
|
||||
} else {
|
||||
debug!("P2Pool Tab | Rendering [Node List] elements");
|
||||
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
|
||||
// [Monero node IP/RPC/ZMQ]
|
||||
ui.horizontal(|ui| {
|
||||
//---------------------------------------------------------------------------------------------------- Advanced
|
||||
} else {
|
||||
debug!("P2Pool Tab | Rendering [Node List] elements");
|
||||
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
|
||||
// [Monero node IP/RPC/ZMQ]
|
||||
ui.horizontal(|ui| {
|
||||
ui.group(|ui| {
|
||||
let width = width/10.0;
|
||||
ui.vertical(|ui| {
|
||||
|
@ -338,12 +429,11 @@ impl crate::disk::P2pool {
|
|||
// [Ping List]
|
||||
debug!("P2Pool Tab | Rendering [Node List]");
|
||||
let text = RichText::new(format!("{}. {}", self.selected_index+1, self.selected_name));
|
||||
ComboBox::from_id_source("manual_nodes").selected_text(text).show_ui(ui, |ui| {
|
||||
let mut n = 0;
|
||||
for (name, node) in node_vec.iter() {
|
||||
let text = RichText::new(format!("{}. {}\n IP: {}\n RPC: {}\n ZMQ: {}", n+1, name, node.ip, node.rpc, node.zmq));
|
||||
ComboBox::from_id_source("manual_nodes").selected_text(text).width(width).show_ui(ui, |ui| {
|
||||
for (i, (name, node)) in node_vec.iter().enumerate() {
|
||||
let text = RichText::new(format!("{}. {}\n IP: {}\n RPC: {}\n ZMQ: {}", i+1, name, node.ip, node.rpc, node.zmq));
|
||||
if ui.add(SelectableLabel::new(self.selected_name == *name, text)).clicked() {
|
||||
self.selected_index = n;
|
||||
self.selected_index = i;
|
||||
let node = node.clone();
|
||||
self.selected_name = name.clone();
|
||||
self.selected_ip = node.ip.clone();
|
||||
|
@ -354,7 +444,6 @@ impl crate::disk::P2pool {
|
|||
self.rpc = node.rpc;
|
||||
self.zmq = node.zmq;
|
||||
}
|
||||
n += 1;
|
||||
}
|
||||
});
|
||||
// [Add/Save]
|
||||
|
@ -453,49 +542,84 @@ impl crate::disk::P2pool {
|
|||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(5.0);
|
||||
ui.add_space(5.0);
|
||||
|
||||
debug!("P2Pool Tab | Rendering [Main/Mini/Peers/Log] elements");
|
||||
// [Main/Mini]
|
||||
ui.horizontal(|ui| {
|
||||
let height = height/4.0;
|
||||
ui.group(|ui| { ui.horizontal(|ui| {
|
||||
let width = (width/4.0)-SPACE;
|
||||
let height = height + 6.0;
|
||||
if ui.add_sized([width, height], SelectableLabel::new(!self.mini, "P2Pool Main")).on_hover_text(P2POOL_MAIN).clicked() { self.mini = false; }
|
||||
if ui.add_sized([width, height], SelectableLabel::new(self.mini, "P2Pool Mini")).on_hover_text(P2POOL_MINI).clicked() { self.mini = true; }
|
||||
})});
|
||||
// [Out/In Peers] + [Log Level]
|
||||
ui.group(|ui| { ui.vertical(|ui| {
|
||||
let text = (ui.available_width()/10.0)-SPACE;
|
||||
let width = (text*8.0)-SPACE;
|
||||
let height = height/3.0;
|
||||
ui.style_mut().spacing.slider_width = width/1.1;
|
||||
ui.style_mut().spacing.interact_size.y = height;
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([text, height], Label::new("Out peers [10-450]:"));
|
||||
ui.add_sized([width, height], Slider::new(&mut self.out_peers, 10..=450)).on_hover_text(P2POOL_OUT);
|
||||
ui.add_space(ui.available_width()-4.0);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([text, height], Label::new(" In peers [10-450]:"));
|
||||
ui.add_sized([width, height], Slider::new(&mut self.in_peers, 10..=450)).on_hover_text(P2POOL_IN);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([text, height], Label::new(" Log level [0-6]:"));
|
||||
ui.add_sized([width, height], Slider::new(&mut self.log_level, 0..=6)).on_hover_text(P2POOL_LOG);
|
||||
});
|
||||
})});
|
||||
});
|
||||
debug!("P2Pool Tab | Rendering [Main/Mini/Peers/Log] elements");
|
||||
// [Main/Mini]
|
||||
ui.horizontal(|ui| {
|
||||
let height = height / 4.0;
|
||||
ui.group(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let width = (width / 4.0) - SPACE;
|
||||
let height = height + 6.0;
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(!self.mini, "P2Pool Main"),
|
||||
)
|
||||
.on_hover_text(P2POOL_MAIN)
|
||||
.clicked()
|
||||
{
|
||||
self.mini = false;
|
||||
}
|
||||
if ui
|
||||
.add_sized(
|
||||
[width, height],
|
||||
SelectableLabel::new(self.mini, "P2Pool Mini"),
|
||||
)
|
||||
.on_hover_text(P2POOL_MINI)
|
||||
.clicked()
|
||||
{
|
||||
self.mini = true;
|
||||
}
|
||||
})
|
||||
});
|
||||
// [Out/In Peers] + [Log Level]
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
let text = (ui.available_width() / 10.0) - SPACE;
|
||||
let width = (text * 8.0) - SPACE;
|
||||
let height = height / 3.0;
|
||||
ui.style_mut().spacing.slider_width = width / 1.1;
|
||||
ui.style_mut().spacing.interact_size.y = height;
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([text, height], Label::new("Out peers [10-450]:"));
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Slider::new(&mut self.out_peers, 10..=450),
|
||||
)
|
||||
.on_hover_text(P2POOL_OUT);
|
||||
ui.add_space(ui.available_width() - 4.0);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([text, height], Label::new(" In peers [10-450]:"));
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Slider::new(&mut self.in_peers, 10..=450),
|
||||
)
|
||||
.on_hover_text(P2POOL_IN);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([text, height], Label::new(" Log level [0-6]:"));
|
||||
ui.add_sized([width, height], Slider::new(&mut self.log_level, 0..=6))
|
||||
.on_hover_text(P2POOL_LOG);
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
debug!("P2Pool Tab | Rendering Backup host button");
|
||||
ui.group(|ui| {
|
||||
let width = width - SPACE;
|
||||
let height = ui.available_height() / 3.0;
|
||||
// [Backup host]
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.backup_host, "Backup host")).on_hover_text(P2POOL_BACKUP_HOST_ADVANCED);
|
||||
});
|
||||
}
|
||||
}
|
||||
debug!("P2Pool Tab | Rendering Backup host button");
|
||||
ui.group(|ui| {
|
||||
let width = width - SPACE;
|
||||
let height = ui.available_height() / 3.0;
|
||||
// [Backup host]
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.backup_host, "Backup host"),
|
||||
)
|
||||
.on_hover_text(P2POOL_BACKUP_HOST_ADVANCED);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
58
src/panic.rs
58
src/panic.rs
|
@ -1,24 +1,20 @@
|
|||
//---------------------------------------------------------------------------------------------------- Use
|
||||
use crate::constants::{
|
||||
GUPAX_VERSION,
|
||||
P2POOL_VERSION,
|
||||
XMRIG_VERSION,
|
||||
OS_NAME,
|
||||
COMMIT,
|
||||
};
|
||||
use crate::constants::{COMMIT, GUPAX_VERSION, OS_NAME, P2POOL_VERSION, XMRIG_VERSION};
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
/// Set custom panic hook.
|
||||
pub(crate) fn set_panic_hook(now: std::time::Instant) {
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
// Set stack-trace.
|
||||
let stack_trace = std::backtrace::Backtrace::force_capture();
|
||||
let args = std::env::args_os();
|
||||
let uptime = now.elapsed().as_secs_f32();
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
// Set stack-trace.
|
||||
let stack_trace = std::backtrace::Backtrace::force_capture();
|
||||
let args = std::env::args_os();
|
||||
let uptime = now.elapsed().as_secs_f32();
|
||||
|
||||
// Re-format panic info.
|
||||
let panic_info = format!(
|
||||
"{panic_info:#?}
|
||||
// Re-format panic info.
|
||||
let panic_info = format!(
|
||||
"{panic_info:#?}
|
||||
|
||||
info:
|
||||
OS | {OS_NAME}
|
||||
|
@ -30,21 +26,23 @@ info:
|
|||
uptime | {uptime} seconds
|
||||
|
||||
stack backtrace:\n{stack_trace}",
|
||||
);
|
||||
);
|
||||
|
||||
// Attempt to write panic info to disk.
|
||||
match crate::disk::get_gupax_data_path() {
|
||||
Ok(mut path) => {
|
||||
path.push("crash.txt");
|
||||
match std::fs::write(&path, &panic_info) {
|
||||
Ok(_) => eprintln!("\nmass_panic!() - Saved panic log to: {}\n", path.display()),
|
||||
Err(e) => eprintln!("\nmass_panic!() - Could not save panic log: {e}\n"),
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("panic_hook PATH error: {e}"),
|
||||
}
|
||||
// Attempt to write panic info to disk.
|
||||
match crate::disk::get_gupax_data_path() {
|
||||
Ok(mut path) => {
|
||||
path.push("crash.txt");
|
||||
match std::fs::write(&path, &panic_info) {
|
||||
Ok(_) => {
|
||||
eprintln!("\nmass_panic!() - Saved panic log to: {}\n", path.display())
|
||||
}
|
||||
Err(e) => eprintln!("\nmass_panic!() - Could not save panic log: {e}\n"),
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("panic_hook PATH error: {e}"),
|
||||
}
|
||||
|
||||
// Exit all threads.
|
||||
benri::mass_panic!(panic_info);
|
||||
}));
|
||||
// Exit all threads.
|
||||
benri::mass_panic!(panic_info);
|
||||
}));
|
||||
}
|
||||
|
|
204
src/regex.rs
204
src/regex.rs
|
@ -17,45 +17,51 @@
|
|||
|
||||
// Some regexes used throughout Gupax.
|
||||
|
||||
use regex::Regex;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Lazy
|
||||
pub static REGEXES: Lazy<Regexes> = Lazy::new(|| Regexes::new());
|
||||
pub static P2POOL_REGEX: Lazy<P2poolRegex> = Lazy::new(|| P2poolRegex::new());
|
||||
pub static XMRIG_REGEX: Lazy<XmrigRegex> = Lazy::new(|| XmrigRegex::new());
|
||||
pub static REGEXES: Lazy<Regexes> = Lazy::new(Regexes::new);
|
||||
pub static P2POOL_REGEX: Lazy<P2poolRegex> = Lazy::new(P2poolRegex::new);
|
||||
pub static XMRIG_REGEX: Lazy<XmrigRegex> = Lazy::new(XmrigRegex::new);
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- [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,
|
||||
pub name: Regex,
|
||||
pub address: Regex,
|
||||
pub ipv4: Regex,
|
||||
pub domain: Regex,
|
||||
pub port: Regex,
|
||||
}
|
||||
|
||||
impl Regexes {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
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(),
|
||||
domain: Regex::new(r#"^[A-Za-z0-9-.]+[A-Za-z0-9-]+$"#).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(address: &str) -> bool {
|
||||
address.len() == 95 && REGEXES.address.is_match(address) && !address.contains('0') && !address.contains('O') && !address.contains('l')
|
||||
}
|
||||
#[inline]
|
||||
// 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(address: &str) -> bool {
|
||||
address.len() == 95
|
||||
&& REGEXES.address.is_match(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?
|
||||
|
@ -73,92 +79,114 @@ impl Regexes {
|
|||
// 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)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct P2poolRegex {
|
||||
pub date: Regex,
|
||||
pub payout: Regex,
|
||||
pub payout_float: Regex,
|
||||
pub block: Regex,
|
||||
pub block_int: Regex,
|
||||
pub block_comma: Regex,
|
||||
pub synchronized: Regex,
|
||||
pub date: Regex,
|
||||
pub payout: Regex,
|
||||
pub payout_float: Regex,
|
||||
pub block: Regex,
|
||||
pub block_int: Regex,
|
||||
pub block_comma: Regex,
|
||||
pub synchronized: Regex,
|
||||
pub next_height_1: Regex,
|
||||
}
|
||||
|
||||
impl P2poolRegex {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
date: Regex::new("[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+.[0-9]+").unwrap(),
|
||||
payout: Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(), // Assumes 12 digits after the dot.
|
||||
payout_float: Regex::new("[0-9].[0-9]{12}").unwrap(), // Assumes 12 digits after the dot.
|
||||
block: Regex::new("block [0-9]{7}").unwrap(), // Monero blocks will be 7 digits for... the next 10,379 years
|
||||
block_int: Regex::new("[0-9]{7}").unwrap(),
|
||||
block_comma: Regex::new("[0-9],[0-9]{3},[0-9]{3}").unwrap(),
|
||||
synchronized: Regex::new("SYNCHRONIZED").unwrap(),
|
||||
}
|
||||
}
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
date: Regex::new("[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+.[0-9]+").unwrap(),
|
||||
payout: Regex::new("payout of [0-9].[0-9]+ XMR").unwrap(), // Assumes 12 digits after the dot.
|
||||
payout_float: Regex::new("[0-9].[0-9]{12}").unwrap(), // Assumes 12 digits after the dot.
|
||||
block: Regex::new("block [0-9]{7}").unwrap(), // Monero blocks will be 7 digits for... the next 10,379 years
|
||||
block_int: Regex::new("[0-9]{7}").unwrap(),
|
||||
block_comma: Regex::new("[0-9],[0-9]{3},[0-9]{3}").unwrap(),
|
||||
synchronized: Regex::new("SYNCHRONIZED").unwrap(),
|
||||
next_height_1: Regex::new("next height = 1").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- XMRig regex.
|
||||
#[derive(Debug)]
|
||||
pub struct XmrigRegex {
|
||||
pub not_mining: Regex,
|
||||
pub new_job: Regex,
|
||||
pub not_mining: Regex,
|
||||
pub new_job: Regex,
|
||||
}
|
||||
|
||||
impl XmrigRegex {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
not_mining: Regex::new("no active pools, stop mining").unwrap(),
|
||||
new_job: Regex::new("new job").unwrap(),
|
||||
}
|
||||
}
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
not_mining: Regex::new("no active pools, stop mining").unwrap(),
|
||||
new_job: Regex::new("new job").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- TESTS
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use regex::Regex;
|
||||
use super::*;
|
||||
use super::*;
|
||||
use regex::Regex;
|
||||
|
||||
#[test]
|
||||
fn build_regexes() {
|
||||
let r = 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_regexes() {
|
||||
let r = 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, "sub.domain.com"));
|
||||
assert!(Regex::is_match(&r.domain, "sub.domain.longtld"));
|
||||
assert!(Regex::is_match(&r.domain, "sub.sub.domain.longtld"));
|
||||
assert!(Regex::is_match(&r.domain, "my.node.com"));
|
||||
assert!(Regex::is_match(&r.domain, "my.node.longtld"));
|
||||
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"));
|
||||
assert!(Regex::is_match(&r.domain, "www.my-monero-node123.longtld"));
|
||||
assert!(Regex::is_match(&r.domain, "www.my-monero-node123.org"));
|
||||
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() {
|
||||
let r = 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";
|
||||
let text3 = "NOTICE 2020-12-11 12:35:41.3150 SideChain SYNCHRONIZED";
|
||||
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");
|
||||
assert_eq!(r.synchronized.find(text3).unwrap().as_str(), "SYNCHRONIZED");
|
||||
}
|
||||
#[test]
|
||||
fn build_p2pool_regex() {
|
||||
let r = 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";
|
||||
let text3 = "NOTICE 2020-12-11 12:35:41.3150 SideChain SYNCHRONIZED";
|
||||
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");
|
||||
assert_eq!(r.synchronized.find(text3).unwrap().as_str(), "SYNCHRONIZED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_xmrig_regex() {
|
||||
let r = XmrigRegex::new();
|
||||
let text = "[2022-02-12 12:49:30.311] net no active pools, stop mining";
|
||||
let text2 = "[2022-02-12 12:49:30.311] net new job from 192.168.2.1:3333 diff 402K algo rx/0 height 2241142 (11 tx)";
|
||||
assert_eq!(r.not_mining.find(text).unwrap().as_str(), "no active pools, stop mining");
|
||||
assert_eq!(r.new_job.find(text2).unwrap().as_str(), "new job");
|
||||
}
|
||||
#[test]
|
||||
fn build_xmrig_regex() {
|
||||
let r = XmrigRegex::new();
|
||||
let text = "[2022-02-12 12:49:30.311] net no active pools, stop mining";
|
||||
let text2 = "[2022-02-12 12:49:30.311] net new job from 192.168.2.1:3333 diff 402K algo rx/0 height 2241142 (11 tx)";
|
||||
assert_eq!(
|
||||
r.not_mining.find(text).unwrap().as_str(),
|
||||
"no active pools, stop mining"
|
||||
);
|
||||
assert_eq!(r.new_job.find(text2).unwrap().as_str(), "new job");
|
||||
}
|
||||
}
|
||||
|
|
1234
src/status.rs
1234
src/status.rs
File diff suppressed because it is too large
Load diff
315
src/sudo.rs
315
src/sudo.rs
|
@ -19,166 +19,189 @@
|
|||
// [zeroize] is used to wipe the memory after use.
|
||||
// Only gets imported in [main.rs] for Unix.
|
||||
|
||||
use zeroize::Zeroize;
|
||||
use std::{
|
||||
thread,
|
||||
sync::{Arc,Mutex},
|
||||
process::*,
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
};
|
||||
use crate::{
|
||||
Helper,
|
||||
disk::Xmrig,
|
||||
ProcessSignal,
|
||||
constants::*,
|
||||
macros::*,
|
||||
};
|
||||
use crate::{constants::*, disk::Xmrig, macros::*, Helper, ProcessSignal};
|
||||
use log::*;
|
||||
use std::{
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
process::*,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SudoState {
|
||||
pub windows: bool, // If this bool is set, this struct is just a dummy so I don't have to change my type signatures :)
|
||||
pub testing: bool, // Are we attempting a sudo test right now?
|
||||
pub success: bool, // Was the sudo test a success?
|
||||
pub hide: bool, // Are we hiding the password?
|
||||
pub msg: String, // The message shown to the user if unsuccessful
|
||||
pub pass: String, // The actual password wrapped in a [SecretVec]
|
||||
pub signal: ProcessSignal, // Main GUI will set this depending on if we want [Start] or [Restart]
|
||||
pub windows: bool, // If this bool is set, this struct is just a dummy so I don't have to change my type signatures :)
|
||||
pub testing: bool, // Are we attempting a sudo test right now?
|
||||
pub success: bool, // Was the sudo test a success?
|
||||
pub hide: bool, // Are we hiding the password?
|
||||
pub msg: String, // The message shown to the user if unsuccessful
|
||||
pub pass: String, // The actual password wrapped in a [SecretVec]
|
||||
pub signal: ProcessSignal, // Main GUI will set this depending on if we want [Start] or [Restart]
|
||||
}
|
||||
|
||||
impl Default for SudoState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SudoState {
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
windows: true,
|
||||
testing: false,
|
||||
success: false,
|
||||
hide: true,
|
||||
msg: String::new(),
|
||||
pass: String::new(),
|
||||
signal: ProcessSignal::None,
|
||||
}
|
||||
}
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
windows: false,
|
||||
testing: false,
|
||||
success: false,
|
||||
hide: true,
|
||||
msg: "".to_string(),
|
||||
pass: String::with_capacity(256),
|
||||
signal: ProcessSignal::None,
|
||||
}
|
||||
}
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
windows: true,
|
||||
testing: false,
|
||||
success: false,
|
||||
hide: true,
|
||||
msg: String::new(),
|
||||
pass: String::new(),
|
||||
signal: ProcessSignal::None,
|
||||
}
|
||||
}
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
windows: false,
|
||||
testing: false,
|
||||
success: false,
|
||||
hide: true,
|
||||
msg: "".to_string(),
|
||||
pass: String::with_capacity(256),
|
||||
signal: ProcessSignal::None,
|
||||
}
|
||||
}
|
||||
|
||||
// Resets the state.
|
||||
pub fn reset(state: &Arc<Mutex<Self>>) {
|
||||
Self::wipe(state);
|
||||
let mut state = lock!(state);
|
||||
state.testing = false;
|
||||
state.success = false;
|
||||
// state.signal = ProcessSignal::None;
|
||||
}
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
// Resets the state.
|
||||
pub fn reset(state: &Arc<Mutex<Self>>) {
|
||||
Self::wipe(state);
|
||||
let mut state = lock!(state);
|
||||
state.testing = false;
|
||||
state.success = false;
|
||||
// state.signal = ProcessSignal::None;
|
||||
}
|
||||
|
||||
// Swaps the pass with another 256-capacity String,
|
||||
// zeroizes the old and drops it.
|
||||
pub fn wipe(state: &Arc<Mutex<Self>>) {
|
||||
let mut new = String::with_capacity(256);
|
||||
// new is now == old, and vice-versa.
|
||||
std::mem::swap(&mut new, &mut lock!(state).pass);
|
||||
// we're wiping & dropping the old pass here.
|
||||
new.zeroize();
|
||||
std::mem::drop(new);
|
||||
info!("Sudo | Password wipe with 0's ... OK");
|
||||
}
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
// Swaps the pass with another 256-capacity String,
|
||||
// zeroizes the old and drops it.
|
||||
pub fn wipe(state: &Arc<Mutex<Self>>) {
|
||||
let mut new = String::with_capacity(256);
|
||||
// new is now == old, and vice-versa.
|
||||
std::mem::swap(&mut new, &mut lock!(state).pass);
|
||||
// we're wiping & dropping the old pass here.
|
||||
new.zeroize();
|
||||
std::mem::drop(new);
|
||||
info!("Sudo | Password wipe with 0's ... OK");
|
||||
}
|
||||
|
||||
// Spawns a thread and tests sudo with the provided password.
|
||||
// Sudo takes the password through STDIN via [--stdin].
|
||||
// Sets the appropriate state fields on success/failure.
|
||||
pub fn test_sudo(state: Arc<Mutex<Self>>, helper: &Arc<Mutex<Helper>>, xmrig: &Xmrig, path: &PathBuf) {
|
||||
let helper = Arc::clone(helper);
|
||||
let xmrig = xmrig.clone();
|
||||
let path = path.clone();
|
||||
thread::spawn(move || {
|
||||
// Set to testing
|
||||
lock!(state).testing = true;
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
// Spawns a thread and tests sudo with the provided password.
|
||||
// Sudo takes the password through STDIN via [--stdin].
|
||||
// Sets the appropriate state fields on success/failure.
|
||||
pub fn test_sudo(
|
||||
state: Arc<Mutex<Self>>,
|
||||
helper: &Arc<Mutex<Helper>>,
|
||||
xmrig: &Xmrig,
|
||||
path: &PathBuf,
|
||||
) {
|
||||
let helper = Arc::clone(helper);
|
||||
let xmrig = xmrig.clone();
|
||||
let path = path.clone();
|
||||
thread::spawn(move || {
|
||||
// Set to testing
|
||||
lock!(state).testing = true;
|
||||
|
||||
// Make sure sudo timestamp is reset
|
||||
let reset = Command::new("sudo")
|
||||
.arg("--reset-timestamp")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::piped())
|
||||
.status();
|
||||
match reset {
|
||||
Ok(_) => info!("Sudo | Resetting timestamp ... OK"),
|
||||
Err(e) => {
|
||||
error!("Sudo | Couldn't reset timestamp: {}", e);
|
||||
Self::wipe(&state);
|
||||
lock!(state).msg = format!("Sudo error: {}", e);
|
||||
lock!(state).testing = false;
|
||||
return
|
||||
},
|
||||
}
|
||||
// Make sure sudo timestamp is reset
|
||||
let reset = Command::new("sudo")
|
||||
.arg("--reset-timestamp")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::piped())
|
||||
.status();
|
||||
match reset {
|
||||
Ok(_) => info!("Sudo | Resetting timestamp ... OK"),
|
||||
Err(e) => {
|
||||
error!("Sudo | Couldn't reset timestamp: {}", e);
|
||||
Self::wipe(&state);
|
||||
lock!(state).msg = format!("Sudo error: {}", e);
|
||||
lock!(state).testing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn testing sudo
|
||||
let mut sudo = Command::new("sudo")
|
||||
.args(["--stdin", "--validate"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
// Spawn testing sudo
|
||||
let mut sudo = Command::new("sudo")
|
||||
.args(["--stdin", "--validate"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
// Write pass to STDIN
|
||||
let mut stdin = sudo.stdin.take().unwrap();
|
||||
stdin.write_all(lock!(state).pass.as_bytes()).unwrap();
|
||||
drop(stdin);
|
||||
// Write pass to STDIN
|
||||
let mut stdin = sudo.stdin.take().unwrap();
|
||||
stdin.write_all(lock!(state).pass.as_bytes()).unwrap();
|
||||
drop(stdin);
|
||||
|
||||
// Sudo re-prompts and will hang.
|
||||
// To workaround this, try checking
|
||||
// results for 5 seconds in a loop.
|
||||
for i in 1..=5 {
|
||||
match sudo.try_wait() {
|
||||
Ok(Some(code)) => if code.success() {
|
||||
info!("Sudo | Password ... OK!");
|
||||
lock!(state).success = true;
|
||||
break
|
||||
},
|
||||
Ok(None) => {
|
||||
info!("Sudo | Waiting [{}/5]...", i);
|
||||
std::thread::sleep(SECOND);
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Sudo | Couldn't reset timestamp: {}", e);
|
||||
Self::wipe(&state);
|
||||
lock!(state).msg = format!("Sudo error: {}", e);
|
||||
lock!(state).testing = false;
|
||||
return
|
||||
},
|
||||
}
|
||||
}
|
||||
if let Err(e) = sudo.kill() { warn!("Sudo | Kill error (it probably already exited): {}", e); }
|
||||
if lock!(state).success {
|
||||
match lock!(state).signal {
|
||||
ProcessSignal::Restart => crate::helper::Helper::restart_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
|
||||
ProcessSignal::Stop => crate::helper::Helper::stop_xmrig(&helper),
|
||||
_ => crate::helper::Helper::start_xmrig(&helper, &xmrig, &path, Arc::clone(&state)),
|
||||
}
|
||||
} else {
|
||||
lock!(state).msg = "Incorrect password! (or sudo timeout)".to_string();
|
||||
Self::wipe(&state);
|
||||
}
|
||||
lock!(state).signal = ProcessSignal::None;
|
||||
lock!(state).testing = false;
|
||||
});
|
||||
}
|
||||
// Sudo re-prompts and will hang.
|
||||
// To workaround this, try checking
|
||||
// results for 5 seconds in a loop.
|
||||
for i in 1..=5 {
|
||||
match sudo.try_wait() {
|
||||
Ok(Some(code)) => {
|
||||
if code.success() {
|
||||
info!("Sudo | Password ... OK!");
|
||||
lock!(state).success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
info!("Sudo | Waiting [{}/5]...", i);
|
||||
std::thread::sleep(SECOND);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Sudo | Couldn't reset timestamp: {}", e);
|
||||
Self::wipe(&state);
|
||||
lock!(state).msg = format!("Sudo error: {}", e);
|
||||
lock!(state).testing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Err(e) = sudo.kill() {
|
||||
warn!("Sudo | Kill error (it probably already exited): {}", e);
|
||||
}
|
||||
if lock!(state).success {
|
||||
match lock!(state).signal {
|
||||
ProcessSignal::Restart => crate::helper::Helper::restart_xmrig(
|
||||
&helper,
|
||||
&xmrig,
|
||||
&path,
|
||||
Arc::clone(&state),
|
||||
),
|
||||
ProcessSignal::Stop => crate::helper::Helper::stop_xmrig(&helper),
|
||||
_ => crate::helper::Helper::start_xmrig(
|
||||
&helper,
|
||||
&xmrig,
|
||||
&path,
|
||||
Arc::clone(&state),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
lock!(state).msg = "Incorrect password! (or sudo timeout)".to_string();
|
||||
Self::wipe(&state);
|
||||
}
|
||||
lock!(state).signal = ProcessSignal::None;
|
||||
lock!(state).testing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
1582
src/update.rs
1582
src/update.rs
File diff suppressed because it is too large
Load diff
691
src/xmr.rs
691
src/xmr.rs
|
@ -22,12 +22,8 @@
|
|||
// These represent:
|
||||
// "(DATE, ATOMIC_UNIT, MONERO_BLOCK)"
|
||||
|
||||
use crate::{
|
||||
human::*,
|
||||
};
|
||||
use crate::regex::{
|
||||
P2POOL_REGEX,
|
||||
};
|
||||
use crate::human::*;
|
||||
use crate::regex::P2POOL_REGEX;
|
||||
|
||||
use log::*;
|
||||
|
||||
|
@ -41,66 +37,76 @@ use log::*;
|
|||
// [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)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AtomicUnit(u64);
|
||||
|
||||
impl Default for AtomicUnit {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AtomicUnit {
|
||||
pub const fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
pub const fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
pub const fn from_u64(u: u64) -> Self {
|
||||
Self(u)
|
||||
}
|
||||
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_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 add_self(self, atomic_unit: Self) -> Self {
|
||||
Self(self.0 + atomic_unit.0)
|
||||
}
|
||||
|
||||
pub const fn to_u64(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
pub const fn to_u64(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn to_string(self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
#[allow(clippy::inherent_to_string_shadow_display)]
|
||||
// This is terrible but it formats it in a different way
|
||||
// than `Display`, but for backwards compat, changing it
|
||||
// requires touching other code, so...
|
||||
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 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 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_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_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)
|
||||
}
|
||||
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))
|
||||
}
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", Self::to_human_number_12_point(self))
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- [PayoutOrd]
|
||||
|
@ -113,300 +119,381 @@ impl std::fmt::Display for AtomicUnit {
|
|||
// [0] = DATE
|
||||
// [1] = XMR IN ATOMIC-UNITS
|
||||
// [2] = MONERO BLOCK
|
||||
#[derive(Debug,Clone)]
|
||||
#[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 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 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_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;
|
||||
}
|
||||
for (i, (date, atomic_unit, block)) in a.0.iter().enumerate() {
|
||||
if *date != b.0[i].0 {
|
||||
return false;
|
||||
}
|
||||
if *atomic_unit != b.0[i].1 {
|
||||
return false;
|
||||
}
|
||||
if *block != b.0[i].2 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
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) -> (String, AtomicUnit, HumanNumber) {
|
||||
// Date
|
||||
let date = match P2POOL_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) = P2POOL_REGEX.payout.find(line) {
|
||||
if let Some(word) = P2POOL_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) = P2POOL_REGEX.block.find(line) {
|
||||
if let Some(word) = P2POOL_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: "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) -> (String, AtomicUnit, HumanNumber) {
|
||||
// Date
|
||||
let date = match P2POOL_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) = P2POOL_REGEX.payout.find(line) {
|
||||
if let Some(word) = P2POOL_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) = P2POOL_REGEX.block.find(line) {
|
||||
if let Some(word) = P2POOL_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) -> (String, AtomicUnit, HumanNumber) {
|
||||
// Date
|
||||
let date = match P2POOL_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) = P2POOL_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 P2POOL_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)
|
||||
}
|
||||
// Expected input: "2022-01-27 01:30:23.1377 | 0.000000000001 XMR | Block 2,642,816"
|
||||
pub fn parse_formatted_payout_line(line: &str) -> (String, AtomicUnit, HumanNumber) {
|
||||
// Date
|
||||
let date = match P2POOL_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) = P2POOL_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 P2POOL_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 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!("PayoutOrd | Parsing line: [{}]", line);
|
||||
vec.push(Self::parse_formatted_payout_line(line));
|
||||
}
|
||||
*self = Self(vec);
|
||||
}
|
||||
// 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 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!("PayoutOrd | Parsing line: [{}]", line);
|
||||
vec.push(Self::parse_formatted_payout_line(line));
|
||||
}
|
||||
*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 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));
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
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));
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
// 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()
|
||||
}
|
||||
// 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.
|
||||
// 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 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(())
|
||||
}
|
||||
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
|
||||
#[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);
|
||||
}
|
||||
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 push_to_payout_ord() {
|
||||
use crate::xmr::PayoutOrd;
|
||||
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 sum_payout_ord_atomic_unit() {
|
||||
use crate::human::HumanNumber;
|
||||
use crate::xmr::AtomicUnit;
|
||||
use crate::xmr::PayoutOrd;
|
||||
let 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);
|
||||
#[test]
|
||||
fn sort_p2pool_payout_ord() {
|
||||
use crate::human::HumanNumber;
|
||||
use crate::xmr::AtomicUnit;
|
||||
use crate::xmr::PayoutOrd;
|
||||
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
|
||||
// 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);
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
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);
|
||||
#[test]
|
||||
fn payout_ord_is_same() {
|
||||
use crate::human::HumanNumber;
|
||||
use crate::xmr::AtomicUnit;
|
||||
use crate::xmr::PayoutOrd;
|
||||
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);
|
||||
}
|
||||
assert!(PayoutOrd::is_same(&payout_ord, &payout_ord_2));
|
||||
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));
|
||||
}
|
||||
|
||||
#[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);
|
||||
#[test]
|
||||
fn view_reverse_payout_ord() {
|
||||
use crate::human::HumanNumber;
|
||||
use crate::xmr::AtomicUnit;
|
||||
use crate::xmr::PayoutOrd;
|
||||
let 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(clippy::never_loop)]
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
440
src/xmrig.rs
440
src/xmrig.rs
|
@ -15,125 +15,177 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
Regexes,
|
||||
constants::*,
|
||||
disk::*,
|
||||
Process,
|
||||
PubXmrigApi,
|
||||
macros::*,
|
||||
};
|
||||
use crate::regex::REGEXES;
|
||||
use crate::{constants::*, disk::*, macros::*, Process, PubXmrigApi, Regexes};
|
||||
use egui::{
|
||||
TextEdit,SelectableLabel,ComboBox,Label,Button,RichText,Slider,Checkbox,
|
||||
TextStyle::*,
|
||||
Button, Checkbox, ComboBox, Label, RichText, SelectableLabel, Slider, TextEdit, TextStyle::*,
|
||||
};
|
||||
use std::{
|
||||
sync::{Arc,Mutex},
|
||||
};
|
||||
use regex::Regex;
|
||||
use log::*;
|
||||
use crate::regex::{
|
||||
REGEXES,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
impl crate::disk::Xmrig {
|
||||
#[inline(always)]
|
||||
pub fn show(&mut self, pool_vec: &mut Vec<(String, Pool)>, process: &Arc<Mutex<Process>>, api: &Arc<Mutex<PubXmrigApi>>, buffer: &mut String, width: f32, height: f32, _ctx: &egui::Context, ui: &mut egui::Ui) {
|
||||
let text_edit = height / 25.0;
|
||||
//---------------------------------------------------------------------------------------------------- [Simple] Console
|
||||
debug!("XMRig Tab | Rendering [Console]");
|
||||
ui.group(|ui| {
|
||||
if self.simple {
|
||||
let height = height / 1.5;
|
||||
let width = width - SPACE;
|
||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
egui::ScrollArea::vertical().stick_to_bottom(true).max_width(width).max_height(height).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
|
||||
ui.add_sized([width, height], TextEdit::multiline(&mut lock!(api).output.as_str()));
|
||||
});
|
||||
});
|
||||
//---------------------------------------------------------------------------------------------------- [Advanced] Console
|
||||
} else {
|
||||
let height = height / 2.8;
|
||||
let width = width - SPACE;
|
||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
egui::ScrollArea::vertical().stick_to_bottom(true).max_width(width).max_height(height).auto_shrink([false; 2]).show_viewport(ui, |ui, _| {
|
||||
ui.add_sized([width, height], TextEdit::multiline(&mut lock!(api).output.as_str()));
|
||||
});
|
||||
});
|
||||
ui.separator();
|
||||
let response = ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(buffer), r#"Commands: [h]ashrate, [p]ause, [r]esume, re[s]ults, [c]onnection"#)).on_hover_text(XMRIG_INPUT);
|
||||
// If the user pressed enter, dump buffer contents into the process STDIN
|
||||
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
|
||||
response.request_focus(); // Get focus back
|
||||
let buffer = std::mem::take(buffer); // Take buffer
|
||||
let mut process = lock!(process); // Lock
|
||||
if process.is_alive() { process.input.push(buffer); } // Push only if alive
|
||||
}
|
||||
}
|
||||
});
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn show(
|
||||
&mut self,
|
||||
pool_vec: &mut Vec<(String, Pool)>,
|
||||
process: &Arc<Mutex<Process>>,
|
||||
api: &Arc<Mutex<PubXmrigApi>>,
|
||||
buffer: &mut String,
|
||||
width: f32,
|
||||
height: f32,
|
||||
_ctx: &egui::Context,
|
||||
ui: &mut egui::Ui,
|
||||
) {
|
||||
let text_edit = height / 25.0;
|
||||
//---------------------------------------------------------------------------------------------------- [Simple] Console
|
||||
debug!("XMRig Tab | Rendering [Console]");
|
||||
ui.group(|ui| {
|
||||
if self.simple {
|
||||
let height = height / 1.5;
|
||||
let width = width - SPACE;
|
||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
egui::ScrollArea::vertical()
|
||||
.stick_to_bottom(true)
|
||||
.max_width(width)
|
||||
.max_height(height)
|
||||
.auto_shrink([false; 2])
|
||||
.show_viewport(ui, |ui, _| {
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
TextEdit::multiline(&mut lock!(api).output.as_str()),
|
||||
);
|
||||
});
|
||||
});
|
||||
//---------------------------------------------------------------------------------------------------- [Advanced] Console
|
||||
} else {
|
||||
let height = height / 2.8;
|
||||
let width = width - SPACE;
|
||||
egui::Frame::none().fill(DARK_GRAY).show(ui, |ui| {
|
||||
ui.style_mut().override_text_style = Some(Name("MonospaceSmall".into()));
|
||||
egui::ScrollArea::vertical()
|
||||
.stick_to_bottom(true)
|
||||
.max_width(width)
|
||||
.max_height(height)
|
||||
.auto_shrink([false; 2])
|
||||
.show_viewport(ui, |ui, _| {
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
TextEdit::multiline(&mut lock!(api).output.as_str()),
|
||||
);
|
||||
});
|
||||
});
|
||||
ui.separator();
|
||||
let response = ui
|
||||
.add_sized(
|
||||
[width, text_edit],
|
||||
TextEdit::hint_text(
|
||||
TextEdit::singleline(buffer),
|
||||
r#"Commands: [h]ashrate, [p]ause, [r]esume, re[s]ults, [c]onnection"#,
|
||||
),
|
||||
)
|
||||
.on_hover_text(XMRIG_INPUT);
|
||||
// If the user pressed enter, dump buffer contents into the process STDIN
|
||||
if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
|
||||
response.request_focus(); // Get focus back
|
||||
let buffer = std::mem::take(buffer); // Take buffer
|
||||
let mut process = lock!(process); // Lock
|
||||
if process.is_alive() {
|
||||
process.input.push(buffer);
|
||||
} // Push only if alive
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Arguments
|
||||
if !self.simple {
|
||||
debug!("XMRig Tab | Rendering [Arguments]");
|
||||
ui.group(|ui| { ui.horizontal(|ui| {
|
||||
let width = (width/10.0) - SPACE;
|
||||
ui.add_sized([width, text_edit], Label::new("Command arguments:"));
|
||||
ui.add_sized([ui.available_width(), text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.arguments), r#"--url <...> --user <...> --config <...>"#)).on_hover_text(XMRIG_ARGUMENTS);
|
||||
self.arguments.truncate(1024);
|
||||
})});
|
||||
ui.set_enabled(self.arguments.is_empty());
|
||||
//---------------------------------------------------------------------------------------------------- Address
|
||||
debug!("XMRig Tab | Rendering [Address]");
|
||||
ui.group(|ui| {
|
||||
let width = width - SPACE;
|
||||
ui.spacing_mut().text_edit_width = (width)-(SPACE*3.0);
|
||||
let text;
|
||||
let color;
|
||||
let len = format!("{:02}", self.address.len());
|
||||
if self.address.is_empty() {
|
||||
text = format!("Monero Address [{}/95] ➖", len);
|
||||
color = LIGHT_GRAY;
|
||||
} else if Regexes::addr_ok(&self.address) {
|
||||
text = format!("Monero Address [{}/95] ✔", len);
|
||||
color = GREEN;
|
||||
} else {
|
||||
text = format!("Monero Address [{}/95] ❌", len);
|
||||
color = RED;
|
||||
}
|
||||
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
|
||||
ui.add_sized([width, text_edit], TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4...")).on_hover_text(XMRIG_ADDRESS);
|
||||
self.address.truncate(95);
|
||||
});
|
||||
}
|
||||
//---------------------------------------------------------------------------------------------------- Arguments
|
||||
if !self.simple {
|
||||
debug!("XMRig Tab | Rendering [Arguments]");
|
||||
ui.group(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let width = (width / 10.0) - SPACE;
|
||||
ui.add_sized([width, text_edit], Label::new("Command arguments:"));
|
||||
ui.add_sized(
|
||||
[ui.available_width(), text_edit],
|
||||
TextEdit::hint_text(
|
||||
TextEdit::singleline(&mut self.arguments),
|
||||
r#"--url <...> --user <...> --config <...>"#,
|
||||
),
|
||||
)
|
||||
.on_hover_text(XMRIG_ARGUMENTS);
|
||||
self.arguments.truncate(1024);
|
||||
})
|
||||
});
|
||||
ui.set_enabled(self.arguments.is_empty());
|
||||
//---------------------------------------------------------------------------------------------------- Address
|
||||
debug!("XMRig Tab | Rendering [Address]");
|
||||
ui.group(|ui| {
|
||||
let width = width - SPACE;
|
||||
ui.spacing_mut().text_edit_width = (width) - (SPACE * 3.0);
|
||||
let text;
|
||||
let color;
|
||||
let len = format!("{:02}", self.address.len());
|
||||
if self.address.is_empty() {
|
||||
text = format!("Monero Address [{}/95] ➖", len);
|
||||
color = LIGHT_GRAY;
|
||||
} else if Regexes::addr_ok(&self.address) {
|
||||
text = format!("Monero Address [{}/95] ✔", len);
|
||||
color = GREEN;
|
||||
} else {
|
||||
text = format!("Monero Address [{}/95] ❌", len);
|
||||
color = RED;
|
||||
}
|
||||
ui.add_sized(
|
||||
[width, text_edit],
|
||||
Label::new(RichText::new(text).color(color)),
|
||||
);
|
||||
ui.add_sized(
|
||||
[width, text_edit],
|
||||
TextEdit::hint_text(TextEdit::singleline(&mut self.address), "4..."),
|
||||
)
|
||||
.on_hover_text(XMRIG_ADDRESS);
|
||||
self.address.truncate(95);
|
||||
});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Threads
|
||||
if self.simple { ui.add_space(SPACE); }
|
||||
debug!("XMRig Tab | Rendering [Threads]");
|
||||
ui.vertical(|ui| {
|
||||
let width = width / 10.0;
|
||||
let text_width = width * 2.4;
|
||||
ui.spacing_mut().slider_width = width * 6.5;
|
||||
ui.spacing_mut().icon_width = width / 25.0;
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([text_width, text_edit], Label::new(format!("Threads [1-{}]:", self.max_threads)));
|
||||
ui.add_sized([width, text_edit], Slider::new(&mut self.current_threads, 1..=self.max_threads)).on_hover_text(XMRIG_THREADS);
|
||||
});
|
||||
#[cfg(not(target_os = "linux"))] // Pause on active isn't supported on Linux
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized([text_width, text_edit], Label::new(format!("Pause on active [0-255]:")));
|
||||
ui.add_sized([width, text_edit], Slider::new(&mut self.pause, 0..=255)).on_hover_text(format!("{} [{}] seconds.", XMRIG_PAUSE, self.pause));
|
||||
});
|
||||
});
|
||||
//---------------------------------------------------------------------------------------------------- Threads
|
||||
if self.simple {
|
||||
ui.add_space(SPACE);
|
||||
}
|
||||
debug!("XMRig Tab | Rendering [Threads]");
|
||||
ui.vertical(|ui| {
|
||||
let width = width / 10.0;
|
||||
let text_width = width * 2.4;
|
||||
ui.spacing_mut().slider_width = width * 6.5;
|
||||
ui.spacing_mut().icon_width = width / 25.0;
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized(
|
||||
[text_width, text_edit],
|
||||
Label::new(format!("Threads [1-{}]:", self.max_threads)),
|
||||
);
|
||||
ui.add_sized(
|
||||
[width, text_edit],
|
||||
Slider::new(&mut self.current_threads, 1..=self.max_threads),
|
||||
)
|
||||
.on_hover_text(XMRIG_THREADS);
|
||||
});
|
||||
#[cfg(not(target_os = "linux"))] // Pause on active isn't supported on Linux
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized(
|
||||
[text_width, text_edit],
|
||||
Label::new(format!("Pause on active [0-255]:")),
|
||||
);
|
||||
ui.add_sized([width, text_edit], Slider::new(&mut self.pause, 0..=255))
|
||||
.on_hover_text(format!("{} [{}] seconds.", XMRIG_PAUSE, self.pause));
|
||||
});
|
||||
});
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Simple
|
||||
if !self.simple {
|
||||
debug!("XMRig Tab | Rendering [Pool List] elements");
|
||||
let width = ui.available_width() - 10.0;
|
||||
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
|
||||
// [Pool IP/Port]
|
||||
ui.horizontal(|ui| {
|
||||
//---------------------------------------------------------------------------------------------------- Simple
|
||||
if !self.simple {
|
||||
debug!("XMRig Tab | Rendering [Pool List] elements");
|
||||
let width = ui.available_width() - 10.0;
|
||||
let mut incorrect_input = false; // This will disable [Add/Delete] on bad input
|
||||
// [Pool IP/Port]
|
||||
ui.horizontal(|ui| {
|
||||
ui.group(|ui| {
|
||||
let width = width/10.0;
|
||||
ui.vertical(|ui| {
|
||||
|
@ -228,9 +280,8 @@ impl crate::disk::Xmrig {
|
|||
// [Node List]
|
||||
debug!("XMRig Tab | Rendering [Node List] ComboBox");
|
||||
let text = RichText::new(format!("{}. {}", self.selected_index+1, self.selected_name));
|
||||
ComboBox::from_id_source("manual_pool").selected_text(text).show_ui(ui, |ui| {
|
||||
let mut n = 0;
|
||||
for (name, pool) in pool_vec.iter() {
|
||||
ComboBox::from_id_source("manual_pool").selected_text(text).width(width).show_ui(ui, |ui| {
|
||||
for (n, (name, pool)) in pool_vec.iter().enumerate() {
|
||||
let text = format!("{}. {}\n IP: {}\n Port: {}\n Rig: {}", n+1, name, pool.ip, pool.port, pool.rig);
|
||||
if ui.add(SelectableLabel::new(self.selected_name == *name, text)).clicked() {
|
||||
self.selected_index = n;
|
||||
|
@ -244,7 +295,6 @@ impl crate::disk::Xmrig {
|
|||
self.ip = pool.ip;
|
||||
self.port = pool.port;
|
||||
}
|
||||
n += 1;
|
||||
}
|
||||
});
|
||||
// [Add/Save]
|
||||
|
@ -343,77 +393,97 @@ impl crate::disk::Xmrig {
|
|||
});
|
||||
});
|
||||
});
|
||||
ui.add_space(5.0);
|
||||
ui.add_space(5.0);
|
||||
|
||||
debug!("XMRig Tab | Rendering [API] TextEdits");
|
||||
// [HTTP API IP/Port]
|
||||
ui.group(|ui| { ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
let width = width/10.0;
|
||||
ui.spacing_mut().text_edit_width = width*2.39;
|
||||
// HTTP API
|
||||
ui.horizontal(|ui| {
|
||||
let text;
|
||||
let color;
|
||||
let len = format!("{:03}", self.api_ip.len());
|
||||
if self.api_ip.is_empty() {
|
||||
text = format!("HTTP API IP [{}/255]➖", len);
|
||||
color = LIGHT_GRAY;
|
||||
incorrect_input = true;
|
||||
} else if self.api_ip == "localhost" || REGEXES.ipv4.is_match(&self.api_ip) || REGEXES.domain.is_match(&self.api_ip) {
|
||||
text = format!("HTTP API IP [{}/255]✔", len);
|
||||
color = GREEN;
|
||||
} else {
|
||||
text = format!("HTTP API IP [{}/255]❌", len);
|
||||
color = RED;
|
||||
incorrect_input = true;
|
||||
}
|
||||
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
|
||||
ui.text_edit_singleline(&mut self.api_ip).on_hover_text(XMRIG_API_IP);
|
||||
self.api_ip.truncate(255);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
let text;
|
||||
let color;
|
||||
let len = self.api_port.len();
|
||||
if self.api_port.is_empty() {
|
||||
text = format!("HTTP API Port [ {}/5 ]➖", len);
|
||||
color = LIGHT_GRAY;
|
||||
incorrect_input = true;
|
||||
} else if REGEXES.port.is_match(&self.api_port) {
|
||||
text = format!("HTTP API Port [ {}/5 ]✔", len);
|
||||
color = GREEN;
|
||||
} else {
|
||||
text = format!("HTTP API Port [ {}/5 ]❌", len);
|
||||
color = RED;
|
||||
incorrect_input = true;
|
||||
}
|
||||
ui.add_sized([width, text_edit], Label::new(RichText::new(text).color(color)));
|
||||
ui.text_edit_singleline(&mut self.api_port).on_hover_text(XMRIG_API_PORT);
|
||||
self.api_port.truncate(5);
|
||||
});
|
||||
});
|
||||
debug!("XMRig Tab | Rendering [API] TextEdits");
|
||||
// [HTTP API IP/Port]
|
||||
ui.group(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
let width = width / 10.0;
|
||||
ui.spacing_mut().text_edit_width = width * 2.39;
|
||||
// HTTP API
|
||||
ui.horizontal(|ui| {
|
||||
let text;
|
||||
let color;
|
||||
let len = format!("{:03}", self.api_ip.len());
|
||||
if self.api_ip.is_empty() {
|
||||
text = format!("HTTP API IP [{}/255]➖", len);
|
||||
color = LIGHT_GRAY;
|
||||
incorrect_input = true;
|
||||
} else if self.api_ip == "localhost"
|
||||
|| REGEXES.ipv4.is_match(&self.api_ip)
|
||||
|| REGEXES.domain.is_match(&self.api_ip)
|
||||
{
|
||||
text = format!("HTTP API IP [{}/255]✔", len);
|
||||
color = GREEN;
|
||||
} else {
|
||||
text = format!("HTTP API IP [{}/255]❌", len);
|
||||
color = RED;
|
||||
incorrect_input = true;
|
||||
}
|
||||
ui.add_sized(
|
||||
[width, text_edit],
|
||||
Label::new(RichText::new(text).color(color)),
|
||||
);
|
||||
ui.text_edit_singleline(&mut self.api_ip)
|
||||
.on_hover_text(XMRIG_API_IP);
|
||||
self.api_ip.truncate(255);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
let text;
|
||||
let color;
|
||||
let len = self.api_port.len();
|
||||
if self.api_port.is_empty() {
|
||||
text = format!("HTTP API Port [ {}/5 ]➖", len);
|
||||
color = LIGHT_GRAY;
|
||||
incorrect_input = true;
|
||||
} else if REGEXES.port.is_match(&self.api_port) {
|
||||
text = format!("HTTP API Port [ {}/5 ]✔", len);
|
||||
color = GREEN;
|
||||
} else {
|
||||
text = format!("HTTP API Port [ {}/5 ]❌", len);
|
||||
color = RED;
|
||||
incorrect_input = true;
|
||||
}
|
||||
ui.add_sized(
|
||||
[width, text_edit],
|
||||
Label::new(RichText::new(text).color(color)),
|
||||
);
|
||||
ui.text_edit_singleline(&mut self.api_port)
|
||||
.on_hover_text(XMRIG_API_PORT);
|
||||
self.api_port.truncate(5);
|
||||
});
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
ui.separator();
|
||||
|
||||
debug!("XMRig Tab | Rendering [TLS/Keepalive] buttons");
|
||||
ui.vertical(|ui| {
|
||||
// TLS/Keepalive
|
||||
ui.horizontal(|ui| {
|
||||
let width = (ui.available_width()/2.0)-11.0;
|
||||
let height = text_edit*2.0;
|
||||
// let mut style = (*ctx.style()).clone();
|
||||
// style.spacing.icon_width_inner = width / 8.0;
|
||||
// style.spacing.icon_width = width / 6.0;
|
||||
// style.spacing.icon_spacing = 20.0;
|
||||
// ctx.set_style(style);
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.tls, "TLS Connection")).on_hover_text(XMRIG_TLS);
|
||||
ui.separator();
|
||||
ui.add_sized([width, height], Checkbox::new(&mut self.keepalive, "Keepalive")).on_hover_text(XMRIG_KEEPALIVE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
debug!("XMRig Tab | Rendering [TLS/Keepalive] buttons");
|
||||
ui.vertical(|ui| {
|
||||
// TLS/Keepalive
|
||||
ui.horizontal(|ui| {
|
||||
let width = (ui.available_width() / 2.0) - 11.0;
|
||||
let height = text_edit * 2.0;
|
||||
// let mut style = (*ctx.style()).clone();
|
||||
// style.spacing.icon_width_inner = width / 8.0;
|
||||
// style.spacing.icon_width = width / 6.0;
|
||||
// style.spacing.icon_spacing = 20.0;
|
||||
// ctx.set_style(style);
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.tls, "TLS Connection"),
|
||||
)
|
||||
.on_hover_text(XMRIG_TLS);
|
||||
ui.separator();
|
||||
ui.add_sized(
|
||||
[width, height],
|
||||
Checkbox::new(&mut self.keepalive, "Keepalive"),
|
||||
)
|
||||
.on_hover_text(XMRIG_KEEPALIVE);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
extend-exclude = [
|
||||
"src/cpu.json",
|
||||
"pgp/",
|
||||
"external",
|
||||
]
|
||||
|
|
|
@ -2,11 +2,6 @@
|
|||
|
||||
START_TIME=$EPOCHSECONDS
|
||||
|
||||
# Get original timezone
|
||||
OG_TIMEZONE=$(timedatectl show | grep Timezone)
|
||||
OG_TIMEZONE=${OG_TIMEZONE/Timezone=/}
|
||||
set_og_timezone() { sudo timedatectl set-timezone "$OG_TIMEZONE"; }
|
||||
|
||||
title() { printf "\n\e[1;93m%s\e[0m\n" "============================ $1 ============================"; }
|
||||
check() {
|
||||
local CODE=$?
|
||||
|
@ -14,21 +9,16 @@ check() {
|
|||
printf "${BASH_LINENO} | %s ... \e[1;92mOK\e[0m\n" "$1"
|
||||
else
|
||||
printf "${BASH_LINENO} | %s ... \e[1;91mFAIL\e[0m\n" "$1"
|
||||
set_og_timezone
|
||||
exit $CODE
|
||||
fi
|
||||
}
|
||||
int() {
|
||||
printf "\n\n%s\n" "Exit detected, resetting timezone to [${OG_TIMEZONE}]"
|
||||
set_og_timezone
|
||||
exit 1
|
||||
}
|
||||
|
||||
trap 'int' INT
|
||||
|
||||
# Check sudo (for changing timezone)
|
||||
title "Basic checks"
|
||||
sudo -v; check "sudo"
|
||||
# Check for needed files
|
||||
[[ -d skel ]]; check "skel"
|
||||
[[ -f skel/CHANGELOG.md ]]; check "skel/CHANGELOG.md"
|
||||
|
@ -62,9 +52,6 @@ title "Windows folder check"
|
|||
title "RNG Date"
|
||||
RNG=$((EPOCHSECONDS-RANDOM*4)); check "RNG ... $RNG"
|
||||
DATE=$(date -d @${RNG}); check "DATE ... $DATE"
|
||||
RNG_TIMEZONE=$(timedatectl list-timezones | sed -n "$((RANDOM%$(timedatectl list-timezones | wc -l)))p"); check "RNG_TIMEZONE ... $RNG_TIMEZONE"
|
||||
# Set random timezone
|
||||
sudo timedatectl set-timezone "$RNG_TIMEZONE"; check "set rng timezone"
|
||||
|
||||
# Tar Linux Bundle
|
||||
title "Tar Linux"
|
||||
|
@ -138,5 +125,4 @@ check "Changelog into clipboard"
|
|||
|
||||
# Reset timezone
|
||||
title "End"
|
||||
set_og_timezone; check "Reset timezone"
|
||||
printf "\n%s\n" "package.sh ... Took [$((EPOCHSECONDS-START_TIME))] seconds ... OK!"
|
||||
|
|
Loading…
Reference in a new issue