Skip to main content

deepseek_rust_cli/tools/
system.rs

1use std::{collections::HashMap, process::Stdio, sync::Arc};
2
3use anyhow::Result;
4use tokio::{io::AsyncReadExt, process::Command, sync::Mutex};
5
6pub struct ProcessInfo {
7    pub cmd: String,
8    pub pid: u32,
9    pub logs: Arc<Mutex<String>>,
10    pub child: Arc<Mutex<tokio::process::Child>>,
11}
12
13pub static BACKGROUND_PROCESSES: once_cell::sync::Lazy<Mutex<HashMap<u32, ProcessInfo>>> =
14    once_cell::sync::Lazy::new(|| Mutex::new(HashMap::new()));
15
16pub async fn start_background_process(
17    command: &str,
18    cwd: Option<&str>,
19    env_vars: Option<HashMap<String, String>>,
20) -> Result<String> {
21    let mut cmd = if cfg!(target_os = "windows") {
22        let mut c = Command::new("cmd");
23        c.arg("/C").arg(command);
24        c
25    } else {
26        let mut c = Command::new("sh");
27        c.arg("-c").arg(command);
28        c
29    };
30
31    if let Some(path) = cwd {
32        cmd.current_dir(path);
33    }
34
35    if let Some(vars) = env_vars {
36        cmd.envs(vars);
37    }
38
39    // Pipe stdout and stderr so we can read them
40    cmd.stdout(Stdio::piped());
41    cmd.stderr(Stdio::piped());
42
43    let mut child = cmd.spawn()?;
44    let pid = child.id().unwrap_or(0);
45
46    let stdout_opt = child.stdout.take();
47    let stderr_opt = child.stderr.take();
48
49    let logs = Arc::new(Mutex::new(String::new()));
50    let child_arc = Arc::new(Mutex::new(child));
51
52    // Spawn task to read stdout continuously and write to logs
53    let logs_clone1 = logs.clone();
54    if let Some(mut stdout) = stdout_opt {
55        tokio::spawn(async move {
56            let mut buf = [0u8; 1024];
57            while let Ok(n) = stdout.read(&mut buf).await {
58                if n == 0 {
59                    break;
60                }
61                let text = String::from_utf8_lossy(&buf[..n]);
62                let mut guard = logs_clone1.lock().await;
63                guard.push_str(&text);
64            }
65        });
66    }
67
68    // Spawn task to read stderr continuously and write to logs
69    let logs_clone2 = logs.clone();
70    if let Some(mut stderr) = stderr_opt {
71        tokio::spawn(async move {
72            let mut buf = [0u8; 1024];
73            while let Ok(n) = stderr.read(&mut buf).await {
74                if n == 0 {
75                    break;
76                }
77                let text = String::from_utf8_lossy(&buf[..n]);
78                let mut guard = logs_clone2.lock().await;
79                guard.push_str(&text);
80            }
81        });
82    }
83
84    let mut processes = BACKGROUND_PROCESSES.lock().await;
85    processes.insert(
86        pid,
87        ProcessInfo {
88            cmd: command.to_string(),
89            pid,
90            logs,
91            child: child_arc,
92        },
93    );
94
95    Ok(format!(
96        "Started background process with PID: {} for command: '{}'",
97        pid, command
98    ))
99}
100
101pub async fn read_background_process_logs(pid: u32) -> Result<String> {
102    let processes = BACKGROUND_PROCESSES.lock().await;
103    if let Some(proc_info) = processes.get(&pid) {
104        let logs_guard = proc_info.logs.lock().await;
105        Ok(logs_guard.clone())
106    } else {
107        Err(anyhow::anyhow!(
108            "No background process found with PID: {}",
109            pid
110        ))
111    }
112}
113
114pub async fn kill_background_process(pid: u32) -> Result<String> {
115    let mut processes = BACKGROUND_PROCESSES.lock().await;
116    if let Some(proc_info) = processes.remove(&pid) {
117        let mut child_guard = proc_info.child.lock().await;
118        child_guard.kill().await?;
119        Ok(format!(
120            "Successfully terminated background process with PID: {}",
121            pid
122        ))
123    } else {
124        Err(anyhow::anyhow!(
125            "No background process found with PID: {}",
126            pid
127        ))
128    }
129}
130
131pub async fn list_background_processes() -> Result<String> {
132    let mut processes = BACKGROUND_PROCESSES.lock().await;
133
134    // Clean up completed processes and build output
135    let mut to_remove = Vec::new();
136    let mut output = String::new();
137
138    for (&pid, proc_info) in processes.iter() {
139        let mut child_guard = proc_info.child.lock().await;
140        match child_guard.try_wait() {
141            Ok(Some(_status)) => {
142                // Process has exited
143                to_remove.push(pid);
144            }
145            Ok(None) => {
146                // Process is still running
147                output.push_str(&format!(
148                    "PID: {} | Command: '{}' | Status: Running\n",
149                    pid, proc_info.cmd
150                ));
151            }
152            Err(_) => {
153                // Error querying process, assume dead
154                to_remove.push(pid);
155            }
156        }
157    }
158
159    for pid in to_remove {
160        processes.remove(&pid);
161    }
162
163    if output.is_empty() {
164        Ok("No active background processes.".to_string())
165    } else {
166        Ok(output.trim_end().to_string())
167    }
168}
169
170pub async fn execute_shell_command(
171    command: &str,
172    is_background: bool,
173    cwd: Option<&str>,
174    env_vars: Option<HashMap<String, String>>,
175) -> Result<String> {
176    let mut cmd = if cfg!(target_os = "windows") {
177        let mut c = Command::new("cmd");
178        c.arg("/C").arg(command);
179        c
180    } else {
181        let mut c = Command::new("sh");
182        c.arg("-c").arg(command);
183        c
184    };
185
186    if let Some(path) = cwd {
187        cmd.current_dir(path);
188    }
189
190    if let Some(vars) = env_vars {
191        cmd.envs(vars);
192    }
193
194    if is_background {
195        let mut child = cmd.spawn()?;
196        let pid = child.id().unwrap_or(0);
197        tokio::spawn(async move {
198            let _ = child.wait().await;
199        });
200        Ok(format!("Started background process with PID: {}", pid))
201    } else {
202        let output = cmd.output().await?;
203        Ok(format!(
204            "{}{}",
205            String::from_utf8_lossy(&output.stdout),
206            String::from_utf8_lossy(&output.stderr)
207        ))
208    }
209}
210
211pub fn get_system_info() -> Result<String> {
212    Ok(format!(
213        "OS: {}, Arch: {}",
214        std::env::consts::OS,
215        std::env::consts::ARCH
216    ))
217}
218
219pub async fn check_port_status(port: u16, host: Option<&str>) -> Result<String> {
220    let h = host.unwrap_or("127.0.0.1");
221    let addr = format!("{}:{}", h, port);
222
223    // Try to connect to see if something is listening
224    let connect_res = tokio::time::timeout(
225        std::time::Duration::from_millis(500),
226        tokio::net::TcpStream::connect(&addr),
227    )
228    .await;
229
230    match connect_res {
231        Ok(Ok(_stream)) => Ok(format!(
232            "Port {} is OCCUPIED (something is listening on it).",
233            port
234        )),
235        _ => {
236            // Try to bind to see if it is available for us to listen on, or blocked by system
237            match tokio::net::TcpListener::bind(&addr).await {
238                Ok(_) => Ok(format!("Port {} is FREE (available for use).", port)),
239                Err(e) => Ok(format!("Port {} is BLOCKED or unavailable: {}", port, e)),
240            }
241        }
242    }
243}