use std::fs::{File, OpenOptions};
use std::path::Path;
use std::time::Duration;
use fs2::FileExt;
use crate::error::{CoreError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockKind {
Shared,
Exclusive,
}
pub struct FileLock {
file: File,
kind: LockKind,
}
impl FileLock {
fn acquire(path: &Path, kind: LockKind, timeout: Duration) -> Result<Self> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(path)?;
let deadline = std::time::Instant::now() + timeout;
loop {
let acquired = match kind {
LockKind::Shared => file.try_lock_shared().is_ok(),
LockKind::Exclusive => file.try_lock_exclusive().is_ok(),
};
if acquired {
return Ok(Self { file, kind });
}
if std::time::Instant::now() >= deadline {
let secs = timeout.as_secs();
tracing::warn!(lock = %path.display(), "lock acquire timed out");
return Err(CoreError::LockTimeout(secs.max(1)));
}
std::thread::sleep(Duration::from_millis(25));
}
}
pub fn kind(&self) -> LockKind {
self.kind
}
}
impl Drop for FileLock {
fn drop(&mut self) {
let _ = self.file.unlock();
}
}
pub fn acquire(path: &Path, kind: LockKind, timeout: Duration) -> Result<FileLock> {
FileLock::acquire(path, kind, timeout)
}
pub fn is_locked(path: &Path) -> bool {
let Ok(file) = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(path)
else {
return false;
};
file.try_lock_exclusive().is_err()
}