use std::{ os::unix::net::{UnixListener, UnixStream}, sync::{Arc, atomic::Ordering}, thread::JoinHandle, }; use camino::{Utf8Path, Utf8PathBuf}; use gatherer_api::{ request::{self, Request}, response::{self, Response}, }; use crate::{Database, State, thread}; pub struct ApiThread { handle: JoinHandle<()>, } impl ApiThread { pub fn start( socket_path: Utf8PathBuf, db: Arc, state: State, graph_directory: Utf8PathBuf, ) -> Self { let handle = std::thread::spawn(move || runner(socket_path, db, state, graph_directory)); tracing::info!("started api thread"); Self { handle } } pub fn wait(self) { self.handle.join().unwrap() } } fn runner(socket_path: Utf8PathBuf, db: Arc, state: State, graph_directory: Utf8PathBuf) { let us = UnixListener::bind(socket_path).unwrap(); for connection in us.incoming() { tracing::info!("got connection!"); match connection { Ok(stream) => { let db_clone = Arc::clone(&db); let state_clone = state.clone(); let graph_directory = graph_directory.clone(); std::thread::spawn(|| { handle_connection(stream, db_clone, state_clone, graph_directory) }); } Err(err) => { tracing::error!(%err) } } } } fn handle_connection( mut stream: UnixStream, db: Arc, state: State, graph_directory: Utf8PathBuf, ) { let mut buffer = vec![0; 8096]; loop { match gatherer_api::read_packet(&mut stream, &mut buffer) { Err(err) => { tracing::error!("read failed; closing connection! {err}"); return; } Ok(count) => { let packet = &buffer[..count]; let req = Request::from_buffer(packet).unwrap(); let response = match req.message() { request::Message::GetGraphBounds => graph_bounds(&state), request::Message::GenerateGraphs => { let body = req.get_body().unwrap(); generate_graphs(body, &db) } request::Message::GetRegularGraphs => regular_graphs(&state, &graph_directory), }; tracing::trace!("sending response back"); response.send(&mut stream).unwrap(); } } } } fn graph_bounds(state: &State) -> Response { tracing::info!("request for graph bounds"); let bounds = response::GraphBounds { mem_upper: state.meminfo_upper_bound.load(Ordering::Relaxed) as u64, mem_current: state.meminfo_current.load(Ordering::Relaxed) as u64, net_upper: state.netinfo_upper_bound.load(Ordering::Relaxed) as u64, cpu_upper: state.cpuinfo_upper_bound.load(Ordering::Relaxed) as u8, }; Response::new(response::Message::GraphBounds) .with_body(bounds) .unwrap() } fn generate_graphs(body: request::GenerateGraphsBody, db: &Database) -> Response { tracing::info!("request to generate graphs"); let root = body.directory; let mem = root.join("current_hostmeminfo.gif"); let net = root.join("current_hostnetinfo.gif"); let cpu = root.join("current_hostcpuinfo.gif"); let meminfo = thread::make_mem_graph(db, &mem).unwrap(); let net_upper = thread::make_net_graph(db, &net).unwrap(); let cpu_upper = thread::make_cpu_graph(db, &cpu).unwrap(); tracing::trace!("made all graphs"); let response_body = response::GeneratedGraphs { meminfo: mem, netinfo: net, cpuinfo: cpu, bounds: response::GraphBounds { mem_upper: meminfo.upper, mem_current: meminfo.current, net_upper: net_upper, cpu_upper: cpu_upper, }, }; Response::new(response::Message::GeneratedGraphs) .with_body(response_body) .unwrap() } fn regular_graphs(state: &State, graph_directory: &Utf8Path) -> Response { tracing::info!("request for regular graphs"); let root = graph_directory; let mem = root.join("current_hostmeminfo.gif"); let net = root.join("current_hostnetinfo.gif"); let cpu = root.join("current_hostcpuinfo.gif"); let response_body = response::GeneratedGraphs { meminfo: mem, netinfo: net, cpuinfo: cpu, bounds: response::GraphBounds { mem_upper: state.meminfo_upper_bound.load(Ordering::Relaxed), mem_current: state.meminfo_current.load(Ordering::Relaxed), net_upper: state.netinfo_upper_bound.load(Ordering::Relaxed), cpu_upper: state.cpuinfo_upper_bound.load(Ordering::Relaxed), }, }; Response::new(response::Message::GeneratedGraphs) .with_body(response_body) .unwrap() }