remove native dialog dep

This commit is contained in:
Joe Ardent 2025-08-01 09:16:05 -07:00
parent 5907909470
commit 5f2e2f3eb2
6 changed files with 188 additions and 448 deletions

280
Cargo.lock generated
View file

@ -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"

View file

@ -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"] }

View file

@ -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;

View file

@ -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!(
"{:?}:{:?}", "{:?}:{:?}",

View file

@ -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);
} }
} }

View file

@ -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