fix export: use error handling

This commit is contained in:
edouardparis 2025-03-19 13:24:54 +01:00
parent bd54fb29bf
commit f983b418ea

View File

@ -3,7 +3,7 @@ use std::{
fmt::Display,
fs::{self, File},
io::{Read, Write},
path::PathBuf,
path::{Path, PathBuf},
str::FromStr,
sync::{
mpsc::{channel, sync_channel, Receiver, Sender, SyncSender},
@ -48,19 +48,6 @@ use crate::{
const DUMP_LABELS_LIMIT: u32 = 100;
macro_rules! send_error {
($sender:ident, $error:ident) => {
if let Err(e) = $sender.send(Progress::Error(Error::$error)) {
tracing::error!("Import/Export fail to send msg: {}", e);
}
};
($sender:ident, $error:expr) => {
if let Err(e) = $sender.send(Progress::Error($error)) {
tracing::error!("Import/Export fail to send msg: {}", e);
}
};
}
macro_rules! send_progress {
($sender:ident, $progress:ident) => {
if let Err(e) = $sender.send(Progress::$progress) {
@ -74,41 +61,13 @@ macro_rules! send_progress {
};
}
macro_rules! open_file_write {
($path:ident, $sender:ident) => {{
let dir = match $path.parent() {
Some(dir) => dir,
None => {
send_error!($sender, NoParentDir);
return;
}
};
if !dir.exists() {
if let Err(e) = fs::create_dir_all(dir) {
send_error!($sender, e.into());
return;
}
}
match File::create($path.as_path()) {
Ok(f) => f,
Err(e) => {
send_error!($sender, e.into());
return;
}
}
}};
}
macro_rules! open_file_read {
($path:ident, $sender:ident) => {{
match File::open($path.as_path()) {
Ok(f) => f,
Err(e) => {
send_error!($sender, e.into());
return;
}
}
}};
async fn open_file_write(path: &Path) -> Result<File, Error> {
let dir = path.parent().ok_or(Error::NoParentDir)?;
if !dir.exists() {
fs::create_dir_all(dir)?;
}
let file = File::create(path)?;
Ok(file)
}
#[derive(Debug, Clone)]
@ -314,17 +273,23 @@ impl Export {
daemon: Option<Arc<dyn Daemon + Sync + Send>>,
path: PathBuf,
) {
match export_type {
ImportExportType::Transactions => export_transactions(sender, daemon, path).await,
ImportExportType::ExportPsbt(str) => export_string(sender, path, str),
ImportExportType::Descriptor(descriptor) => export_descriptor(sender, path, descriptor),
ImportExportType::ExportLabels => export_labels(sender, daemon, path).await,
ImportExportType::ImportPsbt => import_psbt(sender, path),
ImportExportType::ImportDescriptor => import_descriptor(sender, path),
ImportExportType::ExportBackup(str) => export_string(sender, path, str),
ImportExportType::ImportBackup(..) => import_backup(sender, path, daemon).await,
ImportExportType::WalletFromBackup => wallet_from_backup(sender, path).await,
};
if let Err(e) = match export_type {
ImportExportType::Transactions => export_transactions(&sender, daemon, path).await,
ImportExportType::ExportPsbt(str) => export_string(&sender, path, str).await,
ImportExportType::Descriptor(descriptor) => {
export_descriptor(&sender, path, descriptor).await
}
ImportExportType::ExportLabels => export_labels(&sender, daemon, path).await,
ImportExportType::ImportPsbt => import_psbt(&sender, path).await,
ImportExportType::ImportDescriptor => import_descriptor(&sender, path).await,
ImportExportType::ExportBackup(str) => export_string(&sender, path, str).await,
ImportExportType::ImportBackup(..) => import_backup(&sender, path, daemon).await,
ImportExportType::WalletFromBackup => wallet_from_backup(&sender, path).await,
} {
if let Err(e) = sender.send(Progress::Error(e)) {
tracing::error!("Import/Export fail to send msg: {}", e);
}
}
}
pub async fn start(&mut self) {
@ -422,233 +387,194 @@ pub fn export_subscription(
}
pub async fn export_transactions(
sender: Sender<Progress>,
sender: &Sender<Progress>,
daemon: Option<Arc<dyn Daemon + Sync + Send>>,
path: PathBuf,
) {
async move {
let daemon = match daemon {
Some(d) => d,
None => {
send_error!(sender, Error::DaemonMissing);
return;
}
};
let mut file = open_file_write!(path, sender);
) -> Result<(), Error> {
let daemon = daemon.ok_or(Error::DaemonMissing)?;
let mut file = open_file_write(&path).await?;
let header = "Date,Label,Value,Fee,Txid,Block\n".to_string();
if let Err(e) = file.write_all(header.as_bytes()) {
send_error!(sender, e.into());
return;
let header = "Date,Label,Value,Fee,Txid,Block\n".to_string();
file.write_all(header.as_bytes())?;
// look 2 hour forward
// https://github.com/bitcoin/bitcoin/blob/62bd61de110b057cbfd6e31e4d0b727d93119c72/src/chain.h#L29
let mut end = ((Utc::now() + Duration::hours(2)).timestamp()) as u32;
let total_txs = daemon
.list_confirmed_txs(0, end, u32::MAX as u64)
.await?
.transactions
.len();
if total_txs == 0 {
send_progress!(sender, Ended);
} else {
send_progress!(sender, Progress(5.0));
}
let max = match daemon.backend() {
DaemonBackend::RemoteBackend => DEFAULT_LIMIT as u64,
_ => u32::MAX as u64,
};
// store txs in a map to avoid duplicates
let mut map = HashMap::<Txid, HistoryTransaction>::new();
let mut limit = max;
loop {
let history_txs = daemon.list_history_txs(0, end, limit).await?;
let dl = map.len() + history_txs.len();
if dl > 0 {
let progress = (dl as f32) / (total_txs as f32) * 80.0;
send_progress!(sender, Progress(progress));
}
// look 2 hour forward
// https://github.com/bitcoin/bitcoin/blob/62bd61de110b057cbfd6e31e4d0b727d93119c72/src/chain.h#L29
let mut end = ((Utc::now() + Duration::hours(2)).timestamp()) as u32;
let total_txs = daemon.list_confirmed_txs(0, end, u32::MAX as u64).await;
let total_txs = match total_txs {
Ok(r) => r.transactions.len(),
Err(e) => {
send_error!(sender, e.into());
return;
}
};
if total_txs == 0 {
send_progress!(sender, Ended);
} else {
send_progress!(sender, Progress(5.0));
// all txs have been fetched
if history_txs.is_empty() {
break;
}
let max = match daemon.backend() {
DaemonBackend::RemoteBackend => DEFAULT_LIMIT as u64,
_ => u32::MAX as u64,
};
// store txs in a map to avoid duplicates
let mut map = HashMap::<Txid, HistoryTransaction>::new();
let mut limit = max;
loop {
let history = daemon.list_history_txs(0, end, limit).await;
let history_txs = match history {
Ok(h) => h,
Err(e) => {
send_error!(sender, e.into());
return;
}
if history_txs.len() == limit as usize {
let first = if let Some(t) = history_txs.first().expect("checked").time {
t
} else {
return Err(Error::TxTimeMissing);
};
let dl = map.len() + history_txs.len();
if dl > 0 {
let progress = (dl as f32) / (total_txs as f32) * 80.0;
send_progress!(sender, Progress(progress));
}
// all txs have been fetched
if history_txs.is_empty() {
break;
}
if history_txs.len() == limit as usize {
let first = if let Some(t) = history_txs.first().expect("checked").time {
t
} else {
send_error!(sender, TxTimeMissing);
return;
};
let last = if let Some(t) = history_txs.last().expect("checked").time {
t
} else {
send_error!(sender, TxTimeMissing);
return;
};
// limit too low, all tx are in the same timestamp
// we must increase limit and retry
if first == last {
limit += DEFAULT_LIMIT as u64;
continue;
} else {
// add txs to map
for tx in history_txs {
let txid = tx.txid;
map.insert(txid, tx);
}
limit = max;
end = first.min(last);
continue;
}
} else
/* history_txs.len() < limit */
{
let last = if let Some(t) = history_txs.last().expect("checked").time {
t
} else {
return Err(Error::TxTimeMissing);
};
// limit too low, all tx are in the same timestamp
// we must increase limit and retry
if first == last {
limit += DEFAULT_LIMIT as u64;
continue;
} else {
// add txs to map
for tx in history_txs {
let txid = tx.txid;
map.insert(txid, tx);
}
break;
limit = max;
end = first.min(last);
continue;
}
} else
/* history_txs.len() < limit */
{
// add txs to map
for tx in history_txs {
let txid = tx.txid;
map.insert(txid, tx);
}
break;
}
let mut txs: Vec<_> = map.into_values().collect();
txs.sort_by(|a, b| b.compare(a));
for mut tx in txs {
let date_time = tx
.time
.map(|t| {
let mut str = DateTime::from_timestamp(t as i64, 0)
.expect("bitcoin timestamp")
.to_rfc3339();
//str has the form `1996-12-19T16:39:57-08:00`
// ^ ^^^^^^
// replace `T` by ` `| | drop this part
str = str.replace("T", " ");
str[0..(str.len() - 6)].to_string()
})
.unwrap_or("".to_string());
let txid = tx.txid.clone().to_string();
let txid_label = tx.labels().get(&txid).cloned();
let mut label = if let Some(txid) = txid_label {
txid
} else {
"".to_string()
};
if !label.is_empty() {
label = format!("\"{}\"", label);
}
let txid = tx.txid.to_string();
let fee = tx.fee_amount.unwrap_or(Amount::ZERO).to_sat() as i128;
let mut inputs_amount = 0;
tx.coins.iter().for_each(|(_, coin)| {
inputs_amount += coin.amount.to_sat() as i128;
});
let value = tx.incoming_amount.to_sat() as i128 - inputs_amount;
let value = value as f64 / 100_000_000.0;
let fee = fee as f64 / 100_000_000.0;
let block = tx.height.map(|h| h.to_string()).unwrap_or("".to_string());
let fee = if fee != 0.0 {
fee.to_string()
} else {
"".into()
};
let line = format!(
"{},{},{},{},{},{}\n",
date_time, label, value, fee, txid, block
);
if let Err(e) = file.write_all(line.as_bytes()) {
send_error!(sender, e.into());
return;
}
}
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
}
.await;
let mut txs: Vec<_> = map.into_values().collect();
txs.sort_by(|a, b| b.compare(a));
for mut tx in txs {
let date_time = tx
.time
.map(|t| {
let mut str = DateTime::from_timestamp(t as i64, 0)
.expect("bitcoin timestamp")
.to_rfc3339();
//str has the form `1996-12-19T16:39:57-08:00`
// ^ ^^^^^^
// replace `T` by ` `| | drop this part
str = str.replace("T", " ");
str[0..(str.len() - 6)].to_string()
})
.unwrap_or("".to_string());
let txid = tx.txid.clone().to_string();
let txid_label = tx.labels().get(&txid).cloned();
let mut label = if let Some(txid) = txid_label {
txid
} else {
"".to_string()
};
if !label.is_empty() {
label = format!("\"{}\"", label);
}
let txid = tx.txid.to_string();
let fee = tx.fee_amount.unwrap_or(Amount::ZERO).to_sat() as i128;
let mut inputs_amount = 0;
tx.coins.iter().for_each(|(_, coin)| {
inputs_amount += coin.amount.to_sat() as i128;
});
let value = tx.incoming_amount.to_sat() as i128 - inputs_amount;
let value = value as f64 / 100_000_000.0;
let fee = fee as f64 / 100_000_000.0;
let block = tx.height.map(|h| h.to_string()).unwrap_or("".to_string());
let fee = if fee != 0.0 {
fee.to_string()
} else {
"".into()
};
let line = format!(
"{},{},{},{},{},{}\n",
date_time, label, value, fee, txid, block
);
file.write_all(line.as_bytes())?;
}
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
Ok(())
}
pub fn export_descriptor(sender: Sender<Progress>, path: PathBuf, descriptor: LianaDescriptor) {
let mut file = open_file_write!(path, sender);
pub async fn export_descriptor(
sender: &Sender<Progress>,
path: PathBuf,
descriptor: LianaDescriptor,
) -> Result<(), Error> {
let mut file = open_file_write(&path).await?;
let descr_string = descriptor.to_string();
if let Err(e) = file.write_all(descr_string.as_bytes()) {
send_error!(sender, e.into());
return;
}
file.write_all(descr_string.as_bytes())?;
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
Ok(())
}
pub fn export_string(sender: Sender<Progress>, path: PathBuf, psbt: String) {
let mut file = open_file_write!(path, sender);
if let Err(e) = file.write_all(psbt.as_bytes()) {
send_error!(sender, e.into());
return;
}
pub async fn export_string(
sender: &Sender<Progress>,
path: PathBuf,
psbt: String,
) -> Result<(), Error> {
let mut file = open_file_write(&path).await?;
file.write_all(psbt.as_bytes())?;
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
Ok(())
}
pub fn import_psbt(sender: Sender<Progress>, path: PathBuf) {
let mut file = open_file_read!(path, sender);
pub async fn import_psbt(sender: &Sender<Progress>, path: PathBuf) -> Result<(), Error> {
let mut file = File::open(&path)?;
let mut psbt_str = String::new();
if let Err(e) = file.read_to_string(&mut psbt_str) {
send_error!(sender, e.into());
return;
}
file.read_to_string(&mut psbt_str)?;
let psbt = match Psbt::from_str(&psbt_str) {
Ok(psbt) => psbt,
Err(_) => {
send_error!(sender, Error::ParsePsbt);
return;
}
};
let psbt = Psbt::from_str(&psbt_str).map_err(|_| Error::ParsePsbt)?;
send_progress!(sender, Progress(100.0));
send_progress!(sender, Psbt(psbt));
Ok(())
}
pub fn import_descriptor(sender: Sender<Progress>, path: PathBuf) {
let mut file = open_file_read!(path, sender);
pub async fn import_descriptor(sender: &Sender<Progress>, path: PathBuf) -> Result<(), Error> {
let mut file = File::open(path)?;
let mut descr_str = String::new();
if let Err(e) = file.read_to_string(&mut descr_str) {
send_error!(sender, e.into());
return;
}
let descriptor = match LianaDescriptor::from_str(&descr_str) {
Ok(psbt) => psbt,
Err(_) => {
send_error!(sender, Error::ParseDescriptor);
return;
}
};
file.read_to_string(&mut descr_str)?;
let descriptor = LianaDescriptor::from_str(&descr_str).map_err(|_| Error::ParseDescriptor)?;
send_progress!(sender, Progress(100.0));
send_progress!(sender, Descriptor(descriptor));
Ok(())
}
/// Import a backup in an already existing wallet:
@ -663,42 +589,30 @@ pub fn import_descriptor(sender: Sender<Progress>, path: PathBuf) {
/// - import labels if no conflict or user ACK
/// - update aliases if no conflict or user ACK
pub async fn import_backup(
sender: Sender<Progress>,
sender: &Sender<Progress>,
path: PathBuf,
daemon: Option<Arc<dyn Daemon + Sync + Send>>,
) {
let daemon = match daemon {
Some(d) => d,
None => {
send_error!(sender, Error::DaemonMissing);
return;
}
};
) -> Result<(), Error> {
let daemon = daemon.ok_or(Error::DaemonMissing)?;
// TODO: drop after support for restore to liana-connect
if matches!(daemon.backend(), DaemonBackend::RemoteBackend) {
send_error!(
sender,
Error::BackupImport("Restore to a Liana-connect backend is not yet supported!".into())
);
return;
return Err(Error::BackupImport(
"Restore to a Liana-connect backend is not yet supported!".into(),
));
}
// Load backup from file
let mut file = open_file_read!(path, sender);
let mut file = File::open(&path)?;
let mut backup_str = String::new();
if let Err(e) = file.read_to_string(&mut backup_str) {
send_error!(sender, e.into());
return;
}
file.read_to_string(&mut backup_str)?;
let backup: Result<Backup, _> = serde_json::from_str(&backup_str);
let backup = match backup {
Ok(psbt) => psbt,
Err(e) => {
send_error!(sender, Error::BackupImport(format!("{:?}", e)));
return;
return Err(Error::BackupImport(format!("{:?}", e)));
}
};
@ -706,63 +620,47 @@ pub async fn import_backup(
let info = match daemon.get_info().await {
Ok(info) => info,
Err(e) => {
send_error!(sender, Error::Daemon(format!("{e:?}")));
return;
return Err(Error::Daemon(format!("{e:?}")));
}
};
// check if networks matches
let network = info.network;
if backup.network != network {
send_error!(
sender,
Error::BackupImport("The network of the backup don't match the wallet network!".into())
);
return;
return Err(Error::BackupImport(
"The network of the backup don't match the wallet network!".into(),
));
}
// check if descriptors matches
let descriptor = info.descriptors.main;
let account = match backup.accounts.len() {
0 => {
send_error!(
sender,
Error::BackupImport("There is no account in the backup!".into())
);
return;
return Err(Error::BackupImport(
"There is no account in the backup!".into(),
));
}
1 => backup.accounts.first().expect("already checked"),
_ => {
send_error!(
sender,
Error::BackupImport(
"Liana is actually not supporting import of backup with several accounts!"
.into()
)
);
return;
return Err(Error::BackupImport(
"Liana is actually not supporting import of backup with several accounts!".into(),
));
}
};
let backup_descriptor = match LianaDescriptor::from_str(&account.descriptor) {
Ok(d) => d,
Err(_) => {
send_error!(
sender,
Error::BackupImport(
"The backup descriptor is not a valid Liana descriptor!".into()
)
);
return;
return Err(Error::BackupImport(
"The backup descriptor is not a valid Liana descriptor!".into(),
));
}
};
if backup_descriptor != descriptor {
send_error!(
sender,
Error::BackupImport("The backup descriptor do not match this wallet!".into())
);
return;
return Err(Error::BackupImport(
"The backup descriptor do not match this wallet!".into(),
));
}
// TODO: check if timestamp matches?
@ -773,8 +671,7 @@ pub async fn import_backup(
let db_labels = match daemon.get_labels_bip329(0, u32::MAX).await {
Ok(l) => l,
Err(_) => {
send_error!(sender, Error::BackupImport("Fail to dump DB labels".into()));
return;
return Err(Error::BackupImport("Fail to dump DB labels".into()));
}
};
@ -797,11 +694,7 @@ pub async fn import_backup(
write_labels = match ack_receiver.recv() {
Ok(b) => b,
Err(_) => {
send_error!(
sender,
Error::BackupImport("Fail to receive labels ACK".into())
);
return;
return Err(Error::BackupImport("Fail to receive labels ACK".into()));
}
}
}
@ -815,19 +708,11 @@ pub async fn import_backup(
Some(c) => match &c.data_dir {
Some(dd) => dd,
None => {
send_error!(
sender,
Error::BackupImport("Fail to get Daemon config".into())
);
return;
return Err(Error::BackupImport("Fail to get Daemon config".into()));
}
},
None => {
send_error!(
sender,
Error::BackupImport("Fail to get Daemon config".into())
);
return;
return Err(Error::BackupImport("Fail to get Daemon config".into()));
}
};
@ -837,11 +722,7 @@ pub async fn import_backup(
let settings = match Settings::from_file(datadir.to_path_buf(), network) {
Ok(s) => s,
Err(_) => {
send_error!(
sender,
Error::BackupImport("Fail to get App Settings".into())
);
return;
return Err(Error::BackupImport("Fail to get App Settings".into()));
}
};
@ -856,11 +737,9 @@ pub async fn import_backup(
.map(|s| (s.master_fingerprint, s))
.collect(),
_ => {
send_error!(
sender,
Error::BackupImport("Settings.wallets.len() is not 1".into())
);
return;
return Err(Error::BackupImport(
"Settings.wallets.len() is not 1".into(),
));
}
};
@ -881,11 +760,7 @@ pub async fn import_backup(
write_aliases = match ack_receiver.recv() {
Ok(a) => a,
Err(_) => {
send_error!(
sender,
Error::BackupImport("Fail to receive aliases ACK".into())
);
return;
return Err(Error::BackupImport("Fail to receive aliases ACK".into()));
}
};
}
@ -905,11 +780,9 @@ pub async fn import_backup(
let change = if db_change < i { Some(i) } else { None };
if daemon.update_deriv_indexes(receive, change).await.is_err() {
send_error!(
sender,
Error::BackupImport("Fail to update derivation indexes".into())
);
return;
return Err(Error::BackupImport(
"Fail to update derivation indexes".into(),
));
}
// parse PSBTs
@ -920,8 +793,7 @@ pub async fn import_backup(
psbts.push(p);
}
Err(_) => {
send_error!(sender, Error::BackupImport("Fail to parse PSBT".into()));
return;
return Err(Error::BackupImport("Fail to parse PSBT".into()));
}
}
}
@ -929,8 +801,7 @@ pub async fn import_backup(
// import PSBTs
for psbt in psbts {
if daemon.update_spend_tx(&psbt).await.is_err() {
send_error!(sender, Error::BackupImport("Fail to store PSBT".into()));
return;
return Err(Error::BackupImport("Fail to store PSBT".into()));
}
}
@ -947,8 +818,7 @@ pub async fn import_backup(
})
.collect();
if daemon.update_labels(&labels).await.is_err() {
send_error!(sender, Error::BackupImport("Fail to import labels".into()));
return;
return Err(Error::BackupImport("Fail to import labels".into()));
}
}
@ -969,11 +839,7 @@ pub async fn import_backup(
settings.wallets.get_mut(0).expect("already checked").keys =
settings_aliases.clone().into_values().collect();
if settings.to_file(datadir.to_path_buf(), network).is_err() {
send_error!(
sender,
Error::BackupImport("Fail to import keys aliases".into())
);
return;
return Err(Error::BackupImport("Fail to import keys aliases".into()));
} else {
// Update wallet state
send_progress!(sender, UpdateAliases(settings_aliases));
@ -982,6 +848,7 @@ pub async fn import_backup(
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
Ok(())
}
#[derive(Debug)]
@ -1035,22 +902,18 @@ impl From<DaemonError> for RestoreBackupError {
/// - extract descriptor
/// - extract network
/// - extract aliases
pub async fn wallet_from_backup(sender: Sender<Progress>, path: PathBuf) {
pub async fn wallet_from_backup(sender: &Sender<Progress>, path: PathBuf) -> Result<(), Error> {
// Load backup from file
let mut file = open_file_read!(path, sender);
let mut file = File::open(path)?;
let mut backup_str = String::new();
if let Err(e) = file.read_to_string(&mut backup_str) {
send_error!(sender, e.into());
return;
}
file.read_to_string(&mut backup_str)?;
let backup: Result<Backup, _> = serde_json::from_str(&backup_str);
let backup = match backup {
Ok(psbt) => psbt,
Err(e) => {
send_error!(sender, Error::BackupImport(format!("{:?}", e)));
return;
return Err(Error::BackupImport(format!("{:?}", e)));
}
};
@ -1058,35 +921,24 @@ pub async fn wallet_from_backup(sender: Sender<Progress>, path: PathBuf) {
let account = match backup.accounts.len() {
0 => {
send_error!(
sender,
Error::BackupImport("There is no account in the backup!".into())
);
return;
return Err(Error::BackupImport(
"There is no account in the backup!".into(),
));
}
1 => backup.accounts.first().expect("already checked"),
_ => {
send_error!(
sender,
Error::BackupImport(
"Liana is actually not supporting import of backup with several accounts!"
.into()
)
);
return;
return Err(Error::BackupImport(
"Liana is actually not supporting import of backup with several accounts!".into(),
));
}
};
let descriptor = match LianaDescriptor::from_str(&account.descriptor) {
Ok(d) => d,
Err(_) => {
send_error!(
sender,
Error::BackupImport(
"The backup descriptor is not a valid Liana descriptor!".into()
)
);
return;
return Err(Error::BackupImport(
"The backup descriptor is not a valid Liana descriptor!".into(),
));
}
};
@ -1109,6 +961,7 @@ pub async fn wallet_from_backup(sender: Sender<Progress>, path: PathBuf) {
);
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
Ok(())
}
#[allow(unused)]
@ -1223,28 +1076,18 @@ pub async fn import_backup_at_launch(
}
pub async fn export_labels(
sender: Sender<Progress>,
sender: &Sender<Progress>,
daemon: Option<Arc<dyn Daemon + Sync + Send>>,
path: PathBuf,
) {
let daemon = match daemon {
Some(d) => d,
None => {
send_error!(sender, Error::DaemonMissing);
return;
}
};
) -> Result<(), Error> {
let daemon = daemon.ok_or(Error::DaemonMissing)?;
let mut labels = Labels::new(Vec::new());
let mut offset = 0u32;
loop {
let mut fetched = match daemon.get_labels_bip329(offset, DUMP_LABELS_LIMIT).await {
Ok(l) => l,
Err(e) => {
send_error!(sender, e.into());
return;
}
}
.into_vec();
let mut fetched = daemon
.get_labels_bip329(offset, DUMP_LABELS_LIMIT)
.await?
.into_vec();
let fetch_len = fetched.len() as u32;
labels.append(&mut fetched);
if fetch_len < DUMP_LABELS_LIMIT {
@ -1253,21 +1096,13 @@ pub async fn export_labels(
offset += DUMP_LABELS_LIMIT;
}
}
let json = match labels.export() {
Ok(j) => j,
Err(e) => {
send_error!(sender, e.into());
return;
}
};
let mut file = open_file_write!(path, sender);
let json = labels.export()?;
let mut file = open_file_write(&path).await?;
if let Err(e) = file.write_all(json.as_bytes()) {
send_error!(sender, e.into());
return;
}
file.write_all(json.as_bytes())?;
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
Ok(())
}
pub async fn get_path(filename: String, write: bool) -> Option<PathBuf> {