use crate::cache::fingerprint::compute_content_hash;
use crate::cache::metadata::CacheMetadataManager;
use crate::cache::models::{CachedSpec, CACHE_FORMAT_VERSION};
use crate::error::Error;
use crate::fs::OsFileSystem;
use std::fs;
use std::path::Path;
pub fn load_cached_spec<P: AsRef<Path>>(
cache_dir: P,
spec_name: &str,
) -> Result<CachedSpec, Error> {
let fs = OsFileSystem;
let metadata_manager = CacheMetadataManager::new(&fs);
let spec = match metadata_manager.check_spec_version(&cache_dir, spec_name) {
Ok(true) => {
load_cached_spec_without_version_check(&cache_dir, spec_name)?
}
Ok(false) => {
load_cached_spec_with_version_check(&cache_dir, spec_name)?
}
Err(_) => {
load_cached_spec_with_version_check(&cache_dir, spec_name)?
}
};
check_spec_file_freshness(&cache_dir, spec_name, &metadata_manager)?;
Ok(spec)
}
fn check_spec_file_freshness<P: AsRef<Path>>(
cache_dir: P,
spec_name: &str,
metadata_manager: &CacheMetadataManager<'_, OsFileSystem>,
) -> Result<(), Error> {
let Ok(Some((stored_hash, stored_mtime, stored_size))) =
metadata_manager.get_stored_fingerprint(&cache_dir, spec_name)
else {
return Ok(());
};
let Some(config_dir) = cache_dir.as_ref().parent() else {
return Ok(()); };
let spec_path = config_dir
.join(crate::constants::DIR_SPECS)
.join(format!("{spec_name}{}", crate::constants::FILE_EXT_YAML));
let Ok(file_meta) = fs::metadata(&spec_path) else {
return Ok(()); };
let current_mtime = file_meta
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs());
let Some(current_mtime) = current_mtime else {
return Ok(()); };
let current_size = file_meta.len();
if stored_mtime != current_mtime || stored_size != current_size {
return Err(Error::cache_stale(spec_name));
}
let Ok(content) = fs::read(&spec_path) else {
return Ok(()); };
let current_hash = compute_content_hash(&content);
if stored_hash != current_hash {
return Err(Error::cache_stale(spec_name));
}
Ok(())
}
fn load_cached_spec_without_version_check<P: AsRef<Path>>(
cache_dir: P,
spec_name: &str,
) -> Result<CachedSpec, Error> {
let cache_path = cache_dir
.as_ref()
.join(format!("{spec_name}{}", crate::constants::FILE_EXT_BIN));
if !cache_path.exists() {
return Err(Error::cached_spec_not_found(spec_name));
}
let cache_data = fs::read(&cache_path)
.map_err(|e| Error::io_error(format!("Failed to read cache file: {e}")))?;
postcard::from_bytes(&cache_data)
.map_err(|e| Error::cached_spec_corrupted(spec_name, e.to_string()))
}
fn load_cached_spec_with_version_check<P: AsRef<Path>>(
cache_dir: P,
spec_name: &str,
) -> Result<CachedSpec, Error> {
let cache_path = cache_dir
.as_ref()
.join(format!("{spec_name}{}", crate::constants::FILE_EXT_BIN));
if !cache_path.exists() {
return Err(Error::cached_spec_not_found(spec_name));
}
let cache_data = fs::read(&cache_path)
.map_err(|e| Error::io_error(format!("Failed to read cache file: {e}")))?;
let cached_spec: CachedSpec = postcard::from_bytes(&cache_data)
.map_err(|e| Error::cached_spec_corrupted(spec_name, e.to_string()))?;
if cached_spec.cache_format_version != CACHE_FORMAT_VERSION {
return Err(Error::cache_version_mismatch(
spec_name,
cached_spec.cache_format_version,
CACHE_FORMAT_VERSION,
));
}
Ok(cached_spec)
}