1use crate::paths::EddaPaths;
2use fs2::FileExt;
3use std::fs::{File, OpenOptions};
4
5pub struct WorkspaceLock {
8 _file: File,
9}
10
11impl WorkspaceLock {
12 pub fn acquire(paths: &EddaPaths) -> anyhow::Result<Self> {
15 let file = OpenOptions::new()
16 .create(true)
17 .truncate(false)
18 .read(true)
19 .write(true)
20 .open(&paths.lock_file)
21 .map_err(|e| {
22 anyhow::anyhow!("cannot open lock file {}: {}", paths.lock_file.display(), e)
23 })?;
24
25 file.try_lock_exclusive().map_err(|_| {
26 anyhow::anyhow!(
27 "workspace is locked by another process ({})",
28 paths.lock_file.display()
29 )
30 })?;
31
32 Ok(Self { _file: file })
33 }
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 #[test]
41 fn acquire_and_drop() {
42 let tmp = std::env::temp_dir().join(format!("edda_lock_test_{}", std::process::id()));
43 let _ = std::fs::remove_dir_all(&tmp);
44 let p = EddaPaths::discover(&tmp);
45 p.ensure_layout().unwrap();
46
47 let lock = WorkspaceLock::acquire(&p).unwrap();
48 assert!(WorkspaceLock::acquire(&p).is_err());
50 drop(lock);
51 let _lock2 = WorkspaceLock::acquire(&p).unwrap();
53
54 let _ = std::fs::remove_dir_all(&tmp);
55 }
56}