From 9466c108753b57c9fc987c26f52f2b6adfccdae4 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Mon, 17 Feb 2025 13:58:03 -0500 Subject: [PATCH 01/20] use bincode Signed-off-by: kernelkind --- Cargo.lock | 1 + Cargo.toml | 1 + crates/notedeck/Cargo.toml | 1 + 3 files changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 6cd25be5..c91c4842 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2747,6 +2747,7 @@ name = "notedeck" version = "0.3.1" dependencies = [ "base32", + "bincode", "dirs", "eframe", "egui", diff --git a/Cargo.toml b/Cargo.toml index 73da0bf5..76cca0d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ urlencoding = "2.1.3" uuid = { version = "1.10.0", features = ["v4"] } security-framework = "2.11.0" sha2 = "0.10.8" +bincode = "1.3.3" [patch.crates-io] egui = { git = "https://github.com/damus-io/egui", branch = "update_layouter_0.29.1" } diff --git a/crates/notedeck/Cargo.toml b/crates/notedeck/Cargo.toml index 05f6406b..9b6d17b1 100644 --- a/crates/notedeck/Cargo.toml +++ b/crates/notedeck/Cargo.toml @@ -25,6 +25,7 @@ thiserror = { workspace = true } puffin = { workspace = true, optional = true } puffin_egui = { workspace = true, optional = true } sha2 = { workspace = true } +bincode = { workspace = true } [dev-dependencies] tempfile = { workspace = true } From fa9e318e410e5cb18ffff9e604bae64ca879a359 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Tue, 18 Feb 2025 16:04:18 -0500 Subject: [PATCH 02/20] update ehttp to 0.5.0 Signed-off-by: kernelkind --- Cargo.lock | 17 ++--------------- Cargo.toml | 2 +- crates/notedeck_columns/src/media_upload.rs | 18 ++++++++---------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c91c4842..1eb4a56b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1269,7 +1269,7 @@ checksum = "bf3c1f5cd8dfe2ade470a218696c66cf556fcfd701e7830fa2e9f4428292a2a1" dependencies = [ "ahash", "egui", - "ehttp 0.5.0", + "ehttp", "enum-map", "image", "log", @@ -1326,19 +1326,6 @@ dependencies = [ "web-time 1.1.0", ] -[[package]] -name = "ehttp" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80b69a6f9168b96c0ae04763bec27a8b06b34343c334dd2703a4ec21f0f5e110" -dependencies = [ - "js-sys", - "ureq", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "ehttp" version = "0.5.0" @@ -2809,7 +2796,7 @@ dependencies = [ "egui_nav", "egui_tabs", "egui_virtual_list", - "ehttp 0.2.0", + "ehttp", "enostr", "hex", "image", diff --git a/Cargo.toml b/Cargo.toml index 76cca0d9..b1490234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ egui_extras = { version = "0.29.1", features = ["all_loaders"] } egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "ac7d663307b76634757024b438dd4b899790da99" } egui_tabs = "0.2.0" egui_virtual_list = "0.5.0" -ehttp = "0.2.0" +ehttp = "0.5.0" enostr = { path = "crates/enostr" } ewebsock = { version = "0.2.0", features = ["tls"] } hex = "0.4.3" diff --git a/crates/notedeck_columns/src/media_upload.rs b/crates/notedeck_columns/src/media_upload.rs index 73447eb8..fb6fb411 100644 --- a/crates/notedeck_columns/src/media_upload.rs +++ b/crates/notedeck_columns/src/media_upload.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::PathBuf; use base64::{prelude::BASE64_URL_SAFE, Engine}; use ehttp::Request; @@ -104,15 +104,13 @@ fn create_nip96_request( body.extend(file_contents); body.extend(format!("\r\n--{}--\r\n", boundary).as_bytes()); - let headers = { - let mut map = BTreeMap::new(); - map.insert( - "Content-Type".to_owned(), - format!("multipart/form-data; boundary={boundary}"), - ); - map.insert("Authorization".to_owned(), format!("Nostr {nip98_base64}")); - map - }; + let headers = ehttp::Headers::new(&[ + ( + "Content-Type", + format!("multipart/form-data; boundary={boundary}").as_str(), + ), + ("Authorization", format!("Nostr {nip98_base64}").as_str()), + ]); Request { method: "POST".to_string(), From 4cd80c10b15fa308523453f46efc0c43308479f9 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Mon, 17 Feb 2025 13:59:48 -0500 Subject: [PATCH 03/20] introduce UrlMimes Signed-off-by: kernelkind --- Cargo.lock | 1 + crates/notedeck/Cargo.toml | 1 + crates/notedeck/src/lib.rs | 1 + crates/notedeck/src/urls.rs | 214 ++++++++++++++++++++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 crates/notedeck/src/urls.rs diff --git a/Cargo.lock b/Cargo.lock index 1eb4a56b..c1325a6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2738,6 +2738,7 @@ dependencies = [ "dirs", "eframe", "egui", + "ehttp", "enostr", "hex", "image", diff --git a/crates/notedeck/Cargo.toml b/crates/notedeck/Cargo.toml index 9b6d17b1..3408fb32 100644 --- a/crates/notedeck/Cargo.toml +++ b/crates/notedeck/Cargo.toml @@ -26,6 +26,7 @@ puffin = { workspace = true, optional = true } puffin_egui = { workspace = true, optional = true } sha2 = { workspace = true } bincode = { workspace = true } +ehttp = {workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index 61c31fba..c7053236 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -21,6 +21,7 @@ mod timecache; mod timed_serializer; pub mod ui; mod unknowns; +mod urls; mod user_account; pub use accounts::{AccountData, Accounts, AccountsAction, AddAccountAction, SwitchAccountAction}; diff --git a/crates/notedeck/src/urls.rs b/crates/notedeck/src/urls.rs new file mode 100644 index 00000000..2ccafa6c --- /dev/null +++ b/crates/notedeck/src/urls.rs @@ -0,0 +1,214 @@ +use std::{ + collections::HashMap, + fs::File, + io::{Read, Write}, + path::PathBuf, + sync::{Arc, RwLock}, + time::{Duration, SystemTime}, +}; + +use egui::TextBuffer; +use poll_promise::Promise; + +use crate::Error; + +const FILE_NAME: &str = "urls.bin"; +const SAVE_INTERVAL: Duration = Duration::from_secs(60); + +type UrlsToMime = HashMap; + +/// caches mime type for a URL. saves to disk on interval [`SAVE_INTERVAL`] +pub struct UrlCache { + last_saved: SystemTime, + path: PathBuf, + cache: Arc>, + from_disk_promise: Option>>, +} + +impl UrlCache { + pub fn rel_dir() -> &'static str { + FILE_NAME + } + + pub fn new(path: PathBuf) -> Self { + Self { + last_saved: SystemTime::now(), + path: path.clone(), + cache: Default::default(), + from_disk_promise: Some(read_from_disk(path)), + } + } + + pub fn get_type(&self, url: &str) -> Option { + self.cache.read().ok()?.get(url).cloned() + } + + pub fn set_type(&mut self, url: String, mime_type: String) { + if let Ok(mut locked_cache) = self.cache.write() { + locked_cache.insert(url, mime_type); + } + } + + pub fn handle_io(&mut self) { + if let Some(promise) = &mut self.from_disk_promise { + if let Some(maybe_cache) = promise.ready_mut() { + if let Some(cache) = maybe_cache.take() { + merge_cache(self.cache.clone(), cache) + } + + self.from_disk_promise = None; + } + } + + if let Ok(cur_duration) = SystemTime::now().duration_since(self.last_saved) { + if cur_duration >= SAVE_INTERVAL { + save_to_disk(self.path.clone(), self.cache.clone()); + self.last_saved = SystemTime::now(); + } + } + } +} + +fn merge_cache(cur_cache: Arc>, from_disk: UrlsToMime) { + std::thread::spawn(move || { + if let Ok(mut locked_cache) = cur_cache.write() { + locked_cache.extend(from_disk); + } + }); +} + +fn read_from_disk(path: PathBuf) -> Promise> { + let (sender, promise) = Promise::new(); + + std::thread::spawn(move || { + let result: Result = (|| { + let mut file = File::open(path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + let data: UrlsToMime = + bincode::deserialize(&buffer).map_err(|e| Error::Generic(e.to_string()))?; + Ok(data) + })(); + + match result { + Ok(data) => sender.send(Some(data)), + Err(e) => { + tracing::error!("problem deserializing UrlCache: {e}"); + sender.send(None) + } + } + }); + + promise +} + +fn save_to_disk(path: PathBuf, cache: Arc>) { + std::thread::spawn(move || { + let result: Result<(), Error> = (|| { + if let Ok(cache) = cache.read() { + let cache = &*cache; + let encoded = + bincode::serialize(cache).map_err(|e| Error::Generic(e.to_string()))?; + let mut file = File::create(&path)?; + file.write_all(&encoded)?; + file.sync_all()?; + tracing::info!("Saved UrlCache to disk."); + Ok(()) + } else { + Err(Error::Generic( + "Could not read UrlCache behind RwLock".to_owned(), + )) + } + })(); + + if let Err(e) = result { + tracing::error!("Failed to save UrlCache: {}", e); + } + }); +} + +fn ehttp_get_mime_type(url: &str, sender: poll_promise::Sender) { + let request = ehttp::Request::head(url); + + let url = url.to_owned(); + ehttp::fetch( + request, + move |response: Result| match response { + Ok(resp) => { + if let Some(content_type) = resp.headers.get("content-type") { + sender.send(MimeResult::Ok(extract_mime_type(content_type).to_owned())); + } else { + sender.send(MimeResult::Err(HttpError::MissingHeader)); + tracing::error!("Content-Type header not found for {url}"); + } + } + Err(err) => { + sender.send(MimeResult::Err(HttpError::HttpFailure)); + tracing::error!("failed ehttp for UrlCache: {err}"); + } + }, + ); +} + +#[derive(Debug)] +enum HttpError { + HttpFailure, + MissingHeader, +} + +type MimeResult = Result; + +fn extract_mime_type(content_type: &str) -> &str { + content_type + .split(';') + .next() + .unwrap_or(content_type) + .trim() +} + +pub struct UrlMimes { + pub cache: UrlCache, + in_flight: HashMap>, +} + +impl UrlMimes { + pub fn new(url_cache: UrlCache) -> Self { + Self { + cache: url_cache, + in_flight: Default::default(), + } + } + + pub fn get(&mut self, url: &str) -> Option { + if let Some(mime_type) = self.cache.get_type(url) { + Some(mime_type) + } else if let Some(promise) = self.in_flight.get_mut(url) { + if let Some(mime_result) = promise.ready_mut() { + match mime_result { + Ok(mime_type) => { + let mime_type = mime_type.take(); + self.cache.set_type(url.to_owned(), mime_type.clone()); + self.in_flight.remove(url); + Some(mime_type) + } + Err(HttpError::HttpFailure) => { + // allow retrying + self.in_flight.remove(url); + None + } + Err(HttpError::MissingHeader) => { + // response was malformed, don't retry + None + } + } + } else { + None + } + } else { + let (sender, promise) = Promise::new(); + ehttp_get_mime_type(url, sender); + self.in_flight.insert(url.to_owned(), promise); + None + } + } +} From 5791b0c5b11cde5d5d0248c70c4eea902ddf59d7 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 14:10:39 -0500 Subject: [PATCH 04/20] use mime_guess Signed-off-by: kernelkind --- Cargo.lock | 13 ++++++++++++- Cargo.toml | 1 + crates/notedeck/Cargo.toml | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c1325a6a..9060f12d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2371,7 +2371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2507,6 +2507,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "mime_guess2" version = "2.0.5" @@ -2742,6 +2752,7 @@ dependencies = [ "enostr", "hex", "image", + "mime_guess", "nostrdb", "poll-promise", "puffin 0.19.1 (git+https://github.com/jb55/puffin?rev=70ff86d5503815219b01a009afd3669b7903a057)", diff --git a/Cargo.toml b/Cargo.toml index b1490234..49dfecd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ uuid = { version = "1.10.0", features = ["v4"] } security-framework = "2.11.0" sha2 = "0.10.8" bincode = "1.3.3" +mime_guess = "2.0.5" [patch.crates-io] egui = { git = "https://github.com/damus-io/egui", branch = "update_layouter_0.29.1" } diff --git a/crates/notedeck/Cargo.toml b/crates/notedeck/Cargo.toml index 3408fb32..3ed439b6 100644 --- a/crates/notedeck/Cargo.toml +++ b/crates/notedeck/Cargo.toml @@ -27,6 +27,7 @@ puffin_egui = { workspace = true, optional = true } sha2 = { workspace = true } bincode = { workspace = true } ehttp = {workspace = true } +mime_guess = { workspace = true } [dev-dependencies] tempfile = { workspace = true } From bf68eb3ea82d6bc7c332abde90ada3de16340c32 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 17:43:22 -0500 Subject: [PATCH 05/20] add SupportedMimeType Signed-off-by: kernelkind --- crates/notedeck/src/urls.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/notedeck/src/urls.rs b/crates/notedeck/src/urls.rs index 2ccafa6c..0705aa43 100644 --- a/crates/notedeck/src/urls.rs +++ b/crates/notedeck/src/urls.rs @@ -212,3 +212,30 @@ impl UrlMimes { } } } + +struct SupportedMimeType { + mime: mime_guess::Mime, +} + +impl SupportedMimeType { + #[allow(unused)] + pub fn from_extension(extension: &str) -> Result { + if let Some(mime) = mime_guess::from_ext(extension) + .first() + .filter(is_mime_supported) + { + Ok(Self { mime }) + } else { + Err(Error::Generic("Unsupported mime type".to_owned())) + } + } + + #[allow(unused)] + pub fn to_mime(&self) -> &str { + self.mime.essence_str() + } +} + +fn is_mime_supported(mime: &mime_guess::Mime) -> bool { + mime.type_() == mime_guess::mime::IMAGE +} From 4f4a0feb8c8bf20c25f28f583040603d2c37f6cd Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sat, 15 Feb 2025 23:06:45 -0500 Subject: [PATCH 06/20] rename ImageCache -> MediaCache Signed-off-by: kernelkind --- crates/notedeck/src/app.rs | 10 +++++----- crates/notedeck/src/context.rs | 4 ++-- crates/notedeck/src/imgcache.rs | 14 +++++++------- crates/notedeck/src/lib.rs | 2 +- crates/notedeck_columns/src/accounts/mod.rs | 4 ++-- crates/notedeck_columns/src/app.rs | 4 ++-- crates/notedeck_columns/src/images.rs | 10 +++++----- crates/notedeck_columns/src/timeline/route.rs | 6 +++--- crates/notedeck_columns/src/ui/accounts.rs | 8 ++++---- crates/notedeck_columns/src/ui/add_column.rs | 6 +++--- crates/notedeck_columns/src/ui/column/header.rs | 6 +++--- crates/notedeck_columns/src/ui/mention.rs | 8 ++++---- crates/notedeck_columns/src/ui/note/contents.rs | 12 ++++++------ crates/notedeck_columns/src/ui/note/mod.rs | 6 +++--- crates/notedeck_columns/src/ui/note/post.rs | 6 +++--- .../notedeck_columns/src/ui/note/quote_repost.rs | 6 +++--- crates/notedeck_columns/src/ui/note/reply.rs | 6 +++--- .../src/ui/note/reply_description.rs | 6 +++--- crates/notedeck_columns/src/ui/profile/edit.rs | 6 +++--- crates/notedeck_columns/src/ui/profile/mod.rs | 6 +++--- crates/notedeck_columns/src/ui/profile/picture.rs | 10 +++++----- crates/notedeck_columns/src/ui/profile/preview.rs | 10 +++++----- crates/notedeck_columns/src/ui/search_results.rs | 8 ++++---- crates/notedeck_columns/src/ui/side_panel.rs | 6 +++--- crates/notedeck_columns/src/ui/thread.rs | 6 +++--- crates/notedeck_columns/src/ui/timeline.rs | 12 ++++++------ 26 files changed, 94 insertions(+), 94 deletions(-) diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs index dfb4468b..170cef88 100644 --- a/crates/notedeck/src/app.rs +++ b/crates/notedeck/src/app.rs @@ -1,7 +1,7 @@ use crate::persist::{AppSizeHandler, ZoomHandler}; use crate::{ - Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, ImageCache, - KeyStorageType, NoteCache, RelayDebugView, ThemeHandler, UnknownIds, + Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageType, + MediaCache, NoteCache, RelayDebugView, ThemeHandler, UnknownIds, }; use egui::ThemePreference; use enostr::RelayPool; @@ -19,7 +19,7 @@ pub trait App { /// Main notedeck app framework pub struct Notedeck { ndb: Ndb, - img_cache: ImageCache, + img_cache: MediaCache, unknown_ids: UnknownIds, pool: RelayPool, note_cache: NoteCache, @@ -129,7 +129,7 @@ impl Notedeck { let _ = std::fs::create_dir_all(&dbpath_str); - let img_cache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); + let img_cache_dir = path.path(DataPathType::Cache).join(MediaCache::rel_dir()); let _ = std::fs::create_dir_all(img_cache_dir.clone()); let map_size = if cfg!(target_os = "windows") { @@ -184,7 +184,7 @@ impl Notedeck { } } - let img_cache = ImageCache::new(img_cache_dir); + let img_cache = MediaCache::new(img_cache_dir); let note_cache = NoteCache::default(); let unknown_ids = UnknownIds::default(); let zoom = ZoomHandler::new(&path); diff --git a/crates/notedeck/src/context.rs b/crates/notedeck/src/context.rs index 5801fbac..5cf2b7a9 100644 --- a/crates/notedeck/src/context.rs +++ b/crates/notedeck/src/context.rs @@ -1,4 +1,4 @@ -use crate::{Accounts, Args, DataPath, ImageCache, NoteCache, ThemeHandler, UnknownIds}; +use crate::{Accounts, Args, DataPath, MediaCache, NoteCache, ThemeHandler, UnknownIds}; use enostr::RelayPool; use nostrdb::Ndb; @@ -7,7 +7,7 @@ use nostrdb::Ndb; pub struct AppContext<'a> { pub ndb: &'a mut Ndb, - pub img_cache: &'a mut ImageCache, + pub img_cache: &'a mut MediaCache, pub unknown_ids: &'a mut UnknownIds, pub pool: &'a mut RelayPool, pub note_cache: &'a mut NoteCache, diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs index 9cbe31a1..6fdb7aaf 100644 --- a/crates/notedeck/src/imgcache.rs +++ b/crates/notedeck/src/imgcache.rs @@ -13,15 +13,15 @@ use std::path; use std::path::PathBuf; use tracing::warn; -pub type ImageCacheValue = Promise>; -pub type ImageCacheMap = HashMap; +pub type MediaCacheValue = Promise>; +pub type MediaCacheMap = HashMap; -pub struct ImageCache { +pub struct MediaCache { pub cache_dir: path::PathBuf, - url_imgs: ImageCacheMap, + url_imgs: MediaCacheMap, } -impl ImageCache { +impl MediaCache { pub fn new(cache_dir: path::PathBuf) -> Self { Self { cache_dir, @@ -118,11 +118,11 @@ impl ImageCache { Ok(()) } - pub fn map(&self) -> &ImageCacheMap { + pub fn map(&self) -> &MediaCacheMap { &self.url_imgs } - pub fn map_mut(&mut self) -> &mut ImageCacheMap { + pub fn map_mut(&mut self) -> &mut MediaCacheMap { &mut self.url_imgs } } diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index c7053236..034f11c3 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -31,7 +31,7 @@ pub use context::AppContext; pub use error::{Error, FilterError}; pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; -pub use imgcache::ImageCache; +pub use imgcache::MediaCache; pub use muted::{MuteFun, Muted}; pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf}; pub use notecache::{CachedNote, NoteCache}; diff --git a/crates/notedeck_columns/src/accounts/mod.rs b/crates/notedeck_columns/src/accounts/mod.rs index 919fb609..6f6205cb 100644 --- a/crates/notedeck_columns/src/accounts/mod.rs +++ b/crates/notedeck_columns/src/accounts/mod.rs @@ -2,7 +2,7 @@ use enostr::FullKeypair; use nostrdb::Ndb; use notedeck::{ - Accounts, AccountsAction, AddAccountAction, ImageCache, SingleUnkIdAction, SwitchAccountAction, + Accounts, AccountsAction, AddAccountAction, MediaCache, SingleUnkIdAction, SwitchAccountAction, }; use crate::app::get_active_columns_mut; @@ -27,7 +27,7 @@ pub fn render_accounts_route( ui: &mut egui::Ui, ndb: &Ndb, col: usize, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, accounts: &mut Accounts, decks: &mut DecksCache, login_state: &mut AcquireKeyState, diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs index df2848f0..568001d8 100644 --- a/crates/notedeck_columns/src/app.rs +++ b/crates/notedeck_columns/src/app.rs @@ -13,7 +13,7 @@ use crate::{ Result, }; -use notedeck::{Accounts, AppContext, DataPath, DataPathType, FilterState, ImageCache, UnknownIds}; +use notedeck::{Accounts, AppContext, DataPath, DataPathType, FilterState, MediaCache, UnknownIds}; use enostr::{ClientMessage, Keypair, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPool}; use uuid::Uuid; @@ -464,7 +464,7 @@ impl Damus { let decks_cache = DecksCache::default(); let path = DataPath::new(&data_path); - let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); + let imgcache_dir = path.path(DataPathType::Cache).join(MediaCache::rel_dir()); let _ = std::fs::create_dir_all(imgcache_dir.clone()); let debug = true; diff --git a/crates/notedeck_columns/src/images.rs b/crates/notedeck_columns/src/images.rs index 0a0d8c59..87ae5b35 100644 --- a/crates/notedeck_columns/src/images.rs +++ b/crates/notedeck_columns/src/images.rs @@ -1,6 +1,6 @@ use egui::{pos2, Color32, ColorImage, Rect, Sense, SizeHint, TextureHandle}; use image::imageops::FilterType; -use notedeck::ImageCache; +use notedeck::MediaCache; use notedeck::Result; use poll_promise::Promise; use std::path; @@ -9,7 +9,7 @@ use tokio::fs; //pub type ImageCacheKey = String; //pub type ImageCacheValue = Promise>; -//pub type ImageCache = HashMap; +//pub type MediaCache = HashMap; // NOTE(jb55): chatgpt wrote this because I was too dumb to pub fn aspect_fill( @@ -213,12 +213,12 @@ pub enum ImageType { } pub fn fetch_img( - img_cache: &ImageCache, + img_cache: &MediaCache, ctx: &egui::Context, url: &str, imgtyp: ImageType, ) -> Promise> { - let key = ImageCache::key(url); + let key = MediaCache::key(url); let path = img_cache.cache_dir.join(key); if path.exists() { @@ -249,7 +249,7 @@ fn fetch_img_from_net( let texture_handle = ctx.load_texture(&cloned_url, img.clone(), Default::default()); // write to disk - std::thread::spawn(move || ImageCache::write(&cache_path, &cloned_url, img)); + std::thread::spawn(move || MediaCache::write(&cache_path, &cloned_url, img)); texture_handle }); diff --git a/crates/notedeck_columns/src/timeline/route.rs b/crates/notedeck_columns/src/timeline/route.rs index 51f95bef..81f990ae 100644 --- a/crates/notedeck_columns/src/timeline/route.rs +++ b/crates/notedeck_columns/src/timeline/route.rs @@ -7,12 +7,12 @@ use crate::{ use enostr::Pubkey; use nostrdb::Ndb; -use notedeck::{Accounts, ImageCache, MuteFun, NoteCache, UnknownIds}; +use notedeck::{Accounts, MediaCache, MuteFun, NoteCache, UnknownIds}; #[allow(clippy::too_many_arguments)] pub fn render_timeline_route( ndb: &Ndb, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, unknown_ids: &mut UnknownIds, note_cache: &mut NoteCache, timeline_cache: &mut TimelineCache, @@ -102,7 +102,7 @@ pub fn render_profile_route( accounts: &Accounts, ndb: &Ndb, timeline_cache: &mut TimelineCache, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, note_cache: &mut NoteCache, unknown_ids: &mut UnknownIds, col: usize, diff --git a/crates/notedeck_columns/src/ui/accounts.rs b/crates/notedeck_columns/src/ui/accounts.rs index 01ad6597..051cfc88 100644 --- a/crates/notedeck_columns/src/ui/accounts.rs +++ b/crates/notedeck_columns/src/ui/accounts.rs @@ -3,14 +3,14 @@ use egui::{ Align, Button, Frame, Image, InnerResponse, Layout, RichText, ScrollArea, Ui, UiBuilder, Vec2, }; use nostrdb::{Ndb, Transaction}; -use notedeck::{Accounts, ImageCache}; +use notedeck::{Accounts, MediaCache}; use super::profile::preview::SimpleProfilePreview; pub struct AccountsView<'a> { ndb: &'a Ndb, accounts: &'a Accounts, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, } #[derive(Clone, Debug)] @@ -27,7 +27,7 @@ enum ProfilePreviewAction { } impl<'a> AccountsView<'a> { - pub fn new(ndb: &'a Ndb, accounts: &'a Accounts, img_cache: &'a mut ImageCache) -> Self { + pub fn new(ndb: &'a Ndb, accounts: &'a Accounts, img_cache: &'a mut MediaCache) -> Self { AccountsView { ndb, accounts, @@ -54,7 +54,7 @@ impl<'a> AccountsView<'a> { ui: &mut Ui, accounts: &Accounts, ndb: &Ndb, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, ) -> Option { let mut return_op: Option = None; ui.allocate_ui_with_layout( diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs index 9783599b..32a98dd6 100644 --- a/crates/notedeck_columns/src/ui/add_column.rs +++ b/crates/notedeck_columns/src/ui/add_column.rs @@ -17,7 +17,7 @@ use crate::{ Damus, }; -use notedeck::{AppContext, ImageCache, NotedeckTextStyle, UserAccount}; +use notedeck::{AppContext, MediaCache, NotedeckTextStyle, UserAccount}; use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter}; use super::{anim::AnimationHelper, padding, ProfilePreview}; @@ -163,7 +163,7 @@ impl AddColumnOption { pub struct AddColumnView<'a> { key_state_map: &'a mut HashMap, ndb: &'a Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, cur_account: Option<&'a UserAccount>, } @@ -171,7 +171,7 @@ impl<'a> AddColumnView<'a> { pub fn new( key_state_map: &'a mut HashMap, ndb: &'a Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, cur_account: Option<&'a UserAccount>, ) -> Self { Self { diff --git a/crates/notedeck_columns/src/ui/column/header.rs b/crates/notedeck_columns/src/ui/column/header.rs index 88043909..3713d0c9 100644 --- a/crates/notedeck_columns/src/ui/column/header.rs +++ b/crates/notedeck_columns/src/ui/column/header.rs @@ -16,11 +16,11 @@ use egui::Margin; use egui::{RichText, Stroke, UiBuilder}; use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use notedeck::{ImageCache, NotedeckTextStyle}; +use notedeck::{MediaCache, NotedeckTextStyle}; pub struct NavTitle<'a> { ndb: &'a Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, columns: &'a Columns, routes: &'a [Route], col_id: usize, @@ -29,7 +29,7 @@ pub struct NavTitle<'a> { impl<'a> NavTitle<'a> { pub fn new( ndb: &'a Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, columns: &'a Columns, routes: &'a [Route], col_id: usize, diff --git a/crates/notedeck_columns/src/ui/mention.rs b/crates/notedeck_columns/src/ui/mention.rs index 83e53ac5..6ad00e42 100644 --- a/crates/notedeck_columns/src/ui/mention.rs +++ b/crates/notedeck_columns/src/ui/mention.rs @@ -3,11 +3,11 @@ use crate::{actionbar::NoteAction, profile::get_display_name, timeline::Timeline use egui::Sense; use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use notedeck::ImageCache; +use notedeck::MediaCache; pub struct Mention<'a> { ndb: &'a Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, txn: &'a Transaction, pk: &'a [u8; 32], selectable: bool, @@ -17,7 +17,7 @@ pub struct Mention<'a> { impl<'a> Mention<'a> { pub fn new( ndb: &'a Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, txn: &'a Transaction, pk: &'a [u8; 32], ) -> Self { @@ -64,7 +64,7 @@ impl egui::Widget for Mention<'_> { fn mention_ui( ndb: &Ndb, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, txn: &Transaction, pk: &[u8; 32], ui: &mut egui::Ui, diff --git a/crates/notedeck_columns/src/ui/note/contents.rs b/crates/notedeck_columns/src/ui/note/contents.rs index 22f8fff4..0ccf4efd 100644 --- a/crates/notedeck_columns/src/ui/note/contents.rs +++ b/crates/notedeck_columns/src/ui/note/contents.rs @@ -8,11 +8,11 @@ use egui::{Color32, Hyperlink, Image, RichText}; use nostrdb::{BlockType, Mention, Ndb, Note, NoteKey, Transaction}; use tracing::warn; -use notedeck::{ImageCache, NoteCache}; +use notedeck::{MediaCache, NoteCache}; pub struct NoteContents<'a> { ndb: &'a Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, note_cache: &'a mut NoteCache, txn: &'a Transaction, note: &'a Note<'a>, @@ -24,7 +24,7 @@ pub struct NoteContents<'a> { impl<'a> NoteContents<'a> { pub fn new( ndb: &'a Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, note_cache: &'a mut NoteCache, txn: &'a Transaction, note: &'a Note, @@ -72,7 +72,7 @@ pub fn render_note_preview( ui: &mut egui::Ui, ndb: &Ndb, note_cache: &mut NoteCache, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, txn: &Transaction, id: &[u8; 32], parent: NoteKey, @@ -134,7 +134,7 @@ fn is_image_link(url: &str) -> bool { fn render_note_contents( ui: &mut egui::Ui, ndb: &Ndb, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, note_cache: &mut NoteCache, txn: &Transaction, note: &Note, @@ -279,7 +279,7 @@ fn rot13(input: &str) -> String { fn image_carousel( ui: &mut egui::Ui, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, images: Vec, carousel_id: egui::Id, ) { diff --git a/crates/notedeck_columns/src/ui/note/mod.rs b/crates/notedeck_columns/src/ui/note/mod.rs index 7fe89787..46103864 100644 --- a/crates/notedeck_columns/src/ui/note/mod.rs +++ b/crates/notedeck_columns/src/ui/note/mod.rs @@ -25,14 +25,14 @@ use egui::emath::{pos2, Vec2}; use egui::{Id, Label, Pos2, Rect, Response, RichText, Sense}; use enostr::{NoteId, Pubkey}; use nostrdb::{Ndb, Note, NoteKey, Transaction}; -use notedeck::{CachedNote, ImageCache, NoteCache, NotedeckTextStyle}; +use notedeck::{CachedNote, MediaCache, NoteCache, NotedeckTextStyle}; use super::profile::preview::one_line_display_name_widget; pub struct NoteView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, parent: Option, note: &'a nostrdb::Note<'a>, flags: NoteOptions, @@ -74,7 +74,7 @@ impl<'a> NoteView<'a> { pub fn new( ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, note: &'a nostrdb::Note<'a>, mut flags: NoteOptions, ) -> Self { diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs index 00bc6778..b766b792 100644 --- a/crates/notedeck_columns/src/ui/note/post.rs +++ b/crates/notedeck_columns/src/ui/note/post.rs @@ -13,7 +13,7 @@ use egui::{vec2, Frame, Layout, Margin, Pos2, ScrollArea, Sense, TextBuffer}; use enostr::{FilledKeypair, FullKeypair, NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; -use notedeck::{ImageCache, NoteCache}; +use notedeck::{MediaCache, NoteCache}; use tracing::error; use super::contents::render_note_preview; @@ -22,7 +22,7 @@ pub struct PostView<'a> { ndb: &'a Ndb, draft: &'a mut Draft, post_type: PostType, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, note_cache: &'a mut NoteCache, poster: FilledKeypair<'a>, id_source: Option, @@ -88,7 +88,7 @@ impl<'a> PostView<'a> { ndb: &'a Ndb, draft: &'a mut Draft, post_type: PostType, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, note_cache: &'a mut NoteCache, poster: FilledKeypair<'a>, inner_rect: egui::Rect, diff --git a/crates/notedeck_columns/src/ui/note/quote_repost.rs b/crates/notedeck_columns/src/ui/note/quote_repost.rs index 4abad310..22f98c5e 100644 --- a/crates/notedeck_columns/src/ui/note/quote_repost.rs +++ b/crates/notedeck_columns/src/ui/note/quote_repost.rs @@ -1,6 +1,6 @@ use enostr::{FilledKeypair, NoteId}; use nostrdb::Ndb; -use notedeck::{ImageCache, NoteCache}; +use notedeck::{MediaCache, NoteCache}; use crate::{ draft::Draft, @@ -13,7 +13,7 @@ pub struct QuoteRepostView<'a> { ndb: &'a Ndb, poster: FilledKeypair<'a>, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, draft: &'a mut Draft, quoting_note: &'a nostrdb::Note<'a>, id_source: Option, @@ -27,7 +27,7 @@ impl<'a> QuoteRepostView<'a> { ndb: &'a Ndb, poster: FilledKeypair<'a>, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, draft: &'a mut Draft, quoting_note: &'a nostrdb::Note<'a>, inner_rect: egui::Rect, diff --git a/crates/notedeck_columns/src/ui/note/reply.rs b/crates/notedeck_columns/src/ui/note/reply.rs index c64df0fc..6aa5a627 100644 --- a/crates/notedeck_columns/src/ui/note/reply.rs +++ b/crates/notedeck_columns/src/ui/note/reply.rs @@ -4,13 +4,13 @@ use crate::ui::note::{NoteOptions, PostResponse, PostType}; use enostr::{FilledKeypair, NoteId}; use nostrdb::Ndb; -use notedeck::{ImageCache, NoteCache}; +use notedeck::{MediaCache, NoteCache}; pub struct PostReplyView<'a> { ndb: &'a Ndb, poster: FilledKeypair<'a>, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, draft: &'a mut Draft, note: &'a nostrdb::Note<'a>, id_source: Option, @@ -25,7 +25,7 @@ impl<'a> PostReplyView<'a> { poster: FilledKeypair<'a>, draft: &'a mut Draft, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, note: &'a nostrdb::Note<'a>, inner_rect: egui::Rect, note_options: NoteOptions, diff --git a/crates/notedeck_columns/src/ui/note/reply_description.rs b/crates/notedeck_columns/src/ui/note/reply_description.rs index 8c7bacdd..2d280358 100644 --- a/crates/notedeck_columns/src/ui/note/reply_description.rs +++ b/crates/notedeck_columns/src/ui/note/reply_description.rs @@ -4,7 +4,7 @@ use crate::{ }; use egui::{Label, RichText, Sense}; use nostrdb::{Ndb, Note, NoteReply, Transaction}; -use notedeck::{ImageCache, NoteCache}; +use notedeck::{MediaCache, NoteCache}; #[must_use = "Please handle the resulting note action"] pub fn reply_desc( @@ -12,7 +12,7 @@ pub fn reply_desc( txn: &Transaction, note_reply: &NoteReply, ndb: &Ndb, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, note_cache: &mut NoteCache, note_options: NoteOptions, ) -> Option { @@ -29,7 +29,7 @@ pub fn reply_desc( // note link renderer helper let note_link = |ui: &mut egui::Ui, note_cache: &mut NoteCache, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, text: &str, note: &Note<'_>| { let r = ui.add( diff --git a/crates/notedeck_columns/src/ui/profile/edit.rs b/crates/notedeck_columns/src/ui/profile/edit.rs index 411f6140..b6300f7e 100644 --- a/crates/notedeck_columns/src/ui/profile/edit.rs +++ b/crates/notedeck_columns/src/ui/profile/edit.rs @@ -1,7 +1,7 @@ use core::f32; use egui::{vec2, Button, Layout, Margin, RichText, Rounding, ScrollArea, TextEdit}; -use notedeck::{ImageCache, NotedeckTextStyle}; +use notedeck::{MediaCache, NotedeckTextStyle}; use crate::{colors, profile_state::ProfileState}; @@ -9,11 +9,11 @@ use super::{banner, unwrap_profile_url, ProfilePic}; pub struct EditProfileView<'a> { state: &'a mut ProfileState, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, } impl<'a> EditProfileView<'a> { - pub fn new(state: &'a mut ProfileState, img_cache: &'a mut ImageCache) -> Self { + pub fn new(state: &'a mut ProfileState, img_cache: &'a mut MediaCache) -> Self { Self { state, img_cache } } diff --git a/crates/notedeck_columns/src/ui/profile/mod.rs b/crates/notedeck_columns/src/ui/profile/mod.rs index c466895d..1d429216 100644 --- a/crates/notedeck_columns/src/ui/profile/mod.rs +++ b/crates/notedeck_columns/src/ui/profile/mod.rs @@ -23,7 +23,7 @@ use crate::{ NostrName, }; -use notedeck::{Accounts, ImageCache, MuteFun, NoteCache, NotedeckTextStyle, UnknownIds}; +use notedeck::{Accounts, MediaCache, MuteFun, NoteCache, NotedeckTextStyle, UnknownIds}; pub struct ProfileView<'a> { pubkey: &'a Pubkey, @@ -33,7 +33,7 @@ pub struct ProfileView<'a> { note_options: NoteOptions, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, unknown_ids: &'a mut UnknownIds, is_muted: &'a MuteFun, } @@ -52,7 +52,7 @@ impl<'a> ProfileView<'a> { timeline_cache: &'a mut TimelineCache, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, unknown_ids: &'a mut UnknownIds, is_muted: &'a MuteFun, note_options: NoteOptions, diff --git a/crates/notedeck_columns/src/ui/profile/picture.rs b/crates/notedeck_columns/src/ui/profile/picture.rs index 541683de..d8455a65 100644 --- a/crates/notedeck_columns/src/ui/profile/picture.rs +++ b/crates/notedeck_columns/src/ui/profile/picture.rs @@ -4,10 +4,10 @@ use egui::{vec2, Sense, Stroke, TextureHandle}; use nostrdb::{Ndb, Transaction}; use tracing::info; -use notedeck::{AppContext, ImageCache}; +use notedeck::{AppContext, MediaCache}; pub struct ProfilePic<'cache, 'url> { - cache: &'cache mut ImageCache, + cache: &'cache mut MediaCache, url: &'url str, size: f32, border: Option, @@ -20,7 +20,7 @@ impl egui::Widget for ProfilePic<'_, '_> { } impl<'cache, 'url> ProfilePic<'cache, 'url> { - pub fn new(cache: &'cache mut ImageCache, url: &'url str) -> Self { + pub fn new(cache: &'cache mut MediaCache, url: &'url str) -> Self { let size = Self::default_size(); ProfilePic { cache, @@ -35,7 +35,7 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { } pub fn from_profile( - cache: &'cache mut ImageCache, + cache: &'cache mut MediaCache, profile: &nostrdb::ProfileRecord<'url>, ) -> Option { profile @@ -80,7 +80,7 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { fn render_pfp( ui: &mut egui::Ui, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, url: &str, ui_size: f32, border: Option, diff --git a/crates/notedeck_columns/src/ui/profile/preview.rs b/crates/notedeck_columns/src/ui/profile/preview.rs index de1bbc24..8cdbc2b8 100644 --- a/crates/notedeck_columns/src/ui/profile/preview.rs +++ b/crates/notedeck_columns/src/ui/profile/preview.rs @@ -4,18 +4,18 @@ use egui::{Frame, Label, RichText, Widget}; use egui_extras::Size; use nostrdb::ProfileRecord; -use notedeck::{ImageCache, NotedeckTextStyle, UserAccount}; +use notedeck::{MediaCache, NotedeckTextStyle, UserAccount}; use super::{about_section_widget, banner, display_name_widget, get_display_name, get_profile_url}; pub struct ProfilePreview<'a, 'cache> { profile: &'a ProfileRecord<'a>, - cache: &'cache mut ImageCache, + cache: &'cache mut MediaCache, banner_height: Size, } impl<'a, 'cache> ProfilePreview<'a, 'cache> { - pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut ImageCache) -> Self { + pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut MediaCache) -> Self { let banner_height = Size::exact(80.0); ProfilePreview { profile, @@ -69,14 +69,14 @@ impl egui::Widget for ProfilePreview<'_, '_> { pub struct SimpleProfilePreview<'a, 'cache> { profile: Option<&'a ProfileRecord<'a>>, - cache: &'cache mut ImageCache, + cache: &'cache mut MediaCache, is_nsec: bool, } impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> { pub fn new( profile: Option<&'a ProfileRecord<'a>>, - cache: &'cache mut ImageCache, + cache: &'cache mut MediaCache, is_nsec: bool, ) -> Self { SimpleProfilePreview { diff --git a/crates/notedeck_columns/src/ui/search_results.rs b/crates/notedeck_columns/src/ui/search_results.rs index 5497faf6..ef64b218 100644 --- a/crates/notedeck_columns/src/ui/search_results.rs +++ b/crates/notedeck_columns/src/ui/search_results.rs @@ -1,6 +1,6 @@ use egui::{vec2, FontId, Pos2, Rect, ScrollArea, Vec2b}; use nostrdb::{Ndb, ProfileRecord, Transaction}; -use notedeck::{fonts::get_font_size, ImageCache, NotedeckTextStyle}; +use notedeck::{fonts::get_font_size, MediaCache, NotedeckTextStyle}; use tracing::error; use crate::{ @@ -13,13 +13,13 @@ use super::{profile::get_profile_url, ProfilePic}; pub struct SearchResultsView<'a> { ndb: &'a Ndb, txn: &'a Transaction, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, results: &'a Vec<&'a [u8; 32]>, } impl<'a> SearchResultsView<'a> { pub fn new( - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, ndb: &'a Ndb, txn: &'a Transaction, results: &'a Vec<&'a [u8; 32]>, @@ -84,7 +84,7 @@ impl<'a> SearchResultsView<'a> { fn user_result<'a>( profile: &'a ProfileRecord<'_>, - cache: &'a mut ImageCache, + cache: &'a mut MediaCache, index: usize, width: f32, ) -> impl egui::Widget + 'a { diff --git a/crates/notedeck_columns/src/ui/side_panel.rs b/crates/notedeck_columns/src/ui/side_panel.rs index b864206a..7682d344 100644 --- a/crates/notedeck_columns/src/ui/side_panel.rs +++ b/crates/notedeck_columns/src/ui/side_panel.rs @@ -15,7 +15,7 @@ use crate::{ support::Support, }; -use notedeck::{Accounts, ImageCache, NotedeckTextStyle, ThemeHandler, UserAccount}; +use notedeck::{Accounts, MediaCache, NotedeckTextStyle, ThemeHandler, UserAccount}; use super::{ anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}, @@ -29,7 +29,7 @@ static ICON_WIDTH: f32 = 40.0; pub struct DesktopSidePanel<'a> { ndb: &'a nostrdb::Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, selected_account: Option<&'a UserAccount>, decks_cache: &'a DecksCache, } @@ -70,7 +70,7 @@ impl SidePanelResponse { impl<'a> DesktopSidePanel<'a> { pub fn new( ndb: &'a nostrdb::Ndb, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, selected_account: Option<&'a UserAccount>, decks_cache: &'a DecksCache, ) -> Self { diff --git a/crates/notedeck_columns/src/ui/thread.rs b/crates/notedeck_columns/src/ui/thread.rs index 94a04c70..16f357fe 100644 --- a/crates/notedeck_columns/src/ui/thread.rs +++ b/crates/notedeck_columns/src/ui/thread.rs @@ -5,7 +5,7 @@ use crate::{ }; use nostrdb::{Ndb, Transaction}; -use notedeck::{ImageCache, MuteFun, NoteCache, RootNoteId, UnknownIds}; +use notedeck::{MediaCache, MuteFun, NoteCache, RootNoteId, UnknownIds}; use tracing::error; use super::timeline::TimelineTabView; @@ -15,7 +15,7 @@ pub struct ThreadView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, unknown_ids: &'a mut UnknownIds, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, selected_note_id: &'a [u8; 32], note_options: NoteOptions, id_source: egui::Id, @@ -29,7 +29,7 @@ impl<'a> ThreadView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, unknown_ids: &'a mut UnknownIds, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, selected_note_id: &'a [u8; 32], note_options: NoteOptions, is_muted: &'a MuteFun, diff --git a/crates/notedeck_columns/src/ui/timeline.rs b/crates/notedeck_columns/src/ui/timeline.rs index 2236816d..880883d3 100644 --- a/crates/notedeck_columns/src/ui/timeline.rs +++ b/crates/notedeck_columns/src/ui/timeline.rs @@ -12,7 +12,7 @@ use egui::{vec2, Direction, Layout, Pos2, Stroke}; use egui_tabs::TabColor; use nostrdb::{Ndb, Transaction}; use notedeck::note::root_note_id_from_selected_id; -use notedeck::{ImageCache, MuteFun, NoteCache}; +use notedeck::{MediaCache, MuteFun, NoteCache}; use tracing::{error, warn}; use super::anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}; @@ -22,7 +22,7 @@ pub struct TimelineView<'a> { timeline_cache: &'a mut TimelineCache, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, note_options: NoteOptions, reverse: bool, is_muted: &'a MuteFun, @@ -34,7 +34,7 @@ impl<'a> TimelineView<'a> { timeline_cache: &'a mut TimelineCache, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, note_options: NoteOptions, is_muted: &'a MuteFun, ) -> TimelineView<'a> { @@ -78,7 +78,7 @@ fn timeline_ui( timeline_id: &TimelineKind, timeline_cache: &mut TimelineCache, note_cache: &mut NoteCache, - img_cache: &mut ImageCache, + img_cache: &mut MediaCache, reversed: bool, note_options: NoteOptions, is_muted: &MuteFun, @@ -321,7 +321,7 @@ pub struct TimelineTabView<'a> { txn: &'a Transaction, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, is_muted: &'a MuteFun, } @@ -334,7 +334,7 @@ impl<'a> TimelineTabView<'a> { txn: &'a Transaction, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut ImageCache, + img_cache: &'a mut MediaCache, is_muted: &'a MuteFun, ) -> Self { Self { From 594ea0b42de63faf28b050cef0dc673cdb7f2617 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 19 Feb 2025 17:39:46 -0500 Subject: [PATCH 07/20] Use TexturedImage in MediaCache Signed-off-by: kernelkind --- crates/notedeck/src/imgcache.rs | 6 +++- crates/notedeck/src/lib.rs | 2 +- crates/notedeck_columns/src/images.rs | 17 +++++++---- .../notedeck_columns/src/ui/note/contents.rs | 30 ++++++++++--------- crates/notedeck_columns/src/ui/note/post.rs | 28 ++++++++++------- .../src/ui/profile/picture.rs | 12 ++++++-- 6 files changed, 61 insertions(+), 34 deletions(-) diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs index 6fdb7aaf..d54adee3 100644 --- a/crates/notedeck/src/imgcache.rs +++ b/crates/notedeck/src/imgcache.rs @@ -13,9 +13,13 @@ use std::path; use std::path::PathBuf; use tracing::warn; -pub type MediaCacheValue = Promise>; +pub type MediaCacheValue = Promise>; pub type MediaCacheMap = HashMap; +pub enum TexturedImage { + Static(TextureHandle), +} + pub struct MediaCache { pub cache_dir: path::PathBuf, url_imgs: MediaCacheMap, diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index 034f11c3..3b0a6003 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -31,7 +31,7 @@ pub use context::AppContext; pub use error::{Error, FilterError}; pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; -pub use imgcache::MediaCache; +pub use imgcache::{MediaCache, TexturedImage}; pub use muted::{MuteFun, Muted}; pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf}; pub use notecache::{CachedNote, NoteCache}; diff --git a/crates/notedeck_columns/src/images.rs b/crates/notedeck_columns/src/images.rs index 87ae5b35..8752246c 100644 --- a/crates/notedeck_columns/src/images.rs +++ b/crates/notedeck_columns/src/images.rs @@ -1,7 +1,8 @@ -use egui::{pos2, Color32, ColorImage, Rect, Sense, SizeHint, TextureHandle}; +use egui::{pos2, Color32, ColorImage, Rect, Sense, SizeHint}; use image::imageops::FilterType; use notedeck::MediaCache; use notedeck::Result; +use notedeck::TexturedImage; use poll_promise::Promise; use std::path; use std::path::PathBuf; @@ -177,7 +178,7 @@ fn fetch_img_from_disk( ctx: &egui::Context, url: &str, path: &path::Path, -) -> Promise> { +) -> Promise> { let ctx = ctx.clone(); let url = url.to_owned(); let path = path.to_owned(); @@ -195,7 +196,11 @@ fn fetch_img_from_disk( flat_samples.as_slice(), ); - Ok(ctx.load_texture(&url, img, Default::default())) + Ok(TexturedImage::Static(ctx.load_texture( + &url, + img, + Default::default(), + ))) }) } @@ -217,7 +222,7 @@ pub fn fetch_img( ctx: &egui::Context, url: &str, imgtyp: ImageType, -) -> Promise> { +) -> Promise> { let key = MediaCache::key(url); let path = img_cache.cache_dir.join(key); @@ -235,7 +240,7 @@ fn fetch_img_from_net( ctx: &egui::Context, url: &str, imgtyp: ImageType, -) -> Promise> { +) -> Promise> { let (sender, promise) = Promise::new(); let request = ehttp::Request::get(url); let ctx = ctx.clone(); @@ -251,7 +256,7 @@ fn fetch_img_from_net( // write to disk std::thread::spawn(move || MediaCache::write(&cache_path, &cloned_url, img)); - texture_handle + TexturedImage::Static(texture_handle) }); sender.send(handle); // send the results back to the UI thread. diff --git a/crates/notedeck_columns/src/ui/note/contents.rs b/crates/notedeck_columns/src/ui/note/contents.rs index 0ccf4efd..3010df85 100644 --- a/crates/notedeck_columns/src/ui/note/contents.rs +++ b/crates/notedeck_columns/src/ui/note/contents.rs @@ -329,20 +329,22 @@ fn image_carousel( //ui.add(egui::Spinner::new().size(spinsz)); } // Use the previously resolved image - Some(Ok(img)) => { - let img_resp = ui.add( - Image::new(img) - .max_height(height) - .rounding(5.0) - .fit_to_original_size(1.0), - ); - img_resp.context_menu(|ui| { - if ui.button("Copy Link").clicked() { - ui.ctx().copy_text(image); - ui.close_menu(); - } - }); - } + Some(Ok(img)) => match img { + notedeck::TexturedImage::Static(texture_handle) => { + let img_resp = ui.add( + Image::new(texture_handle) + .max_height(height) + .rounding(5.0) + .fit_to_original_size(1.0), + ); + img_resp.context_menu(|ui| { + if ui.button("Copy Link").clicked() { + ui.ctx().copy_text(image); + ui.close_menu(); + } + }); + } + }, } } }) diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs index b766b792..0dd47ac0 100644 --- a/crates/notedeck_columns/src/ui/note/post.rs +++ b/crates/notedeck_columns/src/ui/note/post.rs @@ -407,18 +407,26 @@ impl<'a> PostView<'a> { media_size }; - let img_resp = ui.add(egui::Image::new(texture).max_size(size).rounding(12.0)); + match texture { + notedeck::TexturedImage::Static(texture_handle) => { + let img_resp = ui.add( + egui::Image::new(texture_handle) + .max_size(size) + .rounding(12.0), + ); - let remove_button_rect = { - let top_left = img_resp.rect.left_top(); - let spacing = 13.0; - let center = Pos2::new(top_left.x + spacing, top_left.y + spacing); - egui::Rect::from_center_size(center, egui::vec2(26.0, 26.0)) - }; - if show_remove_upload_button(ui, remove_button_rect).clicked() { - to_remove.push(i); + let remove_button_rect = { + let top_left = img_resp.rect.left_top(); + let spacing = 13.0; + let center = Pos2::new(top_left.x + spacing, top_left.y + spacing); + egui::Rect::from_center_size(center, egui::vec2(26.0, 26.0)) + }; + if show_remove_upload_button(ui, remove_button_rect).clicked() { + to_remove.push(i); + } + ui.advance_cursor_after_rect(img_resp.rect); + } } - ui.advance_cursor_after_rect(img_resp.rect); } Some(Err(e)) => { self.draft.upload_errors.push(e.to_string()); diff --git a/crates/notedeck_columns/src/ui/profile/picture.rs b/crates/notedeck_columns/src/ui/profile/picture.rs index d8455a65..74fc889d 100644 --- a/crates/notedeck_columns/src/ui/profile/picture.rs +++ b/crates/notedeck_columns/src/ui/profile/picture.rs @@ -119,10 +119,18 @@ fn render_pfp( //error!("Image load error: {:?}", e); paint_circle(ui, ui_size, border) } - Some(Ok(img)) => pfp_image(ui, img, ui_size, border), + Some(Ok(img)) => match img { + notedeck::TexturedImage::Static(texture_handle) => { + pfp_image(ui, texture_handle, ui_size, border) + } + }, } } - Some(Ok(img)) => pfp_image(ui, img, ui_size, border), + Some(Ok(img)) => match img { + notedeck::TexturedImage::Static(texture_handle) => { + pfp_image(ui, texture_handle, ui_size, border) + } + }, } } From 32b3e2110dafeea49485f339727491ec3faa37fd Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 19 Feb 2025 14:19:45 -0500 Subject: [PATCH 08/20] render MediaCache method Signed-off-by: kernelkind --- crates/notedeck_columns/src/ui/images.rs | 42 ++++++++++++++++++++++++ crates/notedeck_columns/src/ui/mod.rs | 1 + 2 files changed, 43 insertions(+) create mode 100644 crates/notedeck_columns/src/ui/images.rs diff --git a/crates/notedeck_columns/src/ui/images.rs b/crates/notedeck_columns/src/ui/images.rs new file mode 100644 index 00000000..06e6164e --- /dev/null +++ b/crates/notedeck_columns/src/ui/images.rs @@ -0,0 +1,42 @@ +use notedeck::{MediaCache, TexturedImage}; + +use crate::images::ImageType; + +use super::ProfilePic; + +pub fn render_media_cache( + ui: &mut egui::Ui, + cache: &mut MediaCache, + url: &str, + img_type: ImageType, + show_waiting: impl FnOnce(&mut egui::Ui), + show_error: impl FnOnce(&mut egui::Ui, String), + show_success: impl FnOnce(&mut egui::Ui, &str, &mut TexturedImage), +) -> egui::Response { + let m_cached_promise = cache.map().get(url); + + if m_cached_promise.is_none() { + let res = crate::images::fetch_img(cache, ui.ctx(), url, img_type); + cache.map_mut().insert(url.to_owned(), res); + } + + egui::Frame::none() + .show(ui, |ui| { + match cache.map_mut().get_mut(url).and_then(|p| p.ready_mut()) { + None => show_waiting(ui), + Some(Err(err)) => { + let err = err.to_string(); + let no_pfp = crate::images::fetch_img( + cache, + ui.ctx(), + ProfilePic::no_pfp_url(), + ImageType::Profile(128), + ); + cache.map_mut().insert(url.to_owned(), no_pfp); + show_error(ui, err) + } + Some(Ok(renderable_media)) => show_success(ui, url, renderable_media), + } + }) + .response +} diff --git a/crates/notedeck_columns/src/ui/mod.rs b/crates/notedeck_columns/src/ui/mod.rs index 0b8a37ae..3e209fc0 100644 --- a/crates/notedeck_columns/src/ui/mod.rs +++ b/crates/notedeck_columns/src/ui/mod.rs @@ -5,6 +5,7 @@ pub mod anim; pub mod column; pub mod configure_deck; pub mod edit_deck; +pub mod images; pub mod mention; pub mod note; pub mod preview; From 7c2b4775f165415f7a734c6fed0ac84ccc14342e Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 19 Feb 2025 17:54:55 -0500 Subject: [PATCH 09/20] move MediaCache rendering to render_media_cache call Signed-off-by: kernelkind --- crates/notedeck/src/imgcache.rs | 7 ++ crates/notedeck/src/lib.rs | 2 +- .../notedeck_columns/src/ui/note/contents.rs | 75 ++++++------------ crates/notedeck_columns/src/ui/note/post.rs | 76 ++++++++----------- .../src/ui/profile/picture.rs | 56 ++++---------- 5 files changed, 81 insertions(+), 135 deletions(-) diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs index d54adee3..509cff3b 100644 --- a/crates/notedeck/src/imgcache.rs +++ b/crates/notedeck/src/imgcache.rs @@ -130,3 +130,10 @@ impl MediaCache { &mut self.url_imgs } } + +// TODO: temporary... +pub fn get_texture(textured_image: &TexturedImage) -> &TextureHandle { + match textured_image { + TexturedImage::Static(texture_handle) => texture_handle, + } +} diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index 3b0a6003..49e70d8e 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -31,7 +31,7 @@ pub use context::AppContext; pub use error::{Error, FilterError}; pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; -pub use imgcache::{MediaCache, TexturedImage}; +pub use imgcache::{get_texture, MediaCache, TexturedImage}; pub use muted::{MuteFun, Muted}; pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf}; pub use notecache::{CachedNote, NoteCache}; diff --git a/crates/notedeck_columns/src/ui/note/contents.rs b/crates/notedeck_columns/src/ui/note/contents.rs index 3010df85..7361c2b8 100644 --- a/crates/notedeck_columns/src/ui/note/contents.rs +++ b/crates/notedeck_columns/src/ui/note/contents.rs @@ -1,7 +1,7 @@ use crate::ui::{ self, + images::render_media_cache, note::{NoteOptions, NoteResponse}, - ProfilePic, }; use crate::{actionbar::NoteAction, images::ImageType, timeline::TimelineKind}; use egui::{Color32, Hyperlink, Image, RichText}; @@ -295,57 +295,32 @@ fn image_carousel( .show(ui, |ui| { ui.horizontal(|ui| { for image in images { - // If the cache is empty, initiate the fetch - let m_cached_promise = img_cache.map().get(&image); - if m_cached_promise.is_none() { - let res = crate::images::fetch_img( - img_cache, - ui.ctx(), - &image, - ImageType::Content(width.round() as u32, height.round() as u32), - ); - img_cache.map_mut().insert(image.to_owned(), res); - } - - // What is the state of the fetch? - match img_cache.map()[&image].ready() { - // Still waiting - None => { + render_media_cache( + ui, + img_cache, + &image, + ImageType::Content(width.round() as u32, height.round() as u32), + |ui| { ui.allocate_space(egui::vec2(spinsz, spinsz)); - //ui.add(egui::Spinner::new().size(spinsz)); - } - // Failed to fetch image! - Some(Err(_err)) => { - // FIXME - use content-specific error instead - let no_pfp = crate::images::fetch_img( - img_cache, - ui.ctx(), - ProfilePic::no_pfp_url(), - ImageType::Profile(128), - ); - img_cache.map_mut().insert(image.to_owned(), no_pfp); - // spin until next pass - ui.allocate_space(egui::vec2(spinsz, spinsz)); - //ui.add(egui::Spinner::new().size(spinsz)); - } - // Use the previously resolved image - Some(Ok(img)) => match img { - notedeck::TexturedImage::Static(texture_handle) => { - let img_resp = ui.add( - Image::new(texture_handle) - .max_height(height) - .rounding(5.0) - .fit_to_original_size(1.0), - ); - img_resp.context_menu(|ui| { - if ui.button("Copy Link").clicked() { - ui.ctx().copy_text(image); - ui.close_menu(); - } - }); - } }, - } + |ui, _| { + ui.allocate_space(egui::vec2(spinsz, spinsz)); + }, + |ui, url, renderable_media| { + let img_resp = ui.add( + Image::new(notedeck::get_texture(renderable_media)) + .max_height(height) + .rounding(5.0) + .fit_to_original_size(1.0), + ); + img_resp.context_menu(|ui| { + if ui.button("Copy Link").clicked() { + ui.ctx().copy_text(url.to_owned()); + ui.close_menu(); + } + }); + }, + ); } }) .response diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs index 0dd47ac0..ca84dc55 100644 --- a/crates/notedeck_columns/src/ui/note/post.rs +++ b/crates/notedeck_columns/src/ui/note/post.rs @@ -1,8 +1,8 @@ use crate::draft::{Draft, Drafts, MentionHint}; -use crate::images::fetch_img; use crate::media_upload::{nostrbuild_nip96_upload, MediaPath}; use crate::post::{downcast_post_buffer, MentionType, NewPost}; use crate::profile::get_display_name; +use crate::ui::images::render_media_cache; use crate::ui::search_results::SearchResultsView; use crate::ui::{self, note::NoteOptions, Preview, PreviewConfig}; use crate::Result; @@ -13,7 +13,7 @@ use egui::{vec2, Frame, Layout, Margin, Pos2, ScrollArea, Sense, TextBuffer}; use enostr::{FilledKeypair, FullKeypair, NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; -use notedeck::{MediaCache, NoteCache}; +use notedeck::{get_texture, MediaCache, NoteCache}; use tracing::error; use super::contents::render_note_preview; @@ -384,21 +384,19 @@ impl<'a> PostView<'a> { } else { (300, 300) }; - let m_cached_promise = self.img_cache.map().get(&media.url); - if m_cached_promise.is_none() { - let promise = fetch_img( - self.img_cache, - ui.ctx(), - &media.url, - crate::images::ImageType::Content(width, height), - ); - self.img_cache - .map_mut() - .insert(media.url.to_owned(), promise); - } - - match self.img_cache.map()[&media.url].ready() { - Some(Ok(texture)) => { + render_media_cache( + ui, + self.img_cache, + &media.url, + crate::images::ImageType::Content(width, height), + |ui| { + ui.spinner(); + }, + |_, e| { + self.draft.upload_errors.push(e.clone()); + error!("{e}"); + }, + |ui, _, renderable_media| { let media_size = vec2(width as f32, height as f32); let max_size = vec2(300.0, 300.0); let size = if media_size.x > max_size.x || media_size.y > max_size.y { @@ -407,35 +405,25 @@ impl<'a> PostView<'a> { media_size }; - match texture { - notedeck::TexturedImage::Static(texture_handle) => { - let img_resp = ui.add( - egui::Image::new(texture_handle) - .max_size(size) - .rounding(12.0), - ); + let texture_handle = get_texture(renderable_media); + let img_resp = ui.add( + egui::Image::new(texture_handle) + .max_size(size) + .rounding(12.0), + ); - let remove_button_rect = { - let top_left = img_resp.rect.left_top(); - let spacing = 13.0; - let center = Pos2::new(top_left.x + spacing, top_left.y + spacing); - egui::Rect::from_center_size(center, egui::vec2(26.0, 26.0)) - }; - if show_remove_upload_button(ui, remove_button_rect).clicked() { - to_remove.push(i); - } - ui.advance_cursor_after_rect(img_resp.rect); - } + let remove_button_rect = { + let top_left = img_resp.rect.left_top(); + let spacing = 13.0; + let center = Pos2::new(top_left.x + spacing, top_left.y + spacing); + egui::Rect::from_center_size(center, egui::vec2(26.0, 26.0)) + }; + if show_remove_upload_button(ui, remove_button_rect).clicked() { + to_remove.push(i); } - } - Some(Err(e)) => { - self.draft.upload_errors.push(e.to_string()); - error!("{e}"); - } - None => { - ui.spinner(); - } - } + ui.advance_cursor_after_rect(img_resp.rect); + }, + ); } to_remove.reverse(); for i in to_remove { diff --git a/crates/notedeck_columns/src/ui/profile/picture.rs b/crates/notedeck_columns/src/ui/profile/picture.rs index 74fc889d..b6abbbaf 100644 --- a/crates/notedeck_columns/src/ui/profile/picture.rs +++ b/crates/notedeck_columns/src/ui/profile/picture.rs @@ -1,4 +1,5 @@ use crate::images::ImageType; +use crate::ui::images::render_media_cache; use crate::ui::{Preview, PreviewConfig}; use egui::{vec2, Sense, Stroke, TextureHandle}; use nostrdb::{Ndb, Transaction}; @@ -91,47 +92,22 @@ fn render_pfp( // We will want to downsample these so it's not blurry on hi res displays let img_size = 128u32; - let m_cached_promise = img_cache.map().get(url); - if m_cached_promise.is_none() { - let res = crate::images::fetch_img(img_cache, ui.ctx(), url, ImageType::Profile(img_size)); - img_cache.map_mut().insert(url.to_owned(), res); - } - - match img_cache.map()[url].ready() { - None => paint_circle(ui, ui_size, border), - - // Failed to fetch profile! - Some(Err(_err)) => { - let m_failed_promise = img_cache.map().get(url); - if m_failed_promise.is_none() { - let no_pfp = crate::images::fetch_img( - img_cache, - ui.ctx(), - ProfilePic::no_pfp_url(), - ImageType::Profile(img_size), - ); - img_cache.map_mut().insert(url.to_owned(), no_pfp); - } - - match img_cache.map().get(url).unwrap().ready() { - None => paint_circle(ui, ui_size, border), - Some(Err(_e)) => { - //error!("Image load error: {:?}", e); - paint_circle(ui, ui_size, border) - } - Some(Ok(img)) => match img { - notedeck::TexturedImage::Static(texture_handle) => { - pfp_image(ui, texture_handle, ui_size, border) - } - }, - } - } - Some(Ok(img)) => match img { - notedeck::TexturedImage::Static(texture_handle) => { - pfp_image(ui, texture_handle, ui_size, border) - } + render_media_cache( + ui, + img_cache, + url, + ImageType::Profile(img_size), + |ui| { + paint_circle(ui, ui_size, border); }, - } + |ui, _| { + paint_circle(ui, ui_size, border); + }, + |ui, _, renderable_media| { + let texture_handle = notedeck::get_texture(renderable_media); + pfp_image(ui, texture_handle, ui_size, border); + }, + ) } fn pfp_image( From 888a933e56fafa1b4dc59e5d79861b131662d24c Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sat, 15 Feb 2025 23:09:57 -0500 Subject: [PATCH 10/20] support multiple media cache files Signed-off-by: kernelkind --- crates/notedeck/src/app.rs | 4 +++- crates/notedeck/src/imgcache.rs | 12 ++++++++++-- crates/notedeck/src/lib.rs | 2 +- crates/notedeck_columns/src/app.rs | 4 +++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs index 170cef88..8cc79b57 100644 --- a/crates/notedeck/src/app.rs +++ b/crates/notedeck/src/app.rs @@ -129,7 +129,9 @@ impl Notedeck { let _ = std::fs::create_dir_all(&dbpath_str); - let img_cache_dir = path.path(DataPathType::Cache).join(MediaCache::rel_dir()); + let img_cache_dir = path + .path(DataPathType::Cache) + .join(MediaCache::rel_dir(crate::imgcache::MediaCacheType::Image)); let _ = std::fs::create_dir_all(img_cache_dir.clone()); let map_size = if cfg!(target_os = "windows") { diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs index 509cff3b..0ba841d8 100644 --- a/crates/notedeck/src/imgcache.rs +++ b/crates/notedeck/src/imgcache.rs @@ -25,6 +25,11 @@ pub struct MediaCache { url_imgs: MediaCacheMap, } +pub enum MediaCacheType { + Image, + Gif, +} + impl MediaCache { pub fn new(cache_dir: path::PathBuf) -> Self { Self { @@ -33,8 +38,11 @@ impl MediaCache { } } - pub fn rel_dir() -> &'static str { - "img" + pub fn rel_dir(cache_type: MediaCacheType) -> &'static str { + match cache_type { + MediaCacheType::Image => "img", + MediaCacheType::Gif => "gif", + } } /* diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index 49e70d8e..78f68921 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -31,7 +31,7 @@ pub use context::AppContext; pub use error::{Error, FilterError}; pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; -pub use imgcache::{get_texture, MediaCache, TexturedImage}; +pub use imgcache::{get_texture, MediaCache, MediaCacheType, TexturedImage}; pub use muted::{MuteFun, Muted}; pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf}; pub use notecache::{CachedNote, NoteCache}; diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs index 568001d8..c624aeda 100644 --- a/crates/notedeck_columns/src/app.rs +++ b/crates/notedeck_columns/src/app.rs @@ -464,7 +464,9 @@ impl Damus { let decks_cache = DecksCache::default(); let path = DataPath::new(&data_path); - let imgcache_dir = path.path(DataPathType::Cache).join(MediaCache::rel_dir()); + let imgcache_dir = path + .path(DataPathType::Cache) + .join(MediaCache::rel_dir(notedeck::MediaCacheType::Image)); let _ = std::fs::create_dir_all(imgcache_dir.clone()); let debug = true; From fc9e1ff7f60b316e1b5b0673d32e039452a7b69e Mon Sep 17 00:00:00 2001 From: kernelkind Date: Tue, 25 Feb 2025 16:20:45 -0500 Subject: [PATCH 11/20] introduce Images Signed-off-by: kernelkind --- crates/notedeck/src/imgcache.rs | 39 +++++++++++++++++++++++++++++++++ crates/notedeck/src/lib.rs | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs index 0ba841d8..42ceaa85 100644 --- a/crates/notedeck/src/imgcache.rs +++ b/crates/notedeck/src/imgcache.rs @@ -1,3 +1,4 @@ +use crate::urls::{UrlCache, UrlMimes}; use crate::Result; use egui::TextureHandle; use poll_promise::Promise; @@ -6,6 +7,7 @@ use egui::ColorImage; use std::collections::HashMap; use std::fs::{create_dir_all, File}; +use std::time::{Duration, Instant, SystemTime}; use hex::ToHex; use sha2::Digest; @@ -25,6 +27,7 @@ pub struct MediaCache { url_imgs: MediaCacheMap, } +#[derive(Clone)] pub enum MediaCacheType { Image, Gif, @@ -145,3 +148,39 @@ pub fn get_texture(textured_image: &TexturedImage) -> &TextureHandle { TexturedImage::Static(texture_handle) => texture_handle, } } + +#[allow(dead_code)] +pub struct Images { + pub static_imgs: MediaCache, + pub gifs: MediaCache, + pub urls: UrlMimes, + pub gif_states: GifStateMap, +} + +impl Images { + #[allow(dead_code)] + /// path to directory to place [`MediaCache`]s + pub fn new(path: path::PathBuf) -> Self { + Self { + static_imgs: MediaCache::new(path.join(MediaCache::rel_dir(MediaCacheType::Image))), + gifs: MediaCache::new(path.join(MediaCache::rel_dir(MediaCacheType::Gif))), + urls: UrlMimes::new(UrlCache::new(path.join(UrlCache::rel_dir()))), + gif_states: Default::default(), + } + } + + #[allow(dead_code)] + pub fn migrate_v0(&self) -> Result<()> { + self.static_imgs.migrate_v0()?; + self.gifs.migrate_v0() + } +} + +pub type GifStateMap = HashMap; + +pub struct GifState { + pub last_frame_rendered: Instant, + pub last_frame_duration: Duration, + pub next_frame_time: Option, + pub last_frame_index: usize, +} diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index 78f68921..6efc86a0 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -31,7 +31,7 @@ pub use context::AppContext; pub use error::{Error, FilterError}; pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; -pub use imgcache::{get_texture, MediaCache, MediaCacheType, TexturedImage}; +pub use imgcache::{get_texture, GifState, GifStateMap, MediaCache, MediaCacheType, TexturedImage}; pub use muted::{MuteFun, Muted}; pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf}; pub use notecache::{CachedNote, NoteCache}; From 75a352a86fba52d41c09b36d3a7cb01082dcf0c3 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 19 Feb 2025 14:21:34 -0500 Subject: [PATCH 12/20] render Images method Signed-off-by: kernelkind --- crates/notedeck_columns/src/ui/images.rs | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/notedeck_columns/src/ui/images.rs b/crates/notedeck_columns/src/ui/images.rs index 06e6164e..7731804e 100644 --- a/crates/notedeck_columns/src/ui/images.rs +++ b/crates/notedeck_columns/src/ui/images.rs @@ -1,9 +1,35 @@ -use notedeck::{MediaCache, TexturedImage}; +use notedeck::{Images, MediaCache, MediaCacheType, TexturedImage}; use crate::images::ImageType; use super::ProfilePic; +pub fn render_images( + ui: &mut egui::Ui, + images: &mut Images, + url: &str, + img_type: ImageType, + cache_type: MediaCacheType, + show_waiting: impl FnOnce(&mut egui::Ui), + show_error: impl FnOnce(&mut egui::Ui, String), + show_success: impl FnOnce(&mut egui::Ui, &str, &mut TexturedImage), +) -> egui::Response { + let cache = match cache_type.clone() { + MediaCacheType::Image => &mut images.static_imgs, + MediaCacheType::Gif => &mut images.gifs, + }; + + render_media_cache( + ui, + cache, + url, + img_type, + show_waiting, + show_error, + show_success, + ) +} + pub fn render_media_cache( ui: &mut egui::Ui, cache: &mut MediaCache, From 33fdf647e3999a759704676b9536f9b0745aaade Mon Sep 17 00:00:00 2001 From: kernelkind Date: Tue, 18 Feb 2025 19:42:17 -0500 Subject: [PATCH 13/20] migrate to using Images instead of MediaCache directly Signed-off-by: kernelkind --- crates/notedeck/src/app.rs | 12 +++++------- crates/notedeck/src/context.rs | 4 ++-- crates/notedeck/src/imgcache.rs | 3 --- crates/notedeck/src/lib.rs | 4 +++- crates/notedeck/src/urls.rs | 8 ++++---- crates/notedeck_columns/src/accounts/mod.rs | 4 ++-- crates/notedeck_columns/src/app.rs | 6 ++---- crates/notedeck_columns/src/timeline/route.rs | 6 +++--- crates/notedeck_columns/src/ui/accounts.rs | 8 ++++---- crates/notedeck_columns/src/ui/add_column.rs | 6 +++--- crates/notedeck_columns/src/ui/column/header.rs | 6 +++--- crates/notedeck_columns/src/ui/images.rs | 8 ++------ crates/notedeck_columns/src/ui/mention.rs | 9 +++++---- crates/notedeck_columns/src/ui/note/contents.rs | 17 +++++++++-------- crates/notedeck_columns/src/ui/note/mod.rs | 6 +++--- crates/notedeck_columns/src/ui/note/post.rs | 13 +++++++------ .../src/ui/note/quote_repost.rs | 6 +++--- crates/notedeck_columns/src/ui/note/reply.rs | 6 +++--- .../src/ui/note/reply_description.rs | 6 +++--- crates/notedeck_columns/src/ui/profile/edit.rs | 6 +++--- crates/notedeck_columns/src/ui/profile/mod.rs | 6 +++--- .../notedeck_columns/src/ui/profile/picture.rs | 14 +++++++------- .../notedeck_columns/src/ui/profile/preview.rs | 10 +++++----- .../notedeck_columns/src/ui/search_results.rs | 8 ++++---- crates/notedeck_columns/src/ui/side_panel.rs | 6 +++--- crates/notedeck_columns/src/ui/thread.rs | 6 +++--- crates/notedeck_columns/src/ui/timeline.rs | 13 +++++++------ 27 files changed, 101 insertions(+), 106 deletions(-) diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs index 8cc79b57..0270fed0 100644 --- a/crates/notedeck/src/app.rs +++ b/crates/notedeck/src/app.rs @@ -1,7 +1,7 @@ use crate::persist::{AppSizeHandler, ZoomHandler}; use crate::{ - Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageType, - MediaCache, NoteCache, RelayDebugView, ThemeHandler, UnknownIds, + Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, Images, + KeyStorageType, NoteCache, RelayDebugView, ThemeHandler, UnknownIds, }; use egui::ThemePreference; use enostr::RelayPool; @@ -19,7 +19,7 @@ pub trait App { /// Main notedeck app framework pub struct Notedeck { ndb: Ndb, - img_cache: MediaCache, + img_cache: Images, unknown_ids: UnknownIds, pool: RelayPool, note_cache: NoteCache, @@ -129,9 +129,7 @@ impl Notedeck { let _ = std::fs::create_dir_all(&dbpath_str); - let img_cache_dir = path - .path(DataPathType::Cache) - .join(MediaCache::rel_dir(crate::imgcache::MediaCacheType::Image)); + let img_cache_dir = path.path(DataPathType::Cache); let _ = std::fs::create_dir_all(img_cache_dir.clone()); let map_size = if cfg!(target_os = "windows") { @@ -186,7 +184,7 @@ impl Notedeck { } } - let img_cache = MediaCache::new(img_cache_dir); + let img_cache = Images::new(img_cache_dir); let note_cache = NoteCache::default(); let unknown_ids = UnknownIds::default(); let zoom = ZoomHandler::new(&path); diff --git a/crates/notedeck/src/context.rs b/crates/notedeck/src/context.rs index 5cf2b7a9..76204d25 100644 --- a/crates/notedeck/src/context.rs +++ b/crates/notedeck/src/context.rs @@ -1,4 +1,4 @@ -use crate::{Accounts, Args, DataPath, MediaCache, NoteCache, ThemeHandler, UnknownIds}; +use crate::{Accounts, Args, DataPath, Images, NoteCache, ThemeHandler, UnknownIds}; use enostr::RelayPool; use nostrdb::Ndb; @@ -7,7 +7,7 @@ use nostrdb::Ndb; pub struct AppContext<'a> { pub ndb: &'a mut Ndb, - pub img_cache: &'a mut MediaCache, + pub img_cache: &'a mut Images, pub unknown_ids: &'a mut UnknownIds, pub pool: &'a mut RelayPool, pub note_cache: &'a mut NoteCache, diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs index 42ceaa85..86d789a5 100644 --- a/crates/notedeck/src/imgcache.rs +++ b/crates/notedeck/src/imgcache.rs @@ -149,7 +149,6 @@ pub fn get_texture(textured_image: &TexturedImage) -> &TextureHandle { } } -#[allow(dead_code)] pub struct Images { pub static_imgs: MediaCache, pub gifs: MediaCache, @@ -158,7 +157,6 @@ pub struct Images { } impl Images { - #[allow(dead_code)] /// path to directory to place [`MediaCache`]s pub fn new(path: path::PathBuf) -> Self { Self { @@ -169,7 +167,6 @@ impl Images { } } - #[allow(dead_code)] pub fn migrate_v0(&self) -> Result<()> { self.static_imgs.migrate_v0()?; self.gifs.migrate_v0() diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index 6efc86a0..741437ca 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -31,7 +31,9 @@ pub use context::AppContext; pub use error::{Error, FilterError}; pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; -pub use imgcache::{get_texture, GifState, GifStateMap, MediaCache, MediaCacheType, TexturedImage}; +pub use imgcache::{ + get_texture, GifState, GifStateMap, Images, MediaCache, MediaCacheType, TexturedImage, +}; pub use muted::{MuteFun, Muted}; pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf}; pub use notecache::{CachedNote, NoteCache}; diff --git a/crates/notedeck/src/urls.rs b/crates/notedeck/src/urls.rs index 0705aa43..21dce4d3 100644 --- a/crates/notedeck/src/urls.rs +++ b/crates/notedeck/src/urls.rs @@ -93,7 +93,7 @@ fn read_from_disk(path: PathBuf) -> Promise> { match result { Ok(data) => sender.send(Some(data)), Err(e) => { - tracing::error!("problem deserializing UrlCache: {e}"); + tracing::error!("problem deserializing UrlMimes: {e}"); sender.send(None) } } @@ -116,13 +116,13 @@ fn save_to_disk(path: PathBuf, cache: Arc>) { Ok(()) } else { Err(Error::Generic( - "Could not read UrlCache behind RwLock".to_owned(), + "Could not read UrlMimes behind RwLock".to_owned(), )) } })(); if let Err(e) = result { - tracing::error!("Failed to save UrlCache: {}", e); + tracing::error!("Failed to save UrlMimes: {}", e); } }); } @@ -144,7 +144,7 @@ fn ehttp_get_mime_type(url: &str, sender: poll_promise::Sender) { } Err(err) => { sender.send(MimeResult::Err(HttpError::HttpFailure)); - tracing::error!("failed ehttp for UrlCache: {err}"); + tracing::error!("failed ehttp for UrlMimes: {err}"); } }, ); diff --git a/crates/notedeck_columns/src/accounts/mod.rs b/crates/notedeck_columns/src/accounts/mod.rs index 6f6205cb..f98803d2 100644 --- a/crates/notedeck_columns/src/accounts/mod.rs +++ b/crates/notedeck_columns/src/accounts/mod.rs @@ -2,7 +2,7 @@ use enostr::FullKeypair; use nostrdb::Ndb; use notedeck::{ - Accounts, AccountsAction, AddAccountAction, MediaCache, SingleUnkIdAction, SwitchAccountAction, + Accounts, AccountsAction, AddAccountAction, Images, SingleUnkIdAction, SwitchAccountAction, }; use crate::app::get_active_columns_mut; @@ -27,7 +27,7 @@ pub fn render_accounts_route( ui: &mut egui::Ui, ndb: &Ndb, col: usize, - img_cache: &mut MediaCache, + img_cache: &mut Images, accounts: &mut Accounts, decks: &mut DecksCache, login_state: &mut AcquireKeyState, diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs index c624aeda..e521da67 100644 --- a/crates/notedeck_columns/src/app.rs +++ b/crates/notedeck_columns/src/app.rs @@ -13,7 +13,7 @@ use crate::{ Result, }; -use notedeck::{Accounts, AppContext, DataPath, DataPathType, FilterState, MediaCache, UnknownIds}; +use notedeck::{Accounts, AppContext, DataPath, DataPathType, FilterState, UnknownIds}; use enostr::{ClientMessage, Keypair, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPool}; use uuid::Uuid; @@ -464,9 +464,7 @@ impl Damus { let decks_cache = DecksCache::default(); let path = DataPath::new(&data_path); - let imgcache_dir = path - .path(DataPathType::Cache) - .join(MediaCache::rel_dir(notedeck::MediaCacheType::Image)); + let imgcache_dir = path.path(DataPathType::Cache); let _ = std::fs::create_dir_all(imgcache_dir.clone()); let debug = true; diff --git a/crates/notedeck_columns/src/timeline/route.rs b/crates/notedeck_columns/src/timeline/route.rs index 81f990ae..ea506b91 100644 --- a/crates/notedeck_columns/src/timeline/route.rs +++ b/crates/notedeck_columns/src/timeline/route.rs @@ -7,12 +7,12 @@ use crate::{ use enostr::Pubkey; use nostrdb::Ndb; -use notedeck::{Accounts, MediaCache, MuteFun, NoteCache, UnknownIds}; +use notedeck::{Accounts, Images, MuteFun, NoteCache, UnknownIds}; #[allow(clippy::too_many_arguments)] pub fn render_timeline_route( ndb: &Ndb, - img_cache: &mut MediaCache, + img_cache: &mut Images, unknown_ids: &mut UnknownIds, note_cache: &mut NoteCache, timeline_cache: &mut TimelineCache, @@ -102,7 +102,7 @@ pub fn render_profile_route( accounts: &Accounts, ndb: &Ndb, timeline_cache: &mut TimelineCache, - img_cache: &mut MediaCache, + img_cache: &mut Images, note_cache: &mut NoteCache, unknown_ids: &mut UnknownIds, col: usize, diff --git a/crates/notedeck_columns/src/ui/accounts.rs b/crates/notedeck_columns/src/ui/accounts.rs index 051cfc88..5133982d 100644 --- a/crates/notedeck_columns/src/ui/accounts.rs +++ b/crates/notedeck_columns/src/ui/accounts.rs @@ -3,14 +3,14 @@ use egui::{ Align, Button, Frame, Image, InnerResponse, Layout, RichText, ScrollArea, Ui, UiBuilder, Vec2, }; use nostrdb::{Ndb, Transaction}; -use notedeck::{Accounts, MediaCache}; +use notedeck::{Accounts, Images}; use super::profile::preview::SimpleProfilePreview; pub struct AccountsView<'a> { ndb: &'a Ndb, accounts: &'a Accounts, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, } #[derive(Clone, Debug)] @@ -27,7 +27,7 @@ enum ProfilePreviewAction { } impl<'a> AccountsView<'a> { - pub fn new(ndb: &'a Ndb, accounts: &'a Accounts, img_cache: &'a mut MediaCache) -> Self { + pub fn new(ndb: &'a Ndb, accounts: &'a Accounts, img_cache: &'a mut Images) -> Self { AccountsView { ndb, accounts, @@ -54,7 +54,7 @@ impl<'a> AccountsView<'a> { ui: &mut Ui, accounts: &Accounts, ndb: &Ndb, - img_cache: &mut MediaCache, + img_cache: &mut Images, ) -> Option { let mut return_op: Option = None; ui.allocate_ui_with_layout( diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs index 32a98dd6..2cce5662 100644 --- a/crates/notedeck_columns/src/ui/add_column.rs +++ b/crates/notedeck_columns/src/ui/add_column.rs @@ -17,7 +17,7 @@ use crate::{ Damus, }; -use notedeck::{AppContext, MediaCache, NotedeckTextStyle, UserAccount}; +use notedeck::{AppContext, Images, NotedeckTextStyle, UserAccount}; use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter}; use super::{anim::AnimationHelper, padding, ProfilePreview}; @@ -163,7 +163,7 @@ impl AddColumnOption { pub struct AddColumnView<'a> { key_state_map: &'a mut HashMap, ndb: &'a Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, cur_account: Option<&'a UserAccount>, } @@ -171,7 +171,7 @@ impl<'a> AddColumnView<'a> { pub fn new( key_state_map: &'a mut HashMap, ndb: &'a Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, cur_account: Option<&'a UserAccount>, ) -> Self { Self { diff --git a/crates/notedeck_columns/src/ui/column/header.rs b/crates/notedeck_columns/src/ui/column/header.rs index 3713d0c9..564853ab 100644 --- a/crates/notedeck_columns/src/ui/column/header.rs +++ b/crates/notedeck_columns/src/ui/column/header.rs @@ -16,11 +16,11 @@ use egui::Margin; use egui::{RichText, Stroke, UiBuilder}; use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use notedeck::{MediaCache, NotedeckTextStyle}; +use notedeck::{Images, NotedeckTextStyle}; pub struct NavTitle<'a> { ndb: &'a Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, columns: &'a Columns, routes: &'a [Route], col_id: usize, @@ -29,7 +29,7 @@ pub struct NavTitle<'a> { impl<'a> NavTitle<'a> { pub fn new( ndb: &'a Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, columns: &'a Columns, routes: &'a [Route], col_id: usize, diff --git a/crates/notedeck_columns/src/ui/images.rs b/crates/notedeck_columns/src/ui/images.rs index 7731804e..0954a8ab 100644 --- a/crates/notedeck_columns/src/ui/images.rs +++ b/crates/notedeck_columns/src/ui/images.rs @@ -1,4 +1,4 @@ -use notedeck::{Images, MediaCache, MediaCacheType, TexturedImage}; +use notedeck::{Images, MediaCache, TexturedImage}; use crate::images::ImageType; @@ -9,15 +9,11 @@ pub fn render_images( images: &mut Images, url: &str, img_type: ImageType, - cache_type: MediaCacheType, show_waiting: impl FnOnce(&mut egui::Ui), show_error: impl FnOnce(&mut egui::Ui, String), show_success: impl FnOnce(&mut egui::Ui, &str, &mut TexturedImage), ) -> egui::Response { - let cache = match cache_type.clone() { - MediaCacheType::Image => &mut images.static_imgs, - MediaCacheType::Gif => &mut images.gifs, - }; + let cache = &mut images.static_imgs; render_media_cache( ui, diff --git a/crates/notedeck_columns/src/ui/mention.rs b/crates/notedeck_columns/src/ui/mention.rs index 6ad00e42..1177bf5e 100644 --- a/crates/notedeck_columns/src/ui/mention.rs +++ b/crates/notedeck_columns/src/ui/mention.rs @@ -3,11 +3,11 @@ use crate::{actionbar::NoteAction, profile::get_display_name, timeline::Timeline use egui::Sense; use enostr::Pubkey; use nostrdb::{Ndb, Transaction}; -use notedeck::MediaCache; +use notedeck::Images; pub struct Mention<'a> { ndb: &'a Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, txn: &'a Transaction, pk: &'a [u8; 32], selectable: bool, @@ -17,7 +17,7 @@ pub struct Mention<'a> { impl<'a> Mention<'a> { pub fn new( ndb: &'a Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, txn: &'a Transaction, pk: &'a [u8; 32], ) -> Self { @@ -62,9 +62,10 @@ impl egui::Widget for Mention<'_> { } } +#[allow(clippy::too_many_arguments)] fn mention_ui( ndb: &Ndb, - img_cache: &mut MediaCache, + img_cache: &mut Images, txn: &Transaction, pk: &[u8; 32], ui: &mut egui::Ui, diff --git a/crates/notedeck_columns/src/ui/note/contents.rs b/crates/notedeck_columns/src/ui/note/contents.rs index 7361c2b8..26ffd5c2 100644 --- a/crates/notedeck_columns/src/ui/note/contents.rs +++ b/crates/notedeck_columns/src/ui/note/contents.rs @@ -1,6 +1,6 @@ +use crate::ui::images::render_images; use crate::ui::{ self, - images::render_media_cache, note::{NoteOptions, NoteResponse}, }; use crate::{actionbar::NoteAction, images::ImageType, timeline::TimelineKind}; @@ -8,11 +8,11 @@ use egui::{Color32, Hyperlink, Image, RichText}; use nostrdb::{BlockType, Mention, Ndb, Note, NoteKey, Transaction}; use tracing::warn; -use notedeck::{MediaCache, NoteCache}; +use notedeck::{Images, NoteCache}; pub struct NoteContents<'a> { ndb: &'a Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, note_cache: &'a mut NoteCache, txn: &'a Transaction, note: &'a Note<'a>, @@ -22,9 +22,10 @@ pub struct NoteContents<'a> { } impl<'a> NoteContents<'a> { + #[allow(clippy::too_many_arguments)] pub fn new( ndb: &'a Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, note_cache: &'a mut NoteCache, txn: &'a Transaction, note: &'a Note, @@ -72,7 +73,7 @@ pub fn render_note_preview( ui: &mut egui::Ui, ndb: &Ndb, note_cache: &mut NoteCache, - img_cache: &mut MediaCache, + img_cache: &mut Images, txn: &Transaction, id: &[u8; 32], parent: NoteKey, @@ -134,7 +135,7 @@ fn is_image_link(url: &str) -> bool { fn render_note_contents( ui: &mut egui::Ui, ndb: &Ndb, - img_cache: &mut MediaCache, + img_cache: &mut Images, note_cache: &mut NoteCache, txn: &Transaction, note: &Note, @@ -279,7 +280,7 @@ fn rot13(input: &str) -> String { fn image_carousel( ui: &mut egui::Ui, - img_cache: &mut MediaCache, + img_cache: &mut Images, images: Vec, carousel_id: egui::Id, ) { @@ -295,7 +296,7 @@ fn image_carousel( .show(ui, |ui| { ui.horizontal(|ui| { for image in images { - render_media_cache( + render_images( ui, img_cache, &image, diff --git a/crates/notedeck_columns/src/ui/note/mod.rs b/crates/notedeck_columns/src/ui/note/mod.rs index 46103864..71e92bb5 100644 --- a/crates/notedeck_columns/src/ui/note/mod.rs +++ b/crates/notedeck_columns/src/ui/note/mod.rs @@ -25,14 +25,14 @@ use egui::emath::{pos2, Vec2}; use egui::{Id, Label, Pos2, Rect, Response, RichText, Sense}; use enostr::{NoteId, Pubkey}; use nostrdb::{Ndb, Note, NoteKey, Transaction}; -use notedeck::{CachedNote, MediaCache, NoteCache, NotedeckTextStyle}; +use notedeck::{CachedNote, Images, NoteCache, NotedeckTextStyle}; use super::profile::preview::one_line_display_name_widget; pub struct NoteView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, parent: Option, note: &'a nostrdb::Note<'a>, flags: NoteOptions, @@ -74,7 +74,7 @@ impl<'a> NoteView<'a> { pub fn new( ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, note: &'a nostrdb::Note<'a>, mut flags: NoteOptions, ) -> Self { diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs index ca84dc55..88303ffb 100644 --- a/crates/notedeck_columns/src/ui/note/post.rs +++ b/crates/notedeck_columns/src/ui/note/post.rs @@ -2,7 +2,7 @@ use crate::draft::{Draft, Drafts, MentionHint}; use crate::media_upload::{nostrbuild_nip96_upload, MediaPath}; use crate::post::{downcast_post_buffer, MentionType, NewPost}; use crate::profile::get_display_name; -use crate::ui::images::render_media_cache; +use crate::ui::images::render_images; use crate::ui::search_results::SearchResultsView; use crate::ui::{self, note::NoteOptions, Preview, PreviewConfig}; use crate::Result; @@ -13,7 +13,7 @@ use egui::{vec2, Frame, Layout, Margin, Pos2, ScrollArea, Sense, TextBuffer}; use enostr::{FilledKeypair, FullKeypair, NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; -use notedeck::{get_texture, MediaCache, NoteCache}; +use notedeck::{get_texture, Images, NoteCache}; use tracing::error; use super::contents::render_note_preview; @@ -22,7 +22,7 @@ pub struct PostView<'a> { ndb: &'a Ndb, draft: &'a mut Draft, post_type: PostType, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, note_cache: &'a mut NoteCache, poster: FilledKeypair<'a>, id_source: Option, @@ -88,7 +88,7 @@ impl<'a> PostView<'a> { ndb: &'a Ndb, draft: &'a mut Draft, post_type: PostType, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, note_cache: &'a mut NoteCache, poster: FilledKeypair<'a>, inner_rect: egui::Rect, @@ -384,7 +384,8 @@ impl<'a> PostView<'a> { } else { (300, 300) }; - render_media_cache( + + render_images( ui, self.img_cache, &media.url, @@ -393,7 +394,7 @@ impl<'a> PostView<'a> { ui.spinner(); }, |_, e| { - self.draft.upload_errors.push(e.clone()); + self.draft.upload_errors.push(e.to_string()); error!("{e}"); }, |ui, _, renderable_media| { diff --git a/crates/notedeck_columns/src/ui/note/quote_repost.rs b/crates/notedeck_columns/src/ui/note/quote_repost.rs index 22f98c5e..9bf8a877 100644 --- a/crates/notedeck_columns/src/ui/note/quote_repost.rs +++ b/crates/notedeck_columns/src/ui/note/quote_repost.rs @@ -1,6 +1,6 @@ use enostr::{FilledKeypair, NoteId}; use nostrdb::Ndb; -use notedeck::{MediaCache, NoteCache}; +use notedeck::{Images, NoteCache}; use crate::{ draft::Draft, @@ -13,7 +13,7 @@ pub struct QuoteRepostView<'a> { ndb: &'a Ndb, poster: FilledKeypair<'a>, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, draft: &'a mut Draft, quoting_note: &'a nostrdb::Note<'a>, id_source: Option, @@ -27,7 +27,7 @@ impl<'a> QuoteRepostView<'a> { ndb: &'a Ndb, poster: FilledKeypair<'a>, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, draft: &'a mut Draft, quoting_note: &'a nostrdb::Note<'a>, inner_rect: egui::Rect, diff --git a/crates/notedeck_columns/src/ui/note/reply.rs b/crates/notedeck_columns/src/ui/note/reply.rs index 6aa5a627..d8a1eb9e 100644 --- a/crates/notedeck_columns/src/ui/note/reply.rs +++ b/crates/notedeck_columns/src/ui/note/reply.rs @@ -4,13 +4,13 @@ use crate::ui::note::{NoteOptions, PostResponse, PostType}; use enostr::{FilledKeypair, NoteId}; use nostrdb::Ndb; -use notedeck::{MediaCache, NoteCache}; +use notedeck::{Images, NoteCache}; pub struct PostReplyView<'a> { ndb: &'a Ndb, poster: FilledKeypair<'a>, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, draft: &'a mut Draft, note: &'a nostrdb::Note<'a>, id_source: Option, @@ -25,7 +25,7 @@ impl<'a> PostReplyView<'a> { poster: FilledKeypair<'a>, draft: &'a mut Draft, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, note: &'a nostrdb::Note<'a>, inner_rect: egui::Rect, note_options: NoteOptions, diff --git a/crates/notedeck_columns/src/ui/note/reply_description.rs b/crates/notedeck_columns/src/ui/note/reply_description.rs index 2d280358..372b8332 100644 --- a/crates/notedeck_columns/src/ui/note/reply_description.rs +++ b/crates/notedeck_columns/src/ui/note/reply_description.rs @@ -4,7 +4,7 @@ use crate::{ }; use egui::{Label, RichText, Sense}; use nostrdb::{Ndb, Note, NoteReply, Transaction}; -use notedeck::{MediaCache, NoteCache}; +use notedeck::{Images, NoteCache}; #[must_use = "Please handle the resulting note action"] pub fn reply_desc( @@ -12,7 +12,7 @@ pub fn reply_desc( txn: &Transaction, note_reply: &NoteReply, ndb: &Ndb, - img_cache: &mut MediaCache, + img_cache: &mut Images, note_cache: &mut NoteCache, note_options: NoteOptions, ) -> Option { @@ -29,7 +29,7 @@ pub fn reply_desc( // note link renderer helper let note_link = |ui: &mut egui::Ui, note_cache: &mut NoteCache, - img_cache: &mut MediaCache, + img_cache: &mut Images, text: &str, note: &Note<'_>| { let r = ui.add( diff --git a/crates/notedeck_columns/src/ui/profile/edit.rs b/crates/notedeck_columns/src/ui/profile/edit.rs index b6300f7e..33c57149 100644 --- a/crates/notedeck_columns/src/ui/profile/edit.rs +++ b/crates/notedeck_columns/src/ui/profile/edit.rs @@ -1,7 +1,7 @@ use core::f32; use egui::{vec2, Button, Layout, Margin, RichText, Rounding, ScrollArea, TextEdit}; -use notedeck::{MediaCache, NotedeckTextStyle}; +use notedeck::{Images, NotedeckTextStyle}; use crate::{colors, profile_state::ProfileState}; @@ -9,11 +9,11 @@ use super::{banner, unwrap_profile_url, ProfilePic}; pub struct EditProfileView<'a> { state: &'a mut ProfileState, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, } impl<'a> EditProfileView<'a> { - pub fn new(state: &'a mut ProfileState, img_cache: &'a mut MediaCache) -> Self { + pub fn new(state: &'a mut ProfileState, img_cache: &'a mut Images) -> Self { Self { state, img_cache } } diff --git a/crates/notedeck_columns/src/ui/profile/mod.rs b/crates/notedeck_columns/src/ui/profile/mod.rs index 1d429216..258ab7ff 100644 --- a/crates/notedeck_columns/src/ui/profile/mod.rs +++ b/crates/notedeck_columns/src/ui/profile/mod.rs @@ -23,7 +23,7 @@ use crate::{ NostrName, }; -use notedeck::{Accounts, MediaCache, MuteFun, NoteCache, NotedeckTextStyle, UnknownIds}; +use notedeck::{Accounts, Images, MuteFun, NoteCache, NotedeckTextStyle, UnknownIds}; pub struct ProfileView<'a> { pubkey: &'a Pubkey, @@ -33,7 +33,7 @@ pub struct ProfileView<'a> { note_options: NoteOptions, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, unknown_ids: &'a mut UnknownIds, is_muted: &'a MuteFun, } @@ -52,7 +52,7 @@ impl<'a> ProfileView<'a> { timeline_cache: &'a mut TimelineCache, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, unknown_ids: &'a mut UnknownIds, is_muted: &'a MuteFun, note_options: NoteOptions, diff --git a/crates/notedeck_columns/src/ui/profile/picture.rs b/crates/notedeck_columns/src/ui/profile/picture.rs index b6abbbaf..72c8e822 100644 --- a/crates/notedeck_columns/src/ui/profile/picture.rs +++ b/crates/notedeck_columns/src/ui/profile/picture.rs @@ -1,14 +1,14 @@ use crate::images::ImageType; -use crate::ui::images::render_media_cache; +use crate::ui::images::render_images; use crate::ui::{Preview, PreviewConfig}; use egui::{vec2, Sense, Stroke, TextureHandle}; use nostrdb::{Ndb, Transaction}; use tracing::info; -use notedeck::{AppContext, MediaCache}; +use notedeck::{AppContext, Images}; pub struct ProfilePic<'cache, 'url> { - cache: &'cache mut MediaCache, + cache: &'cache mut Images, url: &'url str, size: f32, border: Option, @@ -21,7 +21,7 @@ impl egui::Widget for ProfilePic<'_, '_> { } impl<'cache, 'url> ProfilePic<'cache, 'url> { - pub fn new(cache: &'cache mut MediaCache, url: &'url str) -> Self { + pub fn new(cache: &'cache mut Images, url: &'url str) -> Self { let size = Self::default_size(); ProfilePic { cache, @@ -36,7 +36,7 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { } pub fn from_profile( - cache: &'cache mut MediaCache, + cache: &'cache mut Images, profile: &nostrdb::ProfileRecord<'url>, ) -> Option { profile @@ -81,7 +81,7 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> { fn render_pfp( ui: &mut egui::Ui, - img_cache: &mut MediaCache, + img_cache: &mut Images, url: &str, ui_size: f32, border: Option, @@ -92,7 +92,7 @@ fn render_pfp( // We will want to downsample these so it's not blurry on hi res displays let img_size = 128u32; - render_media_cache( + render_images( ui, img_cache, url, diff --git a/crates/notedeck_columns/src/ui/profile/preview.rs b/crates/notedeck_columns/src/ui/profile/preview.rs index 8cdbc2b8..50aaca29 100644 --- a/crates/notedeck_columns/src/ui/profile/preview.rs +++ b/crates/notedeck_columns/src/ui/profile/preview.rs @@ -4,18 +4,18 @@ use egui::{Frame, Label, RichText, Widget}; use egui_extras::Size; use nostrdb::ProfileRecord; -use notedeck::{MediaCache, NotedeckTextStyle, UserAccount}; +use notedeck::{Images, NotedeckTextStyle, UserAccount}; use super::{about_section_widget, banner, display_name_widget, get_display_name, get_profile_url}; pub struct ProfilePreview<'a, 'cache> { profile: &'a ProfileRecord<'a>, - cache: &'cache mut MediaCache, + cache: &'cache mut Images, banner_height: Size, } impl<'a, 'cache> ProfilePreview<'a, 'cache> { - pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut MediaCache) -> Self { + pub fn new(profile: &'a ProfileRecord<'a>, cache: &'cache mut Images) -> Self { let banner_height = Size::exact(80.0); ProfilePreview { profile, @@ -69,14 +69,14 @@ impl egui::Widget for ProfilePreview<'_, '_> { pub struct SimpleProfilePreview<'a, 'cache> { profile: Option<&'a ProfileRecord<'a>>, - cache: &'cache mut MediaCache, + cache: &'cache mut Images, is_nsec: bool, } impl<'a, 'cache> SimpleProfilePreview<'a, 'cache> { pub fn new( profile: Option<&'a ProfileRecord<'a>>, - cache: &'cache mut MediaCache, + cache: &'cache mut Images, is_nsec: bool, ) -> Self { SimpleProfilePreview { diff --git a/crates/notedeck_columns/src/ui/search_results.rs b/crates/notedeck_columns/src/ui/search_results.rs index ef64b218..4592a1f9 100644 --- a/crates/notedeck_columns/src/ui/search_results.rs +++ b/crates/notedeck_columns/src/ui/search_results.rs @@ -1,6 +1,6 @@ use egui::{vec2, FontId, Pos2, Rect, ScrollArea, Vec2b}; use nostrdb::{Ndb, ProfileRecord, Transaction}; -use notedeck::{fonts::get_font_size, MediaCache, NotedeckTextStyle}; +use notedeck::{fonts::get_font_size, Images, NotedeckTextStyle}; use tracing::error; use crate::{ @@ -13,13 +13,13 @@ use super::{profile::get_profile_url, ProfilePic}; pub struct SearchResultsView<'a> { ndb: &'a Ndb, txn: &'a Transaction, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, results: &'a Vec<&'a [u8; 32]>, } impl<'a> SearchResultsView<'a> { pub fn new( - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, ndb: &'a Ndb, txn: &'a Transaction, results: &'a Vec<&'a [u8; 32]>, @@ -84,7 +84,7 @@ impl<'a> SearchResultsView<'a> { fn user_result<'a>( profile: &'a ProfileRecord<'_>, - cache: &'a mut MediaCache, + cache: &'a mut Images, index: usize, width: f32, ) -> impl egui::Widget + 'a { diff --git a/crates/notedeck_columns/src/ui/side_panel.rs b/crates/notedeck_columns/src/ui/side_panel.rs index 7682d344..429c6b45 100644 --- a/crates/notedeck_columns/src/ui/side_panel.rs +++ b/crates/notedeck_columns/src/ui/side_panel.rs @@ -15,7 +15,7 @@ use crate::{ support::Support, }; -use notedeck::{Accounts, MediaCache, NotedeckTextStyle, ThemeHandler, UserAccount}; +use notedeck::{Accounts, Images, NotedeckTextStyle, ThemeHandler, UserAccount}; use super::{ anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}, @@ -29,7 +29,7 @@ static ICON_WIDTH: f32 = 40.0; pub struct DesktopSidePanel<'a> { ndb: &'a nostrdb::Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, selected_account: Option<&'a UserAccount>, decks_cache: &'a DecksCache, } @@ -70,7 +70,7 @@ impl SidePanelResponse { impl<'a> DesktopSidePanel<'a> { pub fn new( ndb: &'a nostrdb::Ndb, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, selected_account: Option<&'a UserAccount>, decks_cache: &'a DecksCache, ) -> Self { diff --git a/crates/notedeck_columns/src/ui/thread.rs b/crates/notedeck_columns/src/ui/thread.rs index 16f357fe..67ae5b07 100644 --- a/crates/notedeck_columns/src/ui/thread.rs +++ b/crates/notedeck_columns/src/ui/thread.rs @@ -5,7 +5,7 @@ use crate::{ }; use nostrdb::{Ndb, Transaction}; -use notedeck::{MediaCache, MuteFun, NoteCache, RootNoteId, UnknownIds}; +use notedeck::{Images, MuteFun, NoteCache, RootNoteId, UnknownIds}; use tracing::error; use super::timeline::TimelineTabView; @@ -15,7 +15,7 @@ pub struct ThreadView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, unknown_ids: &'a mut UnknownIds, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, selected_note_id: &'a [u8; 32], note_options: NoteOptions, id_source: egui::Id, @@ -29,7 +29,7 @@ impl<'a> ThreadView<'a> { ndb: &'a Ndb, note_cache: &'a mut NoteCache, unknown_ids: &'a mut UnknownIds, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, selected_note_id: &'a [u8; 32], note_options: NoteOptions, is_muted: &'a MuteFun, diff --git a/crates/notedeck_columns/src/ui/timeline.rs b/crates/notedeck_columns/src/ui/timeline.rs index 880883d3..1ebf10b5 100644 --- a/crates/notedeck_columns/src/ui/timeline.rs +++ b/crates/notedeck_columns/src/ui/timeline.rs @@ -12,7 +12,7 @@ use egui::{vec2, Direction, Layout, Pos2, Stroke}; use egui_tabs::TabColor; use nostrdb::{Ndb, Transaction}; use notedeck::note::root_note_id_from_selected_id; -use notedeck::{MediaCache, MuteFun, NoteCache}; +use notedeck::{Images, MuteFun, NoteCache}; use tracing::{error, warn}; use super::anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}; @@ -22,19 +22,20 @@ pub struct TimelineView<'a> { timeline_cache: &'a mut TimelineCache, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, note_options: NoteOptions, reverse: bool, is_muted: &'a MuteFun, } impl<'a> TimelineView<'a> { + #[allow(clippy::too_many_arguments)] pub fn new( timeline_id: &'a TimelineKind, timeline_cache: &'a mut TimelineCache, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, note_options: NoteOptions, is_muted: &'a MuteFun, ) -> TimelineView<'a> { @@ -78,7 +79,7 @@ fn timeline_ui( timeline_id: &TimelineKind, timeline_cache: &mut TimelineCache, note_cache: &mut NoteCache, - img_cache: &mut MediaCache, + img_cache: &mut Images, reversed: bool, note_options: NoteOptions, is_muted: &MuteFun, @@ -321,7 +322,7 @@ pub struct TimelineTabView<'a> { txn: &'a Transaction, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, is_muted: &'a MuteFun, } @@ -334,7 +335,7 @@ impl<'a> TimelineTabView<'a> { txn: &'a Transaction, ndb: &'a Ndb, note_cache: &'a mut NoteCache, - img_cache: &'a mut MediaCache, + img_cache: &'a mut Images, is_muted: &'a MuteFun, ) -> Self { Self { From 9592452757ae9bf8a8c9445794d37f296faf8470 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 14:11:34 -0500 Subject: [PATCH 14/20] URL mime hosted completeness Signed-off-by: kernelkind --- crates/notedeck/src/lib.rs | 1 + crates/notedeck/src/urls.rs | 41 ++++++++++++++++++- .../notedeck_columns/src/ui/note/contents.rs | 15 ++++--- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index 741437ca..516d9f57 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -49,6 +49,7 @@ pub use theme::ColorTheme; pub use time::time_ago_since; pub use timecache::TimeCached; pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds}; +pub use urls::{supported_mime_hosted_at_url, UrlMimes}; pub use user_account::UserAccount; // export libs diff --git a/crates/notedeck/src/urls.rs b/crates/notedeck/src/urls.rs index 21dce4d3..ee7c77d5 100644 --- a/crates/notedeck/src/urls.rs +++ b/crates/notedeck/src/urls.rs @@ -9,6 +9,7 @@ use std::{ use egui::TextBuffer; use poll_promise::Promise; +use url::Url; use crate::Error; @@ -218,7 +219,6 @@ struct SupportedMimeType { } impl SupportedMimeType { - #[allow(unused)] pub fn from_extension(extension: &str) -> Result { if let Some(mime) = mime_guess::from_ext(extension) .first() @@ -239,3 +239,42 @@ impl SupportedMimeType { fn is_mime_supported(mime: &mime_guess::Mime) -> bool { mime.type_() == mime_guess::mime::IMAGE } + +fn url_has_supported_mime(url: &str) -> MimeHostedAtUrl { + if let Ok(url) = Url::parse(url) { + if let Some(path) = url.path_segments() { + if let Some(file_name) = path.last() { + if let Some(ext) = std::path::Path::new(file_name) + .extension() + .and_then(|ext| ext.to_str()) + { + if SupportedMimeType::from_extension(ext).is_ok() { + return MimeHostedAtUrl::Yes; + } else { + return MimeHostedAtUrl::No; + } + } + } + } + } + MimeHostedAtUrl::Maybe +} + +pub fn supported_mime_hosted_at_url(urls: &mut UrlMimes, url: &str) -> bool { + match url_has_supported_mime(url) { + MimeHostedAtUrl::Yes => true, + MimeHostedAtUrl::Maybe => urls + .get(url) + .and_then(|s| s.parse::().ok()) + .map_or(false, |mime: mime_guess::mime::Mime| { + is_mime_supported(&mime) + }), + MimeHostedAtUrl::No => false, + } +} + +enum MimeHostedAtUrl { + Yes, + Maybe, + No, +} diff --git a/crates/notedeck_columns/src/ui/note/contents.rs b/crates/notedeck_columns/src/ui/note/contents.rs index 26ffd5c2..243e7f77 100644 --- a/crates/notedeck_columns/src/ui/note/contents.rs +++ b/crates/notedeck_columns/src/ui/note/contents.rs @@ -8,7 +8,7 @@ use egui::{Color32, Hyperlink, Image, RichText}; use nostrdb::{BlockType, Mention, Ndb, Note, NoteKey, Transaction}; use tracing::warn; -use notedeck::{Images, NoteCache}; +use notedeck::{supported_mime_hosted_at_url, Images, NoteCache}; pub struct NoteContents<'a> { ndb: &'a Ndb, @@ -127,10 +127,6 @@ pub fn render_note_preview( .inner } -fn is_image_link(url: &str) -> bool { - url.ends_with("png") || url.ends_with("jpg") || url.ends_with("jpeg") -} - #[allow(clippy::too_many_arguments)] fn render_note_contents( ui: &mut egui::Ui, @@ -212,9 +208,12 @@ fn render_note_contents( } BlockType::Url => { - let lower_url = block.as_str().to_lowercase(); - if !hide_media && is_image_link(&lower_url) { - images.push(block.as_str().to_string()); + if !hide_media { + let url = block.as_str().to_string(); + + if supported_mime_hosted_at_url(&mut img_cache.urls, &url) { + images.push(url); + } } else { #[cfg(feature = "profiling")] puffin::profile_scope!("url contents"); From 0461a98d5d907669689beeaddc68dfe073ac7f9a Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 13:32:19 -0500 Subject: [PATCH 15/20] handle UrlCache io Signed-off-by: kernelkind --- crates/notedeck_columns/src/app.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs index e521da67..4a9df4a3 100644 --- a/crates/notedeck_columns/src/app.rs +++ b/crates/notedeck_columns/src/app.rs @@ -172,6 +172,8 @@ fn unknown_id_send(unknown_ids: &mut UnknownIds, pool: &mut RelayPool) { } fn update_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ctx: &egui::Context) { + app_ctx.img_cache.urls.cache.handle_io(); + match damus.state { DamusState::Initializing => { damus.state = DamusState::Initialized; From 3e79f9229177eaf7901a610b9a6743328737965e Mon Sep 17 00:00:00 2001 From: kernelkind Date: Tue, 25 Feb 2025 16:02:37 -0500 Subject: [PATCH 16/20] introduce gif animation Signed-off-by: kernelkind --- crates/notedeck/src/imgcache.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs index 86d789a5..3cb3a8d3 100644 --- a/crates/notedeck/src/imgcache.rs +++ b/crates/notedeck/src/imgcache.rs @@ -7,6 +7,7 @@ use egui::ColorImage; use std::collections::HashMap; use std::fs::{create_dir_all, File}; +use std::sync::mpsc::Receiver; use std::time::{Duration, Instant, SystemTime}; use hex::ToHex; @@ -20,6 +21,37 @@ pub type MediaCacheMap = HashMap; pub enum TexturedImage { Static(TextureHandle), + Animated(Animation), +} + +pub struct Animation { + pub first_frame: TextureFrame, + pub other_frames: Vec, + pub receiver: Option>, +} + +impl Animation { + 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 TextureFrame { + pub delay: Duration, + pub texture: TextureHandle, +} + +pub struct ImageFrame { + pub delay: Duration, + pub image: ColorImage, } pub struct MediaCache { @@ -146,6 +178,7 @@ impl MediaCache { pub fn get_texture(textured_image: &TexturedImage) -> &TextureHandle { match textured_image { TexturedImage::Static(texture_handle) => texture_handle, + TexturedImage::Animated(_animation) => todo!(), // Temporary... } } From d1c7a5a239eaa7f9cf56cb38c29552f261c2235d Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 15:06:25 -0500 Subject: [PATCH 17/20] handle gif state Signed-off-by: kernelkind --- crates/notedeck_columns/src/gif.rs | 122 +++++++++++++++++++++++++++++ crates/notedeck_columns/src/lib.rs | 1 + 2 files changed, 123 insertions(+) create mode 100644 crates/notedeck_columns/src/gif.rs diff --git a/crates/notedeck_columns/src/gif.rs b/crates/notedeck_columns/src/gif.rs new file mode 100644 index 00000000..714ded29 --- /dev/null +++ b/crates/notedeck_columns/src/gif.rs @@ -0,0 +1,122 @@ +use std::{ + sync::mpsc::TryRecvError, + time::{Instant, SystemTime}, +}; + +use egui::TextureHandle; +use notedeck::{GifState, GifStateMap, TexturedImage}; + +pub struct LatextTexture<'a> { + pub texture: &'a TextureHandle, + pub request_next_repaint: Option, +} + +/// This is necessary because other repaint calls can effectively steal our repaint request. +/// So we must keep on requesting to repaint at our desired time to ensure our repaint goes through. +/// See [`egui::Context::request_repaint_after`] +pub fn handle_repaint<'a>(ui: &egui::Ui, latest: LatextTexture<'a>) -> &'a TextureHandle { + if let Some(repaint) = latest.request_next_repaint { + if let Ok(dur) = repaint.duration_since(SystemTime::now()) { + ui.ctx().request_repaint_after(dur); + } + } + latest.texture +} + +#[must_use = "caller should pass the return value to `gif::handle_repaint`"] +pub fn retrieve_latest_texture<'a>( + url: &str, + gifs: &'a mut GifStateMap, + cached_image: &'a mut TexturedImage, +) -> LatextTexture<'a> { + match cached_image { + TexturedImage::Static(texture) => LatextTexture { + texture, + request_next_repaint: None, + }, + 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 now = Instant::now(); + let (texture, maybe_new_state, request_next_repaint) = match gifs.get(url) { + Some(prev_state) => { + let should_advance = + 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 = SystemTime::now().checked_add(frame.delay); + ( + &frame.texture, + Some(GifState { + last_frame_rendered: now, + last_frame_duration: frame.delay, + next_frame_time, + last_frame_index: maybe_new_index, + }), + next_frame_time, + ) + } + None => { + let (tex, state) = + match animation.get_frame(prev_state.last_frame_index) { + Some(frame) => (&frame.texture, None), + None => (&animation.first_frame.texture, None), + }; + + (tex, state, prev_state.next_frame_time) + } + } + } else { + let (tex, state) = match animation.get_frame(prev_state.last_frame_index) { + Some(frame) => (&frame.texture, None), + None => (&animation.first_frame.texture, None), + }; + (tex, state, prev_state.next_frame_time) + } + } + None => ( + &animation.first_frame.texture, + Some(GifState { + last_frame_rendered: now, + last_frame_duration: animation.first_frame.delay, + next_frame_time: None, + last_frame_index: 0, + }), + None, + ), + }; + + if let Some(new_state) = maybe_new_state { + gifs.insert(url.to_owned(), new_state); + } + + LatextTexture { + texture, + request_next_repaint, + } + } + } +} diff --git a/crates/notedeck_columns/src/lib.rs b/crates/notedeck_columns/src/lib.rs index 3ae0e640..a68e6f22 100644 --- a/crates/notedeck_columns/src/lib.rs +++ b/crates/notedeck_columns/src/lib.rs @@ -15,6 +15,7 @@ mod deck_state; mod decks; mod draft; mod frame_history; +mod gif; mod images; mod key_parsing; pub mod login_manager; From 490dedfaf1aee578bd8f24ec1015528cb1678fa3 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 15:08:13 -0500 Subject: [PATCH 18/20] integrate gifs Signed-off-by: kernelkind --- crates/notedeck/src/imgcache.rs | 71 +++--- crates/notedeck/src/lib.rs | 3 +- crates/notedeck/src/urls.rs | 36 ++- crates/notedeck_columns/src/images.rs | 235 +++++++++++++++--- crates/notedeck_columns/src/ui/images.rs | 25 +- .../notedeck_columns/src/ui/note/contents.rs | 25 +- crates/notedeck_columns/src/ui/note/post.rs | 9 +- .../src/ui/profile/picture.rs | 12 +- 8 files changed, 318 insertions(+), 98 deletions(-) diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs index 3cb3a8d3..2d579d0b 100644 --- a/crates/notedeck/src/imgcache.rs +++ b/crates/notedeck/src/imgcache.rs @@ -1,6 +1,7 @@ use crate::urls::{UrlCache, UrlMimes}; use crate::Result; use egui::TextureHandle; +use image::{Delay, Frame}; use poll_promise::Promise; use egui::ColorImage; @@ -80,31 +81,8 @@ impl MediaCache { } } - /* - pub fn fetch(image: &str) -> Result { - let m_cached_promise = img_cache.map().get(image); - if m_cached_promise.is_none() { - let res = crate::images::fetch_img( - img_cache, - ui.ctx(), - &image, - ImageType::Content(width.round() as u32, height.round() as u32), - ); - img_cache.map_mut().insert(image.to_owned(), res); - } - } - */ - pub fn write(cache_dir: &path::Path, url: &str, data: ColorImage) -> Result<()> { - let file_path = cache_dir.join(Self::key(url)); - if let Some(p) = file_path.parent() { - create_dir_all(p)?; - } - let file = File::options() - .write(true) - .create(true) - .truncate(true) - .open(file_path)?; + let file = Self::create_file(cache_dir, url)?; let encoder = image::codecs::webp::WebPEncoder::new_lossless(file); encoder.encode( @@ -117,6 +95,33 @@ impl MediaCache { Ok(()) } + fn create_file(cache_dir: &path::Path, url: &str) -> Result { + let file_path = cache_dir.join(Self::key(url)); + if let Some(p) = file_path.parent() { + create_dir_all(p)?; + } + Ok(File::options() + .write(true) + .create(true) + .truncate(true) + .open(file_path)?) + } + + pub fn write_gif(cache_dir: &path::Path, url: &str, data: Vec) -> Result<()> { + let file = Self::create_file(cache_dir, url)?; + + let mut encoder = image::codecs::gif::GifEncoder::new(file); + for img in data { + let buf = color_image_to_rgba(img.image); + let frame = Frame::from_parts(buf, 0, 0, Delay::from_saturating_duration(img.delay)); + if let Err(e) = encoder.encode_frame(frame) { + tracing::error!("problem encoding frame: {e}"); + } + } + + Ok(()) + } + pub fn key(url: &str) -> String { let k: String = sha2::Sha256::digest(url.as_bytes()).encode_hex(); PathBuf::from(&k[0..2]) @@ -174,12 +179,18 @@ impl MediaCache { } } -// TODO: temporary... -pub fn get_texture(textured_image: &TexturedImage) -> &TextureHandle { - match textured_image { - TexturedImage::Static(texture_handle) => texture_handle, - TexturedImage::Animated(_animation) => todo!(), // Temporary... - } +fn color_image_to_rgba(color_image: ColorImage) -> image::RgbaImage { + let width = color_image.width() as u32; + let height = color_image.height() as u32; + + let rgba_pixels: Vec = color_image + .pixels + .iter() + .flat_map(|color| color.to_array()) // Convert Color32 to `[u8; 4]` + .collect(); + + image::RgbaImage::from_raw(width, height, rgba_pixels) + .expect("Failed to create RgbaImage from ColorImage") } pub struct Images { diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index 516d9f57..e9f21aca 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -32,7 +32,8 @@ pub use error::{Error, FilterError}; pub use filter::{FilterState, FilterStates, UnifiedSubscription}; pub use fonts::NamedFontFamily; pub use imgcache::{ - get_texture, GifState, GifStateMap, Images, MediaCache, MediaCacheType, TexturedImage, + Animation, GifState, GifStateMap, ImageFrame, Images, MediaCache, MediaCacheType, + MediaCacheValue, TextureFrame, TexturedImage, }; pub use muted::{MuteFun, Muted}; pub use note::{NoteRef, RootIdError, RootNoteId, RootNoteIdBuf}; diff --git a/crates/notedeck/src/urls.rs b/crates/notedeck/src/urls.rs index ee7c77d5..938dd9eb 100644 --- a/crates/notedeck/src/urls.rs +++ b/crates/notedeck/src/urls.rs @@ -11,7 +11,7 @@ use egui::TextBuffer; use poll_promise::Promise; use url::Url; -use crate::Error; +use crate::{Error, MediaCacheType}; const FILE_NAME: &str = "urls.bin"; const SAVE_INTERVAL: Duration = Duration::from_secs(60); @@ -230,10 +230,26 @@ impl SupportedMimeType { } } + pub fn from_mime(mime: mime_guess::mime::Mime) -> Result { + if is_mime_supported(&mime) { + Ok(Self { mime }) + } else { + Err(Error::Generic("Unsupported mime type".to_owned())) + } + } + #[allow(unused)] pub fn to_mime(&self) -> &str { self.mime.essence_str() } + + pub fn to_cache_type(&self) -> MediaCacheType { + if self.mime == mime_guess::mime::IMAGE_GIF { + MediaCacheType::Gif + } else { + MediaCacheType::Image + } + } } fn is_mime_supported(mime: &mime_guess::Mime) -> bool { @@ -248,8 +264,8 @@ fn url_has_supported_mime(url: &str) -> MimeHostedAtUrl { .extension() .and_then(|ext| ext.to_str()) { - if SupportedMimeType::from_extension(ext).is_ok() { - return MimeHostedAtUrl::Yes; + if let Ok(supported) = SupportedMimeType::from_extension(ext) { + return MimeHostedAtUrl::Yes(supported.to_cache_type()); } else { return MimeHostedAtUrl::No; } @@ -260,21 +276,23 @@ fn url_has_supported_mime(url: &str) -> MimeHostedAtUrl { MimeHostedAtUrl::Maybe } -pub fn supported_mime_hosted_at_url(urls: &mut UrlMimes, url: &str) -> bool { +pub fn supported_mime_hosted_at_url(urls: &mut UrlMimes, url: &str) -> Option { match url_has_supported_mime(url) { - MimeHostedAtUrl::Yes => true, + MimeHostedAtUrl::Yes(cache_type) => Some(cache_type), MimeHostedAtUrl::Maybe => urls .get(url) .and_then(|s| s.parse::().ok()) - .map_or(false, |mime: mime_guess::mime::Mime| { - is_mime_supported(&mime) + .and_then(|mime: mime_guess::mime::Mime| { + SupportedMimeType::from_mime(mime) + .ok() + .map(|s| s.to_cache_type()) }), - MimeHostedAtUrl::No => false, + MimeHostedAtUrl::No => None, } } enum MimeHostedAtUrl { - Yes, + Yes(MediaCacheType), Maybe, No, } diff --git a/crates/notedeck_columns/src/images.rs b/crates/notedeck_columns/src/images.rs index 8752246c..242934d3 100644 --- a/crates/notedeck_columns/src/images.rs +++ b/crates/notedeck_columns/src/images.rs @@ -1,17 +1,28 @@ use egui::{pos2, Color32, ColorImage, Rect, Sense, SizeHint}; +use image::codecs::gif::GifDecoder; use image::imageops::FilterType; +use image::AnimationDecoder; +use image::DynamicImage; +use image::FlatSamples; +use image::Frame; +use notedeck::Animation; +use notedeck::ImageFrame; use notedeck::MediaCache; +use notedeck::MediaCacheType; use notedeck::Result; +use notedeck::TextureFrame; use notedeck::TexturedImage; use poll_promise::Promise; +use std::collections::VecDeque; +use std::io::Cursor; use std::path; use std::path::PathBuf; +use std::sync::mpsc; +use std::sync::mpsc::SyncSender; +use std::thread; +use std::time::Duration; use tokio::fs; -//pub type ImageCacheKey = String; -//pub type ImageCacheValue = Promise>; -//pub type MediaCache = HashMap; - // NOTE(jb55): chatgpt wrote this because I was too dumb to pub fn aspect_fill( ui: &mut egui::Ui, @@ -103,7 +114,7 @@ pub fn round_image(image: &mut ColorImage) { } } -fn process_pfp_bitmap(imgtyp: ImageType, image: &mut image::DynamicImage) -> ColorImage { +fn process_pfp_bitmap(imgtyp: ImageType, mut image: image::DynamicImage) -> ColorImage { #[cfg(feature = "profiling")] puffin::profile_function!(); @@ -126,10 +137,10 @@ fn process_pfp_bitmap(imgtyp: ImageType, image: &mut image::DynamicImage) -> Col if image.width() > smaller { let excess = image.width() - smaller; - *image = image.crop_imm(excess / 2, 0, image.width() - excess, image.height()); + image = image.crop_imm(excess / 2, 0, image.width() - excess, image.height()); } else if image.height() > smaller { let excess = image.height() - smaller; - *image = image.crop_imm(0, excess / 2, image.width(), image.height() - excess); + image = image.crop_imm(0, excess / 2, image.width(), image.height() - excess); } let image = image.resize(size, size, FilterType::CatmullRom); // DynamicImage let image_buffer = image.into_rgba8(); // RgbaImage (ImageBuffer) @@ -167,8 +178,8 @@ fn parse_img_response(response: ehttp::Response, imgtyp: ImageType) -> Result Promise> { let ctx = ctx.clone(); let url = url.to_owned(); let path = path.to_owned(); Promise::spawn_async(async move { - let data = fs::read(path).await?; - let image_buffer = image::load_from_memory(&data).map_err(notedeck::Error::Image)?; + match cache_type { + MediaCacheType::Image => { + let data = fs::read(path).await?; + let image_buffer = + image::load_from_memory(&data).map_err(notedeck::Error::Image)?; - // TODO: remove unwrap here - let flat_samples = image_buffer.as_flat_samples_u8().unwrap(); - let img = ColorImage::from_rgba_unmultiplied( - [ - image_buffer.width() as usize, - image_buffer.height() as usize, - ], - flat_samples.as_slice(), - ); - - Ok(TexturedImage::Static(ctx.load_texture( - &url, - img, - Default::default(), - ))) + let img = buffer_to_color_image( + image_buffer.as_flat_samples_u8(), + image_buffer.width(), + image_buffer.height(), + ); + Ok(TexturedImage::Static(ctx.load_texture( + &url, + img, + Default::default(), + ))) + } + MediaCacheType::Gif => { + let gif_bytes = fs::read(path.clone()).await?; // Read entire file into a Vec + 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, + write_to_disk: bool, + process_to_egui: impl Fn(DynamicImage) -> ColorImage + Send + Copy + 'static, +) -> Result { + 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 = decoder + .into_frames() + .collect::, image::ImageError>>() + .map_err(|e| notedeck::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::error!("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(notedeck::Error::Generic( + "first frame not found for gif".to_owned(), + )) + }, + |first_frame| { + Ok(TexturedImage::Animated(Animation { + 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>, + 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: ctx.load_texture(format!("{}{}", url, index), color_img, Default::default()), + } +} + +fn buffer_to_color_image( + samples: Option>, + width: u32, + height: u32, +) -> ColorImage { + // TODO(jb55): remove unwrap here + let flat_samples = samples.unwrap(); + ColorImage::from_rgba_unmultiplied([width as usize, height as usize], flat_samples.as_slice()) +} + pub fn fetch_binary_from_disk(path: PathBuf) -> Result> { std::fs::read(path).map_err(|e| notedeck::Error::Generic(e.to_string())) } @@ -222,14 +363,15 @@ pub fn fetch_img( ctx: &egui::Context, url: &str, imgtyp: ImageType, + cache_type: MediaCacheType, ) -> Promise> { let key = MediaCache::key(url); let path = img_cache.cache_dir.join(key); if path.exists() { - fetch_img_from_disk(ctx, url, &path) + fetch_img_from_disk(ctx, url, &path, cache_type) } else { - fetch_img_from_net(&img_cache.cache_dir, ctx, url, imgtyp) + fetch_img_from_net(&img_cache.cache_dir, ctx, url, imgtyp, cache_type) } // TODO: fetch image from local cache @@ -240,6 +382,7 @@ fn fetch_img_from_net( ctx: &egui::Context, url: &str, imgtyp: ImageType, + cache_type: MediaCacheType, ) -> Promise> { let (sender, promise) = Promise::new(); let request = ehttp::Request::get(url); @@ -247,17 +390,35 @@ fn fetch_img_from_net( let cloned_url = url.to_owned(); let cache_path = cache_path.to_owned(); ehttp::fetch(request, move |response| { - let handle = response - .map_err(notedeck::Error::Generic) - .and_then(|resp| parse_img_response(resp, imgtyp)) - .map(|img| { - let texture_handle = ctx.load_texture(&cloned_url, img.clone(), Default::default()); + let handle = response.map_err(notedeck::Error::Generic).and_then(|resp| { + match cache_type { + MediaCacheType::Image => { + let img = parse_img_response(resp, imgtyp); + img.map(|img| { + let texture_handle = + ctx.load_texture(&cloned_url, img.clone(), Default::default()); - // write to disk - std::thread::spawn(move || MediaCache::write(&cache_path, &cloned_url, img)); + // write to disk + std::thread::spawn(move || { + MediaCache::write(&cache_path, &cloned_url, img) + }); - TexturedImage::Static(texture_handle) - }); + 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_pfp_bitmap(imgtyp, img), + ) + } + } + }); sender.send(handle); // send the results back to the UI thread. ctx.request_repaint(); diff --git a/crates/notedeck_columns/src/ui/images.rs b/crates/notedeck_columns/src/ui/images.rs index 0954a8ab..e3dd5a24 100644 --- a/crates/notedeck_columns/src/ui/images.rs +++ b/crates/notedeck_columns/src/ui/images.rs @@ -1,44 +1,54 @@ -use notedeck::{Images, MediaCache, TexturedImage}; +use notedeck::{GifStateMap, Images, MediaCache, MediaCacheType, TexturedImage}; use crate::images::ImageType; use super::ProfilePic; +#[allow(clippy::too_many_arguments)] pub fn render_images( ui: &mut egui::Ui, images: &mut Images, url: &str, img_type: ImageType, + cache_type: MediaCacheType, show_waiting: impl FnOnce(&mut egui::Ui), show_error: impl FnOnce(&mut egui::Ui, String), - show_success: impl FnOnce(&mut egui::Ui, &str, &mut TexturedImage), + show_success: impl FnOnce(&mut egui::Ui, &str, &mut TexturedImage, &mut GifStateMap), ) -> egui::Response { - let cache = &mut images.static_imgs; + let cache = match cache_type { + MediaCacheType::Image => &mut images.static_imgs, + MediaCacheType::Gif => &mut images.gifs, + }; render_media_cache( ui, cache, + &mut images.gif_states, url, img_type, + cache_type, show_waiting, show_error, show_success, ) } -pub fn render_media_cache( +#[allow(clippy::too_many_arguments)] +fn render_media_cache( ui: &mut egui::Ui, cache: &mut MediaCache, + gif_states: &mut GifStateMap, url: &str, img_type: ImageType, + cache_type: MediaCacheType, show_waiting: impl FnOnce(&mut egui::Ui), show_error: impl FnOnce(&mut egui::Ui, String), - show_success: impl FnOnce(&mut egui::Ui, &str, &mut TexturedImage), + show_success: impl FnOnce(&mut egui::Ui, &str, &mut TexturedImage, &mut GifStateMap), ) -> egui::Response { let m_cached_promise = cache.map().get(url); if m_cached_promise.is_none() { - let res = crate::images::fetch_img(cache, ui.ctx(), url, img_type); + let res = crate::images::fetch_img(cache, ui.ctx(), url, img_type, cache_type.clone()); cache.map_mut().insert(url.to_owned(), res); } @@ -53,11 +63,12 @@ pub fn render_media_cache( ui.ctx(), ProfilePic::no_pfp_url(), ImageType::Profile(128), + cache_type, ); cache.map_mut().insert(url.to_owned(), no_pfp); show_error(ui, err) } - Some(Ok(renderable_media)) => show_success(ui, url, renderable_media), + Some(Ok(renderable_media)) => show_success(ui, url, renderable_media, gif_states), } }) .response diff --git a/crates/notedeck_columns/src/ui/note/contents.rs b/crates/notedeck_columns/src/ui/note/contents.rs index 243e7f77..9b60ef2a 100644 --- a/crates/notedeck_columns/src/ui/note/contents.rs +++ b/crates/notedeck_columns/src/ui/note/contents.rs @@ -1,3 +1,4 @@ +use crate::gif::{handle_repaint, retrieve_latest_texture}; use crate::ui::images::render_images; use crate::ui::{ self, @@ -8,7 +9,7 @@ use egui::{Color32, Hyperlink, Image, RichText}; use nostrdb::{BlockType, Mention, Ndb, Note, NoteKey, Transaction}; use tracing::warn; -use notedeck::{supported_mime_hosted_at_url, Images, NoteCache}; +use notedeck::{supported_mime_hosted_at_url, Images, MediaCacheType, NoteCache}; pub struct NoteContents<'a> { ndb: &'a Ndb, @@ -142,7 +143,7 @@ fn render_note_contents( puffin::profile_function!(); let selectable = options.has_selectable_text(); - let mut images: Vec = vec![]; + let mut images: Vec<(String, MediaCacheType)> = vec![]; let mut note_action: Option = None; let mut inline_note: Option<(&[u8; 32], &str)> = None; let hide_media = options.has_hide_media(); @@ -211,8 +212,10 @@ fn render_note_contents( if !hide_media { let url = block.as_str().to_string(); - if supported_mime_hosted_at_url(&mut img_cache.urls, &url) { - images.push(url); + if let Some(cache_type) = + supported_mime_hosted_at_url(&mut img_cache.urls, &url) + { + images.push((url, cache_type)); } } else { #[cfg(feature = "profiling")] @@ -280,7 +283,7 @@ fn rot13(input: &str) -> String { fn image_carousel( ui: &mut egui::Ui, img_cache: &mut Images, - images: Vec, + images: Vec<(String, MediaCacheType)>, carousel_id: egui::Id, ) { // let's make sure everything is within our area @@ -294,25 +297,31 @@ fn image_carousel( .id_salt(carousel_id) .show(ui, |ui| { ui.horizontal(|ui| { - for image in images { + for (image, cache_type) in images { render_images( ui, img_cache, &image, ImageType::Content(width.round() as u32, height.round() as u32), + cache_type, |ui| { ui.allocate_space(egui::vec2(spinsz, spinsz)); }, |ui, _| { ui.allocate_space(egui::vec2(spinsz, spinsz)); }, - |ui, url, renderable_media| { + |ui, url, renderable_media, gifs| { + let texture = handle_repaint( + ui, + retrieve_latest_texture(&image, gifs, renderable_media), + ); let img_resp = ui.add( - Image::new(notedeck::get_texture(renderable_media)) + Image::new(texture) .max_height(height) .rounding(5.0) .fit_to_original_size(1.0), ); + img_resp.context_menu(|ui| { if ui.button("Copy Link").clicked() { ui.ctx().copy_text(url.to_owned()); diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs index 88303ffb..1374f1f0 100644 --- a/crates/notedeck_columns/src/ui/note/post.rs +++ b/crates/notedeck_columns/src/ui/note/post.rs @@ -1,4 +1,5 @@ use crate::draft::{Draft, Drafts, MentionHint}; +use crate::gif::{handle_repaint, retrieve_latest_texture}; use crate::media_upload::{nostrbuild_nip96_upload, MediaPath}; use crate::post::{downcast_post_buffer, MentionType, NewPost}; use crate::profile::get_display_name; @@ -13,7 +14,7 @@ use egui::{vec2, Frame, Layout, Margin, Pos2, ScrollArea, Sense, TextBuffer}; use enostr::{FilledKeypair, FullKeypair, NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; -use notedeck::{get_texture, Images, NoteCache}; +use notedeck::{Images, NoteCache}; use tracing::error; use super::contents::render_note_preview; @@ -390,6 +391,7 @@ impl<'a> PostView<'a> { self.img_cache, &media.url, crate::images::ImageType::Content(width, height), + notedeck::MediaCacheType::Image, // TODO(kernelkind): support gifs in PostView |ui| { ui.spinner(); }, @@ -397,7 +399,7 @@ impl<'a> PostView<'a> { self.draft.upload_errors.push(e.to_string()); error!("{e}"); }, - |ui, _, renderable_media| { + |ui, url, renderable_media, gifs| { let media_size = vec2(width as f32, height as f32); let max_size = vec2(300.0, 300.0); let size = if media_size.x > max_size.x || media_size.y > max_size.y { @@ -406,7 +408,8 @@ impl<'a> PostView<'a> { media_size }; - let texture_handle = get_texture(renderable_media); + let texture_handle = + handle_repaint(ui, retrieve_latest_texture(url, gifs, renderable_media)); let img_resp = ui.add( egui::Image::new(texture_handle) .max_size(size) diff --git a/crates/notedeck_columns/src/ui/profile/picture.rs b/crates/notedeck_columns/src/ui/profile/picture.rs index 72c8e822..8309e55e 100644 --- a/crates/notedeck_columns/src/ui/profile/picture.rs +++ b/crates/notedeck_columns/src/ui/profile/picture.rs @@ -1,3 +1,4 @@ +use crate::gif::{handle_repaint, retrieve_latest_texture}; use crate::images::ImageType; use crate::ui::images::render_images; use crate::ui::{Preview, PreviewConfig}; @@ -5,7 +6,7 @@ use egui::{vec2, Sense, Stroke, TextureHandle}; use nostrdb::{Ndb, Transaction}; use tracing::info; -use notedeck::{AppContext, Images}; +use notedeck::{supported_mime_hosted_at_url, AppContext, Images}; pub struct ProfilePic<'cache, 'url> { cache: &'cache mut Images, @@ -92,19 +93,24 @@ fn render_pfp( // We will want to downsample these so it's not blurry on hi res displays let img_size = 128u32; + let cache_type = supported_mime_hosted_at_url(&mut img_cache.urls, url) + .unwrap_or(notedeck::MediaCacheType::Image); + render_images( ui, img_cache, url, ImageType::Profile(img_size), + cache_type, |ui| { paint_circle(ui, ui_size, border); }, |ui, _| { paint_circle(ui, ui_size, border); }, - |ui, _, renderable_media| { - let texture_handle = notedeck::get_texture(renderable_media); + |ui, url, renderable_media, gifs| { + let texture_handle = + handle_repaint(ui, retrieve_latest_texture(url, gifs, renderable_media)); pfp_image(ui, texture_handle, ui_size, border); }, ) From e5fc461a79dc94c26590ecd75a2d307428b16dc3 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 18:09:52 -0500 Subject: [PATCH 19/20] use SupportedMimeType for media_upload Signed-off-by: kernelkind --- crates/notedeck/src/lib.rs | 2 +- crates/notedeck/src/urls.rs | 4 +- crates/notedeck_columns/src/media_upload.rs | 46 ++------------------- 3 files changed, 6 insertions(+), 46 deletions(-) diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs index e9f21aca..aa13ff86 100644 --- a/crates/notedeck/src/lib.rs +++ b/crates/notedeck/src/lib.rs @@ -50,7 +50,7 @@ pub use theme::ColorTheme; pub use time::time_ago_since; pub use timecache::TimeCached; pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds}; -pub use urls::{supported_mime_hosted_at_url, UrlMimes}; +pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes}; pub use user_account::UserAccount; // export libs diff --git a/crates/notedeck/src/urls.rs b/crates/notedeck/src/urls.rs index 938dd9eb..ce323222 100644 --- a/crates/notedeck/src/urls.rs +++ b/crates/notedeck/src/urls.rs @@ -214,7 +214,8 @@ impl UrlMimes { } } -struct SupportedMimeType { +#[derive(Debug)] +pub struct SupportedMimeType { mime: mime_guess::Mime, } @@ -238,7 +239,6 @@ impl SupportedMimeType { } } - #[allow(unused)] pub fn to_mime(&self) -> &str { self.mime.essence_str() } diff --git a/crates/notedeck_columns/src/media_upload.rs b/crates/notedeck_columns/src/media_upload.rs index fb6fb411..21acd091 100644 --- a/crates/notedeck_columns/src/media_upload.rs +++ b/crates/notedeck_columns/src/media_upload.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use base64::{prelude::BASE64_URL_SAFE, Engine}; use ehttp::Request; use nostrdb::{Note, NoteBuilder}; +use notedeck::SupportedMimeType; use poll_promise::Promise; use sha2::{Digest, Sha256}; use url::Url; @@ -232,13 +233,13 @@ fn find_nip94_ev_in_json(json: String) -> Result { pub struct MediaPath { full_path: PathBuf, file_name: String, - media_type: SupportedMediaType, + media_type: SupportedMimeType, } impl MediaPath { pub fn new(path: PathBuf) -> Result { if let Some(ex) = path.extension().and_then(|f| f.to_str()) { - let media_type = SupportedMediaType::from_extension(ex)?; + let media_type = SupportedMimeType::from_extension(ex)?; let file_name = path .file_name() .and_then(|name| name.to_str()) @@ -259,47 +260,6 @@ impl MediaPath { } } -#[derive(Debug)] -pub enum SupportedMediaType { - Png, - Jpeg, - Webp, -} - -impl SupportedMediaType { - pub fn mime_extension(&self) -> &str { - match &self { - SupportedMediaType::Png => "png", - SupportedMediaType::Jpeg => "jpeg", - SupportedMediaType::Webp => "webp", - } - } - - pub fn to_mime(&self) -> String { - format!("{}/{}", self.mime_type(), self.mime_extension()) - } - - fn mime_type(&self) -> String { - match &self { - SupportedMediaType::Png | SupportedMediaType::Jpeg | SupportedMediaType::Webp => { - "image" - } - } - .to_string() - } - - fn from_extension(ext: &str) -> Result { - match ext.to_lowercase().as_str() { - "jpeg" | "jpg" => Ok(SupportedMediaType::Jpeg), - "png" => Ok(SupportedMediaType::Png), - "webp" => Ok(SupportedMediaType::Webp), - unsupported_type => Err(Error::Generic(format!( - "{unsupported_type} is not a valid file type to upload." - ))), - } - } -} - #[derive(Clone, Debug, serde::Deserialize)] pub struct Nip94Event { pub url: String, From 9d88ba141539618c22a304430c6f0b6d9d99bbed Mon Sep 17 00:00:00 2001 From: kernelkind Date: Thu, 20 Feb 2025 16:48:54 -0500 Subject: [PATCH 20/20] render gif in PostView Signed-off-by: kernelkind --- crates/notedeck_columns/src/ui/note/post.rs | 93 ++++++++++++--------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs index 1374f1f0..c64dfff7 100644 --- a/crates/notedeck_columns/src/ui/note/post.rs +++ b/crates/notedeck_columns/src/ui/note/post.rs @@ -14,7 +14,7 @@ use egui::{vec2, Frame, Layout, Margin, Pos2, ScrollArea, Sense, TextBuffer}; use enostr::{FilledKeypair, FullKeypair, NoteId, Pubkey, RelayPool}; use nostrdb::{Ndb, Transaction}; -use notedeck::{Images, NoteCache}; +use notedeck::{supported_mime_hosted_at_url, Images, NoteCache}; use tracing::error; use super::contents::render_note_preview; @@ -386,48 +386,59 @@ impl<'a> PostView<'a> { (300, 300) }; - render_images( - ui, - self.img_cache, - &media.url, - crate::images::ImageType::Content(width, height), - notedeck::MediaCacheType::Image, // TODO(kernelkind): support gifs in PostView - |ui| { - ui.spinner(); - }, - |_, e| { - self.draft.upload_errors.push(e.to_string()); - error!("{e}"); - }, - |ui, url, renderable_media, gifs| { - let media_size = vec2(width as f32, height as f32); - let max_size = vec2(300.0, 300.0); - let size = if media_size.x > max_size.x || media_size.y > max_size.y { - max_size - } else { - media_size - }; + if let Some(cache_type) = + supported_mime_hosted_at_url(&mut self.img_cache.urls, &media.url) + { + render_images( + ui, + self.img_cache, + &media.url, + crate::images::ImageType::Content(width, height), + cache_type, + |ui| { + ui.spinner(); + }, + |_, e| { + self.draft.upload_errors.push(e.to_string()); + error!("{e}"); + }, + |ui, url, renderable_media, gifs| { + let media_size = vec2(width as f32, height as f32); + let max_size = vec2(300.0, 300.0); + let size = if media_size.x > max_size.x || media_size.y > max_size.y { + max_size + } else { + media_size + }; - let texture_handle = - handle_repaint(ui, retrieve_latest_texture(url, gifs, renderable_media)); - let img_resp = ui.add( - egui::Image::new(texture_handle) - .max_size(size) - .rounding(12.0), - ); + let texture_handle = handle_repaint( + ui, + retrieve_latest_texture(url, gifs, renderable_media), + ); + let img_resp = ui.add( + egui::Image::new(texture_handle) + .max_size(size) + .rounding(12.0), + ); - let remove_button_rect = { - let top_left = img_resp.rect.left_top(); - let spacing = 13.0; - let center = Pos2::new(top_left.x + spacing, top_left.y + spacing); - egui::Rect::from_center_size(center, egui::vec2(26.0, 26.0)) - }; - if show_remove_upload_button(ui, remove_button_rect).clicked() { - to_remove.push(i); - } - ui.advance_cursor_after_rect(img_resp.rect); - }, - ); + let remove_button_rect = { + let top_left = img_resp.rect.left_top(); + let spacing = 13.0; + let center = Pos2::new(top_left.x + spacing, top_left.y + spacing); + egui::Rect::from_center_size(center, egui::vec2(26.0, 26.0)) + }; + if show_remove_upload_button(ui, remove_button_rect).clicked() { + to_remove.push(i); + } + ui.advance_cursor_after_rect(img_resp.rect); + }, + ); + } else { + self.draft + .upload_errors + .push("Uploaded media is not supported.".to_owned()); + error!("Unsupported mime type at url: {}", &media.url); + } } to_remove.reverse(); for i in to_remove {