aperture_cli/cache/
metadata.rs

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