From 076a692e1bf897bf8c084760de65238acabc79f0 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 9 Aug 2023 02:22:16 +0300 Subject: [PATCH] wallet + config + ui: load wallet info, wallet connections and app config refactoring, check external connections availability, config subdir argument format, item button color, radio buttons stroke, chain type selection from connections, update translations --- locales/en.yml | 3 + locales/ru.yml | 3 + src/config.rs | 127 ++++ src/gui/views/network/connections/content.rs | 119 ++-- src/gui/views/network/content.rs | 4 + src/gui/views/network/setup/node.rs | 4 +- src/gui/views/network/setup/p2p.rs | 2 +- src/gui/views/views.rs | 17 +- src/gui/views/wallets/content.rs | 37 +- src/gui/views/wallets/creation/creation.rs | 4 +- src/gui/views/wallets/setup/connection.rs | 22 +- src/gui/views/wallets/setup/mod.rs | 5 +- src/gui/views/wallets/setup/types.rs | 22 - src/gui/views/wallets/wallet/content.rs | 75 ++- src/gui/views/wallets/wallet/info.rs | 9 +- src/gui/views/wallets/wallet/receive.rs | 3 +- src/gui/views/wallets/wallet/send.rs | 3 +- src/gui/views/wallets/wallet/settings.rs | 3 +- src/gui/views/wallets/wallet/types.rs | 7 +- src/lib.rs | 11 +- src/node/config.rs | 24 +- src/settings.rs | 157 ++--- src/wallet/config.rs | 45 +- src/wallet/connections/config.rs | 123 ++-- src/wallet/connections/external.rs | 77 ++- src/wallet/types.rs | 11 +- src/wallet/wallets.rs | 586 ++++--------------- 27 files changed, 647 insertions(+), 856 deletions(-) create mode 100644 src/config.rs delete mode 100644 src/gui/views/wallets/setup/types.rs diff --git a/locales/en.yml b/locales/en.yml index 05fbeab..c47b4bc 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -47,6 +47,9 @@ network: autorun: Autorun disabled_server: 'Enable integrated node or add another connection method by pressing %{dots} in the top-left corner of the screen.' no_ips: There are no available IP addresses on your system, server cannot be started, check your network connectivity. + available: Available + not_available: Not available + availability_check: Availability check sync_status: node_restarting: Node is restarting node_down: Node is down diff --git a/locales/ru.yml b/locales/ru.yml index e117812..807b9a4 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -47,6 +47,9 @@ network: autorun: Автозапуск disabled_server: 'Включите встроенный узел или добавьте другой способ подключения, нажав %{dots} в левом-верхнем углу экрана.' no_ips: В вашей системе отсутствуют доступные IP адреса, запуск сервера невозможен, проверьте ваше подключение к сети. + available: Доступно + not_available: Недоступно + availability_check: Проверка доступности sync_status: node_restarting: Узел перезапускается node_down: Узел выключен diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..ceef0a8 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,127 @@ +// 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 serde_derive::{Deserialize, Serialize}; +use crate::node::NodeConfig; +use crate::Settings; +use crate::wallet::ConnectionsConfig; + +/// Application configuration. +#[derive(Serialize, Deserialize)] +pub struct AppConfig { + /// Run node server on startup. + pub auto_start_node: bool, + /// Chain type for node and wallets. + pub(crate) chain_type: ChainTypes, + + /// Flag to show wallet list at dual panel wallets mode. + show_wallets_at_dual_panel: bool, + /// Flag to show all connections at network panel or integrated node info. + show_connections_network_panel: bool, +} + +impl Default for AppConfig { + fn default() -> Self { + Self { + auto_start_node: false, + chain_type: ChainTypes::default(), + show_wallets_at_dual_panel: true, + show_connections_network_panel: false, + } + } +} + +impl AppConfig { + /// Application configuration file name. + pub const FILE_NAME: &'static str = "app.toml"; + + /// Save application configuration to the file. + pub fn save(&self) { + Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, None)); + } + + /// Change global [`ChainTypes`] and load new [`NodeConfig`]. + pub fn change_chain_type(chain_type: &ChainTypes) { + let current_chain_type = Self::chain_type(); + if current_chain_type != *chain_type { + // Save chain type at app config. + { + let mut w_app_config = Settings::app_config_to_update(); + w_app_config.chain_type = *chain_type; + w_app_config.save(); + } + // Load node configuration for selected chain type. + { + let mut w_node_config = Settings::node_config_to_update(); + let node_config = NodeConfig::for_chain_type(chain_type); + w_node_config.node = node_config.node; + w_node_config.peers = node_config.peers; + } + // Load connections configuration + { + let mut w_node_config = Settings::conn_config_to_update(); + *w_node_config = ConnectionsConfig::for_chain_type(chain_type); + } + } + } + + /// Get current [`ChainTypes`] for node and wallets. + pub fn chain_type() -> ChainTypes { + let r_config = Settings::app_config_to_read(); + r_config.chain_type + } + + /// Check if integrated node is starting with application. + pub fn autostart_node() -> bool { + let r_config = Settings::app_config_to_read(); + r_config.auto_start_node + } + + /// Toggle integrated node autostart. + pub fn toggle_node_autostart() { + let autostart = Self::autostart_node(); + let mut w_app_config = Settings::app_config_to_update(); + w_app_config.auto_start_node = !autostart; + w_app_config.save(); + } + + /// Check if it's needed to show wallet list at dual panel wallets mode. + pub fn show_wallets_at_dual_panel() -> bool { + let r_config = Settings::app_config_to_read(); + r_config.show_wallets_at_dual_panel + } + + /// Toggle flag to show wallet list at dual panel wallets mode. + pub fn toggle_show_wallets_at_dual_panel() { + let show = Self::show_wallets_at_dual_panel(); + let mut w_app_config = Settings::app_config_to_update(); + w_app_config.show_wallets_at_dual_panel = !show; + w_app_config.save(); + } + + /// Check if it's needed to show all connections or integrated node info at network panel. + pub fn show_connections_network_panel() -> bool { + let r_config = Settings::app_config_to_read(); + r_config.show_connections_network_panel + } + + /// Toggle flag to show all connections or integrated node info at network panel. + pub fn toggle_show_connections_network_panel() { + let show = Self::show_connections_network_panel(); + let mut w_app_config = Settings::app_config_to_update(); + w_app_config.show_connections_network_panel = !show; + w_app_config.save(); + } +} \ No newline at end of file diff --git a/src/gui/views/network/connections/content.rs b/src/gui/views/network/connections/content.rs index c016842..7989c5d 100644 --- a/src/gui/views/network/connections/content.rs +++ b/src/gui/views/network/connections/content.rs @@ -17,25 +17,25 @@ use url::Url; use crate::AppConfig; use crate::gui::Colors; -use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE_SIMPLE, PENCIL, PLAY, STOP, TRASH, X_CIRCLE}; +use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, PENCIL, POWER, TRASH, X_CIRCLE}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; +use crate::gui::views::{Modal, NodeSetup, View}; use crate::gui::views::types::{ModalContainer, ModalPosition}; use crate::node::{Node, NodeConfig}; use crate::wallet::{ConnectionsConfig, ExternalConnection}; /// Network connections content. pub struct ConnectionsContent { - /// Flag to check if modal was just opened. + /// Flag to check if [`Modal`] was just opened to focus on input field. first_modal_launch: bool, /// External connection URL value for [`Modal`]. ext_node_url_edit: String, /// External connection API secret value for [`Modal`]. ext_node_secret_edit: String, - /// Flag to show URL format error. + /// Flag to show URL format error at [`Modal`]. ext_node_url_error: bool, - /// Flag to check if existing connection is editing. - edit_ext_conn: Option, + /// Editing external connection identifier for [`Modal`]. + ext_conn_id_edit: Option, /// [`Modal`] identifiers allowed at this ui container. modal_ids: Vec<&'static str> @@ -43,12 +43,15 @@ pub struct ConnectionsContent { impl Default for ConnectionsContent { fn default() -> Self { + if AppConfig::show_connections_network_panel() { + ExternalConnection::start_ext_conn_availability_check(); + } Self { first_modal_launch: true, ext_node_url_edit: "".to_string(), ext_node_secret_edit: "".to_string(), ext_node_url_error: false, - edit_ext_conn: None, + ext_conn_id_edit: None, modal_ids: vec![ Self::NETWORK_EXT_CONNECTION_MODAL ] @@ -81,20 +84,30 @@ impl ConnectionsContent { // Draw modal content for current ui container. self.current_modal_ui(ui, frame, cb); + // Show network type selection. + let saved_chain_type = AppConfig::chain_type(); + NodeSetup::chain_type_ui(ui); + // Start external connections availability check on network type change. + if saved_chain_type != AppConfig::chain_type() { + ExternalConnection::start_ext_conn_availability_check(); + } + ui.add_space(5.0); + // Show integrated node info content. Self::integrated_node_item_ui(ui); - ui.add_space(8.0); - ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::GRAY)); - ui.add_space(6.0); - - // Show external connections. - let ext_conn_list = ConnectionsConfig::external_connections(); - for (index, conn) in ext_conn_list.iter().enumerate() { - ui.horizontal_wrapped(|ui| { - // Draw connection list item. - self.ext_conn_item_ui(ui, conn, index, ext_conn_list.len(), cb); - }); + let ext_conn_list = ConnectionsConfig::ext_conn_list(); + if !ext_conn_list.is_empty() { + ui.add_space(6.0); + ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::GRAY)); + ui.add_space(6.0); + // Show external connections. + for (index, conn) in ext_conn_list.iter().enumerate() { + ui.horizontal_wrapped(|ui| { + // Draw connection list item. + self.ext_conn_item_ui(ui, conn, index, ext_conn_list.len(), cb); + }); + } } } @@ -115,26 +128,26 @@ impl ConnectionsContent { ui.style_mut().visuals.widgets.hovered.rounding = Rounding::same(8.0); ui.style_mut().visuals.widgets.active.rounding = Rounding::same(8.0); - // Draw button to show integrated node. - View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, || { + // Draw button to show integrated node info. + View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || { AppConfig::toggle_show_connections_network_panel(); }); if !Node::is_running() { // Draw button to start integrated node. - View::item_button(ui, Rounding::none(), PLAY, || { + View::item_button(ui, Rounding::none(), POWER, Some(Colors::GREEN), || { Node::start(); }); } else if !Node::is_starting() && !Node::is_stopping() && !Node::is_restarting() { - // Show button to open closed wallet. - View::item_button(ui, Rounding::none(), STOP, || { + // Draw button to open closed wallet. + View::item_button(ui, Rounding::none(), POWER, Some(Colors::RED), || { Node::stop(false); }); } let layout_size = ui.available_size(); ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { - ui.add_space(7.0); + ui.add_space(6.0); ui.vertical(|ui| { ui.add_space(3.0); ui.label(RichText::new(t!("network.node")) @@ -157,7 +170,6 @@ impl ConnectionsContent { }; let status_text = format!("{} {}", status_icon, Node::get_sync_status_text()); ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY)); - ui.add_space(4.0); }) }); }); @@ -172,28 +184,28 @@ impl ConnectionsContent { cb: &dyn PlatformCallbacks) { // Setup layout size. let mut rect = ui.available_rect_before_wrap(); - rect.set_height(42.0); + rect.set_height(53.0); // Draw round background. let bg_rect = rect.clone(); let item_rounding = View::item_rounding(index, len, false); - ui.painter().rect(bg_rect, item_rounding, Colors::FILL, View::ITEM_STROKE); + ui.painter().rect(bg_rect, item_rounding, Colors::FILL_DARK, View::ITEM_STROKE); ui.vertical(|ui| { ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { // Draw buttons for non-default connections. - if conn.url != ExternalConnection::DEFAULT_EXTERNAL_NODE_URL { + if conn.url != ExternalConnection::DEFAULT_MAIN_URL { let button_rounding = View::item_rounding(index, len, true); - View::item_button(ui, button_rounding, TRASH, || { - ConnectionsConfig::remove_external_connection(conn); + View::item_button(ui, button_rounding, TRASH, None, || { + ConnectionsConfig::remove_ext_conn(conn.id); }); - View::item_button(ui, Rounding::none(), PENCIL, || { + View::item_button(ui, Rounding::none(), PENCIL, None, || { // Setup values for Modal. self.first_modal_launch = true; self.ext_node_url_edit = conn.url.clone(); self.ext_node_secret_edit = conn.secret.clone().unwrap_or("".to_string()); self.ext_node_url_error = false; - self.edit_ext_conn = Some(conn.clone()); + self.ext_conn_id_edit = Some(conn.id); // Show modal. Modal::new(Self::NETWORK_EXT_CONNECTION_MODAL) .position(ModalPosition::CenterTop) @@ -206,11 +218,29 @@ impl ConnectionsContent { let layout_size = ui.available_size(); ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { ui.add_space(6.0); - // Draw connections URL. - let conn_text = format!("{} {}", GLOBE_SIMPLE, conn.url); - ui.label(RichText::new(conn_text) - .color(Colors::TEXT_BUTTON) - .size(16.0)); + ui.vertical(|ui| { + // Draw connections URL. + ui.add_space(4.0); + let conn_text = format!("{} {}", COMPUTER_TOWER, conn.url); + ui.label(RichText::new(conn_text) + .color(Colors::TEXT) + .size(15.0)); + + ui.add_space(1.0); + + // Setup connection status text. + let status_text = if let Some(available) = conn.available { + if available { + format!("{} {}", CHECK_CIRCLE, t!("network.available")) + } else { + format!("{} {}", X_CIRCLE, t!("network.not_available")) + } + } else { + format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check")) + }; + ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY)); + ui.add_space(3.0); + }); }); }); }); @@ -223,7 +253,7 @@ impl ConnectionsContent { self.ext_node_url_edit = "".to_string(); self.ext_node_secret_edit = "".to_string(); self.ext_node_url_error = false; - self.edit_ext_conn = None; + self.ext_conn_id_edit = None; // Show modal. Modal::new(Self::NETWORK_EXT_CONNECTION_MODAL) .position(ModalPosition::CenterTop) @@ -246,7 +276,7 @@ impl ConnectionsContent { // Draw node URL text edit. let url_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_url_edit) - .id(Id::from(modal.id).with("node_url_edit")) + .id(Id::from(modal.id).with(self.ext_conn_id_edit)) .font(TextStyle::Heading) .desired_width(ui.available_width()) .cursor_at_end(true) @@ -315,13 +345,12 @@ impl ConnectionsContent { }; // Update or create new connections. - let ext_conn = ExternalConnection::new(url.clone(), secret); - if let Some(edit_conn) = self.edit_ext_conn.clone() { - ConnectionsConfig::update_external_connection(edit_conn, ext_conn); - self.edit_ext_conn = None; - } else { - ConnectionsConfig::add_external_connection(ext_conn); + let mut ext_conn = ExternalConnection::new(url, secret); + if let Some(id) = self.ext_conn_id_edit { + ext_conn.id = id; } + ConnectionsConfig::add_ext_conn(ext_conn); + self.ext_conn_id_edit = None; // Close modal. cb.hide_keyboard(); diff --git a/src/gui/views/network/content.rs b/src/gui/views/network/content.rs index 73ae3a2..3937b40 100644 --- a/src/gui/views/network/content.rs +++ b/src/gui/views/network/content.rs @@ -23,6 +23,7 @@ use crate::gui::views::{ConnectionsContent, NetworkMetrics, NetworkMining, Netwo use crate::gui::views::network::types::{NetworkTab, NetworkTabType}; use crate::gui::views::types::TitleType; use crate::node::Node; +use crate::wallet::ExternalConnection; /// Network content. pub struct NetworkContent { @@ -178,6 +179,9 @@ impl NetworkContent { if !show_connections { View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || { AppConfig::toggle_show_connections_network_panel(); + if AppConfig::show_connections_network_panel() { + ExternalConnection::start_ext_conn_availability_check(); + } }); } else { View::title_button(ui, PLUS_CIRCLE, || { diff --git a/src/gui/views/network/setup/node.rs b/src/gui/views/network/setup/node.rs index b36fe82..4aaf041 100644 --- a/src/gui/views/network/setup/node.rs +++ b/src/gui/views/network/setup/node.rs @@ -108,7 +108,7 @@ impl NodeSetup { ui.add_space(4.0); // Show chain type setup. - self.chain_type_ui(ui); + Self::chain_type_ui(ui); // Show loading indicator or controls to stop/start/restart node. if Node::is_stopping() || Node::is_restarting() || Node::is_starting() { @@ -220,7 +220,7 @@ impl NodeSetup { } /// Draw [`ChainTypes`] setup content. - fn chain_type_ui(&mut self, ui: &mut egui::Ui) { + pub fn chain_type_ui(ui: &mut egui::Ui) { let saved_chain_type = AppConfig::chain_type(); let mut selected_chain_type = saved_chain_type; diff --git a/src/gui/views/network/setup/p2p.rs b/src/gui/views/network/setup/p2p.rs index 2004869..bb4b348 100644 --- a/src/gui/views/network/setup/p2p.rs +++ b/src/gui/views/network/setup/p2p.rs @@ -908,7 +908,7 @@ fn peer_item_ui(ui: &mut egui::Ui, ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| { // Draw delete button for non-default seed peers. if peer_type != &PeerType::DefaultSeed { - View::item_button(ui, View::item_rounding(index, len, true), TRASH, || { + View::item_button(ui, View::item_rounding(index, len, true), TRASH, None, || { match peer_type { PeerType::CustomSeed => { NodeConfig::remove_custom_seed(peer_addr); diff --git a/src/gui/views/views.rs b/src/gui/views/views.rs index be921e7..322fb44 100644 --- a/src/gui/views/views.rs +++ b/src/gui/views/views.rs @@ -104,6 +104,8 @@ impl View { /// Title button with transparent background fill color, contains only icon. pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) { ui.scope(|ui| { + // Disable stroke when inactive. + ui.style_mut().visuals.widgets.inactive.bg_stroke = Stroke::NONE; // Setup stroke around title buttons on click. ui.style_mut().visuals.widgets.hovered.bg_stroke = Self::HOVER_STROKE; ui.style_mut().visuals.widgets.active.bg_stroke = Self::DEFAULT_STROKE; @@ -173,8 +175,12 @@ impl View { } } - /// Draw list item [`Button`] with given vertical padding and rounding on left and right sides. - pub fn item_button(ui: &mut egui::Ui, r: Rounding, icon: &'static str, action: impl FnOnce()) { + /// Draw list item [`Button`] with provided rounding. + pub fn item_button(ui: &mut egui::Ui, + rounding: Rounding, + text: &'static str, + color: Option, + action: impl FnOnce()) { // Setup button size. let mut rect = ui.available_rect_before_wrap(); rect.set_width(32.0); @@ -193,9 +199,12 @@ impl View { ui.visuals_mut().widgets.hovered.bg_stroke = Self::HOVER_STROKE; ui.visuals_mut().widgets.active.bg_stroke = Self::ITEM_STROKE; + // Setup button text color. + let text_color = if let Some(c) = color { c } else { Colors::ITEM_BUTTON }; + // Show button. - let br = Button::new(RichText::new(icon).size(20.0).color(Colors::ITEM_BUTTON)) - .rounding(r) + let br = Button::new(RichText::new(text).size(20.0).color(text_color)) + .rounding(rounding) .min_size(button_size) .ui(ui); br.surrender_focus(); diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index 19170e4..efe6377 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -24,7 +24,7 @@ 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::{Wallet, Wallets}; +use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet, Wallets}; /// Wallets content. pub struct WalletsContent { @@ -140,9 +140,7 @@ impl WalletsContent { } if create_wallet || !show_wallet { // Show wallet creation content. - self.creation_content.ui(ui, frame, cb, |mut wallet| { - // Load the wallet. - Wallets::load(&mut wallet); + self.creation_content.ui(ui, frame, cb, |wallet| { // Add created wallet to list. self.wallets.add(wallet); }); @@ -365,17 +363,23 @@ impl WalletsContent { if !wallet.is_open() { // Show button to open closed wallet. - View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, || { + View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, None, || { self.wallets.select(Some(id)); self.show_open_wallet_modal(cb); }); - } else if !is_selected { - // Show button to select opened wallet. - View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, || { - self.wallets.select(Some(id)); - }); + } else { + if !is_selected { + // Show button to select opened wallet. + View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || { + self.wallets.select(Some(id)); + }); + } // Show button to close opened wallet. - View::item_button(ui, Rounding::none(), LOCK_KEY, || { + View::item_button(ui, if !is_selected { + Rounding::none() + } else { + View::item_rounding(0, 1, true) + }, LOCK_KEY, None, || { let _ = wallet.close(); }); } @@ -390,9 +394,12 @@ impl WalletsContent { View::ellipsize_text(ui, wallet.config.name.to_owned(), 18.0, name_color); // Setup wallet connection text. - let external_url = &wallet.config.external_node_url; - let conn_text = if let Some(url) = external_url { - format!("{} {}", GLOBE_SIMPLE, url) + let conn_text = if let Some(id) = wallet.config.ext_conn_id { + let ext_conn_url = match ConnectionsConfig::ext_conn(id) { + None => ExternalConnection::DEFAULT_MAIN_URL.to_string(), + Some(ext_conn) => ext_conn.url + }; + format!("{} {}", GLOBE_SIMPLE, ext_conn_url) } else { format!("{} {}", COMPUTER_TOWER, t!("network.node")) }; @@ -525,7 +532,7 @@ impl WalletsContent { if self.pass_edit.is_empty() { return; } - match self.wallets.open_selected(self.pass_edit.clone()) { + match self.wallets.launch_selected(self.pass_edit.clone()) { Ok(_) => { // Clear values. self.pass_edit = "".to_string(); diff --git a/src/gui/views/wallets/creation/creation.rs b/src/gui/views/wallets/creation/creation.rs index 0154921..78900c8 100644 --- a/src/gui/views/wallets/creation/creation.rs +++ b/src/gui/views/wallets/creation/creation.rs @@ -271,8 +271,8 @@ impl WalletCreation { let name = self.name_edit.clone(); let pass = self.pass_edit.clone(); let phrase = self.mnemonic_setup.mnemonic.get_phrase(); - let ext_conn = self.network_setup.get_ext_conn_url(); - let wallet = Wallet::create(name, pass.clone(), phrase, ext_conn).unwrap(); + let conn_method = &self.network_setup.method; + let wallet = Wallet::create(name, pass.clone(), phrase, conn_method).unwrap(); // Open created wallet. wallet.open(pass).unwrap(); // Pass created wallet to callback. diff --git a/src/gui/views/wallets/setup/connection.rs b/src/gui/views/wallets/setup/connection.rs index 114ac8e..b0d2397 100644 --- a/src/gui/views/wallets/setup/connection.rs +++ b/src/gui/views/wallets/setup/connection.rs @@ -20,13 +20,13 @@ use crate::gui::icons::{GLOBE, GLOBE_SIMPLE}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, View}; use crate::gui::views::types::{ModalContainer, ModalPosition}; -use crate::gui::views::wallets::setup::ConnectionMethod; use crate::wallet::{ConnectionsConfig, ExternalConnection}; +use crate::wallet::types::ConnectionMethod; /// Wallet node connection method setup content. pub struct ConnectionSetup { /// Selected connection method. - method: ConnectionMethod, + pub method: ConnectionMethod, /// Flag to check if modal was just opened. first_modal_launch: bool, @@ -82,14 +82,6 @@ impl ConnectionSetup { // Self { method: ConnectionMethod::Integrated } // } - /// Get external connection URL. - pub fn get_ext_conn_url(&self) -> Option { - match &self.method { - ConnectionMethod::Integrated => None, - ConnectionMethod::External(url) => Some(url.clone()) - } - } - pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { // Draw modal content for current ui container. self.current_modal_ui(ui, frame, cb); @@ -122,10 +114,10 @@ impl ConnectionSetup { ui.add_space(12.0); // Show external nodes URLs selection. - for conn in ConnectionsConfig::external_connections() { + for conn in ConnectionsConfig::ext_conn_list() { View::radio_value(ui, &mut self.method, - ConnectionMethod::External(conn.url.clone()), + ConnectionMethod::External(conn.id), conn.url); ui.add_space(12.0); } @@ -162,7 +154,7 @@ impl ConnectionSetup { // Draw node URL text edit. let url_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_url_edit) - .id(Id::from(modal.id).with("node_url_edit")) + .id(Id::from(modal.id)) .font(TextStyle::Heading) .desired_width(ui.available_width()) .cursor_at_end(true) @@ -230,10 +222,10 @@ impl ConnectionSetup { Some(self.ext_node_secret_edit.to_owned()) }; let ext_conn = ExternalConnection::new(url.clone(), secret); - ConnectionsConfig::add_external_connection(ext_conn); + ConnectionsConfig::add_ext_conn(ext_conn.clone()); // Set added connection as current. - self.method = ConnectionMethod::External(url); + self.method = ConnectionMethod::External(ext_conn.id); // Close modal. cb.hide_keyboard(); diff --git a/src/gui/views/wallets/setup/mod.rs b/src/gui/views/wallets/setup/mod.rs index 8ded50d..3e49db6 100644 --- a/src/gui/views/wallets/setup/mod.rs +++ b/src/gui/views/wallets/setup/mod.rs @@ -13,7 +13,4 @@ // limitations under the License. mod connection; -pub use connection::ConnectionSetup; - -mod types; -pub use types::*; \ No newline at end of file +pub use connection::ConnectionSetup; \ No newline at end of file diff --git a/src/gui/views/wallets/setup/types.rs b/src/gui/views/wallets/setup/types.rs deleted file mode 100644 index fa29124..0000000 --- a/src/gui/views/wallets/setup/types.rs +++ /dev/null @@ -1,22 +0,0 @@ -// 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. - -/// Wallet node connection method type. -#[derive(PartialEq)] -pub enum ConnectionMethod { - /// Integrated node connection. - Integrated, - /// External node connection. - External(String) -} \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index 6228b02..22dd691 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -15,13 +15,13 @@ use egui::{Margin, RichText}; use crate::gui::Colors; -use crate::gui::icons::{DOWNLOAD, UPLOAD, WALLET, WRENCH}; +use crate::gui::icons::{DOWNLOAD, POWER, UPLOAD, WALLET, WRENCH}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::View; +use crate::gui::views::{Root, View}; use crate::gui::views::wallets::{WalletInfo, WalletReceive, WalletSend, WalletSettings}; use crate::gui::views::wallets::types::{WalletTab, WalletTabType}; use crate::node::Node; -use crate::wallet::{Wallet, Wallets}; +use crate::wallet::Wallet; /// Selected and opened wallet content. pub struct WalletContent { @@ -71,11 +71,12 @@ impl WalletContent { ..Default::default() }) .show_inside(ui, |ui| { + self.current_tab.ui(ui, frame, wallet, cb); }); // Refresh content after delay for loaded wallet. - if wallet.is_loaded() { - ui.ctx().request_repaint_after(Wallets::INFO_UPDATE_DELAY); + if wallet.get_info().is_some() { + ui.ctx().request_repaint_after(Wallet::INFO_UPDATE_DELAY); } else { ui.ctx().request_repaint(); } @@ -117,34 +118,48 @@ impl WalletContent { } /// Content to draw when wallet is loading. - fn loading_ui(ui: &mut egui::Ui, wallet: &Wallet) { - if wallet.config.external_node_url.is_none() && !Node::is_running() { - + pub fn show_loading_ui(ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet) -> bool { + if wallet.config.ext_conn_id.is_none() && !Node::is_running() { + let dual_panel_root = Root::is_dual_panel_mode(frame); + View::center_content(ui, if !dual_panel_root { 162.0 } else { 96.0 }, |ui| { + let text = t!("wallets.enable_node", "settings" => WRENCH); + ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); + // Show button to enable integrated node at non-dual root panel mode. + if !dual_panel_root { + ui.add_space(8.0); + let enable_node_text = format!("{} {}", POWER, t!("network.enable_node")); + View::button(ui, enable_node_text, Colors::GOLD, || { + Node::start(); + }); + } + }); + return true; } else { - if let Some(error) = &wallet.loading_error { - // View::center_content(ui, 162.0, |ui| { - // let text = t!("wallets.enable_node", "settings" => WRENCH); - // View::big_loading_spinner(ui); - // ui.add_space(18.0); - // let text = if wallet.loading_progress == 0 { - // t!("wallet_loading") - // } else { - // format!("{}: {}%", t!("wallet_loading"), wallet.loading_progress) - // }; - // ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); - // }); - } else if !wallet.is_loaded() { - View::center_content(ui, 162.0, |ui| { - View::big_loading_spinner(ui); - ui.add_space(18.0); - let text = if wallet.loading_progress == 0 { - t!("wallets.wallet_loading") - } else { - format!("{}: {}%", t!("wallets.wallet_loading"), wallet.loading_progress) - }; + if wallet.get_info().is_none() || wallet.loading_progress() < 100 { + if let Some(error) = &wallet.loading_error { + println!("e: {}", error); + let text = t!("wallets.wallet_loading_err"); ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); - }); + //TODO: button to retry. + } else { + 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) + }; + ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT)); + }); + } + return true; + } else { + println!("12345 info!!!"); } } + false } } \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/info.rs b/src/gui/views/wallets/wallet/info.rs index 1048fc4..f814a32 100644 --- a/src/gui/views/wallets/wallet/info.rs +++ b/src/gui/views/wallets/wallet/info.rs @@ -15,6 +15,8 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::wallets::types::WalletTab; use crate::gui::views::wallets::wallet::types::WalletTabType; +use crate::gui::views::wallets::wallet::WalletContent; +use crate::wallet::Wallet; /// Wallet info tab content. #[derive(Default)] @@ -25,6 +27,11 @@ impl WalletTab for WalletInfo { WalletTabType::Info } - fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { + fn ui(&mut self, + ui: &mut egui::Ui, + frame: &mut eframe::Frame, + wallet: &Wallet, + cb: &dyn PlatformCallbacks) { + WalletContent::show_loading_ui(ui, frame, wallet); } } \ No newline at end of file diff --git a/src/gui/views/wallets/wallet/receive.rs b/src/gui/views/wallets/wallet/receive.rs index 06b79a1..e0dbe6c 100644 --- a/src/gui/views/wallets/wallet/receive.rs +++ b/src/gui/views/wallets/wallet/receive.rs @@ -14,6 +14,7 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; +use crate::wallet::Wallet; /// Receive funds tab content. #[derive(Default)] @@ -24,6 +25,6 @@ impl WalletTab for WalletReceive { WalletTabType::Receive } - fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, 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/gui/views/wallets/wallet/send.rs b/src/gui/views/wallets/wallet/send.rs index 7b899d3..3683391 100644 --- a/src/gui/views/wallets/wallet/send.rs +++ b/src/gui/views/wallets/wallet/send.rs @@ -14,6 +14,7 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; +use crate::wallet::Wallet; /// Send funds tab content. #[derive(Default)] @@ -24,6 +25,6 @@ impl WalletTab for WalletSend { WalletTabType::Send } - fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, 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/gui/views/wallets/wallet/settings.rs b/src/gui/views/wallets/wallet/settings.rs index f9d63db..d57920c 100644 --- a/src/gui/views/wallets/wallet/settings.rs +++ b/src/gui/views/wallets/wallet/settings.rs @@ -14,6 +14,7 @@ use crate::gui::platform::PlatformCallbacks; use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType}; +use crate::wallet::Wallet; /// Wallet settings tab content. #[derive(Default)] @@ -24,6 +25,6 @@ impl WalletTab for WalletSettings { WalletTabType::Settings } - fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, 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/gui/views/wallets/wallet/types.rs b/src/gui/views/wallets/wallet/types.rs index f85c175..7e542a2 100644 --- a/src/gui/views/wallets/wallet/types.rs +++ b/src/gui/views/wallets/wallet/types.rs @@ -13,11 +13,16 @@ // limitations under the License. use crate::gui::platform::PlatformCallbacks; +use crate::wallet::Wallet; /// Wallet tab content interface. pub trait WalletTab { fn get_type(&self) -> WalletTabType; - fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks); + fn ui(&mut self, + ui: &mut egui::Ui, + frame: &mut eframe::Frame, + wallet: &Wallet, + cb: &dyn PlatformCallbacks); } /// Type of [`WalletTab`] content. diff --git a/src/lib.rs b/src/lib.rs index 7d58829..45d8bbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,6 @@ use egui::{Context, Stroke}; #[cfg(target_os = "android")] use winit::platform::android::activity::AndroidApp; -pub use settings::{AppConfig, Settings}; use crate::gui::{Colors, PlatformApp}; use crate::gui::platform::PlatformCallbacks; @@ -29,7 +28,13 @@ i18n!("locales"); mod node; mod wallet; + mod settings; +pub use settings::Settings; + +mod config; +pub use config::AppConfig; +use crate::gui::views::View; pub mod gui; @@ -123,8 +128,10 @@ pub fn setup_visuals(ctx: &Context) { // Setup selection color. visuals.selection.stroke = Stroke { width: 1.0, color: Colors::TEXT }; visuals.selection.bg_fill = Colors::GOLD; - // Disable stroke around panels by default + // Disable stroke around panels by default. visuals.widgets.noninteractive.bg_stroke = Stroke::NONE; + // Setup stroke around inactive widgets. + visuals.widgets.inactive.bg_stroke = View::DEFAULT_STROKE; // Setup visuals ctx.set_visuals(visuals); } diff --git a/src/node/config.rs b/src/node/config.rs index afa02e4..2bb606b 100644 --- a/src/node/config.rs +++ b/src/node/config.rs @@ -45,9 +45,7 @@ impl PeersConfig { /// Save peers config to the file. pub fn save(&self) { let chain_type = AppConfig::chain_type(); - let chain_name = chain_type.shortname(); - let sub_dir = Some(chain_name.as_str()); - let config_path = Settings::get_config_path(Self::FILE_NAME, sub_dir); + let config_path = Settings::get_config_path(Self::FILE_NAME, Some(chain_type.shortname())); Settings::write_to_file(self, config_path); } @@ -147,8 +145,7 @@ impl NodeConfig { // Initialize peers config. let peers_config = { - let chain_name = chain_type.shortname(); - let sub_dir = Some(chain_name.as_str()); + let sub_dir = Some(chain_type.shortname()); let path = Settings::get_config_path(PeersConfig::FILE_NAME, sub_dir); let config = Settings::read_from_file::(path.clone()); if !path.exists() || config.is_err() { @@ -160,8 +157,7 @@ impl NodeConfig { // Initialize node config. let node_config = { - let chain_name = chain_type.shortname(); - let sub_dir = Some(chain_name.as_str()); + let sub_dir = Some(chain_type.shortname()); let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir); let config = Settings::read_from_file::(path.clone()); if !path.exists() || config.is_err() { @@ -176,9 +172,8 @@ impl NodeConfig { /// Save default node config for specified [`ChainTypes`]. fn save_default_node_server_config(chain_type: &ChainTypes) -> ConfigMembers { - let chain_name = chain_type.shortname(); - let sub_dir = Some(chain_name.as_str()); - let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir); + let sub_dir = Some(chain_type.shortname()); + let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir.clone()); let mut default_config = GlobalConfig::for_chain(chain_type); default_config.update_paths(&Settings::get_base_path(sub_dir)); let config = default_config.members.unwrap(); @@ -188,8 +183,7 @@ impl NodeConfig { /// Save default peers config for specified [`ChainTypes`]. fn save_default_peers_config(chain_type: &ChainTypes) -> PeersConfig { - let chain_name = chain_type.shortname(); - let sub_dir = Some(chain_name.as_str()); + let sub_dir = Some(chain_type.shortname()); let path = Settings::get_config_path(PeersConfig::FILE_NAME, sub_dir); let config = PeersConfig::default(); Settings::write_to_file(&config, path); @@ -198,8 +192,7 @@ impl NodeConfig { /// Save node config to the file. pub fn save(&self) { - let chain_name = self.node.server.chain_type.shortname(); - let sub_dir = Some(chain_name.as_str()); + let sub_dir = Some(self.node.server.chain_type.shortname()); let config_path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir); Settings::write_to_file(&self.node, config_path); } @@ -241,8 +234,7 @@ impl NodeConfig { /// Get path for secret file. fn get_secret_path(chain_type: &ChainTypes, secret_file_name: &str) -> PathBuf { - let chain_name = chain_type.shortname(); - let sub_dir = Some(chain_name.as_str()); + let sub_dir = Some(chain_type.shortname()); let grin_path = Settings::get_base_path(sub_dir); let mut api_secret_path = grin_path; api_secret_path.push(secret_file_name); diff --git a/src/settings.rs b/src/settings.rs index 2071ab5..1b32602 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,144 +18,46 @@ use std::path::PathBuf; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use grin_config::ConfigError; -use grin_core::global::ChainTypes; use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; use serde::de::DeserializeOwned; +use serde::Serialize; +use crate::config::AppConfig; use crate::node::NodeConfig; +use crate::wallet::ConnectionsConfig; lazy_static! { /// Static settings state to be accessible globally. static ref SETTINGS_STATE: Arc = Arc::new(Settings::init()); } -/// Application configuration file name. -const APP_CONFIG_FILE_NAME: &'static str = "app.toml"; - -/// Common application settings. -#[derive(Serialize, Deserialize)] -pub struct AppConfig { - /// Run node server on startup. - pub auto_start_node: bool, - /// Chain type for node and wallets. - chain_type: ChainTypes, - - /// Flag to initially show wallet list at dual panel wallets mode. - show_wallets_at_dual_panel: bool, - /// Flag to initially show all connections at network panel. - show_connections_network_panel: bool, -} - -impl Default for AppConfig { - fn default() -> Self { - Self { - auto_start_node: false, - chain_type: ChainTypes::default(), - show_wallets_at_dual_panel: true, - show_connections_network_panel: false, - } - } -} - -impl AppConfig { - /// Save app config to file. - pub fn save(&self) { - Settings::write_to_file(self, Settings::get_config_path(APP_CONFIG_FILE_NAME, None)); - } - - /// Change chain type and load new [`NodeConfig`]. - pub fn change_chain_type(chain_type: &ChainTypes) { - let current_chain_type = Self::chain_type(); - if current_chain_type != *chain_type { - // Save chain type at app config. - { - let mut w_app_config = Settings::app_config_to_update(); - w_app_config.chain_type = *chain_type; - w_app_config.save(); - } - // Load node config for selected chain type. - { - let mut w_node_config = Settings::node_config_to_update(); - let node_config = NodeConfig::for_chain_type(chain_type); - w_node_config.node = node_config.node; - w_node_config.peers = node_config.peers; - } - } - } - - /// Get current [`ChainTypes`] for node and wallets. - pub fn chain_type() -> ChainTypes { - let r_config = Settings::app_config_to_read(); - r_config.chain_type - } - - /// Check if integrated node is starting with application. - pub fn autostart_node() -> bool { - let r_config = Settings::app_config_to_read(); - r_config.auto_start_node - } - - /// Toggle integrated node autostart. - pub fn toggle_node_autostart() { - let autostart = Self::autostart_node(); - let mut w_app_config = Settings::app_config_to_update(); - w_app_config.auto_start_node = !autostart; - w_app_config.save(); - } - - /// Toggle flag to show wallet list at dual panel wallets mode. - pub fn show_wallets_at_dual_panel() -> bool { - let r_config = Settings::app_config_to_read(); - r_config.show_wallets_at_dual_panel - } - - /// Toggle flag to show wallet list at dual panel wallets mode. - pub fn toggle_show_wallets_at_dual_panel() { - let show = Self::show_wallets_at_dual_panel(); - let mut w_app_config = Settings::app_config_to_update(); - w_app_config.show_wallets_at_dual_panel = !show; - w_app_config.save(); - } - - /// Toggle flag to show all connections at network panel. - pub fn show_connections_network_panel() -> bool { - let r_config = Settings::app_config_to_read(); - r_config.show_connections_network_panel - } - - /// Toggle flag to show all connections at network panel. - pub fn toggle_show_connections_network_panel() { - let show = Self::show_connections_network_panel(); - let mut w_app_config = Settings::app_config_to_update(); - w_app_config.show_connections_network_panel = !show; - w_app_config.save(); - } -} - /// Main application directory name. const MAIN_DIR_NAME: &'static str = ".grim"; -/// Provides access to application, integrated node and wallets configs. +/// Contains initialized configurations. pub struct Settings { - /// Application config instance. + /// Application configuration. app_config: Arc>, - /// Integrated node config instance. + /// Integrated node configuration. node_config: Arc>, + /// Wallet connections configuration. + conn_config: Arc>, } impl Settings { /// Initialize settings with app and node configs. fn init() -> Self { - let path = Settings::get_config_path(APP_CONFIG_FILE_NAME, None); - let app_config = Self::init_config::(path); + let app_config_path = Settings::get_config_path(AppConfig::FILE_NAME, None); + let app_config = Self::init_config::(app_config_path); + let chain_type = &app_config.chain_type; Self { - node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(&app_config.chain_type))), + node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(chain_type))), + conn_config: Arc::new(RwLock::new(ConnectionsConfig::for_chain_type(chain_type))), app_config: Arc::new(RwLock::new(app_config)), } } - /// Initialize config from provided file path or set [`Default`] if file not exists. + /// Initialize configuration from provided file path or set [`Default`] if file not exists. pub fn init_config(path: PathBuf) -> T { let parsed = Self::read_from_file::(path.clone()); if !path.exists() || parsed.is_err() { @@ -167,29 +69,38 @@ impl Settings { } } - - /// Get node config to read values. + /// Get node configuration to read values. pub fn node_config_to_read() -> RwLockReadGuard<'static, NodeConfig> { SETTINGS_STATE.node_config.read().unwrap() } - /// Get node config to update values. + /// Get node configuration to update values. pub fn node_config_to_update() -> RwLockWriteGuard<'static, NodeConfig> { SETTINGS_STATE.node_config.write().unwrap() } - /// Get app config to read values. + /// Get app configuration to read values. pub fn app_config_to_read() -> RwLockReadGuard<'static, AppConfig> { SETTINGS_STATE.app_config.read().unwrap() } - /// Get app config to update values. + /// Get app configuration to update values. pub fn app_config_to_update() -> RwLockWriteGuard<'static, AppConfig> { SETTINGS_STATE.app_config.write().unwrap() } - /// Get base directory path for config. - pub fn get_base_path(sub_dir: Option<&str>) -> PathBuf { + /// Get connections configuration to read values. + pub fn conn_config_to_read() -> RwLockReadGuard<'static, ConnectionsConfig> { + SETTINGS_STATE.conn_config.read().unwrap() + } + + /// Get connections configuration to update values. + pub fn conn_config_to_update() -> RwLockWriteGuard<'static, ConnectionsConfig> { + SETTINGS_STATE.conn_config.write().unwrap() + } + + /// Get base directory path for configuration. + pub fn get_base_path(sub_dir: Option) -> PathBuf { // Check if dir exists. let mut path = match dirs::home_dir() { Some(p) => p, @@ -206,14 +117,14 @@ impl Settings { path } - /// Get config file path from provided name and sub-directory if needed. - pub fn get_config_path(config_name: &str, sub_dir: Option<&str>) -> PathBuf { + /// Get configuration file path from provided name and sub-directory if needed. + pub fn get_config_path(config_name: &str, sub_dir: Option) -> PathBuf { let mut settings_path = Self::get_base_path(sub_dir); settings_path.push(config_name); settings_path } - /// Read config from the file. + /// Read configuration from the file. pub fn read_from_file(config_path: PathBuf) -> Result { let file_content = fs::read_to_string(config_path.clone())?; let parsed = toml::from_str::(file_content.as_str()); @@ -228,7 +139,7 @@ impl Settings { } } - /// Write config to the file. + /// Write configuration to the file. pub fn write_to_file(config: &T, path: PathBuf) { let conf_out = toml::to_string(config).unwrap(); let mut file = File::create(path.to_str().unwrap()).unwrap(); diff --git a/src/wallet/config.rs b/src/wallet/config.rs index 25d1ade..84a5801 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -19,33 +19,49 @@ use grin_core::global::ChainTypes; use serde_derive::{Deserialize, Serialize}; use crate::{AppConfig, Settings}; +use crate::wallet::types::ConnectionMethod; /// Wallet configuration. #[derive(Serialize, Deserialize, Clone)] pub struct WalletConfig { /// Chain type for current wallet. - pub(crate) chain_type: ChainTypes, + pub chain_type: ChainTypes, /// Identifier for a wallet. - pub(crate) id: i64, + pub id: i64, /// Human-readable wallet name for ui. - pub(crate) name: String, - /// External node connection URL. - pub(crate) external_node_url: Option, + pub name: String, + /// External connection identifier. + pub ext_conn_id: Option, + /// Minimal amount of confirmations. + pub min_confirmations: u64 } -/// Wallet configuration file name. -const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml"; /// Base wallets directory name. pub const BASE_DIR_NAME: &'static str = "wallets"; +/// Wallet configuration file name. +const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml"; + +/// Minimal amount of confirmations default value. +const MIN_CONFIRMATIONS_DEFAULT: u64 = 10; impl WalletConfig { /// Create wallet config. - pub fn create(name: String, external_node_url: Option) -> WalletConfig { + pub fn create(name: String, conn_method: &ConnectionMethod) -> WalletConfig { + // Setup configuration path. let id = chrono::Utc::now().timestamp(); let chain_type = AppConfig::chain_type(); let config_path = Self::get_config_file_path(chain_type, id); - - let config = WalletConfig { chain_type, id, name, external_node_url }; + // Write configuration to the file. + let config = WalletConfig { + chain_type, + id, + name, + ext_conn_id: match conn_method { + ConnectionMethod::Integrated => None, + ConnectionMethod::External(id) => Some(*id) + }, + min_confirmations: MIN_CONFIRMATIONS_DEFAULT, + }; Settings::write_to_file(&config, config_path); config } @@ -62,8 +78,7 @@ impl WalletConfig { /// Get wallets base directory path for provided [`ChainTypes`]. pub fn get_base_path(chain_type: ChainTypes) -> PathBuf { - let chain_name = chain_type.shortname(); - let sub_dir = Some(chain_name.as_str()); + let sub_dir = Some(chain_type.shortname()); let mut wallets_path = Settings::get_base_path(sub_dir); wallets_path.push(BASE_DIR_NAME); // Create wallets base directory if it doesn't exist. @@ -98,10 +113,4 @@ impl WalletConfig { let config_path = Self::get_config_file_path(self.chain_type, self.id); Settings::write_to_file(self, config_path); } - - /// Set external node connection URL. - pub fn save_external_node_url(&mut self, url: Option) { - self.external_node_url = url; - self.save(); - } } \ No newline at end of file diff --git a/src/wallet/connections/config.rs b/src/wallet/connections/config.rs index b787c4e..37dd1a4 100644 --- a/src/wallet/connections/config.rs +++ b/src/wallet/connections/config.rs @@ -12,66 +12,73 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::{Arc, RwLock}; - -use lazy_static::lazy_static; +use grin_core::global::ChainTypes; use serde_derive::{Deserialize, Serialize}; +use crate::config::AppConfig; use crate::Settings; use crate::wallet::ExternalConnection; -lazy_static! { - /// Static connections state to be accessible globally. - static ref CONNECTIONS_STATE: Arc> = Arc::new( - RwLock::new( - Settings::init_config(Settings::get_config_path(CONFIG_FILE_NAME, None)) - ) - ); -} - /// Wallet connections configuration. #[derive(Serialize, Deserialize, Clone)] pub struct ConnectionsConfig { + /// Network type for connections. + chain_type: ChainTypes, /// URLs of external connections for wallets. external: Vec } -impl Default for ConnectionsConfig { - fn default() -> Self { - Self { - external: vec![ - ExternalConnection::default() - ], +impl ConnectionsConfig { + /// Wallet connections configuration file name. + pub const FILE_NAME: &'static str = "connections.toml"; + + /// Initialize configuration for provided [`ChainTypes`]. + pub fn for_chain_type(chain_type: &ChainTypes) -> Self { + let path = Settings::get_config_path(Self::FILE_NAME, Some(chain_type.shortname())); + let parsed = Settings::read_from_file::(path.clone()); + if !path.exists() || parsed.is_err() { + let default_config = ConnectionsConfig { + chain_type: *chain_type, + external: if chain_type == &ChainTypes::Mainnet { + vec![ + ExternalConnection::default_main() + ] + } else { + vec![] + }, + }; + Settings::write_to_file(&default_config, path); + default_config + } else { + parsed.unwrap() } } -} -/// Wallet configuration file name. -const CONFIG_FILE_NAME: &'static str = "connections.toml"; - -impl ConnectionsConfig { - /// Save connections config to file. + /// Save connections configuration to the file. pub fn save(&self) { - Settings::write_to_file(self, Settings::get_config_path(CONFIG_FILE_NAME, None)); + let chain_type = AppConfig::chain_type(); + let sub_dir = Some(chain_type.shortname()); + Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, sub_dir)); } - /// Get external connections for the wallet. - pub fn external_connections() -> Vec { - let r_config = CONNECTIONS_STATE.read().unwrap(); + /// Get [`ExternalConnection`] list. + pub fn ext_conn_list() -> Vec { + let r_config = Settings::conn_config_to_read(); r_config.external.clone() } - /// Save external connection for the wallet in app config. - pub fn add_external_connection(conn: ExternalConnection) { + /// Save [`ExternalConnection`] in configuration. + pub fn add_ext_conn(conn: ExternalConnection) { // Do not update default connection. - if conn.url == ExternalConnection::DEFAULT_EXTERNAL_NODE_URL { + if conn.url == ExternalConnection::DEFAULT_MAIN_URL { return; } - let mut w_config = CONNECTIONS_STATE.write().unwrap(); + let mut w_config = Settings::conn_config_to_update(); let mut exists = false; for mut c in w_config.external.iter_mut() { - // Update connection if URL exists. - if c.url == conn.url { + // Update connection if config exists. + if c.id == conn.id { + c.url = conn.url.clone(); c.secret = conn.secret.clone(); exists = true; break; @@ -84,39 +91,33 @@ impl ConnectionsConfig { w_config.save(); } - /// Save external connection for the wallet in app config. - pub fn update_external_connection(conn: ExternalConnection, updated: ExternalConnection) { - // Do not update default connection. - if conn.url == ExternalConnection::DEFAULT_EXTERNAL_NODE_URL { - return; - } - let mut w_config = CONNECTIONS_STATE.write().unwrap(); - for mut c in w_config.external.iter_mut() { - // Update connection if URL exists. - if c.url == conn.url { - c.url = updated.url.clone(); - c.secret = updated.secret.clone(); - break; - } - } - w_config.save(); - } - - /// Get external node connection secret from provided URL. - pub fn get_external_connection_secret(url: String) -> Option { - let r_config = CONNECTIONS_STATE.read().unwrap(); + /// Get [`ExternalConnection`] by provided identifier. + pub fn ext_conn(id: i64) -> Option { + let r_config = Settings::conn_config_to_read(); for c in &r_config.external { - if c.url == url { - return c.secret.clone(); + if c.id == id { + return Some(c.clone()); } } None } - /// Remove external node connection. - pub fn remove_external_connection(conn: &ExternalConnection) { - let mut w_config = CONNECTIONS_STATE.write().unwrap(); - let index = w_config.external.iter().position(|c| c.url == conn.url); + /// Set [`ExternalConnection`] availability flag. + pub fn update_ext_conn_availability(id: i64, available: bool) { + let mut w_config = Settings::conn_config_to_update(); + for mut c in w_config.external.iter_mut() { + if c.id == id { + c.available = Some(available); + w_config.save(); + break; + } + } + } + + /// Remove external node connection by provided identifier. + pub fn remove_ext_conn(id: i64) { + let mut w_config = Settings::conn_config_to_update(); + let index = w_config.external.iter().position(|c| c.id == id); if let Some(i) = index { w_config.external.remove(i); w_config.save(); diff --git a/src/wallet/connections/external.rs b/src/wallet/connections/external.rs index 84e5de6..f36b6aa 100644 --- a/src/wallet/connections/external.rs +++ b/src/wallet/connections/external.rs @@ -12,28 +12,85 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::time::Duration; + use serde_derive::{Deserialize, Serialize}; -/// External node connection for the wallet. +use crate::AppConfig; +use crate::wallet::ConnectionsConfig; + +/// External connection for the wallet. #[derive(Serialize, Deserialize, Clone)] pub struct ExternalConnection { + /// Connection identifier. + pub id: i64, /// Node URL. pub url: String, /// Optional API secret key. - pub secret: Option + pub secret: Option, + + /// Flag to check if server is available. + #[serde(skip_serializing)] + pub available: Option } impl ExternalConnection { - /// Default external node URL. - pub const DEFAULT_EXTERNAL_NODE_URL: &'static str = "https://grinnnode.live:3413"; + /// Default external node URL for main network. + pub const DEFAULT_MAIN_URL: &'static str = "https://grinnode.live:3413"; - pub fn new(url: String, secret: Option) -> Self { - Self { url, secret } + /// External connections availability check delay. + const AVAILABILITY_CHECK_DELAY: Duration = Duration::from_millis(10 * 1000); + + /// Create default external connection. + pub fn default_main() -> Self { + Self { id: 1, url: Self::DEFAULT_MAIN_URL.to_string(), secret: None, available: None } } -} -impl Default for ExternalConnection { - fn default() -> Self { - Self { url: Self::DEFAULT_EXTERNAL_NODE_URL.to_string(), secret: None } + /// Create new external connection. + pub fn new(url: String, secret: Option) -> Self { + let id = chrono::Utc::now().timestamp(); + Self { id, url, secret, available: None } + } + + /// Check connection availability. + fn check_conn_availability(&self) { + // Check every connection at separate thread. + let conn = self.clone(); + std::thread::spawn(move || { + let url = url::Url::parse(conn.url.as_str()).unwrap(); + let addr = url.socket_addrs(|| None).unwrap(); + match std::net::TcpStream::connect_timeout(&addr[0], Self::AVAILABILITY_CHECK_DELAY) { + Ok(_) => { + ConnectionsConfig::update_ext_conn_availability(conn.id, true); + } + Err(_) => { + ConnectionsConfig::update_ext_conn_availability(conn.id, false); + } + } + }); + } + + /// Start external connections availability check at another thread. + pub fn start_ext_conn_availability_check() { + std::thread::spawn(move || { + let chain_type = AppConfig::chain_type(); + loop { + // Stop checking if connections are not showing or network type was changed. + if !AppConfig::show_connections_network_panel() + || chain_type != AppConfig::chain_type() { + break; + } + + // Check external connections URLs availability. + let conn_list = ConnectionsConfig::ext_conn_list(); + for conn in conn_list { + // Check every connection at separate thread. + conn.check_conn_availability(); + } + + // Pause checking for delay value. + std::thread::sleep(Self::AVAILABILITY_CHECK_DELAY); + } + }); } } \ No newline at end of file diff --git a/src/wallet/types.rs b/src/wallet/types.rs index b1adc2f..2daed83 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -76,4 +76,13 @@ pub type WalletInstance = Arc< >, >, >, ->; \ No newline at end of file +>; + +/// Wallet node connection method type. +#[derive(PartialEq)] +pub enum ConnectionMethod { + /// Integrated node. + Integrated, + /// External node, contains connection identifier. + External(i64) +} \ No newline at end of file diff --git a/src/wallet/wallets.rs b/src/wallet/wallets.rs index f5bb102..12e1393 100644 --- a/src/wallet/wallets.rs +++ b/src/wallet/wallets.rs @@ -12,30 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{cmp, thread}; +use std::thread; use std::path::PathBuf; -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, mpsc, RwLock}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::time::Duration; use grin_core::global; use grin_core::global::ChainTypes; -use grin_keychain::{ExtKeychain, Identifier, Keychain}; +use grin_keychain::{ExtKeychain, Keychain}; use grin_util::types::ZeroingString; -use grin_wallet_api::{Foreign, ForeignCheckMiddlewareFn, Owner}; use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient}; -use grin_wallet_libwallet::{Error, NodeClient, NodeVersionInfo, OutputStatus, scan, Slate, slate_versions, SlatepackArmor, Slatepacker, SlatepackerArgs, TxLogEntry, wallet_lock, WalletBackend, WalletInfo, WalletInst, WalletLCProvider}; -use log::debug; +use grin_wallet_libwallet::{Error, NodeClient, StatusMessage, WalletBackend, WalletInfo, WalletInst, WalletLCProvider}; +use grin_wallet_libwallet::api_impl::owner::retrieve_summary_info; use parking_lot::Mutex; -use uuid::Uuid; use crate::AppConfig; use crate::node::NodeConfig; -use crate::wallet::{ConnectionsConfig, WalletConfig}; -use crate::wallet::selection::lock_tx_context; -use crate::wallet::tx::{add_inputs_to_slate, new_tx_slate}; -use crate::wallet::types::WalletInstance; -use crate::wallet::updater::{cancel_tx, refresh_output_state, retrieve_txs}; +use crate::wallet::{ConnectionsConfig, ExternalConnection, WalletConfig}; +use crate::wallet::types::{ConnectionMethod, WalletInstance}; /// [`Wallet`] list wrapper. pub struct Wallets { @@ -55,12 +50,6 @@ impl Default for Wallets { } impl Wallets { - /// Delay in seconds to update outputs after wallet opening. - pub const OUTPUTS_UPDATE_DELAY: Duration = Duration::from_millis(30 * 1000); - - /// Delay in seconds to update wallet info. - pub const INFO_UPDATE_DELAY: Duration = Duration::from_millis(10 * 1000); - /// Initialize wallets from base directory for provided [`ChainType`]. fn init(chain_type: ChainTypes) -> Vec { let mut wallets = Vec::new(); @@ -109,87 +98,15 @@ impl Wallets { false } - /// Open selected wallet. - pub fn open_selected(&mut self, password: String) -> Result<(), Error> { + /// Open and load selected wallet. + pub fn launch_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); + return w.open(password.clone()); } } Err(Error::GenericError("Wallet is not selected".to_string())) } - - /// Load the wallet by scanning available outputs at separate thread. - pub fn load(w: &mut Wallet) { - if !w.is_open() { - return; - } - let mut wallet = w.clone(); - thread::spawn(move || { - // Get pmmr range output indexes. - match wallet.pmmr_range() { - Ok((mut lowest_index, highest_index)) => { - println!("pmmr_range: {} {}", lowest_index, highest_index); - let mut from_index = lowest_index; - loop { - // Stop loop if wallet is not open. - if !wallet.is_open() { - break; - } - - // Scan outputs for last retrieved index. - println!("scan_outputs from {} to {}", from_index, highest_index); - match wallet.scan_outputs(from_index, highest_index) { - Ok(last_index) => { - println!("retrieved index: {}", last_index); - if lowest_index == 0 { - lowest_index = last_index; - } - - // Finish scanning or start new scan from last retrieved index. - if last_index == highest_index { - println!("scan finished"); - wallet.loading_progress = 100; - wallet.is_loaded.store(true, Ordering::Relaxed); - } else { - println!("continue scan"); - from_index = last_index; - } - - // Update loading progress. - let range = highest_index - lowest_index; - let progress = last_index - lowest_index; - wallet.loading_progress = cmp::min( - (progress / range) as u8 * 100, - 99 - ); - println!("progress: {}", wallet.loading_progress); - } - Err(e) => { - if !wallet.is_loaded() { - wallet.loading_error = Some(e); - break; - } - } - } - - // Stop loop if wallet is not open. - if !wallet.is_open() { - break; - } - - // Scan outputs at next cycle again. - println!("finished scanning, waiting"); - thread::sleep(Self::OUTPUTS_UPDATE_DELAY); - } - } - Err(e) => { - wallet.loading_progress = 0; - wallet.loading_error = Some(e); - } - } - }); - } } /// Contains wallet instance and config. @@ -199,29 +116,33 @@ pub struct Wallet { instance: WalletInstance, /// Wallet configuration. - pub(crate) config: WalletConfig, + pub config: WalletConfig, /// Flag to check if wallet is open. is_open: Arc, - /// Flag to check if wallet is loaded and ready to use. - is_loaded: Arc, /// Error on wallet loading. pub loading_error: Option, /// Loading progress in percents - pub loading_progress: u8, + pub loading_progress: Arc, + + /// Wallet balance information. + info: Arc>> } impl Wallet { + /// Delay in seconds to update wallet info. + pub const INFO_UPDATE_DELAY: Duration = Duration::from_millis(20 * 1000); + /// Create wallet from provided instance and config. fn new(instance: WalletInstance, config: WalletConfig) -> Self { Self { instance, config, - is_loaded: Arc::new(AtomicBool::new(false)), - is_open: Arc::new(AtomicBool::new(false)), + is_open: Arc::from(AtomicBool::new(false)), loading_error: None, - loading_progress: 0, + loading_progress: Arc::new(AtomicU8::new(0)), + info: Arc::new(RwLock::new(None)), } } @@ -230,9 +151,9 @@ impl Wallet { name: String, password: String, mnemonic: String, - external_node_url: Option + conn_method: &ConnectionMethod ) -> Result { - let config = WalletConfig::create(name, external_node_url); + let config = WalletConfig::create(name, conn_method); let instance = Self::create_wallet_instance(config.clone())?; let w = Wallet::new(instance, config); { @@ -278,8 +199,12 @@ impl Wallet { } // Setup node client. - let (node_api_url, node_secret) = if let Some(url) = &config.external_node_url { - (url.to_owned(), ConnectionsConfig::get_external_connection_secret(url.to_owned())) + let (node_api_url, node_secret) = if let Some(id) = config.ext_conn_id { + if let Some(conn) = ConnectionsConfig::ext_conn(id) { + (conn.url, conn.secret) + } else { + (ExternalConnection::DEFAULT_MAIN_URL.to_string(), None) + } } else { let api_url = format!("http://{}", NodeConfig::get_api_address()); let api_secret = NodeConfig::get_foreign_api_secret(); @@ -314,16 +239,85 @@ impl Wallet { Ok(Arc::new(Mutex::new(wallet))) } - /// Open wallet instance. + /// Open wallet and start info updating 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)?; - lc.open_wallet(None, ZeroingString::from(password), false, false)?; - self.is_open.store(true, Ordering::Relaxed); + + let mut wallet = self.clone(); + match lc.open_wallet(None, ZeroingString::from(password), false, false) { + Ok(result) => { + self.is_open.store(true, Ordering::Relaxed); + let keychain_mask = result.clone(); + + // Launch loop at separate thread to update wallet info. + thread::spawn(move || loop { + // Stop updating if wallet was closed. + if !wallet.is_open() { + break; + } + let (tx, rx) = mpsc::channel::(); + // Update progress at separate thread. + let wallet_scan = wallet.clone(); + thread::spawn(move || { + while let Ok(m) = rx.recv() { + println!("m: {}", serde_json::to_string::(&m.clone()).unwrap()); + 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) => { + let mut w_info = wallet.info.write().unwrap(); + *w_info = Some(info.1); + } + Err(e) => { + println!("Error!: {}", e); + wallet.loading_error = Some(e); + } + } + + // Repeat after default delay or after 1 second if update was not complete. + let delay = if wallet.loading_progress() == 100 { + Self::INFO_UPDATE_DELAY + } else { + Duration::from_millis(1000) + }; + thread::sleep(delay); + }); + } + Err(e) => return Err(e) + } Ok(()) } + pub fn loading_progress(&self) -> u8 { + self.loading_progress.load(Ordering::Relaxed) + } + /// Check if wallet is open. pub fn is_open(&self) -> bool { self.is_open.load(Ordering::Relaxed) @@ -340,377 +334,9 @@ impl Wallet { Ok(()) } - /// Check if wallet is loaded and ready to use. - pub fn is_loaded(&self) -> bool { - self.is_loaded.load(Ordering::Relaxed) - } - - /// Scan wallet outputs to check/repair the wallet. - fn scan_outputs( - &self, - last_retrieved_index: u64, - highest_index: u64 - ) -> Result { - let wallet = self.instance.clone(); - let info = scan( - wallet.clone(), - None, - false, - last_retrieved_index, - highest_index, - &None, - )?; - let result = info.last_pmmr_index; - - let parent_key_id = { - wallet_lock!(wallet.clone(), w); - w.parent_key_id().clone() - }; - { - wallet_lock!(wallet, w); - let mut batch = w.batch(None)?; - batch.save_last_confirmed_height(&parent_key_id, info.height)?; - batch.commit()?; - }; - Ok(result) - } - - /// Get pmmr indices representing the outputs for the wallet. - fn pmmr_range(&self) -> Result<(u64, u64), Error> { - wallet_lock!(self.instance.clone(), w); - let pmmr_range = w.w2n_client().height_range_to_pmmr_indices(0, None)?; - Ok(pmmr_range) - } - - /// Create transaction. - pub fn tx_create( - &self, - amount: u64, - minimum_confirmations: u64, - selection_strategy_is_use_all: bool, - ) -> Result<(Vec, String), Error> { - let parent_key_id = { - wallet_lock!(self.instance, w); - w.parent_key_id().clone() - }; - - let slate = { - wallet_lock!(self.instance, w); - let mut slate = new_tx_slate(&mut **w, amount, false, 2, false, None)?; - let height = w.w2n_client().get_chain_tip()?.0; - - let context = add_inputs_to_slate( - &mut **w, - None, - &mut slate, - height, - minimum_confirmations, - 500, - 1, - selection_strategy_is_use_all, - &parent_key_id, - true, - false, - false, - )?; - - { - let mut batch = w.batch(None)?; - batch.save_private_context(slate.id.as_bytes(), &context)?; - batch.commit()?; - } - - lock_tx_context(&mut **w, None, &slate, height, &context, None)?; - slate.compact()?; - slate - }; - - let packer = Slatepacker::new(SlatepackerArgs { - sender: None, // sender - recipients: vec![], - dec_key: None, - }); - let slatepack = packer.create_slatepack(&slate)?; - let api = Owner::new(self.instance.clone(), None); - let txs = api.retrieve_txs(None, false, None, Some(slate.id), None)?; - let result = ( - txs.1, - SlatepackArmor::encode(&slatepack)?, - ); - Ok(result) - } - - /// Callback to check slate compatibility at current node. - fn check_middleware( - name: ForeignCheckMiddlewareFn, - node_version_info: Option, - slate: Option<&Slate>, - ) -> Result<(), Error> { - match name { - ForeignCheckMiddlewareFn::BuildCoinbase => Ok(()), - _ => { - let mut bhv = 3; - if let Some(n) = node_version_info { - bhv = n.block_header_version; - } - if let Some(s) = slate { - if bhv > 4 - && s.version_info.block_header_version - < slate_versions::GRIN_BLOCK_HEADER_VERSION - { - Err(Error::Compatibility( - "Incoming Slate is not compatible with this wallet. \ - Please upgrade the node or use a different one." - .into(), - ))?; - } - } - Ok(()) - } - } - } - - /// Receive transaction. - pub fn tx_receive( - &self, - account: &str, - slate_armored: &str - ) -> Result<(Vec, String), Error> { - let foreign_api = - Foreign::new(self.instance.clone(), None, Some(Self::check_middleware), false); - let owner_api = Owner::new(self.instance.clone(), None); - - let mut slate = - owner_api.slate_from_slatepack_message(None, slate_armored.to_owned(), vec![0])?; - let slatepack = - owner_api.decode_slatepack_message(None, slate_armored.to_owned(), vec![0])?; - - let _ret_address = slatepack.sender; - - slate = foreign_api.receive_tx(&slate, Some(&account), None)?; - let txs = owner_api.retrieve_txs(None, false, None, Some(slate.id), None)?; - let packer = Slatepacker::new(SlatepackerArgs { - sender: None, // sender - recipients: vec![], - dec_key: None, - }); - let slatepack = packer.create_slatepack(&slate)?; - let result = ( - txs.1, - SlatepackArmor::encode(&slatepack)?, - ); - Ok(result) - } - - /// Cancel transaction. - pub fn tx_cancel(&self, id: u32) -> Result { - wallet_lock!(self.instance, w); - let parent_key_id = w.parent_key_id(); - cancel_tx(&mut **w, None, &parent_key_id, Some(id), None)?; - Ok("".to_owned()) - } - - /// Get transaction info. - pub fn get_tx(&self, tx_slate_id: &str) -> Result<(bool, Vec), Error> { - let api = Owner::new(self.instance.clone(), None); - let uuid = Uuid::parse_str(tx_slate_id).unwrap(); - let txs = api.retrieve_txs(None, true, None, Some(uuid), None)?; - Ok(txs) - } - - /// Finalize transaction. - pub fn tx_finalize(&self, slate_armored: &str) -> Result<(bool, Vec), Error> { - let owner_api = Owner::new(self.instance.clone(), None); - let mut slate = - owner_api.slate_from_slatepack_message(None, slate_armored.to_owned(), vec![0])?; - let slatepack = - owner_api.decode_slatepack_message(None, slate_armored.to_owned(), vec![0])?; - - let _ret_address = slatepack.sender; - - slate = owner_api.finalize_tx(None, &slate)?; - let txs = owner_api.retrieve_txs(None, false, None, Some(slate.id), None)?; - Ok(txs) - } - - /// Post transaction to node for broadcasting. - pub fn tx_post(&self, tx_slate_id: &str) -> Result<(), Error> { - let api = Owner::new(self.instance.clone(), None); - let tx_uuid = Uuid::parse_str(tx_slate_id).unwrap(); - let (_, txs) = api.retrieve_txs(None, true, None, Some(tx_uuid.clone()), None)?; - if txs[0].confirmed { - return Err(Error::GenericError(format!( - "Transaction with id {} is already confirmed. Not posting.", - tx_slate_id - ))); - } - let stored_tx = api.get_stored_tx(None, None, Some(&tx_uuid))?; - match stored_tx { - Some(stored_tx) => { - api.post_tx(None, &stored_tx, true)?; - Ok(()) - } - None => Err(Error::GenericError(format!( - "Transaction with id {} does not have transaction data. Not posting.", - tx_slate_id - ))), - } - } - - /// Get transactions and base wallet info. - pub fn get_txs_info( - &self, - minimum_confirmations: u64 - ) -> Result<(bool, Vec, WalletInfo), Error> { - let refreshed = Self::update_state(self.instance.clone()).unwrap_or(false); - let wallet_info = { - wallet_lock!(self.instance, w); - let parent_key_id = w.parent_key_id(); - Self::get_info(&mut **w, &parent_key_id, minimum_confirmations)? - }; - let api = Owner::new(self.instance.clone(), None); - - let txs = api.retrieve_txs(None, false, None, None, None)?; - Ok((refreshed, txs.1, wallet_info)) - } - - /// Update wallet instance state. - fn update_state<'a, L, C, K>( - wallet_inst: Arc>>>, - ) -> Result - where - L: WalletLCProvider<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, - { - let parent_key_id = { - wallet_lock!(wallet_inst, w); - w.parent_key_id().clone() - }; - let mut client = { - wallet_lock!(wallet_inst, w); - w.w2n_client().clone() - }; - let tip = client.get_chain_tip()?; - - // Step1: Update outputs and transactions purely based on UTXO state. - { - wallet_lock!(wallet_inst, w); - if !match refresh_output_state(&mut **w, None, tip.0, &parent_key_id, true) { - Ok(_) => true, - Err(_) => false, - } { - // We are unable to contact the node. - return Ok(false); - } - } - - let mut txs = { - wallet_lock!(wallet_inst, w); - retrieve_txs(&mut **w, None, None, None, Some(&parent_key_id), true)? - }; - - for tx in txs.iter_mut() { - // Step 2: Cancel any transactions with an expired TTL. - if let Some(e) = tx.ttl_cutoff_height { - if tip.0 >= e { - wallet_lock!(wallet_inst, w); - let parent_key_id = w.parent_key_id(); - cancel_tx(&mut **w, None, &parent_key_id, Some(tx.id), None)?; - continue; - } - } - // Step 3: Update outstanding transactions with no change outputs by kernel. - if tx.confirmed { - continue; - } - if tx.amount_debited != 0 && tx.amount_credited != 0 { - continue; - } - if let Some(e) = tx.kernel_excess { - let res = client.get_kernel(&e, tx.kernel_lookup_min_height, Some(tip.0)); - let kernel = match res { - Ok(k) => k, - Err(_) => return Ok(false), - }; - if let Some(k) = kernel { - debug!("Kernel Retrieved: {:?}", k); - wallet_lock!(wallet_inst, w); - let mut batch = w.batch(None)?; - tx.confirmed = true; - tx.update_confirmation_ts(); - batch.save_tx_log_entry(tx.clone(), &parent_key_id)?; - batch.commit()?; - } - } - } - - return Ok(true); - } - - /// Get summary info about the wallet. - fn get_info<'a, T: ?Sized, C, K>( - wallet: &mut T, - parent_key_id: &Identifier, - minimum_confirmations: u64, - ) -> Result - where - T: WalletBackend<'a, C, K>, - C: NodeClient + 'a, - K: Keychain + 'a, - { - let current_height = wallet.last_confirmed_height()?; - let outputs = wallet - .iter() - .filter(|out| out.root_key_id == *parent_key_id); - - let mut unspent_total = 0; - let mut immature_total = 0; - let mut awaiting_finalization_total = 0; - let mut unconfirmed_total = 0; - let mut locked_total = 0; - let mut reverted_total = 0; - - for out in outputs { - match out.status { - OutputStatus::Unspent => { - if out.is_coinbase && out.lock_height > current_height { - immature_total += out.value; - } else if out.num_confirmations(current_height) < minimum_confirmations { - // Treat anything less than minimum confirmations as "unconfirmed". - unconfirmed_total += out.value; - } else { - unspent_total += out.value; - } - } - OutputStatus::Unconfirmed => { - // We ignore unconfirmed coinbase outputs completely. - if !out.is_coinbase { - if minimum_confirmations == 0 { - unconfirmed_total += out.value; - } else { - awaiting_finalization_total += out.value; - } - } - } - OutputStatus::Locked => { - locked_total += out.value; - } - OutputStatus::Reverted => reverted_total += out.value, - OutputStatus::Spent => {} - } - } - - Ok(WalletInfo { - last_confirmed_height: current_height, - minimum_confirmations, - total: unspent_total + unconfirmed_total + immature_total, - amount_awaiting_finalization: awaiting_finalization_total, - amount_awaiting_confirmation: unconfirmed_total, - amount_immature: immature_total, - amount_locked: locked_total, - amount_currently_spendable: unspent_total, - amount_reverted: reverted_total, - }) + /// Get wallet info. + pub fn get_info(&self) -> Option { + let r_info = self.info.read().unwrap(); + r_info.clone() } } \ No newline at end of file