proc_cli/commands/
stop.rs1use crate::core::{parse_targets, resolve_targets, Process};
11use crate::error::{ProcError, Result};
12use crate::ui::{OutputFormat, Printer};
13use clap::Args;
14use dialoguer::Confirm;
15use serde::Serialize;
16
17#[derive(Args, Debug)]
19pub struct StopCommand {
20 #[arg(required = true)]
22 target: String,
23
24 #[arg(long, short = 'y')]
26 yes: bool,
27
28 #[arg(long)]
30 dry_run: bool,
31
32 #[arg(long, short)]
34 json: bool,
35
36 #[arg(long, short, default_value = "10")]
38 timeout: u64,
39}
40
41impl StopCommand {
42 pub fn execute(&self) -> Result<()> {
44 let format = if self.json {
45 OutputFormat::Json
46 } else {
47 OutputFormat::Human
48 };
49 let printer = Printer::new(format, false);
50
51 let targets = parse_targets(&self.target);
53 let (processes, not_found) = resolve_targets(&targets);
54
55 for target in ¬_found {
57 printer.warning(&format!("Target not found: {}", target));
58 }
59
60 if processes.is_empty() {
61 return Err(ProcError::ProcessNotFound(self.target.clone()));
62 }
63
64 if self.dry_run {
66 printer.warning(&format!(
67 "Dry run: would stop {} process{}",
68 processes.len(),
69 if processes.len() == 1 { "" } else { "es" }
70 ));
71 printer.print_processes(&processes);
72 return Ok(());
73 }
74
75 if !self.yes && !self.json {
77 self.show_processes(&processes);
78
79 let prompt = format!(
80 "Stop {} process{}?",
81 processes.len(),
82 if processes.len() == 1 { "" } else { "es" }
83 );
84
85 if !Confirm::new()
86 .with_prompt(prompt)
87 .default(false)
88 .interact()?
89 {
90 printer.warning("Aborted");
91 return Ok(());
92 }
93 }
94
95 let mut stopped = Vec::new();
97 let mut failed = Vec::new();
98
99 for proc in &processes {
100 match proc.terminate() {
101 Ok(()) => {
102 let stopped_gracefully = self.wait_for_exit(proc);
104 if stopped_gracefully {
105 stopped.push(proc.clone());
106 } else {
107 match proc.kill_and_wait() {
109 Ok(_) => stopped.push(proc.clone()),
110 Err(e) => failed.push((proc.clone(), e.to_string())),
111 }
112 }
113 }
114 Err(e) => failed.push((proc.clone(), e.to_string())),
115 }
116 }
117
118 if self.json {
120 printer.print_json(&StopOutput {
121 action: "stop",
122 success: failed.is_empty(),
123 stopped_count: stopped.len(),
124 failed_count: failed.len(),
125 stopped: &stopped,
126 failed: &failed
127 .iter()
128 .map(|(p, e)| FailedStop {
129 process: p,
130 error: e,
131 })
132 .collect::<Vec<_>>(),
133 });
134 } else {
135 self.print_results(&printer, &stopped, &failed);
136 }
137
138 Ok(())
139 }
140
141 fn wait_for_exit(&self, proc: &Process) -> bool {
142 let start = std::time::Instant::now();
143 let timeout = std::time::Duration::from_secs(self.timeout);
144
145 while start.elapsed() < timeout {
146 if !proc.is_running() {
147 return true;
148 }
149 std::thread::sleep(std::time::Duration::from_millis(100));
150 }
151
152 false
153 }
154
155 fn show_processes(&self, processes: &[Process]) {
156 use colored::*;
157
158 println!(
159 "\n{} Found {} process{}:\n",
160 "!".yellow().bold(),
161 processes.len().to_string().cyan().bold(),
162 if processes.len() == 1 { "" } else { "es" }
163 );
164
165 for proc in processes {
166 println!(
167 " {} {} [PID {}] - {:.1}% CPU, {:.1} MB",
168 "→".bright_black(),
169 proc.name.white().bold(),
170 proc.pid.to_string().cyan(),
171 proc.cpu_percent,
172 proc.memory_mb
173 );
174 }
175 println!();
176 }
177
178 fn print_results(&self, printer: &Printer, stopped: &[Process], failed: &[(Process, String)]) {
179 use colored::*;
180
181 if !stopped.is_empty() {
182 println!(
183 "{} Stopped {} process{}",
184 "✓".green().bold(),
185 stopped.len().to_string().cyan().bold(),
186 if stopped.len() == 1 { "" } else { "es" }
187 );
188 for proc in stopped {
189 println!(
190 " {} {} [PID {}]",
191 "→".bright_black(),
192 proc.name.white(),
193 proc.pid.to_string().cyan()
194 );
195 }
196 }
197
198 if !failed.is_empty() {
199 printer.error(&format!(
200 "Failed to stop {} process{}",
201 failed.len(),
202 if failed.len() == 1 { "" } else { "es" }
203 ));
204 for (proc, err) in failed {
205 println!(
206 " {} {} [PID {}]: {}",
207 "→".bright_black(),
208 proc.name.white(),
209 proc.pid.to_string().cyan(),
210 err.red()
211 );
212 }
213 }
214 }
215}
216
217#[derive(Serialize)]
218struct StopOutput<'a> {
219 action: &'static str,
220 success: bool,
221 stopped_count: usize,
222 failed_count: usize,
223 stopped: &'a [Process],
224 failed: &'a [FailedStop<'a>],
225}
226
227#[derive(Serialize)]
228struct FailedStop<'a> {
229 process: &'a Process,
230 error: &'a str,
231}