reminder_cli/
daemon.rs

1use crate::notification::send_notification;
2use crate::storage::Storage;
3use anyhow::{Context, Result};
4use std::fs;
5use std::process::{Command, Stdio};
6use std::thread;
7use std::time::Duration;
8
9pub fn start_daemon() -> Result<()> {
10    let pid_file = Storage::pid_file_path()?;
11    
12    if is_daemon_running()? {
13        println!("Daemon is already running");
14        return Ok(());
15    }
16
17    let exe = std::env::current_exe()?;
18    
19    let child = Command::new(exe)
20        .arg("daemon")
21        .arg("run")
22        .stdin(Stdio::null())
23        .stdout(Stdio::null())
24        .stderr(Stdio::null())
25        .spawn()
26        .context("Failed to start daemon process")?;
27
28    fs::write(&pid_file, child.id().to_string())?;
29    println!("Daemon started with PID: {}", child.id());
30    
31    Ok(())
32}
33
34pub fn stop_daemon() -> Result<()> {
35    let pid_file = Storage::pid_file_path()?;
36    
37    if !pid_file.exists() {
38        println!("Daemon is not running");
39        return Ok(());
40    }
41
42    let pid_str = fs::read_to_string(&pid_file)?;
43    let pid: i32 = pid_str.trim().parse()?;
44
45    #[cfg(unix)]
46    {
47        let _ = Command::new("kill").arg(pid.to_string()).status();
48    }
49
50    #[cfg(windows)]
51    {
52        let _ = Command::new("taskkill")
53            .args(["/PID", &pid.to_string(), "/F"])
54            .status();
55    }
56
57    fs::remove_file(&pid_file)?;
58    println!("Daemon stopped");
59    
60    Ok(())
61}
62
63pub fn daemon_status() -> Result<()> {
64    if is_daemon_running()? {
65        let pid_file = Storage::pid_file_path()?;
66        let pid = fs::read_to_string(&pid_file)?;
67        println!("Daemon is running (PID: {})", pid.trim());
68    } else {
69        println!("Daemon is not running");
70    }
71    Ok(())
72}
73
74pub fn is_daemon_running() -> Result<bool> {
75    let pid_file = Storage::pid_file_path()?;
76    
77    if !pid_file.exists() {
78        return Ok(false);
79    }
80
81    let pid_str = fs::read_to_string(&pid_file)?;
82    let pid: u32 = match pid_str.trim().parse() {
83        Ok(p) => p,
84        Err(_) => {
85            fs::remove_file(&pid_file)?;
86            return Ok(false);
87        }
88    };
89
90    #[cfg(unix)]
91    {
92        let output = Command::new("kill")
93            .args(["-0", &pid.to_string()])
94            .output();
95        
96        match output {
97            Ok(o) => Ok(o.status.success()),
98            Err(_) => {
99                fs::remove_file(&pid_file)?;
100                Ok(false)
101            }
102        }
103    }
104
105    #[cfg(windows)]
106    {
107        let output = Command::new("tasklist")
108            .args(["/FI", &format!("PID eq {}", pid)])
109            .output();
110        
111        match output {
112            Ok(o) => {
113                let stdout = String::from_utf8_lossy(&o.stdout);
114                Ok(stdout.contains(&pid.to_string()))
115            }
116            Err(_) => {
117                fs::remove_file(&pid_file)?;
118                Ok(false)
119            }
120        }
121    }
122}
123
124pub fn run_daemon_loop() -> Result<()> {
125    let storage = Storage::new()?;
126    
127    loop {
128        let mut reminders = storage.load()?;
129        let mut updated = false;
130
131        for reminder in reminders.iter_mut() {
132            if reminder.is_due() {
133                if let Err(e) = send_notification(reminder) {
134                    eprintln!("Failed to send notification: {}", e);
135                }
136                reminder.calculate_next_trigger();
137                updated = true;
138            }
139        }
140
141        if updated {
142            storage.save(&reminders)?;
143        }
144
145        thread::sleep(Duration::from_secs(30));
146    }
147}