batuta/stack/crates_io/
cache.rs1use 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#[derive(Debug, Default, Serialize, Deserialize)]
13pub struct PersistentCache {
14 pub entries: HashMap<String, PersistentCacheEntry>,
15}
16
17impl PersistentCache {
18 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 pub fn load() -> Self {
28 Self::load_from(&Self::cache_path())
29 }
30
31 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 pub fn save(&self) -> Result<()> {
45 self.save_to(&Self::cache_path())
46 }
47
48 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 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 pub fn insert(&mut self, name: String, response: CrateResponse, ttl: Duration) {
71 self.entries.insert(name, PersistentCacheEntry::new(response, ttl));
72 }
73
74 pub fn clear_expired(&mut self) {
76 self.entries.retain(|_, entry| !entry.is_expired());
77 }
78}