Skip to main content

aperture_cli/cache/
metadata.rs

1use crate::cache::models::{GlobalCacheMetadata, SpecMetadata, CACHE_FORMAT_VERSION};
2use crate::constants;
3use crate::error::Error;
4use crate::fs::FileSystem;
5use std::path::Path;
6
7/// Manages cache metadata for optimized version checking
8pub struct CacheMetadataManager<'a, F: FileSystem> {
9    fs: &'a F,
10}
11
12impl<'a, F: FileSystem> CacheMetadataManager<'a, F> {
13    pub const fn new(fs: &'a F) -> Self {
14        Self { fs }
15    }
16
17    /// Load global cache metadata, creating default if it doesn't exist
18    ///
19    /// # Errors
20    /// Returns an error if the metadata file exists but cannot be read or parsed
21    pub fn load_metadata<P: AsRef<Path>>(
22        &self,
23        cache_dir: P,
24    ) -> Result<GlobalCacheMetadata, Error> {
25        let metadata_path = cache_dir.as_ref().join(constants::CACHE_METADATA_FILENAME);
26
27        if !self.fs.exists(&metadata_path) {
28            // Create default metadata file
29            let metadata = GlobalCacheMetadata::default();
30            self.save_metadata(&cache_dir, &metadata)?;
31            return Ok(metadata);
32        }
33
34        let content = self.fs.read_to_string(&metadata_path)?;
35        serde_json::from_str(&content)
36            .map_err(|e| Error::invalid_config(format!("Failed to parse cache metadata: {e}")))
37    }
38
39    /// Save global cache metadata
40    ///
41    /// # Errors
42    /// Returns an error if the metadata cannot be serialized or written to disk
43    pub fn save_metadata<P: AsRef<Path>>(
44        &self,
45        cache_dir: P,
46        metadata: &GlobalCacheMetadata,
47    ) -> Result<(), Error> {
48        let metadata_path = cache_dir.as_ref().join(constants::CACHE_METADATA_FILENAME);
49
50        // Ensure cache directory exists
51        self.fs.create_dir_all(cache_dir.as_ref())?;
52
53        let content = serde_json::to_string_pretty(metadata).map_err(|e| {
54            Error::serialization_error(format!("Failed to serialize cache metadata: {e}"))
55        })?;
56
57        self.fs.write_all(&metadata_path, content.as_bytes())?;
58        Ok(())
59    }
60
61    /// Check if a spec's cache is compatible with current version
62    ///
63    /// # Errors
64    /// Returns an error if the metadata file cannot be loaded
65    pub fn check_spec_version<P: AsRef<Path>>(
66        &self,
67        cache_dir: P,
68        spec_name: &str,
69    ) -> Result<bool, Error> {
70        let metadata = self.load_metadata(&cache_dir)?;
71
72        // Check global format version
73        if metadata.cache_format_version != CACHE_FORMAT_VERSION {
74            return Ok(false);
75        }
76
77        // Check if spec exists in metadata
78        Ok(metadata.specs.contains_key(spec_name))
79    }
80
81    /// Update metadata for a specific spec
82    ///
83    /// # Errors
84    /// Returns an error if the metadata cannot be loaded or saved
85    pub fn update_spec_metadata<P: AsRef<Path>>(
86        &self,
87        cache_dir: P,
88        spec_name: &str,
89        file_size: u64,
90    ) -> Result<(), Error> {
91        let mut metadata = self.load_metadata(&cache_dir)?;
92
93        let spec_metadata = SpecMetadata {
94            updated_at: chrono::Utc::now().to_rfc3339(),
95            file_size,
96        };
97
98        metadata.specs.insert(spec_name.to_string(), spec_metadata);
99        self.save_metadata(&cache_dir, &metadata)?;
100        Ok(())
101    }
102
103    /// Remove spec from metadata
104    ///
105    /// # Errors
106    /// Returns an error if the metadata cannot be loaded or saved
107    pub fn remove_spec_metadata<P: AsRef<Path>>(
108        &self,
109        cache_dir: P,
110        spec_name: &str,
111    ) -> Result<(), Error> {
112        let mut metadata = self.load_metadata(&cache_dir)?;
113        metadata.specs.remove(spec_name);
114        self.save_metadata(&cache_dir, &metadata)?;
115        Ok(())
116    }
117
118    /// Get all specs in metadata
119    ///
120    /// # Errors
121    /// Returns an error if the metadata file cannot be loaded
122    pub fn list_cached_specs<P: AsRef<Path>>(&self, cache_dir: P) -> Result<Vec<String>, Error> {
123        let metadata = self.load_metadata(&cache_dir)?;
124        Ok(metadata.specs.keys().cloned().collect())
125    }
126}