1use std::collections::HashMap;
7use std::path::PathBuf;
8use std::process::{Child};
9use std::time::SystemTime;
10use serde::{Serialize, Deserialize};
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub enum ProcessStatus {
15 Running,
16 Stopped,
17 Error(String),
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ProcessInfo {
23 pub pid: u32,
24 pub label: String,
25 pub command: String,
26 pub start_time: SystemTime,
27 pub status: ProcessStatus,
28}
29
30#[derive(Debug)]
32pub struct ProcessState {
33 processes: HashMap<u32, ProcessInfo>,
34 state_file: PathBuf,
35}
36
37impl ProcessState {
38 pub fn new<S: AsRef<str>>(namespace: S) -> Result<Self, Box<dyn std::error::Error>> {
40 let state_dir = dirs::data_dir()
41 .unwrap_or_else(|| PathBuf::from("."))
42 .join(namespace.as_ref());
43
44 std::fs::create_dir_all(&state_dir)?;
45 let state_file = state_dir.join("processes.json");
46
47 let mut state = Self {
48 processes: HashMap::new(),
49 state_file,
50 };
51 state.load_state()?;
52 Ok(state)
53 }
54
55 pub fn add_process(&mut self, child: &Child, label: &str, command: &str) -> Result<(), Box<dyn std::error::Error>> {
57 let pid = child.id();
58 let info = ProcessInfo {
59 pid,
60 label: label.to_string(),
61 command: command.to_string(),
62 start_time: SystemTime::now(),
63 status: ProcessStatus::Running,
64 };
65 self.processes.insert(pid, info);
66 self.save_state()?;
67 Ok(())
68 }
69
70 pub fn remove_process(&mut self, pid: u32) -> Result<(), Box<dyn std::error::Error>> {
72 self.processes.remove(&pid);
73 self.save_state()?;
74 Ok(())
75 }
76
77 pub fn update_status(&mut self, pid: u32, status: ProcessStatus) -> Result<(), Box<dyn std::error::Error>> {
79 if let Some(p) = self.processes.get_mut(&pid) {
80 p.status = status;
81 self.save_state()?;
82 }
83 Ok(())
84 }
85
86 pub fn refresh(&mut self) -> Result<(), Box<dyn std::error::Error>> {
90 let mut changed = false;
91
92 for info in self.processes.values_mut() {
93 #[cfg(unix)]
94 let is_running = std::process::Command::new("kill")
95 .arg("-0")
96 .arg(info.pid.to_string())
97 .status()
98 .map(|status| status.success())
99 .unwrap_or(false);
100
101 #[cfg(windows)]
102 let is_running = true; if !is_running && info.status != ProcessStatus::Stopped {
105 info.status = ProcessStatus::Stopped;
106 changed = true;
107 }
108 }
109
110 if changed {
111 self.save_state()?;
112 }
113
114 Ok(())
115 }
116
117 pub fn get_running(&self) -> Vec<&ProcessInfo> {
119 self.processes
120 .values()
121 .filter(|p| p.status == ProcessStatus::Running)
122 .collect()
123 }
124
125 pub fn get_all(&self) -> Vec<&ProcessInfo> {
127 self.processes.values().collect()
128 }
129
130 fn save_state(&self) -> Result<(), Box<dyn std::error::Error>> {
132 let serialized = serde_json::to_string_pretty(&self.processes)?;
133 std::fs::write(&self.state_file, serialized)?;
134 Ok(())
135 }
136
137 fn load_state(&mut self) -> Result<(), Box<dyn std::error::Error>> {
139 if self.state_file.exists() {
140 let content = std::fs::read_to_string(&self.state_file)?;
141 self.processes = serde_json::from_str(&content)?;
142 }
143 Ok(())
144 }
145}