use crate::cache::models::{GlobalCacheMetadata, SpecMetadata, CACHE_FORMAT_VERSION};
use crate::constants;
use crate::error::Error;
use crate::fs::FileSystem;
use std::path::Path;
pub struct CacheMetadataManager<'a, F: FileSystem> {
fs: &'a F,
}
impl<'a, F: FileSystem> CacheMetadataManager<'a, F> {
pub const fn new(fs: &'a F) -> Self {
Self { fs }
}
pub fn load_metadata<P: AsRef<Path>>(
&self,
cache_dir: P,
) -> Result<GlobalCacheMetadata, Error> {
let metadata_path = cache_dir.as_ref().join(constants::CACHE_METADATA_FILENAME);
if !self.fs.exists(&metadata_path) {
let metadata = GlobalCacheMetadata::default();
self.save_metadata(&cache_dir, &metadata)?;
return Ok(metadata);
}
let content = self.fs.read_to_string(&metadata_path)?;
serde_json::from_str(&content)
.map_err(|e| Error::invalid_config(format!("Failed to parse cache metadata: {e}")))
}
pub fn save_metadata<P: AsRef<Path>>(
&self,
cache_dir: P,
metadata: &GlobalCacheMetadata,
) -> Result<(), Error> {
let metadata_path = cache_dir.as_ref().join(constants::CACHE_METADATA_FILENAME);
self.fs.create_dir_all(cache_dir.as_ref())?;
let content = serde_json::to_string_pretty(metadata).map_err(|e| {
Error::serialization_error(format!("Failed to serialize cache metadata: {e}"))
})?;
self.fs.atomic_write(&metadata_path, content.as_bytes())?;
Ok(())
}
pub fn check_spec_version<P: AsRef<Path>>(
&self,
cache_dir: P,
spec_name: &str,
) -> Result<bool, Error> {
let metadata = self.load_metadata(&cache_dir)?;
if metadata.cache_format_version != CACHE_FORMAT_VERSION {
return Ok(false);
}
Ok(metadata.specs.contains_key(spec_name))
}
pub fn update_spec_metadata<P: AsRef<Path>>(
&self,
cache_dir: P,
spec_name: &str,
file_size: u64,
) -> Result<(), Error> {
self.update_spec_metadata_with_fingerprint(
cache_dir, spec_name, file_size, None, None, None,
)
}
pub fn update_spec_metadata_with_fingerprint<P: AsRef<Path>>(
&self,
cache_dir: P,
spec_name: &str,
file_size: u64,
content_hash: Option<String>,
mtime_secs: Option<u64>,
spec_file_size: Option<u64>,
) -> Result<(), Error> {
let mut metadata = self.load_metadata(&cache_dir)?;
let spec_metadata = SpecMetadata {
updated_at: chrono::Utc::now().to_rfc3339(),
file_size,
content_hash,
mtime_secs,
spec_file_size,
};
metadata.specs.insert(spec_name.to_string(), spec_metadata);
self.save_metadata(&cache_dir, &metadata)?;
Ok(())
}
pub fn get_stored_fingerprint<P: AsRef<Path>>(
&self,
cache_dir: P,
spec_name: &str,
) -> Result<Option<(String, u64, u64)>, Error> {
let metadata = self.load_metadata(&cache_dir)?;
let Some(spec_meta) = metadata.specs.get(spec_name) else {
return Ok(None);
};
let (Some(stored_hash), Some(stored_mtime), Some(stored_size)) = (
&spec_meta.content_hash,
spec_meta.mtime_secs,
spec_meta.spec_file_size,
) else {
return Ok(None);
};
Ok(Some((stored_hash.clone(), stored_mtime, stored_size)))
}
pub fn remove_spec_metadata<P: AsRef<Path>>(
&self,
cache_dir: P,
spec_name: &str,
) -> Result<(), Error> {
let mut metadata = self.load_metadata(&cache_dir)?;
metadata.specs.remove(spec_name);
self.save_metadata(&cache_dir, &metadata)?;
Ok(())
}
pub fn list_cached_specs<P: AsRef<Path>>(&self, cache_dir: P) -> Result<Vec<String>, Error> {
let metadata = self.load_metadata(&cache_dir)?;
Ok(metadata.specs.keys().cloned().collect())
}
}