Use type wrapper instead of PathBuf for directories

With the multiplicity of directories, liana directory,
network directory and in the future per wallet lianad
directories, passing a simple type PathBuf does not
transmit to the contributors the information about
which directory each module works with.

This commit introduces wrappers around PathBuf
to give this information and link parent directories
to their child directories.
This commit is contained in:
edouardparis 2025-04-24 16:34:18 +02:00
parent 55ccf23f86
commit bb7777878e
31 changed files with 420 additions and 291 deletions

View File

@ -1,15 +1,17 @@
use crate::daemon::{
model::{Coin, ListCoinsResult},
Daemon, DaemonError,
use crate::{
daemon::{
model::{Coin, ListCoinsResult},
Daemon, DaemonError,
},
dir::LianaDirectory,
};
use liana::miniscript::bitcoin::Network;
use lianad::commands::CoinStatus;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Cache {
pub datadir_path: PathBuf,
pub datadir_path: LianaDirectory,
pub network: Network,
pub blockheight: i32,
pub coins: Vec<Coin>,
@ -25,7 +27,7 @@ pub struct Cache {
impl std::default::Default for Cache {
fn default() -> Self {
Self {
datadir_path: std::path::PathBuf::new(),
datadir_path: LianaDirectory::new(std::path::PathBuf::new()),
network: Network::Bitcoin,
blockheight: 0,
coins: Vec::new(),

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::path::Path;
use tracing_subscriber::filter;
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -107,30 +107,3 @@ impl std::fmt::Display for ConfigError {
}
impl std::error::Error for ConfigError {}
// Get the absolute path to the liana configuration folder.
///
/// This a "liana" directory in the XDG standard configuration directory for all OSes but
/// Linux-based ones, for which it's `~/.liana`.
/// Rationale: we want to have the database, RPC socket, etc.. in the same folder as the
/// configuration file but for Linux the XDG specify a data directory (`~/.local/share/`) different
/// from the configuration one (`~/.config/`).
pub fn default_datadir() -> Result<PathBuf, Box<dyn std::error::Error>> {
#[cfg(target_os = "linux")]
let configs_dir = dirs::home_dir();
#[cfg(not(target_os = "linux"))]
let configs_dir = dirs::config_dir();
if let Some(mut path) = configs_dir {
#[cfg(target_os = "linux")]
path.push(".liana");
#[cfg(not(target_os = "linux"))]
path.push("Liana");
return Ok(path);
}
Err("Failed to get default data directory".into())
}

View File

@ -11,7 +11,6 @@ mod error;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@ -37,6 +36,7 @@ use wallet::{sync_status, SyncStatus};
use crate::{
app::{cache::Cache, error::Error, menu::Menu, wallet::Wallet},
daemon::{embedded::EmbeddedDaemon, Daemon, DaemonBackend},
dir::LianaDirectory,
node::{bitcoind::Bitcoind, NodeType},
};
@ -58,7 +58,7 @@ impl Panels {
fn new(
cache: &Cache,
wallet: Arc<Wallet>,
data_dir: PathBuf,
data_dir: LianaDirectory,
daemon_backend: DaemonBackend,
internal_bitcoind: Option<&Bitcoind>,
config: Arc<Config>,
@ -155,7 +155,7 @@ impl App {
wallet: Arc<Wallet>,
config: Config,
daemon: Arc<dyn Daemon + Sync + Send>,
data_dir: PathBuf,
data_dir: LianaDirectory,
internal_bitcoind: Option<Bitcoind>,
restored_from_backup: bool,
) -> (App, Task<Message>) {
@ -385,15 +385,14 @@ impl App {
pub fn load_daemon_config(
&mut self,
datadir_path: PathBuf,
datadir_path: LianaDirectory,
cfg: DaemonConfig,
) -> Result<(), Error> {
Handle::current().block_on(async { self.daemon.stop().await })?;
let network = cfg.bitcoin_config.network;
let daemon = EmbeddedDaemon::start(cfg)?;
self.daemon = Arc::new(daemon);
let mut daemon_config_path = datadir_path;
daemon_config_path.push(network.to_string());
let mut daemon_config_path = datadir_path.network_directory(network).path().to_path_buf();
daemon_config_path.push("daemon.toml");
let content =

View File

@ -3,14 +3,14 @@
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use liana::miniscript::bitcoin::{bip32::Fingerprint, Network};
use liana::miniscript::bitcoin::bip32::Fingerprint;
use liana_ui::component::form;
use serde::{Deserialize, Serialize};
use crate::{
backup::{Key, KeyRole, KeyType},
dir::NetworkDirectory,
hw::HardwareWalletConfig,
services::{self, connect::client::backend},
};
@ -23,9 +23,8 @@ pub struct Settings {
}
impl Settings {
pub fn from_file(datadir: PathBuf, network: Network) -> Result<Self, SettingsError> {
let mut path = datadir;
path.push(network.to_string());
pub fn from_file(network_dir: &NetworkDirectory) -> Result<Self, SettingsError> {
let mut path = network_dir.path().to_path_buf();
path.push(DEFAULT_FILE_NAME);
let config = std::fs::read(path)
@ -41,9 +40,8 @@ impl Settings {
Ok(config)
}
pub fn to_file(&self, datadir: PathBuf, network: Network) -> Result<(), SettingsError> {
let mut path = datadir;
path.push(network.to_string());
pub fn to_file(&self, network_dir: &NetworkDirectory) -> Result<(), SettingsError> {
let mut path = network_dir.path().to_path_buf();
path.push(DEFAULT_FILE_NAME);
let content = serde_json::to_string_pretty(&self).map_err(|e| {
@ -257,10 +255,11 @@ impl std::fmt::Display for SettingsError {
/// global settings.
pub mod global {
use crate::dir::LianaDirectory;
use async_hwi::bitbox::{ConfigError, NoiseConfig, NoiseConfigData};
use serde::{Deserialize, Serialize};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
pub const DEFAULT_FILE_NAME: &str = "global_settings.json";
@ -283,9 +282,9 @@ pub mod global {
impl PersistedBitboxNoiseConfig {
/// Creates a new persisting noise config, which stores the pairing information in "bitbox.json"
/// in the provided directory.
pub fn new(global_datadir: &Path) -> PersistedBitboxNoiseConfig {
pub fn new(global_datadir: &LianaDirectory) -> PersistedBitboxNoiseConfig {
PersistedBitboxNoiseConfig {
file_path: global_datadir.join(DEFAULT_FILE_NAME),
file_path: global_datadir.path().join(DEFAULT_FILE_NAME),
}
}
}

View File

@ -1,5 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::Arc;
use iced::Subscription;
@ -29,6 +28,7 @@ use crate::{
model::{LabelItem, Labelled, SpendStatus, SpendTx},
Daemon,
},
dir::LianaDirectory,
hw::{HardwareWallet, HardwareWallets},
};
@ -444,7 +444,7 @@ impl SignModal {
pub fn new(
signed: HashSet<Fingerprint>,
wallet: Arc<Wallet>,
datadir_path: PathBuf,
datadir_path: LianaDirectory,
network: Network,
is_saved: bool,
) -> Self {

View File

@ -1,5 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::Arc;
use iced::{widget::qr_code, Subscription, Task};
@ -10,6 +9,7 @@ use liana::miniscript::bitcoin::{
use liana_ui::{component::modal, widget::*};
use crate::daemon::model::LabelsLoader;
use crate::dir::LianaDirectory;
use crate::{
app::{
cache::Cache,
@ -54,7 +54,7 @@ impl Labelled for Addresses {
}
pub struct ReceivePanel {
data_dir: PathBuf,
data_dir: LianaDirectory,
wallet: Arc<Wallet>,
addresses: Addresses,
labels_edited: LabelsEdited,
@ -63,7 +63,7 @@ pub struct ReceivePanel {
}
impl ReceivePanel {
pub fn new(data_dir: PathBuf, wallet: Arc<Wallet>) -> Self {
pub fn new(data_dir: LianaDirectory, wallet: Arc<Wallet>) -> Self {
Self {
data_dir,
wallet,
@ -217,7 +217,7 @@ pub struct VerifyAddressModal {
impl VerifyAddressModal {
pub fn new(
data_dir: PathBuf,
data_dir: LianaDirectory,
wallet: Arc<Wallet>,
network: Network,
address: Address,
@ -338,7 +338,7 @@ mod tests {
use liana::{descriptors::LianaDescriptor, miniscript::bitcoin::Address};
use serde_json::json;
use std::str::FromStr;
use std::{path::PathBuf, str::FromStr};
const DESC: &str = "wsh(or_d(multi(2,[ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/<0;1>/*,[de6eb005/48'/1'/0'/2']tpubDFGuYfS2JwiUSEXiQuNGdT3R7WTDhbaE6jbUhgYSSdhmfQcSx7ZntMPPv7nrkvAqjpj3jX9wbhSGMeKVao4qAzhbNyBi7iQmv5xxQk6H6jz/<0;1>/*),and_v(v:pkh([ffd63c8d/48'/1'/0'/2']tpubDExA3EC3iAsPxPhFn4j6gMiVup6V2eH3qKyk69RcTc9TTNRfFYVPad8bJD5FCHVQxyBT4izKsvr7Btd2R4xmQ1hZkvsqGBaeE82J71uTK4N/<2;3>/*),older(3))))#p9ax3xxp";
@ -356,8 +356,10 @@ mod tests {
))),
)]);
let wallet = Arc::new(Wallet::new(LianaDescriptor::from_str(DESC).unwrap()));
let sandbox: Sandbox<ReceivePanel> =
Sandbox::new(ReceivePanel::new(PathBuf::new(), wallet.clone()));
let sandbox: Sandbox<ReceivePanel> = Sandbox::new(ReceivePanel::new(
LianaDirectory::new(PathBuf::new()),
wallet.clone(),
));
let client = Arc::new(Lianad::new(daemon.run()));
let cache = Cache::default();
let sandbox = sandbox.load(client.clone(), &cache, wallet).await;

View File

@ -2,7 +2,6 @@ mod bitcoind;
mod wallet;
use std::convert::From;
use std::path::PathBuf;
use std::sync::Arc;
use iced::Task;
@ -23,13 +22,14 @@ use crate::{
Config,
},
daemon::{Daemon, DaemonBackend},
dir::LianaDirectory,
export::{ImportExportMessage, ImportExportType},
};
use super::export::ExportModal;
pub struct SettingsState {
data_dir: PathBuf,
data_dir: LianaDirectory,
wallet: Arc<Wallet>,
setting: Option<Box<dyn State>>,
daemon_backend: DaemonBackend,
@ -39,7 +39,7 @@ pub struct SettingsState {
impl SettingsState {
pub fn new(
data_dir: PathBuf,
data_dir: LianaDirectory,
wallet: Arc<Wallet>,
daemon_backend: DaemonBackend,
internal_bitcoind: bool,

View File

@ -1,6 +1,5 @@
use std::collections::HashSet;
use std::convert::From;
use std::path::PathBuf;
use std::sync::Arc;
use iced::{Subscription, Task};
@ -27,6 +26,7 @@ use crate::{
Config,
},
daemon::{Daemon, DaemonBackend},
dir::LianaDirectory,
export::{ImportExportMessage, ImportExportType},
hw::{HardwareWallet, HardwareWalletConfig, HardwareWallets},
};
@ -44,7 +44,7 @@ impl Modal {
}
pub struct WalletSettingsState {
data_dir: PathBuf,
data_dir: LianaDirectory,
warning: Option<Error>,
descriptor: LianaDescriptor,
keys_aliases: Vec<(Fingerprint, form::Value<String>)>,
@ -56,7 +56,7 @@ pub struct WalletSettingsState {
}
impl WalletSettingsState {
pub fn new(data_dir: PathBuf, wallet: Arc<Wallet>, config: Arc<Config>) -> Self {
pub fn new(data_dir: LianaDirectory, wallet: Arc<Wallet>, config: Arc<Config>) -> Self {
WalletSettingsState {
data_dir,
descriptor: wallet.main_descriptor.clone(),
@ -296,7 +296,7 @@ impl From<WalletSettingsState> for Box<dyn State> {
}
pub struct RegisterWalletModal {
data_dir: PathBuf,
data_dir: LianaDirectory,
wallet: Arc<Wallet>,
warning: Option<Error>,
chosen_hw: Option<usize>,
@ -306,7 +306,7 @@ pub struct RegisterWalletModal {
}
impl RegisterWalletModal {
pub fn new(data_dir: PathBuf, wallet: Arc<Wallet>, network: Network) -> Self {
pub fn new(data_dir: LianaDirectory, wallet: Arc<Wallet>, network: Network) -> Self {
let mut registered = HashSet::new();
for hw in &wallet.hardware_wallets {
registered.insert(hw.fingerprint);
@ -406,7 +406,7 @@ impl RegisterWalletModal {
}
async fn register_wallet(
data_dir: PathBuf,
data_dir: LianaDirectory,
network: Network,
hw: std::sync::Arc<dyn async_hwi::HWI + Send + Sync>,
fingerprint: Fingerprint,
@ -427,7 +427,8 @@ async fn register_wallet(
};
if daemon.backend() != DaemonBackend::RemoteBackend {
let mut settings = settings::Settings::from_file(data_dir.clone(), network)?;
let network_dir = data_dir.network_directory(network);
let mut settings = settings::Settings::from_file(&network_dir)?;
let checksum = wallet.descriptor_checksum();
if let Some(wallet_setting) = settings
@ -446,7 +447,7 @@ async fn register_wallet(
}
}
settings.to_file(data_dir, network)?;
settings.to_file(&network_dir)?;
}
let mut wallet = wallet.as_ref().clone();
@ -469,14 +470,15 @@ async fn register_wallet(
}
pub async fn update_keys_aliases(
data_dir: PathBuf,
data_dir: LianaDirectory,
network: Network,
wallet: Arc<Wallet>,
keys_aliases: Vec<(Fingerprint, String)>,
daemon: Arc<dyn Daemon + Sync + Send>,
) -> Result<Arc<Wallet>, Error> {
if daemon.backend() != DaemonBackend::RemoteBackend {
let mut settings = settings::Settings::from_file(data_dir.clone(), network)?;
let network_dir = data_dir.network_directory(network);
let mut settings = settings::Settings::from_file(&network_dir)?;
let checksum = wallet.descriptor_checksum();
if let Some(wallet_setting) = settings
.wallets
@ -493,7 +495,7 @@ pub async fn update_keys_aliases(
.collect();
}
settings.to_file(data_dir, network)?;
settings.to_file(&network_dir)?;
}
let mut wallet = wallet.as_ref().clone();

View File

@ -1,7 +1,7 @@
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::sync::Arc;
use crate::dir::{LianaDirectory, NetworkDirectory};
use crate::{
app::settings, daemon::DaemonBackend, hw::HardwareWalletConfig, node::NodeType, signer::Signer,
};
@ -101,12 +101,8 @@ impl Wallet {
.to_string()
}
pub fn load_from_settings(
self,
datadir_path: &Path,
network: bitcoin::Network,
) -> Result<Self, WalletError> {
let wallet = match settings::Settings::from_file(datadir_path.to_path_buf(), network) {
pub fn load_from_settings(self, dir: &NetworkDirectory) -> Result<Self, WalletError> {
let wallet = match settings::Settings::from_file(dir) {
Ok(settings) => {
if let Some(wallet_setting) = settings.wallets.first() {
self.with_name(wallet_setting.name.clone())
@ -140,7 +136,7 @@ impl Wallet {
};
tracing::info!("Settings file not found, creating one");
s.to_file(datadir_path.to_path_buf(), network)?;
s.to_file(dir)?;
self
}
Err(e) => return Err(e.into()),
@ -151,10 +147,10 @@ impl Wallet {
pub fn load_hotsigners(
self,
datadir_path: &Path,
datadir_path: &LianaDirectory,
network: bitcoin::Network,
) -> Result<Self, WalletError> {
let hot_signers = match HotSigner::from_datadir(datadir_path, network) {
let hot_signers = match HotSigner::from_datadir(datadir_path.path(), network) {
Ok(signers) => signers,
Err(e) => match e {
liana::signer::SignerError::MnemonicStorage(e) => {

View File

@ -12,7 +12,6 @@ use serde_json::Value;
use std::{
collections::{BTreeMap, HashMap},
fmt::{Debug, Display},
path::PathBuf,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
@ -21,6 +20,7 @@ use tokio::sync::mpsc::UnboundedSender;
use crate::{
app::{settings::Settings, wallet::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,
@ -180,7 +180,7 @@ impl Backup {
/// Create a Backup from the Liana App context
pub async fn from_app(
datadir: PathBuf,
datadir: LianaDirectory,
network: Network,
config: Arc<Config>,
wallet: Arc<Wallet>,
@ -194,8 +194,8 @@ impl Backup {
let descriptor = wallet.main_descriptor.to_string();
let keys = wallet.keys();
let settings =
Settings::from_file(datadir, network).map_err(|_| Error::SettingsFromFile)?;
let network_dir = datadir.network_directory(network);
let settings = Settings::from_file(&network_dir).map_err(|_| Error::SettingsFromFile)?;
if settings.wallets.len() == 1 {
if let Ok(settings) = serde_json::to_value(settings.wallets[0].clone()) {
proprietary.insert(SETTINGS_KEY.to_string(), settings);

View File

@ -1,7 +1,6 @@
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use std::iter::FromIterator;
use std::path::Path;
use async_trait::async_trait;
use lianad::bip329::Labels;
@ -21,6 +20,7 @@ use lianad::{
};
use super::{model::*, Daemon, DaemonBackend, DaemonError};
use crate::dir::LianaDirectory;
pub trait Client {
type Error: Into<DaemonError> + Debug;
@ -65,7 +65,11 @@ impl<C: Client + Send + Sync + Debug> Daemon for Lianad<C> {
None
}
async fn is_alive(&self, _datadir: &Path, _network: Network) -> Result<(), DaemonError> {
async fn is_alive(
&self,
_datadir: &LianaDirectory,
_network: Network,
) -> Result<(), DaemonError> {
Ok(())
}

View File

@ -1,10 +1,10 @@
use lianad::bip329::Labels;
use lianad::commands::UpdateDerivIndexesResult;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use tokio::sync::Mutex;
use super::{model::*, node, Daemon, DaemonBackend, DaemonError};
use crate::dir::LianaDirectory;
use async_trait::async_trait;
use liana::miniscript::bitcoin::{address, psbt::Psbt, Address, Network, OutPoint, Txid};
use lianad::{
@ -67,7 +67,11 @@ impl Daemon for EmbeddedDaemon {
Some(&self.config)
}
async fn is_alive(&self, _datadir: &Path, _network: Network) -> Result<(), DaemonError> {
async fn is_alive(
&self,
_datadir: &LianaDirectory,
_network: Network,
) -> Result<(), DaemonError> {
let mut handle = self.handle.lock().await;
if let Some(h) = handle.as_ref() {
if h.is_alive() {

View File

@ -7,7 +7,6 @@ use std::convert::TryInto;
use std::fmt::Debug;
use std::io::ErrorKind;
use std::iter::FromIterator;
use std::path::Path;
use async_trait::async_trait;
@ -97,7 +96,11 @@ impl DaemonBackend {
pub trait Daemon: Debug {
fn backend(&self) -> DaemonBackend;
fn config(&self) -> Option<&Config>;
async fn is_alive(&self, datadir: &Path, network: Network) -> Result<(), DaemonError>;
async fn is_alive(
&self,
datadir: &crate::dir::LianaDirectory,
network: Network,
) -> Result<(), DaemonError>;
async fn stop(&self) -> Result<(), DaemonError>;
async fn get_info(&self) -> Result<model::GetInfoResult, DaemonError>;
async fn get_new_address(&self) -> Result<model::GetAddressResult, DaemonError>;

View File

@ -1,18 +0,0 @@
pub fn create_directory(datadir_path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(unix)]
return {
use std::fs::DirBuilder;
use std::os::unix::fs::DirBuilderExt;
let mut builder = DirBuilder::new();
builder.mode(0o700).recursive(true).create(datadir_path)?;
Ok(())
};
// TODO: permissions on Windows..
#[cfg(not(unix))]
return {
std::fs::create_dir_all(datadir_path)?;
Ok(())
};
}

123
liana-gui/src/dir.rs Normal file
View File

@ -0,0 +1,123 @@
use liana::miniscript::bitcoin::Network;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, PartialEq)]
pub struct LianaDirectory(PathBuf);
impl LianaDirectory {
pub fn new(p: PathBuf) -> Self {
LianaDirectory(p)
}
pub fn new_default() -> Result<Self, Box<dyn std::error::Error>> {
default_datadir().map(LianaDirectory::new)
}
}
impl LianaDirectory {
pub fn exists(&self) -> bool {
self.0.as_path().exists()
}
pub fn init(&self) -> Result<(), Box<dyn std::error::Error>> {
create_directory(self.0.as_path())
}
pub fn path(&self) -> &Path {
self.0.as_path()
}
pub fn network_directory(&self, network: Network) -> NetworkDirectory {
let mut path = self.0.clone();
path.push(network.to_string());
NetworkDirectory::new(path)
}
pub fn bitcoind_directory(&self) -> BitcoindDirectory {
let mut path = self.0.clone();
path.push("bitcoind");
BitcoindDirectory::new(path)
}
}
// Get the absolute path to the liana configuration folder.
///
/// This a "liana" directory in the XDG standard configuration directory for all OSes but
/// Linux-based ones, for which it's `~/.liana`.
/// Rationale: we want to have the database, RPC socket, etc.. in the same folder as the
/// configuration file but for Linux the XDG specify a data directory (`~/.local/share/`) different
/// from the configuration one (`~/.config/`).
fn default_datadir() -> Result<PathBuf, Box<dyn std::error::Error>> {
#[cfg(target_os = "linux")]
let configs_dir = dirs::home_dir();
#[cfg(not(target_os = "linux"))]
let configs_dir = dirs::config_dir();
if let Some(mut path) = configs_dir {
#[cfg(target_os = "linux")]
path.push(".liana");
#[cfg(not(target_os = "linux"))]
path.push("Liana");
return Ok(path);
}
Err("Failed to get default data directory".into())
}
#[derive(Clone, Debug)]
pub struct NetworkDirectory(PathBuf);
impl NetworkDirectory {
pub fn new(p: PathBuf) -> Self {
NetworkDirectory(p)
}
}
impl NetworkDirectory {
pub fn exists(&self) -> bool {
self.0.as_path().exists()
}
pub fn init(&self) -> Result<(), Box<dyn std::error::Error>> {
create_directory(self.0.as_path())
}
pub fn path(&self) -> &Path {
self.0.as_path()
}
}
#[derive(Clone, Debug)]
pub struct BitcoindDirectory(PathBuf);
impl BitcoindDirectory {
pub fn new(p: PathBuf) -> Self {
BitcoindDirectory(p)
}
pub fn exists(&self) -> bool {
self.0.as_path().exists()
}
pub fn init(&self) -> Result<(), Box<dyn std::error::Error>> {
create_directory(self.0.as_path())
}
pub fn path(&self) -> &Path {
self.0.as_path()
}
}
fn create_directory(datadir_path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(unix)]
return {
use std::fs::DirBuilder;
use std::os::unix::fs::DirBuilderExt;
let mut builder = DirBuilder::new();
builder.mode(0o700).recursive(true).create(datadir_path)?;
Ok(())
};
// TODO: permissions on Windows..
#[cfg(not(unix))]
return {
std::fs::create_dir_all(datadir_path)?;
Ok(())
};
}

View File

@ -44,6 +44,7 @@ use crate::{
model::{HistoryTransaction, Labelled},
Daemon, DaemonBackend, DaemonError,
},
dir::{LianaDirectory, NetworkDirectory},
node::bitcoind::Bitcoind,
services::connect::client::backend::api::DEFAULT_LIMIT,
};
@ -163,7 +164,7 @@ pub enum ImportExportType {
ExportPsbt(String),
ExportXpub(String),
ExportBackup(String),
ExportProcessBackup(PathBuf, Network, Arc<Config>, Arc<Wallet>),
ExportProcessBackup(LianaDirectory, Network, Arc<Config>, Arc<Wallet>),
ImportBackup(
Option<Sender<bool>>, /*overwrite_labels*/
Option<Sender<bool>>, /*overwrite_aliases*/
@ -819,7 +820,8 @@ pub async fn import_backup(
let settings = if !account.keys.is_empty() {
// TODO: change lianad_datadir is common to gui datadir only for legacy wallet before
// multiple wallet
let settings = match Settings::from_file(lianad_datadir.path().to_path_buf(), network) {
let network_dir = NetworkDirectory::new(lianad_datadir.path().to_path_buf());
let settings = match Settings::from_file(&network_dir) {
Ok(s) => s,
Err(_) => {
return Err(Error::BackupImport("Failed to get App Settings".into()));
@ -938,10 +940,8 @@ pub async fn import_backup(
settings.wallets.get_mut(0).expect("already checked").keys =
settings_aliases.clone().into_values().collect();
if settings
.to_file(lianad_datadir.path().to_path_buf(), network)
.is_err()
{
let network_dir = NetworkDirectory::new(lianad_datadir.path().to_path_buf());
if settings.to_file(&network_dir).is_err() {
return Err(Error::BackupImport("Failed to import keys aliases".into()));
} else {
// Update wallet state
@ -1085,7 +1085,7 @@ pub async fn import_backup_at_launch(
wallet: Arc<Wallet>,
config: Config,
daemon: Arc<dyn Daemon + Sync + Send>,
datadir: PathBuf,
datadir: LianaDirectory,
internal_bitcoind: Option<Bitcoind>,
backup: Backup,
) -> Result<
@ -1094,7 +1094,7 @@ pub async fn import_backup_at_launch(
Arc<Wallet>,
Config,
Arc<dyn Daemon + Sync + Send>,
PathBuf,
LianaDirectory,
Option<Bitcoind>,
),
RestoreBackupError,
@ -1231,7 +1231,7 @@ pub async fn get_path(filename: String, write: bool) -> Option<PathBuf> {
}
pub async fn app_backup(
datadir: PathBuf,
datadir: LianaDirectory,
network: Network,
config: Arc<Config>,
wallet: Arc<Wallet>,
@ -1243,7 +1243,7 @@ pub async fn app_backup(
}
pub async fn app_backup_export(
datadir: PathBuf,
datadir: LianaDirectory,
network: Network,
config: Arc<Config>,
wallet: Arc<Wallet>,

View File

@ -1,11 +1,13 @@
use iced::Task;
use std::{
collections::HashMap,
path::PathBuf,
sync::{Arc, Mutex},
};
use crate::app::{settings, wallet::Wallet};
use crate::{
app::{settings, wallet::Wallet},
dir::LianaDirectory,
};
use async_hwi::{
bitbox::{api::runtime, BitBox02, PairingBitbox02},
coldcard,
@ -156,7 +158,7 @@ pub struct HardwareWallets {
pub list: Vec<HardwareWallet>,
pub aliases: HashMap<Fingerprint, String>,
wallet: Option<Arc<Wallet>>,
datadir_path: PathBuf,
datadir_path: LianaDirectory,
}
impl std::fmt::Debug for HardwareWallets {
@ -166,7 +168,7 @@ impl std::fmt::Debug for HardwareWallets {
}
impl HardwareWallets {
pub fn new(datadir_path: PathBuf, network: Network) -> Self {
pub fn new(datadir_path: LianaDirectory, network: Network) -> Self {
Self {
network,
list: Vec::new(),
@ -380,7 +382,7 @@ struct State {
wallet: Option<Arc<Wallet>>,
connected_supported_hws: Vec<String>,
api: Option<ledger::HidApi>,
datadir_path: PathBuf,
datadir_path: LianaDirectory,
}
fn refresh(mut state: State) -> impl Stream<Item = HardwareWalletMessage> {

View File

@ -1,11 +1,11 @@
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use crate::{
app::settings::KeySetting,
backup::Backup,
dir::LianaDirectory,
node::bitcoind::{Bitcoind, InternalBitcoindConfig},
services::connect::client::backend::{BackendClient, BackendWalletClient},
signer::Signer,
@ -60,7 +60,7 @@ pub struct Context {
pub descriptor: Option<LianaDescriptor>,
pub keys: HashMap<bitcoin::bip32::Fingerprint, KeySetting>,
pub hws: Vec<(DeviceKind, bitcoin::bip32::Fingerprint, Option<[u8; 32]>)>,
pub root_directory: PathBuf,
pub liana_directory: LianaDirectory,
pub network: bitcoin::Network,
pub hw_is_used: bool,
// In case a user entered a mnemonic,
@ -76,7 +76,7 @@ pub struct Context {
impl Context {
pub fn new(
network: bitcoin::Network,
root_directory: PathBuf,
liana_directory: LianaDirectory,
remote_backend: RemoteBackend,
) -> Self {
Self {
@ -89,7 +89,7 @@ impl Context {
keys: HashMap::new(),
bitcoin_backend: None,
descriptor: None,
root_directory,
liana_directory,
network,
hw_is_used: false,
recovered_signer: None,

View File

@ -27,7 +27,7 @@ use crate::{
},
backup,
daemon::DaemonError,
datadir::create_directory,
dir::{LianaDirectory, NetworkDirectory},
hw::{HardwareWalletConfig, HardwareWallets},
services::{
self,
@ -60,7 +60,7 @@ pub enum UserFlow {
pub struct Installer {
pub network: bitcoin::Network,
pub datadir: PathBuf,
pub datadir: LianaDirectory,
current: usize,
steps: Vec<Box<dyn Step>>,
@ -101,7 +101,7 @@ impl Installer {
}
pub fn new(
destination_path: PathBuf,
destination_path: LianaDirectory,
network: bitcoin::Network,
remote_backend: Option<BackendClient>,
user_flow: UserFlow,
@ -135,7 +135,7 @@ impl Installer {
ChooseBackend::new(network).into(),
RemoteBackendLogin::new(network).into(),
SelectBitcoindTypeStep::new().into(),
InternalBitcoindStep::new(&context.root_directory).into(),
InternalBitcoindStep::new(&context.liana_directory).into(),
DefineNode::default().into(),
Final::new().into(),
],
@ -148,7 +148,7 @@ impl Installer {
RecoverMnemonic::default().into(),
RegisterDescriptor::new_import_wallet().into(),
SelectBitcoindTypeStep::new().into(),
InternalBitcoindStep::new(&context.root_directory).into(),
InternalBitcoindStep::new(&context.liana_directory).into(),
DefineNode::default().into(),
Final::new().into(),
],
@ -168,8 +168,8 @@ impl Installer {
(installer, command)
}
pub fn destination_path(&self) -> PathBuf {
self.context.root_directory.clone()
pub fn destination_path(&self) -> LianaDirectory {
self.context.liana_directory.clone()
}
pub fn subscription(&self) -> Subscription<Message> {
@ -272,21 +272,23 @@ impl Installer {
}
}
Message::Installed(Err(e)) => {
let mut network_directory = self.context.root_directory.clone();
network_directory.push(self.context.bitcoin_config.network.to_string());
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) {
if let Err(e) = std::fs::remove_dir_all(network_directory.path()) {
error!(
"Failed to completely delete the data directory (path: '{}'): {}",
network_directory.to_string_lossy(),
network_directory.path().to_string_lossy(),
e
);
} else {
warn!(
"Successfully deleted data directory at '{}'.",
network_directory.to_string_lossy()
network_directory.path().to_string_lossy()
);
};
self.steps
@ -360,24 +362,26 @@ pub async fn install_local_wallet(
ctx: Context,
signer: Arc<Mutex<Signer>>,
) -> Result<PathBuf, Error> {
let network_datadir = ctx
.liana_directory
.network_directory(ctx.bitcoin_config.network);
network_datadir
.init()
.map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?;
let cfg: lianad::config::Config = extract_daemon_config(&ctx)?;
daemon_check(cfg.clone())?;
info!("daemon checked");
let mut network_datadir_path = ctx.root_directory.clone();
network_datadir_path.push(cfg.bitcoin_config.network.to_string());
create_directory(&network_datadir_path)
.map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?;
// Step needed because of ValueAfterTable error in the toml serialize implementation.
let daemon_config = toml::Value::try_from(&cfg)
.map_err(|e| Error::Unexpected(format!("Failed to serialize daemon config: {}", e)))?;
// create lianad configuration file
let _daemon_config_path = create_and_write_file(
network_datadir_path.clone(),
&network_datadir,
"daemon.toml",
daemon_config.to_string().as_bytes(),
)?;
@ -392,7 +396,7 @@ pub async fn install_local_wallet(
signer
.lock()
.unwrap()
.store(&ctx.root_directory, cfg.bitcoin_config.network)
.store(&ctx.liana_directory, cfg.bitcoin_config.network)
.map_err(|e| Error::Unexpected(format!("Failed to store mnemonic: {}", e)))?;
info!("Hot signer mnemonic stored");
@ -400,7 +404,7 @@ pub async fn install_local_wallet(
if let Some(signer) = &ctx.recovered_signer {
signer
.store(&ctx.root_directory, cfg.bitcoin_config.network)
.store(&ctx.liana_directory, cfg.bitcoin_config.network)
.map_err(|e| Error::Unexpected(format!("Failed to store mnemonic: {}", e)))?;
info!("Recovered signer mnemonic stored");
@ -408,7 +412,7 @@ pub async fn install_local_wallet(
// create liana GUI configuration file
let gui_config_path = create_and_write_file(
network_datadir_path.clone(),
&network_datadir,
gui_config::DEFAULT_FILE_NAME,
toml::to_string(&gui_config::Config::new(
// Installer started a bitcoind, it is expected that gui will start it on startup
@ -423,7 +427,7 @@ pub async fn install_local_wallet(
// create liana GUI settings file
let settings: gui_settings::Settings = extract_local_gui_settings(&ctx);
create_and_write_file(
network_datadir_path,
&network_datadir,
gui_settings::DEFAULT_FILE_NAME,
serde_json::to_string_pretty(&settings)
.map_err(|e| Error::Unexpected(format!("Failed to serialize settings: {}", e)))?
@ -440,9 +444,9 @@ pub async fn create_remote_wallet(
signer: Arc<Mutex<Signer>>,
remote_backend: BackendClient,
) -> Result<PathBuf, Error> {
let mut network_datadir_path = ctx.root_directory.clone();
network_datadir_path.push(ctx.network.to_string());
create_directory(&network_datadir_path)
let network_datadir = ctx.liana_directory.network_directory(ctx.network);
network_datadir
.init()
.map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?;
let descriptor = ctx
@ -457,7 +461,7 @@ pub async fn create_remote_wallet(
signer
.lock()
.unwrap()
.store(&ctx.root_directory, ctx.network)
.store(&ctx.liana_directory, ctx.network)
.map_err(|e| Error::Unexpected(format!("Failed to store mnemonic: {}", e)))?;
info!("Hot signer mnemonic stored");
@ -465,18 +469,15 @@ pub async fn create_remote_wallet(
if let Some(signer) = &ctx.recovered_signer {
signer
.store(&ctx.root_directory, ctx.network)
.store(&ctx.liana_directory, ctx.network)
.map_err(|e| Error::Unexpected(format!("Failed to store mnemonic: {}", e)))?;
info!("Recovered signer mnemonic stored");
}
let mut network_datadir_path = ctx.root_directory.clone();
network_datadir_path.push(ctx.network.to_string());
// create liana GUI configuration file
let gui_config_path = create_and_write_file(
network_datadir_path.clone(),
&network_datadir,
gui_config::DEFAULT_FILE_NAME,
toml::to_string(&gui_config::Config {
log_level: Some("info".to_string()),
@ -540,7 +541,7 @@ pub async fn create_remote_wallet(
// create liana GUI settings file
let settings: gui_settings::Settings = extract_remote_gui_settings(&ctx, &remote_backend).await;
create_and_write_file(
network_datadir_path.clone(),
&network_datadir,
gui_settings::DEFAULT_FILE_NAME,
serde_json::to_string_pretty(&settings)
.map_err(|e| Error::Unexpected(format!("Failed to serialize settings: {}", e)))?
@ -560,21 +561,21 @@ pub async fn import_remote_wallet(
if let Some(signer) = &ctx.recovered_signer {
signer
.store(&ctx.root_directory, ctx.network)
.store(&ctx.liana_directory, ctx.network)
.map_err(|e| Error::Unexpected(format!("Failed to store mnemonic: {}", e)))?;
info!("Recovered signer mnemonic stored");
}
let mut network_datadir_path = ctx.root_directory.clone();
network_datadir_path.push(ctx.network.to_string());
create_directory(&network_datadir_path)
let network_datadir = ctx.liana_directory.network_directory(ctx.network);
network_datadir
.init()
.map_err(|e| Error::Unexpected(format!("Failed to create datadir path: {}", e)))?;
// create liana GUI settings file
let settings: gui_settings::Settings = extract_remote_gui_settings(&ctx, &backend).await;
create_and_write_file(
network_datadir_path.clone(),
&network_datadir,
gui_settings::DEFAULT_FILE_NAME,
serde_json::to_string_pretty(&settings)
.map_err(|e| Error::Unexpected(format!("Failed to serialize settings: {}", e)))?
@ -585,7 +586,7 @@ pub async fn import_remote_wallet(
// create liana GUI configuration file
let gui_config_path = create_and_write_file(
network_datadir_path.clone(),
&network_datadir,
gui_config::DEFAULT_FILE_NAME,
toml::to_string(&gui_config::Config {
log_level: Some("info".to_string()),
@ -602,12 +603,12 @@ pub async fn import_remote_wallet(
}
pub fn create_and_write_file(
mut network_datadir: PathBuf,
network_datadir: &NetworkDirectory,
file_name: &str,
data: &[u8],
) -> Result<PathBuf, Error> {
network_datadir.push(file_name);
let path = network_datadir;
let mut path = network_datadir.path().to_path_buf();
path.push(file_name);
let mut file =
std::fs::File::create(&path).map_err(|e| Error::CannotCreateFile(e.to_string()))?;
file.write_all(data)
@ -681,11 +682,13 @@ pub fn extract_local_gui_settings(ctx: &Context) -> Settings {
}
pub fn extract_daemon_config(ctx: &Context) -> Result<Config, Error> {
let mut data_directory = ctx
.root_directory
let data_directory = ctx
.liana_directory
.network_directory(ctx.bitcoin_config.network)
.path()
.to_path_buf()
.canonicalize()
.map_err(|e| Error::Unexpected(format!("Failed to canonicalize datadir path: {}", e)))?;
data_directory.push(ctx.bitcoin_config.network.to_string());
Ok(Config::new(
ctx.bitcoin_config.clone(),
ctx.bitcoin_backend.clone(),

View File

@ -627,7 +627,7 @@ mod tests {
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use crate::installer::descriptor::KeySource;
use crate::{dir::LianaDirectory, installer::descriptor::KeySource};
pub struct Sandbox<S: Step> {
step: Arc<Mutex<S>>,
@ -646,7 +646,10 @@ mod tests {
}
pub async fn update(&self, message: Message) {
let mut hws = HardwareWallets::new(PathBuf::from_str("/").unwrap(), Network::Bitcoin);
let mut hws = HardwareWallets::new(
LianaDirectory::new(PathBuf::from_str("/").unwrap()),
Network::Bitcoin,
);
let cmd = self.step.lock().unwrap().update(&mut hws, message);
if let Some(mut stream) = into_stream(cmd) {
while let Some(action) = stream.next().await {
@ -665,7 +668,7 @@ mod tests {
async fn test_define_descriptor_use_hotkey() {
let mut ctx = Context::new(
Network::Signet,
PathBuf::from_str("/").unwrap(),
LianaDirectory::new(PathBuf::from_str("/").unwrap()),
crate::installer::context::RemoteBackend::None,
);
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(
@ -746,7 +749,7 @@ mod tests {
async fn test_define_descriptor_stores_if_hw_is_used() {
let mut ctx = Context::new(
Network::Testnet,
PathBuf::from_str("/").unwrap(),
LianaDirectory::new(PathBuf::from_str("/").unwrap()),
crate::installer::context::RemoteBackend::None,
);
let sandbox: Sandbox<DefineDescriptor> = Sandbox::new(DefineDescriptor::new(

View File

@ -18,6 +18,7 @@ use jsonrpc::{client::Client, simple_http::SimpleHttpTransport};
use liana_ui::{component::form, widget::*};
use crate::dir::LianaDirectory;
use crate::{
download,
hw::HardwareWallets,
@ -489,7 +490,7 @@ impl Default for DefineBitcoind {
}
pub struct InternalBitcoindStep {
liana_datadir: PathBuf,
liana_datadir: LianaDirectory,
bitcoind_datadir: PathBuf,
network: Network,
started: Option<Result<(), StartInternalBitcoindError>>,
@ -509,7 +510,7 @@ impl From<InternalBitcoindStep> for Box<dyn Step> {
}
impl InternalBitcoindStep {
pub fn new(liana_datadir: &PathBuf) -> Self {
pub fn new(liana_datadir: &LianaDirectory) -> Self {
Self {
liana_datadir: liana_datadir.clone(),
bitcoind_datadir: internal_bitcoind_datadir(liana_datadir),
@ -531,7 +532,7 @@ impl Step for InternalBitcoindStep {
if self.exe_path.is_none() {
// Check if current managed bitcoind version is already installed.
// For new installations, we ignore any previous managed bitcoind versions that might be installed.
let exe_path = bitcoind::internal_bitcoind_exe_path(&ctx.root_directory, VERSION);
let exe_path = bitcoind::internal_bitcoind_exe_path(&ctx.liana_directory, VERSION);
if exe_path.exists() {
self.exe_path = Some(exe_path)
} else if self.exe_download.is_none() {

View File

@ -1,5 +1,3 @@
use std::path::PathBuf;
use iced::{
alignment::Horizontal,
widget::{pick_list, scrollable, Button, Space},
@ -14,7 +12,11 @@ use liana_ui::{
};
use lianad::config::ConfigError;
use crate::{app, installer::UserFlow};
use crate::{
app,
dir::{LianaDirectory, NetworkDirectory},
installer::UserFlow,
};
const NETWORKS: [Network; 4] = [
Network::Bitcoin,
@ -37,20 +39,21 @@ pub enum State {
pub struct Launcher {
state: State,
network: Network,
datadir_path: PathBuf,
datadir_path: LianaDirectory,
error: Option<String>,
delete_wallet_modal: Option<DeleteWalletModal>,
}
impl Launcher {
pub fn new(datadir_path: PathBuf, network: Option<Network>) -> (Self, Task<Message>) {
pub fn new(datadir_path: LianaDirectory, network: Option<Network>) -> (Self, Task<Message>) {
let network = network.unwrap_or(
NETWORKS
.iter()
.find(|net| datadir_path.join(net.to_string()).exists())
.find(|net| datadir_path.path().join(net.to_string()).exists())
.cloned()
.unwrap_or(Network::Bitcoin),
);
let network_dir = datadir_path.network_directory(network);
(
Self {
state: State::Unchecked,
@ -59,10 +62,7 @@ impl Launcher {
error: None,
delete_wallet_modal: None,
},
Task::perform(
check_network_datadir(datadir_path.clone(), network),
Message::Checked,
),
Task::perform(check_network_datadir(network_dir), Message::Checked),
)
}
@ -96,8 +96,8 @@ impl Launcher {
})
}
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::ShowModal)) => {
let wallet_datadir = self.datadir_path.join(self.network.to_string());
let config_path = wallet_datadir.join(app::config::DEFAULT_FILE_NAME);
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 {
@ -112,10 +112,8 @@ impl Launcher {
}
Message::View(ViewMessage::SelectNetwork(network)) => {
self.network = network;
Task::perform(
check_network_datadir(self.datadir_path.clone(), self.network),
Message::Checked,
)
let network_dir = self.datadir_path.network_directory(self.network);
Task::perform(check_network_datadir(network_dir), Message::Checked)
}
Message::View(ViewMessage::DeleteWallet(DeleteWalletMessage::Deleted)) => {
self.state = State::NoWallet;
@ -138,8 +136,11 @@ impl Launcher {
Message::View(ViewMessage::Run) => {
if matches!(self.state, State::Wallet { .. }) {
let datadir_path = self.datadir_path.clone();
let mut path = self.datadir_path.clone();
path.push(self.network.to_string());
let mut path = self
.datadir_path
.network_directory(self.network)
.path()
.to_path_buf();
path.push(app::config::DEFAULT_FILE_NAME);
let cfg = app::Config::from_file(&path).expect("Already checked");
let network = self.network;
@ -340,9 +341,9 @@ impl Launcher {
#[derive(Debug, Clone)]
pub enum Message {
View(ViewMessage),
Install(PathBuf, Network, UserFlow),
Install(LianaDirectory, Network, UserFlow),
Checked(Result<State, String>),
Run(PathBuf, app::config::Config, Network),
Run(LianaDirectory, app::config::Config, Network),
}
#[derive(Debug, Clone)]
@ -367,7 +368,7 @@ pub enum DeleteWalletMessage {
struct DeleteWalletModal {
network: Network,
wallet_datadir: PathBuf,
wallet_datadir: NetworkDirectory,
warning: Option<std::io::Error>,
deleted: bool,
// `None` means we were not able to determine whether wallet uses internal bitcoind.
@ -375,7 +376,11 @@ struct DeleteWalletModal {
}
impl DeleteWalletModal {
fn new(network: Network, wallet_datadir: PathBuf, internal_bitcoind: Option<bool>) -> Self {
fn new(
network: Network,
wallet_datadir: NetworkDirectory,
internal_bitcoind: Option<bool>,
) -> Self {
Self {
network,
wallet_datadir,
@ -388,7 +393,7 @@ 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) {
if let Err(e) = std::fs::remove_dir_all(self.wallet_datadir.path()) {
self.warning = Some(e);
} else {
self.deleted = true;
@ -459,9 +464,8 @@ impl DeleteWalletModal {
}
}
async fn check_network_datadir(path: PathBuf, network: Network) -> Result<State, String> {
let mut config_path = path.clone();
config_path.push(network.to_string());
async fn check_network_datadir(path: NetworkDirectory) -> Result<State, String> {
let mut config_path = path.clone().path().to_path_buf();
config_path.push(app::config::DEFAULT_FILE_NAME);
if let Err(e) = app::Config::from_file(&config_path) {
@ -470,13 +474,12 @@ async fn check_network_datadir(path: PathBuf, network: Network) -> Result<State,
} else {
return Err(format!(
"Failed to read GUI configuration file in the directory: {}",
path.to_string_lossy()
path.path().to_string_lossy()
));
}
};
let mut daemon_config_path = path.clone();
daemon_config_path.push(network.to_string());
let mut daemon_config_path = path.clone().path().to_path_buf();
daemon_config_path.push("daemon.toml");
if daemon_config_path.exists() {
@ -510,7 +513,7 @@ async fn check_network_datadir(path: PathBuf, network: Network) -> Result<State,
})?;
}
if let Ok(settings) = app::settings::Settings::from_file(path, network) {
if let Ok(settings) = app::settings::Settings::from_file(&path) {
if let Some(wallet) = settings.wallets.first().cloned() {
return Ok(State::Wallet {
name: Some(wallet.name),

View File

@ -1,7 +1,7 @@
pub mod app;
pub mod backup;
pub mod daemon;
pub mod datadir;
pub mod dir;
pub mod download;
pub mod export;
pub mod hw;

View File

@ -1,7 +1,7 @@
use std::convert::From;
use std::fs::File;
use std::io::{BufRead, BufReader, ErrorKind, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
@ -24,6 +24,7 @@ use lianad::{
use crate::app;
use crate::backup::Backup;
use crate::dir::LianaDirectory;
use crate::export::RestoreBackupError;
use crate::{
app::{
@ -52,7 +53,7 @@ type StartedResult = Result<
>;
pub struct Loader {
pub datadir_path: PathBuf,
pub datadir_path: LianaDirectory,
pub network: bitcoin::Network,
pub gui_config: GUIConfig,
pub daemon_started: bool,
@ -97,7 +98,7 @@ pub enum Message {
Arc<Wallet>,
app::Config,
Arc<dyn Daemon + Sync + Send>,
PathBuf,
LianaDirectory,
Option<Bitcoind>,
),
Error,
@ -113,7 +114,7 @@ pub enum Message {
impl Loader {
pub fn new(
datadir_path: PathBuf,
datadir_path: LianaDirectory,
gui_config: GUIConfig,
network: bitcoin::Network,
internal_bitcoind: Option<Bitcoind>,
@ -284,8 +285,11 @@ impl Loader {
bitcoind.stop();
log::info!("Managed bitcoind stopped.");
} else if self.waiting_daemon_bitcoind && self.gui_config.start_internal_bitcoind {
let mut daemon_config_path = self.datadir_path.clone();
daemon_config_path.push(self.network.to_string());
let mut daemon_config_path = self
.datadir_path
.network_directory(self.network)
.path()
.to_path_buf();
daemon_config_path.push("daemon.toml");
if let Ok(config) = Config::from_file(Some(daemon_config_path)) {
if let Some(BitcoinBackend::Bitcoind(bitcoind_config)) = &config.bitcoin_backend {
@ -406,7 +410,7 @@ fn get_bitcoind_log(log_path: PathBuf) -> impl Stream<Item = Option<String>> {
pub async fn load_application(
daemon: Arc<dyn Daemon + Sync + Send>,
info: GetInfoResult,
datadir_path: PathBuf,
datadir_path: LianaDirectory,
network: bitcoin::Network,
internal_bitcoind: Option<Bitcoind>,
backup: Option<Backup>,
@ -420,8 +424,9 @@ pub async fn load_application(
),
Error,
> {
let network_dir = datadir_path.network_directory(network);
let wallet = Wallet::new(info.descriptors.main)
.load_from_settings(&datadir_path, network)?
.load_from_settings(&network_dir)?
.load_hotsigners(&datadir_path, network)?;
let coins = coins_to_cache(daemon.clone()).await.map(|res| res.coins)?;
@ -547,12 +552,14 @@ async fn connect(
// Daemon can start only if a config path is given.
pub async fn start_bitcoind_and_daemon(
liana_datadir_path: PathBuf,
liana_datadir_path: LianaDirectory,
start_internal_bitcoind: bool,
network: bitcoin::Network,
) -> StartedResult {
let mut config_path = liana_datadir_path.clone();
config_path.push(network.to_string());
let mut config_path = liana_datadir_path
.network_directory(network)
.path()
.to_path_buf();
config_path.push("daemon.toml");
let config = Config::from_file(Some(config_path)).map_err(Error::Config)?;
let mut bitcoind: Option<Bitcoind> = None;
@ -637,9 +644,8 @@ impl From<DaemonError> for Error {
}
/// default lianad socket path is .liana/bitcoin/lianad_rpc
fn socket_path(datadir: &Path, network: bitcoin::Network) -> PathBuf {
let mut path = datadir.to_path_buf();
path.push(network.to_string());
fn socket_path(datadir: &LianaDirectory, network: bitcoin::Network) -> PathBuf {
let mut path = datadir.network_directory(network).path().to_path_buf();
path.push("lianad_rpc");
path
}

View File

@ -9,6 +9,8 @@ use tracing_subscriber::{
reload, Registry,
};
use crate::dir::LianaDirectory;
const INSTALLER_LOG_FILE_NAME: &str = "installer.log";
const GUI_LOG_FILE_NAME: &str = "liana-gui.log";
@ -77,7 +79,8 @@ impl Logger {
}
}
pub fn set_installer_mode(&self, mut datadir: PathBuf, log_level: filter::LevelFilter) {
pub fn set_installer_mode(&self, datadir: LianaDirectory, log_level: filter::LevelFilter) {
let mut datadir = datadir.path().to_path_buf();
datadir.push(INSTALLER_LOG_FILE_NAME);
if let Err(e) = self.set_layer(datadir, log_level) {
error!("Failed to change logger settings: {:#?}", e);
@ -86,10 +89,11 @@ impl Logger {
pub fn set_running_mode(
&self,
mut datadir: PathBuf,
datadir: LianaDirectory,
network: Network,
log_level: filter::LevelFilter,
) {
let mut datadir = datadir.path().to_path_buf();
datadir.push(network.to_string());
datadir.push(GUI_LOG_FILE_NAME);
if let Err(e) = self.set_layer(datadir, log_level) {
@ -97,7 +101,8 @@ impl Logger {
}
}
pub fn remove_install_log_file(&self, mut datadir: PathBuf) {
pub fn remove_install_log_file(&self, datadir: LianaDirectory) {
let mut datadir = datadir.path().to_path_buf();
datadir.push(INSTALLER_LOG_FILE_NAME);
if let Err(e) = std::fs::remove_file(&datadir) {
error!(

View File

@ -22,8 +22,8 @@ use liana_ui::{component::text, font, image, theme, widget::Element};
use lianad::commands::ListCoinsResult;
use liana_gui::{
app::{self, cache::Cache, config::default_datadir, wallet::Wallet, App},
datadir,
app::{self, cache::Cache, wallet::Wallet, App},
dir::LianaDirectory,
export::import_backup_at_launch,
hw::HardwareWalletConfig,
installer::{self, Installer},
@ -39,7 +39,7 @@ use liana_gui::{
#[derive(Debug, PartialEq)]
enum Arg {
DatadirPath(PathBuf),
DatadirPath(LianaDirectory),
Network(bitcoin::Network),
}
@ -72,7 +72,7 @@ Options:
for (i, arg) in args.iter().enumerate() {
if arg == "--datadir" {
if let Some(a) = args.get(i + 1) {
res.push(Arg::DatadirPath(PathBuf::from(a)));
res.push(Arg::DatadirPath(LianaDirectory::new(PathBuf::from(a))));
} else {
return Err("missing arg to --datadir".into());
}
@ -192,25 +192,25 @@ impl GUI {
}
}
(State::Launcher(l), Message::Launch(msg)) => match *msg {
launcher::Message::Install(datadir_path, network, init) => {
if !datadir_path.exists() {
launcher::Message::Install(datadir, network, init) => {
if !datadir.exists() {
// datadir is created right before launching the installer
// so logs can go in <datadir_path>/installer.log
if let Err(e) = datadir::create_directory(&datadir_path) {
if let Err(e) = datadir.init() {
error!("Failed to create datadir: {}", e);
} else {
info!(
"Created a fresh data directory at {}",
&datadir_path.to_string_lossy()
&datadir.path().to_string_lossy()
);
}
}
self.logger.set_installer_mode(
datadir_path.clone(),
datadir.clone(),
self.log_level.unwrap_or(LevelFilter::INFO),
);
let (install, command) = Installer::new(datadir_path, network, None, init);
let (install, command) = Installer::new(datadir, network, None, init);
self.state = State::Installer(Box::new(install));
command.map(|msg| Message::Install(Box::new(msg)))
}
@ -221,9 +221,8 @@ impl GUI {
self.log_level
.unwrap_or_else(|| cfg.log_level().unwrap_or(LevelFilter::INFO)),
);
if let Ok(settings) =
app::settings::Settings::from_file(datadir_path.clone(), network)
{
let network_dir = datadir_path.network_directory(network);
if let Ok(settings) = app::settings::Settings::from_file(&network_dir) {
if settings
.wallets
.first()
@ -267,7 +266,8 @@ impl GUI {
login::Message::Run(Ok((backend_client, wallet, coins))) => {
let config = app::Config::from_file(
&l.datadir
.join(l.network.to_string())
.network_directory(l.network)
.path()
.join(app::config::DEFAULT_FILE_NAME),
)
.expect("A gui configuration file must be present");
@ -293,7 +293,8 @@ impl GUI {
},
(State::Installer(i), Message::Install(msg)) => {
if let installer::Message::Exit(path, internal_bitcoind, remove_log) = *msg {
let settings = app::settings::Settings::from_file(i.datadir.clone(), i.network)
let network_dir = i.datadir.network_directory(i.network);
let settings = app::settings::Settings::from_file(&network_dir)
.expect("A settings file was created");
if settings
.wallets
@ -451,7 +452,7 @@ pub fn create_app_with_remote_backend(
remote_backend: BackendWalletClient,
wallet: api::Wallet,
coins: ListCoinsResult,
datadir: PathBuf,
datadir: LianaDirectory,
network: bitcoin::Network,
config: app::Config,
) -> (app::App, iced::Task<app::Message>) {
@ -513,18 +514,17 @@ pub fn create_app_with_remote_backend(
}
pub enum Config {
Run(PathBuf, app::Config, bitcoin::Network),
Launcher(PathBuf),
Run(LianaDirectory, app::Config, bitcoin::Network),
Launcher(LianaDirectory),
}
impl Config {
pub fn new(
datadir_path: PathBuf,
datadir_path: LianaDirectory,
network: Option<bitcoin::Network>,
) -> Result<Self, Box<dyn Error>> {
if let Some(network) = network {
let mut path = datadir_path.clone();
path.push(network.to_string());
let mut path = datadir_path.network_directory(network).path().to_path_buf();
path.push(app::config::DEFAULT_FILE_NAME);
match app::Config::from_file(&path) {
Ok(cfg) => Ok(Config::Run(datadir_path, cfg, network)),
@ -540,11 +540,11 @@ fn main() -> Result<(), Box<dyn Error>> {
let args = parse_args(std::env::args().collect())?;
let config = match args.as_slice() {
[] => {
let datadir_path = default_datadir().unwrap();
let datadir_path = LianaDirectory::new_default().unwrap();
Config::new(datadir_path, None)
}
[Arg::Network(network)] => {
let datadir_path = default_datadir().unwrap();
let datadir_path = LianaDirectory::new_default().unwrap();
Config::new(datadir_path, Some(*network))
}
[Arg::DatadirPath(datadir_path)] => Config::new(datadir_path.clone(), None),
@ -640,6 +640,7 @@ fn setup_panic_hook() {
#[cfg(test)]
mod tests {
use super::*;
use liana_gui::dir::LianaDirectory;
#[test]
fn test_parse_args() {
@ -651,7 +652,7 @@ mod tests {
);
assert_eq!(
Some(vec![
Arg::DatadirPath(PathBuf::from("hello")),
Arg::DatadirPath(LianaDirectory::new(PathBuf::from("hello"))),
Arg::Network(bitcoin::Network::Testnet)
]),
parse_args(
@ -665,7 +666,7 @@ mod tests {
assert_eq!(
Some(vec![
Arg::Network(bitcoin::Network::Testnet),
Arg::DatadirPath(PathBuf::from("hello"))
Arg::DatadirPath(LianaDirectory::new(PathBuf::from("hello"))),
]),
parse_args(
"--testnet --datadir hello"

View File

@ -19,6 +19,8 @@ use tracing::{info, warn};
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
use crate::dir::LianaDirectory;
#[cfg(target_os = "windows")]
const CREATE_NO_WINDOW: u32 = 0x08000000;
@ -68,21 +70,22 @@ pub fn download_url() -> String {
)
}
pub fn internal_bitcoind_directory(liana_datadir: &PathBuf) -> PathBuf {
let mut datadir = PathBuf::from(liana_datadir);
datadir.push("bitcoind");
datadir
pub fn internal_bitcoind_directory(liana_datadir: &LianaDirectory) -> PathBuf {
liana_datadir.bitcoind_directory().path().to_path_buf()
}
/// Data directory used by internal bitcoind.
pub fn internal_bitcoind_datadir(liana_datadir: &PathBuf) -> PathBuf {
pub fn internal_bitcoind_datadir(liana_datadir: &LianaDirectory) -> PathBuf {
let mut datadir = internal_bitcoind_directory(liana_datadir);
datadir.push("datadir");
datadir
}
/// Internal bitcoind executable path.
pub fn internal_bitcoind_exe_path(liana_datadir: &PathBuf, bitcoind_version: &str) -> PathBuf {
pub fn internal_bitcoind_exe_path(
liana_datadir: &LianaDirectory,
bitcoind_version: &str,
) -> PathBuf {
internal_bitcoind_directory(liana_datadir)
.join(format!("bitcoin-{}", bitcoind_version))
.join("bin")
@ -94,7 +97,7 @@ pub fn internal_bitcoind_exe_path(liana_datadir: &PathBuf, bitcoind_version: &st
}
/// Path of the `bitcoin.conf` file used by internal bitcoind.
pub fn internal_bitcoind_config_path(bitcoind_datadir: &PathBuf) -> PathBuf {
pub fn internal_bitcoind_config_path(bitcoind_datadir: &Path) -> PathBuf {
let mut config_path = PathBuf::from(bitcoind_datadir);
config_path.push("bitcoin.conf");
config_path
@ -111,7 +114,10 @@ pub fn internal_bitcoind_cookie_path(bitcoind_datadir: &Path, network: &Network)
}
/// Path of the cookie file used by internal bitcoind on a given network.
pub fn internal_bitcoind_debug_log_path(lianad_datadir: &PathBuf, network: Network) -> PathBuf {
pub fn internal_bitcoind_debug_log_path(
lianad_datadir: &LianaDirectory,
network: Network,
) -> PathBuf {
let mut debug_log_path = internal_bitcoind_datadir(lianad_datadir);
if let Some(dir) = bitcoind_network_dir(&network) {
debug_log_path.push(dir);
@ -400,7 +406,7 @@ impl Bitcoind {
pub fn start(
network: &bitcoin::Network,
config: BitcoindConfig,
liana_datadir: &PathBuf,
liana_datadir: &LianaDirectory,
) -> Result<Self, StartInternalBitcoindError> {
let bitcoind_datadir = internal_bitcoind_datadir(liana_datadir);
// Find most recent bitcoind version available.

View File

@ -2,7 +2,6 @@ pub mod api;
use std::{
collections::{HashMap, HashSet},
path::Path,
sync::Arc,
};
@ -23,6 +22,7 @@ use tokio::sync::RwLock;
use crate::{
app::settings::{AuthConfig, Settings},
daemon::{model::*, Daemon, DaemonBackend, DaemonError},
dir::LianaDirectory,
hw::HardwareWalletConfig,
};
@ -519,7 +519,11 @@ impl Daemon for BackendWalletClient {
}
/// refresh the token if close to expiration.
async fn is_alive(&self, datadir: &Path, network: Network) -> Result<(), DaemonError> {
async fn is_alive(
&self,
datadir: &LianaDirectory,
network: Network,
) -> Result<(), DaemonError> {
let auth = self.auth().await;
if auth.expires_at < Utc::now().timestamp() + 60 {
match self.inner.auth.try_write() {
@ -534,13 +538,13 @@ impl Daemon for BackendWalletClient {
.refresh_token(&auth.refresh_token)
.await?;
let mut settings = Settings::from_file(datadir.to_path_buf(), network)
.map_err(|e| {
DaemonError::Unexpected(format!(
"Cannot access to settings.json file: {}",
e
))
})?;
let network_dir = datadir.network_directory(network);
let mut settings = Settings::from_file(&network_dir).map_err(|e| {
DaemonError::Unexpected(format!(
"Cannot access to settings.json file: {}",
e
))
})?;
if let Some(wallet_settings) = settings.wallets.iter_mut().find(|w| {
if let Some(auth) = &w.remote_backend_auth {
@ -558,14 +562,12 @@ impl Daemon for BackendWalletClient {
tracing::info!("Wallet id was not found in the settings");
}
settings
.to_file(datadir.to_path_buf(), network)
.map_err(|e| {
DaemonError::Unexpected(format!(
"Cannot access to settings.json file: {}",
e
))
})?;
settings.to_file(&network_dir).map_err(|e| {
DaemonError::Unexpected(format!(
"Cannot access to settings.json file: {}",
e
))
})?;
*old = new;
tracing::info!("Liana backend access was refreshed");

View File

@ -1,4 +1,4 @@
use std::{path::PathBuf, sync::Arc};
use std::sync::Arc;
use iced::{Alignment, Length, Task};
@ -16,6 +16,7 @@ use crate::{
settings::{AuthConfig, Settings, SettingsError, WalletSetting},
},
daemon::DaemonError,
dir::LianaDirectory,
};
use super::client::{
@ -100,7 +101,7 @@ pub enum BackendState {
}
pub struct LianaLiteLogin {
pub datadir: PathBuf,
pub datadir: LianaDirectory,
pub network: Network,
wallet_id: String,
@ -128,7 +129,11 @@ pub enum ConnectionStep {
}
impl LianaLiteLogin {
pub fn new(datadir: PathBuf, network: Network, settings: Settings) -> (Self, Task<Message>) {
pub fn new(
datadir: LianaDirectory,
network: Network,
settings: Settings,
) -> (Self, Task<Message>) {
match settings
.wallets
.first()
@ -490,13 +495,14 @@ impl LianaLiteLogin {
}
async fn update_wallet_auth_settings(
datadir: PathBuf,
datadir: LianaDirectory,
network: Network,
wallet: api::Wallet,
email: String,
refresh_token: String,
) -> Result<(), Error> {
let mut settings = Settings::from_file(datadir.clone(), network)?;
let network_dir = datadir.network_directory(network);
let mut settings = Settings::from_file(&network_dir)?;
let descriptor_checksum = wallet
.descriptor
@ -534,7 +540,7 @@ async fn update_wallet_auth_settings(
);
}
settings.to_file(datadir, network).map_err(|e| {
settings.to_file(&network_dir).map_err(|e| {
DaemonError::Unexpected(format!("Cannot access to settings.json file: {}", e))
})?;

View File

@ -9,6 +9,8 @@ use liana::{
signer::HotSigner,
};
use crate::dir::LianaDirectory;
pub struct Signer {
curve: secp256k1::Secp256k1<secp256k1::All>,
key: HotSigner,
@ -58,9 +60,9 @@ impl Signer {
pub fn store(
&self,
datadir_root: &std::path::Path,
datadir_root: &LianaDirectory,
network: Network,
) -> Result<(), SignerError> {
self.key.store(datadir_root, network, &self.curve)
self.key.store(datadir_root.path(), network, &self.curve)
}
}

View File

@ -405,8 +405,8 @@ impl DaemonHandle {
let data_dir = config
.data_directory()
.ok_or(StartupError::DefaultDataDirNotFound)?;
let fresh_data_dir = !data_dir.exists();
if fresh_data_dir {
let fresh_data_dir = !data_dir.exists() || !data_dir.sqlite_db_file_path().exists();
if !data_dir.exists() {
data_dir
.init()
.map_err(|e| StartupError::DatadirCreation(data_dir.path().to_path_buf(), e))?;