1use std::fs::{File, OpenOptions, TryLockError};
13use std::path::{Path, PathBuf};
14
15use crate::error::LinkError;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum LockMode {
20 Shared,
22 Exclusive,
24}
25
26pub fn lock_file_path<P: AsRef<Path>>(database_filename: P) -> PathBuf {
28 let path = database_filename.as_ref();
29 let mut name = path
30 .file_name()
31 .map(|n| n.to_os_string())
32 .unwrap_or_default();
33 name.push(".lock");
34 match path.parent() {
35 Some(parent) if !parent.as_os_str().is_empty() => parent.join(name),
36 _ => PathBuf::from(name),
37 }
38}
39
40#[derive(Debug)]
46pub struct FileLock {
47 file: File,
48 path: PathBuf,
49 mode: LockMode,
50}
51
52impl FileLock {
53 pub fn acquire<P: AsRef<Path>>(lock_path: P, mode: LockMode) -> Result<Self, LinkError> {
55 let (file, path) = Self::open(lock_path)?;
56 let result = match mode {
57 LockMode::Shared => file.lock_shared(),
58 LockMode::Exclusive => file.lock(),
59 };
60 result.map_err(|error| {
61 LinkError::Lock(format!("failed to lock {}: {error}", path.display()))
62 })?;
63 Ok(Self { file, path, mode })
64 }
65
66 pub fn try_acquire<P: AsRef<Path>>(
69 lock_path: P,
70 mode: LockMode,
71 ) -> Result<Option<Self>, LinkError> {
72 let (file, path) = Self::open(lock_path)?;
73 let result = match mode {
74 LockMode::Shared => file.try_lock_shared(),
75 LockMode::Exclusive => file.try_lock(),
76 };
77 match result {
78 Ok(()) => Ok(Some(Self { file, path, mode })),
79 Err(TryLockError::WouldBlock) => Ok(None),
80 Err(TryLockError::Error(error)) => Err(LinkError::Lock(format!(
81 "failed to lock {}: {error}",
82 path.display()
83 ))),
84 }
85 }
86
87 pub fn path(&self) -> &Path {
89 &self.path
90 }
91
92 pub fn mode(&self) -> LockMode {
94 self.mode
95 }
96
97 fn open<P: AsRef<Path>>(lock_path: P) -> Result<(File, PathBuf), LinkError> {
98 let path = lock_path.as_ref().to_path_buf();
99 if let Some(parent) = path.parent() {
100 if !parent.as_os_str().is_empty() && !parent.exists() {
101 std::fs::create_dir_all(parent)?;
102 }
103 }
104 let file = OpenOptions::new()
105 .create(true)
106 .truncate(false)
107 .read(true)
108 .write(true)
109 .open(&path)?;
110 Ok((file, path))
111 }
112}
113
114impl Drop for FileLock {
115 fn drop(&mut self) {
116 let _ = self.file.unlock();
117 }
118}