use lru::LruCache;
use std::num::NonZeroUsize;
use std::sync::Mutex;
use crate::element::AXElement;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct CacheKey {
pub pid: i32,
pub query: String,
}
pub struct CacheEntry {
pub element: AXElement,
pub timestamp: std::time::Instant,
}
pub struct ElementCache {
cache: Mutex<LruCache<CacheKey, CacheEntry>>,
max_age_ms: u64,
}
impl ElementCache {
#[must_use]
pub fn new(capacity: usize, max_age_ms: u64) -> Self {
Self {
cache: Mutex::new(LruCache::new(
NonZeroUsize::new(capacity).unwrap_or(NonZeroUsize::new(100).unwrap()),
)),
max_age_ms,
}
}
pub fn get(&self, key: &CacheKey) -> Option<AXElement> {
let mut cache = self.cache.lock().ok()?;
if let Some(entry) = cache.get(key) {
if entry.timestamp.elapsed().as_millis() < u128::from(self.max_age_ms) {
return None;
}
cache.pop(key);
}
None
}
pub fn put(&self, key: CacheKey, element: AXElement) {
if let Ok(mut cache) = self.cache.lock() {
cache.put(
key,
CacheEntry {
element,
timestamp: std::time::Instant::now(),
},
);
}
}
pub fn clear(&self) {
if let Ok(mut cache) = self.cache.lock() {
cache.clear();
}
}
pub fn stats(&self) -> CacheStats {
if let Ok(cache) = self.cache.lock() {
CacheStats {
size: cache.len(),
capacity: cache.cap().get(),
}
} else {
CacheStats {
size: 0,
capacity: 0,
}
}
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub size: usize,
pub capacity: usize,
}
static GLOBAL_CACHE: std::sync::OnceLock<ElementCache> = std::sync::OnceLock::new();
pub fn global_cache() -> &'static ElementCache {
GLOBAL_CACHE.get_or_init(|| ElementCache::new(500, 5000))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_key_equality() {
let key1 = CacheKey {
pid: 123,
query: "Save".to_string(),
};
let key2 = CacheKey {
pid: 123,
query: "Save".to_string(),
};
assert_eq!(key1, key2);
}
}