Compare commits
No commits in common. "c8621da2f085df7c1b6331f491318457a69518ed" and "597148f500a19a313bcc5d686e1ef97f1fccc778" have entirely different histories.
c8621da2f0
...
597148f500
7 changed files with 72 additions and 183 deletions
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -457,18 +457,6 @@ dependencies = [
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "educe"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417"
|
|
||||||
dependencies = [
|
|
||||||
"enum-ordinalize",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.104",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
@ -484,26 +472,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "enum-ordinalize"
|
|
||||||
version = "4.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5"
|
|
||||||
dependencies = [
|
|
||||||
"enum-ordinalize-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "enum-ordinalize-derive"
|
|
||||||
version = "4.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.104",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -1167,7 +1135,6 @@ dependencies = [
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"network-interface",
|
"network-interface",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"ratatui-explorer",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1656,16 +1623,6 @@ dependencies = [
|
||||||
"unicode-width 0.2.0",
|
"unicode-width 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ratatui-explorer"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a5135951c1bf2ef7ce25b46c53eacf45f1a3e6b283669229008d6744a9ca1332"
|
|
||||||
dependencies = [
|
|
||||||
"educe",
|
|
||||||
"ratatui",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
|
|
|
@ -18,7 +18,6 @@ mime = "0.3"
|
||||||
mime_guess = "2"
|
mime_guess = "2"
|
||||||
network-interface = { version = "2", features = ["serde"] }
|
network-interface = { version = "2", features = ["serde"] }
|
||||||
ratatui = "0.29"
|
ratatui = "0.29"
|
||||||
ratatui-explorer = "0.2.1"
|
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{collections::BTreeMap, net::SocketAddr, 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::{JoecalService, ReceiveDialog, ReceiveRequest, TransferEvent, error::Result};
|
use joecalsend::{JoecalService, JoecalUploadRequest, TransferEvent, UploadDialog, error::Result};
|
||||||
use julid::Julid;
|
use julid::Julid;
|
||||||
use log::{LevelFilter, debug, error, warn};
|
use log::{LevelFilter, debug, error, warn};
|
||||||
use ratatui::{Frame, widgets::TableState};
|
use ratatui::{Frame, widgets::TableState};
|
||||||
|
@ -18,7 +18,7 @@ pub struct App {
|
||||||
pub events: EventStream,
|
pub events: EventStream,
|
||||||
// addr -> (alias, fingerprint)
|
// addr -> (alias, fingerprint)
|
||||||
pub peers: Peers,
|
pub peers: Peers,
|
||||||
pub receive_requests: BTreeMap<Julid, ReceiveRequest>,
|
pub uploads: BTreeMap<Julid, JoecalUploadRequest>,
|
||||||
upload_state: TableState,
|
upload_state: TableState,
|
||||||
// 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 service
|
// other end is held by the service
|
||||||
|
@ -28,18 +28,12 @@ pub struct App {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum CurrentScreen {
|
pub enum CurrentScreen {
|
||||||
Main,
|
Main,
|
||||||
Sending(SendingScreen),
|
Sending,
|
||||||
Receiving,
|
Receiving,
|
||||||
Stopping,
|
Stopping,
|
||||||
Logging,
|
Logging,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum SendingScreen {
|
|
||||||
Files,
|
|
||||||
Peers,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(service: JoecalService, event_listener: UnboundedReceiver<TransferEvent>) -> Self {
|
pub fn new(service: JoecalService, event_listener: UnboundedReceiver<TransferEvent>) -> Self {
|
||||||
App {
|
App {
|
||||||
|
@ -48,7 +42,7 @@ impl App {
|
||||||
screen: vec![CurrentScreen::Main],
|
screen: vec![CurrentScreen::Main],
|
||||||
events: Default::default(),
|
events: Default::default(),
|
||||||
peers: Default::default(),
|
peers: Default::default(),
|
||||||
receive_requests: Default::default(),
|
uploads: Default::default(),
|
||||||
upload_state: Default::default(),
|
upload_state: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,11 +65,11 @@ impl App {
|
||||||
if let Some(event) = transfer_event {
|
if let Some(event) = transfer_event {
|
||||||
debug!("got transferr event {event:?}");
|
debug!("got transferr event {event:?}");
|
||||||
match event {
|
match event {
|
||||||
TransferEvent::ReceiveRequest { id, request } => {
|
TransferEvent::UploadRequest { id, request } => {
|
||||||
self.receive_requests.insert(id, request);
|
self.uploads.insert(id, request);
|
||||||
}
|
}
|
||||||
TransferEvent::Cancelled(id) | TransferEvent::Received(id) => {
|
TransferEvent::Cancelled(id) | TransferEvent::Received(id) => {
|
||||||
self.receive_requests.remove(&id);
|
self.uploads.remove(&id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +81,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_key_event(&mut self, key_event: KeyEvent) {
|
pub fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||||
match self.screen.last_mut().unwrap() {
|
match self.screen.last().unwrap() {
|
||||||
CurrentScreen::Logging => match key_event.code {
|
CurrentScreen::Logging => match key_event.code {
|
||||||
KeyCode::Esc => self.pop(),
|
KeyCode::Esc => self.pop(),
|
||||||
KeyCode::Left => change_log_level(-1),
|
KeyCode::Left => change_log_level(-1),
|
||||||
|
@ -104,22 +98,6 @@ impl App {
|
||||||
KeyCode::Char('q') => self.exit(),
|
KeyCode::Char('q') => self.exit(),
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
CurrentScreen::Sending(s) => match s {
|
|
||||||
SendingScreen::Files => match key_event.code {
|
|
||||||
KeyCode::Esc => self.pop(),
|
|
||||||
KeyCode::Char('q') => self.exit(),
|
|
||||||
KeyCode::Tab => *s = SendingScreen::Peers,
|
|
||||||
KeyCode::Enter => todo!("send the selected file or enter directory"),
|
|
||||||
_ => todo!("have the file picker handle it"),
|
|
||||||
},
|
|
||||||
SendingScreen::Peers => match key_event.code {
|
|
||||||
KeyCode::Esc => self.pop(),
|
|
||||||
KeyCode::Char('q') => self.exit(),
|
|
||||||
KeyCode::Tab => *s = SendingScreen::Files,
|
|
||||||
KeyCode::Enter => todo!("send to the selected peer"),
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_ => match key_event.code {
|
_ => match key_event.code {
|
||||||
KeyCode::Char('q') => self.exit(),
|
KeyCode::Char('q') => self.exit(),
|
||||||
KeyCode::Char('s') => self.send(),
|
KeyCode::Char('s') => self.send(),
|
||||||
|
@ -136,15 +114,15 @@ impl App {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// keys are sorted, so we can use the table selection index
|
// keys are sorted, so we can use the table selection index
|
||||||
let keys: Vec<_> = self.receive_requests.keys().collect();
|
let keys: Vec<_> = self.uploads.keys().collect();
|
||||||
let Some(key) = keys.get(idx) else {
|
let Some(key) = keys.get(idx) else {
|
||||||
warn!("could not get id from selection index {idx}");
|
warn!("could not get id from selection index {idx}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(req) = self.receive_requests.get(key) else {
|
let Some(req) = self.uploads.get(key) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Err(e) = req.tx.send(ReceiveDialog::Approve) {
|
if let Err(e) = req.tx.send(UploadDialog::UploadConfirm) {
|
||||||
error!("got error sending upload confirmation: {e:?}");
|
error!("got error sending upload confirmation: {e:?}");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -154,18 +132,18 @@ impl App {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// keys are sorted, so we can use the table selection index
|
// keys are sorted, so we can use the table selection index
|
||||||
let keys: Vec<_> = self.receive_requests.keys().cloned().collect();
|
let keys: Vec<_> = self.uploads.keys().cloned().collect();
|
||||||
let Some(key) = keys.get(idx) else {
|
let Some(key) = keys.get(idx) else {
|
||||||
warn!("could not get id from selection index {idx}");
|
warn!("could not get id from selection index {idx}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(req) = self.receive_requests.get(key).cloned() else {
|
let Some(req) = self.uploads.get(key).cloned() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Err(e) = req.tx.send(ReceiveDialog::Deny) {
|
if let Err(e) = req.tx.send(UploadDialog::UploadDeny) {
|
||||||
error!("got error sending upload confirmation: {e:?}");
|
error!("got error sending upload confirmation: {e:?}");
|
||||||
};
|
};
|
||||||
self.receive_requests.remove(key);
|
self.uploads.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, frame: &mut Frame) {
|
pub fn draw(&mut self, frame: &mut Frame) {
|
||||||
|
@ -179,10 +157,8 @@ impl App {
|
||||||
pub fn send(&mut self) {
|
pub fn send(&mut self) {
|
||||||
let last = self.screen.last();
|
let last = self.screen.last();
|
||||||
match last {
|
match last {
|
||||||
Some(CurrentScreen::Sending(_)) => {}
|
Some(CurrentScreen::Sending) => {}
|
||||||
_ => self
|
_ => self.screen.push(CurrentScreen::Sending),
|
||||||
.screen
|
|
||||||
.push(CurrentScreen::Sending(SendingScreen::Files)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use joecalsend::ReceiveRequest;
|
use joecalsend::JoecalUploadRequest;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
|
@ -12,7 +12,7 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
|
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
|
||||||
|
|
||||||
use super::{App, CurrentScreen, Peers, SendingScreen};
|
use super::{App, CurrentScreen, Peers};
|
||||||
|
|
||||||
static MAIN_MENU: LazyLock<Line> = LazyLock::new(|| {
|
static MAIN_MENU: LazyLock<Line> = LazyLock::new(|| {
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
|
@ -42,7 +42,7 @@ static LOGGING_MENU: LazyLock<Line> = LazyLock::new(|| {
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
static CONTENT_RECEIVE_MENU: LazyLock<Line> = LazyLock::new(|| {
|
static UPLOADS_MENU: LazyLock<Line> = LazyLock::new(|| {
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
" Select Previous ".into(),
|
" Select Previous ".into(),
|
||||||
"<UP>".blue().bold(),
|
"<UP>".blue().bold(),
|
||||||
|
@ -59,48 +59,11 @@ static CONTENT_RECEIVE_MENU: LazyLock<Line> = LazyLock::new(|| {
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
static CONTENT_SEND_FILE_MENU: LazyLock<Line> = LazyLock::new(|| {
|
|
||||||
Line::from(vec![
|
|
||||||
" Select Previous ".into(),
|
|
||||||
"<UP>".blue().bold(),
|
|
||||||
" Select Next ".into(),
|
|
||||||
"<DOWN>".blue().bold(),
|
|
||||||
" Select ".into(),
|
|
||||||
"<ENTER>".blue().bold(),
|
|
||||||
" Parent Dir ".into(),
|
|
||||||
"<LEFT>".blue().bold(),
|
|
||||||
" Child Dir ".into(),
|
|
||||||
"<RIGHT>".blue().bold(),
|
|
||||||
" Peers ".into(),
|
|
||||||
"<TAB>".blue().bold(),
|
|
||||||
" Previous Screen ".into(),
|
|
||||||
"<ESC>".blue().bold(),
|
|
||||||
" Quit ".into(),
|
|
||||||
"<Q>".blue().bold(),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
static CONTENT_SEND_PEERS_MENU: LazyLock<Line> = LazyLock::new(|| {
|
|
||||||
Line::from(vec![
|
|
||||||
" Select Previous ".into(),
|
|
||||||
"<UP>".blue().bold(),
|
|
||||||
" Select Next ".into(),
|
|
||||||
"<DOWN>".blue().bold(),
|
|
||||||
" Select ".into(),
|
|
||||||
"<ENTER>".blue().bold(),
|
|
||||||
" Files ".into(),
|
|
||||||
"<TAB>".blue().bold(),
|
|
||||||
" Previous Screen ".into(),
|
|
||||||
"<ESC>".blue().bold(),
|
|
||||||
" Quit ".into(),
|
|
||||||
"<Q>".blue().bold(),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Widget for &mut App {
|
impl Widget for &mut App {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let main_layout = Layout::vertical([Constraint::Min(5), Constraint::Min(3)]);
|
let main_layout =
|
||||||
let [top, bottom] = main_layout.areas(area);
|
Layout::vertical([Constraint::Min(5), Constraint::Min(10), Constraint::Min(3)]);
|
||||||
|
let [top, _middle, bottom] = main_layout.areas(area);
|
||||||
|
|
||||||
let footer_layout =
|
let footer_layout =
|
||||||
Layout::horizontal([Constraint::Percentage(30), Constraint::Percentage(70)]);
|
Layout::horizontal([Constraint::Percentage(30), Constraint::Percentage(70)]);
|
||||||
|
@ -114,68 +77,42 @@ impl Widget for &mut App {
|
||||||
|
|
||||||
let subscreen_margin = Margin::new(2, 4);
|
let subscreen_margin = Margin::new(2, 4);
|
||||||
|
|
||||||
let current_screen = self.screen.last().unwrap();
|
let mode = self.screen.last().unwrap();
|
||||||
|
let ups: Vec<_> = self.uploads.values().collect();
|
||||||
match current_screen {
|
match mode {
|
||||||
CurrentScreen::Main => {
|
CurrentScreen::Main => {
|
||||||
let rx_reqs: Vec<_> = self.receive_requests.values().collect();
|
main_page(*mode, &MAIN_MENU, area, buf);
|
||||||
outer_frame(*current_screen, &MAIN_MENU, area, buf);
|
|
||||||
logger(header_right.inner(header_margin), buf);
|
logger(header_right.inner(header_margin), buf);
|
||||||
let peers = PeersWidget { peers: &self.peers };
|
let peers = PeersWidget { peers: &self.peers };
|
||||||
peers.render(footer_right.inner(footer_margin), buf);
|
peers.render(footer_right.inner(footer_margin), buf);
|
||||||
NetworkInfoWidget.render(footer_left.inner(footer_margin), buf);
|
NetworkInfoWidget.render(footer_left.inner(footer_margin), buf);
|
||||||
receive_requests(
|
upload_requests(
|
||||||
&rx_reqs,
|
&ups,
|
||||||
&mut self.upload_state,
|
&mut self.upload_state,
|
||||||
header_left.inner(header_margin),
|
header_left.inner(header_margin),
|
||||||
buf,
|
buf,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CurrentScreen::Logging => {
|
CurrentScreen::Logging => {
|
||||||
outer_frame(*current_screen, &LOGGING_MENU, area, buf);
|
main_page(*mode, &LOGGING_MENU, area, buf);
|
||||||
logger(area.inner(subscreen_margin), buf);
|
logger(area.inner(subscreen_margin), buf);
|
||||||
}
|
}
|
||||||
CurrentScreen::Receiving => {
|
CurrentScreen::Receiving => {
|
||||||
let rx_reqs: Vec<_> = self.receive_requests.values().collect();
|
main_page(*mode, &UPLOADS_MENU, area, buf);
|
||||||
outer_frame(*current_screen, &CONTENT_RECEIVE_MENU, area, buf);
|
upload_requests(
|
||||||
receive_requests(
|
&ups,
|
||||||
&rx_reqs,
|
|
||||||
&mut self.upload_state,
|
&mut self.upload_state,
|
||||||
area.inner(subscreen_margin),
|
area.inner(subscreen_margin),
|
||||||
buf,
|
buf,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CurrentScreen::Sending(s) => match s {
|
|
||||||
SendingScreen::Files => {
|
|
||||||
outer_frame(*current_screen, &CONTENT_SEND_FILE_MENU, area, buf)
|
|
||||||
}
|
|
||||||
SendingScreen::Peers => {
|
|
||||||
outer_frame(*current_screen, &CONTENT_SEND_PEERS_MENU, area, buf)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
_ => {
|
||||||
outer_frame(*current_screen, &MAIN_MENU, area, buf);
|
main_page(*mode, &MAIN_MENU, area, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outer_frame(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 logger(area: Rect, buf: &mut Buffer) {
|
fn logger(area: Rect, buf: &mut Buffer) {
|
||||||
let title = Line::from(log::max_level().as_str());
|
let title = Line::from(log::max_level().as_str());
|
||||||
let logger = TuiLoggerWidget::default()
|
let logger = TuiLoggerWidget::default()
|
||||||
|
@ -191,8 +128,24 @@ fn logger(area: Rect, buf: &mut Buffer) {
|
||||||
logger.render(area, buf);
|
logger.render(area, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_requests(
|
fn main_page(screen: CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer) {
|
||||||
requests: &[&ReceiveRequest],
|
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 upload_requests(
|
||||||
|
requests: &[&JoecalUploadRequest],
|
||||||
state: &mut TableState,
|
state: &mut TableState,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
|
|
17
src/lib.rs
17
src/lib.rs
|
@ -39,23 +39,26 @@ pub enum Listeners {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ReceiveDialog {
|
pub enum UploadDialog {
|
||||||
Approve,
|
UploadDeny,
|
||||||
Deny,
|
UploadConfirm,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TransferEvent {
|
pub enum TransferEvent {
|
||||||
Received(Julid),
|
Received(Julid),
|
||||||
Cancelled(Julid),
|
Cancelled(Julid),
|
||||||
ReceiveRequest { id: Julid, request: ReceiveRequest },
|
UploadRequest {
|
||||||
|
id: Julid,
|
||||||
|
request: JoecalUploadRequest,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ReceiveRequest {
|
pub struct JoecalUploadRequest {
|
||||||
pub alias: String,
|
pub alias: String,
|
||||||
pub files: HashMap<String, FileMetadata>,
|
pub files: HashMap<String, FileMetadata>,
|
||||||
pub tx: UnboundedSender<ReceiveDialog>,
|
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.
|
||||||
|
@ -80,7 +83,7 @@ impl JoecalService {
|
||||||
) -> crate::error::Result<Self> {
|
) -> crate::error::Result<Self> {
|
||||||
let socket = UdpSocket::bind(LISTENING_SOCKET_ADDR).await?;
|
let socket = UdpSocket::bind(LISTENING_SOCKET_ADDR).await?;
|
||||||
socket.set_multicast_loop_v4(true)?;
|
socket.set_multicast_loop_v4(true)?;
|
||||||
socket.set_multicast_ttl_v4(8)?; // 8 hops out from localnet
|
socket.set_multicast_ttl_v4(2)?; // one hop out from localnet
|
||||||
socket.join_multicast_v4(MULTICAST_IP, Ipv4Addr::from_bits(0))?;
|
socket.join_multicast_v4(MULTICAST_IP, Ipv4Addr::from_bits(0))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -62,15 +62,15 @@ async fn start_and_run(
|
||||||
.insert(addr.to_owned(), (alias, fingerprint.to_owned()));
|
.insert(addr.to_owned(), (alias, fingerprint.to_owned()));
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut stale_uploads = Vec::with_capacity(app.receive_requests.len());
|
let mut stale_uploads = Vec::with_capacity(app.uploads.len());
|
||||||
let now = chrono::Utc::now().timestamp_millis() as u64;
|
let now = chrono::Utc::now().timestamp_millis() as u64;
|
||||||
for (id, request) in app.receive_requests.iter() {
|
for (id, request) in app.uploads.iter() {
|
||||||
if request.tx.is_closed() || (now - id.timestamp()) > 60_000 {
|
if request.tx.is_closed() || (now - id.timestamp()) > 60_000 {
|
||||||
stale_uploads.push(*id);
|
stale_uploads.push(*id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for id in stale_uploads {
|
for id in stale_uploads {
|
||||||
app.receive_requests.remove(&id);
|
app.uploads.remove(&id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::mpsc::unbounded_channel;
|
use tokio::sync::mpsc::unbounded_channel;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
JoecalService, ReceiveDialog, ReceiveRequest, TransferEvent,
|
JoecalService, JoecalUploadRequest, TransferEvent, UploadDialog,
|
||||||
error::{LocalSendError, Result},
|
error::{LocalSendError, Result},
|
||||||
models::{Device, FileMetadata},
|
models::{Device, FileMetadata},
|
||||||
};
|
};
|
||||||
|
@ -198,7 +198,7 @@ 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();
|
||||||
let request = ReceiveRequest {
|
let request = JoecalUploadRequest {
|
||||||
alias: req.info.alias.clone(),
|
alias: req.info.alias.clone(),
|
||||||
files: req.files.clone(),
|
files: req.files.clone(),
|
||||||
tx,
|
tx,
|
||||||
|
@ -206,7 +206,7 @@ pub async fn register_prepare_upload(
|
||||||
|
|
||||||
match service
|
match service
|
||||||
.transfer_event_tx
|
.transfer_event_tx
|
||||||
.send(TransferEvent::ReceiveRequest { id, request })
|
.send(TransferEvent::UploadRequest { id, request })
|
||||||
{
|
{
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -215,13 +215,14 @@ pub async fn register_prepare_upload(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(confirmation) = rx.recv().await else {
|
let confirmation = rx.recv().await;
|
||||||
|
|
||||||
|
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
|
||||||
warn!("could not read content receive response from the frontend");
|
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
};
|
};
|
||||||
|
|
||||||
if confirmation != ReceiveDialog::Approve {
|
if confirmation != UploadDialog::UploadConfirm {
|
||||||
return StatusCode::FORBIDDEN.into_response();
|
return StatusCode::FORBIDDEN.into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue