remove native dialog dep
This commit is contained in:
parent
5907909470
commit
5f2e2f3eb2
6 changed files with 188 additions and 448 deletions
280
Cargo.lock
generated
280
Cargo.lock
generated
|
@ -97,12 +97,6 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ascii"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.88"
|
version = "0.1.88"
|
||||||
|
@ -237,15 +231,6 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block2"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
|
||||||
dependencies = [
|
|
||||||
"objc2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.19.0"
|
version = "3.19.0"
|
||||||
|
@ -449,15 +434,6 @@ dependencies = [
|
||||||
"dirs-sys",
|
"dirs-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs"
|
|
||||||
version = "6.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
|
||||||
dependencies = [
|
|
||||||
"dirs-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs-sys"
|
name = "dirs-sys"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -470,18 +446,6 @@ dependencies = [
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dispatch2"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"block2",
|
|
||||||
"libc",
|
|
||||||
"objc2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -518,12 +482,6 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "env_home"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.11.8"
|
version = "0.11.8"
|
||||||
|
@ -611,12 +569,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "formatx"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d8866fac38f53fc87fa3ae1b09ddd723e0482f8fa74323518b4c59df2c55a00a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
@ -1135,15 +1087,6 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.14.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
@ -1190,7 +1133,6 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"native-dialog",
|
|
||||||
"network-interface",
|
"network-interface",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -1349,30 +1291,6 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "native-dialog"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f006431cea71a83e6668378cb5abc2d52af299cbac6dca1780c6eeca90822df"
|
|
||||||
dependencies = [
|
|
||||||
"ascii",
|
|
||||||
"block2",
|
|
||||||
"dirs",
|
|
||||||
"dispatch2",
|
|
||||||
"formatx",
|
|
||||||
"objc2",
|
|
||||||
"objc2-app-kit",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-core-graphics",
|
|
||||||
"objc2-foundation",
|
|
||||||
"raw-window-handle",
|
|
||||||
"thiserror 2.0.12",
|
|
||||||
"versions",
|
|
||||||
"wfd",
|
|
||||||
"which",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -1428,15 +1346,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "8.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
@ -1446,147 +1355,6 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551"
|
|
||||||
dependencies = [
|
|
||||||
"objc2-encode",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-app-kit"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"block2",
|
|
||||||
"libc",
|
|
||||||
"objc2",
|
|
||||||
"objc2-cloud-kit",
|
|
||||||
"objc2-core-data",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-core-graphics",
|
|
||||||
"objc2-core-image",
|
|
||||||
"objc2-foundation",
|
|
||||||
"objc2-quartz-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-cloud-kit"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"objc2",
|
|
||||||
"objc2-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-core-data"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"objc2",
|
|
||||||
"objc2-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-core-foundation"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"block2",
|
|
||||||
"dispatch2",
|
|
||||||
"libc",
|
|
||||||
"objc2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-core-graphics"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"block2",
|
|
||||||
"dispatch2",
|
|
||||||
"libc",
|
|
||||||
"objc2",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-io-surface",
|
|
||||||
"objc2-metal",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-core-image"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e"
|
|
||||||
dependencies = [
|
|
||||||
"objc2",
|
|
||||||
"objc2-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-encode"
|
|
||||||
version = "4.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-foundation"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"block2",
|
|
||||||
"libc",
|
|
||||||
"objc2",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-io-surface"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"objc2",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-metal"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"objc2",
|
|
||||||
"objc2-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-quartz-core"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"objc2",
|
|
||||||
"objc2-foundation",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
|
@ -1846,7 +1614,7 @@ dependencies = [
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"indoc",
|
"indoc",
|
||||||
"instability",
|
"instability",
|
||||||
"itertools 0.13.0",
|
"itertools",
|
||||||
"lru",
|
"lru",
|
||||||
"paste",
|
"paste",
|
||||||
"strum",
|
"strum",
|
||||||
|
@ -1855,12 +1623,6 @@ dependencies = [
|
||||||
"unicode-width 0.2.0",
|
"unicode-width 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "raw-window-handle"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
|
@ -2627,7 +2389,7 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools 0.13.0",
|
"itertools",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width 0.1.14",
|
"unicode-width 0.1.14",
|
||||||
]
|
]
|
||||||
|
@ -2685,16 +2447,6 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "versions"
|
|
||||||
version = "7.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "80a7e511ce1795821207a837b7b1c8d8aca0c648810966ad200446ae58f6667f"
|
|
||||||
dependencies = [
|
|
||||||
"itertools 0.14.0",
|
|
||||||
"nom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -2800,28 +2552,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wfd"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e713040b67aae5bf1a0ae3e1ebba8cc29ab2b90da9aa1bff6e09031a8a41d7a8"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "which"
|
|
||||||
version = "7.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"env_home",
|
|
||||||
"rustix 1.0.7",
|
|
||||||
"winsafe",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -3078,12 +2808,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winsafe"
|
|
||||||
version = "0.0.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
version = "0.39.0"
|
version = "0.39.0"
|
||||||
|
|
|
@ -16,7 +16,6 @@ local-ip-address = "0.6"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2"
|
mime_guess = "2"
|
||||||
native-dialog = "0.9"
|
|
||||||
network-interface = { version = "2", features = ["serde"] }
|
network-interface = { version = "2", features = ["serde"] }
|
||||||
ratatui = "0.29"
|
ratatui = "0.29"
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
|
153
src/app/mod.rs
153
src/app/mod.rs
|
@ -1,36 +1,21 @@
|
||||||
use std::{
|
use std::{collections::BTreeMap, net::SocketAddr, sync::OnceLock, time::Duration};
|
||||||
collections::BTreeMap,
|
|
||||||
net::SocketAddr,
|
|
||||||
sync::{LazyLock, OnceLock},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind};
|
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind};
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use joecalsend::{
|
use joecalsend::{
|
||||||
Config, JoecalState, Listeners, TransferEvent, UploadDialog,
|
Config, JoecalState, JoecalUploadRequest, Listeners, TransferEvent, UploadDialog,
|
||||||
error::{LocalSendError, Result},
|
error::{LocalSendError, Result},
|
||||||
models::Device,
|
models::Device,
|
||||||
};
|
};
|
||||||
|
use julid::Julid;
|
||||||
use log::{LevelFilter, error, info};
|
use log::{LevelFilter, error, info};
|
||||||
use native_dialog::MessageDialogBuilder;
|
use ratatui::{DefaultTerminal, Frame};
|
||||||
use ratatui::{
|
|
||||||
DefaultTerminal, Frame,
|
|
||||||
buffer::Buffer,
|
|
||||||
layout::{Constraint, Layout, Margin, Rect},
|
|
||||||
style::{Style, Stylize},
|
|
||||||
symbols::border,
|
|
||||||
text::{Line, Text},
|
|
||||||
widgets::{Block, Paragraph, Widget},
|
|
||||||
};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::mpsc::{UnboundedReceiver, unbounded_channel},
|
sync::mpsc::{UnboundedReceiver, unbounded_channel},
|
||||||
task::JoinSet,
|
task::JoinSet,
|
||||||
};
|
};
|
||||||
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
|
|
||||||
|
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
use widgets::*;
|
|
||||||
|
|
||||||
pub type Peers = BTreeMap<SocketAddr, (String, String)>;
|
pub type Peers = BTreeMap<SocketAddr, (String, String)>;
|
||||||
|
|
||||||
|
@ -40,6 +25,7 @@ pub struct App {
|
||||||
pub events: EventStream,
|
pub events: EventStream,
|
||||||
// addr -> (alias, fingerprint)
|
// addr -> (alias, fingerprint)
|
||||||
pub peers: Peers,
|
pub peers: Peers,
|
||||||
|
pub uploads: BTreeMap<Julid, JoecalUploadRequest>,
|
||||||
// for getting messages back from the web server or web client about things we've done; the
|
// for getting messages back from the web server or web client about things we've done; the
|
||||||
// other end is held by the state
|
// other end is held by the state
|
||||||
transfer_event_rx: OnceLock<UnboundedReceiver<TransferEvent>>,
|
transfer_event_rx: OnceLock<UnboundedReceiver<TransferEvent>>,
|
||||||
|
@ -67,6 +53,7 @@ impl App {
|
||||||
screen: vec![CurrentScreen::Main],
|
screen: vec![CurrentScreen::Main],
|
||||||
peers: Default::default(),
|
peers: Default::default(),
|
||||||
events: Default::default(),
|
events: Default::default(),
|
||||||
|
uploads: Default::default(),
|
||||||
transfer_event_rx: Default::default(),
|
transfer_event_rx: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,31 +118,13 @@ impl App {
|
||||||
transfer_event = self.transfer_event_rx.get_mut().unwrap().recv() => {
|
transfer_event = self.transfer_event_rx.get_mut().unwrap().recv() => {
|
||||||
if let Some(event) = transfer_event {
|
if let Some(event) = transfer_event {
|
||||||
match event {
|
match event {
|
||||||
TransferEvent::UploadRequest { alias, id } => {
|
TransferEvent::UploadRequest { id, request } => {
|
||||||
let sender =
|
self.uploads.insert(id, request);
|
||||||
self
|
|
||||||
.state
|
|
||||||
.get()
|
|
||||||
.unwrap()
|
|
||||||
.get_upload_request(id)
|
|
||||||
.await
|
|
||||||
.ok_or(LocalSendError::SessionInactive)?;
|
|
||||||
|
|
||||||
// TODO: replace this with ratatui widget dialog
|
|
||||||
let upload_confirmed = MessageDialogBuilder::default()
|
|
||||||
.set_title(&alias)
|
|
||||||
.set_text("Do you want to receive files from this device?")
|
|
||||||
.confirm()
|
|
||||||
.show()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if upload_confirmed {
|
|
||||||
let _ = sender.send(UploadDialog::UploadConfirm);
|
|
||||||
} else {
|
|
||||||
let _ = sender.send(UploadDialog::UploadDeny);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TransferEvent::Sent => {}
|
TransferEvent::Sent => {}
|
||||||
|
TransferEvent::Received(id) => {
|
||||||
|
self.uploads.remove(&id);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,106 +205,6 @@ fn change_log_level(delta: isize) {
|
||||||
log::set_max_level(level);
|
log::set_max_level(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
static MAIN_MENU: LazyLock<Line> = LazyLock::new(|| {
|
|
||||||
Line::from(vec![
|
|
||||||
" Send ".into(),
|
|
||||||
"<S>".blue().bold(),
|
|
||||||
" Receive ".into(),
|
|
||||||
"<R>".blue().bold(),
|
|
||||||
" Logs ".into(),
|
|
||||||
"<L>".blue().bold(),
|
|
||||||
" Previous Screen ".into(),
|
|
||||||
"<ESC>".blue().bold(),
|
|
||||||
" Quit ".into(),
|
|
||||||
"<Q>".blue().bold(),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
static LOGGING_MENU: LazyLock<Line> = LazyLock::new(|| {
|
|
||||||
Line::from(vec![
|
|
||||||
" Reduce Logging Level ".into(),
|
|
||||||
"<LEFT>".blue().bold(),
|
|
||||||
" Increase Logging Level ".into(),
|
|
||||||
"<RIGHT>".blue().bold(),
|
|
||||||
" Previous Screen ".into(),
|
|
||||||
"<ESC>".blue().bold(),
|
|
||||||
" Quit ".into(),
|
|
||||||
"<Q>".blue().bold(),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Widget for &App {
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
||||||
let main_layout =
|
|
||||||
Layout::vertical([Constraint::Min(5), Constraint::Min(10), Constraint::Min(3)]);
|
|
||||||
let [top, _middle, bottom] = main_layout.areas(area);
|
|
||||||
|
|
||||||
let footer_layout =
|
|
||||||
Layout::horizontal([Constraint::Percentage(30), Constraint::Percentage(70)]);
|
|
||||||
let [footer_left, footer_right] = footer_layout.areas(bottom);
|
|
||||||
|
|
||||||
let header_layout =
|
|
||||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]);
|
|
||||||
let [_header_left, header_right] = header_layout.areas(top);
|
|
||||||
|
|
||||||
let mode = self.screen.last().unwrap();
|
|
||||||
match mode {
|
|
||||||
CurrentScreen::Main => {
|
|
||||||
main_page(*mode, &MAIN_MENU, area, buf);
|
|
||||||
logger(header_right.inner(Margin::new(1, 2)), buf);
|
|
||||||
let peers = PeersWidget { peers: &self.peers };
|
|
||||||
peers.render(footer_right.inner(Margin::new(1, 1)), buf);
|
|
||||||
NetworkInfoWidget.render(footer_left.inner(Margin::new(1, 1)), buf);
|
|
||||||
}
|
|
||||||
CurrentScreen::Logging => {
|
|
||||||
main_page(*mode, &LOGGING_MENU, area, buf);
|
|
||||||
logger(area.inner(Margin::new(2, 4)), buf);
|
|
||||||
}
|
|
||||||
CurrentScreen::Receiving => {
|
|
||||||
main_page(*mode, &MAIN_MENU, area, buf);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
main_page(*mode, &MAIN_MENU, area, buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn logger(area: Rect, buf: &mut Buffer) {
|
|
||||||
let title = Line::from(log::max_level().as_str());
|
|
||||||
let logger = TuiLoggerWidget::default()
|
|
||||||
.output_separator('|')
|
|
||||||
.output_timestamp(Some("%H:%M:%S%.3f".to_string()))
|
|
||||||
.output_level(Some(TuiLoggerLevelOutput::Abbreviated))
|
|
||||||
.output_target(true)
|
|
||||||
.output_file(false)
|
|
||||||
.output_line(false)
|
|
||||||
.block(
|
|
||||||
Block::bordered()
|
|
||||||
.border_set(border::THICK)
|
|
||||||
.title(title.centered()),
|
|
||||||
)
|
|
||||||
.style(Style::default())
|
|
||||||
.state(&TuiWidgetState::new().set_default_display_level(LevelFilter::Debug));
|
|
||||||
logger.render(area, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main_page(screen: CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer) {
|
|
||||||
let title = Line::from(" Joecalsend ".bold());
|
|
||||||
let block = Block::bordered()
|
|
||||||
.title(title.centered())
|
|
||||||
.title_bottom(menu.clone().centered())
|
|
||||||
.border_set(border::THICK);
|
|
||||||
|
|
||||||
let current_screen = format!("{screen:?}",);
|
|
||||||
let text = Text::from(Line::from(current_screen.yellow()));
|
|
||||||
|
|
||||||
Paragraph::new(text)
|
|
||||||
.centered()
|
|
||||||
.block(block)
|
|
||||||
.render(area, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn shutdown(handles: &mut JoinSet<Listeners>) {
|
async fn shutdown(handles: &mut JoinSet<Listeners>) {
|
||||||
let mut alarm = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
let mut alarm = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
||||||
alarm.tick().await;
|
alarm.tick().await;
|
||||||
|
|
|
@ -1,12 +1,147 @@
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use joecalsend::JoecalUploadRequest;
|
||||||
|
use log::LevelFilter;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::{Constraint, Rect},
|
layout::{Constraint, Layout, Margin, Rect},
|
||||||
style::Stylize,
|
style::{Style, Stylize},
|
||||||
text::{Line, ToLine},
|
symbols::border,
|
||||||
widgets::{Block, Borders, List, ListItem, Padding, Row, Table, Widget},
|
text::{Line, Text, ToLine},
|
||||||
|
widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Row, Table, Widget},
|
||||||
};
|
};
|
||||||
|
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
|
||||||
|
|
||||||
use super::Peers;
|
use super::{App, CurrentScreen, Peers};
|
||||||
|
|
||||||
|
static MAIN_MENU: LazyLock<Line> = LazyLock::new(|| {
|
||||||
|
Line::from(vec![
|
||||||
|
" Send ".into(),
|
||||||
|
"<S>".blue().bold(),
|
||||||
|
" Receive ".into(),
|
||||||
|
"<R>".blue().bold(),
|
||||||
|
" Logs ".into(),
|
||||||
|
"<L>".blue().bold(),
|
||||||
|
" Previous Screen ".into(),
|
||||||
|
"<ESC>".blue().bold(),
|
||||||
|
" Quit ".into(),
|
||||||
|
"<Q>".blue().bold(),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
static LOGGING_MENU: LazyLock<Line> = LazyLock::new(|| {
|
||||||
|
Line::from(vec![
|
||||||
|
" Reduce Logging Level ".into(),
|
||||||
|
"<LEFT>".blue().bold(),
|
||||||
|
" Increase Logging Level ".into(),
|
||||||
|
"<RIGHT>".blue().bold(),
|
||||||
|
" Previous Screen ".into(),
|
||||||
|
"<ESC>".blue().bold(),
|
||||||
|
" Quit ".into(),
|
||||||
|
"<Q>".blue().bold(),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
impl Widget for &App {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let main_layout =
|
||||||
|
Layout::vertical([Constraint::Min(5), Constraint::Min(10), Constraint::Min(3)]);
|
||||||
|
let [top, _middle, bottom] = main_layout.areas(area);
|
||||||
|
|
||||||
|
let footer_layout =
|
||||||
|
Layout::horizontal([Constraint::Percentage(30), Constraint::Percentage(70)]);
|
||||||
|
let [footer_left, footer_right] = footer_layout.areas(bottom);
|
||||||
|
let footer_margin = Margin::new(1, 1);
|
||||||
|
|
||||||
|
let header_layout =
|
||||||
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]);
|
||||||
|
let [header_left, header_right] = header_layout.areas(top);
|
||||||
|
let header_margin = Margin::new(1, 2);
|
||||||
|
|
||||||
|
let mode = self.screen.last().unwrap();
|
||||||
|
match mode {
|
||||||
|
CurrentScreen::Main => {
|
||||||
|
main_page(*mode, &MAIN_MENU, area, buf);
|
||||||
|
logger(header_right.inner(header_margin), buf);
|
||||||
|
let peers = PeersWidget { peers: &self.peers };
|
||||||
|
peers.render(footer_right.inner(footer_margin), buf);
|
||||||
|
NetworkInfoWidget.render(footer_left.inner(footer_margin), buf);
|
||||||
|
let ups: Vec<_> = self.uploads.values().collect();
|
||||||
|
uploads(&ups, header_left.inner(header_margin), buf);
|
||||||
|
}
|
||||||
|
CurrentScreen::Logging => {
|
||||||
|
main_page(*mode, &LOGGING_MENU, area, buf);
|
||||||
|
logger(area.inner(Margin::new(2, 4)), buf);
|
||||||
|
}
|
||||||
|
CurrentScreen::Receiving => {
|
||||||
|
main_page(*mode, &MAIN_MENU, area, buf);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
main_page(*mode, &MAIN_MENU, area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logger(area: Rect, buf: &mut Buffer) {
|
||||||
|
let title = Line::from(log::max_level().as_str());
|
||||||
|
let logger = TuiLoggerWidget::default()
|
||||||
|
.output_separator('|')
|
||||||
|
.output_timestamp(Some("%H:%M:%S%.3f".to_string()))
|
||||||
|
.output_level(Some(TuiLoggerLevelOutput::Abbreviated))
|
||||||
|
.output_target(true)
|
||||||
|
.output_file(false)
|
||||||
|
.output_line(false)
|
||||||
|
.block(
|
||||||
|
Block::bordered()
|
||||||
|
.border_set(border::THICK)
|
||||||
|
.title(title.centered()),
|
||||||
|
)
|
||||||
|
.style(Style::default())
|
||||||
|
.state(&TuiWidgetState::new().set_default_display_level(LevelFilter::Debug));
|
||||||
|
logger.render(area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main_page(screen: CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer) {
|
||||||
|
let title = Line::from(" Joecalsend ".bold());
|
||||||
|
let block = Block::bordered()
|
||||||
|
.title(title.centered())
|
||||||
|
.title_bottom(menu.clone().centered())
|
||||||
|
.border_set(border::THICK);
|
||||||
|
|
||||||
|
let current_screen = format!("{screen:?}",);
|
||||||
|
let text = Text::from(Line::from(current_screen.yellow()));
|
||||||
|
|
||||||
|
Paragraph::new(text)
|
||||||
|
.centered()
|
||||||
|
.block(block)
|
||||||
|
.render(area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uploads(requests: &[&JoecalUploadRequest], area: Rect, buf: &mut Buffer) {
|
||||||
|
//let mut lines = Vec::new();
|
||||||
|
let title = Line::from(" Upload Requests ").bold();
|
||||||
|
let block = Block::bordered()
|
||||||
|
.title(title.centered())
|
||||||
|
.border_set(border::THICK);
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
for &req in requests {
|
||||||
|
let src = req.alias.to_line().left_aligned();
|
||||||
|
let files = req
|
||||||
|
.files
|
||||||
|
.values()
|
||||||
|
.map(|f| f.file_name.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let files = files.join(", ");
|
||||||
|
let files = Line::from(files);
|
||||||
|
rows.push(Row::new([src, files.centered()]));
|
||||||
|
}
|
||||||
|
let widths = [Constraint::Max(20), Constraint::Min(50)];
|
||||||
|
let table = Table::new(rows, widths)
|
||||||
|
.block(block)
|
||||||
|
.header(Row::new(["Sender".bold(), "Files".bold()]));
|
||||||
|
table.render(area, buf);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PeersWidget<'p> {
|
pub struct PeersWidget<'p> {
|
||||||
|
@ -44,13 +179,10 @@ impl Widget for NetworkInfoWidget {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
// let rows = Rows::new(rect);
|
|
||||||
// let mut table = Table::default();
|
|
||||||
|
|
||||||
let udp = "UDP socket".yellow();
|
let udp = "UDP socket".yellow();
|
||||||
let udp = udp.to_line().left_aligned();
|
let udp = udp.to_line().left_aligned();
|
||||||
let uaddr = format!("{:?}", joecalsend::LISTENING_SOCKET_ADDR).yellow();
|
let uaddr = format!("{:?}", joecalsend::LISTENING_SOCKET_ADDR).yellow();
|
||||||
let udp = Row::new(vec![udp.into(), uaddr.to_line().right_aligned()]);
|
let udp = Row::new(vec![udp, uaddr.to_line().right_aligned()]);
|
||||||
|
|
||||||
let mip = format!(
|
let mip = format!(
|
||||||
"{:?}:{:?}",
|
"{:?}:{:?}",
|
||||||
|
|
28
src/lib.rs
28
src/lib.rs
|
@ -12,7 +12,7 @@ use std::{
|
||||||
|
|
||||||
use julid::Julid;
|
use julid::Julid;
|
||||||
use log::error;
|
use log::error;
|
||||||
use models::Device;
|
use models::{Device, FileMetadata};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
|
@ -46,9 +46,19 @@ pub enum UploadDialog {
|
||||||
|
|
||||||
pub enum TransferEvent {
|
pub enum TransferEvent {
|
||||||
Sent,
|
Sent,
|
||||||
Received,
|
Received(Julid),
|
||||||
Failed,
|
Failed,
|
||||||
UploadRequest { alias: String, id: Julid },
|
UploadRequest {
|
||||||
|
id: Julid,
|
||||||
|
request: JoecalUploadRequest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct JoecalUploadRequest {
|
||||||
|
pub alias: String,
|
||||||
|
pub files: HashMap<String, FileMetadata>,
|
||||||
|
pub tx: UnboundedSender<UploadDialog>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains the main network and backend state for an application session.
|
/// Contains the main network and backend state for an application session.
|
||||||
|
@ -60,7 +70,7 @@ pub struct JoecalState {
|
||||||
pub running_state: Arc<Mutex<RunningState>>,
|
pub running_state: Arc<Mutex<RunningState>>,
|
||||||
pub socket: Arc<UdpSocket>,
|
pub socket: Arc<UdpSocket>,
|
||||||
pub client: reqwest::Client,
|
pub client: reqwest::Client,
|
||||||
upload_requests: Arc<Mutex<HashMap<Julid, UnboundedSender<UploadDialog>>>>,
|
pub upload_requests: Arc<Mutex<HashMap<Julid, JoecalUploadRequest>>>,
|
||||||
shutdown_sender: OnceLock<ShutdownSender>,
|
shutdown_sender: OnceLock<ShutdownSender>,
|
||||||
// the receiving end will be held by the application so it can update the UI based on backend
|
// the receiving end will be held by the application so it can update the UI based on backend
|
||||||
// events
|
// events
|
||||||
|
@ -152,7 +162,7 @@ impl JoecalState {
|
||||||
peers.clear();
|
peers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_upload_request(&self, id: Julid) -> Option<UnboundedSender<UploadDialog>> {
|
pub async fn get_upload_request(&self, id: Julid) -> Option<JoecalUploadRequest> {
|
||||||
self.upload_requests.lock().await.get(&id).cloned()
|
self.upload_requests.lock().await.get(&id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,8 +176,12 @@ impl JoecalState {
|
||||||
///
|
///
|
||||||
/// IMPORTANT! Be sure to call `clear_upload_request(id)` when you're done
|
/// IMPORTANT! Be sure to call `clear_upload_request(id)` when you're done
|
||||||
/// getting an answer back/before you exit!
|
/// getting an answer back/before you exit!
|
||||||
pub async fn add_upload_request(&self, id: Julid, tx: UnboundedSender<UploadDialog>) {
|
pub async fn add_upload_request(&self, id: Julid, request: JoecalUploadRequest) {
|
||||||
self.upload_requests.lock().await.entry(id).insert_entry(tx);
|
self.upload_requests
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.entry(id)
|
||||||
|
.insert_entry(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,12 @@ use axum::{
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
use julid::Julid;
|
use julid::Julid;
|
||||||
use log::{debug, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::mpsc::unbounded_channel;
|
use tokio::sync::mpsc::unbounded_channel;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
JoecalState, TransferEvent, UploadDialog,
|
JoecalState, JoecalUploadRequest, TransferEvent, UploadDialog,
|
||||||
error::{LocalSendError, Result},
|
error::{LocalSendError, Result},
|
||||||
models::{Device, FileMetadata},
|
models::{Device, FileMetadata},
|
||||||
};
|
};
|
||||||
|
@ -199,22 +199,24 @@ pub async fn register_prepare_upload(
|
||||||
let id = Julid::new();
|
let id = Julid::new();
|
||||||
let (tx, mut rx) = unbounded_channel();
|
let (tx, mut rx) = unbounded_channel();
|
||||||
// be sure to clear this request before this function exits!
|
// be sure to clear this request before this function exits!
|
||||||
state.add_upload_request(id, tx).await;
|
let request = JoecalUploadRequest {
|
||||||
|
|
||||||
let dialog_send = state.transfer_event_tx.send(TransferEvent::UploadRequest {
|
|
||||||
alias: req.info.alias.clone(),
|
alias: req.info.alias.clone(),
|
||||||
id,
|
files: req.files.clone(),
|
||||||
});
|
tx,
|
||||||
match dialog_send {
|
};
|
||||||
|
|
||||||
|
match state
|
||||||
|
.transfer_event_tx
|
||||||
|
.send(TransferEvent::UploadRequest { id, request })
|
||||||
|
{
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
state.clear_upload_request(id).await;
|
error!("error sending transfer event to app: {e:?}");
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let confirmation = rx.recv().await;
|
let confirmation = rx.recv().await;
|
||||||
state.clear_upload_request(id).await;
|
|
||||||
|
|
||||||
let Some(confirmation) = confirmation else {
|
let Some(confirmation) = confirmation else {
|
||||||
// the frontend must have dropped the tx before trying to send a reply back
|
// the frontend must have dropped the tx before trying to send a reply back
|
||||||
|
|
Loading…
Reference in a new issue