1use anyhow::{Result, anyhow};
2use colored::*;
3use fs2::FileExt;
4use std::fs;
5use std::io::{Read, Seek, SeekFrom, Write};
6use std::path::PathBuf;
7
8fn get_lock_path() -> Result<PathBuf> {
9 let home_dir =
10 crate::utils::get_user_home().ok_or_else(|| anyhow!("Could not find home directory."))?;
11 Ok(home_dir.join(".zoi").join("pkgs").join("lock"))
12}
13
14pub fn acquire_lock() -> Result<LockGuard> {
25 if std::env::var("ZOI_SKIP_LOCK").is_ok_and(|v| v == "1") {
26 return Ok(LockGuard::noop());
27 }
28 let lock_path = get_lock_path()?;
29
30 if let Some(parent) = lock_path.parent()
31 && let Err(e) = fs::create_dir_all(parent)
32 {
33 eprintln!(
34 "Warning: could not create lock directory {}: {}",
35 parent.display(),
36 e
37 );
38 }
39
40 let file = fs::OpenOptions::new()
41 .read(true)
42 .write(true)
43 .create(true)
44 .truncate(false)
45 .open(&lock_path)?;
46
47 if file.try_lock_exclusive().is_err() {
48 let mut content = String::new();
49 if let Ok(mut f) = fs::File::open(&lock_path) {
50 let _ = f.read_to_string(&mut content);
51 }
52
53 let pid_info = if !content.trim().is_empty() {
54 format!(" (held by PID {})", content.trim())
55 } else {
56 String::new()
57 };
58
59 eprintln!(
60 "{}: Another Zoi process{} may be running.",
61 "Error".red().bold(),
62 pid_info
63 );
64 eprintln!(
65 "If you are absolutely sure no other Zoi process is running, you can manually remove the lock file:"
66 );
67 eprintln!(" {}", lock_path.display());
68 return Err(anyhow!("Could not acquire lock."));
69 }
70
71 let mut file = file;
72 let _ = file.set_len(0);
73 let _ = file.seek(SeekFrom::Start(0));
74 let _ = write!(file, "{}", std::process::id());
75 let _ = file.flush();
76
77 Ok(LockGuard {
78 path: Some(lock_path),
79 _file: Some(file),
80 })
81}
82
83pub fn release_lock() -> Result<()> {
84 let lock_path = get_lock_path()?;
85 if lock_path.exists() {
86 let _ = fs::remove_file(lock_path);
87 }
88 Ok(())
89}
90
91pub struct LockGuard {
92 path: Option<PathBuf>,
93 _file: Option<fs::File>,
94}
95
96impl LockGuard {
97 pub fn noop() -> Self {
98 Self {
99 path: None,
100 _file: None,
101 }
102 }
103}
104
105impl Drop for LockGuard {
106 fn drop(&mut self) {
107 if let Some(path) = self.path.take() {
108 self._file.take();
109
110 if path.exists()
111 && let Err(e) = fs::remove_file(&path)
112 {
113 debug_assert!(false, "Failed to remove lock file: {}", e);
114 }
115 }
116 }
117}