use std::{fs::File, path::Path, sync::Arc};
struct LockedFileGuardInner(File);
impl Drop for LockedFileGuardInner {
fn drop(&mut self) {
log::debug!("Unlocking database lock");
self.0
.unlock()
.inspect_err(|e| {
log::warn!("Failed to unlock database lock: {e:?}");
})
.ok();
}
}
#[derive(Clone)]
#[expect(unused)]
pub struct LockedFileGuard(Arc<LockedFileGuardInner>);
impl LockedFileGuard {
pub fn create_new(path: &Path) -> crate::Result<Self> {
log::debug!("Acquiring database lock at {}", path.display());
let file = match File::create_new(path) {
Ok(f) => f,
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => File::open(path)?,
e => e?,
};
file.try_lock().map_err(|e| match e {
std::fs::TryLockError::Error(e) => {
log::error!("Failed to acquire database lock - if this is expected, you can try opening again (maybe wait a little)");
crate::Error::Io(e)
}
std::fs::TryLockError::WouldBlock => crate::Error::Locked,
})?;
Ok(Self(Arc::new(LockedFileGuardInner(file))))
}
pub fn try_acquire(path: &Path) -> crate::Result<Self> {
const RETRIES: usize = 3;
log::debug!("Acquiring database lock at {}", path.display());
let file = File::open(path)?;
for i in 1..=RETRIES {
if let Err(e) = file.try_lock() {
match e {
std::fs::TryLockError::Error(e) => {
log::error!("Failed to acquire database lock - if this is expected, you can try opening again (maybe wait a little)");
return Err(crate::Error::Io(e));
}
std::fs::TryLockError::WouldBlock => {
if i == RETRIES {
return Err(crate::Error::Locked);
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
} else {
break;
}
}
Ok(Self(Arc::new(LockedFileGuardInner(file))))
}
}