#![forbid(unsafe_code)]
#[macro_use]
extern crate log;
use std::fs::{self, File, OpenOptions};
use std::io;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct Lockfile {
handle: Option<File>,
path: PathBuf,
}
impl Lockfile {
pub fn create(path: impl AsRef<Path>) -> Result<Lockfile, io::Error> {
let path = path.as_ref();
let dir = path.parent().expect("lockfile path must have a parent");
fs::create_dir_all(dir)?;
debug!(
r#"lockfile parent directories created/found at "{}""#,
dir.display()
);
let mut lockfile_opts = OpenOptions::new();
lockfile_opts.create_new(true).read(true).write(true);
let lockfile = lockfile_opts.open(path)?;
debug!(r#"lockfile created at "{}""#, path.display());
Ok(Lockfile {
handle: Some(lockfile),
path: path.to_owned(),
})
}
#[inline]
pub fn path(&self) -> &Path {
self.path.as_path()
}
pub fn release(mut self) -> Result<(), io::Error> {
self.handle.take().expect("handle already dropped");
fs::remove_file(&self.path)?;
debug!(r#"Removed lockfile at "{}""#, self.path.display());
Ok(())
}
}
impl Drop for Lockfile {
fn drop(&mut self) {
if let Some(handle) = self.handle.take() {
drop(handle);
match fs::remove_file(&self.path) {
Ok(()) => debug!(r#"Removed lockfile at "{}""#, self.path.display()),
Err(e) => warn!(
r#"could not remove lockfile at "{}": {}"#,
self.path.display(),
e
),
}
}
}
}
impl AsRef<Path> for Lockfile {
#[inline]
fn as_ref(&self) -> &Path {
self.path.as_ref()
}
}
impl io::Read for Lockfile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::Read::read(&mut self.handle.as_ref().unwrap(), buf)
}
}
impl<'a> io::Read for &'a Lockfile {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
io::Read::read(&mut self.handle.as_ref().unwrap(), buf)
}
}
impl io::Write for Lockfile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
io::Write::write(&mut self.handle.as_ref().unwrap(), buf)
}
fn flush(&mut self) -> io::Result<()> {
io::Write::flush(&mut self.handle.as_ref().unwrap())
}
}
impl<'a> io::Write for &'a Lockfile {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
io::Write::write(&mut self.handle.as_ref().unwrap(), buf)
}
fn flush(&mut self) -> io::Result<()> {
io::Write::flush(&mut self.handle.as_ref().unwrap())
}
}
impl io::Seek for Lockfile {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.handle.as_ref().unwrap(), pos)
}
}
impl<'a> io::Seek for &'a Lockfile {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
io::Seek::seek(&mut self.handle.as_ref().unwrap(), pos)
}
}
#[cfg(test)]
mod tests {
extern crate tempfile;
use self::tempfile::NamedTempFile;
use super::Lockfile;
use std::path::PathBuf;
use std::fs;
use std::io;
fn tmp_path() -> PathBuf {
NamedTempFile::new().unwrap().into_temp_path().to_owned()
}
#[test]
fn smoke() {
let path = tmp_path();
let lockfile = Lockfile::create(&path).unwrap();
assert_eq!(lockfile.path(), path);
lockfile.release().unwrap();
assert_eq!(fs::metadata(path).unwrap_err().kind(), io::ErrorKind::NotFound);
}
#[test]
fn lock_twice() {
let path = tmp_path();
let _lockfile = Lockfile::create(&path).unwrap();
assert_eq!(Lockfile::create(&path).unwrap_err().kind(), io::ErrorKind::AlreadyExists);
}
}