use crate::config::AppConfig;
use crate::event::Event;
use anyhow::Result;
use std::net::Ipv4Addr;
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;
use tokio::net::UdpSocket;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::watch::Receiver as WatchReceiver;
use tracing::{debug, error, info, warn};
const MAX_DATAGRAM_SIZE: usize = 8192;
fn is_socket_addr_multicast(socket_addr: &SocketAddr) -> bool {
match socket_addr.ip() {
IpAddr::V4(ipv4) => ipv4.is_multicast(),
IpAddr::V6(ipv6) => ipv6.is_multicast(),
}
}
async fn join_multicast(socket: &UdpSocket, multicast_addr_str: &str) -> Result<()> {
let multicast_socket_addr: SocketAddr = multicast_addr_str.parse()?;
if !is_socket_addr_multicast(&multicast_socket_addr) {
return Err(anyhow::anyhow!(
"Address is not multicast: {}",
multicast_addr_str
));
}
let ip_addr = match multicast_socket_addr.ip() {
std::net::IpAddr::V4(ip) => ip,
std::net::IpAddr::V6(_) => return Err(anyhow::anyhow!("IPv6 multicast not yet supported")),
};
let interface = Ipv4Addr::UNSPECIFIED;
socket.join_multicast_v4(ip_addr, interface)?;
info!(
"Joined multicast group {} on interface {}",
ip_addr, interface
);
Ok(())
}
pub async fn run_broadcaster(
app_config: Arc<AppConfig>,
mut event_rx: Receiver<Event>,
shutdown_signal: WatchReceiver<bool>,
) -> Result<()> {
if app_config.folders_to_watch.is_empty() {
return Err(anyhow::anyhow!("No folders specified for broadcaster mode"));
}
let target_addr_str = app_config.multicast_addr.clone();
let target_socket_addr: SocketAddr = target_addr_str.parse()?;
let local_addr: SocketAddr = "0.0.0.0:0".parse()?;
let socket = UdpSocket::bind(local_addr).await?;
info!("Broadcaster started, sending to {}", target_socket_addr);
let mut shutdown = shutdown_signal.clone();
loop {
tokio::select! {
Some(event) = event_rx.recv() => {
debug!("Broadcaster received event: {:?}", event);
match serde_json::to_vec(&event) {
Ok(payload) => {
if payload.len() > MAX_DATAGRAM_SIZE {
warn!("Serialized event too large for UDP: {} bytes", payload.len());
continue;
}
match socket.send_to(&payload, &target_socket_addr).await {
Ok(bytes_sent) => {
debug!("Sent {} bytes for event: {:?}", bytes_sent, event.op);
}
Err(e) => {
error!("Failed to send UDP multicast message: {}", e);
}
}
}
Err(e) => {
error!("Failed to serialize event to JSON: {}", e);
}
}
}
Ok(()) = shutdown.changed() => {
if *shutdown.borrow() {
info!("Broadcaster shutting down due to signal.");
break;
}
}
else => {
info!("Broadcaster event channel closed or shutdown error. Exiting.");
break;
}
}
}
Ok(())
}
pub async fn run_listener(
app_config: Arc<AppConfig>,
event_tx_for_web: Option<Sender<Event>>,
shutdown_signal: WatchReceiver<bool>,
) -> Result<()> {
let listen_addr_str = app_config.multicast_addr.clone();
let listen_socket_addr: SocketAddr = listen_addr_str.parse()?;
if !is_socket_addr_multicast(&listen_socket_addr) {
return Err(anyhow::anyhow!(
"Address is not multicast: {}",
listen_addr_str
));
}
let local_bind_addr_str = format!("0.0.0.0:{}", listen_socket_addr.port());
let local_bind_addr: SocketAddr = local_bind_addr_str.parse()?;
let socket = UdpSocket::bind(local_bind_addr).await?;
info!(
"Listener attempting to bind to {} and join multicast group {}",
local_bind_addr, listen_addr_str
);
join_multicast(&socket, &listen_addr_str).await?;
info!(
"Listener started, listening on multicast group {}",
listen_addr_str
);
let mut buffer = vec![0u8; MAX_DATAGRAM_SIZE];
let mut shutdown = shutdown_signal.clone();
loop {
tokio::select! {
result = socket.recv_from(&mut buffer) => {
match result {
Ok((len, src_addr)) => {
let data = &buffer[..len];
match serde_json::from_slice::<Event>(data) {
Ok(event) => {
info!("Listener received event from {}: Path: {}, Op: {}", src_addr, event.path, event.op);
if let Some(tx) = &event_tx_for_web {
if let Err(e) = tx.send(event.clone()).await {
error!("Failed to send event to web hub: {}", e);
}
}
}
Err(e) => {
warn!("Failed to deserialize event from {}: {}. Data (UTF-8 lossy): {}", src_addr, e, String::from_utf8_lossy(data));
}
}
}
Err(e) => {
error!("Error receiving UDP packet: {}", e);
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
}
Ok(()) = shutdown.changed() => {
if *shutdown.borrow() {
info!("Listener shutting down due to signal.");
break;
}
}
else => {
info!("Listener shutdown or channel error. Exiting.");
break;
}
}
}
Ok(())
}