From b486b33fa5c90852947ca1e81351444d95b1098f Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sun, 3 Aug 2025 13:46:03 -0700 Subject: [PATCH] cleanup and renames ('state' to 'service') --- src/app/mod.rs | 24 +++++++++++++++--------- src/app/widgets.rs | 4 ++-- src/discovery.rs | 17 +++++++++-------- src/http_server.rs | 26 +++++++++----------------- src/lib.rs | 24 +++++++++++++----------- src/main.rs | 4 ++-- src/transfer.rs | 11 ++++++----- 7 files changed, 56 insertions(+), 54 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 72a4e37..ddb202c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,11 +1,13 @@ use std::{collections::BTreeMap, net::SocketAddr, time::Duration}; +use axum::body::Bytes; use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind}; use futures::{FutureExt, StreamExt}; use joecalsend::{JoecalService, ReceiveDialog, ReceiveRequest, TransferEvent, error::Result}; use julid::Julid; use log::{LevelFilter, debug, error, warn}; use ratatui::{Frame, widgets::TableState}; +use ratatui_explorer::FileExplorer; use tokio::sync::mpsc::UnboundedReceiver; pub mod widgets; @@ -19,10 +21,12 @@ pub struct App { // addr -> (alias, fingerprint) pub peers: Peers, pub receive_requests: BTreeMap, - upload_state: TableState, + receiving_state: TableState, // for getting messages back from the web server or web client about things we've done; the // other end is held by the service event_listener: UnboundedReceiver, + file_picker: FileExplorer, + content: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -46,10 +50,12 @@ impl App { service, event_listener, screen: vec![CurrentScreen::Main], + file_picker: FileExplorer::new().expect("could not create file explorer"), + content: None, events: Default::default(), peers: Default::default(), receive_requests: Default::default(), - upload_state: Default::default(), + receiving_state: Default::default(), } } @@ -60,7 +66,7 @@ impl App { match evt { Event::Key(key) if key.kind == KeyEventKind::Press - => self.handle_key_event(key), + => self.handle_key_event(key, evt), Event::Mouse(_) => {} Event::Resize(_, _) => {} _ => {} @@ -86,7 +92,7 @@ impl App { Ok(()) } - pub fn handle_key_event(&mut self, key_event: KeyEvent) { + pub fn handle_key_event(&mut self, key_event: KeyEvent, event: crossterm::event::Event) { match self.screen.last_mut().unwrap() { CurrentScreen::Logging => match key_event.code { KeyCode::Esc => self.pop(), @@ -96,8 +102,8 @@ impl App { _ => {} }, CurrentScreen::Receiving => match key_event.code { - KeyCode::Up => self.upload_state.select_previous(), - KeyCode::Down => self.upload_state.select_next(), + KeyCode::Up => self.receiving_state.select_previous(), + KeyCode::Down => self.receiving_state.select_next(), KeyCode::Char('a') => self.accept(), KeyCode::Char('d') => self.deny(), KeyCode::Esc => self.pop(), @@ -110,7 +116,7 @@ impl App { 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"), + _ => self.file_picker.handle(&event).unwrap_or_default(), }, SendingScreen::Peers => match key_event.code { KeyCode::Esc => self.pop(), @@ -132,7 +138,7 @@ impl App { } pub fn accept(&mut self) { - let Some(idx) = self.upload_state.selected() else { + let Some(idx) = self.receiving_state.selected() else { return; }; // keys are sorted, so we can use the table selection index @@ -150,7 +156,7 @@ impl App { } pub fn deny(&mut self) { - let Some(idx) = self.upload_state.selected() else { + let Some(idx) = self.receiving_state.selected() else { return; }; // keys are sorted, so we can use the table selection index diff --git a/src/app/widgets.rs b/src/app/widgets.rs index 1a39df9..be73218 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -126,7 +126,7 @@ impl Widget for &mut App { NetworkInfoWidget.render(footer_left.inner(footer_margin), buf); receive_requests( &rx_reqs, - &mut self.upload_state, + &mut self.receiving_state, header_left.inner(header_margin), buf, ); @@ -140,7 +140,7 @@ impl Widget for &mut App { outer_frame(*current_screen, &CONTENT_RECEIVE_MENU, area, buf); receive_requests( &rx_reqs, - &mut self.upload_state, + &mut self.receiving_state, area.inner(subscreen_margin), buf, ); diff --git a/src/discovery.rs b/src/discovery.rs index 2e15a20..fdae242 100644 --- a/src/discovery.rs +++ b/src/discovery.rs @@ -14,18 +14,19 @@ use tokio::net::UdpSocket; use crate::{Config, JoecalService, RunningState, models::Device}; impl JoecalService { - pub async fn announce( - &self, - socket: Option, - config: &Config, - ) -> crate::error::Result<()> { + pub async fn announce(&self, socket: Option) -> crate::error::Result<()> { trace!("announcing"); announce_http(&self.device, socket, self.client.clone()).await?; - announce_multicast(&self.device, config.multicast_addr, self.socket.clone()).await?; + announce_multicast( + &self.device, + self.config.multicast_addr, + self.socket.clone(), + ) + .await?; Ok(()) } - pub async fn listen_multicast(&self, config: &Config) -> crate::error::Result<()> { + pub async fn listen_multicast(&self) -> crate::error::Result<()> { let mut buf = [0; 65536]; let mut timeout = tokio::time::interval(Duration::from_secs(5)); @@ -48,7 +49,7 @@ impl JoecalService { match r { Ok((size, src)) => { let received_msg = String::from_utf8_lossy(&buf[..size]); - self.process_device(&received_msg, src, config).await; + self.process_device(&received_msg, src, &self.config).await; } Err(e) => { error!("Error receiving message: {e}"); diff --git a/src/http_server.rs b/src/http_server.rs index dda9c94..29b909f 100644 --- a/src/http_server.rs +++ b/src/http_server.rs @@ -1,7 +1,7 @@ use std::net::SocketAddr; use axum::{ - Extension, Json, Router, + Json, Router, extract::DefaultBodyLimit, routing::{get, post}, }; @@ -9,20 +9,16 @@ use tokio::{net::TcpListener, sync::mpsc}; use tower_http::limit::RequestBodyLimitLayer; use crate::{ - Config, JoecalService, + JoecalService, discovery::register_device, - transfer::{register_prepare_upload, register_upload}, + transfer::{prepare_upload, receive_upload}, }; impl JoecalService { - pub async fn start_http_server( - &self, - stop_rx: mpsc::Receiver<()>, - config: &Config, - ) -> crate::error::Result<()> { - let app = self.create_router(config); + pub async fn start_http_server(&self, stop_rx: mpsc::Receiver<()>) -> crate::error::Result<()> { + let app = self.create_router(); // TODO: make addr config - let addr = SocketAddr::from(([0, 0, 0, 0], config.port)); + let addr = SocketAddr::from(([0, 0, 0, 0], self.config.port)); let listener = TcpListener::bind(&addr).await?; axum::serve( @@ -34,7 +30,7 @@ impl JoecalService { Ok(()) } - fn create_router(&self, config: &Config) -> Router { + fn create_router(&self) -> Router { let device = self.device.clone(); Router::new() .route("/api/localsend/v2/register", post(register_device)) @@ -42,14 +38,10 @@ impl JoecalService { "/api/localsend/v2/info", get(move || async move { Json(device) }), ) - .route( - "/api/localsend/v2/prepare-upload", - post(register_prepare_upload), - ) - .route("/api/localsend/v2/upload", post(register_upload)) + .route("/api/localsend/v2/prepare-upload", post(prepare_upload)) + .route("/api/localsend/v2/upload", post(receive_upload)) .layer(DefaultBodyLimit::disable()) .layer(RequestBodyLimitLayer::new(1024 * 1024 * 1024)) - .layer(Extension(config.download_dir.clone())) .with_state(self.clone()) } } diff --git a/src/lib.rs b/src/lib.rs index c7babdc..a4a01c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ pub struct JoecalService { pub running_state: Arc>, pub socket: Arc, pub client: reqwest::Client, + pub config: Config, shutdown_sender: OnceLock, // the receiving end will be held by the application so it can update the UI based on backend // events @@ -76,6 +77,7 @@ pub struct JoecalService { impl JoecalService { pub async fn new( device: Device, + config: Config, transfer_event_tx: UnboundedSender, ) -> crate::error::Result { let socket = UdpSocket::bind(LISTENING_SOCKET_ADDR).await?; @@ -85,6 +87,7 @@ impl JoecalService { Ok(Self { device, + config, client: reqwest::Client::new(), socket: socket.into(), transfer_event_tx, @@ -95,40 +98,39 @@ impl JoecalService { }) } - pub async fn start(&self, config: &Config, handles: &mut JoinSet) { - let state = self.clone(); - let konfig = config.clone(); + pub async fn start(&self, handles: &mut JoinSet) { + let service = self.clone(); + handles.spawn({ let (tx, shutdown_rx) = mpsc::channel(1); let _ = self.shutdown_sender.set(tx); async move { - if let Err(e) = state.start_http_server(shutdown_rx, &konfig).await { + if let Err(e) = service.start_http_server(shutdown_rx).await { error!("HTTP server error: {e}"); } Listeners::Http } }); - let state = self.clone(); - let konfig = config.clone(); + let service = self.clone(); + handles.spawn({ async move { - if let Err(e) = state.listen_multicast(&konfig).await { + if let Err(e) = service.listen_multicast().await { error!("UDP listener error: {e}"); } Listeners::Multicast } }); - let state = self.clone(); - let config = config.clone(); + let service = self.clone(); handles.spawn({ async move { loop { - let rstate = state.running_state.lock().await; + let rstate = service.running_state.lock().await; if *rstate == RunningState::Stopping { break; } - if let Err(e) = state.announce(None, &config).await { + if let Err(e) = service.announce(None).await { error!("Announcement error: {e}"); } tokio::time::sleep(std::time::Duration::from_secs(5)).await; diff --git a/src/main.rs b/src/main.rs index f239cbc..f3fa933 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,14 +35,14 @@ async fn start_and_run( ) -> error::Result<()> { let (event_tx, event_listener) = unbounded_channel(); - let service = JoecalService::new(device, event_tx) + let service = JoecalService::new(device, config.clone(), event_tx) .await .expect("Could not create JoecalService"); let mut app = App::new(service, event_listener); let mut handles = JoinSet::new(); - app.service.start(&config, &mut handles).await; + app.service.start(&mut handles).await; loop { terminal.draw(|frame| app.draw(frame))?; app.handle_events().await?; diff --git a/src/transfer.rs b/src/transfer.rs index 62c15a0..3e7da5a 100644 --- a/src/transfer.rs +++ b/src/transfer.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, net::SocketAddr, path::PathBuf}; use axum::{ - Extension, Json, + Json, body::Bytes, extract::{ConnectInfo, Query, State}, http::StatusCode, @@ -189,7 +189,7 @@ impl JoecalService { } } -pub async fn register_prepare_upload( +pub async fn prepare_upload( State(service): State, ConnectInfo(addr): ConnectInfo, Json(req): Json, @@ -259,10 +259,9 @@ pub async fn register_prepare_upload( .into_response() } -pub async fn register_upload( +pub async fn receive_upload( Query(params): Query, State(service): State, - Extension(download_dir): Extension, body: Bytes, ) -> impl IntoResponse { // Extract query parameters @@ -298,8 +297,10 @@ pub async fn register_upload( } }; + let download_dir = &service.config.download_dir; + // Create directory if it doesn't exist - if let Err(e) = tokio::fs::create_dir_all(&*download_dir).await { + if let Err(e) = tokio::fs::create_dir_all(download_dir).await { return ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create directory: {e}"),