From 0f6b043fb36a4c871486b6d0b722cbd413316a2c Mon Sep 17 00:00:00 2001 From: edouard Date: Fri, 11 Nov 2022 11:11:57 +0100 Subject: [PATCH] Add rescan to gui settings --- gui/src/app/cache.rs | 2 + gui/src/app/message.rs | 3 +- gui/src/app/mod.rs | 29 ++++----- gui/src/app/state/settings.rs | 114 +++++++++++++++++++++++++++++++++- gui/src/app/view/mod.rs | 14 ++++- gui/src/app/view/settings.rs | 111 ++++++++++++++++++++++++++++++++- gui/src/main.rs | 1 + 7 files changed, 254 insertions(+), 20 deletions(-) diff --git a/gui/src/app/cache.rs b/gui/src/app/cache.rs index 693fc8ca..317fae44 100644 --- a/gui/src/app/cache.rs +++ b/gui/src/app/cache.rs @@ -6,6 +6,7 @@ pub struct Cache { pub blockheight: i32, pub coins: Vec, pub spend_txs: Vec, + pub rescan_progress: Option, } impl std::default::Default for Cache { @@ -15,6 +16,7 @@ impl std::default::Default for Cache { blockheight: 0, coins: Vec::new(), spend_txs: Vec::new(), + rescan_progress: None, } } } diff --git a/gui/src/app/message.rs b/gui/src/app/message.rs index 490fa2d6..55b0c6fc 100644 --- a/gui/src/app/message.rs +++ b/gui/src/app/message.rs @@ -19,12 +19,13 @@ pub enum Message { View(view::Message), LoadDaemonConfig(Box), DaemonConfigLoaded(Result<(), Error>), - BlockHeight(Result), + Info(Result), ReceiveAddress(Result), Coins(Result, Error>), SpendTxs(Result, Error>), Psbt(Result), Signed(Result<(Psbt, Fingerprint), Error>), Updated(Result<(), Error>), + StartRescan(Result<(), Error>), ConnectedHardwareWallets(Vec), } diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index f9338f06..64b0b147 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -58,10 +58,12 @@ impl App { fn load_state(&mut self, menu: &Menu) -> Command { self.state = match menu { - menu::Menu::Settings => { - state::SettingsState::new(self.daemon.config().clone(), self.daemon.is_external()) - .into() - } + menu::Menu::Settings => state::SettingsState::new( + self.daemon.config().clone(), + &self.cache, + self.daemon.is_external(), + ) + .into(), menu::Menu::Home => Home::new(&self.cache.coins).into(), menu::Menu::Coins => CoinsPanel::new(&self.cache.coins).into(), menu::Menu::Receive => ReceivePanel::default().into(), @@ -76,7 +78,7 @@ impl App { pub fn subscription(&self) -> Subscription { Subscription::batch(vec![ iced_native::subscription::events().map(Message::Event), - time::every(Duration::from_secs(30)).map(|_| Message::Tick), + time::every(Duration::from_secs(5)).map(|_| Message::Tick), self.state.subscription(), ]) } @@ -109,8 +111,12 @@ impl App { Message::SpendTxs(Ok(txs)) => { self.cache.spend_txs = txs.clone(); } - Message::BlockHeight(Ok(blockheight)) => { - self.cache.blockheight = *blockheight; + Message::Info(Ok(info)) => { + self.cache.blockheight = info.blockheight; + self.cache.rescan_progress = info.rescan_progress; + } + Message::StartRescan(Ok(())) => { + self.cache.rescan_progress = Some(0.0); } _ => {} }; @@ -119,13 +125,8 @@ impl App { Message::Tick => { let daemon = self.daemon.clone(); Command::perform( - async move { - daemon - .get_info() - .map(|res| res.blockheight) - .map_err(|e| e.into()) - }, - Message::BlockHeight, + async move { daemon.get_info().map_err(|e| e.into()) }, + Message::Info, ) } Message::LoadDaemonConfig(cfg) => { diff --git a/gui/src/app/state/settings.rs b/gui/src/app/state/settings.rs index d9a54b25..e1aadce0 100644 --- a/gui/src/app/state/settings.rs +++ b/gui/src/app/state/settings.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +use chrono::prelude::*; use iced::{pure::Element, Command}; use minisafe::config::Config; @@ -42,8 +43,11 @@ pub struct SettingsState { } impl SettingsState { - pub fn new(config: Config, daemon_is_external: bool) -> Self { - let settings = vec![BitcoindSettings::new(&config).into()]; + pub fn new(config: Config, cache: &Cache, daemon_is_external: bool) -> Self { + let settings = vec![ + BitcoindSettings::new(&config).into(), + RescanSetting::new(cache.rescan_progress).into(), + ]; SettingsState { daemon_is_external, @@ -51,7 +55,8 @@ impl SettingsState { config_updated: false, config, settings, - current: None, + // If a scan is running, the current setting edited is the Rescan panel. + current: cache.rescan_progress.map(|_| 1), } } } @@ -85,6 +90,14 @@ impl State for SettingsState { } } }, + Message::Info(res) => match res { + Err(e) => self.warning = Some(e), + Ok(info) => { + if info.rescan_progress == Some(1.0) { + self.settings[1].edited(true); + } + } + }, Message::View(view::Message::Settings(i, msg)) => { if let Some(setting) = self.settings.get_mut(i) { match msg { @@ -237,3 +250,98 @@ impl Setting for BitcoindSettings { } } } + +#[derive(Debug, Default)] +pub struct RescanSetting { + edit: bool, + processing: bool, + success: bool, + year: form::Value, + month: form::Value, + day: form::Value, +} + +impl From for Box { + fn from(s: RescanSetting) -> Box { + Box::new(s) + } +} + +impl RescanSetting { + pub fn new(rescan_progress: Option) -> Self { + Self { + processing: rescan_progress.is_some(), + ..Default::default() + } + } +} + +impl Setting for RescanSetting { + fn edited(&mut self, success: bool) { + self.processing = false; + self.success = success; + } + + fn update( + &mut self, + daemon: Arc, + _cache: &Cache, + message: view::SettingsMessage, + ) -> Command { + match message { + view::SettingsMessage::Edit => { + if !self.processing { + self.edit = true; + } + } + view::SettingsMessage::CancelEdit => { + if !self.processing { + self.edit = false; + } + } + view::SettingsMessage::FieldEdited(field, value) => { + if !self.processing && (value.is_empty() || u32::from_str(&value).is_ok()) { + match field { + "rescan_year" => self.year.value = value, + "rescan_month" => self.month.value = value, + "rescan_day" => self.day.value = value, + _ => {} + } + } + } + view::SettingsMessage::ConfirmEdit => { + let date_time = NaiveDate::from_ymd( + i32::from_str(&self.year.value).unwrap_or(1), + u32::from_str(&self.month.value).unwrap_or(1), + u32::from_str(&self.day.value).unwrap_or(1), + ) + .and_hms(0, 0, 0); + let t = date_time.timestamp() as u32; + self.processing = true; + log::info!("Asking deamon to rescan with timestamp: {}", t); + return Command::perform( + async move { daemon.start_rescan(t).map_err(|e| e.into()) }, + Message::StartRescan, + ); + } + }; + Command::none() + } + + fn view<'a>( + &self, + _config: &'a Config, + cache: &'a Cache, + can_edit: bool, + ) -> Element<'a, view::SettingsMessage> { + view::settings::rescan( + &self.year, + &self.month, + &self.day, + cache.rescan_progress, + self.success, + self.processing, + can_edit, + ) + } +} diff --git a/gui/src/app/view/mod.rs b/gui/src/app/view/mod.rs index 84b32b9c..ebb1ca29 100644 --- a/gui/src/app/view/mod.rs +++ b/gui/src/app/view/mod.rs @@ -225,7 +225,19 @@ pub fn sidebar<'a>(menu: &Menu, cache: &'a Cache) -> widget::Container<'a, Messa .spacing(15) .height(Length::Fill), ) - .push(container(settings_button).height(Length::Shrink)), + .push( + container( + column() + .spacing(10) + .push_maybe(cache.rescan_progress.map(|p| { + container(text(&format!(" Rescan...{:.2}% ", p * 100.0))) + .padding(5) + .style(badge::PillStyle::Simple) + })) + .push(settings_button), + ) + .height(Length::Shrink), + ), ) .style(SidebarStyle) } diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index a8f3223b..f4bb804b 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -1,6 +1,8 @@ +use std::str::FromStr; + use iced::{ alignment, - pure::{column, container, row, widget, Element}, + pure::{column, container, progress_bar, row, widget, Element}, Alignment, Length, }; @@ -17,6 +19,7 @@ use crate::{ color, component::{badge, button, card, form, separation, text::*}, icon, + util::Collection, }, }; @@ -243,3 +246,109 @@ pub fn is_running_label<'a, T: 'a>(is_running: Option) -> widget::Containe container(column()) } } + +pub fn rescan<'a>( + year: &form::Value, + month: &form::Value, + day: &form::Value, + scan_progress: Option, + success: bool, + processing: bool, + can_edit: bool, +) -> Element<'a, SettingsMessage> { + card::simple(container( + column() + .push( + row() + .push(badge::Badge::new(icon::block_icon())) + .push(text("Rescan blockchain").width(Length::Fill)) + .push_maybe(if success { + Some(text("Rescan was successful").color(color::SUCCESS)) + } else { + None + }) + .spacing(20) + .align_items(Alignment::Center) + .width(Length::Fill), + ) + .push(separation().width(Length::Fill)) + .push(if let Some(p) = scan_progress { + container( + column() + .width(Length::Fill) + .push(progress_bar(0.0..=1.0, p as f32).width(Length::Fill)) + .push(text(&format!("Rescan...{:.2}%", p * 100.0))), + ) + } else { + container( + column() + .spacing(10) + .push( + row() + .push(text("Year:").bold().small()) + .push( + form::Form::new("2022", year, |value| { + SettingsMessage::FieldEdited("rescan_year", value) + }) + .size(20) + .padding(5), + ) + .push(text("Month:").bold().small()) + .push( + form::Form::new("12", month, |value| { + SettingsMessage::FieldEdited("rescan_month", value) + }) + .size(20) + .padding(5), + ) + .push(text("Day:").bold().small()) + .push( + form::Form::new("31", day, |value| { + SettingsMessage::FieldEdited("rescan_day", value) + }) + .size(20) + .padding(5), + ) + .align_items(Alignment::Center) + .spacing(10), + ) + .push( + if can_edit + && !processing + && (is_ok_and(&u32::from_str(&year.value), |&v| v > 0) + && is_ok_and(&u32::from_str(&month.value), |&v| { + v > 0 && v <= 12 + }) + && is_ok_and(&u32::from_str(&day.value), |&v| v > 0 && v <= 31)) + { + row().push(column().width(Length::Fill)).push( + button::primary(None, "Start rescan") + .on_press(SettingsMessage::ConfirmEdit) + .width(Length::Shrink), + ) + } else if processing { + row().push(column().width(Length::Fill)).push( + button::primary(None, "Starting rescan...") + .width(Length::Shrink), + ) + } else { + row().push(column().width(Length::Fill)).push( + button::primary(None, "Start rescan").width(Length::Shrink), + ) + }, + ), + ) + }) + .spacing(20), + )) + .width(Length::Fill) + .into() +} + +fn is_ok_and(res: &Result, f: impl FnOnce(&T) -> bool) -> bool { + if let Ok(v) = res { + f(v) + } else { + false + } +} diff --git a/gui/src/main.rs b/gui/src/main.rs index a5cafea8..7f7ceeec 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -167,6 +167,7 @@ impl Application for GUI { blockheight: info.blockheight, coins, spend_txs, + ..Default::default() }; let (app, command) = App::new(cache, loader.gui_config.clone(), minisafed);