Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions crates/notedeck_chrome/src/notedeck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ pub fn main() {
#[cfg(test)]
mod tests {
use super::{Damus, Notedeck};
use notedeck_columns::timeline::TimelineKind;
use std::path::{Path, PathBuf};

fn create_tmp_dir() -> PathBuf {
Expand All @@ -157,11 +158,11 @@ mod tests {
let datapath = create_tmp_dir();
let dbpath = create_tmp_dir();
let args: Vec<String> = [
"--testrunner",
"--datapath",
&datapath.to_str().unwrap(),
"--dbpath",
&dbpath.to_str().unwrap(),
"notedeck",
"--datadir",
datapath.to_str().unwrap(),
"--db",
dbpath.to_str().unwrap(),
]
.iter()
.map(|s| s.to_string())
Expand Down Expand Up @@ -232,8 +233,11 @@ mod tests {
.unwrap();

assert_eq!(app.timeline_cache.timelines.len(), 2);
assert!(app.timeline_cache.timelines.get(&tl1).is_some());
assert!(app.timeline_cache.timelines.get(&tl2).is_some());
assert!(app.timeline_cache.timelines.contains_key(tl1));
assert!(app.timeline_cache.timelines.contains_key(tl2));
let mut keys: Vec<TimelineKind> = app.timeline_cache.timelines.keys().cloned().collect();
keys.sort();
assert_eq!(keys, vec![tl1.clone(), tl2.clone()]);

rmrf(tmpdir);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/notedeck_columns/src/accounts/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ mod tests {
let data_str = "accounts:show";
let data = &data_str.split(":").collect::<Vec<&str>>();
let mut token_writer = TokenWriter::default();
let mut parser = TokenParser::new(&data);
let mut parser = TokenParser::new(data);
let parsed = AccountsRoute::parse_from_tokens(&mut parser).unwrap();
let expected = AccountsRoute::Accounts;
parsed.serialize_tokens(&mut token_writer);
Expand Down
8 changes: 4 additions & 4 deletions crates/notedeck_columns/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,16 @@ fn try_process_event(
if is_ready {
let txn = Transaction::new(app_ctx.ndb).expect("txn");
// only thread timelines are reversed
let reversed = false;
//let reversed = false; // No longer needed here

if let Err(err) = timeline.poll_notes_into_view(
if let Err(err) = timeline.poll_notes_into_pending(
app_ctx.ndb,
&txn,
app_ctx.unknown_ids,
app_ctx.note_cache,
reversed,
//reversed, // Removed
) {
error!("poll_notes_into_view: {err}");
error!("poll_notes_into_pending: {err}");
}
} else {
// TODO: show loading?
Expand Down
2 changes: 1 addition & 1 deletion crates/notedeck_columns/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ mod tests {
let data_str = format!("thread:{}", note_id_hex);
let data = &data_str.split(":").collect::<Vec<&str>>();
let mut token_writer = TokenWriter::default();
let mut parser = TokenParser::new(&data);
let mut parser = TokenParser::new(data);
let parsed = Route::parse(&mut parser, &Pubkey::new(*note_id.bytes())).unwrap();
let expected = Route::Thread(ThreadSelection::from_root_id(RootNoteIdBuf::new_unsafe(
*note_id.bytes(),
Expand Down
2 changes: 1 addition & 1 deletion crates/notedeck_columns/src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use nostrdb::{Filter, FilterBuilder};
use rmpv::Value;
use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter};

#[derive(Debug, Eq, PartialEq, Clone, Hash)]
#[derive(Debug, Eq, PartialEq, Clone, Hash, PartialOrd, Ord)]
pub struct SearchQuery {
author: Option<Pubkey>,
pub search: String,
Expand Down
8 changes: 4 additions & 4 deletions crates/notedeck_columns/src/timeline/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ use std::{borrow::Cow, fmt::Display};
use tokenator::{ParseError, TokenParser, TokenSerializable, TokenWriter};
use tracing::{error, warn};

#[derive(Clone, Hash, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
pub enum PubkeySource {
Explicit(Pubkey),
#[default]
DeckAuthor,
}

#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
pub enum ListKind {
Contact(Pubkey),
}
Expand Down Expand Up @@ -195,7 +195,7 @@ impl Eq for ThreadSelection {}
/// - filter
/// - ... etc
///
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TimelineKind {
List(ListKind),

Expand All @@ -220,7 +220,7 @@ const NOTIFS_TOKEN_DEPRECATED: &str = "notifs";
const NOTIFS_TOKEN: &str = "notifications";

/// Hardcoded algo timelines
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AlgoTimeline {
/// LastPerPubkey: a special nostr query that fetches the last N
/// notes for each pubkey on the list
Expand Down
124 changes: 110 additions & 14 deletions crates/notedeck_columns/src/timeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use egui_virtual_list::VirtualList;
use enostr::{PoolRelay, Pubkey, RelayPool};
use nostrdb::{Filter, Ndb, Note, NoteKey, Transaction};
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;

use tracing::{debug, error, info, warn};
Expand Down Expand Up @@ -196,6 +197,8 @@ pub struct Timeline {
pub filter: FilterStates,
pub views: Vec<TimelineTab>,
pub selected_view: usize,
/// Notes polled from the database but not yet shown in the UI.
pub pending_notes: Vec<NoteRef>,

pub subscription: Option<MultiSubscriber>,
}
Expand Down Expand Up @@ -255,16 +258,13 @@ impl Timeline {
}

pub fn new(kind: TimelineKind, filter_state: FilterState, views: Vec<TimelineTab>) -> Self {
let filter = FilterStates::new(filter_state);
let subscription: Option<MultiSubscriber> = None;
let selected_view = 0;

Timeline {
Self {
kind,
filter,
filter: FilterStates::new(filter_state),
views,
subscription,
selected_view,
selected_view: 0,
pending_notes: Vec::new(),
subscription: None,
}
}

Expand Down Expand Up @@ -389,17 +389,18 @@ impl Timeline {
Ok(())
}

pub fn poll_notes_into_view(
/// Adds newly polled notes to the `pending_notes` list.
/// Returns true if new notes were added.
pub fn poll_notes_into_pending(
&mut self,
ndb: &Ndb,
txn: &Transaction,
unknown_ids: &mut UnknownIds,
note_cache: &mut NoteCache,
reversed: bool,
) -> Result<()> {
) -> Result<bool> {
if !self.kind.should_subscribe_locally() {
// don't need to poll for timelines that don't have local subscriptions
return Ok(());
return Ok(false);
}

let sub = self
Expand All @@ -410,12 +411,107 @@ impl Timeline {

let new_note_ids = ndb.poll_for_notes(sub, 500);
if new_note_ids.is_empty() {
return Ok(());
return Ok(false);
} else {
debug!("{} new notes! {:?}", new_note_ids.len(), new_note_ids);
}

self.insert(&new_note_ids, ndb, txn, unknown_ids, note_cache, reversed)
// Fetch NoteRefs for the new NoteKeys and add to pending_notes
let mut new_refs: Vec<NoteRef> = Vec::with_capacity(new_note_ids.len());
for key in new_note_ids {
let note = match ndb.get_note_by_key(txn, key) {
Ok(note) => note,
Err(_) => {
error!(
"hit race condition in poll_notes_into_pending: note {:?} not found",
key
);
continue;
}
};

// Ensure that unknown ids are captured (needed for profile info etc.)
UnknownIds::update_from_note(txn, ndb, unknown_ids, note_cache, &note);

let created_at = note.created_at();
new_refs.push(NoteRef { key, created_at });
}

// Add to pending, ensuring no duplicates and maintaining order (newest first)
// We assume poll_for_notes returns newest first.
let mut existing_pending_keys: HashSet<_> =
self.pending_notes.iter().map(|nr| nr.key).collect();
let mut added = false;
for new_ref in new_refs.into_iter().rev() {
// Iterate reversed to prepend correctly
if existing_pending_keys.insert(new_ref.key) {
self.pending_notes.insert(0, new_ref); // Prepend to keep newest first
added = true;
}
}

Ok(added)
}

/// Applies the notes currently in `pending_notes` to the visible timeline views.
pub fn apply_pending_notes(
&mut self,
ndb: &Ndb,
txn: &Transaction,
unknown_ids: &mut UnknownIds,
note_cache: &mut NoteCache,
) -> Result<()> {
if self.pending_notes.is_empty() {
return Ok(());
}

let notes_to_apply = std::mem::take(&mut self.pending_notes);
let reversed = false; // Apply reversed logic if needed

// Fetch full notes for applying filters (slightly inefficient but necessary for Notes filter)
let mut fetched_notes: Vec<(Note, NoteRef)> = Vec::with_capacity(notes_to_apply.len());
for key_ref in &notes_to_apply {
let note = match ndb.get_note_by_key(txn, key_ref.key) {
Ok(note) => note,
Err(_) => {
// Note might have been deleted between polling and applying
error!(
"Failed to fetch note {:?} while applying pending notes",
key_ref.key
);
continue;
}
};
// Re-check unknown IDs just in case?
UnknownIds::update_from_note(txn, ndb, unknown_ids, note_cache, &note);
fetched_notes.push((note, *key_ref));
}

for view in &mut self.views {
match view.filter {
ViewFilter::NotesAndReplies => {
// For this view, we just need the NoteRefs
let refs_to_insert: Vec<NoteRef> = notes_to_apply.to_vec();
if !refs_to_insert.is_empty() {
view.insert(&refs_to_insert, reversed);
}
}
ViewFilter::Notes => {
let mut filtered_refs = Vec::with_capacity(fetched_notes.len());
for (note, nr) in &fetched_notes {
let cached_note = note_cache.cached_note_or_insert(nr.key, note);
if ViewFilter::filter_notes(cached_note, note) {
filtered_refs.push(*nr);
}
}
if !filtered_refs.is_empty() {
view.insert(&filtered_refs, reversed);
}
}
}
}

Ok(())
}
}

Expand Down
2 changes: 0 additions & 2 deletions crates/notedeck_columns/src/timeline/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ pub fn render_timeline_route(
note_context,
note_options,
&accounts.get_selected_account().map(|a| (&a.key).into()),
jobs,
)
.ui(ui);

Expand Down Expand Up @@ -65,7 +64,6 @@ pub fn render_timeline_route(
note_context,
note_options,
&accounts.get_selected_account().map(|a| (&a.key).into()),
jobs,
)
.ui(ui);

Expand Down
2 changes: 1 addition & 1 deletion crates/notedeck_columns/src/ui/add_column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ mod tests {
let data_str = "column:algo_selection:last_per_pubkey";
let data = &data_str.split(":").collect::<Vec<&str>>();
let mut token_writer = TokenWriter::default();
let mut parser = TokenParser::new(&data);
let mut parser = TokenParser::new(data);
let parsed = AddColumnRoute::parse_from_tokens(&mut parser).unwrap();
let expected = AddColumnRoute::Algo(AddAlgoRoute::LastPerPubkey);
parsed.serialize_tokens(&mut token_writer);
Expand Down
Loading