diff --git a/src/control.rs b/src/control.rs new file mode 100644 index 0000000..7a20e78 --- /dev/null +++ b/src/control.rs @@ -0,0 +1,128 @@ +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; +use tokio::net::UnixListener; +use tokio::sync::{mpsc, oneshot}; +use tracing::{info, warn}; + +use crate::events::{BridgeStatus, ControlCommand}; + +#[derive(Debug, PartialEq)] +enum ParsedCommand { + IrcConnect, + IrcDisconnect, + IrcReconnect, + OwncastConnect, + OwncastDisconnect, + OwncastReconnect, + Status, + Quit, +} + +fn parse_command(input: &str) -> Option { + let trimmed = input.trim().to_lowercase(); + match trimmed.as_str() { + "irc connect" => Some(ParsedCommand::IrcConnect), + "irc disconnect" => Some(ParsedCommand::IrcDisconnect), + "irc reconnect" => Some(ParsedCommand::IrcReconnect), + "owncast connect" => Some(ParsedCommand::OwncastConnect), + "owncast disconnect" => Some(ParsedCommand::OwncastDisconnect), + "owncast reconnect" => Some(ParsedCommand::OwncastReconnect), + "status" => Some(ParsedCommand::Status), + "quit" => Some(ParsedCommand::Quit), + _ => None, + } +} + +pub async fn run_control_socket( + socket_path: &str, + control_tx: mpsc::Sender, +) -> anyhow::Result<()> { + let _ = std::fs::remove_file(socket_path); + let listener = UnixListener::bind(socket_path)?; + info!(path = %socket_path, "Control socket listening"); + + loop { + let (stream, _) = listener.accept().await?; + let control_tx = control_tx.clone(); + + tokio::spawn(async move { + let (reader, mut writer) = stream.into_split(); + let mut reader = BufReader::new(reader); + let mut line = String::new(); + + if reader.read_line(&mut line).await.is_err() { + return; + } + + match parse_command(&line) { + Some(ParsedCommand::Status) => { + let (reply_tx, reply_rx) = oneshot::channel(); + let cmd = ControlCommand::Status { reply: reply_tx }; + if control_tx.send(cmd).await.is_ok() { + if let Ok(status) = reply_rx.await { + let json = serde_json::to_string_pretty(&status).unwrap_or_default(); + let _ = writer.write_all(json.as_bytes()).await; + let _ = writer.write_all(b"\n").await; + } + } + } + Some(parsed) => { + let cmd = match parsed { + ParsedCommand::IrcConnect => ControlCommand::IrcConnect, + ParsedCommand::IrcDisconnect => ControlCommand::IrcDisconnect, + ParsedCommand::IrcReconnect => ControlCommand::IrcReconnect, + ParsedCommand::OwncastConnect => ControlCommand::OwncastConnect, + ParsedCommand::OwncastDisconnect => ControlCommand::OwncastDisconnect, + ParsedCommand::OwncastReconnect => ControlCommand::OwncastReconnect, + ParsedCommand::Quit => ControlCommand::Quit, + ParsedCommand::Status => unreachable!(), + }; + if control_tx.send(cmd).await.is_ok() { + let _ = writer.write_all(b"OK\n").await; + } else { + let _ = writer.write_all(b"ERROR: channel closed\n").await; + } + } + None => { + let _ = writer.write_all(b"ERROR: unknown command\n").await; + } + } + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_irc_commands() { + assert!(matches!(parse_command("irc connect"), Some(ParsedCommand::IrcConnect))); + assert!(matches!(parse_command("irc disconnect"), Some(ParsedCommand::IrcDisconnect))); + assert!(matches!(parse_command("irc reconnect"), Some(ParsedCommand::IrcReconnect))); + } + + #[test] + fn test_parse_owncast_commands() { + assert!(matches!(parse_command("owncast connect"), Some(ParsedCommand::OwncastConnect))); + assert!(matches!(parse_command("owncast disconnect"), Some(ParsedCommand::OwncastDisconnect))); + assert!(matches!(parse_command("owncast reconnect"), Some(ParsedCommand::OwncastReconnect))); + } + + #[test] + fn test_parse_status_and_quit() { + assert!(matches!(parse_command("status"), Some(ParsedCommand::Status))); + assert!(matches!(parse_command("quit"), Some(ParsedCommand::Quit))); + } + + #[test] + fn test_parse_unknown() { + assert!(parse_command("unknown command").is_none()); + assert!(parse_command("").is_none()); + } + + #[test] + fn test_parse_case_insensitive_trimmed() { + assert!(matches!(parse_command(" IRC CONNECT "), Some(ParsedCommand::IrcConnect))); + assert!(matches!(parse_command("QUIT\n"), Some(ParsedCommand::Quit))); + } +} diff --git a/src/main.rs b/src/main.rs index fa0fdf4..0e86b11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod config; +mod control; mod events; mod health; mod html;