Skip to content

Commit

Permalink
gui(export): do not use a separate function for export logic
Browse files Browse the repository at this point in the history
  • Loading branch information
pythcoiner committed Dec 10, 2024
1 parent 9c48862 commit d2b3874
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 141 deletions.
237 changes: 102 additions & 135 deletions liana-gui/src/app/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use chrono::{DateTime, Duration, Utc};
use liana::miniscript::bitcoin::{Amount, Denomination::Bitcoin};
use lianad::commands::LabelItem;
use tokio::{
runtime::Runtime,
task::{JoinError, JoinHandle},
time::sleep,
};
Expand Down Expand Up @@ -79,33 +78,22 @@ pub enum ExportProgress {
None,
}

#[derive(Debug)]
pub enum ExportType {
Transactions,
}

pub struct ExportState {
pub receiver: Receiver<ExportProgress>,
pub sender: Option<Sender<ExportProgress>>,
pub handle: Option<Arc<Mutex<JoinHandle<()>>>>,
pub daemon: Arc<dyn Daemon + Sync + Send>,
pub export_type: ExportType,
pub path: Box<PathBuf>,
}

impl ExportState {
pub fn new(
daemon: Arc<dyn Daemon + Sync + Send>,
export_type: ExportType,
path: Box<PathBuf>,
) -> Self {
pub fn new(daemon: Arc<dyn Daemon + Sync + Send>, path: Box<PathBuf>) -> Self {
let (sender, receiver) = channel();
ExportState {
receiver,
sender: Some(sender),
handle: None,
daemon,
export_type,
path,
}
}
Expand All @@ -114,11 +102,110 @@ 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 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::<Vec<_>>()
.first()
.map(|l| l.to_string());
let addr = labels
.iter()
.filter(|l| matches!(l, LabelItem::Address(_)))
.collect::<Vec<_>>()
.first()
.map(|l| l.to_string());
let outpoint = labels
.iter()
.filter(|l| matches!(l, LabelItem::OutPoint(_)))
.collect::<Vec<_>>()
.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).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));

Expand All @@ -140,14 +227,6 @@ impl ExportState {
_ => unreachable!(),
}
}

pub fn function(
&self,
) -> impl Fn(Sender<ExportProgress>, Arc<dyn Daemon + Sync + Send>, Box<PathBuf>) {
match self.export_type {
ExportType::Transactions => export_transactions,
}
}
}

pub async fn export_subscription(mut state: ExportState) -> (ExportProgress, ExportState) {
Expand Down Expand Up @@ -188,118 +267,6 @@ pub async fn export_subscription(mut state: ExportState) -> (ExportProgress, Exp
(ExportProgress::None, state)
}

pub fn export_transactions(
sender: Sender<ExportProgress>,
daemon: Arc<dyn Daemon + Sync + Send>,
path: Box<PathBuf>,
) {
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::<Vec<_>>()
.first()
.map(|l| l.to_string());
let addr = labels
.iter()
.filter(|l| matches!(l, LabelItem::Address(_)))
.collect::<Vec<_>>()
.first()
.map(|l| l.to_string());
let outpoint = labels
.iter()
.filter(|l| matches!(l, LabelItem::Txid(_)))
.collect::<Vec<_>>()
.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<PathBuf> {
rfd::AsyncFileDialog::new()
.set_title("Choose a location to export...")
Expand Down
8 changes: 2 additions & 6 deletions liana-gui/src/app/state/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -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,
))
}
Expand Down

0 comments on commit d2b3874

Please sign in to comment.