import: implement import_backup()

This commit is contained in:
pythcoiner 2025-03-08 10:54:41 +01:00
parent b6f900355c
commit d0d7007e83
No known key found for this signature in database
GPG Key ID: C1048AEEDF303B88
12 changed files with 1018 additions and 90 deletions

481
Cargo.lock generated
View File

@ -68,6 +68,49 @@ dependencies = [
"subtle",
]
[[package]]
name = "age"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc171f4874fa10887e47088f81a55fcf030cd421aa31ec2b370cafebcc608a"
dependencies = [
"age-core",
"base64 0.21.7",
"bech32 0.9.1",
"chacha20poly1305",
"cookie-factory",
"hmac",
"i18n-embed",
"i18n-embed-fl",
"lazy_static",
"nom",
"pin-project",
"rand",
"rust-embed",
"scrypt",
"sha2",
"subtle",
"x25519-dalek",
"zeroize",
]
[[package]]
name = "age-core"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99"
dependencies = [
"base64 0.21.7",
"chacha20poly1305",
"cookie-factory",
"hkdf",
"io_tee",
"nom",
"rand",
"secrecy",
"sha2",
]
[[package]]
name = "ahash"
version = "0.8.11"
@ -120,7 +163,7 @@ dependencies = [
"ndk-context",
"ndk-sys 0.6.0+11769913",
"num_enum",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -168,6 +211,12 @@ dependencies = [
"derive_arbitrary",
]
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "arrayref"
version = "0.3.9"
@ -484,6 +533,15 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "basic-toml"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
dependencies = [
"serde",
]
[[package]]
name = "bdk_chain"
version = "0.16.0"
@ -508,6 +566,12 @@ dependencies = [
"electrum-client",
]
[[package]]
name = "bech32"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
[[package]]
name = "bech32"
version = "0.11.0"
@ -516,13 +580,16 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
[[package]]
name = "bip329"
version = "0.1.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdacd3f71820072c8d76eb077a8177b9a001bf03723d797783f97e9feefc0515"
checksum = "8351c1cf438ae5814ad2a696f06fd07d7be4a1a133d889b1785260fa8797798c"
dependencies = [
"age",
"bitcoin",
"hex",
"serde",
"serde_json",
"thiserror",
"thiserror 2.0.12",
]
[[package]]
@ -579,7 +646,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"thiserror",
"thiserror 1.0.69",
"tokio",
"zeroize",
]
@ -592,7 +659,7 @@ checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026"
dependencies = [
"base58ck",
"base64 0.21.7",
"bech32",
"bech32 0.11.0",
"bitcoin-internals 0.3.0",
"bitcoin-io",
"bitcoin-units",
@ -804,7 +871,7 @@ dependencies = [
"polling",
"rustix",
"slab",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -938,7 +1005,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c"
dependencies = [
"thiserror",
"thiserror 1.0.69",
"x11rb",
]
@ -1049,6 +1116,15 @@ dependencies = [
"tiny-keccak",
]
[[package]]
name = "cookie-factory"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
dependencies = [
"futures",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -1136,7 +1212,7 @@ dependencies = [
"rayon",
"rustc-hash 1.1.0",
"rustybuzz",
"self_cell",
"self_cell 1.1.0",
"swash",
"sys-locale",
"ttf-parser 0.21.1",
@ -1275,6 +1351,20 @@ dependencies = [
"winapi",
]
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core 0.9.10",
]
[[package]]
name = "data-url"
version = "0.3.1"
@ -1695,6 +1785,15 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "find-crate"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2"
dependencies = [
"toml",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
@ -1723,6 +1822,50 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
[[package]]
name = "fluent"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a"
dependencies = [
"fluent-bundle",
"unic-langid",
]
[[package]]
name = "fluent-bundle"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493"
dependencies = [
"fluent-langneg",
"fluent-syntax",
"intl-memoizer",
"intl_pluralrules",
"rustc-hash 1.1.0",
"self_cell 0.10.3",
"smallvec",
"unic-langid",
]
[[package]]
name = "fluent-langneg"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
dependencies = [
"unic-langid",
]
[[package]]
name = "fluent-syntax"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d"
dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -2049,7 +2192,7 @@ checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884"
dependencies = [
"log",
"presser",
"thiserror",
"thiserror 1.0.69",
"winapi",
"windows",
]
@ -2171,7 +2314,7 @@ dependencies = [
"com",
"libc",
"libloading 0.8.6",
"thiserror",
"thiserror 1.0.69",
"widestring",
"winapi",
]
@ -2240,6 +2383,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
@ -2330,6 +2482,73 @@ dependencies = [
"tokio-rustls",
]
[[package]]
name = "i18n-config"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e88074831c0be5b89181b05e6748c4915f77769ecc9a4c372f88b169a8509c9"
dependencies = [
"basic-toml",
"log",
"serde",
"serde_derive",
"thiserror 1.0.69",
"unic-langid",
]
[[package]]
name = "i18n-embed"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0454970a5853f498e686cbd7bf9391aac2244928194780cb7a0af0f41937db6"
dependencies = [
"arc-swap",
"fluent",
"fluent-langneg",
"fluent-syntax",
"i18n-embed-impl",
"intl-memoizer",
"log",
"parking_lot 0.12.3",
"rust-embed",
"thiserror 1.0.69",
"unic-langid",
"walkdir",
]
[[package]]
name = "i18n-embed-fl"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7578cee2940492a648bd60fb49ca85ee8c821a63790e0ef5b604cfed353b2a"
dependencies = [
"dashmap",
"find-crate",
"fluent",
"fluent-syntax",
"i18n-config",
"i18n-embed",
"proc-macro-error2",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.98",
"unic-langid",
]
[[package]]
name = "i18n-embed-impl"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2"
dependencies = [
"find-crate",
"i18n-config",
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "iana-time-zone"
version = "0.1.61"
@ -2365,7 +2584,7 @@ dependencies = [
"iced_widget",
"iced_winit",
"image",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -2383,7 +2602,7 @@ dependencies = [
"palette",
"rustc-hash 2.1.1",
"smol_str",
"thiserror",
"thiserror 1.0.69",
"web-time",
]
@ -2434,7 +2653,7 @@ dependencies = [
"once_cell",
"raw-window-handle",
"rustc-hash 2.1.1",
"thiserror",
"thiserror 1.0.69",
"unicode-segmentation",
]
@ -2448,7 +2667,7 @@ dependencies = [
"iced_tiny_skia",
"iced_wgpu",
"log",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -2461,7 +2680,7 @@ dependencies = [
"iced_core",
"iced_futures",
"raw-window-handle",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -2499,7 +2718,7 @@ dependencies = [
"once_cell",
"resvg",
"rustc-hash 2.1.1",
"thiserror",
"thiserror 1.0.69",
"wgpu",
]
@ -2516,7 +2735,7 @@ dependencies = [
"ouroboros",
"qrcode",
"rustc-hash 2.1.1",
"thiserror",
"thiserror 1.0.69",
"unicode-segmentation",
]
@ -2531,7 +2750,7 @@ dependencies = [
"iced_runtime",
"log",
"rustc-hash 2.1.1",
"thiserror",
"thiserror 1.0.69",
"tracing",
"wasm-bindgen-futures",
"web-sys",
@ -2731,6 +2950,25 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "intl-memoizer"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe22e020fce238ae18a6d5d8c502ee76a52a6e880d99477657e6acc30ec57bda"
dependencies = [
"type-map",
"unic-langid",
]
[[package]]
name = "intl_pluralrules"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
dependencies = [
"unic-langid",
]
[[package]]
name = "io-kit-sys"
version = "0.4.1"
@ -2741,6 +2979,12 @@ dependencies = [
"mach2",
]
[[package]]
name = "io_tee"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304"
[[package]]
name = "ipnet"
version = "2.11.0"
@ -2782,7 +3026,7 @@ dependencies = [
"combine",
"jni-sys",
"log",
"thiserror",
"thiserror 1.0.69",
"walkdir",
"windows-sys 0.45.0",
]
@ -2951,7 +3195,7 @@ dependencies = [
"ledger-transport",
"libc",
"log",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -3292,13 +3536,19 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniscript"
version = "12.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd3c9608217b0d6fa9c9c8ddd875b85ab72bd4311cfc8db35e1b5a08fc11f4d"
dependencies = [
"bech32",
"bech32 0.11.0",
"bitcoin",
"serde",
]
@ -3377,7 +3627,7 @@ dependencies = [
"rustc-hash 1.1.0",
"spirv",
"termcolor",
"thiserror",
"thiserror 1.0.69",
"unicode-xid",
]
@ -3393,7 +3643,7 @@ dependencies = [
"ndk-sys 0.6.0+11769913",
"num_enum",
"raw-window-handle",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -3474,6 +3724,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -3945,6 +4205,16 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -4159,6 +4429,28 @@ dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
@ -4419,7 +4711,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.15",
"libredox",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -4594,6 +4886,40 @@ dependencies = [
"smallvec",
]
[[package]]
name = "rust-embed"
version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.98",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rust-ini"
version = "0.19.0"
@ -4736,6 +5062,15 @@ version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
"cipher",
]
[[package]]
name = "same-file"
version = "1.0.6"
@ -4757,6 +5092,17 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scrypt"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
dependencies = [
"pbkdf2",
"salsa20",
"sha2",
]
[[package]]
name = "sct"
version = "0.7.1"
@ -4814,6 +5160,24 @@ dependencies = [
"cc",
]
[[package]]
name = "secrecy"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
"zeroize",
]
[[package]]
name = "self_cell"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d"
dependencies = [
"self_cell 1.1.0",
]
[[package]]
name = "self_cell"
version = "1.1.0"
@ -5033,7 +5397,7 @@ dependencies = [
"log",
"memmap2",
"rustix",
"thiserror",
"thiserror 1.0.69",
"wayland-backend",
"wayland-client",
"wayland-csd-frame",
@ -5174,6 +5538,12 @@ dependencies = [
"float-cmp",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
@ -5315,7 +5685,16 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
@ -5329,6 +5708,17 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "thread_local"
version = "1.1.8"
@ -5601,6 +5991,15 @@ version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
name = "type-map"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f"
dependencies = [
"rustc-hash 1.1.0",
]
[[package]]
name = "typenum"
version = "1.17.0"
@ -5624,7 +6023,26 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815"
dependencies = [
"thiserror",
"thiserror 1.0.69",
]
[[package]]
name = "unic-langid"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44"
dependencies = [
"unic-langid-impl",
]
[[package]]
name = "unic-langid-impl"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5"
dependencies = [
"serde",
"tinystr",
]
[[package]]
@ -6112,7 +6530,7 @@ dependencies = [
"raw-window-handle",
"rustc-hash 1.1.0",
"smallvec",
"thiserror",
"thiserror 1.0.69",
"web-sys",
"wgpu-hal",
"wgpu-types",
@ -6156,7 +6574,7 @@ dependencies = [
"renderdoc-sys",
"rustc-hash 1.1.0",
"smallvec",
"thiserror",
"thiserror 1.0.69",
"wasm-bindgen",
"web-sys",
"wgpu-types",
@ -6234,7 +6652,7 @@ dependencies = [
"clipboard_wayland",
"clipboard_x11",
"raw-window-handle",
"thiserror",
"thiserror 1.0.69",
]
[[package]]
@ -6602,6 +7020,7 @@ checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
dependencies = [
"curve25519-dalek",
"rand_core",
"serde",
"zeroize",
]

View File

@ -48,6 +48,7 @@ impl ExportModal {
ImportExportType::ExportLabels => "Export Labels",
ImportExportType::ImportPsbt => "Import PSBT",
ImportExportType::ImportDescriptor => "Import Descriptor",
ImportExportType::ImportBackup(..) => "Restore Backup",
}
}
@ -70,12 +71,14 @@ impl ExportModal {
ImportExportType::ImportPsbt => "psbt.psbt".into(),
ImportExportType::ImportDescriptor => "descriptor.descriptor".into(),
ImportExportType::ExportLabels => format!("liana-labels-{date}.jsonl"),
ImportExportType::ExportBackup(_) => format!("liana-backup-{date}.json"),
ImportExportType::ExportBackup(_) | ImportExportType::ImportBackup(_, _) => {
format!("liana-backup-{date}.json")
}
}
}
pub fn launch<M: From<ImportExportMessage> + Send + 'static>(&self) -> Task<M> {
Task::perform(get_path(self.default_filename()), move |m| {
pub fn launch<M: From<ImportExportMessage> + Send + 'static>(&self, write: bool) -> Task<M> {
Task::perform(get_path(self.default_filename(), write), move |m| {
ImportExportMessage::Path(m).into()
})
}
@ -95,11 +98,21 @@ impl ExportModal {
self.state = ImportExportState::Progress(p);
}
}
Progress::Finished | Progress::Ended => {
self.state = ImportExportState::Ended;
Progress::Finished | Progress::Ended => self.state = ImportExportState::Ended,
Progress::KeyAliasesConflict(ref sender) => {
if let ImportExportType::ImportBackup(_, aliases) = &self.import_export_type {
self.import_export_type =
ImportExportType::ImportBackup(Some(sender.clone()), aliases.clone());
}
}
Progress::LabelsConflict(ref sender) => {
if let ImportExportType::ImportBackup(labels, _) = &self.import_export_type {
self.import_export_type =
ImportExportType::ImportBackup(labels.clone(), Some(sender.clone()));
}
}
Progress::Error(e) => {
self.error = Some(e);
self.error = Some(e.clone());
}
Progress::None => {}
Progress::Psbt(_) => {
@ -130,6 +143,36 @@ impl ExportModal {
}
}
ImportExportMessage::Close | ImportExportMessage::Open => { /* unreachable */ }
ImportExportMessage::Overwrite => {
if let ImportExportType::ImportBackup(labels, aliases) =
&mut self.import_export_type
{
if let Some(sender) = labels.take() {
if sender.send(true).is_err() {
tracing::error!("ExportModal.update(): fail to send labels ACK");
}
} else if let Some(sender) = aliases.take() {
if sender.send(true).is_err() {
tracing::error!("ExportModal.update(): fail to send aliases ACK");
}
}
}
}
ImportExportMessage::Ignore => {
if let ImportExportType::ImportBackup(labels, aliases) =
&mut self.import_export_type
{
if let Some(sender) = labels.take() {
if sender.send(false).is_err() {
tracing::error!("ExportModal.update(): fail to send labels NACK");
}
} else if let Some(sender) = aliases.take() {
if sender.send(false).is_err() {
tracing::error!("ExportModal.update(): fail to send aliases NACK");
}
}
}
}
}
Task::none()
}
@ -140,7 +183,12 @@ impl ExportModal {
{
let modal = Modal::new(
content,
export_modal(&self.state, self.error.as_ref(), self.modal_title()),
export_modal(
&self.state,
self.error.as_ref(),
self.modal_title(),
&self.import_export_type,
),
);
match self.state {
ImportExportState::TimedOut

View File

@ -183,8 +183,8 @@ impl ImportExportSettingsState {
}
macro_rules! launch {
($s:ident, $m: ident) => {
let launch = $m.launch();
($s:ident, $m: ident, $write:ident) => {
let launch = $m.launch($write);
$s.modal = Some($m);
return launch
};
@ -239,19 +239,19 @@ impl State for ImportExportSettingsState {
Some(daemon),
ImportExportType::Descriptor(self.wallet.main_descriptor.clone()),
);
launch!(self, modal);
launch!(self, modal, true);
}
}
Message::View(view::Message::Settings(view::SettingsMessage::ExportTransactions)) => {
if self.modal.is_none() {
let modal = ExportModal::new(Some(daemon), ImportExportType::Transactions);
launch!(self, modal);
launch!(self, modal, true);
}
}
Message::View(view::Message::Settings(view::SettingsMessage::ExportLabels)) => {
if self.modal.is_none() {
let modal = ExportModal::new(Some(daemon), ImportExportType::ExportLabels);
launch!(self, modal);
launch!(self, modal, true);
}
}
Message::View(view::Message::Settings(view::SettingsMessage::ExportWallet)) => {
@ -281,7 +281,7 @@ impl State for ImportExportSettingsState {
backup,
)))) => {
let modal = ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup));
launch!(self, modal);
launch!(self, modal, true);
}
Message::View(view::Message::Settings(view::SettingsMessage::ImportWallet)) => {
// TODO:

View File

@ -254,7 +254,7 @@ impl State for WalletSettingsState {
if self.modal.is_none() {
let modal =
ExportModal::new(Some(daemon), ImportExportType::ExportBackup(backup));
let launch = modal.launch();
let launch = modal.launch(true);
self.modal = Modal::ImportExport(modal);
launch
} else {
@ -267,7 +267,7 @@ impl State for WalletSettingsState {
Some(daemon),
ImportExportType::Descriptor(self.wallet.main_descriptor.clone()),
);
let launch = modal.launch();
let launch = modal.launch(false);
self.modal = Modal::ImportExport(modal);
launch
} else {

View File

@ -273,7 +273,7 @@ impl State for TransactionsPanel {
ImportExportType::Transactions,
));
if let TransactionsModal::Export(m) = &self.modal {
return m.launch();
return m.launch(true);
}
}
}

View File

@ -11,15 +11,17 @@ use liana_ui::{
widget::Element,
};
use crate::export::{Error, ImportExportMessage, ImportExportState};
use crate::export::ImportExportState;
use crate::export::{Error, ImportExportMessage, ImportExportType};
/// Return the modal view for an export task
pub fn export_modal<'a, Message: From<ImportExportMessage> + Clone + 'a>(
state: &ImportExportState,
error: Option<&'a Error>,
export_type: &str,
title: &str,
import_export_type: &ImportExportType,
) -> Element<'a, Message> {
let button = match state {
let cancel_close = match state {
ImportExportState::Started | ImportExportState::Progress(_) => {
Some(button::secondary(None, "Cancel").on_press(ImportExportMessage::UserStop.into()))
}
@ -27,7 +29,9 @@ pub fn export_modal<'a, Message: From<ImportExportMessage> + Clone + 'a>(
Some(button::secondary(None, "Close").on_press(ImportExportMessage::Close.into()))
}
_ => None,
};
}
.map(Container::new);
let msg = if let Some(error) = error {
format!("{:?}", error)
} else {
@ -41,10 +45,53 @@ pub fn export_modal<'a, Message: From<ImportExportMessage> + Clone + 'a>(
ImportExportState::Progress(p) => format!("Progress: {}%", p.round()),
ImportExportState::TimedOut => "Export failed: timeout".into(),
ImportExportState::Aborted => "Export canceled".into(),
ImportExportState::Ended => "Export successful!".into(),
ImportExportState::Ended => import_export_type.end_message().into(),
ImportExportState::Closed => "".into(),
}
};
let labels_btn = (
"Labels conflict, what do you want to do?".to_string(),
Some(Container::new(
Row::new()
.push(
button::secondary(None, "Overwrite")
.on_press(ImportExportMessage::Overwrite.into()),
)
.push(Space::with_width(30))
.push(
button::secondary(None, "Ignore").on_press(ImportExportMessage::Ignore.into()),
),
)),
);
let aliases_btn = (
"Aliases conflict, what do you want to do?".to_string(),
Some(Container::new(
Row::new()
.push(
button::secondary(None, "Overwrite")
.on_press(ImportExportMessage::Overwrite.into()),
)
.push(Space::with_width(30))
.push(
button::secondary(None, "Ignore").on_press(ImportExportMessage::Ignore.into()),
),
)),
);
let (msg, button) = match (error.is_none(), import_export_type) {
(true, ImportExportType::ImportBackup(labels, aliases)) => match (labels, aliases) {
(Some(_), _) => labels_btn,
(_, Some(_)) => aliases_btn,
_ => (msg, cancel_close),
},
_ => (msg, cancel_close),
};
let button = button.map(|b| {
Container::new(b)
.align_x(Horizontal::Center)
.width(Length::Fill)
});
let p = match state {
ImportExportState::Init => 0.0,
ImportExportState::ChoosePath | ImportExportState::Path(_) | ImportExportState::Started => {
@ -63,17 +110,13 @@ pub fn export_modal<'a, Message: From<ImportExportMessage> + Clone + 'a>(
card::simple(
Column::new()
.spacing(10)
.push(Container::new(h4_bold(export_type)).width(Length::Fill))
.push(Container::new(h4_bold(title)).width(Length::Fill))
.push(Space::with_height(Length::Fill))
.push(progress_bar_row)
.push(Space::with_height(Length::Fill))
.push(Row::new().push(text(msg)))
.push(Space::with_height(Length::Fill))
.push_maybe(button.map(|b| {
Container::new(b)
.align_x(Horizontal::Right)
.width(Length::Fill)
}))
.push_maybe(button)
.push(Space::with_height(5)),
)
.width(Length::Fixed(500.0))

View File

@ -72,6 +72,12 @@ pub enum Error {
TxTimeMissing,
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl From<DaemonError> for Error {
fn from(value: DaemonError) -> Self {
Error::Daemon(value.to_string())
@ -437,7 +443,7 @@ impl Account {
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Key {
pub key: Fingerprint,
#[serde(default, skip_serializing_if = "Option::is_none")]
@ -450,7 +456,7 @@ pub struct Key {
pub metadata: Value,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum KeyRole {
/// Key to be used in normal spending condition
Main,
@ -462,7 +468,7 @@ pub enum KeyRole {
Cosigning,
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum KeyType {
/// Main user
Internal,

View File

@ -5,7 +5,7 @@ use std::{
path::PathBuf,
str::FromStr,
sync::{
mpsc::{channel, Receiver, Sender},
mpsc::{channel, sync_channel, Receiver, Sender, SyncSender},
Arc, Mutex,
},
time,
@ -16,7 +16,10 @@ use liana::{
descriptors::LianaDescriptor,
miniscript::bitcoin::{Amount, Psbt, Txid},
};
use lianad::bip329::{error::ExportError, Labels};
use lianad::{
bip329::{error::ExportError, Labels},
commands::LabelItem,
};
use tokio::{
task::{JoinError, JoinHandle},
time::sleep,
@ -25,7 +28,11 @@ use tokio::{
use iced::futures::{SinkExt, Stream};
use crate::{
app::view,
app::{
settings::{KeySetting, Settings},
view,
},
backup::Backup,
daemon::{
model::{HistoryTransaction, Labelled},
Daemon, DaemonBackend, DaemonError,
@ -61,7 +68,7 @@ macro_rules! send_progress {
};
}
macro_rules! open_file {
macro_rules! open_file_write {
($path:ident, $sender:ident) => {{
let dir = match $path.parent() {
Some(dir) => dir,
@ -86,6 +93,18 @@ macro_rules! open_file {
}};
}
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;
}
}
}};
}
#[derive(Debug, Clone)]
pub enum ImportExportMessage {
Open,
@ -94,6 +113,8 @@ pub enum ImportExportMessage {
UserStop,
Path(Option<PathBuf>),
Close,
Overwrite,
Ignore,
}
impl From<ImportExportMessage> for view::Message {
@ -129,19 +150,53 @@ pub enum Error {
ParsePsbt,
ParseDescriptor,
Bip329Export(String),
BackupImport(String),
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub enum ImportExportType {
Transactions,
ExportPsbt(String),
ExportBackup(String),
ImportBackup(
Option<SyncSender<bool>>, /*overwrite_labels*/
Option<SyncSender<bool>>, /*overwrite_aliases*/
),
Descriptor(LianaDescriptor),
ExportLabels,
ImportPsbt,
ImportDescriptor,
}
impl ImportExportType {
pub fn end_message(&self) -> &str {
match self {
ImportExportType::Transactions
| ImportExportType::ExportPsbt(_)
| ImportExportType::ExportBackup(_)
| ImportExportType::Descriptor(_)
| ImportExportType::ExportLabels => "Export successful!",
ImportExportType::ImportBackup(_, _)
| ImportExportType::ImportPsbt
| ImportExportType::ImportDescriptor => "Import successful",
}
}
}
impl PartialEq for ImportExportType {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::ExportPsbt(l0), Self::ExportPsbt(r0)) => l0 == r0,
(Self::ExportBackup(l0), Self::ExportBackup(r0)) => l0 == r0,
(Self::ImportBackup(l0, l1), Self::ImportBackup(r0, r1)) => {
l0.is_some() == r0.is_some() && l1.is_some() == r1.is_some()
}
(Self::Descriptor(l0), Self::Descriptor(r0)) => l0 == r0,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
impl From<JoinError> for Error {
fn from(value: JoinError) -> Self {
Error::JoinError(format!("{:?}", value))
@ -183,6 +238,8 @@ pub enum Progress {
None,
Psbt(Psbt),
Descriptor(LianaDescriptor),
LabelsConflict(SyncSender<bool>),
KeyAliasesConflict(SyncSender<bool>),
}
pub struct Export {
@ -225,6 +282,7 @@ impl Export {
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,
};
}
@ -335,7 +393,7 @@ pub async fn export_transactions(
return;
}
};
let mut file = open_file!(path, sender);
let mut file = open_file_write!(path, sender);
let header = "Date,Label,Value,Fee,Txid,Block\n".to_string();
if let Err(e) = file.write_all(header.as_bytes()) {
@ -488,7 +546,7 @@ pub async fn export_transactions(
}
pub fn export_descriptor(sender: Sender<Progress>, path: PathBuf, descriptor: LianaDescriptor) {
let mut file = open_file!(path, sender);
let mut file = open_file_write!(path, sender);
let descr_string = descriptor.to_string();
if let Err(e) = file.write_all(descr_string.as_bytes()) {
@ -500,7 +558,7 @@ pub fn export_descriptor(sender: Sender<Progress>, path: PathBuf, descriptor: Li
}
pub fn export_string(sender: Sender<Progress>, path: PathBuf, psbt: String) {
let mut file = open_file!(path, sender);
let mut file = open_file_write!(path, sender);
if let Err(e) = file.write_all(psbt.as_bytes()) {
send_error!(sender, e.into());
@ -511,7 +569,7 @@ pub fn export_string(sender: Sender<Progress>, path: PathBuf, psbt: String) {
}
pub fn import_psbt(sender: Sender<Progress>, path: PathBuf) {
let mut file = open_file!(path, sender);
let mut file = open_file_read!(path, sender);
let mut psbt_str = String::new();
if let Err(e) = file.read_to_string(&mut psbt_str) {
@ -532,7 +590,7 @@ pub fn import_psbt(sender: Sender<Progress>, path: PathBuf) {
}
pub fn import_descriptor(sender: Sender<Progress>, path: PathBuf) {
let mut file = open_file!(path, sender);
let mut file = open_file_read!(path, sender);
let mut descr_str = String::new();
if let Err(e) = file.read_to_string(&mut descr_str) {
@ -552,6 +610,336 @@ pub fn import_descriptor(sender: Sender<Progress>, path: PathBuf) {
send_progress!(sender, Descriptor(descriptor));
}
/// Import a backup in an already existing wallet:
/// - Load backup from file
/// - check if networks matches
/// - check if descriptors matches
/// - check if labels can be imported w/o conflict, if conflic ask user to ACK
/// - check if aliases can be imported w/o conflict, if conflict ask user to ACK
/// - update receive and change indexes
/// - parse psbt from backup
/// - import PSBTs
/// - import labels if no conflict or user ACK
/// - update aliases if no conflict or user ACK
pub async fn import_backup(
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;
}
};
// 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;
}
// Load backup from file
let mut file = open_file_read!(path, sender);
let mut backup_str = String::new();
if let Err(e) = file.read_to_string(&mut backup_str) {
send_error!(sender, e.into());
return;
}
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;
}
};
// get backend info
let info = match daemon.get_info().await {
Ok(info) => info,
Err(e) => {
send_error!(sender, Error::Daemon(format!("{e:?}")));
return;
}
};
// 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;
}
// 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;
}
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;
}
};
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;
}
};
if backup_descriptor != descriptor {
send_error!(
sender,
Error::BackupImport("The backup descriptor do not match this wallet!".into())
);
return;
}
// TODO: check if timestamp matches?
// check if labels can be imported w/o conflict
let mut write_labels = true;
let backup_labels = if let Some(labels) = account.labels.clone() {
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;
}
};
let labels_map = db_labels.clone().into_map();
let backup_labels_map = labels.clone().into_map();
// if there is a conflict, we ask user to ACK before overwrite
let (ack_sender, ack_receiver) = sync_channel(0);
let mut conflict = false;
for (k, l) in &backup_labels_map {
if let Some(lab) = labels_map.get(k) {
if lab != l {
send_progress!(sender, LabelsConflict(ack_sender));
conflict = true;
break;
}
}
}
if conflict {
write_labels = match ack_receiver.recv() {
Ok(b) => b,
Err(_) => {
send_error!(
sender,
Error::BackupImport("Fail to receive labels ACK".into())
);
return;
}
}
}
labels.into_vec()
} else {
Vec::new()
};
let datadir = match daemon.config() {
Some(c) => match &c.data_dir {
Some(dd) => dd,
None => {
send_error!(
sender,
Error::BackupImport("Fail to get Daemon config".into())
);
return;
}
},
None => {
send_error!(
sender,
Error::BackupImport("Fail to get Daemon config".into())
);
return;
}
};
// check if key aliases can be imported w/o conflict
let mut write_aliases = true;
let settings = if !account.keys.is_empty() {
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;
}
};
let settings_aliases: HashMap<_, _> = match settings.wallets.len() {
1 => settings
.wallets
.first()
.expect("already checked")
.keys
.clone()
.into_iter()
.map(|s| (s.master_fingerprint, s))
.collect(),
_ => {
send_error!(
sender,
Error::BackupImport("Settings.wallets.len() is not 1".into())
);
return;
}
};
let (ack_sender, ack_receiver) = sync_channel(0);
let mut conflict = false;
for (fg, key) in &account.keys {
if let Some(k) = settings_aliases.get(fg) {
let ks = k.to_backup();
if ks != *key {
send_progress!(sender, KeyAliasesConflict(ack_sender));
conflict = true;
break;
}
}
}
if conflict {
// wait for the user ACK/NACK
write_aliases = match ack_receiver.recv() {
Ok(a) => a,
Err(_) => {
send_error!(
sender,
Error::BackupImport("Fail to receive aliases ACK".into())
);
return;
}
};
}
Some((settings, settings_aliases))
} else {
None
};
// update receive & change index
let db_receive = info.receive_index;
let i = account.receive_index.unwrap_or(0);
let receive = if db_receive < i { Some(i) } else { None };
let db_change = info.change_index;
let i = account.change_index.unwrap_or(0);
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;
}
// parse PSBTs
let mut psbts = Vec::new();
for psbt_str in &account.psbts {
match Psbt::from_str(psbt_str) {
Ok(p) => {
psbts.push(p);
}
Err(_) => {
send_error!(sender, Error::BackupImport("Fail to parse PSBT".into()));
return;
}
}
}
// 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;
}
}
// import labels if no conflict or user ACK
if write_labels {
let labels: HashMap<LabelItem, Option<String>> = backup_labels
.into_iter()
.filter_map(|l| {
if let Some((item, label)) = LabelItem::from_bip329(&l, network) {
Some((item, Some(label)))
} else {
None
}
})
.collect();
if daemon.update_labels(&labels).await.is_err() {
send_error!(sender, Error::BackupImport("Fail to import labels".into()));
return;
}
}
// update aliases if no conflict or user ACK
if let (true, Some((mut settings, mut settings_aliases))) = (write_aliases, settings) {
for (k, v) in &account.keys {
if let Some(ks) = KeySetting::from_backup(
v.alias.clone().unwrap_or("".into()),
*k,
v.role.clone(),
v.key_type.clone(),
v.metadata.clone(),
) {
settings_aliases.insert(*k, ks);
}
}
settings.wallets.get_mut(0).expect("already checked").keys =
settings_aliases.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;
}
}
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
}
pub async fn export_labels(
sender: Sender<Progress>,
daemon: Option<Arc<dyn Daemon + Sync + Send>>,
@ -590,7 +978,7 @@ pub async fn export_labels(
return;
}
};
let mut file = open_file!(path, sender);
let mut file = open_file_write!(path, sender);
if let Err(e) = file.write_all(json.as_bytes()) {
send_error!(sender, e.into());
@ -600,11 +988,20 @@ pub async fn export_labels(
send_progress!(sender, Ended);
}
pub async fn get_path(filename: String) -> Option<PathBuf> {
rfd::AsyncFileDialog::new()
.set_title("Choose a location to export...")
.set_file_name(filename)
.save_file()
.await
.map(|fh| fh.path().to_path_buf())
pub async fn get_path(filename: String, write: bool) -> Option<PathBuf> {
if write {
rfd::AsyncFileDialog::new()
.set_title("Choose a location to export...")
.set_file_name(filename)
.save_file()
.await
.map(|fh| fh.path().to_path_buf())
} else {
rfd::AsyncFileDialog::new()
.set_title("Choose a file to import...")
.set_file_name(filename)
.pick_file()
.await
.map(|fh| fh.path().to_path_buf())
}
}

View File

@ -345,7 +345,7 @@ impl Step for BackupDescriptor {
}
};
let modal = ExportModal::new(None, ImportExportType::ExportBackup(str));
let launch = modal.launch();
let launch = modal.launch(true);
self.modal = Some(modal);
return launch;
}

View File

@ -55,4 +55,6 @@ rusqlite = { version = "0.30", features = ["bundled", "unlock_notify"] }
# To talk to bitcoind
jsonrpc = { version = "0.17", features = ["minreq_http"], default-features = false }
bip329 = {version = "0.1.1", default-features = false }
# import/export labels
bip329 = "0.3.0"

View File

@ -576,7 +576,7 @@ impl LabelItem {
match label {
bip329::Label::Transaction(tx_record) => {
if let (Some(txid), Some(label)) = (
Txid::from_str(&tx_record.ref_).ok(),
Txid::from_str(&tx_record.ref_.to_string()).ok(),
tx_record.label.clone(),
) {
Some((Self::Txid(txid), label))
@ -586,7 +586,8 @@ impl LabelItem {
}
bip329::Label::Address(address_record) => {
if let (Some(addr), Some(label)) = (
Address::from_str(&address_record.ref_).ok(),
Address::from_str(&address_record.ref_.clone().assume_checked().to_string())
.ok(),
address_record.label.clone(),
) {
if addr.is_valid_for_network(network) {
@ -600,7 +601,7 @@ impl LabelItem {
}
bip329::Label::Output(output_record) => {
if let (Some(outpoint), Some(label)) = (
OutPoint::from_str(&output_record.ref_).ok(),
OutPoint::from_str(&output_record.ref_.to_string()).ok(),
output_record.label.clone(),
) {
Some((Self::OutPoint(outpoint), label))

View File

@ -3,7 +3,14 @@ use liana::descriptors::LianaDescriptor;
use std::{convert::TryFrom, str::FromStr};
use miniscript::bitcoin::{self, address, bip32, consensus::encode, psbt::Psbt};
use miniscript::bitcoin::{
self,
address::{self, NetworkUnchecked},
bip32,
consensus::encode,
psbt::Psbt,
Address, OutPoint,
};
// Due to limitations of Sqlite's ALTER TABLE command and in order not to recreate
// tables during migration:
@ -376,14 +383,19 @@ impl From<DbLabel> for Label {
Some(value.value)
};
match value.item_kind {
DbLabelledKind::Address => Label::Address(bip329::AddressRecord { ref_, label }),
DbLabelledKind::OutPoint => Label::Output(bip329::OutputRecord {
ref_,
DbLabelledKind::Address => Label::Address(bip329::AddressRecord {
ref_: Address::<NetworkUnchecked>::from_str(&ref_)
.expect("db contains valid adresses"),
label,
spendable: None,
}),
DbLabelledKind::OutPoint => Label::Output(bip329::OutputRecord {
ref_: OutPoint::from_str(&ref_).expect(" db contais valid outpoints"),
label,
spendable: true,
}),
DbLabelledKind::Txid => Label::Transaction(bip329::TransactionRecord {
ref_,
ref_: bitcoin::consensus::encode::deserialize_hex(&ref_)
.expect("db contains valid txid"),
label,
// FIXME: "Optional key origin information referencing the wallet associated with the label"
origin: None,