use camino::{Utf8Path, Utf8PathBuf};
use postgresql_embedded::{Version, VersionReq};
use std::fs;
use tracing::{debug, warn};
use super::copy::copy_from_cache;
pub(crate) const COMPLETION_MARKER: &str = ".complete";
const LOG_TARGET: &str = "pg_embed::cache";
#[derive(Debug)]
#[must_use]
pub enum CacheLookupResult {
Hit {
source_dir: Utf8PathBuf,
},
Miss,
}
fn is_cache_entry_complete(version_dir: &Utf8Path) -> bool {
let marker = version_dir.join(COMPLETION_MARKER);
let bin_dir = version_dir.join("bin");
marker.exists() && bin_dir.is_dir()
}
pub fn check_cache(cache_dir: &Utf8Path, version: &str) -> CacheLookupResult {
let version_dir = cache_dir.join(version);
if is_cache_entry_complete(&version_dir) {
debug!(
target: LOG_TARGET,
version = %version,
path = %version_dir,
"cache hit"
);
CacheLookupResult::Hit {
source_dir: version_dir,
}
} else {
log_cache_miss(&version_dir, version);
CacheLookupResult::Miss
}
}
fn log_cache_miss(version_dir: &Utf8Path, version: &str) {
let marker = version_dir.join(COMPLETION_MARKER);
let bin_dir = version_dir.join("bin");
debug!(
target: LOG_TARGET,
version = %version,
marker_exists = marker.exists(),
bin_exists = bin_dir.is_dir(),
"cache miss"
);
}
#[must_use]
pub fn find_matching_cached_version(
cache_dir: &Utf8Path,
version_req: &VersionReq,
) -> Option<(String, Utf8PathBuf)> {
let dir_entries = read_cache_directory(cache_dir)?;
let (version, path) = dir_entries
.filter_map(Result::ok)
.filter_map(|entry| try_parse_cache_entry(&entry, version_req))
.max_by(|a, b| a.0.cmp(&b.0))?;
let version_str = version.to_string();
debug!(
target: LOG_TARGET,
version_req = %version_req,
matched_version = %version_str,
path = %path,
"found matching cached version"
);
Some((version_str, path))
}
fn read_cache_directory(cache_dir: &Utf8Path) -> Option<fs::ReadDir> {
match fs::read_dir(cache_dir) {
Ok(entries) => Some(entries),
Err(err) => {
debug!(
target: LOG_TARGET,
cache_dir = %cache_dir,
error = %err,
"failed to read cache directory"
);
None
}
}
}
fn try_parse_cache_entry(
entry: &fs::DirEntry,
version_req: &VersionReq,
) -> Option<(Version, Utf8PathBuf)> {
let path = entry.path();
let dir_name = path.file_name()?.to_str()?;
if dir_name.starts_with('.') {
return None;
}
let version = Version::parse(dir_name).ok()?;
if !version_req.matches(&version) {
return None;
}
let utf8_path = Utf8PathBuf::from_path_buf(path).ok()?;
is_cache_entry_complete(&utf8_path).then_some((version, utf8_path))
}
#[must_use]
pub fn try_use_cache(cache_dir: &Utf8Path, version: &str, target: &Utf8Path) -> bool {
let CacheLookupResult::Hit { source_dir } = check_cache(cache_dir, version) else {
return false;
};
match copy_from_cache(&source_dir, target) {
Ok(()) => true,
Err(err) => {
warn!(
target: LOG_TARGET,
error = %err,
version = %version,
"cache copy failed, falling back to download"
);
false
}
}
}