mod config; mod listener; mod shutdown; mod tcp; mod udp; #[cfg(feature = "consul")] mod consul; use log::{debug, error, info, warn}; use std::collections::{HashMap, HashSet}; use std::error::Error; use std::sync::Arc; use tokio::sync::{broadcast, RwLock}; pub type Result = std::result::Result>; // #[derive(Debug)] struct ActiveListener { notify_shutdown: broadcast::Sender<()>, targets: Arc>>, } #[tokio::main] async fn main() -> Result<()> { fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( "{}[{}][{}] {}", chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), record.target(), record.level(), message )) }) // Add blanket level filter - .level(log::LevelFilter::Info) .level_for("rustocat", log::LevelFilter::Debug) .chain(std::io::stdout()) .apply()?; let mut listeners: HashMap = HashMap::new(); let text_config_provider = config::FileConfigProvider::new(); let mut target_provider: Box = { #[cfg(feature = "consul")] { let cfg = text_config_provider.load_config()?; if cfg.consul.is_some() && cfg.consul.unwrap() { let consul_config_provider = consul::ConsulConfigProvider::new(); Box::new(consul_config_provider) } else { Box::new(text_config_provider) } } #[cfg(not(feature = "consul"))] Box::new(text_config_provider) }; loop { let mappings = target_provider.get_targets().await?; let mut required_listeners: HashSet = HashSet::new(); for target in mappings { let mut source_str = "".to_owned(); if target.udp == None || target.udp == Some(false) { source_str.push_str("udp:"); } else { source_str.push_str("tcp:"); } source_str.push_str(&target.source); required_listeners.insert(source_str.clone()); if let Some(listener) = listeners.get(&source_str) { let mut invalid = false; let targets = listener.targets.read().await; for t in &target.targets { if !targets.iter().any(|e| *e == *t) { invalid = true; break; } } if !invalid { for t in targets.iter() { if !target.targets.iter().any(|e| *e == *t) { invalid = true; break; } } } if invalid { warn!("Found invalid targets! Adjusting!"); let mut w = listener.targets.write().await; *w = target.targets; } } else { let (notify_shutdown, _) = broadcast::channel(1); let listener = ActiveListener { notify_shutdown: notify_shutdown, targets: Arc::new(RwLock::new(target.targets)), }; let l = listener::Listener { shutdown: shutdown::ShutdownReceiver::new(listener.notify_shutdown.subscribe()), source: target.source.clone(), targets: listener.targets.clone(), }; tokio::spawn(async move { if target.udp == None || target.udp == Some(false) { if let Err(err) = tcp::start_tcp_listener(l).await { error!("tcp listener error: {}", err); } } else { if let Err(err) = udp::start_udp_listener(l).await { error!("udp listener error {}: {}", target.source, err); } } }); listeners.insert(source_str, listener); } } let to_delete: Vec<_> = listeners .keys() .filter(|x| required_listeners.get(*x).is_none()) .cloned() // Clone the result to make listeners mutable again! .collect(); for del_key in to_delete { if let Some(listener) = listeners.get(&del_key) { let res = listener.notify_shutdown.send(()); //Errors are irrelevant here. I guess.... match res { Ok(_) => { info!("Sent shutdown signal!"); } Err(_) => { warn!("Failed to send shutdown signal!"); } } debug!("Removing listener!"); listeners.remove(&del_key); } } target_provider.wait_for_change().await?; info!("Recevied SIGHUP, reloading config!"); } }