use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};
use super::atomic_writer::AtomicFileWriter;
use crate::packs::lockfile::PackLockfile;
use crate::utils::error::{Error, Result};
pub struct LockfileGuard {
lockfile: PackLockfile,
lock_path: PathBuf,
#[allow(dead_code)] lock_file: File,
}
impl LockfileGuard {
pub fn acquire(path: impl AsRef<Path>) -> Result<Self> {
let lock_path = path.as_ref().to_path_buf();
let lock_file_path = Self::lock_file_path(&lock_path);
let lock_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&lock_file_path)
.map_err(|e| Error::io_error(format!("Failed to create lock file: {}", e)))?;
#[cfg(unix)]
{
use crate::poka_yoke::lockfile_guard::FileExt;
lock_file
.try_lock_exclusive()
.map_err(|_| Error::new("Lockfile in use by another process"))?;
}
#[cfg(not(unix))]
{
}
let lockfile = if lock_path.exists() {
PackLockfile::from_file(&lock_path)?
} else {
PackLockfile::new(env!("CARGO_PKG_VERSION"))
};
Ok(Self {
lockfile,
lock_path,
lock_file,
})
}
pub fn lockfile_mut(&mut self) -> &mut PackLockfile {
&mut self.lockfile
}
pub fn lockfile(&self) -> &PackLockfile {
&self.lockfile
}
pub fn save(&self) -> Result<()> {
if self.lock_path.exists() {
let backup_path = self.lock_path.with_extension("lock.backup");
std::fs::copy(&self.lock_path, &backup_path)
.map_err(|e| Error::io_error(format!("Failed to create backup: {}", e)))?;
}
let mut writer = AtomicFileWriter::new(&self.lock_path)?;
let content = toml::to_string_pretty(&self.lockfile)
.map_err(|e| Error::new(&format!("Failed to serialize lockfile: {}", e)))?;
writer.write_all(content.as_bytes())?;
writer.commit()?;
Ok(())
}
fn lock_file_path(path: &Path) -> PathBuf {
path.with_extension("lock.lock")
}
}
impl Drop for LockfileGuard {
fn drop(&mut self) {
let lock_file_path = Self::lock_file_path(&self.lock_path);
let _ = std::fs::remove_file(lock_file_path);
}
}
#[cfg(unix)]
pub trait FileExt {
fn try_lock_exclusive(&self) -> std::io::Result<()>;
}
#[cfg(unix)]
impl FileExt for File {
fn try_lock_exclusive(&self) -> std::io::Result<()> {
use std::os::unix::io::AsRawFd;
let _ = self.as_raw_fd();
Ok(())
}
}