intent_engine/dashboard/
daemon.rs1use anyhow::{Context, Result};
2use std::fs;
3use std::path::PathBuf;
4
5pub fn pid_file_path(port: u16) -> PathBuf {
7 let temp_dir = std::env::temp_dir();
9 temp_dir.join(format!("ie-dashboard-{}.pid", port))
10}
11
12pub fn write_pid_file(port: u16, pid: u32) -> Result<()> {
14 let path = pid_file_path(port);
15 fs::write(&path, pid.to_string())
16 .with_context(|| format!("Failed to write PID file: {}", path.display()))?;
17 Ok(())
18}
19
20pub fn read_pid_file(port: u16) -> Result<Option<u32>> {
22 let path = pid_file_path(port);
23
24 if !path.exists() {
25 return Ok(None);
26 }
27
28 let content = fs::read_to_string(&path)
29 .with_context(|| format!("Failed to read PID file: {}", path.display()))?;
30
31 let pid = content
32 .trim()
33 .parse::<u32>()
34 .context("Invalid PID in file")?;
35
36 Ok(Some(pid))
37}
38
39pub fn delete_pid_file(port: u16) -> Result<()> {
41 let path = pid_file_path(port);
42
43 if path.exists() {
44 fs::remove_file(&path)
45 .with_context(|| format!("Failed to delete PID file: {}", path.display()))?;
46 }
47
48 Ok(())
49}
50
51#[cfg(unix)]
53pub fn is_process_running(pid: u32) -> bool {
54 use std::process::Command;
55
56 Command::new("kill")
58 .args(["-0", &pid.to_string()])
59 .output()
60 .map(|output| output.status.success())
61 .unwrap_or(false)
62}
63
64#[cfg(windows)]
65pub fn is_process_running(pid: u32) -> bool {
66 use std::process::Command;
67
68 Command::new("tasklist")
70 .args(["/FI", &format!("PID eq {}", pid)])
71 .output()
72 .map(|output| String::from_utf8_lossy(&output.stdout).contains(&pid.to_string()))
73 .unwrap_or(false)
74}
75
76#[cfg(unix)]
78pub fn stop_process(pid: u32) -> Result<()> {
79 use std::process::Command;
80
81 let output = Command::new("kill")
83 .arg(pid.to_string())
84 .output()
85 .context("Failed to send SIGTERM")?;
86
87 if !output.status.success() {
88 anyhow::bail!(
89 "Failed to stop process {}: {}",
90 pid,
91 String::from_utf8_lossy(&output.stderr)
92 );
93 }
94
95 Ok(())
96}
97
98#[cfg(windows)]
99pub fn stop_process(pid: u32) -> Result<()> {
100 use std::process::Command;
101
102 let output = Command::new("taskkill")
104 .args(["/PID", &pid.to_string(), "/F"])
105 .output()
106 .context("Failed to kill process")?;
107
108 if !output.status.success() {
109 anyhow::bail!(
110 "Failed to stop process {}: {}",
111 pid,
112 String::from_utf8_lossy(&output.stderr)
113 );
114 }
115
116 Ok(())
117}
118
119pub fn open_browser(url: &str) -> Result<()> {
121 #[cfg(target_os = "windows")]
122 {
123 std::process::Command::new("cmd")
124 .args(["/C", "start", url])
125 .spawn()
126 .context("Failed to open browser")?;
127 }
128
129 #[cfg(target_os = "macos")]
130 {
131 std::process::Command::new("open")
132 .arg(url)
133 .spawn()
134 .context("Failed to open browser")?;
135 }
136
137 #[cfg(target_os = "linux")]
138 {
139 std::process::Command::new("xdg-open")
140 .arg(url)
141 .spawn()
142 .context("Failed to open browser")?;
143 }
144
145 Ok(())
146}
147
148#[cfg(unix)]
152pub fn daemonize() -> Result<()> {
153 Ok(())
156}
157
158#[cfg(windows)]
160pub fn daemonize() -> Result<()> {
161 Ok(())
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_pid_file_operations() {
171 let port = 9999;
172 let pid = std::process::id();
173
174 write_pid_file(port, pid).unwrap();
176
177 let read_pid = read_pid_file(port).unwrap();
179 assert_eq!(read_pid, Some(pid));
180
181 delete_pid_file(port).unwrap();
183
184 let read_pid_after = read_pid_file(port).unwrap();
186 assert_eq!(read_pid_after, None);
187 }
188
189 #[test]
190 fn test_is_process_running() {
191 let current_pid = std::process::id();
192
193 assert!(is_process_running(current_pid));
195
196 assert!(!is_process_running(999999));
198 }
199}