use std::{ io::{BufRead, BufReader}, process::{Child, Command, Stdio}, sync::{Arc, Mutex}, thread, }; use crate::logs::LogBuffer; #[derive(Debug, Clone, PartialEq)] pub enum ServiceStatus { Stopped, Starting, Running, Failed(String), } impl ServiceStatus { pub fn label(&self) -> &str { match self { ServiceStatus::Stopped => "Stopped", ServiceStatus::Starting => "Starting…", ServiceStatus::Running => "Running", ServiceStatus::Failed(_) => "Failed", } } pub fn color(&self) -> egui::Color32 { match self { ServiceStatus::Running => egui::Color32::from_rgb(80, 200, 120), ServiceStatus::Starting => egui::Color32::from_rgb(255, 200, 0), ServiceStatus::Failed(_) => egui::Color32::from_rgb(220, 60, 60), ServiceStatus::Stopped => egui::Color32::from_rgb(150, 150, 150), } } } pub struct ServiceHandle { child: Option, pub status: Arc>, } impl ServiceHandle { pub fn new() -> Self { Self { child: None, status: Arc::new(Mutex::new(ServiceStatus::Stopped)), } } pub fn start( &mut self, binary: &str, env_pairs: &[(String, String)], log_buf: Arc>, ) -> anyhow::Result<()> { if self.is_running() { return Ok(()); } *self.status.lock().unwrap() = ServiceStatus::Starting; let mut cmd = Command::new(binary); for (k, v) in env_pairs { cmd.env(k, v); } cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); let mut child = cmd.spawn().map_err(|e| { *self.status.lock().unwrap() = ServiceStatus::Failed(e.to_string()); e })?; // Drain stdout if let Some(stdout) = child.stdout.take() { let buf = Arc::clone(&log_buf); let status = Arc::clone(&self.status); thread::spawn(move || { *status.lock().unwrap() = ServiceStatus::Running; for line in BufReader::new(stdout).lines().map_while(Result::ok) { buf.lock().unwrap().push(line); } }); } // Drain stderr if let Some(stderr) = child.stderr.take() { let buf = Arc::clone(&log_buf); thread::spawn(move || { for line in BufReader::new(stderr).lines().map_while(Result::ok) { buf.lock().unwrap().push(line); } }); } self.child = Some(child); Ok(()) } pub fn stop(&mut self) { if let Some(mut child) = self.child.take() { let _ = child.kill(); let _ = child.wait(); } *self.status.lock().unwrap() = ServiceStatus::Stopped; } pub fn is_running(&mut self) -> bool { if let Some(child) = &mut self.child { match child.try_wait() { Ok(Some(_)) => { // process exited self.child = None; *self.status.lock().unwrap() = ServiceStatus::Stopped; false } Ok(None) => true, Err(_) => false, } } else { false } } pub fn status(&self) -> ServiceStatus { self.status.lock().unwrap().clone() } } impl Drop for ServiceHandle { fn drop(&mut self) { self.stop(); } }