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}