From 22c5b945c6c47360dd967f9c83942988ba5e6d4c Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 23 May 2023 01:46:42 +0300 Subject: [PATCH] android: show sync progress at notification, prevent app to sleep, temp fix of hanging after closing from recent apps --- Cargo.lock | 1 + Cargo.toml | 3 +- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 27 +- .../mw/gri/android/BackgroundService.java | 131 +++++++++ .../java/mw/gri/android/MainActivity.java | 24 +- .../main/res/drawable-hdpi/ic_stat_name.png | Bin 0 -> 984 bytes .../main/res/drawable-mdpi/ic_stat_name.png | Bin 0 -> 615 bytes .../main/res/drawable-xhdpi/ic_stat_name.png | Bin 0 -> 1398 bytes .../main/res/drawable-xxhdpi/ic_stat_name.png | Bin 0 -> 2185 bytes .../res/drawable-xxxhdpi/ic_stat_name.png | Bin 0 -> 2978 bytes build_and_run.sh | 2 +- src/grim.rs | 2 +- src/gui/views/mod.rs | 6 +- src/gui/views/network.rs | 99 +------ src/gui/views/network_metrics.rs | 54 ++-- src/gui/views/network_mining.rs | 13 + src/gui/views/network_node.rs | 19 +- src/node/mod.rs | 3 +- src/node/node.rs | 274 ++++++++++++------ 20 files changed, 426 insertions(+), 234 deletions(-) create mode 100644 app/src/main/java/mw/gri/android/BackgroundService.java create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_name.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_name.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_name.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_name.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_stat_name.png create mode 100644 src/gui/views/network_mining.rs 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 0000000000000000000000000000000000000000..c4385b478860e7d1852731602d1244bea39de9ce GIT binary patch literal 984 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 YKL?F?^W@3y=>Px#07*qoM6N<$g8!r4R{#J2 literal 0 HcmV?d00001 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