238 lines
7.8 KiB
Rust
238 lines
7.8 KiB
Rust
mod config;
|
|
mod message;
|
|
mod step;
|
|
mod view;
|
|
|
|
use iced::pure::Element;
|
|
use iced::{clipboard, Command, Subscription};
|
|
use iced_native::{window, Event};
|
|
use minisafe::miniscript::bitcoin;
|
|
|
|
use std::convert::TryInto;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
|
|
use crate::{
|
|
app::config as gui_config, hw::HardwareWalletConfig, installer::config::DEFAULT_FILE_NAME,
|
|
};
|
|
|
|
pub use message::Message;
|
|
use step::{Context, DefineBitcoind, DefineDescriptor, Final, RegisterDescriptor, Step, Welcome};
|
|
|
|
pub struct Installer {
|
|
should_exit: bool,
|
|
current: usize,
|
|
steps: Vec<Box<dyn Step>>,
|
|
|
|
/// Context is data passed through each step.
|
|
context: Context,
|
|
}
|
|
|
|
impl Installer {
|
|
fn next(&mut self) {
|
|
if self.current < self.steps.len() - 1 {
|
|
self.current += 1;
|
|
}
|
|
}
|
|
|
|
fn previous(&mut self) {
|
|
if self.current > 0 {
|
|
self.current -= 1;
|
|
}
|
|
}
|
|
|
|
pub fn new(
|
|
destination_path: PathBuf,
|
|
network: bitcoin::Network,
|
|
) -> (Installer, Command<Message>) {
|
|
(
|
|
Installer {
|
|
should_exit: false,
|
|
current: 0,
|
|
steps: vec![
|
|
Welcome::new(network, destination_path.clone()).into(),
|
|
DefineDescriptor::new().into(),
|
|
RegisterDescriptor::default().into(),
|
|
DefineBitcoind::new().into(),
|
|
Final::new().into(),
|
|
],
|
|
context: Context::new(network, Some(destination_path)),
|
|
},
|
|
Command::none(),
|
|
)
|
|
}
|
|
|
|
pub fn subscription(&self) -> Subscription<Message> {
|
|
iced_native::subscription::events().map(Message::Event)
|
|
}
|
|
|
|
pub fn should_exit(&self) -> bool {
|
|
self.should_exit
|
|
}
|
|
|
|
pub fn stop(&mut self) {
|
|
self.should_exit = true;
|
|
}
|
|
|
|
pub fn update(&mut self, message: Message) -> Command<Message> {
|
|
match message {
|
|
Message::Clibpboard(s) => clipboard::write(s),
|
|
Message::Next => {
|
|
let current_step = self
|
|
.steps
|
|
.get_mut(self.current)
|
|
.expect("There is always a step");
|
|
if current_step.apply(&mut self.context) {
|
|
self.next();
|
|
// skip the step according to the current context.
|
|
while self
|
|
.steps
|
|
.get(self.current)
|
|
.expect("There is always a step")
|
|
.skip(&self.context)
|
|
{
|
|
self.next();
|
|
}
|
|
// calculate new current_step.
|
|
let current_step = self
|
|
.steps
|
|
.get_mut(self.current)
|
|
.expect("There is always a step");
|
|
current_step.load_context(&self.context);
|
|
return current_step.load();
|
|
}
|
|
Command::none()
|
|
}
|
|
Message::Previous => {
|
|
self.previous();
|
|
Command::none()
|
|
}
|
|
Message::Install => {
|
|
self.steps
|
|
.get_mut(self.current)
|
|
.expect("There is always a step")
|
|
.update(message);
|
|
Command::perform(install(self.context.clone()), Message::Installed)
|
|
}
|
|
Message::Installed(Err(e)) => {
|
|
let mut data_dir = self.context.data_dir.clone().unwrap();
|
|
data_dir.push(self.context.bitcoin_config.network.to_string());
|
|
// In case of failure during install, block the thread to
|
|
// deleted the data_dir/network directory in order to start clean again.
|
|
std::fs::remove_dir_all(data_dir).expect("Correctly deleted");
|
|
self.steps
|
|
.get_mut(self.current)
|
|
.expect("There is always a step")
|
|
.update(Message::Installed(Err(e)));
|
|
Command::none()
|
|
}
|
|
Message::Event(Event::Window(window::Event::CloseRequested)) => {
|
|
self.stop();
|
|
Command::none()
|
|
}
|
|
_ => self
|
|
.steps
|
|
.get_mut(self.current)
|
|
.expect("There is always a step")
|
|
.update(message),
|
|
}
|
|
}
|
|
|
|
pub fn view(&self) -> Element<Message> {
|
|
self.steps
|
|
.get(self.current)
|
|
.expect("There is always a step")
|
|
.view()
|
|
}
|
|
}
|
|
|
|
pub async fn install(ctx: Context) -> Result<PathBuf, Error> {
|
|
let hardware_wallets = ctx
|
|
.hw_tokens
|
|
.iter()
|
|
.map(|(kind, fingerprint, token)| HardwareWalletConfig::new(kind, fingerprint, token))
|
|
.collect();
|
|
|
|
let mut cfg: minisafe::config::Config = ctx
|
|
.try_into()
|
|
.expect("Everything should be checked at this point");
|
|
// Start Daemon to check correctness of installation
|
|
let daemon = minisafe::DaemonHandle::start_default(cfg.clone()).map_err(|e| {
|
|
Error::Unexpected(format!("Failed to start daemon with entered config: {}", e))
|
|
})?;
|
|
daemon.shutdown();
|
|
|
|
cfg.data_dir =
|
|
Some(cfg.data_dir.unwrap().canonicalize().map_err(|e| {
|
|
Error::Unexpected(format!("Failed to canonicalize datadir path: {}", e))
|
|
})?);
|
|
|
|
let mut datadir_path = cfg.data_dir.clone().unwrap();
|
|
datadir_path.push(cfg.bitcoin_config.network.to_string());
|
|
|
|
// create minisafed configuration file
|
|
let mut minisafed_config_path = datadir_path.clone();
|
|
minisafed_config_path.push(DEFAULT_FILE_NAME);
|
|
let mut minisafed_config_file = std::fs::File::create(&minisafed_config_path)
|
|
.map_err(|e| Error::CannotCreateFile(e.to_string()))?;
|
|
|
|
// Step needed because of ValueAfterTable error in the toml serialize implementation.
|
|
let minisafed_config =
|
|
toml::Value::try_from(&cfg).expect("daemon::Config has a proper Serialize implementation");
|
|
|
|
minisafed_config_file
|
|
.write_all(minisafed_config.to_string().as_bytes())
|
|
.map_err(|e| Error::CannotWriteToFile(e.to_string()))?;
|
|
|
|
// create minisafe GUI configuration file
|
|
let mut gui_config_path = datadir_path;
|
|
gui_config_path.push(gui_config::DEFAULT_FILE_NAME);
|
|
let mut gui_config_file = std::fs::File::create(&gui_config_path)
|
|
.map_err(|e| Error::CannotCreateFile(e.to_string()))?;
|
|
|
|
gui_config_file
|
|
.write_all(
|
|
toml::to_string(&gui_config::Config::new(
|
|
minisafed_config_path.canonicalize().map_err(|e| {
|
|
Error::Unexpected(format!(
|
|
"Failed to canonicalize minisafed config path: {}",
|
|
e
|
|
))
|
|
})?,
|
|
hardware_wallets,
|
|
))
|
|
.unwrap()
|
|
.as_bytes(),
|
|
)
|
|
.map_err(|e| Error::CannotWriteToFile(e.to_string()))?;
|
|
|
|
Ok(gui_config_path)
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Error {
|
|
CannotCreateDatadir(String),
|
|
CannotCreateFile(String),
|
|
CannotWriteToFile(String),
|
|
Unexpected(String),
|
|
HardwareWallet(async_hwi::Error),
|
|
}
|
|
|
|
impl From<async_hwi::Error> for Error {
|
|
fn from(error: async_hwi::Error) -> Self {
|
|
Error::HardwareWallet(error)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Error {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
Self::CannotCreateDatadir(e) => write!(f, "Failed to create datadir: {}", e),
|
|
Self::CannotWriteToFile(e) => write!(f, "Failed to write to file: {}", e),
|
|
Self::CannotCreateFile(e) => write!(f, "Failed to create file: {}", e),
|
|
Self::Unexpected(e) => write!(f, "Unexpected: {}", e),
|
|
Self::HardwareWallet(e) => write!(f, "Hardware Wallet: {}", e),
|
|
}
|
|
}
|
|
}
|