From d84bdb6fb59a31a8a13f970e6c5718bb041b10f3 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Fri, 11 Aug 2023 01:20:41 +0300 Subject: [PATCH] wallets: optimize loading, list refactoring, add wallet closing state, wallet data state and tx loading progress --- src/gui/views/wallets/content.rs | 56 ++- src/gui/views/wallets/wallet/content.rs | 72 ++-- src/gui/views/wallets/wallet/settings.rs | 6 +- src/wallet/list.rs | 112 ++++++ src/wallet/mod.rs | 5 +- src/wallet/types.rs | 25 +- src/wallet/wallets.rs | 491 +++++++++++++---------- 7 files changed, 486 insertions(+), 281 deletions(-) create mode 100644 src/wallet/list.rs diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index fa49401..2b84c27 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -14,7 +14,6 @@ use egui::{Align, Align2, Layout, Margin, RichText, Rounding, ScrollArea, TextStyle, Widget}; use egui_extras::{Size, StripBuilder}; -use grin_core::global::ChainTypes; use crate::AppConfig; use crate::gui::Colors; @@ -24,14 +23,12 @@ use crate::gui::views::{Modal, Root, TitlePanel, View}; use crate::gui::views::types::{ModalContainer, ModalPosition, TitleType}; use crate::gui::views::wallets::creation::WalletCreation; use crate::gui::views::wallets::WalletContent; -use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet, Wallets}; +use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet, WalletList}; /// Wallets content. pub struct WalletsContent { - /// Chain type for loaded wallets. - chain_type: ChainTypes, /// Loaded list of wallets. - wallets: Wallets, + wallets: WalletList, /// Password to open wallet for [`Modal`]. pass_edit: String, @@ -58,8 +55,7 @@ const OPEN_WALLET_MODAL: &'static str = "open_wallet_modal"; impl Default for WalletsContent { fn default() -> Self { Self { - chain_type: AppConfig::chain_type(), - wallets: Wallets::default(), + wallets: WalletList::default(), pass_edit: "".to_string(), hide_pass: true, wrong_pass: false, @@ -99,13 +95,8 @@ impl WalletsContent { // Draw modal content for current ui container. self.current_modal_ui(ui, frame, cb); - // Setup list of wallets if chain type was changed. - let chain_type = AppConfig::chain_type(); - if self.chain_type != chain_type { - self.wallets.reinit(chain_type); - self.chain_type = chain_type; - } - let empty_list = self.wallets.list.is_empty(); + let wallets = self.wallets.list(); + let empty_list = wallets.is_empty(); // Setup wallet content flags. let create_wallet = self.creation_content.can_go_back(); @@ -123,7 +114,7 @@ impl WalletsContent { // Show wallet panel content. egui::SidePanel::right("wallet_panel") .resizable(false) - .min_width(wallet_panel_width) + .exact_width(wallet_panel_width) .frame(egui::Frame { fill: if empty_list && !create_wallet || (dual_panel && show_wallet && !self.show_wallets_at_dual_panel) { @@ -149,7 +140,7 @@ impl WalletsContent { self.wallets.add(wallet); }); } else { - for mut wallet in self.wallets.list.clone() { + for mut wallet in wallets.clone() { // Show content for selected wallet. if self.wallets.is_selected(wallet.config.id) { // Setup wallet content width. @@ -205,13 +196,14 @@ impl WalletsContent { // Setup flag to show wallet creation button if wallet is not showing // at non-dual panel mode or showing at dual panel mode. - let show_creation_btn + let show_creation_button = (!show_wallet && !dual_panel) || (dual_panel && show_wallet); // Show list of wallets. - let scroll = self.wallet_list_ui(ui, dual_panel, show_creation_btn, cb); + let scroll = + self.wallet_list_ui(ui, wallets, dual_panel, show_creation_button, cb); - if show_creation_btn { + if show_creation_button { // Setup right margin for button. let mut right_margin = if dual_panel { wallet_panel_width } else { 0.0 }; if scroll { right_margin += 6.0 } @@ -304,6 +296,7 @@ impl WalletsContent { /// Draw list of wallets. Returns `true` if scroller is showing. fn wallet_list_ui(&mut self, ui: &mut egui::Ui, + wallets: Vec, dual_panel: bool, show_creation_btn: bool, cb: &dyn PlatformCallbacks) -> bool { @@ -331,7 +324,7 @@ impl WalletsContent { rect.set_width(width); ui.allocate_ui(rect.size(), |ui| { - for mut wallet in self.wallets.list.clone() { + for mut wallet in wallets { // Draw wallet list item. self.wallet_item_ui(ui, &mut wallet, cb); ui.add_space(5.0); @@ -386,14 +379,17 @@ impl WalletsContent { self.wallets.select(Some(id)); }); } - // Show button to close opened wallet. - View::item_button(ui, if !is_selected { - Rounding::none() - } else { - View::item_rounding(0, 1, true) - }, LOCK_KEY, None, || { - let _ = wallet.close(); - }); + + // Show button to close opened wallet if wallet is not loading. + if !wallet.is_closing() { + View::item_button(ui, if !is_selected { + Rounding::none() + } else { + View::item_rounding(0, 1, true) + }, LOCK_KEY, None, || { + wallet.close(); + }); + } } let layout_size = ui.available_size(); @@ -420,7 +416,9 @@ impl WalletsContent { // Setup wallet status text. let status_text = if wallet.is_open() { - if wallet.get_info().is_none() { + if wallet.is_closing() { + format!("{} {}", SPINNER, t!("wallets.wallet_closing")) + } else if wallet.get_data().is_none() { if wallet.loading_error() { format!("{} {}", WARNING_CIRCLE, t!("loading_error")) } else { diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index 52b502d..92dd47f 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -79,7 +79,7 @@ impl WalletContent { }); // Refresh content after 1 second for loaded wallet. - if wallet.get_info().is_some() { + if wallet.get_data().is_some() { ui.ctx().request_repaint_after(Duration::from_millis(1000)); } else { ui.ctx().request_repaint(); @@ -123,10 +123,13 @@ impl WalletContent { /// Content to draw when wallet is loading, returns `true` if wallet is not ready. pub fn loading_ui(ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet) -> bool { - if wallet.config.ext_conn_id.is_none() { + if wallet.is_closing() { + Self::loading_progress_ui(ui, wallet); + return true; + } else if wallet.config.ext_conn_id.is_none() { if !Node::is_running() || Node::is_stopping() { let dual_panel_root = Root::is_dual_panel_mode(frame); - View::center_content(ui, if !dual_panel_root { 162.0 } else { 96.0 }, |ui| { + View::center_content(ui, 108.0, |ui| { let text = t!("wallets.enable_node", "settings" => GEAR_FINE); ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); ui.add_space(8.0); @@ -141,41 +144,62 @@ impl WalletContent { } }); return true - } else if wallet.get_info().is_none() { - Self::progress_ui(ui, wallet); + } else if wallet.loading_error() + && Node::get_sync_status() == Some(SyncStatus::NoSync) { + Self::loading_error_ui(ui, wallet); + return true; + } else if wallet.get_data().is_none() { + Self::loading_progress_ui(ui, wallet); return true; } - } else if wallet.get_info().is_none() { - // Show error message with button to retry on wallet loading error or loading progress. + } else if wallet.get_data().is_none() { if wallet.loading_error() { - View::center_content(ui, 162.0, |ui| { - let text = t!("wallets.wallet_loading_err", "settings" => GEAR_FINE); - ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); - ui.add_space(8.0); - let retry_text = format!("{} {}", REPEAT, t!("retry")); - View::button(ui, retry_text, Colors::GOLD, || { - wallet.set_loading_error(false); - }); - }); + Self::loading_error_ui(ui, wallet); } else { - Self::progress_ui(ui, wallet); + Self::loading_progress_ui(ui, wallet); } return true; } false } - /// Draw wallet loading progress. - fn progress_ui(ui: &mut egui::Ui, wallet: &Wallet) { + /// Draw wallet loading error content. + fn loading_error_ui(ui: &mut egui::Ui, wallet: &Wallet) { + View::center_content(ui, 108.0, |ui| { + let text = t!("wallets.wallet_loading_err", "settings" => GEAR_FINE); + ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); + ui.add_space(8.0); + let retry_text = format!("{} {}", REPEAT, t!("retry")); + View::button(ui, retry_text, Colors::GOLD, || { + wallet.set_loading_error(false); + }); + }); + } + + /// Draw wallet loading progress content. + fn loading_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) { View::center_content(ui, 162.0, |ui| { View::big_loading_spinner(ui); ui.add_space(18.0); // Setup loading progress text. - let progress = wallet.loading_progress(); - let text = if progress == 0 { - t!("wallets.wallet_loading") - } else { - format!("{}: {}%", t!("wallets.wallet_loading"), progress) + let text = { + let info_progress = wallet.info_loading_progress(); + if wallet.is_closing() { + t!("wallets.wallet_closing") + } else if info_progress != 100 { + if info_progress == 0 { + t!("wallets.wallet_loading") + } else { + format!("{}: {}%", t!("wallets.wallet_loading"), info_progress) + } + } else { + let tx_progress = wallet.txs_loading_progress(); + if tx_progress == 0 { + t!("wallets.tx_loading") + } else { + format!("{}: {}%", t!("wallets.tx_loading"), tx_progress) + } + } }; ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); }); diff --git a/src/gui/views/wallets/wallet/settings.rs b/src/gui/views/wallets/wallet/settings.rs index d57920c..f1c44e3 100644 --- a/src/gui/views/wallets/wallet/settings.rs +++ b/src/gui/views/wallets/wallet/settings.rs @@ -25,6 +25,10 @@ impl WalletTab for WalletSettings { WalletTabType::Settings } - fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet, cb: &dyn PlatformCallbacks) { + fn ui(&mut self, + ui: &mut egui::Ui, + frame: &mut eframe::Frame, + wallet: &Wallet, + cb: &dyn PlatformCallbacks) { } } \ No newline at end of file diff --git a/src/wallet/list.rs b/src/wallet/list.rs new file mode 100644 index 0000000..9b219b8 --- /dev/null +++ b/src/wallet/list.rs @@ -0,0 +1,112 @@ +// 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. + +use grin_core::global::ChainTypes; +use grin_wallet_libwallet::Error; + +use crate::AppConfig; +use crate::wallet::{Wallet, WalletConfig}; + +/// Wrapper for [`Wallet`] list. +pub struct WalletList { + /// List of wallets. + list: Vec, + /// Selected [`Wallet`] identifier. + selected_id: Option, +} + +impl Default for WalletList { + fn default() -> Self { + Self { + list: Self::init(), + selected_id: None + } + } +} + +impl WalletList { + /// Initialize [`Wallet`] list from base directory. + fn init() -> Vec { + let mut wallets = Vec::new(); + let chain_types = vec![ChainTypes::Mainnet, ChainTypes::Testnet]; + for chain in chain_types { + let wallets_dir = WalletConfig::get_base_path(chain); + // Load wallets from base directory. + for dir in wallets_dir.read_dir().unwrap() { + let wallet_dir = dir.unwrap().path(); + if wallet_dir.is_dir() { + let wallet = Wallet::init(wallet_dir); + if let Some(w) = wallet { + wallets.push(w); + } + } + } + } + wallets + } + + /// Get wallet list for current [`ChainTypes`]. + pub fn list(&self) -> Vec { + let chain_type = AppConfig::chain_type(); + self.list.iter().cloned() + .filter(|w| w.config.chain_type == chain_type) + .collect::>() + } + + /// Add created [`Wallet`] to the list. + pub fn add(&mut self, wallet: Wallet) { + self.selected_id = Some(wallet.config.id); + self.list.insert(0, wallet); + } + + /// Select [`Wallet`] with provided identifier. + pub fn select(&mut self, id: Option) { + self.selected_id = id; + } + + /// Get selected [`Wallet`] name. + pub fn selected_name(&self) -> String { + for w in &self.list { + if Some(w.config.id) == self.selected_id { + return w.config.name.to_owned() + } + } + t!("wallets.unlocked") + } + + /// Check if [`Wallet`] is selected for provided identifier. + pub fn is_selected(&self, id: i64) -> bool { + return Some(id) == self.selected_id; + } + + /// Check if selected [`Wallet`] is open. + pub fn is_selected_open(&self) -> bool { + for w in &self.list { + if Some(w.config.id) == self.selected_id { + return w.is_open() + } + } + false + } + + /// Open selected [`Wallet`]. + pub fn open_selected(&mut self, password: String) -> Result<(), Error> { + for w in self.list.iter_mut() { + if Some(w.config.id) == self.selected_id { + return w.open(password.clone()); + } + } + Err(Error::GenericError("Wallet is not selected".to_string())) + } +} \ No newline at end of file diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 193931d..3f2e25b 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -28,4 +28,7 @@ mod wallets; pub use wallets::*; mod config; -pub use config::*; \ No newline at end of file +pub use config::*; + +mod list; +pub use list::*; \ No newline at end of file diff --git a/src/wallet/types.rs b/src/wallet/types.rs index 2daed83..3c1b4ec 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use grin_keychain::ExtKeychain; use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient}; -use grin_wallet_libwallet::WalletInst; +use grin_wallet_libwallet::{TxLogEntry, WalletInfo, WalletInst}; use parking_lot::Mutex; /// Mnemonic phrase setup mode. @@ -64,6 +64,15 @@ impl PhraseSize { } } +/// Wallet connection method. +#[derive(PartialEq)] +pub enum ConnectionMethod { + /// Integrated node. + Integrated, + /// External node, contains connection identifier. + External(i64) +} + /// Wallet instance type. pub type WalletInstance = Arc< Mutex< @@ -78,11 +87,11 @@ pub type WalletInstance = Arc< >, >; -/// Wallet node connection method type. -#[derive(PartialEq)] -pub enum ConnectionMethod { - /// Integrated node. - Integrated, - /// External node, contains connection identifier. - External(i64) +/// Contains wallet data to show. +#[derive(Clone)] +pub struct WalletData { + /// Wallet balance. + pub info: WalletInfo, + /// Transactions. + pub txs: Vec } \ No newline at end of file diff --git a/src/wallet/wallets.rs b/src/wallet/wallets.rs index 5558f62..fff8859 100644 --- a/src/wallet/wallets.rs +++ b/src/wallet/wallets.rs @@ -14,115 +14,24 @@ use std::path::PathBuf; use std::sync::{Arc, mpsc, RwLock}; -use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU8, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::thread; +use std::thread::Thread; use std::time::Duration; use grin_chain::SyncStatus; use grin_core::global; -use grin_core::global::ChainTypes; use grin_keychain::{ExtKeychain, Keychain}; use grin_util::secp::SecretKey; use grin_util::types::ZeroingString; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient}; -use grin_wallet_libwallet::{Error, NodeClient, StatusMessage, WalletBackend, WalletInfo, WalletInst, WalletLCProvider}; -use grin_wallet_libwallet::api_impl::owner::retrieve_summary_info; +use grin_wallet_libwallet::{Error, NodeClient, StatusMessage, WalletInst, WalletLCProvider}; +use grin_wallet_libwallet::api_impl::owner::{retrieve_summary_info, retrieve_txs}; use parking_lot::Mutex; -use crate::AppConfig; use crate::node::{Node, NodeConfig}; use crate::wallet::{ConnectionsConfig, ExternalConnection, WalletConfig}; -use crate::wallet::types::{ConnectionMethod, WalletInstance}; - -/// [`Wallet`] list wrapper. -pub struct Wallets { - /// List of wallets. - pub(crate) list: Vec, - /// Selected [`Wallet`] identifier. - selected_id: Option, -} - -impl Default for Wallets { - fn default() -> Self { - Self { - list: Self::init(AppConfig::chain_type()), - selected_id: None - } - } -} - -impl Wallets { - /// Initialize [`Wallet`] list from base directory for provided [`ChainType`]. - fn init(chain_type: ChainTypes) -> Vec { - let mut wallets = Vec::new(); - let wallets_dir = WalletConfig::get_base_path(chain_type); - // Load wallets from base directory. - for dir in wallets_dir.read_dir().unwrap() { - let wallet_dir = dir.unwrap().path(); - if wallet_dir.is_dir() { - let wallet = Wallet::init(wallet_dir); - if let Some(w) = wallet { - wallets.push(w); - } - } - } - wallets - } - - /// Reinitialize [`Wallet`] list for provided [`ChainTypes`]. - pub fn reinit(&mut self, chain_type: ChainTypes) { - for w in self.list.iter_mut() { - let _ = w.close(); - } - self.list = Self::init(chain_type); - } - - /// Add created [`Wallet`] to the list. - pub fn add(&mut self, wallet: Wallet) { - self.selected_id = Some(wallet.config.id); - self.list.insert(0, wallet); - } - - /// Select [`Wallet`] with provided identifier. - pub fn select(&mut self, id: Option) { - self.selected_id = id; - } - - /// Get selected [`Wallet`] name. - pub fn selected_name(&self) -> String { - for w in &self.list { - if Some(w.config.id) == self.selected_id { - return w.config.name.to_owned() - } - } - t!("wallets.unlocked") - } - - /// Check if [`Wallet`] is selected for provided identifier. - pub fn is_selected(&self, id: i64) -> bool { - return Some(id) == self.selected_id; - } - - /// Check if selected [`Wallet`] is open. - pub fn is_selected_open(&self) -> bool { - for w in &self.list { - if Some(w.config.id) == self.selected_id { - return w.is_open() - } - } - false - } - - /// Open selected [`Wallet`]. - pub fn open_selected(&mut self, password: String) -> Result<(), Error> { - for w in self.list.iter_mut() { - if Some(w.config.id) == self.selected_id { - return w.open(password.clone()); - } - } - Err(Error::GenericError("Wallet is not selected".to_string())) - } -} +use crate::wallet::types::{ConnectionMethod, WalletData, WalletInstance}; /// Contains wallet instance and handles wallet commands. #[derive(Clone)] @@ -132,19 +41,25 @@ pub struct Wallet { /// Wallet configuration. pub config: WalletConfig, - /// Identifier for launched wallet thread. - thread_id: Arc, + /// Wallet updating thread. + thread: Arc>>, /// Flag to check if wallet is open. is_open: Arc, + /// Flag to check if wallet is loading. + closing: Arc, /// Error on wallet loading. loading_error: Arc, - /// Loading progress in percents - loading_progress: Arc, + /// Info loading progress in percents + info_loading_progress: Arc, + /// Transactions loading progress in percents + txs_loading_progress: Arc, - /// Wallet balance information. - info: Arc>> + /// Wallet data. + data: Arc>>, + /// Attempts amount to update wallet data. + data_update_attempts: Arc } impl Wallet { @@ -153,11 +68,14 @@ impl Wallet { Self { instance, config, - thread_id: Arc::new(AtomicI64::new(0)), + thread: Arc::from(RwLock::new(None)), is_open: Arc::from(AtomicBool::new(false)), + closing: Arc::new(AtomicBool::new(false)), loading_error: Arc::from(AtomicBool::new(false)), - loading_progress: Arc::new(AtomicU8::new(0)), - info: Arc::new(RwLock::new(None)), + info_loading_progress: Arc::from(AtomicU8::new(0)), + txs_loading_progress: Arc::from(AtomicU8::new(0)), + data: Arc::from(RwLock::new(None)), + data_update_attempts: Arc::new(AtomicU8::new(0)) } } @@ -185,7 +103,7 @@ impl Wallet { } /// Initialize [`Wallet`] from provided data path. - fn init(data_path: PathBuf) -> Option { + pub fn init(data_path: PathBuf) -> Option { let wallet_config = WalletConfig::load(data_path.clone()); if let Some(config) = wallet_config { if let Ok(instance) = Self::create_wallet_instance(config.clone()) { @@ -196,10 +114,8 @@ impl Wallet { } /// Reinitialize [`WalletInstance`] to apply new [`WalletConfig`]. - pub fn reinit(&mut self) -> Result<(), Error> { - self.close()?; - self.instance = Self::create_wallet_instance(self.config.clone())?; - Ok(()) + pub fn reinit(&self) { + //TODO: Reinit wallet. } /// Create [`WalletInstance`] from provided [`WalletConfig`]. @@ -254,39 +170,38 @@ impl Wallet { Ok(Arc::new(Mutex::new(wallet))) } - /// Open the wallet and start commands handling and updating info at separate thread. + /// Open the wallet and start update the data at separate thread. pub fn open(&self, password: String) -> Result<(), Error> { let mut wallet_lock = self.instance.lock(); let lc = wallet_lock.lc_provider()?; - lc.close_wallet(None)?; - // Open the wallet. match lc.open_wallet(None, ZeroingString::from(password), false, false) { Ok(keychain) => { + // Start data updating if thread was not launched. + let mut thread_w = self.thread.write().unwrap(); + if thread_w.is_none() { + println!("create new thread"); + let thread = start_wallet(self.clone(), keychain.clone()); + *thread_w = Some(thread); + } else { + println!("unfreeze thread"); + thread_w.clone().unwrap().unpark(); + } self.is_open.store(true, Ordering::Relaxed); - // Start info updating and commands handling. - start_wallet(self.clone(), keychain); } Err(e) => return Err(e) } Ok(()) } - /// Get wallet thread identifier. - fn get_thread_id(&self) -> i64 { - self.thread_id.load(Ordering::Relaxed) + /// Get wallet transactions loading progress. + pub fn txs_loading_progress(&self) -> u8 { + self.txs_loading_progress.load(Ordering::Relaxed) } - /// Refresh wallet thread identifier. - fn new_thread_id(&self) -> i64 { - let id = chrono::Utc::now().timestamp(); - self.thread_id.store(id, Ordering::Relaxed); - id - } - - /// Get wallet loading progress after opening. - pub fn loading_progress(&self) -> u8 { - self.loading_progress.load(Ordering::Relaxed) + /// Get wallet info loading progress. + pub fn info_loading_progress(&self) -> u8 { + self.info_loading_progress.load(Ordering::Relaxed) } /// Check if wallet had an error on loading. @@ -299,121 +214,261 @@ impl Wallet { self.loading_error.store(error, Ordering::Relaxed); } + /// Get wallet data update attempts. + fn get_data_update_attempts(&self) -> u8 { + self.data_update_attempts.load(Ordering::Relaxed) + } + + /// Increment wallet data update attempts. + fn increment_data_update_attempts(&self) { + let mut attempts = self.get_data_update_attempts(); + attempts += 1; + self.data_update_attempts.store(attempts, Ordering::Relaxed); + } + + /// Reset wallet data update attempts. + fn reset_data_update_attempts(&self) { + self.data_update_attempts.store(0, Ordering::Relaxed); + } + /// Check if wallet was open. pub fn is_open(&self) -> bool { self.is_open.load(Ordering::Relaxed) } - /// Close the wallet. - pub fn close(&mut self) -> Result<(), Error> { - if self.is_open() { - let mut wallet_lock = self.instance.lock(); - let lc = wallet_lock.lc_provider()?; - lc.close_wallet(None)?; - // Clear wallet info. - let mut w_info = self.info.write().unwrap(); - *w_info = None; - // Mark wallet as not opened. - self.is_open.store(false, Ordering::Relaxed); - } - Ok(()) + /// Check if wallet is closing. + pub fn is_closing(&self) -> bool { + self.closing.load(Ordering::Relaxed) } - /// Get wallet balance info. - pub fn get_info(&self) -> Option { - let r_info = self.info.read().unwrap(); - r_info.clone() + /// Close the wallet. + pub fn close(&self) { + if !self.is_open() { + return; + } + self.closing.store(true, Ordering::Relaxed); + + // Close wallet at separate thread. + let wallet_close = self.clone(); + thread::spawn(move || { + // Close the wallet. + let _ = Self::close_wallet(&wallet_close.instance); + + // Clear wallet info. + let mut w_data = wallet_close.data.write().unwrap(); + *w_data = None; + + // Reset wallet loading values. + wallet_close.info_loading_progress.store(0, Ordering::Relaxed); + wallet_close.txs_loading_progress.store(0, Ordering::Relaxed); + wallet_close.set_loading_error(false); + wallet_close.reset_data_update_attempts(); + + // Mark wallet as not opened. + wallet_close.closing.store(false, Ordering::Relaxed); + wallet_close.is_open.store(false, Ordering::Relaxed); + }); + } + + /// Close provided [`WalletInstance`]. + fn close_wallet(instance: &WalletInstance) -> Result<(), Error> { + let mut wallet_lock = instance.lock(); + let lc = wallet_lock.lc_provider()?; + lc.close_wallet(None) + } + + /// Get wallet data. + pub fn get_data(&self) -> Option { + let r_data = self.data.read().unwrap(); + r_data.clone() } } -/// Delay in seconds to update wallet info. -const INFO_UPDATE_DELAY: Duration = Duration::from_millis(20 * 1000); +/// Delay in seconds to update wallet data every minute as average block time. +const DATA_UPDATE_DELAY: Duration = Duration::from_millis(20 * 1000); -/// Launch thread for commands handling and info updating after [`INFO_UPDATE_DELAY`]. -fn start_wallet(mut wallet: Wallet, keychain_mask: Option) { - // Setup initial loading values. - wallet.loading_progress.store(0, Ordering::Relaxed); - wallet.set_loading_error(false); +/// Number of attempts to update data after wallet opening before setting an error. +const DATA_UPDATE_ATTEMPTS: u8 = 10; - // Launch loop at separate thread to update wallet info. - let thread_id = wallet.new_thread_id(); +/// Launch thread to update wallet data. +fn start_wallet(wallet: Wallet, keychain: Option) -> Thread { + let wallet_update = wallet.clone(); thread::spawn(move || loop { - // Stop updating if wallet was closed or thread changed. - if !wallet.is_open() || thread_id != wallet.get_thread_id() { - break; + println!("start new cycle"); + // Stop updating if wallet was closed. + if !wallet_update.is_open() { + println!("finishing thread at start"); + let mut thread_w = wallet_update.thread.write().unwrap(); + *thread_w = None; + println!("finish at start complete"); + return; } - // Setup error when required integrated node is not enabled or skip when it's not ready. - if wallet.config.ext_conn_id.is_none() { - if !Node::is_running() { - wallet.set_loading_error(true); - } else if Node::get_sync_status() != Some(SyncStatus::NoSync) { - wallet.set_loading_error(false); - thread::sleep(Duration::from_millis(1000)); + // Set an error when required integrated node is not enabled and + // skip next cycle of update when node sync is not finished. + if wallet_update.config.ext_conn_id.is_none() { + wallet_update.set_loading_error(!Node::is_running() || Node::is_stopping()); + if !Node::is_running() || Node::get_sync_status() != Some(SyncStatus::NoSync) { + println!("integrated node wait"); + thread::park_timeout(Duration::from_millis(1000)); continue; } } - // Update wallet info if it was no loading error after several attempts. - if !wallet.loading_error() { - let wallet_scan = wallet.clone(); - let (tx, rx) = mpsc::channel::(); - // Update loading progress at separate thread. - thread::spawn(move || { - while let Ok(m) = rx.recv() { - // Stop updating if wallet was closed or maximum. - if !wallet_scan.is_open() || wallet_scan.get_thread_id() != thread_id { - return; - } - match m { - StatusMessage::UpdatingOutputs(_) => {} - StatusMessage::UpdatingTransactions(_) => {} - StatusMessage::FullScanWarn(_) => {} - StatusMessage::Scanning(_, progress) => { - wallet_scan.loading_progress.store(progress, Ordering::Relaxed); - } - StatusMessage::ScanningComplete(_) => { - wallet_scan.loading_progress.store(100, Ordering::Relaxed); - } - StatusMessage::UpdateWarning(_) => {} - } - } - }); - - // Retrieve wallet info. - match retrieve_summary_info( - wallet.instance.clone(), - keychain_mask.as_ref(), - &Some(tx), - true, - wallet.config.min_confirmations - ) { - Ok(info) => { - if wallet.loading_progress() != 100 { - wallet.set_loading_error(true); - } else { - let mut w_info = wallet.info.write().unwrap(); - *w_info = Some(info.1); - } - } - Err(_) => { - wallet.set_loading_error(true); - wallet.loading_progress.store(0, Ordering::Relaxed); - } - } + // Update wallet data if there is no error. + if !wallet_update.loading_error() { + update_wallet_data(&wallet_update, keychain.clone()); } // Stop updating if wallet was closed. - if !wallet.is_open() || wallet.get_thread_id() != thread_id { - break; + if !wallet_update.is_open() { + println!("finishing thread after updating"); + let mut thread_w = wallet_update.thread.write().unwrap(); + *thread_w = None; + println!("finishing after updating complete"); + return; } - // Repeat after default delay or after 1 second if update was not complete. - let delay = if wallet.loading_progress() == 100 && !wallet.loading_error() { - INFO_UPDATE_DELAY - } else { + // Repeat after default delay or after 1 second if update was not success. + let delay = if wallet_update.loading_error() + || wallet_update.get_data_update_attempts() != 0 { Duration::from_millis(1000) + } else { + DATA_UPDATE_DELAY }; - thread::sleep(delay); + println!("park for {}", delay.as_millis()); + thread::park_timeout(delay); + }).thread().clone() +} + +/// Handle [`WalletCommand::UpdateData`] command to update [`WalletData`]. +fn update_wallet_data(wallet: &Wallet, key: Option) { + println!("UPDATE start, attempts: {}", wallet.get_data_update_attempts()); + + let wallet_scan = wallet.clone(); + let (info_tx, info_rx) = mpsc::channel::(); + // Update info loading progress at separate thread. + thread::spawn(move || { + while let Ok(m) = info_rx.recv() { + println!("UPDATE INFO MESSAGE"); + match m { + StatusMessage::UpdatingOutputs(_) => {} + StatusMessage::UpdatingTransactions(_) => {} + StatusMessage::FullScanWarn(_) => {} + StatusMessage::Scanning(_, progress) => { + wallet_scan.info_loading_progress.store(progress, Ordering::Relaxed); + } + StatusMessage::ScanningComplete(_) => { + wallet_scan.info_loading_progress.store(100, Ordering::Relaxed); + } + StatusMessage::UpdateWarning(_) => {} + } + } }); + + // Retrieve wallet info. + match retrieve_summary_info( + wallet.instance.clone(), + key.as_ref(), + &Some(info_tx), + true, + wallet.config.min_confirmations + ) { + Ok(info) => { + // Do not retrieve txs if wallet was closed. + if !wallet.is_open() { + println!("UPDATE stop at retrieve_summary_info"); + return; + } + + // Add attempt if scanning was not complete + // or set an error on initial request. + if wallet.info_loading_progress() != 100 { + println!("UPDATE retrieve_summary_info was not completed"); + if wallet.get_data().is_none() { + wallet.set_loading_error(true); + } else { + wallet.increment_data_update_attempts(); + } + } else { + println!("UPDATE before retrieve_txs"); + + let wallet_txs = wallet.clone(); + let (txs_tx, txs_rx) = mpsc::channel::(); + // Update txs loading progress at separate thread. + thread::spawn(move || { + while let Ok(m) = txs_rx.recv() { + println!("UPDATE TXS MESSAGE"); + match m { + StatusMessage::UpdatingOutputs(_) => {} + StatusMessage::UpdatingTransactions(_) => {} + StatusMessage::FullScanWarn(_) => {} + StatusMessage::Scanning(_, progress) => { + wallet_txs.txs_loading_progress.store(progress, Ordering::Relaxed); + } + StatusMessage::ScanningComplete(_) => { + wallet_txs.txs_loading_progress.store(100, Ordering::Relaxed); + } + StatusMessage::UpdateWarning(_) => {} + } + } + }); + + // Retrieve txs. + match retrieve_txs( + wallet.instance.clone(), + key.as_ref(), + &Some(txs_tx), + true, + None, + None, + None + ) { + Ok(txs) => { + // Do not update data if wallet was closed. + if !wallet.is_open() { + return; + } + + // Add attempt if retrieving was not complete + // or set an error on initial request. + if wallet.txs_loading_progress() != 100 { + if wallet.get_data().is_none() { + wallet.set_loading_error(true); + } else { + wallet.increment_data_update_attempts(); + } + } else { + // Set wallet data. + let mut w_data = wallet.data.write().unwrap(); + *w_data = Some(WalletData { info: info.1, txs: txs.1 }); + + // Reset attempts. + wallet.reset_data_update_attempts(); + } + } + Err(_) => { + // Increment attempts value in case of error. + wallet.increment_data_update_attempts(); + } + } + } + } + Err(_) => { + // Increment attempts value in case of error. + wallet.increment_data_update_attempts(); + } + } + + // Reset progress values. + wallet.info_loading_progress.store(0, Ordering::Relaxed); + wallet.txs_loading_progress.store(0, Ordering::Relaxed); + + println!("UPDATE finish, attempts: {}", wallet.get_data_update_attempts()); + + // Set an error if maximum number of attempts was reached. + if wallet.get_data_update_attempts() >= DATA_UPDATE_ATTEMPTS { + wallet.reset_data_update_attempts(); + wallet.set_loading_error(true); + } } \ No newline at end of file