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