refac delete wallet modal and desc backup

This commit is contained in:
edouardparis 2025-05-12 17:51:41 +02:00
parent 9a6218b3f1
commit 30228c6490
13 changed files with 355 additions and 180 deletions

View File

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

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

@ -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(

View File

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

View File

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

View File

@ -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,