use std::path::PathBuf;
use clap::Args;
use serde::Serialize;
use crate::output::OutputFormat;
use super::error::DaemonError;
use super::ipc::{check_socket_alive, cleanup_socket, send_command};
use super::pid::{cleanup_stale_pid, compute_pid_path};
use super::types::DaemonCommand;
#[derive(Debug, Clone, Args)]
pub struct DaemonStopArgs {
#[arg(long, short = 'p', default_value = ".")]
pub project: PathBuf,
}
#[derive(Debug, Clone, Serialize)]
pub struct DaemonStopOutput {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl DaemonStopArgs {
pub fn run(&self, format: OutputFormat, quiet: bool) -> anyhow::Result<()> {
let runtime = tokio::runtime::Runtime::new()?;
runtime.block_on(self.run_async(format, quiet))
}
async fn run_async(&self, format: OutputFormat, quiet: bool) -> anyhow::Result<()> {
let project = self.project.canonicalize().unwrap_or_else(|_| {
std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(&self.project)
});
if !check_socket_alive(&project).await {
let output = DaemonStopOutput {
status: "ok".to_string(),
message: Some("Daemon not running".to_string()),
};
if !quiet {
match format {
OutputFormat::Json | OutputFormat::Compact => {
println!("{}", serde_json::to_string_pretty(&output)?);
}
OutputFormat::Text | OutputFormat::Sarif | OutputFormat::Dot => {
println!("Daemon not running");
}
}
}
let pid_path = compute_pid_path(&project);
let _ = cleanup_stale_pid(&pid_path);
let _ = cleanup_socket(&project);
return Ok(());
}
let cmd = DaemonCommand::Shutdown;
match send_command(&project, &cmd).await {
Ok(_response) => {
let mut retries = 0;
while retries < 50 {
if !check_socket_alive(&project).await {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
retries += 1;
}
let _ = cleanup_socket(&project);
let pid_path = compute_pid_path(&project);
let _ = cleanup_stale_pid(&pid_path);
let output = DaemonStopOutput {
status: "ok".to_string(),
message: Some("Daemon stopped".to_string()),
};
if !quiet {
match format {
OutputFormat::Json | OutputFormat::Compact => {
println!("{}", serde_json::to_string_pretty(&output)?);
}
OutputFormat::Text | OutputFormat::Sarif | OutputFormat::Dot => {
println!("Daemon stopped");
}
}
}
Ok(())
}
Err(DaemonError::NotRunning) | Err(DaemonError::ConnectionRefused) => {
let output = DaemonStopOutput {
status: "ok".to_string(),
message: Some("Daemon not running".to_string()),
};
if !quiet {
match format {
OutputFormat::Json | OutputFormat::Compact => {
println!("{}", serde_json::to_string_pretty(&output)?);
}
OutputFormat::Text | OutputFormat::Sarif | OutputFormat::Dot => {
println!("Daemon not running");
}
}
}
let _ = cleanup_socket(&project);
let pid_path = compute_pid_path(&project);
let _ = cleanup_stale_pid(&pid_path);
Ok(())
}
Err(e) => Err(anyhow::anyhow!("Failed to stop daemon: {}", e)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_daemon_stop_args_default() {
let args = DaemonStopArgs {
project: PathBuf::from("."),
};
assert_eq!(args.project, PathBuf::from("."));
}
#[test]
fn test_daemon_stop_output_serialization() {
let output = DaemonStopOutput {
status: "ok".to_string(),
message: Some("Daemon stopped".to_string()),
};
let json = serde_json::to_string(&output).unwrap();
assert!(json.contains("ok"));
assert!(json.contains("Daemon stopped"));
}
#[test]
fn test_daemon_stop_output_not_running() {
let output = DaemonStopOutput {
status: "ok".to_string(),
message: Some("Daemon not running".to_string()),
};
let json = serde_json::to_string(&output).unwrap();
assert!(json.contains("ok"));
assert!(json.contains("not running"));
}
#[tokio::test]
async fn test_daemon_stop_not_running() {
let temp = TempDir::new().unwrap();
let args = DaemonStopArgs {
project: temp.path().to_path_buf(),
};
let result = args.run_async(OutputFormat::Json, true).await;
assert!(result.is_ok());
}
}