Skip to main content

pagesift_api/types/
asset_cache.rs

1//! In-memory, task-scoped single-flight cache for remote image bytes.
2use std::collections::HashMap;
3use std::sync::{Arc, Mutex};
4
5use tokio::sync::OnceCell;
6
7use super::Attachment;
8
9/// Shared only by pages within one batch or crawl. Never serialized.
10#[derive(Debug, Clone, Default)]
11pub struct AssetFetchCache(Arc<Mutex<HashMap<String, Arc<OnceCell<Attachment>>>>>);
12
13impl PartialEq for AssetFetchCache {
14    fn eq(&self, other: &Self) -> bool {
15        Arc::ptr_eq(&self.0, &other.0)
16    }
17}
18
19impl AssetFetchCache {
20    /// Limit retained keys and bytes per item; uncached items still work normally.
21    pub fn entry(&self, url: &str) -> Option<Arc<OnceCell<Attachment>>> {
22        let mut entries = self.0.lock().unwrap_or_else(|e| e.into_inner());
23        if let Some(cell) = entries.get(url) {
24            return Some(cell.clone());
25        }
26        if entries.len() >= 32 {
27            return None;
28        }
29        let cell = Arc::new(OnceCell::new());
30        entries.insert(url.to_owned(), cell.clone());
31        Some(cell)
32    }
33
34    pub fn discard(&self, url: &str, cell: &Arc<OnceCell<Attachment>>) {
35        let mut entries = self.0.lock().unwrap_or_else(|e| e.into_inner());
36        if entries
37            .get(url)
38            .is_some_and(|existing| Arc::ptr_eq(existing, cell))
39        {
40            entries.remove(url);
41        }
42    }
43
44    /// Populate a task-local entry from an already persisted, verified asset.
45    pub fn seed(&self, url: &str, attachment: Attachment) {
46        if attachment.bytes > 512 * 1024 || attachment.data.is_none() {
47            return;
48        }
49        if let Some(cell) = self.entry(url) {
50            let _ = cell.set(attachment);
51        }
52    }
53}