Skip to main content

driven/cli/
cache.rs

1//! Cache Command
2//!
3//! Manage binary cache and fusion templates.
4
5use crate::Result;
6use crate::fusion::BinaryCache;
7use console::style;
8use std::path::Path;
9
10/// Cache command for managing binary caches
11#[derive(Debug)]
12pub struct CacheCommand;
13
14/// Extended cache statistics
15#[derive(Debug, Default)]
16pub struct CacheStats {
17    /// Number of entries
18    pub entries: usize,
19    /// Total size in bytes
20    pub total_size: usize,
21    /// Oldest entry timestamp
22    pub oldest_entry: Option<std::time::SystemTime>,
23}
24
25impl CacheCommand {
26    /// Show cache status
27    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    /// Compute extended statistics
55    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    /// Clear the cache
81    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    /// Clear all cache entries in directory
100    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    /// Prune old entries
117    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    /// Prune entries older than max_age
137    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    /// Warm the cache with templates from source directory
161    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        // Just scan and report for now
169        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    /// Show cache entries
196    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    /// List cache entries
222    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}