use std::fs::File;
use std::path::{Path, PathBuf};
use crate::{Error, Result};
pub fn default_lock_dir(app_name: &str) -> PathBuf {
#[cfg(target_os = "linux")]
{
let base = std::env::var("XDG_RUNTIME_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/tmp"));
base.join(app_name)
}
#[cfg(not(target_os = "linux"))]
{
let base = std::env::var("TMPDIR")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::temp_dir());
base.join(app_name)
}
}
#[derive(Debug, Clone)]
pub struct Lockfile {
path: PathBuf,
}
impl Lockfile {
pub fn new(app_name: &str, dir: &Path) -> Self {
Self {
path: dir.join(format!("{app_name}.lock")),
}
}
pub fn default_for(app_name: &str) -> Result<Self> {
let dir = default_lock_dir(app_name);
std::fs::create_dir_all(&dir)?;
Ok(Self::new(app_name, &dir))
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn try_acquire(&self) -> Result<LockGuard> {
if let Some(parent) = self.path.parent() {
std::fs::create_dir_all(parent)?;
}
let file = File::options()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&self.path)?;
file.try_lock().map_err(|e| {
Error::Lock(std::io::Error::new(
std::io::Error::from(e).kind(),
format!("another instance holds the lock: {}", self.path.display()),
))
})?;
tracing::debug!(path = %self.path.display(), "lock acquired");
Ok(LockGuard {
_file: file,
path: self.path.clone(),
})
}
}
#[derive(Debug)]
pub struct LockGuard {
_file: File,
path: PathBuf,
}
impl LockGuard {
pub fn path(&self) -> &Path {
&self.path
}
}
impl Drop for LockGuard {
fn drop(&mut self) {
tracing::debug!(path = %self.path.display(), "lock released");
}
}