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