//! A small time-to-live cache over raw response bodies. //! //! Keyed on a logical request key rather than the URL, so the API key never //! becomes part of a cache key. Bodies are stored unparsed: parsing again on a //! hit costs microseconds and keeps one cache serving every endpoint. use std::collections::HashMap; use std::sync::Arc; use std::sync::{Mutex, PoisonError}; use std::time::{Duration, Instant}; #[derive(Debug)] struct Entry { stored_at: Instant, body: Arc, } #[derive(Debug)] pub(crate) struct Cache { ttl: Duration, entries: Mutex>, } impl Cache { pub(crate) fn new(ttl: Duration) -> Self { Self { ttl, entries: Mutex::new(HashMap::new()), } } /// The cached body for `key`, if one is present and still fresh. pub(crate) fn get(&self, key: &str) -> Option> { let mut entries = self.entries.lock().unwrap_or_else(PoisonError::into_inner); let entry = entries.get(key)?; if entry.stored_at.elapsed() < self.ttl { return Some(Arc::clone(&entry.body)); } entries.remove(key); None } pub(crate) fn insert(&self, key: String, body: Arc) { let mut entries = self.entries.lock().unwrap_or_else(PoisonError::into_inner); entries.insert( key, Entry { stored_at: Instant::now(), body, }, ); } /// Drop everything. The daily metadata refresh does not need this — entries /// expire on their own — but a forced refresh from the UI does. pub(crate) fn clear(&self) { self.entries .lock() .unwrap_or_else(PoisonError::into_inner) .clear(); } }