1use std::fs::{File, OpenOptions};
4use std::io;
5use std::path::Path;
6
7use fs2::FileExt;
8
9#[derive(Debug)]
11pub struct StoreLock {
12 file: File,
13}
14
15fn open_lockfile(root: &Path) -> io::Result<File> {
16 std::fs::create_dir_all(root)?;
17 OpenOptions::new()
18 .create(true)
19 .truncate(false)
20 .read(true)
21 .write(true)
22 .open(root.join(".lock"))
23}
24
25impl StoreLock {
26 pub fn exclusive(root: &Path) -> io::Result<Self> {
28 let file = open_lockfile(root)?;
29 FileExt::lock_exclusive(&file)?;
30 Ok(Self { file })
31 }
32
33 pub fn shared(root: &Path) -> io::Result<Self> {
35 let file = open_lockfile(root)?;
36 FileExt::lock_shared(&file)?;
37 Ok(Self { file })
38 }
39
40 pub fn try_exclusive(root: &Path) -> io::Result<Self> {
42 let file = open_lockfile(root)?;
43 FileExt::try_lock_exclusive(&file)?;
44 Ok(Self { file })
45 }
46}
47
48impl Drop for StoreLock {
49 fn drop(&mut self) {
50 let _ = FileExt::unlock(&self.file);
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use tempfile::TempDir;
58
59 #[test]
60 fn exclusive_then_shared_release_roundtrip() {
61 let dir = TempDir::new().unwrap();
62 {
63 let _g = StoreLock::exclusive(dir.path()).unwrap();
64 assert!(dir.path().join(".lock").is_file());
65 } let _g = StoreLock::shared(dir.path()).unwrap();
67 }
68
69 #[test]
70 fn try_exclusive_fails_while_held() {
71 let dir = TempDir::new().unwrap();
72 let _held = StoreLock::exclusive(dir.path()).unwrap();
73 assert!(StoreLock::try_exclusive(dir.path()).is_err());
74 }
75}