cargo_e/e_processmanager.rs
1// src/e_process_manager.rs
2
3use crate::e_cargocommand_ext::{CargoProcessHandle, CargoProcessResult};
4use crate::e_command_builder::TerminalError;
5use crate::e_target::CargoTarget;
6use crate::Cli;
7use chrono::Local;
8use std::collections::HashMap;
9use std::process::ExitStatus;
10use std::sync::atomic::AtomicUsize;
11use std::sync::mpsc::{self, Receiver, Sender};
12use std::sync::{Arc, Mutex};
13use std::thread::{self, sleep};
14use std::time::{Duration, SystemTime};
15use sysinfo::System;
16// use std::io::Write;
17#[cfg(feature = "tui")]
18use crossterm::{
19 cursor, execute,
20 terminal::{Clear, ClearType},
21};
22use std::io::{self, Write};
23use std::sync::atomic::Ordering;
24#[cfg(unix)]
25use {
26 nix::sys::signal::{kill as nix_kill, Signal},
27 nix::unistd::Pid,
28 std::os::unix::process::ExitStatusExt,
29};
30impl ProcessObserver for ProcessManager {
31 fn on_spawn(&self, pid: u32, handle: CargoProcessHandle) {
32 self.processes
33 .lock()
34 .unwrap()
35 .insert(pid, Arc::new(Mutex::new(handle)));
36 }
37 // let pid = handle.lock().unwrap().pid;
38 // self.processes.lock().unwrap().insert(pid, handle);
39 // Ok(())
40}
41
42// #[cfg(feature = "uses_async")]
43// use tokio::sync::Notify;
44
45// pub static PROCESS_MANAGER: Lazy<ProcessManager> = Lazy::new(ProcessManager::new);
46
47pub trait ProcessObserver: Send + Sync + 'static {
48 fn on_spawn(&self, pid: u32, handle: CargoProcessHandle);
49}
50
51pub struct ProcessManager {
52 signalled_count: AtomicUsize,
53 signal_tx: Sender<()>,
54 processes: Mutex<HashMap<u32, Arc<Mutex<CargoProcessHandle>>>>,
55 // #[cfg(feature = "uses_async")]
56 // notifier: Notify,
57}
58
59impl ProcessManager {
60 pub fn new(_cli: &Cli) -> Arc<Self> {
61 let (tx, rx) = mpsc::channel();
62 let manager = Arc::new(Self {
63 signalled_count: AtomicUsize::new(0),
64 signal_tx: tx.clone(),
65 processes: Mutex::new(HashMap::new()),
66 });
67 ProcessManager::install_handler(Arc::clone(&manager), rx);
68 manager
69 }
70
71 pub fn has_signalled(&self) -> usize {
72 self.signalled_count.load(Ordering::SeqCst)
73 }
74
75 fn install_handler(self_: Arc<Self>, rx: Receiver<()>) {
76 match ctrlc::set_handler({
77 let tx = self_.signal_tx.clone();
78 move || {
79 let _ = tx.send(());
80 }
81 }) {
82 Ok(_) => {
83 println!("ctrlc> Ctrl+C handler installed.");
84 thread::spawn(move || {
85 while rx.recv().is_ok() {
86 self_.signalled_count.fetch_add(1, Ordering::SeqCst);
87 println!(
88 "ctrlc> signal received. {}",
89 self_.signalled_count.load(Ordering::SeqCst)
90 );
91 self_.handle_signal();
92 }
93 });
94 }
95 Err(e) => {
96 eprintln!("ctrlc> Failed to install Ctrl+C handler: {}", e);
97 return;
98 }
99 }
100 }
101
102 fn handle_signal(&self) {
103 println!("ctrlc>");
104 let mut processes = self.processes.lock().unwrap();
105 for (pid, handle) in processes.iter() {
106 println!("ctrlc> Terminating process with PID: {}", pid);
107 if let Ok(mut h) = handle.lock() {
108 let _ = h.kill();
109 }
110 }
111 processes.clear();
112 }
113
114 /// Updates the status line in the terminal.
115 /// When `raw_mode` is true, it uses crossterm commands to clear the current line.
116 /// Otherwise, it uses the carriage return (`\r`) trick.
117 pub fn update_status_line(output: &str, raw_mode: bool) -> io::Result<()> {
118 let mut stdout = io::stdout();
119 if raw_mode {
120 // Move cursor to beginning and clear the current line.
121 #[cfg(feature = "tui")]
122 {
123 execute!(
124 stdout,
125 cursor::MoveToColumn(0),
126 Clear(ClearType::CurrentLine)
127 )?;
128 print!("{}", output);
129 }
130 #[cfg(not(feature = "tui"))]
131 print!("\r{}", output);
132 } else {
133 // In non-raw mode, the \r trick can work.
134 print!("\r{}", output);
135 }
136 stdout.flush()
137 }
138
139 pub fn register(&self, handle: CargoProcessHandle) -> u32 {
140 let pid = handle.pid;
141 self.processes
142 .lock()
143 .unwrap()
144 .insert(pid, Arc::new(Mutex::new(handle)));
145
146 // #[cfg(feature = "uses_async")]
147 // self.notifier.notify_waiters();
148
149 pid
150 }
151
152 pub fn take(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
153 self.processes.lock().unwrap().remove(&pid)
154 }
155
156 pub fn try_wait(&self, pid: u32) -> anyhow::Result<Option<ExitStatus>> {
157 // 1. Lock the processes map just long enough to clone the Arc.
158 let handle_arc = {
159 let processes = self.processes.lock().unwrap();
160 // Clone the Arc to keep the handle in the map while getting your own reference.
161 processes
162 .get(&pid)
163 .ok_or_else(|| anyhow::anyhow!("Process handle with PID {} not found", pid))?
164 .clone()
165 };
166
167 // 2. Lock the individual process handle to perform try_wait.
168 let mut handle = handle_arc.lock().unwrap();
169 // Here, try_wait returns a Result<Option<ExitStatus>, std::io::Error>.
170 // The '?' operator will convert any std::io::Error to anyhow::Error automatically.
171 let status = handle.child.try_wait()?;
172
173 // Return the exit status (or None) wrapped in Ok.
174 Ok(status)
175 }
176
177 pub fn get(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
178 self.processes.lock().unwrap().get(&pid).cloned()
179 }
180
181 pub fn list(&self) -> Vec<u32> {
182 self.processes.lock().unwrap().keys().cloned().collect()
183 }
184
185 pub fn status(&self) {
186 let processes = self.processes.lock().unwrap();
187 if processes.is_empty() {
188 println!("No active cargo processes.");
189 } else {
190 println!("Active processes:");
191 for pid in processes.keys() {
192 println!(" - PID: {}", pid);
193 }
194 }
195 }
196
197 // /// Attempts to kill the process corresponding to the provided PID.
198 // /// Returns Ok(true) if the process was found and successfully killed (or had already exited),
199 // /// Ok(false) if the process was not found or did not exit within the maximum attempts,
200 // /// or an error if something went wrong.
201 // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
202 // // Retrieve a clone of the handle (Arc) for the given PID without removing it.
203 // let handle = {
204 // let processes = self.processes.lock().unwrap();
205 // processes.get(&pid).cloned()
206 // };
207
208 // if let Some(handle) = handle {
209 // eprintln!("Attempting to kill PID: {}", pid);
210
211 // let max_attempts = 3;
212 // let mut attempts = 0;
213 // let mut process_exited = false;
214
215 // loop {
216 // // Lock the process handle for this iteration.
217 // if let Ok(mut h) = handle.lock() {
218 // // Check if the process has already exited.
219 // match h.child.try_wait() {
220 // Ok(Some(status)) => {
221 // eprintln!("Process {} already exited with status: {:?}", pid, status);
222 // process_exited = true;
223 // break;
224 // }
225 // Ok(None) => {
226 // // Process is still running.
227 // if attempts == 0 {
228 // #[cfg(not(target_os = "windows"))] {
229 // eprintln!("Sending initial Ctrl+C signal to PID: {}", pid);
230 // crate::e_runall::send_ctrl_c(&mut h.child)?;
231 // }
232 // #[cfg(target_os = "windows")] {
233 // eprintln!("Sending initial kill signal to PID: {}", pid);
234 // }
235 // } else {
236 // eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
237 // }
238 // // Attempt to kill the process.
239 // if let Err(e) = h.kill() {
240 // eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
241 // }
242 // // Mark that an exit was requested.
243 // h.requested_exit = true;
244 // }
245 // Err(e) => {
246 // eprintln!("Error checking exit status for PID {}: {}", pid, e);
247 // break;
248 // }
249 // }
250 // } else {
251 // eprintln!("Could not lock process handle for PID {}", pid);
252 // break;
253 // }
254
255 // attempts += 1;
256 // // Allow some time for the process to exit.
257 // sleep(Duration::from_millis(2000));
258
259 // // Re-check if the process has exited.
260 // if let Ok(mut h) = handle.lock() {
261 // match h.child.try_wait() {
262 // Ok(Some(status)) => {
263 // eprintln!("Process {} exited with status: {:?}", pid, status);
264 // process_exited = true;
265 // break;
266 // }
267 // Ok(None) => {
268 // eprintln!("Process {} still running after attempt {}.", pid, attempts);
269 // }
270 // Err(e) => {
271 // eprintln!("Error rechecking process {}: {}", pid, e);
272 // break;
273 // }
274 // }
275 // }
276
277 // if attempts >= max_attempts {
278 // eprintln!("Maximum kill attempts reached for PID {}.", pid);
279 // break;
280 // }
281 // }
282
283 // // If the process exited, remove it from the map.
284 // if process_exited {
285 // let mut processes = self.processes.lock().unwrap();
286 // processes.remove(&pid);
287 // eprintln!("Process {} removed from map after exit.", pid);
288 // } else {
289 // eprintln!(
290 // "Process {} did not exit after {} attempts; it remains in the map for future handling.",
291 // pid, attempts
292 // );
293 // }
294 // Ok(process_exited)
295 // } else {
296 // eprintln!("Process handle with PID {} not found.", pid);
297 // Ok(false)
298 // }
299 // }
300
301 /// Attempts to kill the process corresponding to the provided PID.
302 /// Returns Ok(true) if the process was found and exited (even via signal),
303 /// Ok(false) if the process wasn’t found or didn’t exit after trying
304 /// all signals (in which case we drop the handle), or Err if something went wrong.
305 pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
306 // Grab the handle, if any.
307 let handle_opt = {
308 let procs = self.processes.lock().unwrap();
309 procs.get(&pid).cloned()
310 };
311
312 if let Some(handle) = handle_opt {
313 eprintln!("Attempting to kill PID: {}", pid);
314
315 #[cfg(unix)]
316 let signals = [
317 Signal::SIGHUP,
318 Signal::SIGINT,
319 Signal::SIGQUIT,
320 Signal::SIGABRT,
321 Signal::SIGKILL,
322 Signal::SIGALRM,
323 Signal::SIGTERM,
324 ];
325 #[cfg(unix)]
326 let max_attempts = signals.len();
327
328 #[cfg(windows)]
329 let max_attempts = 3; // arbitrary, since Child::kill() is always SIGKILL
330
331 let mut attempts = 0;
332 let mut did_exit = false;
333
334 while attempts < max_attempts {
335 // 1) Check status
336 if let Ok(mut h) = handle.lock() {
337 match h.child.try_wait() {
338 Ok(Some(status)) => {
339 // Child has exited—report how.
340 #[cfg(unix)]
341 {
342 if let Some(sig) = status.signal() {
343 eprintln!("Process {} terminated by signal {}", pid, sig);
344 } else if let Some(code) = status.code() {
345 eprintln!("Process {} exited with code {}", pid, code);
346 } else {
347 eprintln!(
348 "Process {} exited with unknown status: {:?}",
349 pid, status
350 );
351 }
352 }
353 #[cfg(not(unix))]
354 {
355 if let Some(code) = status.code() {
356 eprintln!("Process {} exited with code {}", pid, code);
357 } else {
358 eprintln!(
359 "Process {} exited with unknown status: {:?}",
360 pid, status
361 );
362 }
363 }
364 did_exit = true;
365 break;
366 }
367 Ok(None) => {
368 // Still running → send the next signal
369 #[cfg(unix)]
370 {
371 let sig = signals[attempts];
372 eprintln!(
373 "Attempt {}: sending {:?} to PID {}",
374 attempts + 1,
375 sig,
376 pid
377 );
378 nix_kill(Pid::from_raw(pid as i32), sig)?;
379 }
380 #[cfg(windows)]
381 {
382 eprintln!("Attempt {}: killing PID {}", attempts + 1, pid);
383 if let Err(e) = h.child.kill() {
384 eprintln!("Failed to kill PID {}: {}", pid, e);
385 }
386 }
387 h.requested_exit = true;
388 }
389 Err(e) => {
390 eprintln!("Error checking status for PID {}: {}", pid, e);
391 break;
392 }
393 }
394 } else {
395 eprintln!("Could not lock handle for PID {}", pid);
396 break;
397 }
398
399 attempts += 1;
400 if did_exit {
401 break;
402 }
403
404 // Give it a moment before retrying
405 thread::sleep(Duration::from_secs(2));
406 }
407
408 // Remove the handle so it drops (and on Windows that will kill if still alive)
409 {
410 let mut procs = self.processes.lock().unwrap();
411 procs.remove(&pid);
412 }
413
414 if did_exit {
415 eprintln!("Process {} removed from map after exit.", pid);
416 } else {
417 eprintln!(
418 "Dropped handle for PID {} after {} attempts (process may still be running).",
419 pid, attempts
420 );
421 }
422
423 Ok(did_exit)
424 } else {
425 eprintln!("Process handle with PID {} not found.", pid);
426 Ok(false)
427 }
428 }
429
430 // /// Attempts to kill the process corresponding to the provided PID.
431 // /// Returns Ok(true) if the process was found and successfully killed
432 // /// (or had already exited), Ok(false) if the process was not found
433 // /// or did not exit within the maximum attempts (in which case we drop
434 // /// the handle), or an error if something went wrong.
435 // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
436 // // Grab a clone of the Arc<Mutex<ProcessHandle>> if it exists
437 // let handle_opt = {
438 // let processes = self.processes.lock().unwrap();
439 // processes.get(&pid).cloned()
440 // };
441
442 // if let Some(handle) = handle_opt {
443 // eprintln!("Attempting to kill PID: {}", pid);
444
445 // let max_attempts = 3;
446 // let mut attempts = 0;
447 // let mut process_exited = false;
448
449 // loop {
450 // // 1) Check status / send signal
451 // if let Ok(mut h) = handle.lock() {
452 // match h.child.try_wait() {
453 // Ok(Some(status)) => {
454 // // Already exited
455 // eprintln!(
456 // "Process {} already exited with status: {:?}",
457 // pid, status
458 // );
459 // process_exited = true;
460 // break;
461 // }
462 // Ok(None) => {
463 // // Still running → send signal
464 // if attempts == 0 {
465 // #[cfg(not(target_os = "windows"))]
466 // {
467 // eprintln!("Sending initial Ctrl+C to PID: {}", pid);
468 // crate::e_runall::send_ctrl_c(&mut h.child)?;
469 // }
470 // #[cfg(target_os = "windows")]
471 // {
472 // eprintln!("Sending initial kill to PID: {}", pid);
473 // }
474 // } else {
475 // eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
476 // }
477
478 // if let Err(e) = h.kill() {
479 // eprintln!("Failed to send kill to PID {}: {}", pid, e);
480 // }
481 // h.requested_exit = true;
482 // }
483 // Err(e) => {
484 // eprintln!("Error checking status for PID {}: {}", pid, e);
485 // break;
486 // }
487 // }
488 // } else {
489 // eprintln!("Could not lock handle for PID {}", pid);
490 // break;
491 // }
492
493 // attempts += 1;
494 // if attempts >= max_attempts {
495 // eprintln!("Maximum kill attempts reached for PID {}. Dropping handle.", pid);
496 // break;
497 // }
498
499 // // 2) Wait a bit before re-checking
500 // thread::sleep(Duration::from_millis(2_000));
501
502 // // 3) Re-check exit status
503 // if let Ok(mut h) = handle.lock() {
504 // match h.child.try_wait() {
505 // Ok(Some(status)) => {
506 // eprintln!("Process {} exited with status: {:?}", pid, status);
507 // process_exited = true;
508 // break;
509 // }
510 // Ok(None) => {
511 // eprintln!(
512 // "Process {} still running after attempt {}.",
513 // pid, attempts
514 // );
515 // }
516 // Err(e) => {
517 // eprintln!("Error rechecking process {}: {}", pid, e);
518 // break;
519 // }
520 // }
521 // }
522 // }
523
524 // // Remove the handle (dropping it) whether or not the process exited
525 // {
526 // let mut processes = self.processes.lock().unwrap();
527 // processes.remove(&pid);
528 // }
529
530 // if process_exited {
531 // eprintln!("Process {} removed from map after exit.", pid);
532 // } else {
533 // eprintln!(
534 // "Dropped handle for PID {} after {} attempts (process may still be running).",
535 // pid, attempts
536 // );
537 // }
538
539 // Ok(process_exited)
540 // } else {
541 // eprintln!("Process handle with PID {} not found.", pid);
542 // Ok(false)
543 // }
544 // }
545
546 /// Attempts to kill one process.
547 /// Returns Ok(true) if a process was found and killed, Ok(false) if none found,
548 /// or an error if something went wrong.
549 pub fn kill_one(&self) -> anyhow::Result<bool> {
550 // First, lock the map briefly to pick one process handle.
551 let maybe_entry = {
552 let processes = self.processes.lock().unwrap();
553 // Clone the Arc so that we don’t take ownership from the map.
554 processes
555 .iter()
556 .next()
557 .map(|(&pid, handle)| (pid, handle.clone()))
558 };
559
560 if let Some((pid, handle)) = maybe_entry {
561 eprintln!("Attempting to kill PID: {}", pid);
562
563 // We'll attempt to kill the process up to `max_attempts` times.
564 let max_attempts = 3;
565 let mut attempts = 0;
566 let mut process_exited = false;
567
568 loop {
569 // Lock the process handle for this iteration.
570 if let Ok(mut h) = handle.lock() {
571 // Check if the process has already exited.
572 match h.child.try_wait() {
573 Ok(Some(status)) => {
574 eprintln!("Process {} already exited with status: {:?}", pid, status);
575 process_exited = true;
576 sleep(Duration::from_millis(3_000));
577 break;
578 }
579 Ok(None) => {
580 // Process is still running. On the first attempt, or forcefully on later attempts.
581 if attempts == 0 {
582 eprintln!("Sending initial kill signal to PID: {}", pid);
583 } else {
584 eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
585 }
586 sleep(Duration::from_millis(3_000));
587 // Try to kill the process. Handle errors by printing a debug message.
588 if let Err(e) = h.kill() {
589 eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
590 sleep(Duration::from_millis(3_000));
591 }
592 }
593 Err(e) => {
594 eprintln!("Error checking exit status for PID {}: {}", pid, e);
595 sleep(Duration::from_millis(3_000));
596 break;
597 }
598 }
599 } else {
600 eprintln!("Could not lock process handle for PID {}", pid);
601 sleep(Duration::from_millis(3_000));
602 break;
603 }
604
605 attempts += 1;
606 // Allow some time for the process to exit.
607 sleep(Duration::from_millis(3_000));
608
609 // Check again after the sleep.
610 if let Ok(mut h) = handle.lock() {
611 match h.child.try_wait() {
612 Ok(Some(status)) => {
613 eprintln!("Process {} exited with status: {:?}", pid, status);
614 sleep(Duration::from_millis(3_000));
615 process_exited = true;
616 break;
617 }
618 Ok(None) => {
619 eprintln!("Process {} still running after attempt {}.", pid, attempts);
620 sleep(Duration::from_millis(3_000));
621 }
622 Err(e) => {
623 eprintln!("Error rechecking process {}: {}", pid, e);
624 sleep(Duration::from_millis(3_000));
625 break;
626 }
627 }
628 }
629
630 if attempts >= max_attempts {
631 eprintln!("Maximum kill attempts reached for PID {}.", pid);
632 sleep(Duration::from_millis(3_000));
633 break;
634 }
635 }
636
637 // 4) In all cases, remove the handle so it drops
638 {
639 let mut ps = self.processes.lock().unwrap();
640 ps.remove(&pid);
641 }
642 if process_exited {
643 eprintln!("Process {} removed from map after exit.", pid);
644 } else {
645 eprintln!(
646 "Dropped handle for PID {} after {} attempts (process may still be running).",
647 pid, attempts
648 );
649 }
650 sleep(Duration::from_millis(3_000));
651 Ok(process_exited)
652 } else {
653 println!("No processes to kill.");
654 sleep(Duration::from_millis(3_000));
655 Ok(false)
656 }
657 }
658 // pub fn kill_one(&self) {
659 // let mut processes = self.processes.lock().unwrap();
660 // if let Some((&pid, handle)) = processes.iter().next() {
661 // eprintln!("Killing PID: {}", pid);
662 // if let Ok(mut h) = handle.lock() {
663 // let _ = h.kill();
664 // }
665 // processes.remove(&pid);
666 // } else {
667 // println!("No processes to kill.");
668 // }
669 // }
670
671 pub fn kill_all(&self) {
672 let mut processes = self.processes.lock().unwrap();
673 for (pid, handle) in processes.drain() {
674 eprintln!("Killing PID: {}", pid);
675 if let Ok(mut h) = handle.lock() {
676 let _ = h.kill();
677 }
678 }
679 }
680
681 // Returns the terminal error for a given PID.
682 pub fn get_terminal_error(&self, pid: u32) -> Option<TerminalError> {
683 // Lock the process map
684 let processes = self.processes.lock().unwrap();
685
686 // Check if the process exists
687 if let Some(handle) = processes.get(&pid) {
688 // Lock the handle to access the terminal error flag
689 let handle = handle.lock().unwrap();
690 // Return the terminal error flag value
691 return Some(handle.terminal_error_flag.lock().unwrap().clone());
692 }
693
694 // If no process is found for the given PID, return None
695 None
696 }
697
698 // pub fn install_ctrlc_handler(self: Arc<Self>) {
699 // let manager = Arc::clone(&self);
700 // ctrlc::set_handler(move || {
701 // eprintln!("CTRL-C detected. Killing all processes.");
702 // manager.kill_all();
703 // std::process::exit(1);
704 // })
705 // .expect("Failed to install ctrl-c handler");
706 // }
707
708 /// Wait for the process to finish, show interactive status, then return a result
709 pub fn wait(
710 &self,
711 pid: u32,
712 _duration: Option<Duration>,
713 ) -> anyhow::Result<CargoProcessResult> {
714 // Hide the cursor and ensure it’s restored on exit
715 #[allow(dead_code)]
716 struct CursorHide;
717 impl Drop for CursorHide {
718 fn drop(&mut self) {
719 #[cfg(feature = "tui")]
720 {
721 let _ = crossterm::execute!(std::io::stdout(), crossterm::cursor::Show);
722 }
723 }
724 }
725 #[cfg(feature = "tui")]
726 let _cursor_hide = {
727 let mut out = std::io::stdout();
728 crossterm::execute!(out, crossterm::cursor::Hide)?;
729 CursorHide
730 };
731
732 // 1. Remove the handle from the map
733 let handle_arc = {
734 let mut map = self.processes.lock().unwrap();
735 map.remove(&pid)
736 .ok_or_else(|| anyhow::anyhow!("Process handle with PID {} not found", pid))?
737 };
738
739 // 2. Unwrap Arc<Mutex<...>> to get the handle
740 let mut handle = Arc::try_unwrap(handle_arc)
741 .map_err(|_| anyhow::anyhow!("Process handle for PID {} still shared", pid))?
742 .into_inner()
743 .unwrap();
744
745 // 3. Interactive polling loop
746 let mut system = if handle.is_filter {
747 Some(System::new_all())
748 } else {
749 None
750 };
751 const POLL: Duration = Duration::from_secs(1);
752 let mut loop_cnter = 0;
753 loop {
754 loop_cnter += 1;
755 if handle.is_filter && loop_cnter % 2 == 0 {
756 if let Some(ref mut sys) = system {
757 sys.refresh_all();
758 }
759 }
760
761 if handle.is_filter {
762 if let Some(ref sys) = system {
763 if let Some(process) = sys.process((pid as usize).into()) {
764 let status = handle.format_status(Some(process));
765 if !status.is_empty() {
766 print!("\r{}", status);
767 }
768 }
769 }
770 std::io::stdout().flush().unwrap();
771 }
772
773 if let Some(es) = handle.child.try_wait()? {
774 let final_diagnostics = {
775 let diag_lock = handle.diagnostics.lock().unwrap();
776 diag_lock.clone()
777 };
778 handle.result.diagnostics = final_diagnostics.clone();
779 handle.result.exit_status = Some(es);
780 handle.result.end_time = Some(SystemTime::now());
781 println!(
782 "\nProcess with PID {} finished {:?} {}",
783 pid,
784 es,
785 handle.result.diagnostics.len()
786 );
787 break;
788 }
789 std::thread::sleep(POLL);
790 }
791
792 if handle.is_filter {
793 // 4. Extract diagnostics out of Arc<Mutex<_>>
794 let diagnostics = Arc::try_unwrap(handle.diagnostics)
795 .map(|m| m.into_inner().unwrap())
796 .unwrap_or_else(|arc| arc.lock().unwrap().clone());
797
798 // 5. Move them into the final result
799 handle.result.diagnostics = diagnostics;
800 }
801 Ok(handle.result)
802 }
803
804 // pub fn wait(&self, pid: u32, _duration: Option<Duration>) -> anyhow::Result<CargoProcessResult> {
805 // // Hide the cursor and ensure it is restored on exit.
806 // {
807 // let mut stdout = std::io::stdout();
808 // crossterm::execute!(stdout, crossterm::cursor::Hide)?;
809 // }
810
811 // let mut processes = self.processes.lock().unwrap();
812 // if let Some(handle) = processes.get_mut(&pid) {
813 // let mut handle = handle.lock().unwrap();
814 // let mut system = System::new_all();
815
816 // // Initialize start_time if not already set.
817 // let start_time = handle.result.start_time.unwrap_or_else(|| {
818 // let now = SystemTime::now();
819 // handle.result.start_time = Some(now);
820 // now
821 // });
822
823 // // Main loop.
824 // const POLL_DURATION: Duration = Duration::from_secs(1);
825 // loop {
826 // system.refresh_all();
827 // let maybe_system: Option<&System> = if true { Some(&system) } else { None };
828 // // Get formatted status string.
829 // let output = handle.format_status(maybe_system);
830 // print!("\r{}", output);
831 // std::io::stdout().flush().unwrap();
832
833 // if let Some(status) = handle.child.try_wait()? {
834 // handle.result.exit_status = Some(status);
835 // handle.result.end_time = Some(SystemTime::now());
836 // println!("\nProcess with PID {} finished", pid);
837 // return Ok(handle.result.clone());
838 // }
839 // std::thread::sleep(POLL_DURATION);
840 // }
841 // } else {
842 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
843 // }
844 // }
845
846 // pub fn wait(&self, pid: u32, _duration: Option<Duration>) -> anyhow::Result<CargoProcessResult> {
847 // // Turn off (hide) the cursor.
848 // {
849 // let mut stdout = std::io::stdout();
850 // crossterm::execute!(stdout, crossterm::cursor::Hide)?;
851 // }
852 // // Ensure the cursor is shown when we exit.
853 // let _cursor_guard = CursorGuard;
854
855 // let mut processes = self.processes.lock().unwrap();
856 // if let Some(handle) = processes.get_mut(&pid) {
857 // let mut handle = handle.lock().unwrap();
858 // let mut system = System::new_all();
859
860 // // Define the poll duration constant (adjust as needed).
861 // const POLL_DURATION: Duration = Duration::from_secs(1);
862
863 // // Initialize start_time if not already set.
864 // let start_time = handle.result.start_time.unwrap_or_else(|| {
865 // let now = SystemTime::now();
866 // handle.result.start_time = Some(now);
867 // now
868 // });
869 // // Format the start time with seconds precision.
870 // let start_dt: chrono::DateTime<Local> = start_time.into();
871 // let start_str = start_dt.format("%H:%M:%S").to_string();
872 // // Use ANSI color for the start time.
873 // let colored_start = nu_ansi_term::Color::Green.paint(&start_str).to_string();
874 // // Plain version for spacing calculations.
875 // let plain_start = start_str;
876
877 // loop {
878 // system.refresh_all();
879 // let now = SystemTime::now();
880 // let runtime_duration = now.duration_since(start_time).unwrap();
881 // let runtime_str = crate::e_fmt::format_duration(runtime_duration);
882
883 // // Use usize conversion with into()
884 // if let Some(process) = system.process((pid as usize).into()) {
885 // let cpu_usage = process.cpu_usage();
886 // let mem_kb = process.memory();
887 // let mem_human = if mem_kb >= 1024 {
888 // format!("{:.2} MB", mem_kb as f64 / 1024.0)
889 // } else {
890 // format!("{} KB", mem_kb)
891 // };
892
893 // let left_display = format!("{} | CPU: {:.2}% | Mem: {}", colored_start, cpu_usage, mem_human);
894 // let left_plain = format!("{} | CPU: {:.2}% | Mem: {}", plain_start, cpu_usage, mem_human);
895
896 // // Get terminal width with crossterm.
897 // let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
898 // let total_width = cols as usize;
899 // // Right side: the elapsed duration, underlined.
900 // let right_display = nu_ansi_term::Style::new()
901 // .reset_before_style()
902 // .underline()
903 // .paint(&runtime_str)
904 // .to_string();
905 // let left_len = left_plain.len();
906 // let right_len = runtime_str.len();
907 // let padding = if total_width > left_len + right_len {
908 // total_width - left_len - right_len
909 // } else {
910 // 1
911 // };
912
913 // let output = format!("\r{}{}{}", left_display, " ".repeat(padding), right_display);
914 // print!("{}", output);
915 // std::io::stdout().flush().unwrap();
916 // } else {
917 // print!("\rProcess with PID {} not found in sysinfo", pid);
918 // std::io::stdout().flush().unwrap();
919 // }
920
921 // if let Some(status) = handle.child.try_wait()? {
922 // handle.result.exit_status = Some(status);
923 // handle.result.end_time = Some(SystemTime::now());
924 // println!("\nProcess with PID {} finished", pid);
925 // return Ok(handle.result.clone());
926 // }
927
928 // std::thread::sleep(POLL_DURATION);
929 // }
930 // } else {
931 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
932 // }
933 // }
934
935 // pub fn wait(&self, pid: u32, max_polls: Option<usize>) -> anyhow::Result<CargoProcessResult> {
936 // let mut processes = self.processes.lock().unwrap();
937 // if let Some(handle) = processes.get_mut(&pid) {
938 // let mut handle = handle.lock().unwrap();
939 // let mut system = System::new_all();
940
941 // // Initialize start_time if not already set.
942 // let start_time = handle.result.start_time.unwrap_or_else(|| {
943 // let now = SystemTime::now();
944 // handle.result.start_time = Some(now);
945 // now
946 // });
947 // // Format the start time with more precision.
948 // let start_dt: chrono::DateTime<Local> = start_time.into();
949 // let start_str = start_dt.format("%H:%M:%S").to_string();
950
951 // let mut polls = 0;
952 // loop {
953 // system.refresh_all();
954
955 // if let Some(process) = system.process((pid as usize).into()) {
956 // let now = SystemTime::now();
957 // let runtime = now.duration_since(start_time).unwrap();
958 // let runtime_str = Self::format_duration(runtime);
959
960 // // Get memory usage and convert to a human-readable string.
961 // let mem_kb = process.memory();
962 // let mem_human = if mem_kb >= 1024 {
963 // format!("{:.2} MB", mem_kb as f64 / 1024.0)
964 // } else {
965 // format!("{} KB", mem_kb)
966 // };
967
968 // // Build the output string.
969 // let output = format!(
970 // "{} | Runtime: {} | Mem: {} | CPU: {:.2}%%",
971 // start_str,
972 // runtime_str,
973 // mem_human,
974 // process.cpu_usage()
975 // );
976 // // Print on one line and pad to clear leftover characters.
977 // print!("\r{:<80}", output);
978 // std::io::stdout().flush().unwrap();
979 // } else {
980 // print!("\rProcess with PID {} not found in sysinfo", pid);
981 // std::io::stdout().flush().unwrap();
982 // }
983
984 // // Check if the process has finished.
985 // if let Some(status) = handle.child.try_wait()? {
986 // handle.result.exit_status = Some(status);
987 // handle.result.end_time = Some(SystemTime::now());
988 // println!("\nProcess with PID {} finished", pid);
989 // return Ok(handle.result.clone());
990 // }
991
992 // polls += 1;
993 // if let Some(max) = max_polls {
994 // if polls >= max {
995 // println!("\nReached maximum polling iterations ({})", max);
996 // break;
997 // }
998 // }
999 // std::thread::sleep(Duration::from_secs(1));
1000 // }
1001 // Err(anyhow::anyhow!("Process did not finish after maximum polls"))
1002 // } else {
1003 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1004 // }
1005 // }
1006
1007 // pub fn wait(&self, pid: u32) -> anyhow::Result<CargoProcessResult> {
1008 // let mut processes = self.processes.lock().unwrap();
1009 // if let Some(handle) = processes.get_mut(&pid) {
1010 // let mut handle = handle.lock().unwrap();
1011
1012 // loop {
1013 // println!("Waiting for process with PID: {}", pid);
1014
1015 // let status = handle.child.try_wait()?;
1016
1017 // if let Some(status) = status {
1018 // handle.result.exit_status = Some(status);
1019 // handle.result.end_time = Some(SystemTime::now());
1020 // println!("Process with PID {} finished", pid);
1021 // return Ok(handle.result.clone());
1022 // }
1023
1024 // std::thread::sleep(std::time::Duration::from_secs(1));
1025 // }
1026 // } else {
1027 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1028 // }
1029 // }
1030
1031 pub fn format_process_status(
1032 pid: u32,
1033 handle: &Arc<Mutex<CargoProcessHandle>>,
1034 system: &System,
1035 target: &CargoTarget,
1036 target_dimensions: (usize, usize),
1037 ) -> String {
1038 // Assume start_time has been initialized.
1039 let start_time = handle
1040 .clone()
1041 .lock()
1042 .expect("Failed to lock handle")
1043 .start_time;
1044 let start_dt: chrono::DateTime<Local> = start_time.into();
1045 let start_str = start_dt.format("%H:%M:%S").to_string();
1046 let colored_start = nu_ansi_term::Color::LightCyan.paint(&start_str).to_string();
1047 let plain_start = start_str;
1048
1049 // Refresh the system stats and look up the process.
1050 if let Some(process) = system.process((pid as usize).into()) {
1051 let cpu_usage = process.cpu_usage();
1052 let mem_kb = process.memory();
1053 let mem_human = if mem_kb >= 1024 {
1054 format!("{:.2} MB", mem_kb as f64 / 1024.0)
1055 } else {
1056 format!("{} KB", mem_kb)
1057 };
1058
1059 // Calculate runtime.
1060 let now = SystemTime::now();
1061 let runtime_duration = now.duration_since(start_time).unwrap();
1062 let runtime_str = crate::e_fmt::format_duration(runtime_duration);
1063 // compute the max number of digits in either dimension:
1064 let max_digits = target_dimensions
1065 .0
1066 .max(target_dimensions.1)
1067 .to_string()
1068 .len();
1069 let left_display = format!(
1070 "{:0width$}of{:0width$} | {} | {} | PID: {} | CPU: {:.2}% | Mem: {}",
1071 target_dimensions.0,
1072 target_dimensions.1,
1073 nu_ansi_term::Color::Green
1074 .paint(target.display_name.clone())
1075 .to_string(),
1076 colored_start,
1077 pid,
1078 cpu_usage,
1079 mem_human,
1080 width = max_digits,
1081 );
1082 let left_plain = format!(
1083 "{:0width$}of{:0width$} | {} | {} | PID: {} | CPU: {:.2}% | Mem: {}",
1084 target_dimensions.0,
1085 target_dimensions.1,
1086 target.display_name,
1087 plain_start,
1088 pid,
1089 cpu_usage,
1090 mem_human,
1091 width = max_digits,
1092 );
1093
1094 // Get terminal width.
1095 #[cfg(feature = "tui")]
1096 let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
1097 #[cfg(not(feature = "tui"))]
1098 let (cols, _) = (80, 20);
1099 let total_width = cols as usize;
1100
1101 // Format the runtime with underlining.
1102 let right_display = nu_ansi_term::Style::new()
1103 .reset_before_style()
1104 .underline()
1105 .paint(&runtime_str)
1106 .to_string();
1107 let left_len = left_plain.len();
1108 let right_len = runtime_str.len();
1109 let padding = if total_width > left_len + right_len {
1110 total_width - left_len - right_len
1111 } else {
1112 1
1113 };
1114
1115 format!("{}{}{}", left_display, " ".repeat(padding), right_display)
1116 } else {
1117 String::new()
1118 }
1119 }
1120
1121 /// Print the exact diagnostic output as captured.
1122 pub fn print_exact_output(&self) {
1123 let processes = self.processes.lock().unwrap();
1124 for handle in processes.iter() {
1125 println!("--- Full Diagnostics for PID {} ---", handle.0);
1126 let handle_lock = handle.1.lock().unwrap();
1127 let diags = handle_lock.diagnostics.lock().unwrap();
1128 for diag in diags.iter() {
1129 // Print the entire diagnostic.
1130 println!("{:?}: {}", diag.level, diag.message);
1131 }
1132 }
1133 }
1134
1135 /// Print a one‑line summary per warning, numbered with leading zeros.
1136 pub fn print_prefixed_summary(&self) {
1137 // 1. Grab a snapshot of the handles (Arc clones) under the manager lock.
1138 let guard = self.processes.lock().unwrap();
1139 let handles: Vec<_> = guard.iter().map(|h| h.clone()).collect();
1140
1141 // 2. Now we can iterate without holding the manager lock.
1142 for handle in handles {
1143 // Lock only the diagnostics for this handle.
1144 let handle_lock = handle.1.lock().unwrap();
1145 let diags = handle_lock.diagnostics.lock().unwrap();
1146
1147 // Collect warnings.
1148 let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1149
1150 // Determine width for zero-padding.
1151 let width = warnings.len().to_string().len().max(1);
1152 println!(
1153 "\n\n--- Warnings for PID {} --- {} {}",
1154 handle.0,
1155 width,
1156 warnings.len()
1157 );
1158
1159 for (i, diag) in warnings.iter().enumerate() {
1160 // Format the index with leading zeros.
1161 let index = format!("{:0width$}", i + 1, width = width);
1162 // Print the warning with the index.
1163 println!("{}: {}", index, diag.message.trim());
1164 }
1165 }
1166 }
1167
1168 /// file:line:col – source_line, colored by level.
1169 pub fn print_compact(&self) {
1170 let processes = self.processes.lock().unwrap();
1171 for handle in processes.iter() {
1172 println!("--- Compact for PID {} ---", handle.0);
1173 let handle_lock = handle.1.lock().unwrap();
1174 let diags = handle_lock.diagnostics.lock().unwrap();
1175 for diag in diags.iter() {
1176 println!("{}: {} {}", diag.level, diag.lineref, diag.message.trim());
1177 }
1178 }
1179 }
1180 /// Print a shortened version: warnings first then errors.
1181 pub fn print_shortened_output(&self) {
1182 let processes = self.processes.lock().unwrap();
1183 for handle in processes.iter() {
1184 println!("\n\n\n--- Summary for PID {} ---", handle.0);
1185 let handle_lock = handle.1.lock().unwrap();
1186 let diags = handle_lock.diagnostics.lock().unwrap();
1187
1188 // Filter diagnostics for warnings and errors.
1189 let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1190 let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1191
1192 // Print warnings.
1193 if !warnings.is_empty() {
1194 println!("Warnings:");
1195 for diag in warnings {
1196 println!("print_shortened_output:{}", diag.message.trim());
1197 }
1198 } else {
1199 println!("No warnings.");
1200 }
1201
1202 // Print errors.
1203 if !errors.is_empty() {
1204 println!("Errors:");
1205 for diag in errors {
1206 println!("print_shortened_output: {}", diag.message.trim());
1207 }
1208 } else {
1209 println!("No errors.");
1210 }
1211 }
1212 }
1213}
1214
1215// #[cfg(feature = "uses_async")]
1216// impl ProcessManager {
1217// pub async fn wait_for_processes(&self) {
1218// loop {
1219// {
1220// if self.processes.lock().unwrap().is_empty() {
1221// break;
1222// }
1223// }
1224// self.notifier.notified().await;
1225// }
1226// }
1227// }
1228
1229pub struct CursorGuard;
1230
1231impl Drop for CursorGuard {
1232 fn drop(&mut self) {
1233 #[cfg(feature = "tui")]
1234 {
1235 let mut stdout = std::io::stdout();
1236 let _ = crossterm::execute!(stdout, crossterm::cursor::Show);
1237 }
1238 }
1239}