Skip to main content

tauri_mcp/tools/
process.rs

1use crate::{Result, TauriMcpError};
2use crossbeam_channel::{bounded, Receiver, Sender};
3use parking_lot::RwLock;
4use serde_json::Value;
5use std::collections::HashMap;
6use std::path::Path;
7use std::process::Stdio;
8use std::sync::Arc;
9use sysinfo::{System, Pid};
10use tokio::io::{AsyncBufReadExt, BufReader};
11use tokio::process::{Child, Command};
12use tokio::task::JoinHandle;
13use tracing::{debug, error, info, warn};
14use uuid::Uuid;
15
16pub struct ProcessManager {
17    processes: HashMap<String, ProcessInfo>,
18    system: Arc<RwLock<System>>,
19}
20
21struct ProcessInfo {
22    id: String,
23    child: Child,
24    pid: u32,
25    log_receiver: Receiver<String>,
26    log_handle: JoinHandle<()>,
27}
28
29impl ProcessManager {
30    pub fn new() -> Self {
31        Self {
32            processes: HashMap::new(),
33            system: Arc::new(RwLock::new(System::new_all())),
34        }
35    }
36    
37    pub async fn launch_app(&mut self, app_path: &str, args: Vec<String>) -> Result<String> {
38        let path = Path::new(app_path);
39        if !path.exists() {
40            return Err(TauriMcpError::ProcessError(format!("App path does not exist: {}", app_path)));
41        }
42        
43        info!("Launching Tauri app: {} with args: {:?}", app_path, args);
44        
45        let mut cmd = Command::new(app_path);
46        cmd.args(args)
47            .stdout(Stdio::piped())
48            .stderr(Stdio::piped())
49            .stdin(Stdio::null());
50        
51        let mut child = cmd.spawn()
52            .map_err(|e| TauriMcpError::ProcessError(format!("Failed to launch app: {}", e)))?;
53        
54        let pid = child.id()
55            .ok_or_else(|| TauriMcpError::ProcessError("Failed to get process ID".to_string()))?;
56        
57        let process_id = Uuid::new_v4().to_string();
58        
59        let (log_sender, log_receiver) = bounded(1000);
60        
61        let stdout = child.stdout.take()
62            .ok_or_else(|| TauriMcpError::ProcessError("Failed to capture stdout".to_string()))?;
63        let stderr = child.stderr.take()
64            .ok_or_else(|| TauriMcpError::ProcessError("Failed to capture stderr".to_string()))?;
65        
66        let log_handle = tokio::spawn(Self::log_reader(stdout, stderr, log_sender));
67        
68        let process_info = ProcessInfo {
69            id: process_id.clone(),
70            child,
71            pid,
72            log_receiver,
73            log_handle,
74        };
75        
76        self.processes.insert(process_id.clone(), process_info);
77        
78        info!("App launched successfully with process ID: {} (PID: {})", process_id, pid);
79        
80        Ok(process_id)
81    }
82    
83    pub async fn stop_app(&mut self, process_id: &str) -> Result<()> {
84        let mut process_info = self.processes.remove(process_id)
85            .ok_or_else(|| TauriMcpError::ProcessError(format!("Process not found: {}", process_id)))?;
86        
87        info!("Stopping app with process ID: {}", process_id);
88        
89        process_info.child.kill().await
90            .map_err(|e| TauriMcpError::ProcessError(format!("Failed to kill process: {}", e)))?;
91        
92        process_info.log_handle.abort();
93        
94        Ok(())
95    }
96    
97    pub async fn get_app_logs(&self, process_id: &str, lines: Option<usize>) -> Result<Vec<String>> {
98        let process_info = self.processes.get(process_id)
99            .ok_or_else(|| TauriMcpError::ProcessError(format!("Process not found: {}", process_id)))?;
100        
101        let mut logs = Vec::new();
102        
103        while let Ok(log) = process_info.log_receiver.try_recv() {
104            logs.push(log);
105        }
106        
107        if let Some(line_count) = lines {
108            let start = logs.len().saturating_sub(line_count);
109            logs = logs[start..].to_vec();
110        }
111        
112        Ok(logs)
113    }
114    
115    pub async fn monitor_resources(&self, process_id: &str) -> Result<Value> {
116        let process_info = self.processes.get(process_id)
117            .ok_or_else(|| TauriMcpError::ProcessError(format!("Process not found: {}", process_id)))?;
118        
119        let mut system = self.system.write();
120        system.refresh_processes();
121        
122        if let Some(process) = system.process(Pid::from_u32(process_info.pid)) {
123            Ok(serde_json::json!({
124                "cpu_usage": process.cpu_usage(),
125                "memory_usage": process.memory(),
126                "virtual_memory": process.virtual_memory(),
127                "disk_usage": {
128                    "read_bytes": process.disk_usage().read_bytes,
129                    "written_bytes": process.disk_usage().written_bytes,
130                },
131                "status": format!("{:?}", process.status()),
132                "start_time": process.start_time(),
133                "run_time": process.run_time(),
134            }))
135        } else {
136            Err(TauriMcpError::ProcessError("Failed to get process info".to_string()))
137        }
138    }
139    
140    async fn log_reader(
141        stdout: tokio::process::ChildStdout,
142        stderr: tokio::process::ChildStderr,
143        sender: Sender<String>,
144    ) {
145        let stdout_reader = BufReader::new(stdout);
146        let stderr_reader = BufReader::new(stderr);
147        
148        let stdout_sender = sender.clone();
149        let stderr_sender = sender;
150        
151        let stdout_handle = tokio::spawn(async move {
152            let mut lines = stdout_reader.lines();
153            while let Ok(Some(line)) = lines.next_line().await {
154                let log_line = format!("[stdout] {}", line);
155                if stdout_sender.send(log_line).is_err() {
156                    break;
157                }
158            }
159        });
160        
161        let stderr_handle = tokio::spawn(async move {
162            let mut lines = stderr_reader.lines();
163            while let Ok(Some(line)) = lines.next_line().await {
164                let log_line = format!("[stderr] {}", line);
165                if stderr_sender.send(log_line).is_err() {
166                    break;
167                }
168            }
169        });
170        
171        let _ = tokio::join!(stdout_handle, stderr_handle);
172    }
173    
174    pub fn get_running_processes(&self) -> Vec<String> {
175        self.processes.keys().cloned().collect()
176    }
177}