aperture_cli/cache/
metadata.rs1use crate::cache::models::{GlobalCacheMetadata, SpecMetadata, CACHE_FORMAT_VERSION};
2use crate::constants;
3use crate::error::Error;
4use crate::fs::FileSystem;
5use std::path::Path;
6
7pub 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 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 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 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 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.atomic_write(&metadata_path, content.as_bytes())?;
58 Ok(())
59 }
60
61 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 if metadata.cache_format_version != CACHE_FORMAT_VERSION {
74 return Ok(false);
75 }
76
77 Ok(metadata.specs.contains_key(spec_name))
79 }
80
81 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 self.update_spec_metadata_with_fingerprint(
92 cache_dir, spec_name, file_size, None, None, None,
93 )
94 }
95
96 pub fn update_spec_metadata_with_fingerprint<P: AsRef<Path>>(
101 &self,
102 cache_dir: P,
103 spec_name: &str,
104 file_size: u64,
105 content_hash: Option<String>,
106 mtime_secs: Option<u64>,
107 spec_file_size: Option<u64>,
108 ) -> Result<(), Error> {
109 let mut metadata = self.load_metadata(&cache_dir)?;
110
111 let spec_metadata = SpecMetadata {
112 updated_at: chrono::Utc::now().to_rfc3339(),
113 file_size,
114 content_hash,
115 mtime_secs,
116 spec_file_size,
117 };
118
119 metadata.specs.insert(spec_name.to_string(), spec_metadata);
120 self.save_metadata(&cache_dir, &metadata)?;
121 Ok(())
122 }
123
124 pub fn get_stored_fingerprint<P: AsRef<Path>>(
132 &self,
133 cache_dir: P,
134 spec_name: &str,
135 ) -> Result<Option<(String, u64, u64)>, Error> {
136 let metadata = self.load_metadata(&cache_dir)?;
137
138 let Some(spec_meta) = metadata.specs.get(spec_name) else {
139 return Ok(None);
140 };
141
142 let (Some(stored_hash), Some(stored_mtime), Some(stored_size)) = (
144 &spec_meta.content_hash,
145 spec_meta.mtime_secs,
146 spec_meta.spec_file_size,
147 ) else {
148 return Ok(None);
149 };
150
151 Ok(Some((stored_hash.clone(), stored_mtime, stored_size)))
152 }
153
154 pub fn remove_spec_metadata<P: AsRef<Path>>(
159 &self,
160 cache_dir: P,
161 spec_name: &str,
162 ) -> Result<(), Error> {
163 let mut metadata = self.load_metadata(&cache_dir)?;
164 metadata.specs.remove(spec_name);
165 self.save_metadata(&cache_dir, &metadata)?;
166 Ok(())
167 }
168
169 pub fn list_cached_specs<P: AsRef<Path>>(&self, cache_dir: P) -> Result<Vec<String>, Error> {
174 let metadata = self.load_metadata(&cache_dir)?;
175 Ok(metadata.specs.keys().cloned().collect())
176 }
177}