From 66f5209f64212a0832ee4d2de2c4560ad3b78fb1 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sat, 2 Nov 2024 08:58:31 +1300 Subject: [PATCH 01/18] Make code a bit more direct --- gossip-lib/src/media.rs | 49 +++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/gossip-lib/src/media.rs b/gossip-lib/src/media.rs index dd94368b..063adfd5 100644 --- a/gossip-lib/src/media.rs +++ b/gossip-lib/src/media.rs @@ -34,7 +34,6 @@ pub struct Media { // and hand them over. This way we can do the work that takes // longer and the UI can do as little work as possible. image_temp: DashMap, - data_temp: DashMap>, media_pending_processing: DashSet, failed_media: DashMap, } @@ -49,7 +48,6 @@ impl Media { pub(crate) fn new() -> Media { Media { image_temp: DashMap::new(), - data_temp: DashMap::new(), media_pending_processing: DashSet::new(), failed_media: DashMap::new(), } @@ -164,30 +162,6 @@ impl Media { return MediaLoadingResult::Failed(s.to_string()); } - // If we have it, hand it over (we won't need a copy anymore) - if let Some(th) = self.data_temp.remove(url) { - // Verify metadata hash - if let Some(file_metadata) = &file_metadata { - if let Some(x) = &file_metadata.x { - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - hasher.update(&th.1); - let sha256hash = hasher.finalize(); - let hash_str = hex::encode(sha256hash); - if hash_str != *x { - if url.as_str() == "https://mikedilger.com/bs.png" { - tracing::error!("Hash Mismatch Computed"); - } - let error = "Hash Mismatch".to_string(); - self.set_has_failed(&url.to_unchecked_url(), error.clone()); - return MediaLoadingResult::Failed(error); - } - } - } - - return MediaLoadingResult::Ready(th.1); - } - // Do not fetch if disabled if !GLOBALS.db().read_setting_load_media() { return MediaLoadingResult::Disabled; @@ -200,9 +174,26 @@ impl Media { ) { Ok(None) => MediaLoadingResult::Loading, Ok(Some(bytes)) => { - // FIXME: Why not just return it right here? - self.data_temp.insert(url.clone(), bytes); - MediaLoadingResult::Loading + // Verify metadata hash + if let Some(file_metadata) = &file_metadata { + if let Some(x) = &file_metadata.x { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(&bytes); + let sha256hash = hasher.finalize(); + let hash_str = hex::encode(sha256hash); + if hash_str != *x { + if url.as_str() == "https://mikedilger.com/bs.png" { + tracing::error!("Hash Mismatch Computed"); + } + let error = "Hash Mismatch".to_string(); + self.set_has_failed(&url.to_unchecked_url(), error.clone()); + return MediaLoadingResult::Failed(error); + } + } + } + + MediaLoadingResult::Ready(bytes) } Err(e) => { let error = format!("{e}"); From d95f90aa6e27116409daea508af6c3ade3774782 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sat, 2 Nov 2024 08:48:49 +1300 Subject: [PATCH 02/18] depend on blurhash --- Cargo.lock | 10 ++++++++++ gossip-bin/Cargo.toml | 1 + 2 files changed, 11 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7fd4c5e0..3b2bddd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,6 +817,15 @@ dependencies = [ "piper", ] +[[package]] +name = "blurhash" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc" +dependencies = [ + "image", +] + [[package]] name = "brotli" version = "7.0.0" @@ -2262,6 +2271,7 @@ name = "gossip" version = "0.13.0-unstable" dependencies = [ "bech32 0.11.0", + "blurhash", "chrono", "eframe", "egui-video", diff --git a/gossip-bin/Cargo.toml b/gossip-bin/Cargo.toml index 34bb2ea6..e5bebb03 100644 --- a/gossip-bin/Cargo.toml +++ b/gossip-bin/Cargo.toml @@ -20,6 +20,7 @@ appimage = [ "gossip-lib/appimage" ] [dependencies] bech32 = "0.11" +blurhash = { version = "0.2", features = [ "image" ] } eframe = { git = "https://github.com/bu5hm4nn/egui", rev = "f40370c48ae2e07d2bc1d7ec33d094d29dc34e70", features = [ "persistence", "wayland", "wgpu" ] } egui-winit = { git = "https://github.com/bu5hm4nn/egui", rev = "f40370c48ae2e07d2bc1d7ec33d094d29dc34e70", features = [ "default" ] } egui_extras = { git = "https://github.com/bu5hm4nn/egui", rev = "f40370c48ae2e07d2bc1d7ec33d094d29dc34e70", features = [ "syntect" ] } From e35a9187f87afd8e5b319a6d9302eec26eeb0dd6 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sat, 2 Nov 2024 10:46:21 +1300 Subject: [PATCH 03/18] Show blurhash while loading images --- gossip-bin/src/ui/mod.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/gossip-bin/src/ui/mod.rs b/gossip-bin/src/ui/mod.rs index 9dbc130c..9129e989 100644 --- a/gossip-bin/src/ui/mod.rs +++ b/gossip-bin/src/ui/mod.rs @@ -481,6 +481,7 @@ struct GossipUi { theme: Theme, avatars: HashMap, images: HashMap, + blurs: HashMap, /// used when settings.show_media=false to explicitly show media_show_list: HashSet, /// used when settings.show_media=true to explicitly hide @@ -738,6 +739,7 @@ impl GossipUi { theme, avatars: HashMap::new(), images: HashMap::new(), + blurs: HashMap::new(), media_show_list: HashSet::new(), media_hide_list: HashSet::new(), media_full_width_list: HashSet::new(), @@ -1619,7 +1621,39 @@ impl GossipUi { match GLOBALS.media.get_image(&url, volatile, file_metadata) { MediaLoadingResult::Disabled => MediaLoadingResult::Disabled, - MediaLoadingResult::Loading => MediaLoadingResult::Loading, + MediaLoadingResult::Loading => { + if let Some(fm) = file_metadata { + let (w, h) = match fm.dim { + Some((w, h)) => (w, h), + None => (400, 400), + }; + if let Some(bh) = &fm.blurhash { + if let Some(texture_handle) = self.blurs.get(&url) { + return MediaLoadingResult::Ready(texture_handle.clone()); + } else { + if let Ok(rgba_image) = + blurhash::decode_image(bh, w as u32, h as u32, 1.0) + { + let current_size = + [rgba_image.width() as usize, rgba_image.height() as usize]; + let pixels = rgba_image.as_flat_samples(); + let color_image = ColorImage::from_rgba_unmultiplied( + current_size, + pixels.as_slice(), + ); + let texture_handle = ctx.load_texture( + url.as_str().to_owned(), + color_image, + TextureOptions::default(), + ); + self.blurs.insert(url, texture_handle.clone()); + return MediaLoadingResult::Ready(texture_handle); + } + } + } + } + MediaLoadingResult::Loading + } MediaLoadingResult::Ready(rgba_image) => { let current_size = [rgba_image.width() as usize, rgba_image.height() as usize]; let pixels = rgba_image.as_flat_samples(); From 562621a2a6546e91f39e33f9090ac8246a6e990e Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Tue, 5 Nov 2024 11:30:31 +1300 Subject: [PATCH 04/18] update nostr-types ('E' and 'A' tags, kind 1111, EventKind.is_textual()) --- Cargo.lock | 2 +- gossip-bin/Cargo.toml | 2 +- gossip-lib/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b2bddd7..41687a4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3449,7 +3449,7 @@ dependencies = [ [[package]] name = "nostr-types" version = "0.8.0-unstable" -source = "git+https://github.com/mikedilger/nostr-types?rev=071af42b008a8ba0eff3683fec6dda0812819e4f#071af42b008a8ba0eff3683fec6dda0812819e4f" +source = "git+https://github.com/mikedilger/nostr-types?rev=6412151b603ab913572aa21ad8e0ed51f1519c69#6412151b603ab913572aa21ad8e0ed51f1519c69" dependencies = [ "aes", "base64 0.22.1", diff --git a/gossip-bin/Cargo.toml b/gossip-bin/Cargo.toml index e5bebb03..d0edabc6 100644 --- a/gossip-bin/Cargo.toml +++ b/gossip-bin/Cargo.toml @@ -31,7 +31,7 @@ humansize = "2.1" image = { version = "0.25", features = [ "png", "jpeg" ] } lazy_static = "1.5" memoize = "0.4" -nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "071af42b008a8ba0eff3683fec6dda0812819e4f", features = [ "speedy" ] } +nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "6412151b603ab913572aa21ad8e0ed51f1519c69", features = [ "speedy" ] } paste = "1.0" qrcode = "0.14" resvg = "0.35.0" diff --git a/gossip-lib/Cargo.toml b/gossip-lib/Cargo.toml index d4029d7c..09796189 100644 --- a/gossip-lib/Cargo.toml +++ b/gossip-lib/Cargo.toml @@ -55,7 +55,7 @@ kamadak-exif = "0.5" lazy_static = "1.5" linkify = "0.10" mime = "0.3" -nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "071af42b008a8ba0eff3683fec6dda0812819e4f", features = [ "speedy" ] } +nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "6412151b603ab913572aa21ad8e0ed51f1519c69", features = [ "speedy" ] } parking_lot = { version = "0.12", features = [ "arc_lock", "send_guard" ] } paste = "1.0" rand = "0.8" From e972de375d97955df8e042fc0f1bc568bd8766e4 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Tue, 5 Nov 2024 11:35:33 +1300 Subject: [PATCH 05/18] Add EventKind::Comment as an enabled event kind --- gossip-lib/src/feed/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/gossip-lib/src/feed/mod.rs b/gossip-lib/src/feed/mod.rs index 259c89c8..e391038b 100644 --- a/gossip-lib/src/feed/mod.rs +++ b/gossip-lib/src/feed/mod.rs @@ -550,6 +550,7 @@ pub fn enabled_event_kinds() -> Vec { .filter(|k| { *k == EventKind::Metadata || *k == EventKind::TextNote + || *k == EventKind::Comment //|| *k == EventKind::RecommendRelay || *k == EventKind::ContactList || ((*k == EventKind::EncryptedDirectMessage) && direct_messages) From f58bb7c461e4c8202c3a0d2a1ad3ba860e2b8ab6 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Thu, 7 Nov 2024 13:27:40 +1300 Subject: [PATCH 06/18] depend on blurhash --- Cargo.lock | 1 + gossip-lib/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 41687a4f..d1874f67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2306,6 +2306,7 @@ version = "0.13.0-unstable" dependencies = [ "base64 0.22.1", "bech32 0.11.0", + "blurhash", "dashmap", "dirs", "encoding_rs", diff --git a/gossip-lib/Cargo.toml b/gossip-lib/Cargo.toml index 09796189..67bd6173 100644 --- a/gossip-lib/Cargo.toml +++ b/gossip-lib/Cargo.toml @@ -41,6 +41,7 @@ appimage = [] [dependencies] base64 = "0.22" bech32 = "0.11" +blurhash = { version = "0.2", features = [ "image" ] } dashmap = "6.0" dirs = "5.0" encoding_rs = "0.8" From 2a91426680fb9675b1c1f452142ca5b550a0b86d Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Thu, 7 Nov 2024 13:29:26 +1300 Subject: [PATCH 07/18] gossip_lib::media::media_url_mimetype() --- gossip-bin/src/ui/feed/note/content/mod.rs | 27 +++++--------------- gossip-lib/src/lib.rs | 2 +- gossip-lib/src/media.rs | 29 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/gossip-bin/src/ui/feed/note/content/mod.rs b/gossip-bin/src/ui/feed/note/content/mod.rs index a0848b20..f06b7dcd 100644 --- a/gossip-bin/src/ui/feed/note/content/mod.rs +++ b/gossip-bin/src/ui/feed/note/content/mod.rs @@ -231,10 +231,12 @@ pub(super) fn render_hyperlink( let privacy_issue = note.direct_message; if let (Ok(url), Some(nurl)) = (url::Url::try_from(link), app.try_check_url(link)) { - if is_image_url(&url) { - media::show_image(app, ui, nurl, privacy_issue, note.volatile, file_metadata); - } else if is_video_url(&url) { - media::show_video(app, ui, nurl, privacy_issue, note.volatile, file_metadata); + if let Some(mimetype) = gossip_lib::media_url_mimetype(url.path()) { + if mimetype.starts_with("image/") { + media::show_image(app, ui, nurl, privacy_issue, note.volatile, file_metadata); + } else if mimetype.starts_with("video/") { + media::show_video(app, ui, nurl, privacy_issue, note.volatile, file_metadata); + } } else { crate::ui::widgets::break_anywhere_hyperlink_to(ui, link, link); } @@ -376,20 +378,3 @@ pub(super) fn render_unknown_reference(ui: &mut Ui, num: usize) { .write("Gossip can't handle this kind of tag link yet.".to_owned()); } } - -fn is_image_url(url: &url::Url) -> bool { - let lower = url.path().to_lowercase(); - lower.ends_with(".jpg") - || lower.ends_with(".jpeg") - || lower.ends_with(".png") - || lower.ends_with(".gif") - || lower.ends_with(".webp") -} - -fn is_video_url(url: &url::Url) -> bool { - let lower = url.path().to_lowercase(); - lower.ends_with(".mov") - || lower.ends_with(".mp4") - || lower.ends_with(".mkv") - || lower.ends_with(".webm") -} diff --git a/gossip-lib/src/lib.rs b/gossip-lib/src/lib.rs index 48e6b783..da413012 100644 --- a/gossip-lib/src/lib.rs +++ b/gossip-lib/src/lib.rs @@ -102,7 +102,7 @@ pub use gossip_identity::GossipIdentity; pub mod manager; mod media; -pub use media::{Media, MediaLoadingResult}; +pub use media::{media_url_mimetype, Media, MediaLoadingResult}; mod minion; diff --git a/gossip-lib/src/media.rs b/gossip-lib/src/media.rs index 063adfd5..77ed2bb7 100644 --- a/gossip-lib/src/media.rs +++ b/gossip-lib/src/media.rs @@ -355,3 +355,32 @@ fn round_image(image: &mut RgbaImage) { } } } + +pub fn media_url_mimetype(s: &str) -> Option<&'static str> { + let lower = s.to_lowercase(); + if lower.ends_with(".jpg") || lower.ends_with(".jpeg") { + Some("image/jpeg") + } else if lower.ends_with(".png") { + Some("image/png") + } else if lower.ends_with(".gif") { + Some("image/gif") + } else if lower.ends_with(".webp") { + Some("image/webp") + } else if lower.ends_with(".mov") { + Some("video/quicktime") + } else if lower.ends_with(".mp4") { + Some("video/mp4") + } else if lower.ends_with(".webm") { + Some("video/webm") + } else if lower.ends_with(".mkv") { + Some("video/x-matroska") + } else if lower.ends_with(".avi") { + Some("video/x-msvideo") + } else if lower.ends_with(".wmv") { + Some("video/x-ms-wmv") + } else if lower.ends_with(".3gp") { + Some("video/3gpp") + } else { + None + } +} From 3e8b9bdeb87878e166f6027b80584dd397cabab1 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Thu, 7 Nov 2024 13:30:30 +1300 Subject: [PATCH 08/18] Remove comment that no longer applies --- gossip-bin/src/ui/feed/note/content/media.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/gossip-bin/src/ui/feed/note/content/media.rs b/gossip-bin/src/ui/feed/note/content/media.rs index fe1f915c..d7fe6c86 100644 --- a/gossip-bin/src/ui/feed/note/content/media.rs +++ b/gossip-bin/src/ui/feed/note/content/media.rs @@ -181,7 +181,6 @@ fn try_render_image( .rounding(ui.style().noninteractive().rounding) .show(ui, |ui| { let text = if let Some(fm) = &file_metadata { - // FIXME do blurhash if let Some(alt) = &fm.alt { &format!("Loading image: {alt}") } else if let Some(summary) = &fm.summary { From a433b49cec80e43b2b22d542b3df149383ebc2ea Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Thu, 7 Nov 2024 13:34:46 +1300 Subject: [PATCH 09/18] add_tags_mirroring_content: switch to using ShatteredContent instead of regex finding of just nostr urls --- gossip-lib/src/post.rs | 88 +++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/gossip-lib/src/post.rs b/gossip-lib/src/post.rs index 8da355f4..6fd28f8c 100644 --- a/gossip-lib/src/post.rs +++ b/gossip-lib/src/post.rs @@ -4,8 +4,8 @@ use crate::globals::GLOBALS; use crate::relay; use crate::relay::Relay; use nostr_types::{ - ContentEncryptionAlgorithm, Event, EventKind, EventReference, Id, NAddr, NostrBech32, PreEvent, - PublicKey, RelayUrl, Tag, UncheckedUrl, Unixtime, + ContentEncryptionAlgorithm, ContentSegment, Event, EventKind, EventReference, Id, NAddr, + NostrBech32, PreEvent, PublicKey, RelayUrl, ShatteredContent, Tag, UncheckedUrl, Unixtime, }; use std::sync::mpsc; @@ -167,47 +167,55 @@ fn add_gossip_tag(tags: &mut Vec) { } fn add_tags_mirroring_content(content: &str, tags: &mut Vec, direct_message: bool) { - // Add Tags based on references in the content - // - // FIXME - this function takes a 'tags' variable. We may want to let - // the user determine which tags to keep and which to delete, so we - // should probably move this processing into the post editor instead. - // For now, I'm just trying to remove the old #[0] type substitutions - // and use the new NostrBech32 parsing. - for bech32 in NostrBech32::find_all_in_string(content).iter() { - match bech32 { - NostrBech32::CryptSec(_) => { - // add nothing - } - NostrBech32::NAddr(ea) => { - nostr_types::add_addr_to_tags(tags, ea, Some("mention".to_string())); - } - NostrBech32::NEvent(ne) => { - // NIP-10: "Those marked with "mention" denote a quoted or reposted event id." - add_event_to_tags( - tags, - ne.id, - ne.relays.first().cloned(), - ne.author, - "mention", - ); - } - NostrBech32::Id(id) => { - // NIP-10: "Those marked with "mention" denote a quoted or reposted event id." - add_event_to_tags(tags, *id, None, None, "mention"); - } - NostrBech32::Profile(prof) => { - if !direct_message { - nostr_types::add_pubkey_to_tags(tags, prof.pubkey, None); + let shattered_content = ShatteredContent::new(content.to_owned()); + for segment in shattered_content.segments.iter() { + match segment { + ContentSegment::NostrUrl(nurl) => { + match &nurl.0 { + NostrBech32::CryptSec(_) => { + // add nothing + } + NostrBech32::NAddr(ea) => { + nostr_types::add_addr_to_tags(tags, ea, Some("mention".to_string())); + } + NostrBech32::NEvent(ne) => { + // NIP-10: "Those marked with "mention" denote a quoted or reposted event id." + add_event_to_tags( + tags, + ne.id, + ne.relays.first().cloned(), + ne.author, + "mention", + ); + } + NostrBech32::Id(id) => { + // NIP-10: "Those marked with "mention" denote a quoted or reposted event id." + add_event_to_tags(tags, *id, None, None, "mention"); + } + NostrBech32::Profile(prof) => { + if !direct_message { + nostr_types::add_pubkey_to_tags(tags, prof.pubkey, None); + } + } + NostrBech32::Pubkey(pk) => { + if !direct_message { + nostr_types::add_pubkey_to_tags(tags, *pk, None); + } + } + NostrBech32::Relay(_) => { + // we don't need to add this to tags I don't think. + } } } - NostrBech32::Pubkey(pk) => { - if !direct_message { - nostr_types::add_pubkey_to_tags(tags, *pk, None); - } + ContentSegment::TagReference(_index) => { + // do nothing + } + ContentSegment::Hyperlink(_span) => { + // do nothing + // FIXME: do something! } - NostrBech32::Relay(_) => { - // we don't need to add this to tags I don't think. + ContentSegment::Plain(_span) => { + // do nothing } } } From b2509486e80830a312a2f0b56ec71af4752ee59b Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Wed, 6 Nov 2024 08:31:30 +1300 Subject: [PATCH 10/18] post: Produce imeta tags for media URLs --- gossip-lib/src/fetcher.rs | 4 +- gossip-lib/src/overlord.rs | 11 +++-- gossip-lib/src/post.rs | 91 +++++++++++++++++++++++++++++++++----- 3 files changed, 91 insertions(+), 15 deletions(-) diff --git a/gossip-lib/src/fetcher.rs b/gossip-lib/src/fetcher.rs index 1eb165eb..37f41788 100644 --- a/gossip-lib/src/fetcher.rs +++ b/gossip-lib/src/fetcher.rs @@ -266,7 +266,9 @@ impl Fetcher { Ok(None) } - async fn fetch(&self, url: Url, use_temp_cache: bool) { + /// This causes the fetcher to fetch the resource. After it completes, you can pick it up + /// the result using try_get() + pub async fn fetch(&self, url: Url, use_temp_cache: bool) { // Do not fetch if offline if GLOBALS.db().read_setting_offline() { tracing::debug!("FETCH {url}: Failed: offline mode"); diff --git a/gossip-lib/src/overlord.rs b/gossip-lib/src/overlord.rs index 58c5cd1f..92dbedaf 100644 --- a/gossip-lib/src/overlord.rs +++ b/gossip-lib/src/overlord.rs @@ -700,7 +700,8 @@ impl Overlord { annotation, dm_channel, } => { - self.post(content, tags, in_reply_to, annotation, dm_channel)?; + self.post(content, tags, in_reply_to, annotation, dm_channel) + .await?; } ToOverlordMessage::PostAgain(event) => { self.post_again(event)?; @@ -1767,7 +1768,7 @@ impl Overlord { } /// Post a TextNote (kind 1) event - pub fn post( + pub async fn post( &mut self, content: String, tags: Vec, @@ -1787,13 +1788,15 @@ impl Overlord { let mut prepared_events = match dm_channel { Some(channel) => { if channel.can_use_nip17() { - crate::post::prepare_post_nip17(author, content, tags, channel, annotation)? + crate::post::prepare_post_nip17(author, content, tags, channel, annotation) + .await? } else { crate::post::prepare_post_nip04(author, content, channel, annotation)? } } None => { - crate::post::prepare_post_normal(author, content, tags, in_reply_to, annotation)? + crate::post::prepare_post_normal(author, content, tags, in_reply_to, annotation) + .await? } }; diff --git a/gossip-lib/src/post.rs b/gossip-lib/src/post.rs index 6fd28f8c..948f8868 100644 --- a/gossip-lib/src/post.rs +++ b/gossip-lib/src/post.rs @@ -4,12 +4,14 @@ use crate::globals::GLOBALS; use crate::relay; use crate::relay::Relay; use nostr_types::{ - ContentEncryptionAlgorithm, ContentSegment, Event, EventKind, EventReference, Id, NAddr, - NostrBech32, PreEvent, PublicKey, RelayUrl, ShatteredContent, Tag, UncheckedUrl, Unixtime, + ContentEncryptionAlgorithm, ContentSegment, Event, EventKind, EventReference, FileMetadata, Id, + NAddr, NostrBech32, PreEvent, PublicKey, RelayUrl, ShatteredContent, Tag, UncheckedUrl, + Unixtime, Url, }; use std::sync::mpsc; +use std::time::Duration; -pub fn prepare_post_normal( +pub async fn prepare_post_normal( author: PublicKey, content: String, mut tags: Vec, @@ -18,7 +20,7 @@ pub fn prepare_post_normal( ) -> Result)>, Error> { add_gossip_tag(&mut tags); - add_tags_mirroring_content(&content, &mut tags, false); + add_tags_mirroring_content(&content, &mut tags, false).await; if let Some(parent_id) = in_reply_to { add_thread_based_tags(author, &mut tags, parent_id)?; @@ -100,7 +102,7 @@ pub fn prepare_post_nip04( Ok(vec![(event, relay_urls)]) } -pub fn prepare_post_nip17( +pub async fn prepare_post_nip17( author: PublicKey, content: String, mut tags: Vec, @@ -120,7 +122,7 @@ pub fn prepare_post_nip17( add_gossip_tag(&mut tags); - add_tags_mirroring_content(&content, &mut tags, true); + add_tags_mirroring_content(&content, &mut tags, true).await; // All recipients get 'p' tagged on the DM rumor for pk in dm_channel.keys() { @@ -166,7 +168,7 @@ fn add_gossip_tag(tags: &mut Vec) { } } -fn add_tags_mirroring_content(content: &str, tags: &mut Vec, direct_message: bool) { +async fn add_tags_mirroring_content(content: &str, tags: &mut Vec, direct_message: bool) { let shattered_content = ShatteredContent::new(content.to_owned()); for segment in shattered_content.segments.iter() { match segment { @@ -210,9 +212,12 @@ fn add_tags_mirroring_content(content: &str, tags: &mut Vec, direct_message ContentSegment::TagReference(_index) => { // do nothing } - ContentSegment::Hyperlink(_span) => { - // do nothing - // FIXME: do something! + ContentSegment::Hyperlink(span) => { + if let Some(slice) = shattered_content.slice(&span) { + if let Some(mimetype) = crate::media_url_mimetype(slice) { + add_imeta_tag(slice, mimetype, tags).await; + } + } } ContentSegment::Plain(_span) => { // do nothing @@ -231,6 +236,72 @@ fn add_tags_mirroring_content(content: &str, tags: &mut Vec, direct_message } } +async fn add_imeta_tag(urlstr: &str, mimetype: &str, tags: &mut Vec) { + //turn into a nostr_types::Url + let url = match Url::try_from_str(urlstr) { + Ok(url) => url, + _ => return, + }; + + // Fetch the link and wait for it + GLOBALS.fetcher.fetch(url.clone(), false).await; + + // Pick up the result from the fetcher + let bytes = match GLOBALS.fetcher.try_get( + &url, + Duration::from_secs(60 * 60 * GLOBALS.db().read_setting_media_becomes_stale_hours()), + false, + ) { + Ok(Some(b)) => b, + _ => return, + }; + + // FIXME - in case we already have an imeta tag matching this url, we should + // find it, convert it into a FileMetadata, and delete it from tags to + // be replaced at the bottom of this function. However, I don't think + // it will ever happen so I'm just writing this note instead. + + let imeta = { + let unchecked_url = url.to_unchecked_url(); + let mut imeta = FileMetadata::new(unchecked_url); + + imeta.m = Some(mimetype.to_owned()); + imeta.size = Some(bytes.len() as u64); + + let hash = { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(&bytes); + let result = hasher.finalize(); + hex::encode(result) + }; + imeta.x = Some(hash); + + if mimetype.starts_with("image") { + use image::{DynamicImage, GenericImageView}; + if let Ok(dynamic_image) = image::load_from_memory(&bytes) { + let (w, h) = dynamic_image.dimensions(); + // Convert to RGBA8 + let dynamic_image = DynamicImage::ImageRgba8(dynamic_image.to_rgba8()); + if let Ok(blurhash) = blurhash::encode( + (4 * w / h).min(9), + (4 * h / w).min(9), + w, + h, + dynamic_image.as_bytes(), + ) { + imeta.blurhash = Some(blurhash); + imeta.dim = Some((w as usize, h as usize)); + } + } + } + + imeta + }; + + tags.push(imeta.to_imeta_tag()); +} + fn add_thread_based_tags( author: PublicKey, tags: &mut Vec, From 726b8eaea1ffb7ada7a46f5519db5ee2697ff1eb Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Fri, 8 Nov 2024 07:48:15 +1300 Subject: [PATCH 11/18] Fix database compacting (delete backup file first) and limit to once per week --- gossip-lib/src/storage/mod.rs | 36 +++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/gossip-lib/src/storage/mod.rs b/gossip-lib/src/storage/mod.rs index cae875c5..2c36efba 100644 --- a/gossip-lib/src/storage/mod.rs +++ b/gossip-lib/src/storage/mod.rs @@ -70,6 +70,7 @@ use crate::profile::Profile; use crate::relationship::{RelationshipByAddr, RelationshipById}; use crate::relay::Relay; use dashmap::DashMap; +use filetime::FileTime; use heed::types::{Bytes, Unit}; use heed::{Database, Env, EnvFlags, EnvOpenOptions, RoTxn, RwTxn}; use nostr_types::{ @@ -79,7 +80,9 @@ use nostr_types::{ use paste::paste; use speedy::{Readable, Writable}; use std::collections::{BTreeSet, HashMap}; +use std::fs; use std::ops::Bound; +use std::time::SystemTime; use self::event_kci_index::INDEXED_KINDS; use self::event_tag_index1::INDEXED_TAGS; @@ -157,18 +160,43 @@ impl Storage { pub(crate) fn compact() -> Result<(), Error> { let lmdb_dir = Profile::lmdb_dir()?; - let data = { - let mut data = lmdb_dir.clone(); - data.push("data.mdb"); - data + let stamp = { + let mut stamp = lmdb_dir.clone(); + stamp.push(".stamp"); + stamp }; + // If the stamp exists and is less than 1 week old, do not compact + if let Ok(metadata) = fs::metadata(&stamp) { + let last_modified = FileTime::from_last_modification_time(&metadata).seconds(); + let now = FileTime::now().seconds(); + if now - last_modified < 60*60*24+7 { + return Ok(()); + } else { + // Touch the stamp file + let file = fs::File::open(&stamp)?; + file.set_modified(SystemTime::now())?; + } + } else { + // Create stamp file + fs::File::create(&stamp)?; + } + let backup = { let mut backup = lmdb_dir.clone(); backup.push("backup.mdb"); backup }; + // Rmeove the backup file, ignore errors + let _ = std::fs::remove_file(&backup); + + let data = { + let mut data = lmdb_dir.clone(); + data.push("data.mdb"); + data + }; + let old = { let mut old = lmdb_dir.clone(); old.push("data_old.mdb"); From 67f0be4e9b68c80e777826229991e2b7bb5c4d8f Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Fri, 8 Nov 2024 08:55:01 +1300 Subject: [PATCH 12/18] Update nostr-types (fix reply determinations) --- Cargo.lock | 2 +- gossip-bin/Cargo.toml | 2 +- gossip-lib/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1874f67..eaa7f5b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3450,7 +3450,7 @@ dependencies = [ [[package]] name = "nostr-types" version = "0.8.0-unstable" -source = "git+https://github.com/mikedilger/nostr-types?rev=6412151b603ab913572aa21ad8e0ed51f1519c69#6412151b603ab913572aa21ad8e0ed51f1519c69" +source = "git+https://github.com/mikedilger/nostr-types?rev=78cb912d72c6ed39123da4c32d308cfe3ae35d6d#78cb912d72c6ed39123da4c32d308cfe3ae35d6d" dependencies = [ "aes", "base64 0.22.1", diff --git a/gossip-bin/Cargo.toml b/gossip-bin/Cargo.toml index d0edabc6..d83c58fb 100644 --- a/gossip-bin/Cargo.toml +++ b/gossip-bin/Cargo.toml @@ -31,7 +31,7 @@ humansize = "2.1" image = { version = "0.25", features = [ "png", "jpeg" ] } lazy_static = "1.5" memoize = "0.4" -nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "6412151b603ab913572aa21ad8e0ed51f1519c69", features = [ "speedy" ] } +nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "78cb912d72c6ed39123da4c32d308cfe3ae35d6d", features = [ "speedy" ] } paste = "1.0" qrcode = "0.14" resvg = "0.35.0" diff --git a/gossip-lib/Cargo.toml b/gossip-lib/Cargo.toml index 67bd6173..1b045873 100644 --- a/gossip-lib/Cargo.toml +++ b/gossip-lib/Cargo.toml @@ -56,7 +56,7 @@ kamadak-exif = "0.5" lazy_static = "1.5" linkify = "0.10" mime = "0.3" -nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "6412151b603ab913572aa21ad8e0ed51f1519c69", features = [ "speedy" ] } +nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "78cb912d72c6ed39123da4c32d308cfe3ae35d6d", features = [ "speedy" ] } parking_lot = { version = "0.12", features = [ "arc_lock", "send_guard" ] } paste = "1.0" rand = "0.8" From d0260472f0f7448daef882d0867c55b37fc4d169 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Fri, 8 Nov 2024 08:59:56 +1300 Subject: [PATCH 13/18] Migration 44: rebuild relationships --- gossip-lib/src/storage/migrations/m42.rs | 2 +- gossip-lib/src/storage/migrations/m44.rs | 23 +++++++++++++++++++++++ gossip-lib/src/storage/migrations/mod.rs | 5 ++++- 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 gossip-lib/src/storage/migrations/m44.rs diff --git a/gossip-lib/src/storage/migrations/m42.rs b/gossip-lib/src/storage/migrations/m42.rs index eb8dc56c..70bcfe66 100644 --- a/gossip-lib/src/storage/migrations/m42.rs +++ b/gossip-lib/src/storage/migrations/m42.rs @@ -13,7 +13,7 @@ impl Storage { txn: &mut RwTxn<'a>, ) -> Result<(), Error> { // Info message - tracing::info!("{prefix}: rebuilding friends-of-friends datat..."); + tracing::info!("{prefix}: rebuilding friends-of-friends data..."); // Rebuild Fof self.set_flag_rebuild_fof_needed(true, Some(txn))?; diff --git a/gossip-lib/src/storage/migrations/m44.rs b/gossip-lib/src/storage/migrations/m44.rs new file mode 100644 index 00000000..63b1d3e1 --- /dev/null +++ b/gossip-lib/src/storage/migrations/m44.rs @@ -0,0 +1,23 @@ +use crate::error::Error; +use crate::storage::Storage; +use heed::RwTxn; + +impl Storage { + pub(super) fn m44_trigger(&self) -> Result<(), Error> { + Ok(()) + } + + pub(super) fn m44_migrate<'a>( + &'a self, + prefix: &str, + txn: &mut RwTxn<'a>, + ) -> Result<(), Error> { + // Info message + tracing::info!("{prefix}: Flagging that relationships need to be rebuilt..."); + + // Rebuild relationships + self.set_flag_rebuild_relationships_needed(true, Some(txn))?; + + Ok(()) + } +} diff --git a/gossip-lib/src/storage/migrations/mod.rs b/gossip-lib/src/storage/migrations/mod.rs index c00296fa..4a9c182c 100644 --- a/gossip-lib/src/storage/migrations/mod.rs +++ b/gossip-lib/src/storage/migrations/mod.rs @@ -27,6 +27,7 @@ mod m40; mod m41; mod m42; mod m43; +mod m44; use super::Storage; use crate::error::{Error, ErrorKind}; @@ -34,7 +35,7 @@ use heed::RwTxn; impl Storage { const MIN_MIGRATION_LEVEL: u32 = 23; - const MAX_MIGRATION_LEVEL: u32 = 43; + const MAX_MIGRATION_LEVEL: u32 = 44; /// Initialize the database from empty pub(super) fn init_from_empty(&self) -> Result<(), Error> { @@ -130,6 +131,7 @@ impl Storage { 41 => self.m41_trigger()?, 42 => self.m42_trigger()?, 43 => self.m43_trigger()?, + 44 => self.m44_trigger()?, _ => panic!("Unreachable migration level"), } @@ -164,6 +166,7 @@ impl Storage { 41 => self.m41_migrate(&prefix, txn)?, 42 => self.m42_migrate(&prefix, txn)?, 43 => self.m43_migrate(&prefix, txn)?, + 44 => self.m44_migrate(&prefix, txn)?, _ => panic!("Unreachable migration level"), }; From 0c2a25caef41a89fd09f0535bff509cca143f9c2 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Fri, 8 Nov 2024 09:20:51 +1300 Subject: [PATCH 14/18] Comments about where we are using 'q' --- gossip-lib/src/post.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gossip-lib/src/post.rs b/gossip-lib/src/post.rs index 948f8868..c336d629 100644 --- a/gossip-lib/src/post.rs +++ b/gossip-lib/src/post.rs @@ -178,6 +178,8 @@ async fn add_tags_mirroring_content(content: &str, tags: &mut Vec, direct_m // add nothing } NostrBech32::NAddr(ea) => { + // https://github.com/nostr-protocol/nips/pull/1560 may allow us to use 'q' + // in the future nostr_types::add_addr_to_tags(tags, ea, Some("mention".to_string())); } NostrBech32::NEvent(ne) => { @@ -187,12 +189,12 @@ async fn add_tags_mirroring_content(content: &str, tags: &mut Vec, direct_m ne.id, ne.relays.first().cloned(), ne.author, - "mention", + "mention", // this will use 'q', see the function ); } NostrBech32::Id(id) => { // NIP-10: "Those marked with "mention" denote a quoted or reposted event id." - add_event_to_tags(tags, *id, None, None, "mention"); + add_event_to_tags(tags, *id, None, None, "mention"); // this will use 'q', see the function } NostrBech32::Profile(prof) => { if !direct_message { From c7bff06c3de7025a9294235c6a51a64a2dec002c Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Fri, 8 Nov 2024 09:45:31 +1300 Subject: [PATCH 15/18] Sort replies in threads so that author comes first, then time ordered (previously was fixed random) --- gossip-lib/src/storage/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/gossip-lib/src/storage/mod.rs b/gossip-lib/src/storage/mod.rs index 2c36efba..cb05642f 100644 --- a/gossip-lib/src/storage/mod.rs +++ b/gossip-lib/src/storage/mod.rs @@ -170,7 +170,7 @@ impl Storage { if let Ok(metadata) = fs::metadata(&stamp) { let last_modified = FileTime::from_last_modification_time(&metadata).seconds(); let now = FileTime::now().seconds(); - if now - last_modified < 60*60*24+7 { + if now - last_modified < 60 * 60 * 24 + 7 { return Ok(()); } else { // Touch the stamp file @@ -1909,6 +1909,22 @@ impl Storage { }); } + // Sort + if !output.is_empty() { + use std::cmp::Ordering; + let mut filter = Filter::new(); + filter.ids = output.iter().map(|id| (*id).into()).collect(); + let mut events = self.find_events_by_filter(&filter, |_| true)?; + events.sort_by( + |a, b| match (a.pubkey == event.pubkey, b.pubkey == event.pubkey) { + (true, false) => Ordering::Less, + (false, true) => Ordering::Greater, + _ => a.created_at.cmp(&b.created_at), + }, + ); + output = events.iter().map(|e| e.id).collect(); + } + Ok(output) } From b2a92075a82b9a5dedac5e34c0d7b5ae2fd92512 Mon Sep 17 00:00:00 2001 From: dtonon Date: Thu, 7 Nov 2024 22:40:39 +0100 Subject: [PATCH 16/18] Update theme colors --- gossip-bin/src/ui/theme/default.rs | 22 ++++++++++++++-------- gossip-bin/src/ui/theme/mod.rs | 2 ++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/gossip-bin/src/ui/theme/default.rs b/gossip-bin/src/ui/theme/default.rs index 6b7a47cf..3937ae4d 100644 --- a/gossip-bin/src/ui/theme/default.rs +++ b/gossip-bin/src/ui/theme/default.rs @@ -79,6 +79,12 @@ impl ThemeDef for DefaultTheme { fn neutral_800() -> Color32 { Color32::from_rgb(0x26, 0x26, 0x26) } // #262626 + fn neutral_850() -> Color32 { + Color32::from_rgb(0x1e, 0x1e, 0x1e) + } // #1E1E1E + fn neutral_875() -> Color32 { + Color32::from_rgb(0x1b, 0x1b, 0x1b) + } // #1B1B1B fn neutral_900() -> Color32 { Color32::from_rgb(0x17, 0x17, 0x17) } // #171717 @@ -142,17 +148,17 @@ impl ThemeDef for DefaultTheme { fn main_content_bgcolor(dark_mode: bool) -> Color32 { if dark_mode { - Color32::BLACK + Self::neutral_800() } else { - Color32::WHITE + Self::neutral_50() } } fn hovered_content_bgcolor(dark_mode: bool) -> Color32 { if dark_mode { - Self::neutral_950() + Self::neutral_850() } else { - Self::neutral_50() + Color32::WHITE } } @@ -280,7 +286,7 @@ impl ThemeDef for DefaultTheme { // Background colors window_fill: Self::neutral_950(), // pulldown menus and tooltips - panel_fill: Color32::from_gray(0x29), // panel backgrounds, even-table-rows + panel_fill: Self::neutral_875(), // panel backgrounds, even-table-rows faint_bg_color: Color32::from_gray(20), // odd-table-rows extreme_bg_color: Color32::from_gray(45), // text input background; scrollbar background code_bg_color: Color32::from_gray(64), // ??? @@ -372,7 +378,7 @@ impl ThemeDef for DefaultTheme { // Background colors window_fill: Self::neutral_100(), // pulldown menus and tooltips - panel_fill: Color32::from_gray(0xF4), // panel backgrounds, even-table-rows + panel_fill: Self::neutral_200(), // panel backgrounds, even-table-rows faint_bg_color: Color32::from_gray(248), // odd-table-rows extreme_bg_color: Color32::from_gray(246), // text input background; scrollbar background code_bg_color: Color32::from_gray(230), // ??? @@ -695,9 +701,9 @@ impl ThemeDef for DefaultTheme { fn navigation_bg_fill(dark_mode: bool) -> eframe::egui::Color32 { if dark_mode { - Color32::from_gray(0x26) + Self::neutral_800() } else { - Color32::from_gray(0xE1) + Self::neutral_100() } } diff --git a/gossip-bin/src/ui/theme/mod.rs b/gossip-bin/src/ui/theme/mod.rs index 9ee2d93a..81612b8c 100644 --- a/gossip-bin/src/ui/theme/mod.rs +++ b/gossip-bin/src/ui/theme/mod.rs @@ -542,6 +542,8 @@ pub trait ThemeDef: Send + Sync { fn neutral_700() -> Color32; fn neutral_800() -> Color32; fn neutral_900() -> Color32; + fn neutral_850() -> Color32; + fn neutral_875() -> Color32; fn neutral_950() -> Color32; fn accent_dark() -> Color32; fn accent_dark_b20() -> Color32; // overlay 20% black From c977f56a01c45ce36e526a7cdab8b04426afcaab Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sat, 9 Nov 2024 06:58:32 +1300 Subject: [PATCH 17/18] Don't fail if LMDB compact doesn't have space --- gossip-lib/src/storage/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gossip-lib/src/storage/mod.rs b/gossip-lib/src/storage/mod.rs index cb05642f..d76db210 100644 --- a/gossip-lib/src/storage/mod.rs +++ b/gossip-lib/src/storage/mod.rs @@ -211,7 +211,10 @@ impl Storage { // Copy to backup file, compacting tracing::info!("Compacting LMDB..."); - let _ = env.copy_to_file(&backup, heed::CompactionOption::Enabled)?; + if let Err(_) = env.copy_to_file(&backup, heed::CompactionOption::Enabled) { + // Just give up on compacting + return Ok(()); + } env.force_sync()?; let _ = env.prepare_for_closing(); From b291079f20929a0ac45c7c53587eba9cb9571129 Mon Sep 17 00:00:00 2001 From: Mike Dilger Date: Sat, 9 Nov 2024 07:07:31 +1300 Subject: [PATCH 18/18] If LMDB copy+compact fails, erase the partial output --- gossip-lib/src/storage/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gossip-lib/src/storage/mod.rs b/gossip-lib/src/storage/mod.rs index d76db210..24442ec0 100644 --- a/gossip-lib/src/storage/mod.rs +++ b/gossip-lib/src/storage/mod.rs @@ -212,6 +212,9 @@ impl Storage { // Copy to backup file, compacting tracing::info!("Compacting LMDB..."); if let Err(_) = env.copy_to_file(&backup, heed::CompactionOption::Enabled) { + // Erase the attempt + std::fs::remove_file(&backup)?; + // Just give up on compacting return Ok(()); } @@ -226,7 +229,7 @@ impl Storage { // Move the backup to the data std::fs::rename(&backup, &data)?; - // Rmeove the old + // Remove the old std::fs::remove_file(&old)?; Ok(())