import: implement import from file for psbt & descriptor
This commit is contained in:
parent
2b9324993a
commit
158651ebe7
@ -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),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
)))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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...")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user