1use crate::libs::config::Config;
17use crate::libs::data_storage::DataStorage;
18use crate::libs::messages::Message;
19use crate::libs::monitor::Monitor;
20use crate::{msg_bail_anyhow, msg_error, msg_error_anyhow, msg_info, msg_warning};
21use anyhow::Result;
22use std::time::Duration;
23use tracing::{debug, info, instrument, warn};
24
25const PID_FILE: &str = "kasl-watch.pid";
28
29#[instrument]
33pub async fn run_with_signal_handling() -> Result<()> {
34 info!("Starting daemon with signal handling");
35
36 let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
39
40 #[cfg(unix)]
43 {
44 tokio::spawn(async move {
45 use tokio::signal::unix::{SignalKind, signal};
46
47 let mut sigterm = signal(SignalKind::terminate()).unwrap_or_else(|_| panic!("{}", Message::FailedToCreateSigtermHandler));
49 let mut sigint = signal(SignalKind::interrupt()).unwrap_or_else(|_| panic!("{}", Message::FailedToCreateSigintHandler));
50
51 tokio::select! {
53 _ = sigterm.recv() => {
54 msg_info!(Message::WatcherReceivedSigterm);
55 }
56 _ = sigint.recv() => {
57 msg_info!(Message::WatcherReceivedSigint);
58 }
59 }
60
61 let _ = shutdown_tx.send(());
63 });
64 }
65
66 #[cfg(windows)]
67 {
68 tokio::spawn(async move {
69 match tokio::signal::ctrl_c().await {
71 Ok(()) => {
72 msg_info!(Message::WatcherReceivedCtrlC);
73 }
74 Err(e) => {
75 msg_error!(Message::WatcherCtrlCListenFailed(e.to_string()));
76 }
77 }
78
79 let _ = shutdown_tx.send(());
81 });
82 }
83
84 #[cfg(not(any(unix, windows)))]
85 {
86 msg_warning!(Message::WatcherSignalHandlingNotSupported);
89 }
90
91 let monitor_handle = tokio::spawn(async move {
94 match run_monitor().await {
95 Ok(()) => Ok(()),
96 Err(e) => Err(Message::MonitorError(e.to_string())),
97 }
98 });
99
100 let inbox_handle = tokio::spawn(async move {
102 crate::libs::jira_inbox::run_poller().await;
103 });
104
105 tokio::select! {
108 result = monitor_handle => {
109 inbox_handle.abort();
111 match result {
112 Ok(Ok(())) => msg_info!(Message::MonitorExitedNormally),
113 Ok(Err(e)) => msg_error!(Message::MonitorError(e.to_string())),
114 Err(e) => msg_error!(Message::MonitorTaskPanicked(e.to_string())),
115 }
116 }
117 _ = shutdown_rx => {
118 inbox_handle.abort();
120 msg_info!(Message::MonitorShuttingDown);
121 }
123 }
124
125 let pid_path = DataStorage::new().get_path(PID_FILE)?;
128 if pid_path.exists() {
129 let _ = std::fs::remove_file(&pid_path);
130 }
131
132 Ok(())
133}
134
135async fn run_monitor() -> Result<()> {
137 let config = Config::read()?;
138 let monitor_config = config.monitor.unwrap_or_default();
139
140 let mut monitor = Monitor::new(monitor_config)?;
141 monitor.run().await
142}
143
144#[instrument]
160pub fn spawn() -> Result<()> {
161 debug!("Attempting to spawn daemon process");
162 let pid_path = DataStorage::new().get_path(PID_FILE)?;
163
164 if pid_path.exists()
167 && let Ok(pid_str) = std::fs::read_to_string(&pid_path)
168 {
169 msg_info!(Message::WatcherStoppingExisting(pid_str.trim().to_string()));
170
171 if let Err(e) = stop_internal() {
173 msg_warning!(Message::WatcherFailedToStopExisting(e.to_string()));
174 let _ = std::fs::remove_file(&pid_path);
176 }
177
178 std::thread::sleep(Duration::from_millis(1000));
180 }
181
182 let current_exe = std::env::current_exe().unwrap_or_else(|_| panic!("{}", Message::FailedToGetCurrentExecutable.to_string()));
184
185 #[cfg(unix)]
186 {
187 use std::os::unix::process::CommandExt;
188
189 let mut command = std::process::Command::new(current_exe);
191 command.arg("--daemon-run");
192 unsafe {
195 command.pre_exec(|| {
196 nix::unistd::setsid()?;
199 Ok(())
200 });
201 }
202 let child = command.spawn()?;
203
204 let pid = child.id();
205 std::fs::write(pid_path, pid.to_string())?;
206 msg_info!(Message::WatcherStarted(pid));
207 }
208
209 #[cfg(windows)]
210 {
211 use std::os::windows::process::CommandExt;
212
213 const CREATE_NO_WINDOW: u32 = 0x08000000;
215
216 let child = std::process::Command::new(current_exe)
218 .arg("--daemon-run")
219 .creation_flags(CREATE_NO_WINDOW)
220 .spawn()?;
221
222 let pid = child.id();
223 std::fs::write(pid_path, pid.to_string())?;
224 msg_info!(Message::WatcherStarted(pid));
225 }
226
227 #[cfg(not(any(unix, windows)))]
228 {
229 msg_bail_anyhow!(Message::DaemonModeNotSupported);
231 }
232
233 Ok(())
234}
235
236pub fn is_running() -> bool {
238 let pid_path = match DataStorage::new().get_path(PID_FILE) {
239 Ok(path) => path,
240 Err(_) => return false,
241 };
242
243 if !pid_path.exists() {
245 return false;
246 }
247
248 let pid_str = match std::fs::read_to_string(&pid_path) {
250 Ok(content) => content,
251 Err(_) => return false,
252 };
253
254 let pid: u32 = match pid_str.trim().parse() {
255 Ok(pid) => pid,
256 Err(_) => return false,
257 };
258
259 is_process_running(pid)
261}
262
263fn is_process_running(pid: u32) -> bool {
265 #[cfg(windows)]
266 {
267 use winapi::um::errhandlingapi::GetLastError;
268 use winapi::um::handleapi::CloseHandle;
269 use winapi::um::processthreadsapi::OpenProcess;
270 use winapi::um::winnt::PROCESS_QUERY_INFORMATION;
271
272 unsafe {
273 let handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
274 if handle.is_null() {
275 let error = GetLastError();
276 return error != 87;
278 }
279 CloseHandle(handle);
280 true
281 }
282 }
283
284 #[cfg(unix)]
285 {
286 use std::process::Command;
287
288 match Command::new("ps").arg("-p").arg(pid.to_string()).output() {
290 Ok(output) => output.status.success(),
291 Err(_) => false,
292 }
293 }
294
295 #[cfg(not(any(unix, windows)))]
296 {
297 false
299 }
300}
301
302pub fn stop() -> Result<()> {
315 match stop_internal() {
316 Ok(()) => Ok(()),
317 Err(e) => {
318 if e.to_string().contains("not found") || e.to_string().contains("not running") {
321 msg_info!(Message::WatcherNotRunning);
322 Ok(())
323 } else {
324 Err(e)
325 }
326 }
327 }
328}
329
330fn stop_internal() -> Result<()> {
333 let pid_path = DataStorage::new().get_path(PID_FILE)?;
334
335 let pid_str = match std::fs::read_to_string(&pid_path) {
339 Ok(content) => content,
340 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
341 msg_bail_anyhow!(Message::WatcherNotRunningPidNotFound);
342 }
343 Err(e) => return Err(e.into()),
344 };
345 let pid: u32 = pid_str.trim().parse().map_err(|_| msg_error_anyhow!(Message::InvalidPidFileContent))?;
346
347 let killed = kill_process(pid)?;
349
350 if let Err(e) = std::fs::remove_file(&pid_path)
353 && e.kind() != std::io::ErrorKind::NotFound
354 {
355 return Err(e.into());
356 }
357
358 if killed {
359 msg_info!(Message::WatcherStopped(pid));
360 } else {
361 msg_info!(Message::WatcherNotRunning);
364 }
365 Ok(())
366}
367
368#[cfg(windows)]
372fn kill_process(pid: u32) -> Result<bool> {
373 use winapi::um::errhandlingapi::GetLastError;
374 use winapi::um::handleapi::CloseHandle;
375 use winapi::um::processthreadsapi::{OpenProcess, TerminateProcess};
376 use winapi::um::winnt::PROCESS_TERMINATE;
377
378 unsafe {
379 let handle = OpenProcess(PROCESS_TERMINATE, 0, pid);
381 if handle.is_null() {
382 let error = GetLastError();
383 if error == 87 {
384 return Ok(false);
386 }
387 msg_bail_anyhow!(Message::FailedToOpenProcess(error));
388 }
389
390 let result = TerminateProcess(handle, 0);
392
393 CloseHandle(handle);
395
396 if result == 0 {
397 let error = GetLastError();
399 msg_bail_anyhow!(Message::FailedToTerminateProcess(error));
400 } else {
401 std::thread::sleep(Duration::from_millis(100));
403 Ok(true)
404 }
405 }
406}
407
408#[cfg(unix)]
412fn kill_process(pid: u32) -> Result<bool> {
413 use std::process::Command;
414
415 let output = Command::new("ps").arg("-p").arg(pid.to_string()).output()?;
417
418 if !output.status.success() {
419 return Ok(false);
421 }
422
423 Command::new("kill").arg("-TERM").arg(pid.to_string()).output()?;
425
426 for _ in 0..10 {
428 std::thread::sleep(Duration::from_millis(100));
429
430 let check = Command::new("ps").arg("-p").arg(pid.to_string()).output()?;
432
433 if !check.status.success() {
434 return Ok(true);
436 }
437 }
438
439 Command::new("kill").arg("-9").arg(pid.to_string()).output()?;
441
442 std::thread::sleep(Duration::from_millis(100));
444 Ok(true)
445}
446
447#[cfg(not(any(unix, windows)))]
448fn kill_process(_pid: u32) -> Result<bool> {
449 msg_bail_anyhow!(Message::ProcessTerminationNotSupported);
450}