notedeck

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

commit 739e9f87f2db6547b0149a01f9842d66bd8d1b80
parent 135a5c99ae8205fd60af8aedad6d525bf0516554
Author: William Casarin <jb55@jb55.com>
Date:   Fri, 24 May 2024 13:20:33 -0700

nip10: fetch unknown replied-to notes

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
MCargo.lock | 2+-
MCargo.toml | 2+-
Msrc/app.rs | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/notecache.rs | 17+++++++++++++++--
Msrc/timecache.rs | 28+++++++++++++++++++++-------
Msrc/ui/note/mod.rs | 2+-
6 files changed, 93 insertions(+), 16 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -2432,7 +2432,7 @@ dependencies = [ [[package]] name = "nostrdb" version = "0.3.3" -source = "git+https://github.com/damus-io/nostrdb-rs?rev=99d8296fcba5957245ed883e2f3b1c0d1cb16397#99d8296fcba5957245ed883e2f3b1c0d1cb16397" +source = "git+https://github.com/damus-io/nostrdb-rs?rev=5733ece62b8495db8624a21637bacd12ebb22b2c#5733ece62b8495db8624a21637bacd12ebb22b2c" dependencies = [ "bindgen", "cc", diff --git a/Cargo.toml b/Cargo.toml @@ -33,7 +33,7 @@ serde_json = "1.0.89" env_logger = "0.10.0" puffin_egui = { version = "0.27.0", optional = true } puffin = { version = "0.19.0", optional = true } -nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "99d8296fcba5957245ed883e2f3b1c0d1cb16397" } +nostrdb = { git = "https://github.com/damus-io/nostrdb-rs", rev = "5733ece62b8495db8624a21637bacd12ebb22b2c" } #nostrdb = "0.3.3" hex = "0.4.3" base32 = "0.4.0" diff --git a/src/app.rs b/src/app.rs @@ -207,6 +207,7 @@ impl<'a> UnknownId<'a> { fn get_unknown_note_ids<'a>( ndb: &Ndb, + cached_note: &CachedNote, txn: &'a Transaction, note: &Note<'a>, note_key: NoteKey, @@ -218,6 +219,24 @@ fn get_unknown_note_ids<'a>( ids.insert(UnknownId::Pubkey(note.pubkey())); } + // pull notes that notes are replying to + if cached_note.reply.root.is_some() { + let note_reply = cached_note.reply.borrow(note.tags()); + if let Some(root) = note_reply.root() { + if ndb.get_note_by_id(txn, root.id).is_err() { + ids.insert(UnknownId::Id(root.id)); + } + } + + if !note_reply.is_reply_to_root() { + if let Some(reply) = note_reply.reply() { + if ndb.get_note_by_id(txn, reply.id).is_err() { + ids.insert(UnknownId::Id(reply.id)); + } + } + } + } + let blocks = ndb.get_blocks_by_key(txn, note_key)?; for block in blocks.iter(note) { if block.blocktype() != BlockType::MentionBech32 { @@ -288,8 +307,11 @@ fn poll_notes_for_timeline<'a>( .iter() .map(|key| { let note = damus.ndb.get_note_by_key(txn, *key).expect("no note??"); - - let _ = get_unknown_note_ids(&damus.ndb, txn, &note, *key, ids); + let cached_note = damus + .note_cache_mut() + .cached_note_or_insert(*key, &note) + .clone(); + let _ = get_unknown_note_ids(&damus.ndb, &cached_note, txn, &note, *key, ids); let created_at = note.created_at(); ( @@ -440,19 +462,43 @@ fn process_event(damus: &mut Damus, _subid: &str, event: &str) { } } -fn get_unknown_ids<'a>(txn: &'a Transaction, damus: &Damus) -> Result<Vec<UnknownId<'a>>> { +fn get_unknown_ids<'a>(txn: &'a Transaction, damus: &mut Damus) -> Result<Vec<UnknownId<'a>>> { #[cfg(feature = "profiling")] puffin::profile_function!(); let mut ids: HashSet<UnknownId> = HashSet::new(); + let mut new_cached_notes: Vec<(NoteKey, CachedNote)> = vec![]; for timeline in &damus.timelines { for noteref in timeline.notes(ViewFilter::NotesAndReplies) { let note = damus.ndb.get_note_by_key(txn, noteref.key)?; - let _ = get_unknown_note_ids(&damus.ndb, txn, &note, note.key().unwrap(), &mut ids); + let note_key = note.key().unwrap(); + let cached_note = damus.note_cache().cached_note(noteref.key); + let cached_note = if let Some(cn) = cached_note { + cn.clone() + } else { + let new_cached_note = CachedNote::new(&note); + new_cached_notes.push((note_key, new_cached_note.clone())); + new_cached_note + }; + + let _ = get_unknown_note_ids( + &damus.ndb, + &cached_note, + txn, + &note, + note.key().unwrap(), + &mut ids, + ); } } + // This is mainly done to avoid the double mutable borrow that would happen + // if we tried to update the note_cache mutably in the loop above + for (note_key, note) in new_cached_notes { + damus.note_cache_mut().cache_mut().insert(note_key, note); + } + Ok(ids.into_iter().collect()) } @@ -583,6 +629,10 @@ impl Damus { &mut self.note_cache } + pub fn note_cache(&self) -> &NoteCache { + &self.note_cache + } + pub fn selected_timeline(&mut self) -> &mut Timeline { &mut self.timelines[self.selected_timeline as usize] } diff --git a/src/notecache.rs b/src/notecache.rs @@ -16,6 +16,14 @@ impl NoteCache { .or_insert_with(|| CachedNote::new(note)) } + pub fn cached_note(&self, note_key: NoteKey) -> Option<&CachedNote> { + self.cache.get(&note_key) + } + + pub fn cache_mut(&mut self) -> &mut HashMap<NoteKey, CachedNote> { + &mut self.cache + } + pub fn cached_note_or_insert(&mut self, note_key: NoteKey, note: &Note) -> &CachedNote { self.cache .entry(note_key) @@ -23,6 +31,7 @@ impl NoteCache { } } +#[derive(Clone)] pub struct CachedNote { reltime: TimeCached<String>, pub reply: NoteReplyBuf, @@ -45,7 +54,11 @@ impl CachedNote { } } - pub fn reltime_str(&mut self) -> &str { - return self.reltime.get(); + pub fn reltime_str_mut(&mut self) -> &str { + self.reltime.get_mut() + } + + pub fn reltime_str(&self) -> Option<&str> { + self.reltime.get().map(|x| x.as_str()) } } diff --git a/src/timecache.rs b/src/timecache.rs @@ -1,26 +1,40 @@ +use std::rc::Rc; use std::time::{Duration, Instant}; +#[derive(Clone)] pub struct TimeCached<T> { last_update: Instant, expires_in: Duration, value: Option<T>, - refresh: Box<dyn Fn() -> T + 'static>, + refresh: Rc<dyn Fn() -> T + 'static>, } impl<T> TimeCached<T> { - pub fn new(expires_in: Duration, refresh: Box<dyn Fn() -> T + 'static>) -> Self { + pub fn new(expires_in: Duration, refresh: impl Fn() -> T + 'static) -> Self { TimeCached { last_update: Instant::now(), expires_in, value: None, - refresh, + refresh: Rc::new(refresh), } } - pub fn get(&mut self) -> &T { - if self.value.is_none() || self.last_update.elapsed() > self.expires_in { - self.last_update = Instant::now(); - self.value = Some((self.refresh)()); + pub fn needs_update(&self) -> bool { + self.value.is_none() || self.last_update.elapsed() > self.expires_in + } + + pub fn update(&mut self) { + self.last_update = Instant::now(); + self.value = Some((self.refresh)()); + } + + pub fn get(&self) -> Option<&T> { + self.value.as_ref() + } + + pub fn get_mut(&mut self) -> &T { + if self.needs_update() { + self.update(); } self.value.as_ref().unwrap() // This unwrap is safe because we just set the value if it was None. } diff --git a/src/ui/note/mod.rs b/src/ui/note/mod.rs @@ -326,7 +326,7 @@ fn render_reltime( secondary_label(ui, "⋅"); } - secondary_label(ui, note_cache.reltime_str()); + secondary_label(ui, note_cache.reltime_str_mut()); if !before { secondary_label(ui, "⋅");