1use crate::Result;
6use crate::fusion::BinaryCache;
7use console::style;
8use std::path::Path;
9
10#[derive(Debug)]
12pub struct CacheCommand;
13
14#[derive(Debug, Default)]
16pub struct CacheStats {
17 pub entries: usize,
19 pub total_size: usize,
21 pub oldest_entry: Option<std::time::SystemTime>,
23}
24
25impl CacheCommand {
26 pub fn status(cache_dir: &Path) -> Result<()> {
28 println!("{} Cache Status", style("๐ฆ").bold());
29 println!();
30
31 let cache = BinaryCache::open(cache_dir)?;
32 let internal_stats = cache.stats();
33 let extended = Self::compute_extended_stats(cache_dir)?;
34
35 println!(" Directory: {}", cache_dir.display());
36 println!(" Entries: {}", internal_stats.entries);
37 println!(
38 " Size: {} bytes / {} max",
39 internal_stats.size_bytes, internal_stats.max_size_bytes
40 );
41
42 if extended.oldest_entry.is_some() {
43 println!(
44 " Oldest: {:?}",
45 extended
46 .oldest_entry
47 .map(|t| t.elapsed().unwrap_or_default())
48 );
49 }
50
51 Ok(())
52 }
53
54 fn compute_extended_stats(cache_dir: &Path) -> Result<CacheStats> {
56 let mut stats = CacheStats::default();
57
58 if !cache_dir.exists() {
59 return Ok(stats);
60 }
61
62 for entry in std::fs::read_dir(cache_dir)? {
63 let entry = entry?;
64 let metadata = entry.metadata()?;
65
66 if metadata.is_file() {
67 stats.entries += 1;
68 stats.total_size += metadata.len() as usize;
69
70 let modified = metadata.modified().ok();
71 if stats.oldest_entry.is_none() || modified < stats.oldest_entry {
72 stats.oldest_entry = modified;
73 }
74 }
75 }
76
77 Ok(stats)
78 }
79
80 pub fn clear(cache_dir: &Path) -> Result<()> {
82 println!(
83 "{} Clearing cache at {}",
84 style("๐").bold(),
85 cache_dir.display()
86 );
87
88 let removed = Self::clear_cache_dir(cache_dir)?;
89
90 println!(
91 "{} Removed {} cache entries",
92 style("โ").green().bold(),
93 removed
94 );
95
96 Ok(())
97 }
98
99 fn clear_cache_dir(cache_dir: &Path) -> Result<usize> {
101 let mut removed = 0;
102
103 if cache_dir.exists() {
104 for entry in std::fs::read_dir(cache_dir)? {
105 let entry = entry?;
106 if entry.metadata()?.is_file() {
107 std::fs::remove_file(entry.path())?;
108 removed += 1;
109 }
110 }
111 }
112
113 Ok(removed)
114 }
115
116 pub fn prune(cache_dir: &Path, max_age_secs: u64) -> Result<()> {
118 println!(
119 "{} Pruning cache entries older than {} seconds",
120 style("๐งน").bold(),
121 max_age_secs
122 );
123
124 let removed =
125 Self::prune_cache_dir(cache_dir, std::time::Duration::from_secs(max_age_secs))?;
126
127 println!(
128 "{} Removed {} old entries",
129 style("โ").green().bold(),
130 removed
131 );
132
133 Ok(())
134 }
135
136 fn prune_cache_dir(cache_dir: &Path, max_age: std::time::Duration) -> Result<usize> {
138 let mut removed = 0;
139 let now = std::time::SystemTime::now();
140
141 if cache_dir.exists() {
142 for entry in std::fs::read_dir(cache_dir)? {
143 let entry = entry?;
144 if entry.metadata()?.is_file() {
145 if let Ok(modified) = entry.metadata()?.modified() {
146 if let Ok(age) = now.duration_since(modified) {
147 if age > max_age {
148 std::fs::remove_file(entry.path())?;
149 removed += 1;
150 }
151 }
152 }
153 }
154 }
155 }
156
157 Ok(removed)
158 }
159
160 pub fn warm(cache_dir: &Path, source_dir: &Path) -> Result<()> {
162 println!(
163 "{} Warming cache from {}",
164 style("๐ฅ").bold(),
165 source_dir.display()
166 );
167
168 let mut file_count = 0;
170
171 for entry in walkdir::WalkDir::new(source_dir)
172 .follow_links(true)
173 .into_iter()
174 .filter_map(|e| e.ok())
175 {
176 let path = entry.path();
177 if path
178 .extension()
179 .is_some_and(|ext| ext == "drv" || ext == "md")
180 {
181 file_count += 1;
182 }
183 }
184
185 println!(
186 "{} Found {} template files in source",
187 style("โ").green().bold(),
188 file_count
189 );
190 println!(" Cache directory: {}", cache_dir.display());
191
192 Ok(())
193 }
194
195 pub fn list(cache_dir: &Path) -> Result<()> {
197 println!("{} Cache Entries", style("๐").bold());
198 println!();
199
200 let entries = Self::list_cache_entries(cache_dir)?;
201
202 if entries.is_empty() {
203 println!(" No cache entries found.");
204 } else {
205 for (key, size, age) in entries {
206 let age_str = if age.as_secs() < 60 {
207 format!("{}s ago", age.as_secs())
208 } else if age.as_secs() < 3600 {
209 format!("{}m ago", age.as_secs() / 60)
210 } else {
211 format!("{}h ago", age.as_secs() / 3600)
212 };
213
214 println!(" {} ({} bytes, {})", key, size, age_str);
215 }
216 }
217
218 Ok(())
219 }
220
221 fn list_cache_entries(cache_dir: &Path) -> Result<Vec<(String, usize, std::time::Duration)>> {
223 let mut entries = Vec::new();
224 let now = std::time::SystemTime::now();
225
226 if cache_dir.exists() {
227 for entry in std::fs::read_dir(cache_dir)? {
228 let entry = entry?;
229 let metadata = entry.metadata()?;
230
231 if metadata.is_file() {
232 let name = entry.file_name().to_string_lossy().to_string();
233 let size = metadata.len() as usize;
234 let age = metadata
235 .modified()
236 .ok()
237 .and_then(|m| now.duration_since(m).ok())
238 .unwrap_or_default();
239
240 entries.push((name, size, age));
241 }
242 }
243 }
244
245 entries.sort_by(|a, b| a.0.cmp(&b.0));
246 Ok(entries)
247 }
248}