use std::fs::{File, OpenOptions};
use camino::Utf8PathBuf;
use color_eyre::eyre::{Report, eyre};
use fs4::FileExt;
use tracing::debug;
use crate::{
TestBootstrapSettings,
error::{BootstrapError, BootstrapResult},
observability::LOG_TARGET,
};
pub(crate) const SETUP_LOCK_FILE: &str = ".pg-embed-setup.lock";
#[derive(Debug)]
pub(super) struct SetupLock {
_file: File,
}
impl SetupLock {
pub(super) fn acquire(bootstrap: &TestBootstrapSettings) -> BootstrapResult<Self> {
let install_dir =
Utf8PathBuf::from_path_buf(bootstrap.settings.installation_dir.clone())
.map_err(|path| eyre!("installation_dir is not UTF-8: {}", path.display()))?;
std::fs::create_dir_all(&install_dir).map_err(|err| lock_error(&install_dir, err))?;
let path = install_dir.join(SETUP_LOCK_FILE);
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&path)
.map_err(|err| lock_error(&path, err))?;
FileExt::lock(&file).map_err(|err| lock_error(&path, err))?;
debug!(target: LOG_TARGET, lock = %path, "took the install tree's setup lock");
Ok(Self { _file: file })
}
#[cfg(feature = "async-api")]
pub(super) async fn acquire_async(bootstrap: &TestBootstrapSettings) -> BootstrapResult<Self> {
let owned = bootstrap.clone();
tokio::task::spawn_blocking(move || Self::acquire(&owned))
.await
.map_err(|err| {
BootstrapError::from(Report::new(err).wrap_err("setup lock task failed"))
})?
}
}
fn lock_error(path: &Utf8PathBuf, err: std::io::Error) -> BootstrapError {
BootstrapError::from(Report::new(err).wrap_err(format!("cannot take the setup lock at {path}")))
}