use std::collections::HashMap;
use std::process::{Child, Command};
#[derive(Debug)]
pub struct ProcessInfo {
pub name: String,
pub pid: u32,
pub status: ProcessStatus,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ProcessStatus {
Running,
Exited(i32),
Error,
}
pub struct ProcessTracker {
processes: HashMap<String, Child>,
}
impl ProcessTracker {
pub fn new() -> Self {
Self {
processes: HashMap::new(),
}
}
pub fn add(&mut self, name: String, child: Child) {
self.processes.insert(name, child);
}
pub fn remove(&mut self, name: &str) -> Option<Child> {
self.processes.remove(name)
}
pub fn len(&self) -> usize {
self.processes.len()
}
pub fn is_empty(&self) -> bool {
self.processes.is_empty()
}
pub fn list(&self) -> Vec<String> {
self.processes.keys().cloned().collect()
}
pub fn get_status_map(&mut self) -> HashMap<String, String> {
let mut status_map = HashMap::new();
let mut to_remove = Vec::new();
for (name, child) in self.processes.iter_mut() {
let pid = child.id();
match child.try_wait() {
Ok(Some(status)) => {
status_map.insert(
name.clone(),
format!("✗ exited (code: {}, was PID: {})", status.code().unwrap_or(-1), pid),
);
to_remove.push(name.clone());
}
Ok(None) => {
status_map.insert(name.clone(), format!("running (PID: {})", pid));
}
Err(_) => {
status_map.insert(name.clone(), format!("✗ error (PID: {})", pid));
to_remove.push(name.clone());
}
}
}
for name in to_remove {
self.processes.remove(&name);
}
status_map
}
pub fn get_processes(&mut self) -> Vec<ProcessInfo> {
let mut processes = Vec::new();
let mut to_remove = Vec::new();
for (name, child) in self.processes.iter_mut() {
let pid = child.id();
let status = match child.try_wait() {
Ok(Some(exit_status)) => {
to_remove.push(name.clone());
ProcessStatus::Exited(exit_status.code().unwrap_or(-1))
}
Ok(None) => ProcessStatus::Running,
Err(_) => {
to_remove.push(name.clone());
ProcessStatus::Error
}
};
processes.push(ProcessInfo {
name: name.clone(),
pid,
status,
});
}
for name in to_remove {
self.processes.remove(&name);
}
processes
}
pub fn cleanup(&mut self) {
if self.processes.is_empty() {
return;
}
println!("\nCleaning up processes...");
for (name, mut child) in self.processes.drain() {
let pid = child.id();
println!(" ⏹ Stopping {} (PID: {})", name, pid);
#[cfg(unix)]
{
let _ = Command::new("kill").arg(pid.to_string()).output();
std::thread::sleep(std::time::Duration::from_millis(200));
match child.try_wait() {
Ok(Some(_)) => {
}
Ok(None) => {
println!(" Force killing {} (PID: {})", name, pid);
let _ = child.kill();
let _ = child.wait();
}
Err(_) => {
let _ = child.kill();
let _ = child.wait();
}
}
}
#[cfg(not(unix))]
{
let _ = child.kill();
let _ = child.wait();
}
}
println!(" ✓ All processes stopped\n");
}
pub fn stop(&mut self, name: &str) -> Result<(), String> {
if let Some(mut child) = self.processes.remove(name) {
let pid = child.id();
#[cfg(unix)]
{
let _ = Command::new("kill").arg(pid.to_string()).output();
std::thread::sleep(std::time::Duration::from_millis(200));
match child.try_wait() {
Ok(Some(_)) => return Ok(()),
Ok(None) => {
let _ = child.kill();
let _ = child.wait();
return Ok(());
}
Err(_) => {
let _ = child.kill();
let _ = child.wait();
return Ok(());
}
}
}
#[cfg(not(unix))]
{
let _ = child.kill();
let _ = child.wait();
return Ok(());
}
}
Err(format!("Process '{}' not found", name))
}
}
impl Default for ProcessTracker {
fn default() -> Self {
Self::new()
}
}
impl Drop for ProcessTracker {
fn drop(&mut self) {
self.cleanup();
}
}