Add rescan to gui settings

This commit is contained in:
edouard 2022-11-11 11:11:57 +01:00
parent 8155516386
commit 0f6b043fb3
7 changed files with 254 additions and 20 deletions

View File

@ -6,6 +6,7 @@ pub struct Cache {
pub blockheight: i32,
pub coins: Vec<Coin>,
pub spend_txs: Vec<SpendTx>,
pub rescan_progress: Option<f64>,
}
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,
}
}
}

View File

@ -19,12 +19,13 @@ pub enum Message {
View(view::Message),
LoadDaemonConfig(Box<DaemonConfig>),
DaemonConfigLoaded(Result<(), Error>),
BlockHeight(Result<i32, Error>),
Info(Result<GetInfoResult, Error>),
ReceiveAddress(Result<Address, Error>),
Coins(Result<Vec<Coin>, Error>),
SpendTxs(Result<Vec<SpendTx>, Error>),
Psbt(Result<Psbt, Error>),
Signed(Result<(Psbt, Fingerprint), Error>),
Updated(Result<(), Error>),
StartRescan(Result<(), Error>),
ConnectedHardwareWallets(Vec<HardwareWallet>),
}

View File

@ -58,10 +58,12 @@ impl App {
fn load_state(&mut self, menu: &Menu) -> Command<Message> {
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<Message> {
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) => {

View File

@ -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<String>,
month: form::Value<String>,
day: form::Value<String>,
}
impl From<RescanSetting> for Box<dyn Setting> {
fn from(s: RescanSetting) -> Box<dyn Setting> {
Box::new(s)
}
}
impl RescanSetting {
pub fn new(rescan_progress: Option<f64>) -> 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<dyn Daemon + Sync + Send>,
_cache: &Cache,
message: view::SettingsMessage,
) -> Command<Message> {
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,
)
}
}

View File

@ -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)
}

View File

@ -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<bool>) -> widget::Containe
container(column())
}
}
pub fn rescan<'a>(
year: &form::Value<String>,
month: &form::Value<String>,
day: &form::Value<String>,
scan_progress: Option<f64>,
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<T, E>(res: &Result<T, E>, f: impl FnOnce(&T) -> bool) -> bool {
if let Ok(v) = res {
f(v)
} else {
false
}
}

View File

@ -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);