notedeck

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

commit 4413e185072a7b960b6f018bd2dc2eb4e479ac88
parent 3b9d3338499bace951a2ba4c487ff892992419e2
Author: kernelkind <kernelkind@gmail.com>
Date:   Sat, 22 Nov 2025 22:31:11 -0700

refactor: remove unnecessary old JobsCache code

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

Diffstat:
Mcrates/notedeck/src/imgcache.rs | 268+------------------------------------------------------------------------------
Dcrates/notedeck/src/jobs/cache_old.rs | 154-------------------------------------------------------------------------------
Mcrates/notedeck/src/jobs/mod.rs | 4----
Mcrates/notedeck/src/lib.rs | 12+++++-------
Mcrates/notedeck/src/media/blur.rs | 31++-----------------------------
Mcrates/notedeck/src/media/gif.rs | 159+------------------------------------------------------------------------------
Mcrates/notedeck/src/media/images.rs | 290+------------------------------------------------------------------------------
Mcrates/notedeck/src/media/mod.rs | 4++--
Mcrates/notedeck_ui/src/note/media.rs | 153+------------------------------------------------------------------------------
9 files changed, 15 insertions(+), 1060 deletions(-)

diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs @@ -1,5 +1,5 @@ use crate::jobs::MediaJobSender; -use crate::media::gif::{ensure_latest_texture_from_cache, AnimatedImgTexCache}; +use crate::media::gif::AnimatedImgTexCache; use crate::media::images::ImageType; use crate::media::static_imgs::StaticImgTexCache; use crate::media::{ @@ -12,13 +12,11 @@ use crate::RenderableMedia; use crate::Result; use egui::TextureHandle; use image::{Delay, Frame}; -use poll_promise::Promise; use egui::ColorImage; use std::collections::HashMap; use std::fs::{self, create_dir_all, File}; -use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant, SystemTime}; use std::{io, thread}; @@ -29,85 +27,6 @@ use std::path::PathBuf; use std::path::{self, Path}; use tracing::warn; -#[derive(Default)] -pub struct TexturesCacheOld { - pub cache: hashbrown::HashMap<String, TextureStateInternal>, -} - -impl TexturesCacheOld { - pub fn handle_and_get_or_insert_loadable( - &mut self, - url: &str, - closure: impl FnOnce() -> Promise<Option<Result<TexturedImage>>>, - ) -> LoadableTextureState<'_> { - let internal = self.handle_and_get_state_internal(url, true, closure); - - internal.into() - } - - pub fn handle_and_get_or_insert( - &mut self, - url: &str, - closure: impl FnOnce() -> Promise<Option<Result<TexturedImage>>>, - ) -> TextureStateOld<'_> { - let internal = self.handle_and_get_state_internal(url, false, closure); - - internal.into() - } - - fn handle_and_get_state_internal( - &mut self, - url: &str, - use_loading: bool, - closure: impl FnOnce() -> Promise<Option<Result<TexturedImage>>>, - ) -> &mut TextureStateInternal { - let state = match self.cache.raw_entry_mut().from_key(url) { - hashbrown::hash_map::RawEntryMut::Occupied(entry) => { - let state = entry.into_mut(); - handle_occupied(state, use_loading); - - state - } - hashbrown::hash_map::RawEntryMut::Vacant(entry) => { - let res = closure(); - let (_, state) = entry.insert(url.to_owned(), TextureStateInternal::Pending(res)); - - state - } - }; - - state - } - - pub fn insert_pending(&mut self, url: &str, promise: Promise<Option<Result<TexturedImage>>>) { - self.cache - .insert(url.to_owned(), TextureStateInternal::Pending(promise)); - } - - pub fn move_to_loaded(&mut self, url: &str) { - let hashbrown::hash_map::RawEntryMut::Occupied(entry) = - self.cache.raw_entry_mut().from_key(url) - else { - return; - }; - - entry.replace_entry_with(|_, v| { - let TextureStateInternal::Loading(textured) = v else { - return Some(v); - }; - - Some(TextureStateInternal::Loaded(textured)) - }); - } - - pub fn get_and_handle(&mut self, url: &str) -> Option<LoadableTextureState<'_>> { - self.cache.get_mut(url).map(|state| { - handle_occupied(state, true); - state.into() - }) - } -} - pub struct TexturesCache { pub static_image: StaticImgTexCache, pub blurred: BlurCache, @@ -128,55 +47,6 @@ impl TexturesCache { } } -fn handle_occupied(state: &mut TextureStateInternal, use_loading: bool) { - let TextureStateInternal::Pending(promise) = state else { - return; - }; - - let Some(res) = promise.ready_mut() else { - return; - }; - - let Some(res) = res.take() else { - tracing::error!("Failed to take the promise"); - *state = - TextureStateInternal::Error(crate::Error::Generic("Promise already taken".to_owned())); - return; - }; - - match res { - Ok(textured) => { - *state = if use_loading { - TextureStateInternal::Loading(textured) - } else { - TextureStateInternal::Loaded(textured) - } - } - Err(e) => *state = TextureStateInternal::Error(e), - } -} - -pub enum LoadableTextureState<'a> { - Pending, - Error(&'a crate::Error), - Loading { - actual_image_tex: &'a mut TexturedImage, - }, // the texture is in the loading state, for transitioning between the pending and loaded states - Loaded(&'a mut TexturedImage), -} - -pub enum TextureStateOld<'a> { - Pending, - Error(&'a crate::Error), - Loaded(&'a mut TexturedImage), -} - -impl<'a> TextureStateOld<'a> { - pub fn is_loaded(&self) -> bool { - matches!(self, Self::Loaded(_)) - } -} - pub enum TextureState<T> { Pending, Error(crate::Error), @@ -193,75 +63,6 @@ impl<T> std::fmt::Debug for TextureState<T> { } } -impl<'a> From<&'a mut TextureStateInternal> for TextureStateOld<'a> { - fn from(value: &'a mut TextureStateInternal) -> Self { - match value { - TextureStateInternal::Pending(_) => TextureStateOld::Pending, - TextureStateInternal::Error(error) => TextureStateOld::Error(error), - TextureStateInternal::Loading(textured_image) => { - TextureStateOld::Loaded(textured_image) - } - TextureStateInternal::Loaded(textured_image) => TextureStateOld::Loaded(textured_image), - } - } -} - -pub enum TextureStateInternal { - Pending(Promise<Option<Result<TexturedImage>>>), - Error(crate::Error), - Loading(TexturedImage), // the image is in the loading state, for transitioning between blur and image - Loaded(TexturedImage), -} - -impl<'a> From<&'a mut TextureStateInternal> for LoadableTextureState<'a> { - fn from(value: &'a mut TextureStateInternal) -> Self { - match value { - TextureStateInternal::Pending(_) => LoadableTextureState::Pending, - TextureStateInternal::Error(error) => LoadableTextureState::Error(error), - TextureStateInternal::Loading(textured_image) => LoadableTextureState::Loading { - actual_image_tex: textured_image, - }, - TextureStateInternal::Loaded(textured_image) => { - LoadableTextureState::Loaded(textured_image) - } - } - } -} - -pub enum TexturedImage { - Static(TextureHandle), - Animated(AnimationOld), -} - -impl TexturedImage { - pub fn get_first_texture(&self) -> &TextureHandle { - match self { - TexturedImage::Static(texture_handle) => texture_handle, - TexturedImage::Animated(animation) => &animation.first_frame.texture, - } - } -} - -pub struct AnimationOld { - pub first_frame: TextureFrame, - pub other_frames: Vec<TextureFrame>, - pub receiver: Option<Receiver<TextureFrame>>, -} - -impl AnimationOld { - pub fn get_frame(&self, index: usize) -> Option<&TextureFrame> { - if index == 0 { - Some(&self.first_frame) - } else { - self.other_frames.get(index - 1) - } - } - - pub fn num_frames(&self) -> usize { - self.other_frames.len() + 1 - } -} - pub struct Animation { pub first_frame: TextureFrame, pub other_frames: Vec<TextureFrame>, @@ -293,7 +94,6 @@ pub struct ImageFrame { pub struct MediaCache { pub cache_dir: path::PathBuf, - pub textures_cache: TexturesCacheOld, pub cache_type: MediaCacheType, pub cache_size: Arc<Mutex<Option<u64>>>, } @@ -327,7 +127,6 @@ impl MediaCache { Self { cache_dir, - textures_cache: TexturesCacheOld::default(), cache_type, cache_size, } @@ -431,7 +230,6 @@ impl MediaCache { } fn clear(&mut self) { - self.textures_cache.cache.clear(); *self.cache_size.try_lock().unwrap() = Some(0); } } @@ -523,42 +321,6 @@ impl Images { }) } - pub fn latest_texture_old( - &mut self, - ui: &mut egui::Ui, - url: &str, - img_type: ImageType, - animation_mode: AnimationMode, - ) -> Option<TextureHandle> { - let cache_type = crate::urls::supported_mime_hosted_at_url(&mut self.urls, url)?; - - let cache_dir = self.get_cache(cache_type).cache_dir.clone(); - let is_loaded = self - .get_cache_mut(cache_type) - .textures_cache - .handle_and_get_or_insert(url, || { - crate::media::images::fetch_img(&cache_dir, ui.ctx(), url, img_type, cache_type) - }) - .is_loaded(); - - if !is_loaded { - return None; - } - - let cache = match cache_type { - MediaCacheType::Image => &mut self.static_imgs, - MediaCacheType::Gif => &mut self.gifs, - }; - - ensure_latest_texture_from_cache( - ui, - url, - &mut self.gif_states, - &mut cache.textures_cache, - animation_mode, - ) - } - pub fn latest_texture<'a>( &'a mut self, jobs: &MediaJobSender, @@ -655,31 +417,3 @@ pub struct LatestTexture { pub texture: TextureHandle, pub request_next_repaint: Option<SystemTime>, } - -#[profiling::function] -pub fn get_render_state<'a>( - ctx: &egui::Context, - images: &'a mut Images, - cache_type: MediaCacheType, - url: &str, - img_type: ImageType, -) -> RenderState<'a> { - let cache = match cache_type { - MediaCacheType::Image => &mut images.static_imgs, - MediaCacheType::Gif => &mut images.gifs, - }; - - let texture_state = cache.textures_cache.handle_and_get_or_insert(url, || { - crate::media::images::fetch_img(&cache.cache_dir, ctx, url, img_type, cache_type) - }); - - RenderState { - texture_state, - gifs: &mut images.gif_states, - } -} - -pub struct RenderState<'a> { - pub texture_state: TextureStateOld<'a>, - pub gifs: &'a mut GifStateMap, -} diff --git a/crates/notedeck/src/jobs/cache_old.rs b/crates/notedeck/src/jobs/cache_old.rs @@ -1,154 +0,0 @@ -use egui::TextureHandle; -use hashbrown::{hash_map::RawEntryMut, HashMap}; -use poll_promise::Promise; - -use crate::jobs::JobPool; - -#[derive(Default)] -pub struct JobsCacheOld { - jobs: HashMap<JobIdOwned, JobState>, -} - -pub enum JobState { - Pending(Promise<Option<Result<Job, JobError>>>), - Error(JobError), - Completed(Job), -} - -pub enum JobError { - InvalidParameters, -} - -#[derive(Debug)] -pub enum JobParams<'a> { - Blurhash(BlurhashParams<'a>), -} - -#[derive(Debug)] -pub enum JobParamsOwned { - Blurhash(BlurhashParamsOwned), -} - -impl<'a> From<BlurhashParams<'a>> for BlurhashParamsOwned { - fn from(params: BlurhashParams<'a>) -> Self { - BlurhashParamsOwned { - blurhash: params.blurhash.to_owned(), - url: params.url.to_owned(), - ctx: params.ctx.clone(), - } - } -} - -impl<'a> From<JobParams<'a>> for JobParamsOwned { - fn from(params: JobParams<'a>) -> Self { - match params { - JobParams::Blurhash(bp) => JobParamsOwned::Blurhash(bp.into()), - } - } -} - -#[derive(Debug)] -pub struct BlurhashParams<'a> { - pub blurhash: &'a str, - pub url: &'a str, - pub ctx: &'a egui::Context, -} - -#[derive(Debug)] -pub struct BlurhashParamsOwned { - pub blurhash: String, - pub url: String, - pub ctx: egui::Context, -} - -impl JobsCacheOld { - pub fn get_or_insert_with< - 'a, - F: FnOnce(Option<JobParamsOwned>) -> Result<Job, JobError> + Send + 'static, - >( - &'a mut self, - job_pool: &mut JobPool, - jobid: &JobIdOld, - params: Option<JobParams>, - run_job: F, - ) -> &'a mut JobState { - match self.jobs.raw_entry_mut().from_key(jobid) { - RawEntryMut::Occupied(entry) => 's: { - let mut state = entry.into_mut(); - - let JobState::Pending(promise) = &mut state else { - break 's state; - }; - - let Some(res) = promise.ready_mut() else { - break 's state; - }; - - let Some(res) = res.take() else { - tracing::error!("Failed to take the promise for job: {:?}", jobid); - break 's state; - }; - - *state = match res { - Ok(j) => JobState::Completed(j), - Err(e) => JobState::Error(e), - }; - - state - } - RawEntryMut::Vacant(entry) => { - let owned_params = params.map(JobParams::into); - let wrapped: Box<dyn FnOnce() -> Option<Result<Job, JobError>> + Send + 'static> = - Box::new(move || Some(run_job(owned_params))); - - let promise = Promise::spawn_async(job_pool.schedule(wrapped)); - - let (_, state) = entry.insert(jobid.into(), JobState::Pending(promise)); - - state - } - } - } - - pub fn get(&self, jobid: &JobIdOld) -> Option<&JobState> { - self.jobs.get(jobid) - } -} - -impl<'a> From<&JobIdOld<'a>> for JobIdOwned { - fn from(jobid: &JobIdOld<'a>) -> Self { - match jobid { - JobIdOld::Blurhash(s) => JobIdOwned::Blurhash(s.to_string()), - } - } -} - -impl hashbrown::Equivalent<JobIdOwned> for JobIdOld<'_> { - fn equivalent(&self, key: &JobIdOwned) -> bool { - match (self, key) { - (JobIdOld::Blurhash(a), JobIdOwned::Blurhash(b)) => *a == b.as_str(), - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -enum JobIdOwned { - Blurhash(String), // image URL -} - -#[derive(Debug, Hash)] -pub enum JobIdOld<'a> { - Blurhash(&'a str), // image URL -} - -pub enum Job { - Blurhash(Option<TextureHandle>), -} - -impl std::fmt::Debug for Job { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Job::Blurhash(_) => write!(f, "Blurhash"), - } - } -} diff --git a/crates/notedeck/src/jobs/mod.rs b/crates/notedeck/src/jobs/mod.rs @@ -1,5 +1,4 @@ mod cache; -mod cache_old; mod job_pool; mod media; pub(crate) mod types; @@ -8,9 +7,6 @@ pub use crate::jobs::types::{ CompleteResponse, JobOutput, JobPackage, JobRun, NoOutputRun, RunType, }; pub use cache::JobCache; -pub use cache_old::{ - BlurhashParams, Job, JobError, JobIdOld, JobParams, JobParamsOwned, JobState, JobsCacheOld, -}; pub use job_pool::JobPool; pub use media::{ deliver_completed_media_job, run_media_job_pre_action, MediaJobKind, MediaJobResult, diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs @@ -54,18 +54,16 @@ pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; pub use i18n::{CacheStats, FluentArgs, FluentValue, LanguageIdentifier, Localization}; pub use imgcache::{ - get_render_state, Animation, AnimationOld, GifState, GifStateMap, ImageFrame, Images, - LatestTexture, LoadableTextureState, MediaCache, MediaCacheType, RenderState, TextureFrame, - TextureState, TextureStateOld, TexturedImage, TexturesCache, TexturesCacheOld, + Animation, GifState, GifStateMap, ImageFrame, Images, LatestTexture, MediaCache, + MediaCacheType, TextureFrame, TextureState, TexturesCache, }; pub use jobs::{ - deliver_completed_media_job, run_media_job_pre_action, BlurhashParams, Job, JobCache, JobError, - JobIdOld, JobParams, JobParamsOwned, JobPool, JobState, JobsCacheOld, MediaJobSender, + deliver_completed_media_job, run_media_job_pre_action, JobCache, JobPool, MediaJobSender, MediaJobs, }; pub use media::{ - compute_blurhash, update_imeta_blurhashes, ImageMetadata, ImageType, MediaAction, - ObfuscationType, PixelDimensions, PointDimensions, RenderableMedia, + update_imeta_blurhashes, ImageMetadata, ImageType, MediaAction, ObfuscationType, + PixelDimensions, PointDimensions, RenderableMedia, }; pub use muted::{MuteFun, Muted}; pub use name::NostrName; diff --git a/crates/notedeck/src/media/blur.rs b/crates/notedeck/src/media/blur.rs @@ -5,8 +5,8 @@ use nostrdb::Note; use crate::{ jobs::{ - CompleteResponse, Job, JobError, JobOutput, JobPackage, JobParamsOwned, JobRun, - MediaJobKind, MediaJobResult, MediaJobSender, RunType, + CompleteResponse, JobOutput, JobPackage, JobRun, MediaJobKind, MediaJobResult, + MediaJobSender, RunType, }, media::load_texture_checked, TextureState, @@ -170,33 +170,6 @@ pub enum ObfuscationType { Default, } -pub fn compute_blurhash( - params: Option<JobParamsOwned>, - dims: PixelDimensions, -) -> Result<Job, JobError> { - #[allow(irrefutable_let_patterns)] - let Some(JobParamsOwned::Blurhash(params)) = params - else { - return Err(JobError::InvalidParameters); - }; - - let maybe_handle = match generate_blurhash_texturehandle( - &params.ctx, - &params.blurhash, - &params.url, - dims.x, - dims.y, - ) { - Ok(tex) => Some(tex), - Err(e) => { - tracing::error!("failed to render blurhash: {e}"); - None - } - }; - - Ok(Job::Blurhash(maybe_handle)) -} - fn generate_blurhash_texturehandle( ctx: &egui::Context, blurhash: &str, diff --git a/crates/notedeck/src/media/gif.rs b/crates/notedeck/src/media/gif.rs @@ -2,10 +2,10 @@ use std::{ collections::{HashMap, VecDeque}, io::Cursor, path::PathBuf, - sync::mpsc::TryRecvError, time::{Instant, SystemTime}, }; +use crate::GifState; use crate::{ jobs::{ CompleteResponse, JobOutput, JobPackage, JobRun, MediaJobKind, MediaJobResult, @@ -15,125 +15,13 @@ use crate::{ images::{buffer_to_color_image, process_image}, load_texture_checked, }, - AnimationOld, Error, ImageFrame, ImageType, MediaCache, TextureFrame, TextureState, + Error, ImageFrame, ImageType, MediaCache, TextureFrame, TextureState, }; use crate::{media::AnimationMode, Animation}; -use crate::{GifState, GifStateMap, TextureStateOld, TexturedImage, TexturesCacheOld}; use egui::{ColorImage, TextureHandle}; use image::{codecs::gif::GifDecoder, AnimationDecoder, DynamicImage, Frame}; use std::time::Duration; -pub fn ensure_latest_texture_from_cache( - ui: &egui::Ui, - url: &str, - gifs: &mut GifStateMap, - textures: &mut TexturesCacheOld, - animation_mode: AnimationMode, -) -> Option<TextureHandle> { - let tstate = textures.cache.get_mut(url)?; - - let TextureStateOld::Loaded(img) = tstate.into() else { - return None; - }; - - Some(ensure_latest_texture(ui, url, gifs, img, animation_mode)) -} - -struct ProcessedGifFrameOld { - texture: TextureHandle, - maybe_new_state: Option<GifState>, - repaint_at: Option<SystemTime>, -} - -/// Process a gif state frame, and optionally present a new -/// state and when to repaint it -fn process_gif_frame_old( - animation: &AnimationOld, - frame_state: Option<&GifState>, - animation_mode: AnimationMode, -) -> ProcessedGifFrameOld { - let now = Instant::now(); - - match frame_state { - Some(prev_state) => { - let should_advance = animation_mode.can_animate() - && (now - prev_state.last_frame_rendered >= prev_state.last_frame_duration); - - if should_advance { - let maybe_new_index = if animation.receiver.is_some() - || prev_state.last_frame_index < animation.num_frames() - 1 - { - prev_state.last_frame_index + 1 - } else { - 0 - }; - - match animation.get_frame(maybe_new_index) { - Some(frame) => { - let next_frame_time = match animation_mode { - AnimationMode::Continuous { fps } => match fps { - Some(fps) => { - let max_delay_ms = Duration::from_millis((1000.0 / fps) as u64); - SystemTime::now().checked_add(frame.delay.max(max_delay_ms)) - } - None => SystemTime::now().checked_add(frame.delay), - }, - - AnimationMode::NoAnimation | AnimationMode::Reactive => None, - }; - - ProcessedGifFrameOld { - texture: frame.texture.clone(), - maybe_new_state: Some(GifState { - last_frame_rendered: now, - last_frame_duration: frame.delay, - next_frame_time, - last_frame_index: maybe_new_index, - }), - repaint_at: next_frame_time, - } - } - None => { - let (texture, maybe_new_state) = - match animation.get_frame(prev_state.last_frame_index) { - Some(frame) => (frame.texture.clone(), None), - None => (animation.first_frame.texture.clone(), None), - }; - - ProcessedGifFrameOld { - texture, - maybe_new_state, - repaint_at: prev_state.next_frame_time, - } - } - } - } else { - let (texture, maybe_new_state) = - match animation.get_frame(prev_state.last_frame_index) { - Some(frame) => (frame.texture.clone(), None), - None => (animation.first_frame.texture.clone(), None), - }; - - ProcessedGifFrameOld { - texture, - maybe_new_state, - repaint_at: prev_state.next_frame_time, - } - } - } - None => ProcessedGifFrameOld { - texture: animation.first_frame.texture.clone(), - maybe_new_state: Some(GifState { - last_frame_rendered: now, - last_frame_duration: animation.first_frame.delay, - next_frame_time: None, - last_frame_index: 0, - }), - repaint_at: None, - }, - } -} - pub(crate) struct ProcessedGifFrame<'a> { pub texture: &'a TextureHandle, pub maybe_new_state: Option<GifState>, @@ -221,49 +109,6 @@ pub(crate) fn process_gif_frame<'a>( } } -pub fn ensure_latest_texture( - ui: &egui::Ui, - url: &str, - gifs: &mut GifStateMap, - img: &mut TexturedImage, - animation_mode: AnimationMode, -) -> TextureHandle { - match img { - TexturedImage::Static(handle) => handle.clone(), - TexturedImage::Animated(animation) => { - if let Some(receiver) = &animation.receiver { - loop { - match receiver.try_recv() { - Ok(frame) => animation.other_frames.push(frame), - Err(TryRecvError::Empty) => { - break; - } - Err(TryRecvError::Disconnected) => { - animation.receiver = None; - break; - } - } - } - } - - let next_state = process_gif_frame_old(animation, gifs.get(url), animation_mode); - - if let Some(new_state) = next_state.maybe_new_state { - gifs.insert(url.to_owned(), new_state); - } - - if let Some(repaint) = next_state.repaint_at { - tracing::trace!("requesting repaint for {url} after {repaint:?}"); - if let Ok(dur) = repaint.duration_since(SystemTime::now()) { - ui.ctx().request_repaint_after(dur); - } - } - - next_state.texture - } - } -} - pub struct AnimatedImgTexCache { pub(crate) cache: HashMap<String, TextureState<Animation>>, animated_img_cache_path: PathBuf, diff --git a/crates/notedeck/src/media/images.rs b/crates/notedeck/src/media/images.rs @@ -1,20 +1,8 @@ -use crate::media::load_texture_checked; use crate::media::network::HyperHttpResponse; -use crate::{AnimationOld, ImageFrame, MediaCache, MediaCacheType, TextureFrame, TexturedImage}; -use egui::{pos2, Color32, ColorImage, Context, Rect, Sense, SizeHint}; -use image::codecs::gif::GifDecoder; +use egui::{pos2, Color32, ColorImage, Rect, Sense, SizeHint}; use image::imageops::FilterType; -use image::{AnimationDecoder, DynamicImage, FlatSamples, Frame}; -use poll_promise::Promise; -use std::collections::VecDeque; -use std::io::Cursor; +use image::FlatSamples; use std::path::PathBuf; -use std::path::{self, Path}; -use std::sync::mpsc; -use std::sync::mpsc::SyncSender; -use std::thread; -use std::time::Duration; -use tokio::fs; // NOTE(jb55): chatgpt wrote this because I was too dumb to pub fn aspect_fill( @@ -185,34 +173,6 @@ pub fn process_image(imgtyp: ImageType, mut image: image::DynamicImage) -> Color } #[profiling::function] -fn parse_img_response_old( - response: ehttp::Response, - imgtyp: ImageType, -) -> Result<ColorImage, crate::Error> { - let content_type = response.content_type().unwrap_or_default(); - let size_hint = match imgtyp { - ImageType::Profile(size) => SizeHint::Size(size, size), - ImageType::Content(Some((w, h))) => SizeHint::Size(w, h), - ImageType::Content(None) => SizeHint::default(), - }; - - if content_type.starts_with("image/svg") { - profiling::scope!("load_svg"); - - let mut color_image = - egui_extras::image::load_svg_bytes_with_size(&response.bytes, Some(size_hint))?; - round_image(&mut color_image); - Ok(color_image) - } else if content_type.starts_with("image/") { - profiling::scope!("load_from_memory"); - let dyn_image = image::load_from_memory(&response.bytes)?; - Ok(process_image(imgtyp, dyn_image)) - } else { - Err(format!("Expected image, found content-type {content_type:?}").into()) - } -} - -#[profiling::function] pub fn parse_img_response( response: HyperHttpResponse, imgtyp: ImageType, @@ -240,166 +200,6 @@ pub fn parse_img_response( } } -fn fetch_img_from_disk( - ctx: &egui::Context, - url: &str, - path: &path::Path, - cache_type: MediaCacheType, -) -> Promise<Option<Result<TexturedImage, crate::Error>>> { - let ctx = ctx.clone(); - let url = url.to_owned(); - let path = path.to_owned(); - - Promise::spawn_async(async move { - Some(async_fetch_img_from_disk(ctx, url, &path, cache_type).await) - }) -} - -async fn async_fetch_img_from_disk( - ctx: egui::Context, - url: String, - path: &path::Path, - cache_type: MediaCacheType, -) -> Result<TexturedImage, crate::Error> { - match cache_type { - MediaCacheType::Image => { - let data = fs::read(path).await?; - let image_buffer = image::load_from_memory(&data).map_err(crate::Error::Image)?; - - let img = buffer_to_color_image( - image_buffer.as_flat_samples_u8(), - image_buffer.width(), - image_buffer.height(), - ); - Ok(TexturedImage::Static(load_texture_checked( - &ctx, - &url, - img, - Default::default(), - ))) - } - MediaCacheType::Gif => { - let gif_bytes = fs::read(path).await?; // Read entire file into a Vec<u8> - generate_gif(ctx, url, path, gif_bytes, false, |i| { - buffer_to_color_image(i.as_flat_samples_u8(), i.width(), i.height()) - }) - } - } -} - -fn generate_gif( - ctx: egui::Context, - url: String, - path: &path::Path, - data: Vec<u8>, - write_to_disk: bool, - process_to_egui: impl Fn(DynamicImage) -> ColorImage + Send + Copy + 'static, -) -> Result<TexturedImage, crate::Error> { - let decoder = { - let reader = Cursor::new(data.as_slice()); - GifDecoder::new(reader)? - }; - let (tex_input, tex_output) = mpsc::sync_channel(4); - let (maybe_encoder_input, maybe_encoder_output) = if write_to_disk { - let (inp, out) = mpsc::sync_channel(4); - (Some(inp), Some(out)) - } else { - (None, None) - }; - - let mut frames: VecDeque<Frame> = decoder - .into_frames() - .collect::<std::result::Result<VecDeque<_>, image::ImageError>>() - .map_err(|e| crate::Error::Generic(e.to_string()))?; - - let first_frame = frames.pop_front().map(|frame| { - generate_animation_frame( - &ctx, - &url, - 0, - frame, - maybe_encoder_input.as_ref(), - process_to_egui, - ) - }); - - let cur_url = url.clone(); - thread::spawn(move || { - for (index, frame) in frames.into_iter().enumerate() { - let texture_frame = generate_animation_frame( - &ctx, - &cur_url, - index, - frame, - maybe_encoder_input.as_ref(), - process_to_egui, - ); - - if tex_input.send(texture_frame).is_err() { - //tracing::debug!("AnimationTextureFrame mpsc stopped abruptly"); - break; - } - } - }); - - if let Some(encoder_output) = maybe_encoder_output { - let path = path.to_owned(); - - thread::spawn(move || { - let mut imgs = Vec::new(); - while let Ok(img) = encoder_output.recv() { - imgs.push(img); - } - - if let Err(e) = MediaCache::write_gif(&path, &url, imgs) { - tracing::error!("Could not write gif to disk: {e}"); - } - }); - } - - first_frame.map_or_else( - || { - Err(crate::Error::Generic( - "first frame not found for gif".to_owned(), - )) - }, - |first_frame| { - Ok(TexturedImage::Animated(AnimationOld { - other_frames: Default::default(), - receiver: Some(tex_output), - first_frame, - })) - }, - ) -} - -fn generate_animation_frame( - ctx: &egui::Context, - url: &str, - index: usize, - frame: image::Frame, - maybe_encoder_input: Option<&SyncSender<ImageFrame>>, - process_to_egui: impl Fn(DynamicImage) -> ColorImage + Send + 'static, -) -> TextureFrame { - let delay = Duration::from(frame.delay()); - let img = DynamicImage::ImageRgba8(frame.into_buffer()); - let color_img = process_to_egui(img); - - if let Some(sender) = maybe_encoder_input { - if let Err(e) = sender.send(ImageFrame { - delay, - image: color_img.clone(), - }) { - tracing::error!("ImageFrame mpsc unexpectedly closed: {e}"); - } - } - - TextureFrame { - delay, - texture: load_texture_checked(ctx, format!("{url}{index}"), color_img, Default::default()), - } -} - pub fn buffer_to_color_image( samples: Option<FlatSamples<&[u8]>>, width: u32, @@ -422,89 +222,3 @@ pub enum ImageType { /// Content Image with optional size hint Content(Option<(u32, u32)>), } - -pub fn fetch_img( - img_cache_path: &Path, - ctx: &egui::Context, - url: &str, - imgtyp: ImageType, - cache_type: MediaCacheType, -) -> Promise<Option<Result<TexturedImage, crate::Error>>> { - let key = MediaCache::key(url); - let path = img_cache_path.join(key); - - if path.exists() { - fetch_img_from_disk(ctx, url, &path, cache_type) - } else { - fetch_img_from_net(img_cache_path, ctx, url, imgtyp, cache_type) - } - - // TODO: fetch image from local cache -} - -fn fetch_img_from_net( - cache_path: &path::Path, - ctx: &egui::Context, - url: &str, - imgtyp: ImageType, - cache_type: MediaCacheType, -) -> Promise<Option<Result<TexturedImage, crate::Error>>> { - let (sender, promise) = Promise::new(); - let request = ehttp::Request::get(url); - let ctx = ctx.clone(); - let cloned_url = url.to_owned(); - let cache_path = cache_path.to_owned(); - ehttp::fetch(request, move |response| { - let handle = response.map_err(crate::Error::Generic).and_then(|resp| { - match cache_type { - MediaCacheType::Image => { - let img = parse_img_response_old(resp, imgtyp); - img.map(|img| { - let texture_handle = load_texture_checked( - &ctx, - &cloned_url, - img.clone(), - Default::default(), - ); - - // write to disk - std::thread::spawn(move || { - MediaCache::write(&cache_path, &cloned_url, img) - }); - - TexturedImage::Static(texture_handle) - }) - } - MediaCacheType::Gif => { - let gif_bytes = resp.bytes; - generate_gif( - ctx.clone(), - cloned_url, - &cache_path, - gif_bytes, - true, - move |img| process_image(imgtyp, img), - ) - } - } - }); - - sender.send(Some(handle)); // send the results back to the UI thread. - ctx.request_repaint(); - }); - - promise -} - -pub fn fetch_no_pfp_promise( - ctx: &Context, - cache: &MediaCache, -) -> Promise<Option<Result<TexturedImage, crate::Error>>> { - crate::media::images::fetch_img( - &cache.cache_dir, - ctx, - crate::profile::no_pfp_url(), - ImageType::Profile(128), - MediaCacheType::Image, - ) -} diff --git a/crates/notedeck/src/media/mod.rs b/crates/notedeck/src/media/mod.rs @@ -10,8 +10,8 @@ pub mod static_imgs; pub use action::{MediaAction, MediaInfo, ViewMediaInfo}; pub use blur::{ - compute_blurhash, update_imeta_blurhashes, BlurCache, ImageMetadata, ObfuscationType, - PixelDimensions, PointDimensions, + update_imeta_blurhashes, BlurCache, ImageMetadata, ObfuscationType, PixelDimensions, + PointDimensions, }; use egui::{ColorImage, TextureHandle}; pub use images::ImageType; diff --git a/crates/notedeck_ui/src/note/media.rs b/crates/notedeck_ui/src/note/media.rs @@ -1,15 +1,10 @@ -use std::path::Path; - use bitflags::bitflags; use egui::{ vec2, Button, Color32, Context, CornerRadius, FontId, Image, InnerResponse, Response, TextureHandle, Vec2, }; use notedeck::media::latest::ObfuscatedTexture; -use notedeck::{ - compute_blurhash, BlurhashParams, Job, JobIdOld, JobParams, JobPool, JobState, JobsCacheOld, - MediaJobSender, ObfuscationType, PointDimensions, TexturedImage, TexturesCacheOld, -}; +use notedeck::MediaJobSender; use notedeck::{ fonts::get_font_size, show_one_error_message, tr, Images, Localization, MediaAction, MediaCacheType, NotedeckTextStyle, RenderableMedia, @@ -217,115 +212,6 @@ impl MediaUIAction { } } -#[allow(clippy::too_many_arguments)] -pub fn get_content_media_render_state<'a>( - ui: &mut egui::Ui, - job_pool: &'a mut JobPool, - jobs: &'a mut JobsCacheOld, - media_trusted: bool, - size: Vec2, - cache: &'a mut TexturesCacheOld, - url: &'a str, - cache_type: MediaCacheType, - cache_dir: &Path, - obfuscation_type: &'a ObfuscationType, -) -> MediaRenderStateOld<'a> { - let render_type = if media_trusted { - cache.handle_and_get_or_insert_loadable(url, || { - notedeck::media::images::fetch_img( - cache_dir, - ui.ctx(), - url, - ImageType::Content(None), - cache_type, - ) - }) - } else if let Some(render_type) = cache.get_and_handle(url) { - render_type - } else { - return MediaRenderStateOld::Obfuscated(get_obfuscated( - ui, - url, - obfuscation_type, - job_pool, - jobs, - size, - )); - }; - - match render_type { - notedeck::LoadableTextureState::Pending => MediaRenderStateOld::Shimmering(get_obfuscated( - ui, - url, - obfuscation_type, - job_pool, - jobs, - size, - )), - notedeck::LoadableTextureState::Error(e) => MediaRenderStateOld::Error(e), - notedeck::LoadableTextureState::Loading { actual_image_tex } => { - let obfuscation = get_obfuscated(ui, url, obfuscation_type, job_pool, jobs, size); - MediaRenderStateOld::Transitioning { - image: actual_image_tex, - obfuscation, - } - } - notedeck::LoadableTextureState::Loaded(textured_image) => { - MediaRenderStateOld::ActualImage(textured_image) - } - } -} - -fn get_obfuscated<'a>( - ui: &mut egui::Ui, - url: &str, - obfuscation_type: &'a ObfuscationType, - job_pool: &'a mut JobPool, - jobs: &'a mut JobsCacheOld, - size: Vec2, -) -> ObfuscatedTextureOld<'a> { - let ObfuscationType::Blurhash(renderable_blur) = obfuscation_type else { - return ObfuscatedTextureOld::Default; - }; - - let params = BlurhashParams { - blurhash: &renderable_blur.blurhash, - url, - ctx: ui.ctx(), - }; - - let available_points = PointDimensions { - x: size.x, - y: size.y, - }; - - let pixel_sizes = renderable_blur.scaled_pixel_dimensions(ui, available_points); - - let job_state = jobs.get_or_insert_with( - job_pool, - &JobIdOld::Blurhash(url), - Some(JobParams::Blurhash(params)), - move |params| compute_blurhash(params, pixel_sizes), - ); - - let JobState::Completed(m_blur_job) = job_state else { - return ObfuscatedTextureOld::Default; - }; - - #[allow(irrefutable_let_patterns)] - let Job::Blurhash(m_texture_handle) = m_blur_job - else { - tracing::error!("Did not get the correct job type: {:?}", m_blur_job); - return ObfuscatedTextureOld::Default; - }; - - let Some(texture_handle) = m_texture_handle else { - return ObfuscatedTextureOld::Default; - }; - - ObfuscatedTextureOld::Blur(texture_handle) -} - fn copy_link(i18n: &mut Localization, url: &str, img_resp: &Response) { img_resp.context_menu(|ui| { if ui @@ -556,43 +442,6 @@ fn render_default_blur_bg( response } -pub enum MediaRenderStateOld<'a> { - ActualImage(&'a mut TexturedImage), - Transitioning { - image: &'a mut TexturedImage, - obfuscation: ObfuscatedTextureOld<'a>, - }, - Error(&'a notedeck::Error), - Shimmering(ObfuscatedTextureOld<'a>), - Obfuscated(ObfuscatedTextureOld<'a>), -} - -pub enum ObfuscatedTextureOld<'a> { - Blur(&'a TextureHandle), - Default, -} - -/* -pub(crate) fn find_renderable_media<'a>( - urls: &mut UrlMimes, - imeta: &'a HashMap<String, ImageMetadata>, - url: &'a str, -) -> Option<RenderableMedia> { - let media_type = supported_mime_hosted_at_url(urls, url)?; - - let obfuscation_type = match imeta.get(url) { - Some(blur) => ObfuscationType::Blurhash(blur.clone()), - None => ObfuscationType::Default, - }; - - Some(RenderableMedia { - url, - media_type, - obfuscation_type, - }) -} -*/ - #[allow(clippy::too_many_arguments)] fn render_success_media( ui: &mut egui::Ui,