Add rescan to gui settings
This commit is contained in:
parent
8155516386
commit
0f6b043fb3
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>),
|
||||
}
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user