import: implement import from file for psbt & descriptor

This commit is contained in:
pythcoiner 2025-01-30 07:22:18 +01:00
parent 2b9324993a
commit 158651ebe7
No known key found for this signature in database
GPG Key ID: C1048AEEDF303B88
7 changed files with 194 additions and 115 deletions

View File

@ -11,7 +11,7 @@ use lianad::config::Config as DaemonConfig;
use crate::{ use crate::{
app::{cache::Cache, error::Error, view, wallet::Wallet}, app::{cache::Cache, error::Error, view, wallet::Wallet},
daemon::model::*, daemon::model::*,
export::ExportMessage, export::ImportExportMessage,
hw::HardwareWalletMessage, hw::HardwareWalletMessage,
}; };
@ -47,5 +47,5 @@ pub enum Message {
LabelsUpdated(Result<HashMap<String, Option<String>>, Error>), LabelsUpdated(Result<HashMap<String, Option<String>>, Error>),
BroadcastModal(Result<HashSet<Txid>, Error>), BroadcastModal(Result<HashSet<Txid>, Error>),
RbfModal(Box<HistoryTransaction>, bool, Result<HashSet<Txid>, Error>), RbfModal(Box<HistoryTransaction>, bool, Result<HashSet<Txid>, Error>),
Export(ExportMessage), Export(ImportExportMessage),
} }

View File

@ -13,40 +13,40 @@ use crate::{
view::{self, export::export_modal}, view::{self, export::export_modal},
}, },
daemon::Daemon, daemon::Daemon,
export::{self, get_path, ExportMessage, ExportProgress, ExportState, ExportType}, export::{self, get_path, ImportExportMessage, ImportExportState, ImportExportType, Progress},
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct ExportModal { pub struct ExportModal {
path: Option<PathBuf>, path: Option<PathBuf>,
handle: Option<Arc<Mutex<JoinHandle<()>>>>, handle: Option<Arc<Mutex<JoinHandle<()>>>>,
state: ExportState, state: ImportExportState,
error: Option<export::Error>, error: Option<export::Error>,
daemon: Arc<dyn Daemon + Sync + Send>, daemon: Arc<dyn Daemon + Sync + Send>,
export_type: ExportType, import_export_type: ImportExportType,
} }
impl ExportModal { impl ExportModal {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new(daemon: Arc<dyn Daemon + Sync + Send>, export_type: ExportType) -> Self { pub fn new(daemon: Arc<dyn Daemon + Sync + Send>, export_type: ImportExportType) -> Self {
Self { Self {
path: None, path: None,
handle: None, handle: None,
state: ExportState::Init, state: ImportExportState::Init,
error: None, error: None,
daemon, daemon,
export_type, import_export_type: export_type,
} }
} }
pub fn default_filename(&self) -> String { pub fn default_filename(&self) -> String {
match &self.export_type { match &self.import_export_type {
ExportType::Transactions => { ImportExportType::Transactions => {
let date = chrono::Local::now().format("%Y-%m-%dT%H-%M-%S"); let date = chrono::Local::now().format("%Y-%m-%dT%H-%M-%S");
format!("liana-txs-{date}.csv") format!("liana-txs-{date}.csv")
} }
ExportType::Psbt(_) => "psbt.psbt".into(), ImportExportType::ExportPsbt(_) => "psbt.psbt".into(),
ExportType::Descriptor(descriptor) => { ImportExportType::Descriptor(descriptor) => {
let checksum = descriptor let checksum = descriptor
.to_string() .to_string()
.split_once('#') .split_once('#')
@ -55,48 +55,64 @@ impl ExportModal {
.to_string(); .to_string();
format!("liana-{}.descriptor", checksum) format!("liana-{}.descriptor", checksum)
} }
ImportExportType::ImportPsbt => "psbt.psbt".into(),
ImportExportType::ImportDescriptor => "descriptor.descriptor".into(),
} }
} }
pub fn launch(&self) -> Task<app::message::Message> { pub fn launch(&self) -> Task<app::message::Message> {
Task::perform(get_path(self.default_filename()), |m| { Task::perform(get_path(self.default_filename()), |m| {
app::message::Message::View(view::Message::Export(ExportMessage::Path(m))) app::message::Message::View(view::Message::ImportExport(ImportExportMessage::Path(m)))
}) })
} }
pub fn update(&mut self, message: ExportMessage) -> Task<app::message::Message> { pub fn update(&mut self, message: ImportExportMessage) -> Task<app::message::Message> {
match message { match message {
ExportMessage::ExportProgress(m) => match m { ImportExportMessage::Progress(m) => match m {
ExportProgress::Started(handle) => { Progress::Started(handle) => {
self.handle = Some(handle); self.handle = Some(handle);
self.state = ExportState::Progress(0.0); self.state = ImportExportState::Progress(0.0);
} }
ExportProgress::Progress(p) => { Progress::Progress(p) => {
if let ExportState::Progress(_) = self.state { if let ImportExportState::Progress(_) = self.state {
self.state = ExportState::Progress(p); self.state = ImportExportState::Progress(p);
} }
} }
ExportProgress::Finished | ExportProgress::Ended => self.state = ExportState::Ended, Progress::Finished | Progress::Ended => self.state = ImportExportState::Ended,
ExportProgress::Error(e) => self.error = Some(e), Progress::Error(e) => self.error = Some(e),
ExportProgress::None => {} Progress::None => {}
Progress::Psbt(_) => {
if self.import_export_type == ImportExportType::ImportPsbt {
self.state = ImportExportState::Ended;
}
// TODO: forward PSBT
}
Progress::Descriptor(_) => {
if self.import_export_type == ImportExportType::ImportDescriptor {
self.state = ImportExportState::Ended;
}
// TODO: forward Descriptor
}
}, },
ExportMessage::TimedOut => { ImportExportMessage::TimedOut => {
self.stop(ExportState::TimedOut); self.stop(ImportExportState::TimedOut);
} }
ExportMessage::UserStop => { ImportExportMessage::UserStop => {
self.stop(ExportState::Aborted); self.stop(ImportExportState::Aborted);
} }
ExportMessage::Path(p) => { ImportExportMessage::Path(p) => {
if let Some(path) = p { if let Some(path) = p {
self.path = Some(path); self.path = Some(path);
self.start(); self.start();
} else { } else {
return Task::perform(async {}, |_| { return Task::perform(async {}, |_| {
app::message::Message::View(view::Message::Export(ExportMessage::Close)) app::message::Message::View(view::Message::ImportExport(
ImportExportMessage::Close,
))
}); });
} }
} }
ExportMessage::Close | ExportMessage::Open => { /* unreachable */ } ImportExportMessage::Close | ImportExportMessage::Open => { /* unreachable */ }
} }
Task::none() Task::none()
} }
@ -106,36 +122,36 @@ impl ExportModal {
export_modal(&self.state, self.error.as_ref(), "Transactions"), export_modal(&self.state, self.error.as_ref(), "Transactions"),
); );
match self.state { match self.state {
ExportState::TimedOut ImportExportState::TimedOut
| ExportState::Aborted | ImportExportState::Aborted
| ExportState::Ended | ImportExportState::Ended
| ExportState::Closed => modal.on_blur(Some(view::Message::Close)), | ImportExportState::Closed => modal.on_blur(Some(view::Message::Close)),
_ => modal, _ => modal,
} }
.into() .into()
} }
pub fn start(&mut self) { pub fn start(&mut self) {
self.state = ExportState::Started; self.state = ImportExportState::Started;
} }
pub fn stop(&mut self, state: ExportState) { pub fn stop(&mut self, state: ImportExportState) {
if let Some(handle) = self.handle.take() { if let Some(handle) = self.handle.take() {
handle.lock().expect("poisoned").abort(); handle.lock().expect("poisoned").abort();
self.state = state; self.state = state;
} }
} }
pub fn subscription(&self) -> Option<Subscription<export::ExportProgress>> { pub fn subscription(&self) -> Option<Subscription<export::Progress>> {
if let Some(path) = &self.path { if let Some(path) = &self.path {
match &self.state { match &self.state {
ExportState::Started | ExportState::Progress(_) => { ImportExportState::Started | ImportExportState::Progress(_) => {
Some(iced::Subscription::run_with_id( Some(iced::Subscription::run_with_id(
"transactions", "transactions",
export::export_subscription( export::export_subscription(
self.daemon.clone(), self.daemon.clone(),
path.to_path_buf(), path.to_path_buf(),
ExportType::Transactions, self.import_export_type.clone(),
), ),
)) ))
} }

View File

@ -28,7 +28,7 @@ use crate::{
wallet::Wallet, wallet::Wallet,
}, },
daemon::model::{self, LabelsLoader}, daemon::model::{self, LabelsLoader},
export::{ExportMessage, ExportType}, export::{ImportExportMessage, ImportExportType},
}; };
use crate::daemon::{ use crate::daemon::{
@ -266,18 +266,18 @@ impl State for TransactionsPanel {
); );
} }
} }
Message::View(view::Message::Export(ExportMessage::Open)) => { Message::View(view::Message::ImportExport(ImportExportMessage::Open)) => {
if let TransactionsModal::None = &self.modal { if let TransactionsModal::None = &self.modal {
self.modal = TransactionsModal::Export(ExportModal::new( self.modal = TransactionsModal::Export(ExportModal::new(
daemon, daemon,
ExportType::Transactions, ImportExportType::Transactions,
)); ));
if let TransactionsModal::Export(m) = &self.modal { if let TransactionsModal::Export(m) = &self.modal {
return m.launch(); return m.launch();
} }
} }
} }
Message::View(view::Message::Export(ExportMessage::Close)) => { Message::View(view::Message::ImportExport(ImportExportMessage::Close)) => {
if let TransactionsModal::Export(_) = &self.modal { if let TransactionsModal::Export(_) = &self.modal {
self.modal = TransactionsModal::None; self.modal = TransactionsModal::None;
} }
@ -286,7 +286,7 @@ impl State for TransactionsPanel {
return match &mut self.modal { return match &mut self.modal {
TransactionsModal::CreateRbf(modal) => modal.update(daemon, _cache, message), TransactionsModal::CreateRbf(modal) => modal.update(daemon, _cache, message),
TransactionsModal::Export(modal) => { TransactionsModal::Export(modal) => {
if let Message::View(view::Message::Export(m)) = msg { if let Message::View(view::Message::ImportExport(m)) = msg {
modal.update(m.clone()) modal.update(m.clone())
} else { } else {
Task::none() Task::none()
@ -330,7 +330,9 @@ impl State for TransactionsPanel {
if let TransactionsModal::Export(modal) = &self.modal { if let TransactionsModal::Export(modal) = &self.modal {
if let Some(sub) = modal.subscription() { if let Some(sub) = modal.subscription() {
return sub.map(|m| { return sub.map(|m| {
Message::View(view::Message::Export(ExportMessage::ExportProgress(m))) Message::View(view::Message::ImportExport(ImportExportMessage::Progress(
m,
)))
}); });
} }
} }

View File

@ -11,21 +11,21 @@ use liana_ui::{
widget::Element, widget::Element,
}; };
use crate::export::{Error, ExportMessage}; use crate::export::{Error, ImportExportMessage};
use crate::{app::view::message::Message, export::ExportState}; use crate::{app::view::message::Message, export::ImportExportState};
/// Return the modal view for an export task /// Return the modal view for an export task
pub fn export_modal<'a>( pub fn export_modal<'a>(
state: &ExportState, state: &ImportExportState,
error: Option<&'a Error>, error: Option<&'a Error>,
export_type: &str, export_type: &str,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let button = match state { let button = match state {
ExportState::Started | ExportState::Progress(_) => { ImportExportState::Started | ImportExportState::Progress(_) => {
Some(button::secondary(None, "Cancel").on_press(ExportMessage::UserStop.into())) Some(button::secondary(None, "Cancel").on_press(ImportExportMessage::UserStop.into()))
} }
ExportState::Ended | ExportState::TimedOut | ExportState::Aborted => { ImportExportState::Ended | ImportExportState::TimedOut | ImportExportState::Aborted => {
Some(button::secondary(None, "Close").on_press(ExportMessage::Close.into())) Some(button::secondary(None, "Close").on_press(ImportExportMessage::Close.into()))
} }
_ => None, _ => None,
}; };
@ -33,26 +33,29 @@ pub fn export_modal<'a>(
format!("{:?}", error) format!("{:?}", error)
} else { } else {
match state { match state {
ExportState::Init => "".to_string(), ImportExportState::Init => "".to_string(),
ExportState::ChoosePath => { ImportExportState::ChoosePath => {
"Select the path you want to export in the popup window...".into() "Select the path you want to export in the popup window...".into()
} }
ExportState::Path(_) => "".into(), ImportExportState::Path(_) => "".into(),
ExportState::Started => "Starting export...".into(), ImportExportState::Started => "Starting export...".into(),
ExportState::Progress(p) => format!("Progress: {}%", p.round()), ImportExportState::Progress(p) => format!("Progress: {}%", p.round()),
ExportState::TimedOut => "Export failed: timeout".into(), ImportExportState::TimedOut => "Export failed: timeout".into(),
ExportState::Aborted => "Export canceled".into(), ImportExportState::Aborted => "Export canceled".into(),
ExportState::Ended => "Export successful!".into(), ImportExportState::Ended => "Export successful!".into(),
ExportState::Closed => "".into(), ImportExportState::Closed => "".into(),
} }
}; };
let p = match state { let p = match state {
ExportState::Init => 0.0, ImportExportState::Init => 0.0,
ExportState::ChoosePath | ExportState::Path(_) | ExportState::Started => 5.0, ImportExportState::ChoosePath | ImportExportState::Path(_) | ImportExportState::Started => {
ExportState::Progress(p) => *p, 5.0
ExportState::TimedOut | ExportState::Aborted | ExportState::Ended | ExportState::Closed => {
100.0
} }
ImportExportState::Progress(p) => *p,
ImportExportState::TimedOut
| ImportExportState::Aborted
| ImportExportState::Ended
| ImportExportState::Closed => 100.0,
}; };
let progress_bar_row = Row::new() let progress_bar_row = Row::new()
.push(Space::with_width(30)) .push(Space::with_width(30))

View File

@ -1,4 +1,4 @@
use crate::{app::menu::Menu, export::ExportMessage, node::bitcoind::RpcAuthType}; use crate::{app::menu::Menu, export::ImportExportMessage, node::bitcoind::RpcAuthType};
use liana::miniscript::bitcoin::{bip32::Fingerprint, OutPoint}; use liana::miniscript::bitcoin::{bip32::Fingerprint, OutPoint};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -19,7 +19,7 @@ pub enum Message {
SelectHardwareWallet(usize), SelectHardwareWallet(usize),
CreateRbf(CreateRbfMessage), CreateRbf(CreateRbfMessage),
ShowQrCode(usize), ShowQrCode(usize),
Export(ExportMessage), ImportExport(ImportExportMessage),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -25,7 +25,7 @@ use crate::{
}, },
}, },
daemon::model::{HistoryTransaction, Txid}, daemon::model::{HistoryTransaction, Txid},
export::ExportMessage, export::ImportExportMessage,
}; };
pub fn transactions_view<'a>( pub fn transactions_view<'a>(
@ -44,7 +44,10 @@ pub fn transactions_view<'a>(
Row::new() Row::new()
.push(Container::new(h3("Transactions"))) .push(Container::new(h3("Transactions")))
.push(Space::with_width(Length::Fill)) .push(Space::with_width(Length::Fill))
.push(button::secondary(None, "Export").on_press(ExportMessage::Open.into())), .push(
button::secondary(None, "Export")
.on_press(ImportExportMessage::Open.into()),
),
) )
.push( .push(
Column::new() Column::new()

View File

@ -1,8 +1,9 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::{self, File}, fs::{self, File},
io::Write, io::{Read, Write},
path::PathBuf, path::PathBuf,
str::FromStr,
sync::{ sync::{
mpsc::{channel, Receiver, Sender}, mpsc::{channel, Receiver, Sender},
Arc, Mutex, Arc, Mutex,
@ -13,7 +14,7 @@ use std::{
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use liana::{ use liana::{
descriptors::LianaDescriptor, descriptors::LianaDescriptor,
miniscript::bitcoin::{Amount, Txid}, miniscript::bitcoin::{Amount, Psbt, Txid},
}; };
use tokio::{ use tokio::{
task::{JoinError, JoinHandle}, task::{JoinError, JoinHandle},
@ -33,26 +34,26 @@ use crate::{
macro_rules! send_error { macro_rules! send_error {
($sender:ident, $error:ident) => { ($sender:ident, $error:ident) => {
if let Err(e) = $sender.send(ExportProgress::Error(Error::$error)) { if let Err(e) = $sender.send(Progress::Error(Error::$error)) {
tracing::error!("ExportState::start() fail to send msg: {}", e); tracing::error!("Import/Export fail to send msg: {}", e);
} }
}; };
($sender:ident, $error:expr) => { ($sender:ident, $error:expr) => {
if let Err(e) = $sender.send(ExportProgress::Error($error)) { if let Err(e) = $sender.send(Progress::Error($error)) {
tracing::error!("ExportState::start() fail to send msg: {}", e); tracing::error!("Import/Export fail to send msg: {}", e);
} }
}; };
} }
macro_rules! send_progress { macro_rules! send_progress {
($sender:ident, $progress:ident) => { ($sender:ident, $progress:ident) => {
if let Err(e) = $sender.send(ExportProgress::$progress) { if let Err(e) = $sender.send(Progress::$progress) {
tracing::error!("ExportState::start() fail to send msg: {}", e); tracing::error!("ImportExport fail to send msg: {}", e);
} }
}; };
($sender:ident, $progress:ident($val:expr)) => { ($sender:ident, $progress:ident($val:expr)) => {
if let Err(e) = $sender.send(ExportProgress::$progress($val)) { if let Err(e) = $sender.send(Progress::$progress($val)) {
tracing::error!("ExportState::start() fail to send msg: {}", e); tracing::error!("ImportExport fail to send msg: {}", e);
} }
}; };
} }
@ -83,23 +84,23 @@ macro_rules! open_file {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ExportMessage { pub enum ImportExportMessage {
Open, Open,
ExportProgress(ExportProgress), Progress(Progress),
TimedOut, TimedOut,
UserStop, UserStop,
Path(Option<PathBuf>), Path(Option<PathBuf>),
Close, Close,
} }
impl From<ExportMessage> for view::Message { impl From<ImportExportMessage> for view::Message {
fn from(value: ExportMessage) -> Self { fn from(value: ImportExportMessage) -> Self {
Self::Export(value) Self::ImportExport(value)
} }
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ExportState { pub enum ImportExportState {
Init, Init,
ChoosePath, ChoosePath,
Path(PathBuf), Path(PathBuf),
@ -121,13 +122,18 @@ pub enum Error {
NoParentDir, NoParentDir,
Daemon(String), Daemon(String),
TxTimeMissing, TxTimeMissing,
DaemonMissing,
ParsePsbt,
ParseDescriptor,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum ExportType { pub enum ImportExportType {
Transactions, Transactions,
Psbt(String), ExportPsbt(String),
Descriptor(LianaDescriptor), Descriptor(LianaDescriptor),
ImportPsbt,
ImportDescriptor,
} }
impl From<JoinError> for Error { impl From<JoinError> for Error {
@ -156,29 +162,31 @@ pub enum Status {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ExportProgress { pub enum Progress {
Started(Arc<Mutex<JoinHandle<()>>>), Started(Arc<Mutex<JoinHandle<()>>>),
Progress(f32), Progress(f32),
Ended, Ended,
Finished, Finished,
Error(Error), Error(Error),
None, None,
Psbt(Psbt),
Descriptor(LianaDescriptor),
} }
pub struct Export { pub struct Export {
pub receiver: Receiver<ExportProgress>, pub receiver: Receiver<Progress>,
pub sender: Option<Sender<ExportProgress>>, pub sender: Option<Sender<Progress>>,
pub handle: Option<Arc<Mutex<JoinHandle<()>>>>, pub handle: Option<Arc<Mutex<JoinHandle<()>>>>,
pub daemon: Arc<dyn Daemon + Sync + Send>, pub daemon: Option<Arc<dyn Daemon + Sync + Send>>,
pub path: Box<PathBuf>, pub path: Box<PathBuf>,
pub export_type: ExportType, pub export_type: ImportExportType,
} }
impl Export { impl Export {
pub fn new( pub fn new(
daemon: Arc<dyn Daemon + Sync + Send>, daemon: Option<Arc<dyn Daemon + Sync + Send>>,
path: Box<PathBuf>, path: Box<PathBuf>,
export_type: ExportType, export_type: ImportExportType,
) -> Self { ) -> Self {
let (sender, receiver) = channel(); let (sender, receiver) = channel();
Export { Export {
@ -192,15 +200,17 @@ impl Export {
} }
pub async fn export_logic( pub async fn export_logic(
export_type: ExportType, export_type: ImportExportType,
sender: Sender<ExportProgress>, sender: Sender<Progress>,
daemon: Arc<dyn Daemon + Sync + Send>, daemon: Option<Arc<dyn Daemon + Sync + Send>>,
path: PathBuf, path: PathBuf,
) { ) {
match export_type { match export_type {
ExportType::Transactions => export_transactions(sender, daemon, path).await, ImportExportType::Transactions => export_transactions(sender, daemon, path).await,
ExportType::Psbt(psbt) => export_psbt(sender, path, psbt), ImportExportType::ExportPsbt(psbt) => export_psbt(sender, path, psbt),
ExportType::Descriptor(descriptor) => export_descriptor(sender, path, descriptor), ImportExportType::Descriptor(descriptor) => export_descriptor(sender, path, descriptor),
ImportExportType::ImportPsbt => import_psbt(sender, path),
ImportExportType::ImportDescriptor => import_descriptor(sender, path),
}; };
} }
@ -238,10 +248,10 @@ impl Export {
pub fn export_subscription( pub fn export_subscription(
daemon: Arc<dyn Daemon + Sync + Send>, daemon: Arc<dyn Daemon + Sync + Send>,
path: PathBuf, path: PathBuf,
export_type: ExportType, export_type: ImportExportType,
) -> impl Stream<Item = ExportProgress> { ) -> impl Stream<Item = Progress> {
iced::stream::channel(100, move |mut output| async move { iced::stream::channel(100, move |mut output| async move {
let mut state = Export::new(daemon, Box::new(path), export_type); let mut state = Export::new(Some(daemon), Box::new(path), export_type);
loop { loop {
match state.state() { match state.state() {
Status::Init => { Status::Init => {
@ -269,7 +279,7 @@ pub fn export_subscription(
let handle = match state.handle.take() { let handle = match state.handle.take() {
Some(h) => h, Some(h) => h,
None => { None => {
if let Err(e) = output.send(ExportProgress::Error(Error::HandleLost)).await { if let Err(e) = output.send(Progress::Error(Error::HandleLost)).await {
tracing::error!("export_subscription() fail to send message: {}", e); tracing::error!("export_subscription() fail to send message: {}", e);
} }
continue; continue;
@ -278,9 +288,9 @@ pub fn export_subscription(
let msg = { let msg = {
let h = handle.lock().expect("should not fail"); let h = handle.lock().expect("should not fail");
if h.is_finished() { if h.is_finished() {
Some(ExportProgress::Finished) Some(Progress::Finished)
} else if disconnected { } else if disconnected {
Some(ExportProgress::Error(Error::ChannelLost)) Some(Progress::Error(Error::ChannelLost))
} else { } else {
None None
} }
@ -299,11 +309,18 @@ pub fn export_subscription(
} }
pub async fn export_transactions( pub async fn export_transactions(
sender: Sender<ExportProgress>, sender: Sender<Progress>,
daemon: Arc<dyn Daemon + Sync + Send>, daemon: Option<Arc<dyn Daemon + Sync + Send>>,
path: PathBuf, path: PathBuf,
) { ) {
async move { async move {
let daemon = match daemon {
Some(d) => d,
None => {
send_error!(sender, Error::DaemonMissing);
return;
}
};
let mut file = open_file!(path, sender); let mut file = open_file!(path, sender);
let header = "Date,Label,Value,Fee,Txid,Block\n".to_string(); let header = "Date,Label,Value,Fee,Txid,Block\n".to_string();
@ -456,11 +473,7 @@ pub async fn export_transactions(
.await; .await;
} }
pub fn export_descriptor( pub fn export_descriptor(sender: Sender<Progress>, path: PathBuf, descriptor: LianaDescriptor) {
sender: Sender<ExportProgress>,
path: PathBuf,
descriptor: LianaDescriptor,
) {
let mut file = open_file!(path, sender); let mut file = open_file!(path, sender);
let descr_string = descriptor.to_string(); let descr_string = descriptor.to_string();
@ -472,7 +485,7 @@ pub fn export_descriptor(
send_progress!(sender, Ended); send_progress!(sender, Ended);
} }
pub fn export_psbt(sender: Sender<ExportProgress>, path: PathBuf, psbt: String) { pub fn export_psbt(sender: Sender<Progress>, path: PathBuf, psbt: String) {
let mut file = open_file!(path, sender); let mut file = open_file!(path, sender);
if let Err(e) = file.write_all(psbt.as_bytes()) { if let Err(e) = file.write_all(psbt.as_bytes()) {
@ -483,6 +496,48 @@ pub fn export_psbt(sender: Sender<ExportProgress>, path: PathBuf, psbt: String)
send_progress!(sender, Ended); send_progress!(sender, Ended);
} }
pub fn import_psbt(sender: Sender<Progress>, path: PathBuf) {
let mut file = open_file!(path, sender);
let mut psbt_str = String::new();
if let Err(e) = file.read_to_string(&mut psbt_str) {
send_error!(sender, e.into());
return;
}
let psbt = match Psbt::from_str(&psbt_str) {
Ok(psbt) => psbt,
Err(_) => {
send_error!(sender, Error::ParsePsbt);
return;
}
};
send_progress!(sender, Progress(100.0));
send_progress!(sender, Psbt(psbt));
}
pub fn import_descriptor(sender: Sender<Progress>, path: PathBuf) {
let mut file = open_file!(path, sender);
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;
}
};
send_progress!(sender, Progress(100.0));
send_progress!(sender, Descriptor(descriptor));
}
pub async fn get_path(filename: String) -> Option<PathBuf> { pub async fn get_path(filename: String) -> Option<PathBuf> {
rfd::AsyncFileDialog::new() rfd::AsyncFileDialog::new()
.set_title("Choose a location to export...") .set_title("Choose a location to export...")