tldr_cli/commands/daemon/
stop.rs1use std::path::PathBuf;
12
13use clap::Args;
14use serde::Serialize;
15
16use crate::output::OutputFormat;
17
18use super::daemon_active::remove_active;
19use super::error::DaemonError;
20use super::ipc::{check_socket_alive, cleanup_socket, send_command};
21use super::pid::{cleanup_stale_pid, compute_pid_path};
22use super::types::DaemonCommand;
23
24#[derive(Debug, Clone, Args)]
30pub struct DaemonStopArgs {
31 #[arg(long, short = 'p', default_value = ".")]
33 pub project: PathBuf,
34}
35
36#[derive(Debug, Clone, Serialize)]
42pub struct DaemonStopOutput {
43 pub status: String,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub message: Option<String>,
48}
49
50impl DaemonStopArgs {
55 pub fn run(&self, format: OutputFormat, quiet: bool) -> anyhow::Result<()> {
57 let runtime = tokio::runtime::Runtime::new()?;
59 runtime.block_on(self.run_async(format, quiet))
60 }
61
62 async fn run_async(&self, format: OutputFormat, quiet: bool) -> anyhow::Result<()> {
64 let project = self.project.canonicalize().unwrap_or_else(|_| {
66 std::env::current_dir()
67 .unwrap_or_else(|_| PathBuf::from("."))
68 .join(&self.project)
69 });
70
71 if !check_socket_alive(&project).await {
73 let output = DaemonStopOutput {
75 status: "ok".to_string(),
76 message: Some("Daemon not running".to_string()),
77 };
78
79 if !quiet {
80 match format {
81 OutputFormat::Json | OutputFormat::Compact => {
82 println!("{}", serde_json::to_string_pretty(&output)?);
83 }
84 OutputFormat::Text | OutputFormat::Sarif | OutputFormat::Dot => {
85 println!("Daemon not running");
86 }
87 }
88 }
89
90 let pid_path = compute_pid_path(&project);
92 let _ = cleanup_stale_pid(&pid_path);
93 let _ = cleanup_socket(&project);
94 let _ = remove_active();
95
96 return Ok(());
97 }
98
99 let cmd = DaemonCommand::Shutdown;
101 match send_command(&project, &cmd).await {
102 Ok(_response) => {
103 let mut retries = 0;
105 while retries < 50 {
106 if !check_socket_alive(&project).await {
108 break;
109 }
110 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
111 retries += 1;
112 }
113
114 let _ = cleanup_socket(&project);
116 let pid_path = compute_pid_path(&project);
117 let _ = cleanup_stale_pid(&pid_path);
118 let _ = remove_active();
119
120 let output = DaemonStopOutput {
121 status: "ok".to_string(),
122 message: Some("Daemon stopped".to_string()),
123 };
124
125 if !quiet {
126 match format {
127 OutputFormat::Json | OutputFormat::Compact => {
128 println!("{}", serde_json::to_string_pretty(&output)?);
129 }
130 OutputFormat::Text | OutputFormat::Sarif | OutputFormat::Dot => {
131 println!("Daemon stopped");
132 }
133 }
134 }
135
136 Ok(())
137 }
138 Err(DaemonError::NotRunning) | Err(DaemonError::ConnectionRefused) => {
139 let output = DaemonStopOutput {
141 status: "ok".to_string(),
142 message: Some("Daemon not running".to_string()),
143 };
144
145 if !quiet {
146 match format {
147 OutputFormat::Json | OutputFormat::Compact => {
148 println!("{}", serde_json::to_string_pretty(&output)?);
149 }
150 OutputFormat::Text | OutputFormat::Sarif | OutputFormat::Dot => {
151 println!("Daemon not running");
152 }
153 }
154 }
155
156 let _ = cleanup_socket(&project);
158 let pid_path = compute_pid_path(&project);
159 let _ = cleanup_stale_pid(&pid_path);
160 let _ = remove_active();
161
162 Ok(())
163 }
164 Err(e) => Err(anyhow::anyhow!("Failed to stop daemon: {}", e)),
165 }
166 }
167}
168
169#[cfg(test)]
174mod tests {
175 use super::*;
176 use tempfile::TempDir;
177
178 #[test]
179 fn test_daemon_stop_args_default() {
180 let args = DaemonStopArgs {
181 project: PathBuf::from("."),
182 };
183
184 assert_eq!(args.project, PathBuf::from("."));
185 }
186
187 #[test]
188 fn test_daemon_stop_output_serialization() {
189 let output = DaemonStopOutput {
190 status: "ok".to_string(),
191 message: Some("Daemon stopped".to_string()),
192 };
193
194 let json = serde_json::to_string(&output).unwrap();
195 assert!(json.contains("ok"));
196 assert!(json.contains("Daemon stopped"));
197 }
198
199 #[test]
200 fn test_daemon_stop_output_not_running() {
201 let output = DaemonStopOutput {
202 status: "ok".to_string(),
203 message: Some("Daemon not running".to_string()),
204 };
205
206 let json = serde_json::to_string(&output).unwrap();
207 assert!(json.contains("ok"));
208 assert!(json.contains("not running"));
209 }
210
211 #[tokio::test]
212 async fn test_daemon_stop_not_running() {
213 let temp = TempDir::new().unwrap();
214 let args = DaemonStopArgs {
215 project: temp.path().to_path_buf(),
216 };
217
218 let result = args.run_async(OutputFormat::Json, true).await;
220 assert!(result.is_ok());
221 }
222}