1use anyhow::Result;
7use std::fs;
8use std::path::{Path, PathBuf};
9
10use crate::paths::LOCKS_DIR;
11
12pub struct LockGuard {
14 spec_id: String,
15}
16
17impl LockGuard {
18 pub fn new(spec_id: &str) -> Result<Self> {
20 create_lock(spec_id)?;
21 Ok(Self {
22 spec_id: spec_id.to_string(),
23 })
24 }
25}
26
27impl Drop for LockGuard {
28 fn drop(&mut self) {
29 if let Err(e) = remove_lock(&self.spec_id) {
30 eprintln!(
31 "Warning: Failed to remove lock file for spec {}: {}",
32 self.spec_id, e
33 );
34 }
35 }
36}
37
38pub fn create_lock(spec_id: &str) -> Result<PathBuf> {
40 let lock_path = get_lock_path(spec_id);
41 fs::create_dir_all(LOCKS_DIR)?;
42 fs::write(&lock_path, format!("{}", std::process::id()))?;
43 Ok(lock_path)
44}
45
46pub fn remove_lock(spec_id: &str) -> Result<()> {
48 let lock_path = get_lock_path(spec_id);
49 if lock_path.exists() {
50 fs::remove_file(&lock_path)?;
51 }
52 Ok(())
53}
54
55pub fn read_lock(spec_id: &str) -> Result<Option<u32>> {
57 let lock_path = get_lock_path(spec_id);
58
59 if !lock_path.exists() {
60 return Ok(None);
61 }
62
63 let content = fs::read_to_string(&lock_path)?;
64 let pid: u32 = content.trim().parse()?;
65 Ok(Some(pid))
66}
67
68pub fn is_locked(spec_id: &str) -> bool {
70 let lock_path = get_lock_path(spec_id);
71 if !lock_path.exists() {
72 return false;
73 }
74
75 match read_lock(spec_id) {
77 Ok(Some(pid)) => is_process_alive(pid),
78 _ => false,
79 }
80}
81
82fn is_process_alive(pid: u32) -> bool {
84 #[cfg(unix)]
85 {
86 use nix::sys::signal::{kill, Signal};
87 use nix::unistd::Pid;
88
89 kill(Pid::from_raw(pid as i32), Signal::try_from(0).ok()).is_ok()
91 }
92 #[cfg(not(unix))]
93 {
94 std::process::Command::new("tasklist")
96 .args(["/FI", &format!("PID eq {}", pid), "/NH"])
97 .output()
98 .map(|o| !String::from_utf8_lossy(&o.stdout).contains("No tasks"))
99 .unwrap_or(false)
100 }
101}
102
103fn get_lock_path(spec_id: &str) -> PathBuf {
105 Path::new(LOCKS_DIR).join(format!("{}.lock", spec_id))
106}