use crate::model::cover;
use bezel::gpui::{
App, AppContext as _, Asset as _, AssetLogger, Entity, Global, ImageAssetLoader, ImageCache,
ImageCacheError, ImageCacheItem, RenderImage, Resource, Window, hash,
};
use futures::FutureExt as _;
use lru::LruCache;
use std::{num::NonZeroUsize, sync::Arc};
pub const DEFAULT_LIMIT: u64 = 200 * 1_000_000;
pub struct Cache {
pub covers: Entity<Covers>,
}
impl Global for Cache {}
pub struct Covers(LruCache<u64, ImageCacheItem>);
fn capacity(limit: u64) -> NonZeroUsize {
NonZeroUsize::new((limit / cover::RASTER_BYTES).max(1) as usize).expect("max(1) is non-zero")
}
impl Covers {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn limit(&self) -> u64 {
self.0.cap().get() as u64 * cover::RASTER_BYTES
}
pub fn set_limit(&mut self, limit: u64, window: &mut Window, cx: &mut App) {
let cap = capacity(limit);
while self.0.len() > cap.get() {
let Some((_, mut item)) = self.0.pop_lru() else {
break;
};
drop_frame(&mut item, window, cx);
}
self.0.resize(cap);
}
}
fn drop_frame(item: &mut ImageCacheItem, window: &mut Window, cx: &mut App) {
if let Some(Ok(image)) = item.get() {
cx.drop_image(image, Some(window));
}
}
impl ImageCache for Covers {
fn load(
&mut self,
resource: &Resource,
window: &mut Window,
cx: &mut App,
) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
let key = hash(resource);
if let Some(item) = self.0.get_mut(&key) {
return item.get();
}
let load = AssetLogger::<ImageAssetLoader>::load(resource.clone(), cx);
let task = cx.background_executor().spawn(load).shared();
if let Some((_, mut evicted)) = self.0.push(key, ImageCacheItem::Loading(task.clone())) {
drop_frame(&mut evicted, window, cx);
}
let view = window.current_view();
window
.spawn(cx, async move |cx| {
let _ = task.await;
cx.on_next_frame(move |_, cx| cx.notify(view));
})
.detach();
None
}
}
pub fn init(limit: u64, cx: &mut App) {
let covers = cx.new(|_| Covers(LruCache::new(capacity(limit))));
cx.set_global(Cache { covers });
}
pub fn covers(cx: &App) -> Entity<Covers> {
cx.global::<Cache>().covers.clone()
}