more informative shutdown

This commit is contained in:
Joe Ardent 2025-07-08 16:31:00 -07:00
parent 9e1e08851e
commit a543c177b2
3 changed files with 38 additions and 29 deletions

View file

@ -16,7 +16,7 @@ use serde::{Deserialize, Serialize};
use tokio::{ use tokio::{
net::UdpSocket, net::UdpSocket,
sync::{Mutex, mpsc}, sync::{Mutex, mpsc},
task::JoinHandle, task::JoinSet,
}; };
use transfer::Session; use transfer::Session;
@ -25,7 +25,14 @@ pub const MULTICAST_IP: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 167);
pub const LISTENING_SOCKET_ADDR: SocketAddrV4 = pub const LISTENING_SOCKET_ADDR: SocketAddrV4 =
SocketAddrV4::new(Ipv4Addr::from_bits(0), DEFAULT_PORT); SocketAddrV4::new(Ipv4Addr::from_bits(0), DEFAULT_PORT);
type ShutdownSender = mpsc::Sender<()>; pub type ShutdownSender = mpsc::Sender<()>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Listeners {
Udp,
Http,
Multicast,
}
/// Contains the main network and backend state for an application session. /// Contains the main network and backend state for an application session.
#[derive(Clone)] #[derive(Clone)]
@ -57,35 +64,34 @@ impl JoecalState {
}) })
} }
pub async fn start( pub async fn start(&self, config: &Config, handles: &mut JoinSet<Listeners>) {
&self,
config: &Config,
) -> crate::error::Result<(JoinHandle<()>, JoinHandle<()>, JoinHandle<()>)> {
let state = self.clone(); let state = self.clone();
let konfig = config.clone(); let konfig = config.clone();
let server_handle = { handles.spawn({
let (tx, shutdown_rx) = mpsc::channel(1); let (tx, shutdown_rx) = mpsc::channel(1);
self.stop_tx.get_or_init(|| tx); self.stop_tx.get_or_init(|| tx);
tokio::spawn(async move { async move {
if let Err(e) = state.start_http_server(shutdown_rx, &konfig).await { if let Err(e) = state.start_http_server(shutdown_rx, &konfig).await {
eprintln!("HTTP server error: {e}"); eprintln!("HTTP server error: {e}");
} }
}) Listeners::Http
}; }
});
let state = self.clone(); let state = self.clone();
let konfig = config.clone(); let konfig = config.clone();
let udp_handle = { handles.spawn({
tokio::spawn(async move { async move {
if let Err(e) = state.listen_multicast(&konfig).await { if let Err(e) = state.listen_multicast(&konfig).await {
eprintln!("UDP listener error: {e}"); eprintln!("UDP listener error: {e}");
} }
}) Listeners::Multicast
}; }
});
let state = self.clone(); let state = self.clone();
let config = config.clone(); let config = config.clone();
let announcement_handle = { handles.spawn({
tokio::spawn(async move { async move {
loop { loop {
let rstate = state.running_state.lock().await; let rstate = state.running_state.lock().await;
if *rstate == RunningState::Stopping { if *rstate == RunningState::Stopping {
@ -96,10 +102,9 @@ impl JoecalState {
} }
tokio::time::sleep(std::time::Duration::from_secs(5)).await; tokio::time::sleep(std::time::Duration::from_secs(5)).await;
} }
}) Listeners::Udp
}; }
});
Ok((server_handle, udp_handle, announcement_handle))
} }
pub async fn stop(&self) { pub async fn stop(&self) {

View file

@ -1,11 +1,11 @@
#![feature(slice_as_array)] #![feature(slice_as_array)]
use std::{collections::BTreeMap, io, net::SocketAddr}; use std::{collections::BTreeMap, io, net::SocketAddr};
use joecalsend::{Config, JoecalState, RunningState, error, models::Device}; use joecalsend::{Config, JoecalState, error, models::Device};
use local_ip_address::local_ip; use local_ip_address::local_ip;
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig, V4IfAddr}; use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig, V4IfAddr};
use ratatui::{ use ratatui::{
DefaultTerminal, Frame, DefaultTerminal,
buffer::Buffer, buffer::Buffer,
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
layout::Rect, layout::Rect,
@ -14,6 +14,7 @@ use ratatui::{
text::{Line, Text}, text::{Line, Text},
widgets::{Block, Paragraph, Widget}, widgets::{Block, Paragraph, Widget},
}; };
use tokio::task::JoinSet;
mod ui; mod ui;
@ -46,14 +47,19 @@ async fn main() -> error::Result<()> {
.await .await
.expect("Could not create application session"); .expect("Could not create application session");
let config = Config::default(); let config = Config::default();
let (h1, h2, h3) = state.start(&config).await.unwrap(); let mut handles = JoinSet::new();
state.start(&config, &mut handles).await;
let mut app = App::new(state.clone()).await; let mut app = App::new(state.clone()).await;
let mut terminal = ratatui::init(); let mut terminal = ratatui::init();
let result = app.run(&mut terminal).await; let result = app.run(&mut terminal).await;
ratatui::restore(); ratatui::restore();
let _ = tokio::join!(h1, h2, h3); while let Some(handle) = handles.join_next().await {
match handle {
Ok(h) => println!("Stopped {h:?}"),
Err(e) => println!("Got error {e:?}"),
}
}
Ok(result?) Ok(result?)
} }
@ -78,7 +84,7 @@ enum CurrentScreen {
impl App { impl App {
pub async fn new(state: JoecalState) -> Self { pub async fn new(state: JoecalState) -> Self {
App { App {
state: state.clone(), state,
screen: CurrentScreen::Main, screen: CurrentScreen::Main,
peers: Default::default(), peers: Default::default(),
} }

View file

@ -1,10 +1,8 @@
use std::{collections::BTreeMap, net::SocketAddr};
use ratatui::{ use ratatui::{
Frame, Frame,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
style::Stylize, style::Stylize,
text::{Line, Span}, text::Line,
widgets::{Block, Borders, List, ListItem}, widgets::{Block, Borders, List, ListItem},
}; };