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 let _ = remove_lock(&self.spec_id);
30 }
31}
32
33pub fn create_lock(spec_id: &str) -> Result<PathBuf> {
35 let lock_path = get_lock_path(spec_id);
36 fs::create_dir_all(LOCKS_DIR)?;
37 fs::write(&lock_path, format!("{}", std::process::id()))?;
38 Ok(lock_path)
39}
40
41pub fn remove_lock(spec_id: &str) -> Result<()> {
43 let lock_path = get_lock_path(spec_id);
44 if lock_path.exists() {
45 fs::remove_file(&lock_path)?;
46 }
47 Ok(())
48}
49
50pub fn read_lock(spec_id: &str) -> Result<Option<u32>> {
52 let lock_path = get_lock_path(spec_id);
53
54 if !lock_path.exists() {
55 return Ok(None);
56 }
57
58 let content = fs::read_to_string(&lock_path)?;
59 let pid: u32 = content.trim().parse()?;
60 Ok(Some(pid))
61}
62
63pub fn is_locked(spec_id: &str) -> bool {
65 let lock_path = get_lock_path(spec_id);
66 if !lock_path.exists() {
67 return false;
68 }
69
70 match read_lock(spec_id) {
72 Ok(Some(pid)) => is_process_alive(pid),
73 _ => false,
74 }
75}
76
77fn is_process_alive(pid: u32) -> bool {
79 #[cfg(unix)]
80 {
81 use nix::sys::signal::{kill, Signal};
82 use nix::unistd::Pid;
83
84 kill(Pid::from_raw(pid as i32), Signal::try_from(0).ok()).is_ok()
86 }
87 #[cfg(not(unix))]
88 {
89 std::process::Command::new("tasklist")
91 .args(["/FI", &format!("PID eq {}", pid), "/NH"])
92 .output()
93 .map(|o| !String::from_utf8_lossy(&o.stdout).contains("No tasks"))
94 .unwrap_or(false)
95 }
96}
97
98fn get_lock_path(spec_id: &str) -> PathBuf {
100 Path::new(LOCKS_DIR).join(format!("{}.lock", spec_id))
101}