diff --git a/gui/src/installer/message.rs b/gui/src/installer/message.rs index bbf8285d..4a171a19 100644 --- a/gui/src/installer/message.rs +++ b/gui/src/installer/message.rs @@ -33,7 +33,7 @@ pub enum Message { ImportRemoteWallet(ImportRemoteWallet), SelectBitcoindType(SelectBitcoindTypeMsg), InternalBitcoind(InternalBitcoindMsg), - DefineBitcoind(DefineBitcoind), + DefineNode(DefineNode), DefineDescriptor(DefineDescriptor), ImportXpub(Fingerprint, Result), HardwareWallets(HardwareWalletMessage), @@ -72,8 +72,13 @@ pub enum ImportRemoteWallet { pub enum DefineBitcoind { ConfigFieldEdited(ConfigField, String), RpcAuthTypeSelected(RpcAuthType), - PingBitcoindResult(Result<(), Error>), - PingBitcoind, +} + +#[derive(Debug, Clone)] +pub enum DefineNode { + DefineBitcoind(DefineBitcoind), + PingResult(Result<(), Error>), + Ping, } #[derive(Debug, Clone)] diff --git a/gui/src/installer/mod.rs b/gui/src/installer/mod.rs index a05f4e0a..e6b2687e 100644 --- a/gui/src/installer/mod.rs +++ b/gui/src/installer/mod.rs @@ -39,7 +39,7 @@ use crate::{ pub use message::Message; use step::{ - BackupDescriptor, BackupMnemonic, ChooseBackend, DefineBitcoind, DefineDescriptor, Final, + BackupDescriptor, BackupMnemonic, ChooseBackend, DefineDescriptor, DefineNode, Final, ImportDescriptor, ImportRemoteWallet, InternalBitcoindStep, RecoverMnemonic, RegisterDescriptor, RemoteBackendLogin, SelectBitcoindTypeStep, ShareXpubs, Step, }; @@ -126,7 +126,7 @@ impl Installer { RemoteBackendLogin::new(network).into(), SelectBitcoindTypeStep::new().into(), InternalBitcoindStep::new(&context.data_dir).into(), - DefineBitcoind::new().into(), + DefineNode::default().into(), Final::new().into(), ], UserFlow::ShareXpubs => vec![ShareXpubs::new(network, signer.clone()).into()], @@ -139,7 +139,7 @@ impl Installer { RegisterDescriptor::new_import_wallet().into(), SelectBitcoindTypeStep::new().into(), InternalBitcoindStep::new(&context.data_dir).into(), - DefineBitcoind::new().into(), + DefineNode::default().into(), Final::new().into(), ], }, diff --git a/gui/src/installer/step/mod.rs b/gui/src/installer/step/mod.rs index 76113623..c45e56c5 100644 --- a/gui/src/installer/step/mod.rs +++ b/gui/src/installer/step/mod.rs @@ -4,8 +4,9 @@ mod mnemonic; mod node; mod share_xpubs; -pub use node::bitcoind::{ - DefineBitcoind, DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep, +pub use node::{ + bitcoind::{DownloadState, InstallState, InternalBitcoindStep, SelectBitcoindTypeStep}, + DefineNode, }; pub use descriptor::{BackupDescriptor, DefineDescriptor, ImportDescriptor, RegisterDescriptor}; diff --git a/gui/src/installer/step/node/bitcoind.rs b/gui/src/installer/step/node/bitcoind.rs index ef22b7cd..7255cbcd 100644 --- a/gui/src/installer/step/node/bitcoind.rs +++ b/gui/src/installer/step/node/bitcoind.rs @@ -339,11 +339,11 @@ impl Step for SelectBitcoindTypeStep { } } +#[derive(Clone)] pub struct DefineBitcoind { rpc_auth_vals: RpcAuthValues, selected_auth_type: RpcAuthType, address: form::Value, - is_running: Option>, // Internal cache to detect network change. network: Option, @@ -355,42 +355,33 @@ impl DefineBitcoind { rpc_auth_vals: RpcAuthValues::default(), selected_auth_type: RpcAuthType::CookieFile, address: form::Value::default(), - is_running: None, network: None, } } - pub fn ping(&self) -> Command { - let address = self.address.value.to_owned(); - let selected_auth_type = self.selected_auth_type; + pub fn ping(&self) -> Result<(), Error> { let rpc_auth_vals = self.rpc_auth_vals.clone(); - Command::perform( - async move { - let builder = match selected_auth_type { - RpcAuthType::CookieFile => { - let cookie_path = rpc_auth_vals.cookie_path.value; - let cookie = std::fs::read_to_string(cookie_path).map_err(|e| { - Error::Bitcoind(format!("Failed to read cookie file: {}", e)) - })?; - SimpleHttpTransport::builder().cookie_auth(cookie) - } - RpcAuthType::UserPass => { - let user = rpc_auth_vals.user.value; - let password = rpc_auth_vals.password.value; - SimpleHttpTransport::builder().auth(user, Some(password)) - } - }; - let client = Client::with_transport( - builder - .url(&address)? - .timeout(std::time::Duration::from_secs(3)) - .build(), - ); - client.send_request(client.build_request("echo", &[]))?; - Ok(()) - }, - |res| Message::DefineBitcoind(message::DefineBitcoind::PingBitcoindResult(res)), - ) + let builder = match self.selected_auth_type { + RpcAuthType::CookieFile => { + let cookie_path = rpc_auth_vals.cookie_path.value; + let cookie = std::fs::read_to_string(cookie_path) + .map_err(|e| Error::Bitcoind(format!("Failed to read cookie file: {}", e)))?; + SimpleHttpTransport::builder().cookie_auth(cookie) + } + RpcAuthType::UserPass => { + let user = rpc_auth_vals.user.value; + let password = rpc_auth_vals.password.value; + SimpleHttpTransport::builder().auth(user, Some(password)) + } + }; + let client = Client::with_transport( + builder + .url(&self.address.value.to_owned())? + .timeout(std::time::Duration::from_secs(3)) + .build(), + ); + client.send_request(client.build_request("echo", &[]))?; + Ok(()) } pub fn can_try_ping(&self) -> bool { @@ -402,10 +393,8 @@ impl DefineBitcoind { self.address.valid && !self.rpc_auth_vals.cookie_path.value.is_empty() } } -} -impl Step for DefineBitcoind { - fn load_context(&mut self, ctx: &Context) { + pub fn load_context(&mut self, ctx: &Context) { if self.rpc_auth_vals.cookie_path.value.is_empty() // if network changed then the values must be reset to default. || self.network != Some(ctx.bitcoin_config.network) @@ -422,17 +411,12 @@ impl Step for DefineBitcoind { self.network = Some(ctx.bitcoin_config.network); } - fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { - if let Message::DefineBitcoind(msg) = message { + + pub fn update(&mut self, message: message::DefineNode) -> Command { + if let message::DefineNode::DefineBitcoind(msg) = message { match msg { - message::DefineBitcoind::PingBitcoind => { - self.is_running = None; - return self.ping(); - } - message::DefineBitcoind::PingBitcoindResult(res) => self.is_running = Some(res), message::DefineBitcoind::ConfigFieldEdited(field, value) => match field { ConfigField::Address => { - self.is_running = None; self.address.value.clone_from(&value); self.address.valid = false; if let Some((ip, port)) = value.rsplit_once(':') { @@ -444,23 +428,19 @@ impl Step for DefineBitcoind { } } ConfigField::CookieFilePath => { - self.is_running = None; self.rpc_auth_vals.cookie_path.value = value; self.rpc_auth_vals.cookie_path.valid = true; } ConfigField::User => { - self.is_running = None; self.rpc_auth_vals.user.value = value; self.rpc_auth_vals.user.valid = true; } ConfigField::Password => { - self.is_running = None; self.rpc_auth_vals.password.value = value; self.rpc_auth_vals.password.valid = true; } }, message::DefineBitcoind::RpcAuthTypeSelected(auth_type) => { - self.is_running = None; self.selected_auth_type = auth_type; } }; @@ -468,7 +448,7 @@ impl Step for DefineBitcoind { Command::none() } - fn apply(&mut self, ctx: &mut Context) -> bool { + pub fn apply(&mut self, ctx: &mut Context) -> bool { let addr = std::net::SocketAddr::from_str(&self.address.value); let rpc_auth = match self.selected_auth_type { RpcAuthType::CookieFile => { @@ -501,28 +481,8 @@ impl Step for DefineBitcoind { } } - fn view( - &self, - _hws: &HardwareWallets, - progress: (usize, usize), - _email: Option<&str>, - ) -> Element { - view::define_bitcoin( - progress, - &self.address, - &self.rpc_auth_vals, - &self.selected_auth_type, - self.is_running.as_ref(), - self.can_try_ping(), - ) - } - - fn load(&self) -> Command { - self.ping() - } - - fn skip(&self, ctx: &Context) -> bool { - !ctx.bitcoind_is_external || ctx.remote_backend.is_some() + pub fn view(&self) -> Element { + view::define_bitcoind(&self.address, &self.rpc_auth_vals, &self.selected_auth_type) } } @@ -532,12 +492,6 @@ impl Default for DefineBitcoind { } } -impl From for Box { - fn from(s: DefineBitcoind) -> Box { - Box::new(s) - } -} - pub struct InternalBitcoindStep { liana_datadir: PathBuf, bitcoind_datadir: PathBuf, diff --git a/gui/src/installer/step/node/mod.rs b/gui/src/installer/step/node/mod.rs index ac8d5325..22480050 100644 --- a/gui/src/installer/step/node/mod.rs +++ b/gui/src/installer/step/node/mod.rs @@ -1 +1,110 @@ pub mod bitcoind; + +use crate::{ + hw::HardwareWallets, + installer::{ + context::Context, + message::{self, Message}, + step::{node::bitcoind::DefineBitcoind, Step}, + view, Error, + }, +}; + +use iced::Command; +use liana_ui::widget::Element; + +pub struct Node { + definition: DefineBitcoind, + is_running: Option>, +} + +impl Node { + fn new() -> Self { + Node { + definition: DefineBitcoind::new(), + is_running: None, + } + } +} + +pub struct DefineNode { + node: Node, +} + +impl From for Box { + fn from(s: DefineNode) -> Box { + Box::new(s) + } +} + +impl DefineNode { + pub fn new() -> Self { + Self { node: Node::new() } + } + + fn ping(&self) -> Command { + let def = self.node.definition.clone(); + Command::perform(async move { def.ping() }, move |res| { + Message::DefineNode(message::DefineNode::PingResult(res)) + }) + } + + fn update_node(&mut self, message: message::DefineNode) -> Command { + self.node.is_running = None; + self.node.definition.update(message) + } +} + +impl Default for DefineNode { + fn default() -> Self { + Self::new() + } +} + +impl Step for DefineNode { + fn load_context(&mut self, ctx: &Context) { + self.node.definition.load_context(ctx); + } + fn update(&mut self, _hws: &mut HardwareWallets, message: Message) -> Command { + if let Message::DefineNode(msg) = message { + match msg { + message::DefineNode::Ping => { + return self.ping(); + } + message::DefineNode::PingResult(res) => { + self.node.is_running = Some(res); + } + msg @ message::DefineNode::DefineBitcoind(_) => { + return self.update_node(msg); + } + } + } + Command::none() + } + + fn apply(&mut self, ctx: &mut Context) -> bool { + self.node.definition.apply(ctx) + } + + fn view( + &self, + _hws: &HardwareWallets, + progress: (usize, usize), + _email: Option<&str>, + ) -> Element { + view::define_bitcoin_node( + progress, + self.node.definition.view(), + self.node.is_running.as_ref(), + self.node.definition.can_try_ping(), + ) + } + + fn load(&self) -> Command { + self.ping() + } + + fn skip(&self, ctx: &Context) -> bool { + !ctx.bitcoind_is_external || ctx.remote_backend.is_some() + } +} diff --git a/gui/src/installer/view.rs b/gui/src/installer/view.rs index 73f76565..bd86a2f7 100644 --- a/gui/src/installer/view.rs +++ b/gui/src/installer/view.rs @@ -34,7 +34,7 @@ use liana_ui::{ use crate::{ hw::{is_compatible_with_tapminiscript, HardwareWallet, UnsupportedReason}, installer::{ - message::{self, Message}, + message::{self, DefineBitcoind, DefineNode, Message}, prompt, step::{DownloadState, InstallState}, Error, @@ -1159,13 +1159,73 @@ pub fn help_backup<'a>() -> Element<'a, Message> { text(prompt::BACKUP_DESCRIPTOR_HELP).small().into() } -pub fn define_bitcoin<'a>( +pub fn define_bitcoin_node<'a>( progress: (usize, usize), + node_view: Element<'a, Message>, + is_running: Option<&Result<(), Error>>, + can_try_ping: bool, +) -> Element<'a, Message> { + let col = Column::new() + .push(node_view) + .push_maybe(if is_running.is_some() { + is_running.map(|res| { + if res.is_ok() { + Container::new( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(icon::circle_check_icon().style(color::GREEN)) + .push(text("Connection checked").style(color::GREEN)), + ) + } else { + Container::new( + Row::new() + .spacing(10) + .align_items(Alignment::Center) + .push(icon::circle_cross_icon().style(color::RED)) + .push(text("Connection failed").style(color::RED)), + ) + } + }) + } else { + Some(Container::new(Space::with_height(Length::Fixed(25.0)))) + }) + .push( + Row::new() + .spacing(10) + .push(Container::new( + button::secondary(None, "Check connection") + .on_press_maybe(if can_try_ping { + Some(Message::DefineNode(DefineNode::Ping)) + } else { + None + }) + .width(Length::Fixed(200.0)), + )) + .push(if is_running.map(|res| res.is_ok()).unwrap_or(false) { + button::primary(None, "Next") + .on_press(Message::Next) + .width(Length::Fixed(200.0)) + } else { + button::primary(None, "Next").width(Length::Fixed(200.0)) + }), + ) + .spacing(50); + + layout( + progress, + None, + "Set up connection to the Bitcoin full node", + col, + true, + Some(Message::Previous), + ) +} + +pub fn define_bitcoind<'a>( address: &form::Value, rpc_auth_vals: &RpcAuthValues, selected_auth_type: &RpcAuthType, - is_running: Option<&Result<(), Error>>, - can_try_ping: bool, ) -> Element<'a, Message> { let is_loopback = if let Some((ip, _port)) = address.value.clone().rsplit_once(':') { let (ipv4, ipv6) = (Ipv4Addr::from_str(ip), Ipv6Addr::from_str(ip)); @@ -1182,9 +1242,8 @@ pub fn define_bitcoin<'a>( .push(text("Address:").bold()) .push( form::Form::new_trimmed("Address", address, |msg| { - Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited( - ConfigField::Address, - msg, + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::ConfigFieldEdited(ConfigField::Address, msg), )) }) .warning("Please enter correct address") @@ -1220,9 +1279,9 @@ pub fn define_bitcoin<'a>( *auth_type, Some(*selected_auth_type), |new_selection| { - Message::DefineBitcoind( - message::DefineBitcoind::RpcAuthTypeSelected(new_selection), - ) + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::RpcAuthTypeSelected(new_selection), + )) }, )) .spacing(30) @@ -1233,9 +1292,8 @@ pub fn define_bitcoin<'a>( .push(match selected_auth_type { RpcAuthType::CookieFile => Row::new().push( form::Form::new_trimmed("Cookie path", &rpc_auth_vals.cookie_path, |msg| { - Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited( - ConfigField::CookieFilePath, - msg, + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::ConfigFieldEdited(ConfigField::CookieFilePath, msg), )) }) .warning("Please enter correct path") @@ -1245,9 +1303,8 @@ pub fn define_bitcoin<'a>( RpcAuthType::UserPass => Row::new() .push( form::Form::new_trimmed("User", &rpc_auth_vals.user, |msg| { - Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited( - ConfigField::User, - msg, + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::ConfigFieldEdited(ConfigField::User, msg), )) }) .warning("Please enter correct user") @@ -1256,9 +1313,8 @@ pub fn define_bitcoin<'a>( ) .push( form::Form::new_trimmed("Password", &rpc_auth_vals.password, |msg| { - Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited( - ConfigField::Password, - msg, + Message::DefineNode(DefineNode::DefineBitcoind( + DefineBitcoind::ConfigFieldEdited(ConfigField::Password, msg), )) }) .warning("Please enter correct password") @@ -1269,62 +1325,11 @@ pub fn define_bitcoin<'a>( }) .spacing(10); - layout( - progress, - None, - "Set up connection to the Bitcoin full node", - Column::new() - .push(col_address) - .push(col_auth) - .push_maybe(if is_running.is_some() { - is_running.map(|res| { - if res.is_ok() { - Container::new( - Row::new() - .spacing(10) - .align_items(Alignment::Center) - .push(icon::circle_check_icon().style(color::GREEN)) - .push(text("Connection checked").style(color::GREEN)), - ) - } else { - Container::new( - Row::new() - .spacing(10) - .align_items(Alignment::Center) - .push(icon::circle_cross_icon().style(color::RED)) - .push(text("Connection failed").style(color::RED)), - ) - } - }) - } else { - Some(Container::new(Space::with_height(Length::Fixed(25.0)))) - }) - .push( - Row::new() - .spacing(10) - .push(Container::new( - button::secondary(None, "Check connection") - .on_press_maybe(if can_try_ping { - Some(Message::DefineBitcoind( - message::DefineBitcoind::PingBitcoind, - )) - } else { - None - }) - .width(Length::Fixed(200.0)), - )) - .push(if is_running.map(|res| res.is_ok()).unwrap_or(false) { - button::primary(None, "Next") - .on_press(Message::Next) - .width(Length::Fixed(200.0)) - } else { - button::primary(None, "Next").width(Length::Fixed(200.0)) - }), - ) - .spacing(50), - true, - Some(Message::Previous), - ) + Column::new() + .push(col_address) + .push(col_auth) + .spacing(50) + .into() } pub fn select_bitcoind_type<'a>(progress: (usize, usize)) -> Element<'a, Message> {