notedeck

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

commit e0ed122951b6203246fa4499e55ffb31019e667e
parent e1ad2e231f790ba4779c41e069a7540f94a40250
Author: kernelkind <kernelkind@gmail.com>
Date:   Mon,  8 Sep 2025 16:04:12 -0400

use `NdbQueryPackage` to call ndb::query multiple times

necessary to ensure we can retrieve reposts from ndb

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

Diffstat:
Mcrates/notedeck/src/contacts.rs | 12+++++++++---
Mcrates/notedeck/src/filter.rs | 17++++++++++++-----
Mcrates/notedeck_columns/src/multi_subscriber.rs | 4++--
Mcrates/notedeck_columns/src/timeline/cache.rs | 43+++++++++++++++++++++++++++++--------------
Mcrates/notedeck_columns/src/timeline/kind.rs | 27+++++++++++++++++++++------
Mcrates/notedeck_columns/src/timeline/mod.rs | 34++++++++++++++++++++++++----------
6 files changed, 97 insertions(+), 40 deletions(-)

diff --git a/crates/notedeck/src/contacts.rs b/crates/notedeck/src/contacts.rs @@ -1,5 +1,5 @@ use crate::{ - filter::{self, HybridFilter}, + filter::{self, HybridFilter, ValidKind}, Error, }; use nostrdb::{Filter, Note}; @@ -15,8 +15,14 @@ pub fn hybrid_contacts_filter( add_pk: Option<&[u8; 32]>, with_hashtags: bool, ) -> Result<HybridFilter, Error> { - let local = filter::filter_from_tags(note, add_pk, with_hashtags)? - .into_filter(vec![1], filter::default_limit()); + let local = vec![ + filter::filter_from_tags(note, add_pk, with_hashtags)? + .into_query_package(ValidKind::One, filter::default_limit()), + filter::filter_from_tags(note, add_pk, with_hashtags)? + .into_query_package(ValidKind::Six, filter::default_limit()), + filter::filter_from_tags(note, add_pk, with_hashtags)? + .into_query_package(ValidKind::Zero, filter::default_limit()), + ]; let remote = filter::filter_from_tags(note, add_pk, with_hashtags)? .into_filter(vec![1, 0], filter::default_remote_limit()); diff --git a/crates/notedeck/src/filter.rs b/crates/notedeck/src/filter.rs @@ -213,7 +213,7 @@ pub struct FilteredTags { /// The local and remote filter are related but slightly different #[derive(Debug, Clone)] pub struct SplitFilter { - pub local: Vec<Filter>, + pub local: Vec<NdbQueryPackage>, pub remote: Vec<Filter>, } @@ -230,16 +230,23 @@ impl HybridFilter { HybridFilter::Unsplit(filter) } - pub fn split(local: Vec<Filter>, remote: Vec<Filter>) -> Self { + pub fn split(local: Vec<NdbQueryPackage>, remote: Vec<Filter>) -> Self { HybridFilter::Split(SplitFilter { local, remote }) } - pub fn local(&self) -> &[Filter] { + pub fn local(&self) -> NdbQueryPackages { match self { - Self::Split(split) => &split.local, + Self::Split(split) => NdbQueryPackages { + packages: split.local.iter().map(NdbQueryPackage::borrow).collect(), + }, // local as the same as remote in unsplit - Self::Unsplit(local) => local, + Self::Unsplit(local) => NdbQueryPackages { + packages: vec![NdbQueryPackageUnowned { + filters: local, + kind: None, + }], + }, } } diff --git a/crates/notedeck_columns/src/multi_subscriber.rs b/crates/notedeck_columns/src/multi_subscriber.rs @@ -311,7 +311,7 @@ impl TimelineSub { let before = self.state.clone(); match &mut self.state { SubState::NoSub { dependers } => { - let Some(sub) = ndb_sub(ndb, filter.local(), "") else { + let Some(sub) = ndb_sub(ndb, &filter.local().combined(), "") else { return; }; @@ -326,7 +326,7 @@ impl TimelineSub { dependers: _, } => {} SubState::RemoteOnly { remote, dependers } => { - let Some(local) = ndb_sub(ndb, filter.local(), "") else { + let Some(local) = ndb_sub(ndb, &filter.local().combined(), "") else { return; }; self.state = SubState::Unified { diff --git a/crates/notedeck_columns/src/timeline/cache.rs b/crates/notedeck_columns/src/timeline/cache.rs @@ -134,15 +134,22 @@ impl TimelineCache { } let notes = if let FilterState::Ready(filters) = id.filters(txn, ndb) { - if let Ok(results) = ndb.query(txn, filters.local(), 1000) { - results - .into_iter() - .map(NoteRef::from_query_result) - .collect() - } else { - debug!("got no results from TimelineCache lookup for {:?}", id); - vec![] + let mut notes = Vec::new(); + + for package in filters.local().packages { + if let Ok(results) = ndb.query(txn, package.filters, 1000) { + let cur_notes: Vec<NoteRef> = results + .into_iter() + .map(NoteRef::from_query_result) + .collect(); + + notes.extend(cur_notes); + } else { + debug!("got no results from TimelineCache lookup for {:?}", id); + } } + + notes } else { // filter is not ready yet vec![] @@ -178,12 +185,20 @@ impl TimelineCache { let (mut open_result, timeline) = match notes_resp.vitality { Vitality::Stale(timeline) => { // The timeline cache is stale, let's update it - let notes = find_new_notes( - timeline.all_or_any_entries().latest(), - timeline.subscription.get_filter()?.local(), - txn, - ndb, - ); + let notes = { + let mut notes = Vec::new(); + for package in timeline.subscription.get_filter()?.local().packages { + let cur_notes = find_new_notes( + timeline.all_or_any_entries().latest(), + package.filters, + txn, + ndb, + ); + notes.extend(cur_notes); + } + notes + }; + let open_result = if notes.is_empty() { None } else { diff --git a/crates/notedeck_columns/src/timeline/kind.rs b/crates/notedeck_columns/src/timeline/kind.rs @@ -3,6 +3,7 @@ use crate::search::SearchQuery; use crate::timeline::{Timeline, TimelineTab}; use enostr::{Filter, NoteId, Pubkey}; use nostrdb::{Ndb, Transaction}; +use notedeck::filter::{NdbQueryPackage, ValidKind}; use notedeck::{ contacts::{contacts_filter, hybrid_contacts_filter}, filter::{self, default_limit, default_remote_limit, HybridFilter}, @@ -728,15 +729,29 @@ fn last_per_pubkey_filter_state(ndb: &Ndb, pk: &Pubkey) -> FilterState { } fn profile_filter(pk: &[u8; 32]) -> HybridFilter { + let local = vec![ + NdbQueryPackage { + filters: vec![Filter::new() + .authors([pk]) + .kinds([1]) + .limit(default_limit()) + .build()], + kind: ValidKind::One, + }, + NdbQueryPackage { + filters: vec![Filter::new() + .authors([pk]) + .kinds([6]) + .limit(default_limit()) + .build()], + kind: ValidKind::Six, + }, + ]; HybridFilter::split( + local, vec![Filter::new() .authors([pk]) - .kinds([1]) - .limit(default_limit()) - .build()], - vec![Filter::new() - .authors([pk]) - .kinds([1, 0]) + .kinds([1, 6, 0]) .limit(default_remote_limit()) .build()], ) diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs @@ -676,18 +676,32 @@ fn setup_initial_timeline( timeline.subscription, timeline.filter ); - let mut lim = 0i32; - for filter in filters.local() { - lim += filter.limit().unwrap_or(1) as i32; - } + let notes = { + let mut notes = Vec::new(); + + for package in filters.local().packages { + let mut lim = 0i32; + for filter in package.filters { + lim += filter.limit().unwrap_or(1) as i32; + } - debug!("setup_initial_timeline: limit for local filter is {}", lim); + debug!("setup_initial_timeline: limit for local filter is {}", lim); + + let cur_notes: Vec<NoteRef> = ndb + .query(txn, package.filters, lim)? + .into_iter() + .map(NoteRef::from_query_result) + .collect(); + tracing::debug!( + "Found {} notes for kind: {:?}", + cur_notes.len(), + package.kind + ); + notes.extend(&cur_notes); + } - let notes: Vec<NoteRef> = ndb - .query(txn, filters.local(), lim)? - .into_iter() - .map(NoteRef::from_query_result) - .collect(); + notes + }; if let Some(pks) = timeline.insert_new(txn, ndb, note_cache, &notes) { pks.process(ndb, txn, unknown_ids);