1#![allow(dead_code)]
2use std::{collections::HashMap, fs::OpenOptions, sync::Arc, time::Duration};
3
4use anyhow::Result;
5use tokio::{
6 process::{Child, Command},
7 sync::Mutex,
8 time::timeout,
9};
10
11use crate::{
12 core::watcher::WatchContext,
13 exec::{OutpuStrategy, PipeRegistry, metrics::monitor_process},
14 log::logger::Logger,
15};
16
17pub struct CommandOutput {
18 pub status_code: Option<i32>,
19 pub cpu_usage: f32,
20 pub mem_usage_kb: u64,
21}
22
23pub async fn run_command_with_timeout(
24 program: &str,
25 args: &[String],
26 current_dir: &str,
27 timeout_secs: u64,
28 output: &OutpuStrategy,
29 env: Option<HashMap<String, String>>,
30 pipe_registry: Arc<Mutex<PipeRegistry>>,
31) -> Result<CommandOutput> {
32 let mut cmd = Command::new(program);
34
35 let full = std::iter::once(program.to_string())
36 .chain(args.iter().cloned())
37 .collect();
38
39 cmd.args(args).current_dir(current_dir);
40 output.configure(&mut cmd, full, pipe_registry).await?;
41
42 if let Some(vars) = env {
43 for (k, v) in vars {
44 cmd.env(k, v);
45 }
46 }
47
48 let mut child = cmd.spawn()?;
49
50 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
51 let child_pid = child.id();
52 tokio::spawn(async move {
53 let metrics = monitor_process(child_pid.unwrap_or(1)).await;
54 let _ = tx.send(metrics).await;
55 });
56
57 let duration = Duration::from_secs(timeout_secs);
58
59 let run_future = async {
60 let status = child.wait().await?;
61
62 let (cpu_usage, mem_usage_kb) = rx.recv().await.unwrap_or((0.0, 0));
63 println!("METRICS EXTRACTED => {cpu_usage} | {mem_usage_kb}");
64 anyhow::Ok((status, cpu_usage, mem_usage_kb))
65 };
66
67 match timeout(duration, run_future).await {
68 Ok(Ok((status, cpu_usage, mem_usage_kb))) => Ok(CommandOutput {
69 status_code: status.code(),
70 cpu_usage,
71 mem_usage_kb,
72 }),
73 Ok(Err(e)) => Err(anyhow::anyhow!("Error during execution : {}", e)),
74 Err(_) => {
75 child.kill().await.ok();
76 Err(anyhow::anyhow!(
77 "Command timeout after {} seconds, process killed",
78 timeout_secs
79 ))
80 }
81 }
82}
83
84pub async fn run_command_background(
85 program: &str,
86 args: &[String],
87 current_dir: &str,
88 stdout_file: std::fs::File,
89 stderr_file: std::fs::File,
90 env: Option<HashMap<String, String>>,
91) -> Result<Child> {
92 use std::process::Stdio;
93 let stdout_stdio = Stdio::from(stdout_file);
94 let stderr_stdio = Stdio::from(stderr_file);
95
96 let mut cmd = Command::new(program);
97 cmd.args(args)
98 .current_dir(current_dir)
99 .stdout(stdout_stdio)
100 .stderr(stderr_stdio);
101
102 if let Some(vars) = env {
103 for (k, v) in vars {
104 cmd.env(k, v);
105 }
106 }
107
108 let child = cmd.spawn()?;
109
110 Ok(child)
111}
112
113pub async fn exec_timeout(
114 parts: Vec<String>,
115 ctx: &WatchContext,
116 logger: &Logger,
117 timeout: u64,
118 env: Option<HashMap<String, String>>,
119 output_strategy: &OutpuStrategy,
120 pipe_registry: Arc<Mutex<PipeRegistry>>,
121) -> Result<CommandOutput, anyhow::Error> {
122 let program = &parts[0];
123 let args = &parts[1..];
124
125 match run_command_with_timeout(
126 program,
127 args,
128 &ctx.project_dir,
129 timeout,
130 output_strategy,
131 env,
132 pipe_registry,
133 )
134 .await
135 {
136 Ok(output) => {
137 if output.status_code != Some(0) {
138 logger
139 .error(&format!(
140 "Command failed with exit code {:?}",
141 output.status_code
142 ))
143 .await?;
144 return Err(anyhow::anyhow!("Failed command: {:?}", parts));
145 }
146 logger.info(&format!("Command {program} succeeded")).await?;
147 Ok(output)
148 }
149 Err(e) => {
150 logger
151 .error(&format!("Command error or timeout: {parts:?}"))
152 .await?;
153 logger.error(&e.to_string()).await?;
154 Err(anyhow::anyhow!("**Command error:**: `{parts:?}`\n{e}"))
155 }
156 }
157}
158
159pub async fn exec_background(
160 parts: Vec<String>,
161 ctx: &WatchContext,
162 logger: &Logger,
163 env: Option<HashMap<String, String>>,
164) -> Result<(), anyhow::Error> {
165 let program = &parts[0];
166 let args = &parts[1..];
167 logger
168 .info("Command marked as blocking: running in background without waiting")
169 .await?;
170 let log_path = ctx.log_path();
171
172 let stdout_file = OpenOptions::new()
173 .create(true)
174 .append(true)
175 .open(&log_path)?;
176 let stderr_file = OpenOptions::new()
177 .create(true)
178 .append(true)
179 .open(&log_path)?;
180
181 match run_command_background(
182 program,
183 args,
184 &ctx.project_dir,
185 stdout_file,
186 stderr_file,
187 env,
188 )
189 .await
190 {
191 Ok(_child) => {
192 logger.info("Background command launched").await?;
193 }
194 Err(e) => {
195 logger
196 .error(&format!("Failed to launch background command: {e}"))
197 .await?;
198 return Err(e);
199 }
200 }
201
202 logger.info("Background command launched").await?;
203 Ok(())
204}