1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//! Handler for `trusty-search stop`.
use crate::daemon_port_path;
use anyhow::Result;
use colored::Colorize;
/// Why: extracted from `main()`. Stopping involves PID-file lookup, SIGTERM,
/// and a poll loop — clearer in its own function.
/// What: reads `~/.local/share/trusty-search/daemon.lock` for the PID, sends
/// SIGTERM, then waits up to 5 s for the daemon's port file to disappear.
/// Exits 1 if there's no PID file or if SIGTERM fails to deliver.
/// Test: with a running daemon → "Daemon stopped" within 5 s.
pub async fn handle_stop() -> Result<()> {
// The daemon writes its PID into the fs4 lockfile at startup
// (see trusty-search-service/src/daemon.rs). Read the PID, send
// SIGTERM, then poll for the port file to disappear as a signal
// that shutdown completed cleanly.
let lock_path = dirs::data_local_dir().map(|d| d.join("trusty-search").join("daemon.lock"));
let port_path = daemon_port_path();
let pid = lock_path
.as_ref()
.and_then(|p| std::fs::read_to_string(p).ok())
.and_then(|s| s.trim().parse::<u32>().ok());
match pid {
None => {
eprintln!("{} No daemon running (no PID file)", "✗".red());
std::process::exit(1);
}
Some(pid) => {
println!("{} Stopping daemon (PID {})…", "⟳".cyan(), pid);
let status = std::process::Command::new("kill")
.arg("-TERM")
.arg(pid.to_string())
.status();
match status {
Ok(s) if s.success() => {
// Poll up to 5s for the port file to disappear.
for _ in 0..50 {
std::thread::sleep(std::time::Duration::from_millis(100));
if port_path.as_ref().map(|p| !p.exists()).unwrap_or(true) {
println!("{} Daemon stopped", "✓".green());
return Ok(());
}
}
println!("{} Daemon may still be shutting down", "⚠".yellow());
}
_ => {
eprintln!(
"{} Failed to send SIGTERM (process may already be gone)",
"✗".red()
);
std::process::exit(1);
}
}
}
}
Ok(())
}