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