Skip to main content

batuta/stack/crates_io/
cache.rs

1//! Persistent file-based cache for crates.io responses.
2
3use super::types::{CrateResponse, PersistentCacheEntry};
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::fs;
8use std::path::PathBuf;
9use std::time::Duration;
10
11/// Persistent cache stored on disk
12#[derive(Debug, Default, Serialize, Deserialize)]
13pub struct PersistentCache {
14    pub entries: HashMap<String, PersistentCacheEntry>,
15}
16
17impl PersistentCache {
18    /// Get cache file path
19    pub fn cache_path() -> PathBuf {
20        dirs::cache_dir()
21            .unwrap_or_else(|| PathBuf::from("."))
22            .join("batuta")
23            .join("crates_io_cache.json")
24    }
25
26    /// Load cache from disk
27    pub fn load() -> Self {
28        Self::load_from(&Self::cache_path())
29    }
30
31    /// Load cache from a specific path
32    pub fn load_from(path: &std::path::Path) -> Self {
33        if path.exists() {
34            if let Ok(data) = fs::read_to_string(path) {
35                if let Ok(cache) = serde_json::from_str(&data) {
36                    return cache;
37                }
38            }
39        }
40        Self::default()
41    }
42
43    /// Save cache to disk
44    pub fn save(&self) -> Result<()> {
45        self.save_to(&Self::cache_path())
46    }
47
48    /// Save cache to a specific path
49    pub fn save_to(&self, path: &std::path::Path) -> Result<()> {
50        if let Some(parent) = path.parent() {
51            fs::create_dir_all(parent)?;
52        }
53        let data = serde_json::to_string_pretty(self)?;
54        fs::write(path, data)?;
55        Ok(())
56    }
57
58    /// Get cached response
59    pub fn get(&self, name: &str) -> Option<&CrateResponse> {
60        self.entries.get(name).and_then(|entry| {
61            if !entry.is_expired() {
62                Some(&entry.response)
63            } else {
64                None
65            }
66        })
67    }
68
69    /// Insert response into cache
70    pub fn insert(&mut self, name: String, response: CrateResponse, ttl: Duration) {
71        self.entries.insert(name, PersistentCacheEntry::new(response, ttl));
72    }
73
74    /// Clear expired entries
75    pub fn clear_expired(&mut self) {
76        self.entries.retain(|_, entry| !entry.is_expired());
77    }
78}