|
1 | 1 | use std::fs::{self, File}; |
2 | 2 | use std::path::{Path, PathBuf}; |
3 | 3 |
|
4 | | -#[derive(thiserror::Error, Debug)] |
5 | | -pub enum Error { |
6 | | - #[error("The lock at {0:?} is already acquired by a process with PID {1}.")] |
7 | | - InUse(PathBuf, u32), |
8 | | -} |
9 | | - |
10 | 4 | /// Only one server process can "own" a project at a single time. |
11 | 5 | /// We enforce this exclusive access through the creation and deletion of this lockfile. |
12 | 6 | const LOCKFILE: &'static str = ".server-lock"; |
13 | 7 |
|
14 | | -pub struct ProjectLock<'a> { |
| 8 | +pub struct ProjectLock { |
15 | 9 | file: File, |
16 | | - path: &'a Path, |
| 10 | + path: PathBuf, |
17 | 11 | } |
18 | 12 |
|
19 | | -impl<'a> ProjectLock<'_> { |
| 13 | +impl ProjectLock { |
20 | 14 | /// Attempt to acquire a lock on the provided directory. |
21 | | - pub fn lock(project_path: &'a Path) -> Result<Self, Error> { |
22 | | - todo!() |
| 15 | + pub fn lock(project_path: &Path) -> Option<Self> { |
| 16 | + let lock = project_path.join(LOCKFILE); |
| 17 | + match lock.is_file() { |
| 18 | + true => None, |
| 19 | + false => { |
| 20 | + let file = File::create(&lock).ok()?; |
| 21 | + Some(Self { file, path: lock }) |
| 22 | + } |
| 23 | + } |
23 | 24 | } |
24 | 25 | } |
25 | 26 |
|
26 | | -impl Drop for ProjectLock<'_> { |
| 27 | +impl Drop for ProjectLock { |
27 | 28 | fn drop(&mut self) { |
28 | 29 | // The file handle is dropped before this, so we can safely delete it. |
29 | | - fs::remove_file(self.path); |
| 30 | + fs::remove_file(&self.path).unwrap(); |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +#[cfg(test)] |
| 35 | +mod test { |
| 36 | + use super::*; |
| 37 | + use tempfile::TempDir; |
| 38 | + |
| 39 | + /// Test that project locks behave in the following way: |
| 40 | + /// - Attempting to lock an already locked project MUST return None. |
| 41 | + /// - Attempting to lock a project that is not locked will return a ProjectLock. |
| 42 | + /// - Dropping a ProjectLock will remove the lockfile from the project. |
| 43 | + #[test] |
| 44 | + fn test_project_lock() { |
| 45 | + let temp = TempDir::new().unwrap(); |
| 46 | + let root = temp.path(); |
| 47 | + |
| 48 | + // Acquire a lock on the directory. |
| 49 | + { |
| 50 | + let _lock = |
| 51 | + ProjectLock::lock(root).expect("Failed to acquire lock on empty directory."); |
| 52 | + |
| 53 | + // Attempting to acquire it again will return None. |
| 54 | + let relock = ProjectLock::lock(root); |
| 55 | + assert!(relock.is_none()); |
| 56 | + } |
| 57 | + |
| 58 | + // Now that the previous lock is dropped we *should* be able to re-acquire it. |
| 59 | + let _lock = ProjectLock::lock(root).expect("Failed to acquire lock on empty directory."); |
30 | 60 | } |
31 | 61 | } |
0 commit comments