notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 1688beec47e4e69fd96df866a880cc64f7a7d767
parent a0415bada0b0610e77314a658003722c44068d1c
Author: kernelkind <kernelkind@gmail.com>
Date:   Sat, 22 Nov 2025 20:49:51 -0700

feat(static-imgs): add StaticImgTexCache

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck/src/media/mod.rs | 1+
Acrates/notedeck/src/media/static_imgs.rs | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 173 insertions(+), 0 deletions(-)

diff --git a/crates/notedeck/src/media/mod.rs b/crates/notedeck/src/media/mod.rs @@ -5,6 +5,7 @@ pub mod images; pub mod imeta; pub mod network; pub mod renderable; +pub mod static_imgs; pub use action::{MediaAction, MediaInfo, ViewMediaInfo}; pub use blur::{ diff --git a/crates/notedeck/src/media/static_imgs.rs b/crates/notedeck/src/media/static_imgs.rs @@ -0,0 +1,172 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use egui::TextureHandle; + +use crate::{jobs::NoOutputRun, TextureState}; +use crate::{ + jobs::{ + CompleteResponse, JobOutput, JobPackage, JobRun, MediaJobKind, MediaJobResult, + MediaJobSender, RunType, + }, + ImageType, +}; +use crate::{ + media::{ + images::{buffer_to_color_image, parse_img_response}, + load_texture_checked, + network::http_req, + }, + MediaCache, +}; + +pub struct StaticImgTexCache { + pub(crate) cache: HashMap<String, TextureState<TextureHandle>>, + static_img_cache_path: PathBuf, +} + +impl StaticImgTexCache { + pub fn new(static_img_cache_path: PathBuf) -> Self { + Self { + cache: Default::default(), + static_img_cache_path, + } + } + + pub fn contains(&self, url: &str) -> bool { + self.cache.contains_key(url) + } + + pub fn get(&self, url: &str) -> Option<&TextureState<TextureHandle>> { + self.cache.get(url) + } + + pub fn request( + &self, + jobs: &MediaJobSender, + ctx: &egui::Context, + url: &str, + imgtype: ImageType, + ) { + let _ = self.get_or_request(jobs, ctx, url, imgtype); + } + + pub fn get_or_request( + &self, + jobs: &MediaJobSender, + ctx: &egui::Context, + url: &str, + imgtype: ImageType, + ) -> &TextureState<TextureHandle> { + if let Some(res) = self.cache.get(url) { + return res; + } + + let key = MediaCache::key(url); + let path = self.static_img_cache_path.join(key); + + if path.exists() { + let ctx = ctx.clone(); + let url = url.to_owned(); + if let Err(e) = jobs.send(JobPackage::new( + url.to_owned(), + MediaJobKind::StaticImg, + RunType::Output(JobRun::Sync(Box::new(move || { + JobOutput::Complete(CompleteResponse::new(MediaJobResult::StaticImg( + fetch_static_img_from_disk(ctx.clone(), &url, &path), + ))) + }))), + )) { + tracing::error!("{e}"); + } + } else { + let url = url.to_owned(); + let ctx = ctx.clone(); + if let Err(e) = jobs.send(JobPackage::new( + url.to_owned(), + MediaJobKind::StaticImg, + RunType::Output(JobRun::Async(Box::pin(fetch_static_img_from_net( + url, + ctx, + self.static_img_cache_path.clone(), + imgtype, + )))), + )) { + tracing::error!("{e}"); + } + } + + &TextureState::Pending + } +} + +pub fn fetch_static_img_from_disk( + ctx: egui::Context, + url: &str, + path: &Path, +) -> Result<egui::TextureHandle, crate::Error> { + tracing::trace!("Starting job static img from disk for {url}"); + let data = std::fs::read(path)?; + let image_buffer = image::load_from_memory(&data).map_err(crate::Error::Image); + + let image_buffer = match image_buffer { + Ok(i) => i, + Err(e) => { + tracing::error!("could not load img buffer"); + return Err(e); + } + }; + + let img = buffer_to_color_image( + image_buffer.as_flat_samples_u8(), + image_buffer.width(), + image_buffer.height(), + ); + + Ok(load_texture_checked(&ctx, url, img, Default::default())) +} + +async fn fetch_static_img_from_net( + url: String, + ctx: egui::Context, + path: PathBuf, + imgtype: ImageType, +) -> JobOutput<MediaJobResult> { + tracing::trace!("fetch static img from net: starting job. sending http request for {url}"); + let res = match http_req(&url).await { + Ok(r) => r, + Err(e) => { + return JobOutput::complete(MediaJobResult::StaticImg(Err(crate::Error::Generic( + format!("Http error: {e}"), + )))); + } + }; + + tracing::trace!("static img from net: parsing http request from {url}"); + JobOutput::Next(JobRun::Sync(Box::new(move || { + let img = match parse_img_response(res, imgtype) { + Ok(i) => i, + Err(e) => { + return JobOutput::Complete(CompleteResponse::new(MediaJobResult::StaticImg(Err( + e, + )))) + } + }; + + let texture_handle = + load_texture_checked(&ctx, url.clone(), img.clone(), Default::default()); + + JobOutput::Complete( + CompleteResponse::new(MediaJobResult::StaticImg(Ok(texture_handle))).run_no_output( + NoOutputRun::Sync(Box::new(move || { + tracing::trace!("static img from net: Saving output from {url}"); + if let Err(e) = MediaCache::write(&path, &url, img) { + tracing::error!("{e}"); + } + })), + ), + ) + }))) +}