refac delete wallet modal and desc backup
This commit is contained in:
parent
9a6218b3f1
commit
30228c6490
@ -3,6 +3,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_fd_lock::LockWrite;
|
||||
use liana::descriptors::LianaDescriptor;
|
||||
use std::io::SeekFrom;
|
||||
use tokio::fs::OpenOptions;
|
||||
use tokio::io::AsyncSeekExt;
|
||||
@ -190,11 +191,49 @@ impl WalletSettings {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wallet_id(&self) -> String {
|
||||
if let Some(t) = self.pinned_at {
|
||||
format!("{}-{}", self.descriptor_checksum, t)
|
||||
pub fn wallet_id(&self) -> WalletId {
|
||||
WalletId {
|
||||
timestamp: self.pinned_at,
|
||||
descriptor_checksum: self.descriptor_checksum.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WalletId {
|
||||
pub timestamp: Option<i64>,
|
||||
pub descriptor_checksum: String,
|
||||
}
|
||||
|
||||
impl WalletId {
|
||||
pub fn new(descriptor_checksum: String, timestamp: Option<i64>) -> Self {
|
||||
WalletId {
|
||||
timestamp,
|
||||
descriptor_checksum,
|
||||
}
|
||||
}
|
||||
pub fn generate(descriptor: &LianaDescriptor) -> Self {
|
||||
WalletId {
|
||||
timestamp: Some(chrono::Utc::now().timestamp()),
|
||||
descriptor_checksum: descriptor
|
||||
.to_string()
|
||||
.split_once('#')
|
||||
.map(|(_, checksum)| checksum)
|
||||
.expect("LianaDescriptor.to_string() always include the checksum")
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
self.timestamp.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WalletId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(t) = self.timestamp {
|
||||
write!(f, "{}-{}", self.descriptor_checksum, t)
|
||||
} else {
|
||||
self.descriptor_checksum.clone()
|
||||
write!(f, "{}", self.descriptor_checksum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ use liana::{miniscript::bitcoin, signer::HotSigner};
|
||||
use liana::descriptors::LianaDescriptor;
|
||||
use liana::miniscript::bitcoin::bip32::Fingerprint;
|
||||
|
||||
use super::settings::WalletSettings;
|
||||
use super::settings::{WalletId, WalletSettings};
|
||||
|
||||
const DEFAULT_WALLET_NAME: &str = "Liana";
|
||||
|
||||
@ -72,12 +72,8 @@ impl Wallet {
|
||||
}
|
||||
|
||||
// To match with WalletSettings.wallet_id
|
||||
pub fn id(&self) -> String {
|
||||
if let Some(t) = self.pinned_at {
|
||||
format!("{}-{}", self.descriptor_checksum, t)
|
||||
} else {
|
||||
self.descriptor_checksum.clone()
|
||||
}
|
||||
pub fn id(&self) -> WalletId {
|
||||
WalletId::new(self.descriptor_checksum.clone(), self.pinned_at)
|
||||
}
|
||||
|
||||
pub fn with_pinned_at(mut self, pinned_at: Option<i64>) -> Self {
|
||||
|
||||
@ -20,16 +20,13 @@ use tokio::sync::mpsc::UnboundedSender;
|
||||
use crate::{
|
||||
app::{
|
||||
settings::{Settings, WalletSettings},
|
||||
wallet::Wallet,
|
||||
wallet::{wallet_name, Wallet},
|
||||
Config,
|
||||
},
|
||||
daemon::{model::HistoryTransaction, Daemon, DaemonBackend, DaemonError},
|
||||
dir::LianaDirectory,
|
||||
export::Progress,
|
||||
installer::{
|
||||
extract_daemon_config, extract_local_gui_settings, extract_remote_gui_settings, Context,
|
||||
RemoteBackend,
|
||||
},
|
||||
installer::Context,
|
||||
services::connect::client::backend::api::DEFAULT_LIMIT,
|
||||
VERSION,
|
||||
};
|
||||
@ -116,54 +113,23 @@ impl Backup {
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ctx` - the installer context
|
||||
/// * `timestamp` - whether to record the current timestamp as wallet creation time
|
||||
/// (we should want to set timestamp = false for a wallet import for instance)
|
||||
pub async fn from_installer(ctx: Context, timestamp: bool) -> Result<Self, Error> {
|
||||
let descriptor = ctx
|
||||
.descriptor
|
||||
.clone()
|
||||
.ok_or(Error::DescriptorMissing)?
|
||||
.to_string();
|
||||
pub async fn from_installer_descriptor_step(ctx: Context) -> Result<Self, Error> {
|
||||
let descriptor = ctx.descriptor.clone().ok_or(Error::DescriptorMissing)?;
|
||||
|
||||
let now = now();
|
||||
let name = Some(wallet_name(&descriptor));
|
||||
|
||||
let mut account = Account::new(descriptor);
|
||||
|
||||
let mut proprietary = serde_json::Map::new();
|
||||
proprietary.insert(LIANA_VERSION_KEY.to_string(), liana_version().into());
|
||||
|
||||
let settings = match &ctx.remote_backend {
|
||||
// This append while user is importing a wallet already created on Liana-Connect.
|
||||
RemoteBackend::WithWallet(backend) => extract_remote_gui_settings(&ctx, backend).await,
|
||||
// Other cases are about wallet creation, the ctx contains all the keys aliases and
|
||||
// descriptor registration hmacs.
|
||||
_ => extract_local_gui_settings(&ctx),
|
||||
};
|
||||
|
||||
let config =
|
||||
extract_daemon_config(&ctx, &settings).map_err(|e| Error::Daemon(e.to_string()))?;
|
||||
if let Ok(config) = serde_json::to_value(config) {
|
||||
proprietary.insert(CONFIG_KEY.to_string(), config);
|
||||
}
|
||||
|
||||
let name = {
|
||||
let name = settings.name.clone();
|
||||
if let Ok(settings) = serde_json::to_value(settings) {
|
||||
proprietary.insert(SETTINGS_KEY.to_string(), settings);
|
||||
}
|
||||
Some(name)
|
||||
};
|
||||
let mut account = Account::new(descriptor.to_string());
|
||||
account.name = name.clone();
|
||||
account.timestamp = Some(now);
|
||||
account
|
||||
.proprietary
|
||||
.insert(LIANA_VERSION_KEY.to_string(), liana_version().into());
|
||||
|
||||
ctx.keys.iter().for_each(|(k, s)| {
|
||||
account.keys.insert(*k, s.to_backup());
|
||||
});
|
||||
|
||||
account.proprietary = proprietary;
|
||||
account.name = name.clone();
|
||||
if timestamp {
|
||||
account.timestamp = Some(now);
|
||||
}
|
||||
|
||||
Ok(Backup {
|
||||
name,
|
||||
accounts: vec![account],
|
||||
|
||||
82
liana-gui/src/delete.rs
Normal file
82
liana-gui/src/delete.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
app::settings::{self, SettingsError, WalletId},
|
||||
dir::NetworkDirectory,
|
||||
services::connect::client::cache::{self, ConnectCacheError},
|
||||
};
|
||||
|
||||
pub enum DeleteError {
|
||||
Io(std::io::Error),
|
||||
Settings(SettingsError),
|
||||
Connect(ConnectCacheError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DeleteError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Io(e) => write!(f, "{}", e),
|
||||
Self::Settings(e) => write!(f, "{}", e),
|
||||
Self::Connect(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for DeleteError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
DeleteError::Io(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn ignore_not_found<T>(result: std::io::Result<T>) -> std::io::Result<Option<T>> {
|
||||
match result {
|
||||
Ok(value) => Ok(Some(value)),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_wallet(
|
||||
network_dir: &NetworkDirectory,
|
||||
wallet_id: &WalletId,
|
||||
) -> Result<(), DeleteError> {
|
||||
let lianad_directory = network_dir.lianad_data_directory(wallet_id);
|
||||
if !wallet_id.is_legacy() {
|
||||
ignore_not_found(tokio::fs::remove_dir_all(lianad_directory.path()).await)?;
|
||||
} else {
|
||||
// if this is a legacy wallet, then it is the only wallet in the network directory.
|
||||
ignore_not_found(tokio::fs::remove_file(lianad_directory.sqlite_db_file_path()).await)?;
|
||||
ignore_not_found(
|
||||
tokio::fs::remove_dir_all(lianad_directory.lianad_watchonly_wallet_path()).await,
|
||||
)?;
|
||||
ignore_not_found(
|
||||
tokio::fs::remove_file(lianad_directory.path().join("daemon.toml")).await,
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut remaining_accounts = HashSet::<String>::new();
|
||||
settings::update_settings_file(network_dir, |mut settings| {
|
||||
settings
|
||||
.wallets
|
||||
.retain(|settings| settings.wallet_id() != *wallet_id);
|
||||
remaining_accounts = settings
|
||||
.wallets
|
||||
.iter()
|
||||
.filter_map(|settings| {
|
||||
settings
|
||||
.remote_backend_auth
|
||||
.as_ref()
|
||||
.map(|auth| auth.email.clone())
|
||||
})
|
||||
.collect();
|
||||
settings
|
||||
})
|
||||
.await
|
||||
.map_err(DeleteError::Settings)?;
|
||||
|
||||
cache::filter_connect_cache(network_dir, &remaining_accounts)
|
||||
.await
|
||||
.map_err(DeleteError::Connect)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::app::settings::WalletSettings;
|
||||
use crate::app::settings::WalletId;
|
||||
use liana::miniscript::bitcoin::Network;
|
||||
use lianad::datadir::DataDirectory;
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -86,11 +86,11 @@ impl NetworkDirectory {
|
||||
pub fn path(&self) -> &Path {
|
||||
self.0.as_path()
|
||||
}
|
||||
pub fn lianad_data_directory(&self, settings: &WalletSettings) -> DataDirectory {
|
||||
pub fn lianad_data_directory(&self, wallet_id: &WalletId) -> DataDirectory {
|
||||
let mut path = self.0.clone();
|
||||
if let Some(t) = settings.pinned_at {
|
||||
if !wallet_id.is_legacy() {
|
||||
path.push("data");
|
||||
path.push(format!("{}-{}", settings.descriptor_checksum, t))
|
||||
path.push(wallet_id.to_string());
|
||||
}
|
||||
DataDirectory::new(path)
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ pub enum Message {
|
||||
Reload,
|
||||
Select(usize),
|
||||
UseHotSigner,
|
||||
Installed(Result<settings::WalletSettings, Error>),
|
||||
Installed(settings::WalletId, Result<settings::WalletSettings, Error>),
|
||||
CreateTaprootDescriptor(bool),
|
||||
SelectDescriptorTemplate(context::DescriptorTemplate),
|
||||
SelectBackend(SelectBackend),
|
||||
|
||||
@ -14,6 +14,7 @@ use liana_ui::{
|
||||
};
|
||||
use lianad::config::Config;
|
||||
use std::ops::Deref;
|
||||
use tokio::runtime::Handle;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use std::io::Write;
|
||||
@ -23,11 +24,12 @@ use std::sync::{Arc, Mutex};
|
||||
use crate::{
|
||||
app::{
|
||||
config as gui_config,
|
||||
settings::{update_settings_file, AuthConfig, SettingsError, WalletSettings},
|
||||
settings::{update_settings_file, AuthConfig, SettingsError, WalletId, WalletSettings},
|
||||
wallet::wallet_name,
|
||||
},
|
||||
backup,
|
||||
daemon::DaemonError,
|
||||
delete,
|
||||
dir::LianaDirectory,
|
||||
hw::{HardwareWalletConfig, HardwareWallets},
|
||||
services::{
|
||||
@ -252,50 +254,65 @@ impl Installer {
|
||||
.get_mut(self.current)
|
||||
.expect("There is always a step")
|
||||
.update(&mut self.hws, message);
|
||||
let wallet_id = WalletId::generate(
|
||||
self.context
|
||||
.descriptor
|
||||
.as_ref()
|
||||
.expect("Must be a descriptor at this point"),
|
||||
);
|
||||
let context = self.context.clone();
|
||||
let signer = self.signer.clone();
|
||||
match &self.context.remote_backend {
|
||||
RemoteBackend::WithoutWallet(backend) => Task::perform(
|
||||
create_remote_wallet(
|
||||
self.context.clone(),
|
||||
self.signer.clone(),
|
||||
backend.clone(),
|
||||
with_wallet_id(
|
||||
wallet_id.clone(),
|
||||
create_remote_wallet(context, wallet_id, signer, backend.clone()),
|
||||
),
|
||||
Message::Installed,
|
||||
|(id, res)| Message::Installed(id, res),
|
||||
),
|
||||
RemoteBackend::WithWallet(backend) => Task::perform(
|
||||
import_remote_wallet(self.context.clone(), backend.clone()),
|
||||
Message::Installed,
|
||||
with_wallet_id(
|
||||
wallet_id.clone(),
|
||||
import_remote_wallet(context, wallet_id, backend.clone()),
|
||||
),
|
||||
|(id, res)| Message::Installed(id, res),
|
||||
),
|
||||
RemoteBackend::None => Task::perform(
|
||||
install_local_wallet(self.context.clone(), self.signer.clone()),
|
||||
Message::Installed,
|
||||
with_wallet_id(
|
||||
wallet_id.clone(),
|
||||
install_local_wallet(context, wallet_id, signer),
|
||||
),
|
||||
|(id, res)| Message::Installed(id, res),
|
||||
),
|
||||
RemoteBackend::Undefined => unreachable!("Must be defined at this point"),
|
||||
}
|
||||
}
|
||||
Message::Installed(Err(e)) => {
|
||||
Message::Installed(wallet_id, Err(e)) => {
|
||||
let network_directory = self
|
||||
.context
|
||||
.liana_directory
|
||||
.network_directory(self.context.bitcoin_config.network);
|
||||
// In case of failure during install, block the thread to
|
||||
// deleted the data_dir/network directory in order to start clean again.
|
||||
warn!("Installation failed. Cleaning up the leftover data directory.");
|
||||
if let Err(e) = std::fs::remove_dir_all(network_directory.path()) {
|
||||
warn!("Installation failed. Cleaning up the network directory.");
|
||||
if let Err(e) = Handle::current()
|
||||
.block_on(delete::delete_wallet(&network_directory, &wallet_id))
|
||||
{
|
||||
error!(
|
||||
"Failed to completely delete the data directory (path: '{}'): {}",
|
||||
"Failed to completely clean the network directory (path: '{}'): {}",
|
||||
network_directory.path().to_string_lossy(),
|
||||
e
|
||||
);
|
||||
} else {
|
||||
warn!(
|
||||
"Successfully deleted data directory at '{}'.",
|
||||
"Successfully cleaned network directory at '{}'.",
|
||||
network_directory.path().to_string_lossy()
|
||||
);
|
||||
};
|
||||
self.steps
|
||||
.get_mut(self.current)
|
||||
.expect("There is always a step")
|
||||
.update(&mut self.hws, Message::Installed(Err(e)))
|
||||
.update(&mut self.hws, Message::Installed(wallet_id, Err(e)))
|
||||
}
|
||||
Message::WalletFromBackup((ks, backup)) => {
|
||||
self.context.keys = ks;
|
||||
@ -359,8 +376,16 @@ pub fn daemon_check(cfg: lianad::config::Config) -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn with_wallet_id<F>(wallet_id: WalletId, res: F) -> (WalletId, Result<WalletSettings, Error>)
|
||||
where
|
||||
F: std::future::Future<Output = Result<WalletSettings, Error>>,
|
||||
{
|
||||
(wallet_id, res.await)
|
||||
}
|
||||
|
||||
pub async fn install_local_wallet(
|
||||
ctx: Context,
|
||||
wallet_id: WalletId,
|
||||
signer: Arc<Mutex<Signer>>,
|
||||
) -> Result<WalletSettings, Error> {
|
||||
let network_datadir = ctx
|
||||
@ -370,7 +395,31 @@ pub async fn install_local_wallet(
|
||||
.init()
|
||||
.map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?;
|
||||
|
||||
let wallet_settings = extract_local_gui_settings(&ctx);
|
||||
let descriptor = ctx
|
||||
.descriptor
|
||||
.as_ref()
|
||||
.expect("Context must have a descriptor at this point");
|
||||
|
||||
let hardware_wallets = ctx
|
||||
.hws
|
||||
.iter()
|
||||
.filter_map(|(kind, fingerprint, token)| {
|
||||
token
|
||||
.as_ref()
|
||||
.map(|token| HardwareWalletConfig::new(kind, *fingerprint, token))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let wallet_settings = WalletSettings {
|
||||
name: wallet_name(descriptor),
|
||||
pinned_at: wallet_id.timestamp,
|
||||
descriptor_checksum: wallet_id.descriptor_checksum,
|
||||
keys: ctx.keys.values().cloned().collect(),
|
||||
hardware_wallets,
|
||||
remote_backend_auth: None,
|
||||
start_internal_bitcoind: Some(ctx.internal_bitcoind.is_some()),
|
||||
};
|
||||
|
||||
let cfg: lianad::config::Config = extract_daemon_config(&ctx, &wallet_settings)?;
|
||||
|
||||
daemon_check(cfg.clone())?;
|
||||
@ -384,7 +433,7 @@ pub async fn install_local_wallet(
|
||||
// create lianad configuration file
|
||||
create_and_write_file(
|
||||
&network_datadir
|
||||
.lianad_data_directory(&wallet_settings)
|
||||
.lianad_data_directory(&wallet_settings.wallet_id())
|
||||
.path()
|
||||
.join("daemon.toml"),
|
||||
daemon_config.to_string().as_bytes(),
|
||||
@ -447,6 +496,7 @@ pub async fn install_local_wallet(
|
||||
|
||||
pub async fn create_remote_wallet(
|
||||
ctx: Context,
|
||||
wallet_id: WalletId,
|
||||
signer: Arc<Mutex<Signer>>,
|
||||
remote_backend: BackendClient,
|
||||
) -> Result<WalletSettings, Error> {
|
||||
@ -545,7 +595,20 @@ pub async fn create_remote_wallet(
|
||||
let remote_backend = remote_backend.connect_wallet(wallet).0;
|
||||
|
||||
// create liana GUI settings file
|
||||
let wallet_settings = extract_remote_gui_settings(&ctx, &remote_backend).await;
|
||||
// if the wallet is using the remote backend, then the hardware wallet settings and
|
||||
// keys will be store on the remote backend side and not in the settings file.
|
||||
let wallet_settings = WalletSettings {
|
||||
name: wallet_name(descriptor),
|
||||
descriptor_checksum: wallet_id.descriptor_checksum,
|
||||
pinned_at: wallet_id.timestamp,
|
||||
keys: Vec::new(),
|
||||
hardware_wallets: Vec::new(),
|
||||
remote_backend_auth: Some(AuthConfig::new(
|
||||
remote_backend.user_email().to_string(),
|
||||
remote_backend.wallet_id(),
|
||||
)),
|
||||
start_internal_bitcoind: None,
|
||||
};
|
||||
update_settings_file(&network_datadir, |mut settings| {
|
||||
settings.wallets.push(wallet_settings.clone());
|
||||
settings
|
||||
@ -576,6 +639,7 @@ pub async fn create_remote_wallet(
|
||||
|
||||
pub async fn import_remote_wallet(
|
||||
ctx: Context,
|
||||
wallet_id: WalletId,
|
||||
backend: BackendWalletClient,
|
||||
) -> Result<WalletSettings, Error> {
|
||||
tracing::info!("Importing wallet from remote backend");
|
||||
@ -594,7 +658,24 @@ pub async fn import_remote_wallet(
|
||||
.map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?;
|
||||
|
||||
// create liana GUI settings file
|
||||
let wallet_settings = extract_remote_gui_settings(&ctx, &backend).await;
|
||||
// if the wallet is using the remote backend, then the hardware wallet settings and
|
||||
// keys will be store on the remote backend side and not in the settings file.
|
||||
let wallet_settings = WalletSettings {
|
||||
name: wallet_name(
|
||||
ctx.descriptor
|
||||
.as_ref()
|
||||
.expect("Context must have a descriptor at this point"),
|
||||
),
|
||||
descriptor_checksum: wallet_id.descriptor_checksum,
|
||||
pinned_at: wallet_id.timestamp,
|
||||
keys: Vec::new(),
|
||||
hardware_wallets: Vec::new(),
|
||||
remote_backend_auth: Some(AuthConfig::new(
|
||||
backend.user_email().to_string(),
|
||||
backend.wallet_id(),
|
||||
)),
|
||||
start_internal_bitcoind: None,
|
||||
};
|
||||
update_settings_file(&network_datadir, |mut settings| {
|
||||
settings.wallets.push(wallet_settings.clone());
|
||||
settings
|
||||
@ -646,76 +727,11 @@ pub fn create_and_write_file(path: &Path, data: &[u8]) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// if the wallet is using the remote backend, then the hardware wallet settings and
|
||||
// keys will be store on the remote backend side and not in the settings file.
|
||||
pub async fn extract_remote_gui_settings(
|
||||
ctx: &Context,
|
||||
backend: &BackendWalletClient,
|
||||
) -> WalletSettings {
|
||||
let descriptor = ctx
|
||||
.descriptor
|
||||
.as_ref()
|
||||
.expect("Context must have a descriptor at this point");
|
||||
|
||||
let descriptor_checksum = descriptor
|
||||
.to_string()
|
||||
.split_once('#')
|
||||
.map(|(_, checksum)| checksum)
|
||||
.expect("LianaDescriptor.to_string() always include the checksum")
|
||||
.to_string();
|
||||
|
||||
WalletSettings {
|
||||
name: wallet_name(descriptor),
|
||||
descriptor_checksum,
|
||||
pinned_at: Some(chrono::Utc::now().timestamp()),
|
||||
keys: Vec::new(),
|
||||
hardware_wallets: Vec::new(),
|
||||
remote_backend_auth: Some(AuthConfig::new(
|
||||
backend.user_email().to_string(),
|
||||
backend.wallet_id(),
|
||||
)),
|
||||
start_internal_bitcoind: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_local_gui_settings(ctx: &Context) -> WalletSettings {
|
||||
let descriptor = ctx
|
||||
.descriptor
|
||||
.as_ref()
|
||||
.expect("Context must have a descriptor at this point");
|
||||
|
||||
let descriptor_checksum = descriptor
|
||||
.to_string()
|
||||
.split_once('#')
|
||||
.map(|(_, checksum)| checksum)
|
||||
.expect("LianaDescriptor.to_string() always include the checksum")
|
||||
.to_string();
|
||||
|
||||
let hardware_wallets = ctx
|
||||
.hws
|
||||
.iter()
|
||||
.filter_map(|(kind, fingerprint, token)| {
|
||||
token
|
||||
.as_ref()
|
||||
.map(|token| HardwareWalletConfig::new(kind, *fingerprint, token))
|
||||
})
|
||||
.collect();
|
||||
WalletSettings {
|
||||
name: wallet_name(descriptor),
|
||||
pinned_at: Some(chrono::Utc::now().timestamp()),
|
||||
descriptor_checksum,
|
||||
keys: ctx.keys.values().cloned().collect(),
|
||||
hardware_wallets,
|
||||
remote_backend_auth: None,
|
||||
start_internal_bitcoind: Some(ctx.internal_bitcoind.is_some()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_daemon_config(ctx: &Context, settings: &WalletSettings) -> Result<Config, Error> {
|
||||
let data_directory = ctx
|
||||
.liana_directory
|
||||
.network_directory(ctx.bitcoin_config.network)
|
||||
.lianad_data_directory(settings);
|
||||
.lianad_data_directory(&settings.wallet_id());
|
||||
data_directory
|
||||
.init()
|
||||
.map_err(|e| Error::CannotCreateDatadir(e.to_string()))?;
|
||||
|
||||
@ -379,7 +379,7 @@ impl Step for BackupDescriptor {
|
||||
let ctx = ctx.clone();
|
||||
return Task::perform(
|
||||
async move {
|
||||
let backup = Backup::from_installer(ctx, true).await?;
|
||||
let backup = Backup::from_installer_descriptor_step(ctx).await?;
|
||||
serde_json::to_string_pretty(&backup).map_err(|_| backup::Error::Json)
|
||||
},
|
||||
Message::ExportWallet,
|
||||
|
||||
@ -151,7 +151,7 @@ impl Step for Final {
|
||||
},
|
||||
);
|
||||
}
|
||||
Message::Installed(res) => match res {
|
||||
Message::Installed(_, res) => match res {
|
||||
Err(e) => {
|
||||
self.generating = false;
|
||||
self.wallet_settings = None;
|
||||
|
||||
@ -11,9 +11,11 @@ use liana_ui::{
|
||||
widget::*,
|
||||
};
|
||||
use lianad::config::ConfigError;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::{
|
||||
app::{self, settings::WalletSettings},
|
||||
delete::{delete_wallet, DeleteError},
|
||||
dir::{LianaDirectory, NetworkDirectory},
|
||||
installer::UserFlow,
|
||||
};
|
||||
@ -94,19 +96,21 @@ impl Launcher {
|
||||
Message::Install(d, n, UserFlow::ShareXpubs)
|
||||
})
|
||||
}
|
||||
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::ShowModal)) => {
|
||||
let wallet_datadir = self.datadir_path.network_directory(self.network);
|
||||
let config_path = wallet_datadir.path().join(app::config::DEFAULT_FILE_NAME);
|
||||
let internal_bitcoind = if let Ok(cfg) = app::Config::from_file(&config_path) {
|
||||
Some(cfg.start_internal_bitcoind)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.delete_wallet_modal = Some(DeleteWalletModal::new(
|
||||
self.network,
|
||||
wallet_datadir,
|
||||
internal_bitcoind,
|
||||
));
|
||||
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::ShowModal(i))) => {
|
||||
if let State::Wallets { wallets, .. } = &self.state {
|
||||
let wallet_datadir = self.datadir_path.network_directory(self.network);
|
||||
let config_path = wallet_datadir.path().join(app::config::DEFAULT_FILE_NAME);
|
||||
let internal_bitcoind = if let Ok(cfg) = app::Config::from_file(&config_path) {
|
||||
Some(cfg.start_internal_bitcoind)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.delete_wallet_modal = Some(DeleteWalletModal::new(
|
||||
wallet_datadir,
|
||||
wallets[i].clone(),
|
||||
internal_bitcoind,
|
||||
));
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::View(ViewMessage::SelectNetwork(network)) => {
|
||||
@ -116,7 +120,8 @@ impl Launcher {
|
||||
}
|
||||
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) => {
|
||||
self.state = State::NoWallet;
|
||||
Task::none()
|
||||
let network_dir = self.datadir_path.network_directory(self.network);
|
||||
Task::perform(check_network_datadir(network_dir), Message::Checked)
|
||||
}
|
||||
|
||||
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::CloseModal)) => {
|
||||
@ -365,7 +370,7 @@ fn wallets_list_item(
|
||||
Button::new(icon::trash_icon())
|
||||
.style(theme::button::secondary)
|
||||
.padding(10)
|
||||
.on_press(ViewMessage::DeleteWallet(DeleteWalletMessage::ShowModal)),
|
||||
.on_press(ViewMessage::DeleteWallet(DeleteWalletMessage::ShowModal(i))),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
@ -395,16 +400,16 @@ pub enum ViewMessage {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeleteWalletMessage {
|
||||
ShowModal,
|
||||
ShowModal(usize),
|
||||
CloseModal,
|
||||
Confirm,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
struct DeleteWalletModal {
|
||||
network: Network,
|
||||
wallet_datadir: NetworkDirectory,
|
||||
warning: Option<std::io::Error>,
|
||||
network_directory: NetworkDirectory,
|
||||
wallet_settings: WalletSettings,
|
||||
warning: Option<DeleteError>,
|
||||
deleted: bool,
|
||||
// `None` means we were not able to determine whether wallet uses internal bitcoind.
|
||||
internal_bitcoind: Option<bool>,
|
||||
@ -412,13 +417,13 @@ struct DeleteWalletModal {
|
||||
|
||||
impl DeleteWalletModal {
|
||||
fn new(
|
||||
network: Network,
|
||||
wallet_datadir: NetworkDirectory,
|
||||
network_directory: NetworkDirectory,
|
||||
wallet_settings: WalletSettings,
|
||||
internal_bitcoind: Option<bool>,
|
||||
) -> Self {
|
||||
Self {
|
||||
network,
|
||||
wallet_datadir,
|
||||
wallet_settings,
|
||||
network_directory,
|
||||
warning: None,
|
||||
deleted: false,
|
||||
internal_bitcoind,
|
||||
@ -428,7 +433,10 @@ impl DeleteWalletModal {
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
if let Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Confirm)) = message {
|
||||
self.warning = None;
|
||||
if let Err(e) = std::fs::remove_dir_all(self.wallet_datadir.path()) {
|
||||
if let Err(e) = Handle::current().block_on(delete_wallet(
|
||||
&self.network_directory,
|
||||
&self.wallet_settings.wallet_id(),
|
||||
)) {
|
||||
self.warning = Some(e);
|
||||
} else {
|
||||
self.deleted = true;
|
||||
@ -439,6 +447,7 @@ impl DeleteWalletModal {
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let mut confirm_button = button::secondary(None, "Delete wallet")
|
||||
.width(Length::Fixed(200.0))
|
||||
@ -449,8 +458,8 @@ impl DeleteWalletModal {
|
||||
}
|
||||
// Use separate `Row`s for help text in order to have better spacing.
|
||||
let help_text_1 = format!(
|
||||
"Are you sure you want to delete the configuration and all associated data for the network {}?",
|
||||
&self.network
|
||||
"Are you sure you want to delete the configuration and all associated data for the wallet Liana-{}?",
|
||||
&self.wallet_settings.descriptor_checksum,
|
||||
);
|
||||
let help_text_2 = match self.internal_bitcoind {
|
||||
Some(true) => Some("(The Liana-managed Bitcoin node for this network will not be affected by this action.)"),
|
||||
@ -464,9 +473,12 @@ impl DeleteWalletModal {
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push(Container::new(
|
||||
h4_bold(format!("Delete configuration for {}", &self.network))
|
||||
.style(theme::text::destructive)
|
||||
.width(Length::Fill),
|
||||
h4_bold(format!(
|
||||
"Delete configuration for Liana-{}",
|
||||
&self.wallet_settings.descriptor_checksum
|
||||
))
|
||||
.style(theme::text::destructive)
|
||||
.width(Length::Fill),
|
||||
))
|
||||
.push(Row::new().push(text(help_text_1)))
|
||||
.push_maybe(
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
pub mod app;
|
||||
pub mod backup;
|
||||
pub mod daemon;
|
||||
pub mod delete;
|
||||
pub mod dir;
|
||||
pub mod download;
|
||||
pub mod export;
|
||||
|
||||
@ -123,7 +123,7 @@ impl Loader {
|
||||
) -> (Self, Task<Message>) {
|
||||
let socket_path = datadir_path
|
||||
.network_directory(network)
|
||||
.lianad_data_directory(&wallet_settings)
|
||||
.lianad_data_directory(&wallet_settings.wallet_id())
|
||||
.lianad_rpc_socket_path();
|
||||
(
|
||||
Loader {
|
||||
@ -559,7 +559,7 @@ pub async fn start_bitcoind_and_daemon(
|
||||
) -> StartedResult {
|
||||
let mut config_path = liana_datadir_path
|
||||
.network_directory(network)
|
||||
.lianad_data_directory(&settings)
|
||||
.lianad_data_directory(&settings.wallet_id())
|
||||
.path()
|
||||
.to_path_buf();
|
||||
config_path.push("daemon.toml");
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::dir::NetworkDirectory;
|
||||
use async_fd_lock::LockWrite;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::io::SeekFrom;
|
||||
use tokio::fs::OpenOptions;
|
||||
use tokio::io::AsyncSeekExt;
|
||||
@ -138,6 +139,68 @@ pub async fn update_connect_cache(
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
pub async fn filter_connect_cache(
|
||||
network_dir: &NetworkDirectory,
|
||||
emails: &HashSet<String>,
|
||||
) -> Result<(), ConnectCacheError> {
|
||||
let mut path = network_dir.path().to_path_buf();
|
||||
path.push(CONNECT_CACHE_FILENAME);
|
||||
|
||||
let file_exists = tokio::fs::try_exists(&path).await.unwrap_or(false);
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(&path)
|
||||
.await
|
||||
.map_err(|e| ConnectCacheError::ReadingFile(format!("Opening file: {}", e)))?
|
||||
.lock_write()
|
||||
.await
|
||||
.map_err(|e| ConnectCacheError::ReadingFile(format!("Locking file: {:?}", e)))?;
|
||||
|
||||
let mut cache = if file_exists {
|
||||
let mut file_content = Vec::new();
|
||||
file.read_to_end(&mut file_content)
|
||||
.await
|
||||
.map_err(|e| ConnectCacheError::ReadingFile(format!("Reading file content: {}", e)))?;
|
||||
|
||||
match serde_json::from_slice::<ConnectCache>(&file_content) {
|
||||
Ok(cache) => cache,
|
||||
Err(e) => {
|
||||
tracing::warn!("Something wrong with Liana-Connect cache file: {:?}", e);
|
||||
tracing::warn!("Liana-Connect cache file is reset");
|
||||
ConnectCache::default()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ConnectCache::default()
|
||||
};
|
||||
|
||||
cache.accounts.retain(|a| emails.contains(&a.email));
|
||||
|
||||
let content = serde_json::to_vec_pretty(&cache).map_err(|e| {
|
||||
ConnectCacheError::WritingFile(format!("Failed to serialize settings: {}", e))
|
||||
})?;
|
||||
|
||||
file.seek(SeekFrom::Start(0)).await.map_err(|e| {
|
||||
ConnectCacheError::WritingFile(format!("Failed to seek to start of file: {}", e))
|
||||
})?;
|
||||
|
||||
file.write_all(&content).await.map_err(|e| {
|
||||
tracing::warn!("failed to write to file: {:?}", e);
|
||||
ConnectCacheError::WritingFile(e.to_string())
|
||||
})?;
|
||||
|
||||
file.inner_mut()
|
||||
.set_len(content.len() as u64)
|
||||
.await
|
||||
.map_err(|e| ConnectCacheError::WritingFile(format!("Failed to truncate file: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConnectCacheError {
|
||||
NotFound,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user