From 4835465dad3640813d6629862eca8b7d3c16879a Mon Sep 17 00:00:00 2001 From: pythcoiner Date: Tue, 10 Dec 2024 04:21:56 +0100 Subject: [PATCH] gui(export): do not use a separate function for export logic --- liana-gui/src/app/export.rs | 241 +++++++++++++----------------- liana-gui/src/app/state/export.rs | 8 +- 2 files changed, 105 insertions(+), 144 deletions(-) diff --git a/liana-gui/src/app/export.rs b/liana-gui/src/app/export.rs index a82d6f646..0bf87ae48 100644 --- a/liana-gui/src/app/export.rs +++ b/liana-gui/src/app/export.rs @@ -10,15 +10,16 @@ use std::{ }; use chrono::{DateTime, Duration, Utc}; -use liana::miniscript::bitcoin::{Amount, Denomination::Bitcoin}; -use lianad::commands::LabelItem; +use liana::miniscript::bitcoin::Amount; use tokio::{ - runtime::Runtime, task::{JoinError, JoinHandle}, time::sleep, }; -use crate::daemon::{model::Labelled, Daemon, DaemonError}; +use crate::daemon::{ + model::{Labelled, TransactionKind}, + Daemon, DaemonError, +}; macro_rules! send_error { ($sender:ident, $error:ident) => { @@ -79,33 +80,22 @@ pub enum ExportProgress { None, } -#[derive(Debug)] -pub enum ExportType { - Transactions, -} - pub struct ExportState { pub receiver: Receiver, pub sender: Option>, pub handle: Option>>>, pub daemon: Arc, - pub export_type: ExportType, pub path: Box, } impl ExportState { - pub fn new( - daemon: Arc, - export_type: ExportType, - path: Box, - ) -> Self { + pub fn new(daemon: Arc, path: Box) -> Self { let (sender, receiver) = channel(); ExportState { receiver, sender: Some(sender), handle: None, daemon, - export_type, path, } } @@ -114,11 +104,106 @@ impl ExportState { if let (true, Some(sender)) = (self.handle.is_none(), self.sender.take()) { let daemon = self.daemon.clone(); let path = self.path.clone(); - let function = self.function(); let cloned_sender = sender.clone(); let handle = tokio::spawn(async move { - function(sender, daemon, path); + 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; + } + } + let mut file = match File::create(path.as_path()) { + Ok(f) => f, + Err(e) => { + send_error!(sender, e.into()); + return; + } + }; + 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 info = daemon.get_info().await; + + let start = match info { + Ok(info) => info.timestamp, + Err(e) => { + send_error!(sender, e.into()); + return; + } + }; + // look 2 hour forward + let end = ((Utc::now() + Duration::hours(2)).timestamp()) as u32; + + let history = daemon.list_history_txs(start, end, u32::MAX as u64).await; + let txs = match history { + Ok(h) => h, + Err(e) => { + send_error!(sender, e.into()); + return; + } + }; + + 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 = 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 addr = if let TransactionKind::IncomingSinglePayment(outpoint) = tx.kind { + tx.coins.get(&outpoint).map(|c| c.address.to_string()) + } else { + None + }; + let mut label = if let Some(txid) = txid_label { + txid + } else if let Some(addr) = addr { + addr + } else if tx.is_send_to_self() { + "self send".to_string() + } else { + "".to_string() + }; + if !label.is_empty() { + label = format!("\"{}\"", label); + } + let fee = tx.fee_amount.unwrap_or(Amount::ZERO).to_btc(); + let value = tx.incoming_amount.to_btc() - tx.outgoing_amount.to_btc() - fee; + let txid = tx.txid.to_string(); + let block = tx.height.map(|h| h.to_string()).unwrap_or("".to_string()); + + 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; + } + } + + if let Err(e) = sender.send(ExportProgress::Ended(path)) { + tracing::error!("ExportState::start() fail to send msg: {}", e); + } }); let handle = Arc::new(Mutex::new(handle)); @@ -140,14 +225,6 @@ impl ExportState { _ => unreachable!(), } } - - pub fn function( - &self, - ) -> impl Fn(Sender, Arc, Box) { - match self.export_type { - ExportType::Transactions => export_transactions, - } - } } pub async fn export_subscription(mut state: ExportState) -> (ExportProgress, ExportState) { @@ -188,118 +265,6 @@ pub async fn export_subscription(mut state: ExportState) -> (ExportProgress, Exp (ExportProgress::None, state) } -pub fn export_transactions( - sender: Sender, - daemon: Arc, - path: Box, -) { - log::info!("export_transactions()"); - 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; - } - } - let mut file = match File::create(path.as_path()) { - Ok(f) => f, - Err(e) => { - send_error!(sender, e.into()); - return; - } - }; - let header = "Date,Label,Value,Fee,Txid,Block".to_string(); - if let Err(e) = file.write_all(header.as_bytes()) { - send_error!(sender, e.into()); - return; - } - log::info!("export_transactions() header written"); - - let rt = Runtime::new().unwrap(); - let info = rt.block_on(daemon.get_info()); - - let start = match info { - Ok(info) => info.timestamp, - Err(e) => { - send_error!(sender, e.into()); - return; - } - }; - // look 2 hour forward - let end = ((Utc::now() + Duration::hours(2)).timestamp()) as u32; - - let history = rt.block_on(daemon.list_history_txs(start, end, u64::MAX)); - let txs = match history { - Ok(h) => h, - Err(e) => { - send_error!(sender, e.into()); - return; - } - }; - log::info!("export_transactions() history received"); - - for 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 = str.replace("T", " "); - str[0..(str.len() - 6)].to_string() - }) - .unwrap_or("".to_string()); - - let labels = tx.labelled(); - let txid = labels - .iter() - .filter(|l| matches!(l, LabelItem::Txid(_))) - .collect::>() - .first() - .map(|l| l.to_string()); - let addr = labels - .iter() - .filter(|l| matches!(l, LabelItem::Address(_))) - .collect::>() - .first() - .map(|l| l.to_string()); - let outpoint = labels - .iter() - .filter(|l| matches!(l, LabelItem::Txid(_))) - .collect::>() - .first() - .map(|l| l.to_string()); - let mut label = txid.unwrap_or(addr.unwrap_or(outpoint.unwrap_or("".to_string()))); - if !label.is_empty() { - label = format!("\"{}\"", label); - } - let fee = tx.fee_amount.unwrap_or(Amount::ZERO); - let value = (tx.incoming_amount - tx.outgoing_amount - fee).to_string_in(Bitcoin); - let txid = tx.txid.to_string(); - let block = tx.height.map(|h| h.to_string()).unwrap_or("".to_string()); - - 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; - } - } - log::info!("export_transactions() written"); - - if let Err(e) = sender.send(ExportProgress::Ended(path)) { - tracing::error!("ExportState::start() fail to send msg: {}", e); - } -} - pub async fn get_path() -> Option { rfd::AsyncFileDialog::new() .set_title("Choose a location to export...") diff --git a/liana-gui/src/app/state/export.rs b/liana-gui/src/app/state/export.rs index f962fe40b..b4f08fde3 100644 --- a/liana-gui/src/app/state/export.rs +++ b/liana-gui/src/app/state/export.rs @@ -8,7 +8,7 @@ use liana_ui::{component::modal::Modal, widget::Element}; use tokio::task::JoinHandle; use crate::app::{ - export::{self, get_path, ExportProgress, ExportType}, + export::{self, get_path, ExportProgress}, message::Message, view::{self, export::export_modal}, }; @@ -137,11 +137,7 @@ impl ExportModal { ExportState::Started | ExportState::Progress(_) => { Some(iced::subscription::unfold( "transactions", - export::ExportState::new( - self.daemon.clone(), - ExportType::Transactions, - Box::new(path.to_path_buf()), - ), + export::ExportState::new(self.daemon.clone(), Box::new(path.to_path_buf())), export::export_subscription, )) }