deepseek_rust_cli/tools/
system.rs1use 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 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 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 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 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 to_remove.push(pid);
144 }
145 Ok(None) => {
146 output.push_str(&format!(
148 "PID: {} | Command: '{}' | Status: Running\n",
149 pid, proc_info.cmd
150 ));
151 }
152 Err(_) => {
153 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 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 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}