commit 86398fcd1263fce10f0c70de2f902ad9a5a0ee3d
parent f3065e6da9d3c6921618e5e0842d250985f0bc84
Author: kernelkind <kernelkind@gmail.com>
Date: Wed, 25 Feb 2026 18:52:21 -0500
feat(outbox-int): only use one FilterState for timeline
eose tracking happens in outbox
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
7 files changed, 141 insertions(+), 464 deletions(-)
diff --git a/crates/notedeck/src/filter.rs b/crates/notedeck/src/filter.rs
@@ -1,7 +1,6 @@
use crate::error::{Error, FilterError};
use crate::note::NoteRef;
use nostrdb::{Filter, FilterBuilder, Note, Subscription};
-use std::collections::HashMap;
use tracing::{debug, warn};
/// A unified subscription has a local and remote component. The remote subid
@@ -12,130 +11,17 @@ pub struct UnifiedSubscription {
pub remote: String,
}
-/// Each relay can have a different filter state. For example, some
-/// relays may have the contact list, some may not. Let's capture all of
-/// these states so that some relays don't stop the states of other
-/// relays.
-#[derive(Debug)]
-pub struct FilterStates {
- pub initial_state: FilterState,
- pub states: HashMap<String, FilterState>,
-}
-
-impl FilterStates {
- pub fn get_mut(&mut self, relay: &str) -> &FilterState {
- // if our initial state is ready, then just use that
- if let FilterState::Ready(_) = self.initial_state {
- &self.initial_state
- } else {
- // otherwise we look at relay states
- if !self.states.contains_key(relay) {
- self.states
- .insert(relay.to_string(), self.initial_state.clone());
- }
- self.states.get(relay).unwrap()
- }
- }
-
- pub fn get_any_gotremote(&self) -> Option<GotRemoteResult> {
- for (k, v) in self.states.iter() {
- if let FilterState::GotRemote(item_type) = v {
- return match item_type {
- GotRemoteType::Normal(subscription) => Some(GotRemoteResult::Normal {
- relay_id: k.to_owned(),
- sub_id: *subscription,
- }),
- GotRemoteType::Contact => Some(GotRemoteResult::Contact {
- relay_id: k.to_owned(),
- }),
- GotRemoteType::PeopleList => Some(GotRemoteResult::PeopleList {
- relay_id: k.to_owned(),
- }),
- };
- }
- }
-
- None
- }
-
- pub fn get_any_ready(&self) -> Option<&HybridFilter> {
- if let FilterState::Ready(fs) = &self.initial_state {
- Some(fs)
- } else {
- for (_k, v) in self.states.iter() {
- if let FilterState::Ready(ref fs) = v {
- return Some(fs);
- }
- }
-
- None
- }
- }
-
- pub fn new(initial_state: FilterState) -> Self {
- Self {
- initial_state,
- states: HashMap::new(),
- }
- }
-
- pub fn set_relay_state(&mut self, relay: String, state: FilterState) {
- if self.states.contains_key(&relay) {
- let current_state = self.states.get(&relay).unwrap();
- debug!(
- "set_relay_state: {:?} -> {:?} on {}",
- current_state, state, &relay,
- );
- }
- self.states.insert(relay, state);
- }
-
- /// For contacts, since that sub is managed elsewhere
- pub fn set_all_states(&mut self, state: FilterState) {
- for cur_state in self.states.values_mut() {
- *cur_state = state.clone();
- }
- }
-}
-
/// We may need to fetch some data from relays before our filter is ready.
/// [`FilterState`] tracks this.
#[derive(Debug, Clone)]
pub enum FilterState {
NeedsRemote,
- FetchingRemote(FetchingRemoteType),
- GotRemote(GotRemoteType),
+ FetchingRemote,
+ GotRemote,
Ready(HybridFilter),
Broken(FilterError),
}
-pub enum GotRemoteResult {
- Normal {
- relay_id: String,
- sub_id: Subscription,
- },
- Contact {
- relay_id: String,
- },
- PeopleList {
- relay_id: String,
- },
-}
-
-#[derive(Debug, Clone)]
-pub enum FetchingRemoteType {
- Normal(UnifiedSubscription),
- Contact,
- PeopleList,
-}
-
-#[derive(Debug, Clone)]
-pub enum GotRemoteType {
- Normal(Subscription),
- Contact,
- PeopleList,
-}
-
impl FilterState {
/// We tried to fetch a filter but we wither got no data or the data
/// was corrupted, preventing us from getting to the Ready state.
@@ -162,22 +48,6 @@ impl FilterState {
pub fn needs_remote() -> Self {
Self::NeedsRemote
}
-
- /// We got the remote data. Local data should be available to build
- /// the filter for the [`FilterState::Ready`] state
- pub fn got_remote(local_sub: Subscription) -> Self {
- Self::GotRemote(GotRemoteType::Normal(local_sub))
- }
-
- /// We have sent off a remote subscription to get data needed for the
- /// filter. The string is the subscription id
- pub fn fetching_remote(sub_id: String, local_sub: Subscription) -> Self {
- let unified_sub = UnifiedSubscription {
- local: local_sub,
- remote: sub_id,
- };
- Self::FetchingRemote(FetchingRemoteType::Normal(unified_sub))
- }
}
pub fn should_since_optimize(limit: u64, num_notes: usize) -> bool {
diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs
@@ -63,7 +63,7 @@ pub use async_loader::{worker_count, AsyncLoader};
pub use context::{AppContext, SoftKeyboardContext};
use enostr::{OutboxSessionHandler, Wakeup};
pub use error::{show_one_error_message, Error, FilterError, ZapError};
-pub use filter::{FilterState, FilterStates, UnifiedSubscription};
+pub use filter::{FilterState, UnifiedSubscription};
pub use fonts::NamedFontFamily;
pub use i18n::{CacheStats, FluentArgs, FluentValue, LanguageIdentifier, Localization};
pub use imgcache::{
diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs
@@ -18,12 +18,12 @@ use crate::{
Result,
};
use egui_extras::{Size, StripBuilder};
-use enostr::{ClientMessage, Pubkey, RelayEvent, RelayMessage};
+use enostr::Pubkey;
use nostrdb::Transaction;
use notedeck::{
- tr, try_process_events_core, ui::is_compiled_as_mobile, ui::is_narrow, Accounts, AppAction,
- AppContext, AppResponse, DataPath, DataPathType, FilterState, Images, Localization,
- MediaJobSender, NotedeckOptions, SettingsHandler,
+ tr, ui::is_compiled_as_mobile, ui::is_narrow, Accounts, AppAction, AppContext, AppResponse,
+ DataPath, DataPathType, FilterState, Images, Localization, MediaJobSender, NotedeckOptions,
+ SettingsHandler,
};
use notedeck_ui::{
media::{MediaViewer, MediaViewerFlags, MediaViewerState},
@@ -185,27 +185,25 @@ fn try_process_event(
)
});
- try_process_events_core(app_ctx, ctx, |app_ctx, ev| match (&ev.event).into() {
- RelayEvent::Opened => {
- let mut scoped_subs = app_ctx.remote.scoped_subs(app_ctx.accounts);
- timeline::send_initial_timeline_filters(
- damus.options.contains(AppOptions::SinceOptimize),
- &mut damus.timeline_cache,
- &mut damus.subscriptions,
- app_ctx.legacy_pool,
- &ev.relay,
- app_ctx.accounts,
- &mut scoped_subs,
- );
+ let selected_account_pk = *app_ctx.accounts.selected_account_pubkey();
+ for (kind, timeline) in &mut damus.timeline_cache {
+ if timeline.subscription.dependers(&selected_account_pk) == 0 {
+ continue;
}
- RelayEvent::Message(msg) => {
- process_message(damus, app_ctx, &ev.relay, &msg);
+
+ if let FilterState::Ready(filter) = &timeline.filter {
+ if timeline.kind.should_subscribe_locally()
+ && timeline
+ .subscription
+ .get_local(&selected_account_pk)
+ .is_none()
+ {
+ timeline
+ .subscription
+ .try_add_local(selected_account_pk, app_ctx.ndb, filter);
+ }
}
- _ => {}
- });
- for (kind, timeline) in &mut damus.timeline_cache {
- let selected_account_pk = *app_ctx.accounts.selected_account_pubkey();
let is_ready = {
let mut scoped_subs = app_ctx.remote.scoped_subs(app_ctx.accounts);
timeline::is_timeline_ready(app_ctx.ndb, &mut scoped_subs, timeline, app_ctx.accounts)
@@ -242,25 +240,14 @@ fn try_process_event(
| TimelineKind::Algo(timeline::kind::AlgoTimeline::LastPerPubkey(
ListKind::Contact(_),
)) => {
- timeline::fetch_contact_list(
- &mut damus.subscriptions,
- timeline,
- app_ctx.accounts,
- );
+ timeline::fetch_contact_list(timeline, app_ctx.accounts);
}
- TimelineKind::List(ListKind::PeopleList(plr))
+ TimelineKind::List(ListKind::PeopleList(_))
| TimelineKind::Algo(timeline::kind::AlgoTimeline::LastPerPubkey(
- ListKind::PeopleList(plr),
+ ListKind::PeopleList(_),
)) => {
- let plr = plr.clone();
- for relay in &mut app_ctx.legacy_pool.relays {
- timeline::fetch_people_list(
- &mut damus.subscriptions,
- relay,
- timeline,
- &plr,
- );
- }
+ let txn = Transaction::new(app_ctx.ndb).expect("txn");
+ timeline::fetch_people_list(app_ctx.ndb, &txn, timeline);
}
_ => {}
}
@@ -288,7 +275,7 @@ fn schedule_timeline_load(
return;
}
- let Some(filter) = timeline.filter.get_any_ready().cloned() else {
+ let FilterState::Ready(filter) = timeline.filter.clone() else {
return;
};
@@ -359,15 +346,7 @@ fn update_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ctx: &egui::Con
.subscriptions()
.insert("unknownids".to_string(), SubKind::OneShot);
- if let Err(err) = timeline::setup_initial_nostrdb_subs(
- app_ctx.ndb,
- app_ctx.note_cache,
- &mut damus.timeline_cache,
- app_ctx.unknown_ids,
- *app_ctx.accounts.selected_account_pubkey(),
- ) {
- warn!("update_damus init: {err}");
- }
+ setup_selected_account_timeline_subs(&mut damus.timeline_cache, app_ctx);
if !app_ctx.settings.welcome_completed() {
let split =
@@ -396,109 +375,18 @@ fn update_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ctx: &egui::Con
}
}
-fn handle_eose(
- subscriptions: &Subscriptions,
+pub(crate) fn setup_selected_account_timeline_subs(
timeline_cache: &mut TimelineCache,
- ctx: &mut AppContext<'_>,
- subid: &str,
- relay_url: &str,
-) -> Result<()> {
- let sub_kind = if let Some(sub_kind) = subscriptions.subs.get(subid) {
- sub_kind
- } else {
- let n_subids = subscriptions.subs.len();
- warn!(
- "got unknown eose subid {}, {} tracked subscriptions",
- subid, n_subids
- );
- return Ok(());
- };
-
- match sub_kind {
- SubKind::Timeline(_) => {
- // eose on timeline? whatevs
- }
- SubKind::Initial => {
- //let txn = Transaction::new(ctx.ndb)?;
- //unknowns::update_from_columns(
- // &txn,
- // ctx.unknown_ids,
- // timeline_cache,
- // ctx.ndb,
- // ctx.note_cache,
- //);
- //// this is possible if this is the first time
- //if ctx.unknown_ids.ready_to_send() {
- // unknown_id_send(ctx.unknown_ids, ctx.pool);
- //}
- }
-
- // oneshot subs just close when they're done
- SubKind::OneShot => {
- let msg = ClientMessage::close(subid.to_string());
- ctx.legacy_pool.send_to(&msg, relay_url);
- }
-
- SubKind::FetchingContactList(timeline_uid) => {
- let timeline = if let Some(tl) = timeline_cache.get_mut(timeline_uid) {
- tl
- } else {
- error!(
- "timeline uid:{:?} not found for FetchingContactList",
- timeline_uid
- );
- return Ok(());
- };
-
- let filter_state = timeline.filter.get_mut(relay_url);
-
- let FilterState::FetchingRemote(fetching_remote_type) = filter_state else {
- // TODO: we could have multiple contact list results, we need
- // to check to see if this one is newer and use that instead
- warn!(
- "Expected timeline to have FetchingRemote state but was {:?}",
- timeline.filter
- );
- return Ok(());
- };
-
- let new_filter_state = match fetching_remote_type {
- notedeck::filter::FetchingRemoteType::Normal(unified_subscription) => {
- FilterState::got_remote(unified_subscription.local)
- }
- notedeck::filter::FetchingRemoteType::Contact => {
- FilterState::GotRemote(notedeck::filter::GotRemoteType::Contact)
- }
- notedeck::filter::FetchingRemoteType::PeopleList => {
- FilterState::GotRemote(notedeck::filter::GotRemoteType::PeopleList)
- }
- };
-
- // We take the subscription id and pass it to the new state of
- // "GotRemote". This will let future frames know that it can try
- // to look for the contact list in nostrdb.
- timeline
- .filter
- .set_relay_state(relay_url.to_string(), new_filter_state);
- }
- }
-
- Ok(())
-}
-
-fn process_message(damus: &mut Damus, ctx: &mut AppContext<'_>, relay: &str, msg: &RelayMessage) {
- let RelayMessage::Eose(sid) = msg else {
- return;
- };
-
- if let Err(err) = handle_eose(
- &damus.subscriptions,
- &mut damus.timeline_cache,
- ctx,
- sid,
- relay,
+ app_ctx: &mut AppContext<'_>,
+) {
+ if let Err(err) = timeline::setup_initial_nostrdb_subs(
+ app_ctx.ndb,
+ app_ctx.note_cache,
+ timeline_cache,
+ app_ctx.unknown_ids,
+ *app_ctx.accounts.selected_account_pubkey(),
) {
- error!("error handling eose: {}", err);
+ warn!("update_damus init: {err}");
}
}
@@ -871,6 +759,10 @@ fn render_damus_mobile(
app_ctx.legacy_pool,
ui.ctx(),
);
+ setup_selected_account_timeline_subs(
+ &mut app.timeline_cache,
+ app_ctx,
+ );
}
ProcessNavResult::ExternalNoteAction(note_action) => {
@@ -1150,6 +1042,7 @@ fn timelines_view(
let txn = nostrdb::Transaction::new(ctx.ndb).expect("txn");
ctx.accounts
.select_account(&pubkey, ctx.ndb, &txn, ctx.legacy_pool, ui.ctx());
+ setup_selected_account_timeline_subs(&mut app.timeline_cache, ctx);
}
ProcessNavResult::ExternalNoteAction(note_action) => {
diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs
@@ -1,6 +1,6 @@
use crate::{
accounts::{render_accounts_route, AccountsAction, AccountsResponse, AccountsRoute},
- app::{get_active_columns_mut, get_decks_mut},
+ app::{get_active_columns_mut, get_decks_mut, setup_selected_account_timeline_subs},
column::ColumnsAction,
deck_state::DeckState,
decks::{Deck, DecksAction, DecksCache},
@@ -120,6 +120,8 @@ impl SwitchingAction {
decks_cache.add_deck_default(ctx, timeline_cache, switch_action.switch_to);
}
+ setup_selected_account_timeline_subs(timeline_cache, ctx);
+
// pop nav after switch
get_active_columns_mut(ctx.i18n, ctx.accounts, decks_cache)
.column_mut(switch_action.source_column)
diff --git a/crates/notedeck_columns/src/timeline/cache.rs b/crates/notedeck_columns/src/timeline/cache.rs
@@ -213,7 +213,7 @@ impl TimelineCache {
self.timelines.get_mut(id).expect("timeline inserted")
};
- if let Some(filter) = timeline.filter.get_any_ready() {
+ if let FilterState::Ready(filter) = &timeline.filter {
debug!("got open with subscription for {:?}", &timeline.kind);
timeline.subscription.try_add_local(account_pk, ndb, filter);
ensure_remote_timeline_subscription(
@@ -258,7 +258,7 @@ impl TimelineCache {
Vitality::Fresh(timeline) => (None, timeline),
};
- if let Some(filter) = timeline.filter.get_any_ready() {
+ if let FilterState::Ready(filter) = &timeline.filter {
debug!("got open with *new* subscription for {:?}", &timeline.kind);
timeline.subscription.try_add_local(account_pk, ndb, filter);
ensure_remote_timeline_subscription(
@@ -309,7 +309,7 @@ impl TimelineCache {
}
fn collect_stale_notes(timeline: &Timeline, txn: &Transaction, ndb: &Ndb) -> Vec<NoteRef> {
- let Some(filter) = timeline.filter.get_any_ready() else {
+ let FilterState::Ready(filter) = &timeline.filter else {
return Vec::new();
};
diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs
@@ -1,7 +1,6 @@
use crate::{
error::Error,
scoped_sub_owner_keys::timeline_remote_owner_key,
- subscriptions::{self, SubKind, Subscriptions},
timeline::{
kind::{people_list_note_filter, AlgoTimeline, ListKind, PeopleListRef},
note_units::InsertManyResponse,
@@ -13,14 +12,14 @@ use crate::{
use notedeck::{
contacts::hybrid_contacts_filter,
- filter::{self, HybridFilter},
+ filter::{self},
is_future_timestamp, tr, unix_time_secs, Accounts, CachedNote, ContactState, FilterError,
- FilterState, FilterStates, Localization, NoteCache, NoteRef, RelaySelection, ScopedSubApi,
- ScopedSubIdentity, SubConfig, SubKey, UnknownIds,
+ FilterState, Localization, NoteCache, NoteRef, RelaySelection, ScopedSubApi, ScopedSubIdentity,
+ SubConfig, SubKey, UnknownIds,
};
use egui_virtual_list::VirtualList;
-use enostr::{PoolRelay, Pubkey, RelayPool};
+use enostr::Pubkey;
use nostrdb::{Filter, Ndb, Note, NoteKey, Transaction};
use std::rc::Rc;
use std::{cell::RefCell, collections::HashSet};
@@ -76,15 +75,16 @@ pub(crate) fn ensure_remote_timeline_subscription(
pub(crate) fn update_remote_timeline_subscription(
timeline: &mut Timeline,
- account_pk: Pubkey,
remote_filters: Vec<Filter>,
scoped_subs: &mut ScopedSubApi<'_, '_>,
) {
- let owner = timeline_remote_owner_key(account_pk, &timeline.kind);
+ let owner = timeline_remote_owner_key(scoped_subs.selected_account_pubkey(), &timeline.kind);
let identity = ScopedSubIdentity::account(owner, timeline_remote_sub_key(&timeline.kind));
let config = timeline_remote_sub_config(remote_filters);
let _ = scoped_subs.set_sub(identity, config);
- timeline.subscription.mark_remote_seeded(account_pk);
+ timeline
+ .subscription
+ .mark_remote_seeded(scoped_subs.selected_account_pubkey());
}
pub fn drop_timeline_remote_owner(
@@ -307,7 +307,7 @@ pub struct Timeline {
pub kind: TimelineKind,
// We may not have the filter loaded yet, so let's make it an option so
// that codepaths have to explicitly handle it
- pub filter: FilterStates,
+ pub filter: FilterState,
pub views: Vec<TimelineTab>,
pub selected_view: usize,
pub seen_latest_notes: bool,
@@ -381,7 +381,6 @@ impl Timeline {
}
pub fn new(kind: TimelineKind, filter_state: FilterState, views: Vec<TimelineTab>) -> Self {
- let filter = FilterStates::new(filter_state);
let subscription = TimelineSub::default();
let selected_view = 0;
@@ -390,7 +389,7 @@ impl Timeline {
Timeline {
kind,
- filter,
+ filter: filter_state,
views,
subscription,
selected_view,
@@ -611,10 +610,7 @@ impl Timeline {
/// Note: We reset states rather than clearing them so that
/// [`Self::set_all_states`] can update them during the rebuild.
pub fn invalidate(&mut self) {
- self.filter.initial_state = FilterState::NeedsRemote;
- for state in self.filter.states.values_mut() {
- *state = FilterState::NeedsRemote;
- }
+ self.filter = FilterState::NeedsRemote;
self.contact_list_timestamp = None;
}
}
@@ -682,8 +678,6 @@ pub fn setup_new_timeline(
timeline: &mut Timeline,
ndb: &Ndb,
txn: &Transaction,
- subs: &mut Subscriptions,
- pool: &mut RelayPool,
scoped_subs: &mut ScopedSubApi<'_, '_>,
note_cache: &mut NoteCache,
since_optimize: bool,
@@ -691,6 +685,7 @@ pub fn setup_new_timeline(
unknown_ids: &mut UnknownIds,
) {
let account_pk = *accounts.selected_account_pubkey();
+
// if we're ready, setup local subs
if is_timeline_ready(ndb, scoped_subs, timeline, accounts) {
if let Err(err) =
@@ -700,59 +695,30 @@ pub fn setup_new_timeline(
}
}
- for relay in &mut pool.relays {
- send_initial_timeline_filter(since_optimize, subs, relay, timeline, accounts, scoped_subs);
- }
+ send_initial_timeline_filter(since_optimize, ndb, txn, timeline, accounts, scoped_subs);
timeline.subscription.increment(account_pk);
}
-/// Send initial filters for a specific relay. This typically gets called
-/// when we first connect to a new relay for the first time. For
-/// situations where you are adding a new timeline, use
-/// setup_new_timeline.
-#[profiling::function]
-pub fn send_initial_timeline_filters(
- since_optimize: bool,
- timeline_cache: &mut TimelineCache,
- subs: &mut Subscriptions,
- pool: &mut RelayPool,
- relay_id: &str,
- accounts: &Accounts,
- scoped_subs: &mut ScopedSubApi<'_, '_>,
-) -> Option<()> {
- info!("Sending initial filters to {}", relay_id);
- let relay = &mut pool.relays.iter_mut().find(|r| r.url() == relay_id)?;
-
- for (_kind, timeline) in timeline_cache {
- send_initial_timeline_filter(since_optimize, subs, relay, timeline, accounts, scoped_subs);
- }
-
- Some(())
-}
-
pub fn send_initial_timeline_filter(
can_since_optimize: bool,
- subs: &mut Subscriptions,
- relay: &mut PoolRelay,
+ ndb: &Ndb,
+ txn: &Transaction,
timeline: &mut Timeline,
accounts: &Accounts,
scoped_subs: &mut ScopedSubApi<'_, '_>,
) {
- let account_pk = *accounts.selected_account_pubkey();
- let filter_state = timeline.filter.get_mut(relay.url());
-
- match filter_state {
+ match &timeline.filter {
FilterState::Broken(err) => {
error!(
"FetchingRemote state in broken state when sending initial timeline filter? {err}"
);
}
- FilterState::FetchingRemote(_unisub) => {
+ FilterState::FetchingRemote => {
error!("FetchingRemote state when sending initial timeline filter?");
}
- FilterState::GotRemote(_sub) => {
+ FilterState::GotRemote => {
error!("GotRemote state when sending initial timeline filter?");
}
@@ -785,79 +751,65 @@ pub fn send_initial_timeline_filter(
filter
}).collect();
- update_remote_timeline_subscription(timeline, account_pk, new_filters, scoped_subs);
+ update_remote_timeline_subscription(timeline, new_filters, scoped_subs);
}
// we need some data first
- FilterState::NeedsRemote => {
- let people_list_ref = match &timeline.kind {
- TimelineKind::List(ListKind::PeopleList(plr))
- | TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::PeopleList(plr))) => {
- Some(plr.clone())
- }
- _ => None,
- };
- if let Some(plr) = people_list_ref {
- fetch_people_list(subs, relay, timeline, &plr);
- } else {
- fetch_contact_list(subs, timeline, accounts);
+ FilterState::NeedsRemote => match &timeline.kind {
+ TimelineKind::List(ListKind::PeopleList(_))
+ | TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::PeopleList(_))) => {
+ fetch_people_list(ndb, txn, timeline);
}
- }
+ _ => fetch_contact_list(timeline, accounts),
+ },
}
}
-pub fn fetch_contact_list(subs: &mut Subscriptions, timeline: &mut Timeline, accounts: &Accounts) {
- if timeline.filter.get_any_ready().is_some() {
+pub fn fetch_contact_list(timeline: &mut Timeline, accounts: &Accounts) {
+ if matches!(&timeline.filter, FilterState::Ready(_)) {
return;
}
let new_filter_state = match accounts.get_selected_account().data.contacts.get_state() {
- ContactState::Unreceived => {
- FilterState::FetchingRemote(filter::FetchingRemoteType::Contact)
- }
+ ContactState::Unreceived => FilterState::FetchingRemote,
ContactState::Received {
contacts: _,
note_key: _,
timestamp: _,
- } => FilterState::GotRemote(filter::GotRemoteType::Contact),
+ } => FilterState::GotRemote,
};
- timeline.filter.set_all_states(new_filter_state);
+ timeline.filter = new_filter_state;
+}
- let sub = &accounts.get_subs().contacts;
- if subs.subs.contains_key(&sub.remote) {
+pub fn fetch_people_list(ndb: &Ndb, txn: &Transaction, timeline: &mut Timeline) {
+ if matches!(&timeline.filter, FilterState::Ready(_)) {
return;
}
- let sub_kind = SubKind::FetchingContactList(timeline.kind.clone());
- subs.subs.insert(sub.remote.clone(), sub_kind);
-}
-
-pub fn fetch_people_list(
- subs: &mut Subscriptions,
- relay: &mut PoolRelay,
- timeline: &mut Timeline,
- plr: &PeopleListRef,
-) {
- if timeline.filter.get_any_ready().is_some() {
+ let Some(plr) = people_list_ref(&timeline.kind) else {
+ error!("fetch_people_list called for non-people-list timeline");
+ timeline.filter = FilterState::broken(FilterError::EmptyList);
return;
- }
+ };
let filter = people_list_note_filter(plr);
- let sub_id = subscriptions::new_sub_id();
- if let Err(err) = relay.subscribe(sub_id.clone(), vec![filter]) {
- error!("error subscribing for people list: {err}");
+ let results = match ndb.query(txn, std::slice::from_ref(&filter), 1) {
+ Ok(results) => results,
+ Err(err) => {
+ error!("people list query failed in fetch_people_list: {err}");
+ timeline.filter = FilterState::broken(FilterError::EmptyList);
+ return;
+ }
+ };
+
+ if results.is_empty() {
+ timeline.filter = FilterState::FetchingRemote;
return;
}
- timeline.filter.set_relay_state(
- relay.url().to_string(),
- FilterState::FetchingRemote(filter::FetchingRemoteType::PeopleList),
- );
-
- let sub_kind = SubKind::FetchingContactList(timeline.kind.clone());
- subs.subs.insert(sub_id, sub_kind);
+ timeline.filter = FilterState::GotRemote;
}
#[profiling::function]
@@ -867,9 +819,12 @@ fn setup_initial_timeline(
timeline: &mut Timeline,
note_cache: &mut NoteCache,
unknown_ids: &mut UnknownIds,
- filters: &HybridFilter,
account_pk: Pubkey,
) -> Result<()> {
+ let FilterState::Ready(filters) = &timeline.filter else {
+ return Err(Error::App(notedeck::Error::empty_contact_list()));
+ };
+
// some timelines are one-shot and a refreshed, like last_per_pubkey algo feed
if timeline.kind.should_subscribe_locally() {
timeline
@@ -948,21 +903,7 @@ fn setup_timeline_nostrdb_sub(
unknown_ids: &mut UnknownIds,
account_pk: Pubkey,
) -> Result<()> {
- let filter_state = timeline
- .filter
- .get_any_ready()
- .ok_or(Error::App(notedeck::Error::empty_contact_list()))?
- .to_owned();
-
- setup_initial_timeline(
- ndb,
- txn,
- timeline,
- note_cache,
- unknown_ids,
- &filter_state,
- account_pk,
- )?;
+ setup_initial_timeline(ndb, txn, timeline, note_cache, unknown_ids, account_pk)?;
Ok(())
}
@@ -980,43 +921,24 @@ pub fn is_timeline_ready(
) -> bool {
// TODO: we should debounce the filter states a bit to make sure we have
// seen all of the different contact lists from each relay
- if let Some(filter) = timeline.filter.get_any_ready() {
+ if let FilterState::Ready(filter) = &timeline.filter {
let account_pk = *accounts.selected_account_pubkey();
+ let remote_filters = filter.remote().to_vec();
if timeline.subscription.dependers(&account_pk) > 0
&& !timeline.subscription.remote_seeded(&account_pk)
{
- ensure_remote_timeline_subscription(
- timeline,
- account_pk,
- filter.remote().to_vec(),
- scoped_subs,
- );
+ ensure_remote_timeline_subscription(timeline, account_pk, remote_filters, scoped_subs);
}
return true;
}
- let Some(res) = timeline.filter.get_any_gotremote() else {
+ if !matches!(&timeline.filter, FilterState::GotRemote) {
return false;
- };
-
- let (relay_id, note_key) = match res {
- filter::GotRemoteResult::Normal { relay_id, sub_id } => {
- // We got at least one eose for our filter request. Let's see
- // if nostrdb is done processing it yet.
- let res = ndb.poll_for_notes(sub_id, 1);
- if res.is_empty() {
- debug!(
- "check_timeline_filter_state: no notes found (yet?) for timeline {:?}",
- timeline
- );
- return false;
- }
-
- info!("notes found for contact timeline after GotRemote!");
+ }
- (relay_id, res[0])
- }
- filter::GotRemoteResult::Contact { relay_id } => {
+ let note_key = match &timeline.kind {
+ TimelineKind::List(ListKind::Contact(_))
+ | TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::Contact(_))) => {
let ContactState::Received {
contacts: _,
note_key,
@@ -1026,20 +948,10 @@ pub fn is_timeline_ready(
return false;
};
- (relay_id, *note_key)
+ *note_key
}
- filter::GotRemoteResult::PeopleList { relay_id } => {
- // Query ndb directly for the kind 30000 note. It should
- // have been ingested from the relay by now.
- let plr = match &timeline.kind {
- TimelineKind::List(ListKind::PeopleList(plr))
- | TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::PeopleList(plr))) => plr,
- _ => {
- error!("GotRemoteResult::PeopleList but timeline kind is not PeopleList");
- return false;
- }
- };
-
+ TimelineKind::List(ListKind::PeopleList(plr))
+ | TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::PeopleList(plr))) => {
let list_filter = people_list_note_filter(plr);
let txn = Transaction::new(ndb).expect("txn");
let results = match ndb.query(&txn, std::slice::from_ref(&list_filter), 1) {
@@ -1056,8 +968,9 @@ pub fn is_timeline_ready(
}
info!("found people list note after GotRemote!");
- (relay_id, results[0].note_key)
+ results[0].note_key
}
+ _ => return false,
};
let with_hashtags = false;
@@ -1074,34 +987,36 @@ pub fn is_timeline_ready(
match filter {
Err(notedeck::Error::Filter(e)) => {
error!("got broken when building filter {e}");
- timeline
- .filter
- .set_relay_state(relay_id, FilterState::broken(e));
+ timeline.filter = FilterState::broken(e);
false
}
Err(err) => {
error!("got broken when building filter {err}");
- timeline
- .filter
- .set_relay_state(relay_id, FilterState::broken(FilterError::EmptyList));
+ let reason = match &timeline.kind {
+ TimelineKind::List(ListKind::PeopleList(_))
+ | TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::PeopleList(_))) => {
+ FilterError::EmptyList
+ }
+ _ => FilterError::EmptyContactList,
+ };
+ timeline.filter = FilterState::broken(reason);
false
}
Ok(filter) => {
// We just switched to the ready state; remote subscriptions can start now.
- info!("Found contact list! Setting up remote contact list query");
- timeline
- .filter
- .set_relay_state(relay_id, FilterState::ready_hybrid(filter.clone()));
-
- //let ck = &timeline.kind;
- //let subid = damus.gen_subid(&SubKind::Column(ck.clone()));
- update_remote_timeline_subscription(
- timeline,
- *accounts.selected_account_pubkey(),
- filter.remote().to_vec(),
- scoped_subs,
- );
+ info!("Found list note! Setting up remote timeline query");
+ timeline.filter = FilterState::ready_hybrid(filter.clone());
+
+ update_remote_timeline_subscription(timeline, filter.remote().to_vec(), scoped_subs);
true
}
}
}
+
+fn people_list_ref(kind: &TimelineKind) -> Option<&PeopleListRef> {
+ match kind {
+ TimelineKind::List(ListKind::PeopleList(plr))
+ | TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::PeopleList(plr))) => Some(plr),
+ _ => None,
+ }
+}
diff --git a/crates/notedeck_columns/src/ui/add_column.rs b/crates/notedeck_columns/src/ui/add_column.rs
@@ -924,8 +924,6 @@ fn attach_timeline_column(
&mut timeline,
ctx.ndb,
&txn,
- &mut app.subscriptions,
- ctx.legacy_pool,
&mut scoped_subs,
ctx.note_cache,
app.options.contains(AppOptions::SinceOptimize),
@@ -1146,13 +1144,12 @@ fn handle_create_people_list(app: &mut Damus, ctx: &mut AppContext<'_>, col: usi
return;
};
+ let mut scoped_subs = ctx.remote.scoped_subs(ctx.accounts);
crate::timeline::setup_new_timeline(
&mut timeline,
ctx.ndb,
&txn,
- &mut app.subscriptions,
- ctx.legacy_pool,
- &mut ctx.remote.scoped_subs(ctx.accounts),
+ &mut scoped_subs,
ctx.note_cache,
app.options.contains(AppOptions::SinceOptimize),
ctx.accounts,