diff --git a/Cargo.lock b/Cargo.lock
index eed8f28..b191c29 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1736,6 +1736,7 @@ dependencies = [
"grin_servers",
"grin_util",
"jni",
+ "lazy_static",
"log",
"once_cell",
"openssl-sys",
diff --git a/Cargo.toml b/Cargo.toml
index 0c2c2d9..fc19d9f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,6 +46,7 @@ once_cell = "1.10.0"
rust-i18n = "1.1.4"
sys-locale = "0.3.0"
chrono = "0.4.23"
+lazy_static = "1.4.0"
[patch.crates-io]
winit = { git = "https://github.com/rib/winit", branch = "android-activity" }
@@ -63,5 +64,5 @@ jni = "0.21.1"
winit = { version = "0.27.2", features = [ "android-game-activity" ] }
[lib]
-name="grim_android"
+name="grim"
crate_type=["cdylib"]
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index a59689f..a745fea 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ android {
defaultConfig {
applicationId "mw.gri.android"
minSdk 24
- targetSdk 31
+ targetSdk 33
versionCode 1
versionName "0.1.0"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1b48b2c..5a889f0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,28 +2,35 @@
+
+
+
+ android:hardwareAccelerated="true"
+ android:largeHeap="true"
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="Grim"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.Main">
+ android:launchMode="singleTask"
+ android:name=".MainActivity"
+ android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode|keyboard"
+ android:exported="true">
-
+
+
\ No newline at end of file
diff --git a/app/src/main/java/mw/gri/android/BackgroundService.java b/app/src/main/java/mw/gri/android/BackgroundService.java
new file mode 100644
index 0000000..1be3aa6
--- /dev/null
+++ b/app/src/main/java/mw/gri/android/BackgroundService.java
@@ -0,0 +1,131 @@
+package mw.gri.android;
+
+import android.app.*;
+import android.content.Context;
+import android.content.Intent;
+import android.os.*;
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+
+import java.util.List;
+
+public class BackgroundService extends Service {
+
+ private static final String TAG = BackgroundService.class.getSimpleName();
+
+ private PowerManager.WakeLock mWakeLock;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private boolean mStopped = false;
+
+ private static final int SYNC_STATUS_NOTIFICATION_ID = 1;
+ private NotificationCompat.Builder mNotificationBuilder;
+
+ private final Runnable mUpdateSyncStatus = new Runnable() {
+ @Override
+ public void run() {
+ mNotificationBuilder.setContentText(getSyncStatusText());
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ manager.notify(SYNC_STATUS_NOTIFICATION_ID, mNotificationBuilder.build());
+
+ if (!mStopped) {
+ mHandler.postDelayed(this, 500);
+ }
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mWakeLock.acquire();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel notificationChannel = new NotificationChannel(
+ TAG, TAG, NotificationManager.IMPORTANCE_LOW
+ );
+
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ manager.createNotificationChannel(notificationChannel);
+ }
+
+ Intent i = getPackageManager().getLaunchIntentForPackage(this.getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE);
+ mNotificationBuilder = new NotificationCompat.Builder(this, TAG)
+ .setContentTitle(this.getSyncTitle())
+ .setContentText(this.getSyncStatusText())
+ .setSmallIcon(R.drawable.ic_stat_name)
+ .setContentIntent(pendingIntent);
+ Notification notification = mNotificationBuilder.build();
+ startForeground(SYNC_STATUS_NOTIFICATION_ID, notification);
+
+ mHandler.post(mUpdateSyncStatus);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_STICKY;
+ }
+
+ @Override
+ public void onTaskRemoved(Intent rootIntent) {
+ onStop();
+ super.onTaskRemoved(rootIntent);
+ }
+
+ @Override
+ public void onDestroy() {
+ onStop();
+ super.onDestroy();
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ public void onStop() {
+ mStopped = true;
+
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+
+ mHandler.removeCallbacks(mUpdateSyncStatus);
+
+ stopForeground(Service.STOP_FOREGROUND_REMOVE);
+ stopSelf();
+ }
+
+ public static void start(Context context) {
+ if (!isServiceRunning(context)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(new Intent(context, BackgroundService.class));
+ } else {
+ context.startService(new Intent(context, BackgroundService.class));
+ }
+ }
+ }
+
+ public static void stop(Context context) {
+ context.stopService(new Intent(context, BackgroundService.class));
+ }
+
+ private static boolean isServiceRunning(Context context) {
+ ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ List services = activityManager.getRunningServices(Integer.MAX_VALUE);
+
+ for (ActivityManager.RunningServiceInfo runningServiceInfo : services) {
+ if (runningServiceInfo.service.getClassName().equals(BackgroundService.class.getName())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private native String getSyncStatusText();
+ private native String getSyncTitle();
+}
diff --git a/app/src/main/java/mw/gri/android/MainActivity.java b/app/src/main/java/mw/gri/android/MainActivity.java
index 0a2906b..1dd8342 100644
--- a/app/src/main/java/mw/gri/android/MainActivity.java
+++ b/app/src/main/java/mw/gri/android/MainActivity.java
@@ -1,7 +1,7 @@
package mw.gri.android;
-import android.content.Intent;
import android.os.Bundle;
+import android.os.Process;
import android.system.ErrnoException;
import android.system.Os;
import com.google.androidgamesdk.GameActivity;
@@ -9,7 +9,7 @@ import com.google.androidgamesdk.GameActivity;
public class MainActivity extends GameActivity {
static {
- System.loadLibrary("grim_android");
+ System.loadLibrary("grim");
}
@Override
@@ -20,9 +20,29 @@ public class MainActivity extends GameActivity {
throw new RuntimeException(e);
}
super.onCreate(savedInstanceState);
+ BackgroundService.start(getApplicationContext());
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (!mManualExit) {
+ BackgroundService.stop(getApplicationContext());
+ // Temporary fix to prevent app hanging when closed from recent apps
+ Process.killProcess(Process.myPid());
+ }
+ super.onDestroy();
}
public int[] getDisplayCutouts() {
return Utils.getDisplayCutouts(this);
}
+
+ private boolean mManualExit = false;
+
+ // Called from native code
+ public void onExit() {
+ mManualExit = true;
+ BackgroundService.stop(getApplicationContext());
+ finish();
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_name.png b/app/src/main/res/drawable-hdpi/ic_stat_name.png
new file mode 100644
index 0000000..c4385b4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_name.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_stat_name.png b/app/src/main/res/drawable-mdpi/ic_stat_name.png
new file mode 100644
index 0000000..39c3c48
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stat_name.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_name.png b/app/src/main/res/drawable-xhdpi/ic_stat_name.png
new file mode 100644
index 0000000..c46f722
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_name.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_name.png b/app/src/main/res/drawable-xxhdpi/ic_stat_name.png
new file mode 100644
index 0000000..869b711
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stat_name.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png
new file mode 100644
index 0000000..96df0c2
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png differ
diff --git a/build_and_run.sh b/build_and_run.sh
index 3f61857..6823d03 100755
--- a/build_and_run.sh
+++ b/build_and_run.sh
@@ -14,7 +14,7 @@ export CPPFLAGS="-DMDB_USE_ROBUST=0" && export CFLAGS="-DMDB_USE_ROBUST=0" && ca
if [ $? -eq 0 ]
then
- yes | cp -f target/aarch64-linux-android/${type}/libgrim_android.so app/src/main/jniLibs/arm64-v8a
+ yes | cp -f target/aarch64-linux-android/${type}/libgrim.so app/src/main/jniLibs/arm64-v8a
./gradlew clean
./gradlew build
#./gradlew installDebug
diff --git a/src/grim.rs b/src/grim.rs
index 2e60c23..19fc5c2 100644
--- a/src/grim.rs
+++ b/src/grim.rs
@@ -22,7 +22,7 @@ use crate::gui::PlatformApp;
#[allow(dead_code)]
#[cfg(target_os = "android")]
#[no_mangle]
-unsafe fn android_main(app: AndroidApp) {
+fn android_main(app: AndroidApp) {
#[cfg(debug_assertions)]
{
std::env::set_var("RUST_BACKTRACE", "full");
diff --git a/src/gui/views/mod.rs b/src/gui/views/mod.rs
index ff4bc0a..2d9577f 100644
--- a/src/gui/views/mod.rs
+++ b/src/gui/views/mod.rs
@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use eframe::epaint::{Color32, Stroke};
-
mod views;
pub use self::views::View;
@@ -24,11 +22,13 @@ mod network;
mod network_node;
mod network_tuning;
mod network_metrics;
+mod network_mining;
+
pub use self::network::Network;
pub trait NetworkTab {
fn name(&self) -> &String;
- fn ui(&mut self, ui: &mut egui::Ui, node: &mut crate::node::Node);
+ fn ui(&mut self, ui: &mut egui::Ui);
}
diff --git a/src/gui/views/network.rs b/src/gui/views/network.rs
index 5ae24a1..e9b88d9 100644
--- a/src/gui/views/network.rs
+++ b/src/gui/views/network.rs
@@ -42,8 +42,6 @@ enum Mode {
}
pub struct Network {
- node: Node,
-
current_mode: Mode,
node_view: NetworkNode,
@@ -52,9 +50,8 @@ pub struct Network {
impl Default for Network {
fn default() -> Self {
- let node = Node::new(ChainTypes::Mainnet, true);
+ Node::start(ChainTypes::Mainnet);
Self {
- node,
current_mode: Mode::Node,
node_view: NetworkNode::default(),
metrics_view: NetworkMetrics::default()
@@ -67,7 +64,7 @@ impl Network {
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
nav: &mut Navigator,
- cb: &dyn PlatformCallbacks) {
+ _: &dyn PlatformCallbacks) {
egui::TopBottomPanel::top("network_title")
.resizable(false)
@@ -136,13 +133,13 @@ impl Network {
fn draw_tab_content(&mut self, ui: &mut egui::Ui) {
match self.current_mode {
Mode::Node => {
- self.node_view.ui(ui, &mut self.node);
+ self.node_view.ui(ui);
}
Mode::Metrics => {
- self.metrics_view.ui(ui, &mut self.node);
+ self.metrics_view.ui(ui);
}
Mode::Tuning => {
- self.node_view.ui(ui, &mut self.node);
+ self.node_view.ui(ui);
}
Mode::Miner => {}
}
@@ -215,23 +212,12 @@ impl Network {
strip.cell(|ui| {
ui.centered_and_justified(|ui| {
// Select sync status text
- let sync_status = self.node.state.get_sync_status();
- let status_text = if self.node.state.is_restarting() {
- t!("server_restarting")
- } else {
- match sync_status {
- None => {
- t!("server_down")
- }
- Some(ss) => {
- get_sync_status_text(ss).to_string()
- }
- }
- };
+ let sync_status = Node::get_sync_status();
+ let status_text = Node::get_sync_status_text(sync_status);
// Setup text color animation based on sync status
let idle = match sync_status {
- None => { !self.node.state.is_starting() }
+ None => { !Node::is_starting() }
Some(ss) => { ss == SyncStatus::NoSync }
};
let (dark, bright) = (0.3, 1.0);
@@ -267,72 +253,3 @@ impl Network {
}
}
-fn get_sync_status_text(sync_status: SyncStatus) -> String {
- match sync_status {
- SyncStatus::Initial => t!("sync_status.initial"),
- SyncStatus::NoSync => t!("sync_status.no_sync"),
- SyncStatus::AwaitingPeers(_) => t!("sync_status.awaiting_peers"),
- SyncStatus::HeaderSync {
- sync_head,
- highest_height,
- ..
- } => {
- if highest_height == 0 {
- t!("sync_status.header_sync")
- } else {
- let percent = sync_head.height * 100 / highest_height;
- t!("sync_status.header_sync_percent", "percent" => percent)
- }
- }
- SyncStatus::TxHashsetDownload(stat) => {
- if stat.total_size > 0 {
- let percent = stat.downloaded_size * 100 / stat.total_size;
- t!("sync_status.tx_hashset_download_percent", "percent" => percent)
- } else {
- t!("sync_status.tx_hashset_download")
- }
- }
- SyncStatus::TxHashsetSetup => {
- t!("sync_status.tx_hashset_setup")
- }
- SyncStatus::TxHashsetRangeProofsValidation {
- rproofs,
- rproofs_total,
- } => {
- let r_percent = if rproofs_total > 0 {
- (rproofs * 100) / rproofs_total
- } else {
- 0
- };
- t!("sync_status.tx_hashset_range_proofs_validation", "percent" => r_percent)
- }
- SyncStatus::TxHashsetKernelsValidation {
- kernels,
- kernels_total,
- } => {
- let k_percent = if kernels_total > 0 {
- (kernels * 100) / kernels_total
- } else {
- 0
- };
- t!("sync_status.tx_hashset_kernels_validation", "percent" => k_percent)
- }
- SyncStatus::TxHashsetSave | SyncStatus::TxHashsetDone => {
- t!("sync_status.tx_hashset_save")
- }
- SyncStatus::BodySync {
- current_height,
- highest_height,
- } => {
- if highest_height == 0 {
- t!("sync_status.body_sync")
- } else {
- let percent = current_height * 100 / highest_height;
- t!("sync_status.body_sync_percent", "percent" => percent)
- }
- }
- SyncStatus::Shutdown => t!("sync_status.shutdown"),
- }
-}
-
-
diff --git a/src/gui/views/network_metrics.rs b/src/gui/views/network_metrics.rs
index 03cb201..d370ffd 100644
--- a/src/gui/views/network_metrics.rs
+++ b/src/gui/views/network_metrics.rs
@@ -18,7 +18,7 @@ use egui::{RichText, ScrollArea, Spinner, Widget};
use grin_servers::DiffBlock;
use crate::gui::colors::{COLOR_DARK, COLOR_GRAY, COLOR_GRAY_LIGHT};
-use crate::gui::icons::{AT, CALENDAR_PLUS, COINS, CUBE, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
+use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER};
use crate::gui::views::{NetworkTab, View};
use crate::node::Node;
@@ -43,8 +43,8 @@ impl NetworkTab for NetworkMetrics {
&self.title
}
- fn ui(&mut self, ui: &mut egui::Ui, node: &mut Node) {
- let server_stats = node.state.get_stats();
+ fn ui(&mut self, ui: &mut egui::Ui) {
+ let server_stats = Node::get_stats();
// Show loading widget if server is not working or difficulty height is zero.
if !server_stats.is_some() || server_stats.as_ref().unwrap().diff_stats.height == 0 {
ui.centered_and_justified(|ui| {
@@ -86,7 +86,7 @@ impl NetworkTab for NetworkMetrics {
});
ui.add_space(6.0);
- // Show difficulty window info
+ // Show difficulty adjustment window info
ui.vertical_centered_justified(|ui| {
let title = t!("difficulty_at_window", "size" => stats.diff_stats.window_size);
View::sub_title(ui, format!("{} {}", HOURGLASS_MEDIUM, title));
@@ -114,28 +114,32 @@ impl NetworkTab for NetworkMetrics {
});
ui.add_space(6.0);
- // Draw difficulty window blocks
+ // Show difficulty adjustment window blocks
let blocks_size = stats.diff_stats.last_blocks.len();
- ScrollArea::vertical().auto_shrink([false; 2]).stick_to_bottom(true).show_rows(
- ui,
- DIFF_BLOCK_UI_HEIGHT,
- blocks_size,
- |ui, row_range| {
- for index in row_range {
- let db = stats.diff_stats.last_blocks.get(index).unwrap();
- let rounding = if blocks_size == 1 {
- [true, true]
- } else if index == 0 {
- [true, false]
- } else if index == blocks_size - 1 {
- [false, true]
- } else {
- [false, false]
- };
- draw_diff_block(ui, db, rounding)
- }
- },
- );
+ ScrollArea::vertical()
+ .auto_shrink([false; 2])
+ .stick_to_bottom(true)
+ .id_source("diff_scroll")
+ .show_rows(
+ ui,
+ DIFF_BLOCK_UI_HEIGHT,
+ blocks_size,
+ |ui, row_range| {
+ for index in row_range {
+ let db = stats.diff_stats.last_blocks.get(index).unwrap();
+ let rounding = if blocks_size == 1 {
+ [true, true]
+ } else if index == 0 {
+ [true, false]
+ } else if index == blocks_size - 1 {
+ [false, true]
+ } else {
+ [false, false]
+ };
+ draw_diff_block(ui, db, rounding)
+ }
+ },
+ );
}
}
diff --git a/src/gui/views/network_mining.rs b/src/gui/views/network_mining.rs
new file mode 100644
index 0000000..2035f55
--- /dev/null
+++ b/src/gui/views/network_mining.rs
@@ -0,0 +1,13 @@
+// Copyright 2023 The Grim Developers
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
diff --git a/src/gui/views/network_node.rs b/src/gui/views/network_node.rs
index a62fe90..6f73129 100644
--- a/src/gui/views/network_node.rs
+++ b/src/gui/views/network_node.rs
@@ -14,7 +14,6 @@
use eframe::epaint::Stroke;
use egui::{Color32, RichText, Rounding, ScrollArea, Spinner, Widget};
-use grin_servers::common::stats::TxStats;
use grin_servers::PeerStats;
use crate::gui::colors::{COLOR_DARK, COLOR_GRAY, COLOR_GRAY_LIGHT};
@@ -39,8 +38,8 @@ impl NetworkTab for NetworkNode {
&self.title
}
- fn ui(&mut self, ui: &mut egui::Ui, node: &mut Node) {
- let server_stats = node.state.get_stats();
+ fn ui(&mut self, ui: &mut egui::Ui) {
+ let server_stats = Node::get_stats();
if !server_stats.is_some() {
ui.centered_and_justified(|ui| {
Spinner::new().size(42.0).color(COLOR_GRAY).ui(ui);
@@ -80,9 +79,9 @@ impl NetworkTab for NetworkNode {
[false, false, true, false]);
});
columns[1].vertical_centered(|ui| {
- let ts = stats.header_stats.latest_timestamp;
+ let h_ts = stats.header_stats.latest_timestamp;
View::rounded_box(ui,
- format!("{}", ts.format("%d/%m/%Y %H:%M")),
+ format!("{}", h_ts.format("%d/%m/%Y %H:%M")),
t!("time_utc"),
[false, false, false, true]);
});
@@ -116,9 +115,9 @@ impl NetworkTab for NetworkNode {
[false, false, true, false]);
});
columns[1].vertical_centered(|ui| {
- let ts = stats.chain_stats.latest_timestamp;
+ let b_ts = stats.chain_stats.latest_timestamp;
View::rounded_box(ui,
- format!("{}", ts.format("%d/%m/%Y %H:%M")),
+ format!("{}", b_ts.format("%d/%m/%Y %H:%M")),
t!("time_utc"),
[false, false, false, true]);
});
@@ -265,9 +264,7 @@ fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) {
});
// Add space after last item
- if !rounding[0] && rounding[1] {
- ui.add_space(5.0);
- } else if rounding[0] && rounding[1] {
- ui.add_space(2.0);
+ if rounding[1] {
+ ui.add_space(3.0);
}
}
\ No newline at end of file
diff --git a/src/node/mod.rs b/src/node/mod.rs
index 75a956b..a521996 100644
--- a/src/node/mod.rs
+++ b/src/node/mod.rs
@@ -14,5 +14,4 @@
mod node;
-pub use self::node::Node;
-pub use self::node::NodeState;
\ No newline at end of file
+pub use self::node::Node;
\ No newline at end of file
diff --git a/src/node/node.rs b/src/node/node.rs
index 62ae4ef..9e5881f 100644
--- a/src/node/node.rs
+++ b/src/node/node.rs
@@ -25,50 +25,20 @@ use grin_config::config;
use grin_core::global;
use grin_core::global::ChainTypes;
use grin_servers::{Server, ServerStats};
+use jni::objects::JString;
+use jni::sys::jstring;
+use lazy_static::lazy_static;
use log::info;
+lazy_static! {
+ static ref NODE_STATE: Arc = Arc::new(Node::default());
+}
+
pub struct Node {
- /// Node state updated from the separate thread
- pub(crate) state: Arc,
-}
-
-impl Node {
- /// Instantiate new node with provided chain type, start server if needed
- pub fn new(chain_type: ChainTypes, start: bool) -> Self {
- let state = Arc::new(NodeState::new(chain_type));
- if start {
- start_server_thread(state.clone(), chain_type);
- }
- Self { state }
- }
-
- /// Stop server
- pub fn stop(&self) {
- self.state.stop_needed.store(true, Ordering::Relaxed);
- }
-
- /// Start server with provided chain type
- pub fn start(&self, chain_type: ChainTypes) {
- if !self.state.is_running() {
- start_server_thread(self.state.clone(), chain_type);
- }
- }
-
- /// Restart server or start when not running
- pub fn restart(&mut self) {
- if self.state.is_running() {
- self.state.restart_needed.store(true, Ordering::Relaxed);
- } else {
- self.start(*self.state.chain_type);
- }
- }
-}
-
-pub struct NodeState {
/// Data for UI
stats: Arc>>,
/// Chain type of launched server
- chain_type: Arc,
+ chain_type: Arc>,
/// Indicator if server is starting
starting: AtomicBool,
/// Thread flag to stop the server and start it again
@@ -77,102 +47,206 @@ pub struct NodeState {
stop_needed: AtomicBool,
}
-impl NodeState {
- /// Instantiate new node state with provided chain type and server state
- pub fn new(chain_type: ChainTypes) -> Self {
+impl Default for Node {
+ fn default() -> Self {
Self {
stats: Arc::new(RwLock::new(None)),
- chain_type: Arc::new(chain_type),
+ chain_type: Arc::new(RwLock::new(ChainTypes::Mainnet)),
+ starting: AtomicBool::new(false),
restart_needed: AtomicBool::new(false),
stop_needed: AtomicBool::new(false),
- starting: AtomicBool::new(false)
+ }
+ }
+}
+
+impl Node {
+ /// Stop server
+ pub fn stop() {
+ NODE_STATE.stop_needed.store(true, Ordering::Relaxed);
+ }
+
+ /// Start server with provided chain type
+ pub fn start(chain_type: ChainTypes) {
+ if !Self::is_running() {
+ let mut w_chain_type = NODE_STATE.chain_type.write().unwrap();
+ *w_chain_type = chain_type;
+ Self::start_server_thread();
+ }
+ }
+
+ /// Restart server with provided chain type
+ pub fn restart(chain_type: ChainTypes) {
+ if Self::is_running() {
+ let mut w_chain_type = NODE_STATE.chain_type.write().unwrap();
+ *w_chain_type = chain_type;
+ NODE_STATE.restart_needed.store(true, Ordering::Relaxed);
+ } else {
+ Node::start(chain_type);
}
}
/// Check if server is starting
- pub fn is_starting(&self) -> bool {
- self.starting.load(Ordering::Relaxed)
+ pub fn is_starting() -> bool {
+ NODE_STATE.starting.load(Ordering::Relaxed)
}
/// Check if server is running
- pub fn is_running(&self) -> bool {
- self.get_stats().is_some() || self.is_starting()
+ pub fn is_running() -> bool {
+ Self::get_stats().is_some() || Self::is_starting()
}
/// Check if server is stopping
- pub fn is_stopping(&self) -> bool {
- self.stop_needed.load(Ordering::Relaxed)
+ pub fn is_stopping() -> bool {
+ NODE_STATE.stop_needed.load(Ordering::Relaxed)
}
/// Check if server is restarting
- pub fn is_restarting(&self) -> bool {
- self.restart_needed.load(Ordering::Relaxed)
+ pub fn is_restarting() -> bool {
+ NODE_STATE.restart_needed.load(Ordering::Relaxed)
}
/// Get server stats
- pub fn get_stats(&self) -> RwLockReadGuard<'_, Option> {
- self.stats.read().unwrap()
+ pub fn get_stats() -> RwLockReadGuard<'static, Option> {
+ NODE_STATE.stats.read().unwrap()
}
/// Get server sync status, empty when server is not running
- pub fn get_sync_status(&self) -> Option {
+ pub fn get_sync_status() -> Option {
// return Shutdown status when node is stopping
- if self.is_stopping() {
+ if Self::is_stopping() {
return Some(SyncStatus::Shutdown)
}
// return Initial status when node is starting
- if self.is_starting() {
+ if Self::is_starting() {
return Some(SyncStatus::Initial)
}
- let stats = self.get_stats();
+ let stats = Self::get_stats();
// return sync status when server is running (stats are not empty)
if stats.is_some() {
return Some(stats.as_ref().unwrap().sync_status)
}
None
}
-}
-/// Start a thread to launch server and update node state with server stats
-fn start_server_thread(state: Arc, chain_type: ChainTypes) -> JoinHandle<()> {
- thread::spawn(move || {
- state.starting.store(true, Ordering::Relaxed);
- let mut server = start_server(&chain_type);
- let mut first_start = true;
+ /// Start a thread to launch server and update state with server stats
+ fn start_server_thread() -> JoinHandle<()> {
+ thread::spawn(move || {
+ NODE_STATE.starting.store(true, Ordering::Relaxed);
- loop {
- if state.is_restarting() {
- server.stop();
+ let mut server = start_server(&NODE_STATE.chain_type.read().unwrap());
+ let mut first_start = true;
- // Create new server with current chain type
- server = start_server(&state.chain_type);
+ loop {
+ if Self::is_restarting() {
+ server.stop();
- state.restart_needed.store(false, Ordering::Relaxed);
- } else if state.is_stopping() {
- server.stop();
+ // Create new server with current chain type
+ server = start_server(&NODE_STATE.chain_type.read().unwrap());
- let mut w_stats = state.stats.write().unwrap();
- *w_stats = None;
+ NODE_STATE.restart_needed.store(false, Ordering::Relaxed);
+ } else if Self::is_stopping() {
+ server.stop();
- state.stop_needed.store(false, Ordering::Relaxed);
- break;
- } else {
- let stats = server.get_server_stats();
- if stats.is_ok() {
- let mut w_stats = state.stats.write().unwrap();
- *w_stats = Some(stats.as_ref().ok().unwrap().clone());
+ let mut w_stats = NODE_STATE.stats.write().unwrap();
+ *w_stats = None;
- if first_start {
- state.starting.store(false, Ordering::Relaxed);
- first_start = false;
+ NODE_STATE.stop_needed.store(false, Ordering::Relaxed);
+ break;
+ } else {
+ let stats = server.get_server_stats();
+ if stats.is_ok() {
+ let mut w_stats = NODE_STATE.stats.write().unwrap();
+ *w_stats = Some(stats.as_ref().ok().unwrap().clone());
+
+ if first_start {
+ NODE_STATE.starting.store(false, Ordering::Relaxed);
+ first_start = false;
+ }
}
}
+ thread::sleep(Duration::from_millis(300));
}
- thread::sleep(Duration::from_millis(300));
+ })
+ }
+
+ pub fn get_sync_status_text(sync_status: Option) -> String {
+ if Node::is_restarting() {
+ return t!("server_restarting")
}
- })
+
+ if sync_status.is_none() {
+ return t!("server_down")
+ }
+
+ match sync_status.unwrap() {
+ SyncStatus::Initial => t!("sync_status.initial"),
+ SyncStatus::NoSync => t!("sync_status.no_sync"),
+ SyncStatus::AwaitingPeers(_) => t!("sync_status.awaiting_peers"),
+ SyncStatus::HeaderSync {
+ sync_head,
+ highest_height,
+ ..
+ } => {
+ if highest_height == 0 {
+ t!("sync_status.header_sync")
+ } else {
+ let percent = sync_head.height * 100 / highest_height;
+ t!("sync_status.header_sync_percent", "percent" => percent)
+ }
+ }
+ SyncStatus::TxHashsetDownload(stat) => {
+ if stat.total_size > 0 {
+ let percent = stat.downloaded_size * 100 / stat.total_size;
+ t!("sync_status.tx_hashset_download_percent", "percent" => percent)
+ } else {
+ t!("sync_status.tx_hashset_download")
+ }
+ }
+ SyncStatus::TxHashsetSetup => {
+ t!("sync_status.tx_hashset_setup")
+ }
+ SyncStatus::TxHashsetRangeProofsValidation {
+ rproofs,
+ rproofs_total,
+ } => {
+ let r_percent = if rproofs_total > 0 {
+ (rproofs * 100) / rproofs_total
+ } else {
+ 0
+ };
+ t!("sync_status.tx_hashset_range_proofs_validation", "percent" => r_percent)
+ }
+ SyncStatus::TxHashsetKernelsValidation {
+ kernels,
+ kernels_total,
+ } => {
+ let k_percent = if kernels_total > 0 {
+ (kernels * 100) / kernels_total
+ } else {
+ 0
+ };
+ t!("sync_status.tx_hashset_kernels_validation", "percent" => k_percent)
+ }
+ SyncStatus::TxHashsetSave | SyncStatus::TxHashsetDone => {
+ t!("sync_status.tx_hashset_save")
+ }
+ SyncStatus::BodySync {
+ current_height,
+ highest_height,
+ } => {
+ if highest_height == 0 {
+ t!("sync_status.body_sync")
+ } else {
+ let percent = current_height * 100 / highest_height;
+ t!("sync_status.body_sync_percent", "percent" => percent)
+ }
+ }
+ SyncStatus::Shutdown => t!("sync_status.shutdown"),
+ }
+ }
+
}
/// Start server with provided chain type
@@ -280,4 +354,32 @@ fn start_server(chain_type: &ChainTypes) -> Server {
}
server_result.unwrap()
+}
+
+#[allow(dead_code)]
+#[cfg(target_os = "android")]
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn Java_mw_gri_android_BackgroundService_getSyncStatusText(
+ _env: jni::JNIEnv,
+ _class: jni::objects::JObject,
+ _activity: jni::objects::JObject,
+) -> jstring {
+ let sync_status = Node::get_sync_status();
+ let status_text = Node::get_sync_status_text(sync_status);
+ let j_text = _env.new_string(status_text);
+ return j_text.unwrap().into_raw();
+}
+
+#[allow(dead_code)]
+#[cfg(target_os = "android")]
+#[allow(non_snake_case)]
+#[no_mangle]
+pub extern "C" fn Java_mw_gri_android_BackgroundService_getSyncTitle(
+ _env: jni::JNIEnv,
+ _class: jni::objects::JObject,
+ _activity: jni::objects::JObject,
+) -> jstring {
+ let j_text = _env.new_string(t!("integrated_node"));
+ return j_text.unwrap().into_raw();
}
\ No newline at end of file