use std::collections::HashMap;
use std::path::PathBuf;
use std::process::{Child};
use std::time::SystemTime;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ProcessStatus {
Running,
Stopped,
Error(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessInfo {
pub pid: u32,
pub label: String,
pub command: String,
pub start_time: SystemTime,
pub status: ProcessStatus,
}
#[derive(Debug)]
pub struct ProcessState {
processes: HashMap<u32, ProcessInfo>,
state_file: PathBuf,
}
impl ProcessState {
pub fn new<S: AsRef<str>>(namespace: S) -> Result<Self, Box<dyn std::error::Error>> {
let state_dir = dirs::data_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(namespace.as_ref());
std::fs::create_dir_all(&state_dir)?;
let state_file = state_dir.join("processes.json");
let mut state = Self {
processes: HashMap::new(),
state_file,
};
state.load_state()?;
Ok(state)
}
pub fn add_process(&mut self, child: &Child, label: &str, command: &str) -> Result<(), Box<dyn std::error::Error>> {
let pid = child.id();
let info = ProcessInfo {
pid,
label: label.to_string(),
command: command.to_string(),
start_time: SystemTime::now(),
status: ProcessStatus::Running,
};
self.processes.insert(pid, info);
self.save_state()?;
Ok(())
}
pub fn remove_process(&mut self, pid: u32) -> Result<(), Box<dyn std::error::Error>> {
self.processes.remove(&pid);
self.save_state()?;
Ok(())
}
pub fn update_status(&mut self, pid: u32, status: ProcessStatus) -> Result<(), Box<dyn std::error::Error>> {
if let Some(p) = self.processes.get_mut(&pid) {
p.status = status;
self.save_state()?;
}
Ok(())
}
pub fn refresh(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let mut changed = false;
for info in self.processes.values_mut() {
#[cfg(unix)]
let is_running = std::process::Command::new("kill")
.arg("-0")
.arg(info.pid.to_string())
.status()
.map(|status| status.success())
.unwrap_or(false);
#[cfg(windows)]
let is_running = true;
if !is_running && info.status != ProcessStatus::Stopped {
info.status = ProcessStatus::Stopped;
changed = true;
}
}
if changed {
self.save_state()?;
}
Ok(())
}
pub fn get_running(&self) -> Vec<&ProcessInfo> {
self.processes
.values()
.filter(|p| p.status == ProcessStatus::Running)
.collect()
}
pub fn get_all(&self) -> Vec<&ProcessInfo> {
self.processes.values().collect()
}
fn save_state(&self) -> Result<(), Box<dyn std::error::Error>> {
let serialized = serde_json::to_string_pretty(&self.processes)?;
std::fs::write(&self.state_file, serialized)?;
Ok(())
}
fn load_state(&mut self) -> Result<(), Box<dyn std::error::Error>> {
if self.state_file.exists() {
let content = std::fs::read_to_string(&self.state_file)?;
self.processes = serde_json::from_str(&content)?;
}
Ok(())
}
}