use crate::types::{ClipMatch, MediaFile};
use dashmap::DashMap;
use parking_lot::RwLock;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
struct CacheEntry<T> {
value: T,
inserted_at: Instant,
ttl: Duration,
}
impl<T> CacheEntry<T> {
fn new(value: T, ttl: Duration) -> Self {
Self {
value,
inserted_at: Instant::now(),
ttl,
}
}
fn is_expired(&self) -> bool {
self.inserted_at.elapsed() > self.ttl
}
}
pub struct MediaCache {
cache: Arc<DashMap<PathBuf, CacheEntry<MediaFile>>>,
default_ttl: Duration,
}
impl MediaCache {
#[must_use]
pub fn new() -> Self {
Self {
cache: Arc::new(DashMap::new()),
default_ttl: Duration::from_secs(3600),
}
}
#[must_use]
pub fn with_ttl(ttl: Duration) -> Self {
Self {
cache: Arc::new(DashMap::new()),
default_ttl: ttl,
}
}
#[must_use]
pub fn get(&self, path: &PathBuf) -> Option<MediaFile> {
if let Some(entry) = self.cache.get(path) {
if !entry.is_expired() {
return Some(entry.value.clone());
}
drop(entry);
self.cache.remove(path);
}
None
}
pub fn insert(&self, path: PathBuf, media: MediaFile) {
let entry = CacheEntry::new(media, self.default_ttl);
self.cache.insert(path, entry);
}
pub fn insert_with_ttl(&self, path: PathBuf, media: MediaFile, ttl: Duration) {
let entry = CacheEntry::new(media, ttl);
self.cache.insert(path, entry);
}
#[must_use]
pub fn remove(&self, path: &PathBuf) -> Option<MediaFile> {
self.cache.remove(path).map(|(_, entry)| entry.value)
}
pub fn clear(&self) {
self.cache.clear();
}
pub fn cleanup_expired(&self) {
self.cache.retain(|_, entry| !entry.is_expired());
}
#[must_use]
pub fn len(&self) -> usize {
self.cache.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
}
impl Default for MediaCache {
fn default() -> Self {
Self::new()
}
}
pub struct MatchCache {
cache: Arc<RwLock<Vec<ClipMatch>>>,
}
impl MatchCache {
#[must_use]
pub fn new() -> Self {
Self {
cache: Arc::new(RwLock::new(Vec::new())),
}
}
pub fn store(&self, matches: Vec<ClipMatch>) {
*self.cache.write() = matches;
}
#[must_use]
pub fn get_all(&self) -> Vec<ClipMatch> {
self.cache.read().clone()
}
#[must_use]
pub fn find_by_clip_id(&self, clip_id: &str) -> Vec<ClipMatch> {
self.cache
.read()
.iter()
.filter(|m| m.clip.id == clip_id)
.cloned()
.collect()
}
pub fn clear(&self) {
self.cache.write().clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.cache.read().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.cache.read().is_empty()
}
}
impl Default for MatchCache {
fn default() -> Self {
Self::new()
}
}
pub struct LruMediaCache {
capacity: usize,
cache: Arc<DashMap<PathBuf, (MediaFile, Instant)>>,
}
impl LruMediaCache {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
capacity,
cache: Arc::new(DashMap::new()),
}
}
#[must_use]
pub fn get(&self, path: &PathBuf) -> Option<MediaFile> {
if let Some(mut entry) = self.cache.get_mut(path) {
entry.1 = Instant::now();
Some(entry.0.clone())
} else {
None
}
}
pub fn insert(&self, path: PathBuf, media: MediaFile) {
if self.cache.len() >= self.capacity {
self.evict_lru();
}
self.cache.insert(path, (media, Instant::now()));
}
fn evict_lru(&self) {
if let Some(entry) = self.cache.iter().min_by_key(|entry| entry.value().1) {
let oldest_path = entry.key().clone();
drop(entry);
self.cache.remove(&oldest_path);
}
}
pub fn clear(&self) {
self.cache.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.cache.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
#[must_use]
pub const fn capacity(&self) -> usize {
self.capacity
}
}
pub struct SimpleCache<K, V>
where
K: Eq + std::hash::Hash + Clone,
V: Clone,
{
cache: Arc<DashMap<K, V>>,
max_size: usize,
}
impl<K, V> SimpleCache<K, V>
where
K: Eq + std::hash::Hash + Clone,
V: Clone,
{
#[must_use]
pub fn new(max_size: usize) -> Self {
Self {
cache: Arc::new(DashMap::new()),
max_size,
}
}
#[must_use]
pub fn get(&self, key: &K) -> Option<V> {
self.cache.get(key).map(|v| v.clone())
}
pub fn insert(&self, key: K, value: V) {
if self.cache.len() >= self.max_size {
if let Some(first_key) = self.cache.iter().next().map(|entry| entry.key().clone()) {
self.cache.remove(&first_key);
}
}
self.cache.insert(key, value);
}
pub fn remove(&self, key: &K) -> Option<V> {
self.cache.remove(key).map(|(_, v)| v)
}
pub fn clear(&self) {
self.cache.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.cache.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_media_cache() {
let cache = MediaCache::new();
let path = PathBuf::from("/test/file.mov");
let media = MediaFile::new(path.clone());
cache.insert(path.clone(), media.clone());
assert_eq!(cache.len(), 1);
let retrieved = cache.get(&path);
assert!(retrieved.is_some());
let _ = cache.remove(&path);
assert_eq!(cache.len(), 0);
}
#[test]
fn test_media_cache_expiration() {
let cache = MediaCache::with_ttl(Duration::from_millis(100));
let path = PathBuf::from("/test/file.mov");
let media = MediaFile::new(path.clone());
cache.insert(path.clone(), media);
assert!(cache.get(&path).is_some());
thread::sleep(Duration::from_millis(150));
assert!(cache.get(&path).is_none());
}
#[test]
fn test_match_cache() {
let cache = MatchCache::new();
assert_eq!(cache.len(), 0);
cache.store(vec![]);
assert_eq!(cache.len(), 0);
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn test_lru_cache() {
let cache = LruMediaCache::new(2);
let path1 = PathBuf::from("/test/file1.mov");
let path2 = PathBuf::from("/test/file2.mov");
let path3 = PathBuf::from("/test/file3.mov");
cache.insert(path1.clone(), MediaFile::new(path1.clone()));
cache.insert(path2.clone(), MediaFile::new(path2.clone()));
assert_eq!(cache.len(), 2);
cache.insert(path3.clone(), MediaFile::new(path3.clone()));
assert_eq!(cache.len(), 2);
}
#[test]
fn test_simple_cache() {
let cache = SimpleCache::new(3);
cache.insert("key1", "value1");
cache.insert("key2", "value2");
assert_eq!(cache.get(&"key1"), Some("value1"));
assert_eq!(cache.get(&"key3"), None);
cache.remove(&"key1");
assert_eq!(cache.get(&"key1"), None);
}
}