tmc_langs_util/file_util/
lock_unix.rs1use super::LockOptions;
4use crate::{
5 error::FileError,
6 file_util::{self, LOCK_FILE_NAME},
7};
8use file_lock::{FileLock, FileOptions};
9use std::{
10 fs::File,
11 path::{Path, PathBuf},
12};
13
14#[derive(Debug)]
16pub struct Lock {
17 pub path: PathBuf,
18 options: LockOptions,
19 lock_file_path: Option<PathBuf>,
20 forget: bool,
21}
22
23impl Lock {
24 pub fn file(path: impl AsRef<Path>, options: LockOptions) -> Result<Self, FileError> {
25 let path = path.as_ref().to_path_buf();
26
27 if matches!(options, LockOptions::ReadCreate | LockOptions::WriteCreate) {
28 if let Some(parent) = path.parent() {
29 file_util::create_dir_all(parent)?;
30 }
31 }
32 Ok(Self {
33 path,
34 options,
35 lock_file_path: None,
36 forget: false,
37 })
38 }
39
40 pub fn dir(path: impl AsRef<Path>, options: LockOptions) -> Result<Self, FileError> {
41 let path = path.as_ref().to_path_buf();
42
43 if matches!(options, LockOptions::ReadCreate | LockOptions::WriteCreate) {
44 file_util::create_dir_all(&path)?;
45 }
46
47 let lock_path = path.join(LOCK_FILE_NAME);
48 let _creator_lock = FileLock::lock(
52 &lock_path,
53 false,
54 FileOptions::new().write(true).create(true),
55 );
56
57 Ok(Self {
58 path,
59 options,
60 lock_file_path: Some(lock_path),
61 forget: false,
62 })
63 }
64
65 pub fn lock(&mut self) -> Result<Guard<'_>, FileError> {
66 log::trace!("locking {}", self.path.display());
67 let path = match &self.lock_file_path {
68 Some(lock_file) => lock_file,
69 None => &self.path,
70 };
71 let lock = match FileLock::lock(path, true, self.options.into_file_options()) {
72 Ok(lock) => {
73 log::trace!("locked {}", path.display());
74 FileOrLock::Lock(lock)
75 }
76 Err(err) => {
77 log::error!("Failed to lock {}: {err}", path.display());
80 let file = self
81 .options
82 .into_open_options()
83 .open(&self.path)
84 .map_err(|e| FileError::FileOpen(path.to_path_buf(), e))?;
85 FileOrLock::File(file)
86 }
87 };
88 Ok(Guard { lock, path })
89 }
90
91 pub fn forget(mut self) {
92 self.forget = true;
93 }
94}
95
96impl Drop for Lock {
97 fn drop(&mut self) {
98 if self.forget {
99 return;
100 }
101
102 if let Some(lock_file_path) = self.lock_file_path.take() {
104 match FileLock::lock(
107 &lock_file_path,
108 false,
109 FileOptions::new().read(true).write(true),
110 ) {
111 Ok(_) => {
112 let _ = file_util::remove_file(&lock_file_path);
113 }
114 Err(err) => {
115 log::warn!(
116 "Failed to remove lock file {}: {err}",
117 lock_file_path.display()
118 );
119 }
120 }
121 }
122 }
123}
124
125#[derive(Debug)]
126pub struct Guard<'a> {
127 lock: FileOrLock,
128 path: &'a Path,
129}
130
131impl Guard<'_> {
132 pub fn get_file(&self) -> &File {
133 match &self.lock {
134 FileOrLock::File(f) => f,
135 FileOrLock::Lock(l) => &l.file,
136 }
137 }
138
139 pub fn get_file_mut(&mut self) -> &mut File {
140 match &mut self.lock {
141 FileOrLock::File(f) => f,
142 FileOrLock::Lock(l) => &mut l.file,
143 }
144 }
145}
146
147impl Drop for Guard<'_> {
148 fn drop(&mut self) {
149 log::trace!("unlocking {}", self.path.display())
150 }
151}
152
153#[derive(Debug)]
154enum FileOrLock {
155 File(File),
156 Lock(FileLock),
157}
158
159impl LockOptions {
160 fn into_file_options(self) -> FileOptions {
161 match self {
162 LockOptions::Read => FileOptions::new().read(true),
163 LockOptions::ReadCreate => FileOptions::new().read(true).create(true),
164 LockOptions::ReadTruncate => FileOptions::new().read(true).create(true).truncate(true),
165 LockOptions::Write => FileOptions::new().read(true).write(true).append(true),
166 LockOptions::WriteCreate => FileOptions::new()
167 .read(true)
168 .write(true)
169 .append(true)
170 .create(true),
171 LockOptions::WriteTruncate => FileOptions::new()
172 .read(true)
173 .write(true)
174 .create(true)
175 .truncate(true),
176 }
177 }
178}
179
180#[cfg(test)]
181mod test {
182 use super::*;
183
184 #[test]
185 fn can_lock_file() {
186 let file = tempfile::NamedTempFile::new().unwrap();
187 let _lock = Lock::file(file.path(), LockOptions::Read).unwrap();
188 }
189
190 #[test]
191 fn can_lock_dir() {
192 let dir = tempfile::tempdir().unwrap();
193 let _lock = Lock::dir(dir.path(), LockOptions::Read).unwrap();
194 }
195
196 #[test]
197 fn can_delete_locked_file() {
198 let file = tempfile::NamedTempFile::new().unwrap();
199 let _lock = Lock::file(file.path(), LockOptions::Read).unwrap();
200 let _delete_lock = Lock::file(file.path(), LockOptions::Write).unwrap();
201 file_util::remove_file(file.path()).unwrap();
202 }
203}