use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::sync::Arc;
use codeq::error_context_ext::ErrorContextExt;
use fs2::FileExt;
use log::info;
use crate::Config;
#[derive(Debug)]
pub(crate) struct FileLock {
config: Arc<Config>,
f: File,
}
impl FileLock {
pub const LOCK_FILE_NAME: &'static str = "LOCK";
pub(crate) fn new(config: Arc<Config>) -> Result<Self, io::Error> {
let path = Self::lock_path(config.as_ref());
let f = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&path)
.context(|| format!("create lock file '{}'", path))?;
f.try_lock_exclusive().map_err(|e| {
io::Error::new(
io::ErrorKind::WouldBlock,
format!(
"Directory '{}' is already locked by another process, \
shutdown other process to continue; \
error:({})",
config.dir, e
),
)
})?;
info!(
"Directory lock acquired: {}",
Self::lock_path(config.as_ref())
);
Ok(Self { config, f })
}
pub(crate) fn lock_path(config: &Config) -> String {
format!("{}/{}", config.dir, Self::LOCK_FILE_NAME)
}
}
impl Drop for FileLock {
fn drop(&mut self) {
let _ = fs2::FileExt::unlock(&self.f);
info!(
"Directory lock released: {}",
Self::lock_path(self.config.as_ref())
);
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::Config;
use crate::file_lock::FileLock;
#[test]
fn test_lock_file() {
let temp_dir = tempfile::tempdir().unwrap();
let p = temp_dir.path();
let p = p.to_str().unwrap().to_string();
let config = Config {
dir: p,
..Default::default()
};
let config = Arc::new(config);
let lf = FileLock::new(config.clone()).unwrap();
println!("Directory locked successfully");
let lf2 = FileLock::new(config.clone());
assert!(lf2.is_err());
drop(lf);
let _lf2 = FileLock::new(config.clone()).unwrap();
println!("Directory locked successfully after dropping first lock");
}
}