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