363 lines
11 KiB
Rust
363 lines
11 KiB
Rust
use std::convert::From;
|
|
use std::net::SocketAddr;
|
|
use std::path::PathBuf;
|
|
use std::str::FromStr;
|
|
use std::sync::Arc;
|
|
|
|
use chrono::prelude::*;
|
|
use iced::{Command, Element};
|
|
|
|
use liana::config::{BitcoinConfig, BitcoindConfig, Config};
|
|
|
|
use crate::{
|
|
app::{cache::Cache, error::Error, message::Message, state::State, view},
|
|
daemon::Daemon,
|
|
ui::component::form,
|
|
};
|
|
|
|
trait Setting: std::fmt::Debug {
|
|
fn edited(&mut self, success: bool);
|
|
fn update(
|
|
&mut self,
|
|
daemon: Arc<dyn Daemon + Sync + Send>,
|
|
cache: &Cache,
|
|
message: view::SettingsMessage,
|
|
) -> Command<Message>;
|
|
fn view<'a>(&self, cache: &'a Cache, can_edit: bool) -> Element<'a, view::SettingsMessage>;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SettingsState {
|
|
warning: Option<Error>,
|
|
config_updated: bool,
|
|
daemon_is_external: bool,
|
|
daemon_version: Option<String>,
|
|
|
|
settings: Vec<Box<dyn Setting>>,
|
|
current: Option<usize>,
|
|
}
|
|
|
|
impl SettingsState {
|
|
pub fn new(config: Option<Config>, cache: &Cache, daemon_is_external: bool) -> Self {
|
|
let settings = if let Some(config) = &config {
|
|
vec![
|
|
BitcoindSettings::new(
|
|
config.bitcoin_config.clone(),
|
|
config.bitcoind_config.clone().unwrap(),
|
|
)
|
|
.into(),
|
|
RescanSetting::new(cache.rescan_progress).into(),
|
|
]
|
|
} else {
|
|
vec![RescanSetting::new(cache.rescan_progress).into()]
|
|
};
|
|
|
|
SettingsState {
|
|
daemon_version: if !daemon_is_external {
|
|
Some(liana::VERSION.to_string())
|
|
} else {
|
|
None
|
|
},
|
|
daemon_is_external,
|
|
warning: None,
|
|
config_updated: false,
|
|
settings,
|
|
// If a scan is running, the current setting edited is the Rescan panel.
|
|
current: cache.rescan_progress.map(|_| 1),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl State for SettingsState {
|
|
fn update(
|
|
&mut self,
|
|
daemon: Arc<dyn Daemon + Sync + Send>,
|
|
cache: &Cache,
|
|
message: Message,
|
|
) -> Command<Message> {
|
|
match message {
|
|
Message::DaemonConfigLoaded(res) => match res {
|
|
Ok(()) => {
|
|
self.config_updated = true;
|
|
self.warning = None;
|
|
if let Some(current) = self.current {
|
|
if let Some(setting) = self.settings.get_mut(current) {
|
|
setting.edited(true)
|
|
}
|
|
}
|
|
self.current = None;
|
|
}
|
|
Err(e) => {
|
|
self.config_updated = false;
|
|
self.warning = Some(e);
|
|
if let Some(current) = self.current {
|
|
if let Some(setting) = self.settings.get_mut(current) {
|
|
setting.edited(false);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Message::Info(res) => match res {
|
|
Err(e) => self.warning = Some(e),
|
|
Ok(info) => {
|
|
self.daemon_version = Some(info.version);
|
|
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 {
|
|
view::SettingsMessage::Edit => self.current = Some(i),
|
|
view::SettingsMessage::CancelEdit => self.current = None,
|
|
_ => {}
|
|
};
|
|
return setting.update(daemon, cache, msg);
|
|
}
|
|
}
|
|
_ => {}
|
|
};
|
|
Command::none()
|
|
}
|
|
|
|
fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
|
|
let can_edit = self.current.is_none() && !self.daemon_is_external;
|
|
view::settings::list(
|
|
self.daemon_version.as_ref(),
|
|
cache,
|
|
self.warning.as_ref(),
|
|
self.settings
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, setting)| {
|
|
setting
|
|
.view(cache, can_edit)
|
|
.map(move |msg| view::Message::Settings(i, msg))
|
|
})
|
|
.collect(),
|
|
)
|
|
}
|
|
|
|
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
|
if self.daemon_version.is_none() {
|
|
Command::perform(
|
|
async move { daemon.get_info().map_err(|e| e.into()) },
|
|
Message::Info,
|
|
)
|
|
} else {
|
|
Command::none()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<SettingsState> for Box<dyn State> {
|
|
fn from(s: SettingsState) -> Box<dyn State> {
|
|
Box::new(s)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BitcoindSettings {
|
|
bitcoind_config: BitcoindConfig,
|
|
bitcoin_config: BitcoinConfig,
|
|
edit: bool,
|
|
processing: bool,
|
|
cookie_path: form::Value<String>,
|
|
addr: form::Value<String>,
|
|
}
|
|
|
|
impl From<BitcoindSettings> for Box<dyn Setting> {
|
|
fn from(s: BitcoindSettings) -> Box<dyn Setting> {
|
|
Box::new(s)
|
|
}
|
|
}
|
|
|
|
impl BitcoindSettings {
|
|
fn new(bitcoin_config: BitcoinConfig, bitcoind_config: BitcoindConfig) -> BitcoindSettings {
|
|
let path = bitcoind_config.cookie_path.to_str().unwrap().to_string();
|
|
let addr = bitcoind_config.addr.to_string();
|
|
BitcoindSettings {
|
|
bitcoind_config,
|
|
bitcoin_config,
|
|
edit: false,
|
|
processing: false,
|
|
cookie_path: form::Value {
|
|
valid: true,
|
|
value: path,
|
|
},
|
|
addr: form::Value {
|
|
valid: true,
|
|
value: addr,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Setting for BitcoindSettings {
|
|
fn edited(&mut self, success: bool) {
|
|
self.processing = false;
|
|
if success {
|
|
self.edit = false;
|
|
}
|
|
}
|
|
|
|
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 {
|
|
match field {
|
|
"socket_address" => self.addr.value = value,
|
|
"cookie_file_path" => self.cookie_path.value = value,
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
view::SettingsMessage::ConfirmEdit => {
|
|
let new_addr = SocketAddr::from_str(&self.addr.value);
|
|
self.addr.valid = new_addr.is_ok();
|
|
let new_path = PathBuf::from_str(&self.cookie_path.value);
|
|
self.cookie_path.valid = new_path.is_ok();
|
|
|
|
if self.addr.valid & self.cookie_path.valid {
|
|
let mut daemon_config = daemon.config().cloned().unwrap();
|
|
daemon_config.bitcoind_config = Some(liana::config::BitcoindConfig {
|
|
cookie_path: new_path.unwrap(),
|
|
addr: new_addr.unwrap(),
|
|
});
|
|
self.processing = true;
|
|
return Command::perform(async move { daemon_config }, |cfg| {
|
|
Message::LoadDaemonConfig(Box::new(cfg))
|
|
});
|
|
}
|
|
}
|
|
};
|
|
Command::none()
|
|
}
|
|
|
|
fn view<'a>(&self, cache: &'a Cache, can_edit: bool) -> Element<'a, view::SettingsMessage> {
|
|
if self.edit {
|
|
view::settings::bitcoind_edit(
|
|
self.bitcoin_config.network,
|
|
cache.blockheight,
|
|
&self.addr,
|
|
&self.cookie_path,
|
|
self.processing,
|
|
)
|
|
} else {
|
|
view::settings::bitcoind(
|
|
self.bitcoin_config.network,
|
|
&self.bitcoind_config,
|
|
cache.blockheight,
|
|
Some(cache.blockheight != 0),
|
|
can_edit,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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, 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,
|
|
)
|
|
}
|
|
}
|