@@ -1,9 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
multi_subscriber::MultiSubscriber,
|
|
||||||
note::NoteRef,
|
note::NoteRef,
|
||||||
notecache::NoteCache,
|
notecache::NoteCache,
|
||||||
|
notes_holder::{NotesHolder, NotesHolderStorage},
|
||||||
route::{Route, Router},
|
route::{Route, Router},
|
||||||
thread::{Thread, ThreadResult, Threads},
|
thread::Thread,
|
||||||
};
|
};
|
||||||
use enostr::{NoteId, Pubkey, RelayPool};
|
use enostr::{NoteId, Pubkey, RelayPool};
|
||||||
use nostrdb::{Ndb, Transaction};
|
use nostrdb::{Ndb, Transaction};
|
||||||
@@ -21,13 +21,13 @@ pub struct TimelineResponse {
|
|||||||
pub open_profile: Option<Pubkey>,
|
pub open_profile: Option<Pubkey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewThreadNotes {
|
pub struct NewNotes {
|
||||||
pub root_id: NoteId,
|
pub id: [u8; 32],
|
||||||
pub notes: Vec<NoteRef>,
|
pub notes: Vec<NoteRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum BarResult {
|
pub enum NotesHolderResult {
|
||||||
NewThreadNotes(NewThreadNotes),
|
NewNotes(NewNotes),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// open_thread is called when a note is selected and we need to navigate
|
/// open_thread is called when a note is selected and we need to navigate
|
||||||
@@ -41,51 +41,13 @@ fn open_thread(
|
|||||||
router: &mut Router<Route>,
|
router: &mut Router<Route>,
|
||||||
note_cache: &mut NoteCache,
|
note_cache: &mut NoteCache,
|
||||||
pool: &mut RelayPool,
|
pool: &mut RelayPool,
|
||||||
threads: &mut Threads,
|
threads: &mut NotesHolderStorage<Thread>,
|
||||||
selected_note: &[u8; 32],
|
selected_note: &[u8; 32],
|
||||||
) -> Option<BarResult> {
|
) -> Option<NotesHolderResult> {
|
||||||
router.route_to(Route::thread(NoteId::new(selected_note.to_owned())));
|
router.route_to(Route::thread(NoteId::new(selected_note.to_owned())));
|
||||||
|
|
||||||
let root_id = crate::note::root_note_id_from_selected_id(ndb, note_cache, txn, selected_note);
|
let root_id = crate::note::root_note_id_from_selected_id(ndb, note_cache, txn, selected_note);
|
||||||
let thread_res = threads.thread_mut(ndb, txn, root_id);
|
Thread::open(ndb, txn, pool, threads, root_id)
|
||||||
|
|
||||||
let (thread, result) = match thread_res {
|
|
||||||
ThreadResult::Stale(thread) => {
|
|
||||||
// The thread is stale, let's update it
|
|
||||||
let notes = Thread::new_notes(&thread.view().notes, root_id, txn, ndb);
|
|
||||||
let bar_result = if notes.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(BarResult::new_thread_notes(
|
|
||||||
notes,
|
|
||||||
NoteId::new(root_id.to_owned()),
|
|
||||||
))
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// we can't insert and update the VirtualList now, because we
|
|
||||||
// are already borrowing it mutably. Let's pass it as a
|
|
||||||
// result instead
|
|
||||||
//
|
|
||||||
// thread.view.insert(¬es); <-- no
|
|
||||||
//
|
|
||||||
(thread, bar_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadResult::Fresh(thread) => (thread, None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let multi_subscriber = if let Some(multi_subscriber) = &mut thread.multi_subscriber {
|
|
||||||
multi_subscriber
|
|
||||||
} else {
|
|
||||||
let filters = Thread::filters(root_id);
|
|
||||||
thread.multi_subscriber = Some(MultiSubscriber::new(filters));
|
|
||||||
thread.multi_subscriber.as_mut().unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
multi_subscriber.subscribe(ndb, pool);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BarAction {
|
impl BarAction {
|
||||||
@@ -94,11 +56,11 @@ impl BarAction {
|
|||||||
self,
|
self,
|
||||||
ndb: &Ndb,
|
ndb: &Ndb,
|
||||||
router: &mut Router<Route>,
|
router: &mut Router<Route>,
|
||||||
threads: &mut Threads,
|
threads: &mut NotesHolderStorage<Thread>,
|
||||||
note_cache: &mut NoteCache,
|
note_cache: &mut NoteCache,
|
||||||
pool: &mut RelayPool,
|
pool: &mut RelayPool,
|
||||||
txn: &Transaction,
|
txn: &Transaction,
|
||||||
) -> Option<BarResult> {
|
) -> Option<NotesHolderResult> {
|
||||||
match self {
|
match self {
|
||||||
BarAction::Reply(note_id) => {
|
BarAction::Reply(note_id) => {
|
||||||
router.route_to(Route::reply(note_id));
|
router.route_to(Route::reply(note_id));
|
||||||
@@ -123,7 +85,7 @@ impl BarAction {
|
|||||||
self,
|
self,
|
||||||
ndb: &Ndb,
|
ndb: &Ndb,
|
||||||
router: &mut Router<Route>,
|
router: &mut Router<Route>,
|
||||||
threads: &mut Threads,
|
threads: &mut NotesHolderStorage<Thread>,
|
||||||
note_cache: &mut NoteCache,
|
note_cache: &mut NoteCache,
|
||||||
pool: &mut RelayPool,
|
pool: &mut RelayPool,
|
||||||
txn: &Transaction,
|
txn: &Transaction,
|
||||||
@@ -134,34 +96,39 @@ impl BarAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BarResult {
|
impl NotesHolderResult {
|
||||||
pub fn new_thread_notes(notes: Vec<NoteRef>, root_id: NoteId) -> Self {
|
pub fn new_notes(notes: Vec<NoteRef>, id: [u8; 32]) -> Self {
|
||||||
BarResult::NewThreadNotes(NewThreadNotes::new(notes, root_id))
|
NotesHolderResult::NewNotes(NewNotes::new(notes, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process(&self, ndb: &Ndb, txn: &Transaction, threads: &mut Threads) {
|
pub fn process<N: NotesHolder>(
|
||||||
|
&self,
|
||||||
|
ndb: &Ndb,
|
||||||
|
txn: &Transaction,
|
||||||
|
storage: &mut NotesHolderStorage<N>,
|
||||||
|
) {
|
||||||
match self {
|
match self {
|
||||||
// update the thread for next render if we have new notes
|
// update the thread for next render if we have new notes
|
||||||
BarResult::NewThreadNotes(new_notes) => {
|
NotesHolderResult::NewNotes(new_notes) => {
|
||||||
let thread = threads
|
let holder = storage
|
||||||
.thread_mut(ndb, txn, new_notes.root_id.bytes())
|
.notes_holder_mutated(ndb, txn, &new_notes.id)
|
||||||
.get_ptr();
|
.get_ptr();
|
||||||
new_notes.process(thread);
|
new_notes.process(holder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NewThreadNotes {
|
impl NewNotes {
|
||||||
pub fn new(notes: Vec<NoteRef>, root_id: NoteId) -> Self {
|
pub fn new(notes: Vec<NoteRef>, id: [u8; 32]) -> Self {
|
||||||
NewThreadNotes { notes, root_id }
|
NewNotes { notes, id }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simple helper for processing a NewThreadNotes result. It simply
|
/// Simple helper for processing a NewThreadNotes result. It simply
|
||||||
/// inserts/merges the notes into the thread cache
|
/// inserts/merges the notes into the thread cache
|
||||||
pub fn process(&self, thread: &mut Thread) {
|
pub fn process<N: NotesHolder>(&self, thread: &mut N) {
|
||||||
// threads are chronological, ie reversed from reverse-chronological, the default.
|
// threads are chronological, ie reversed from reverse-chronological, the default.
|
||||||
let reversed = true;
|
let reversed = true;
|
||||||
thread.view_mut().insert(&self.notes, reversed);
|
thread.get_view().insert(&self.notes, reversed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/app.rs
13
src/app.rs
@@ -13,8 +13,9 @@ use crate::{
|
|||||||
nav,
|
nav,
|
||||||
note::NoteRef,
|
note::NoteRef,
|
||||||
notecache::{CachedNote, NoteCache},
|
notecache::{CachedNote, NoteCache},
|
||||||
|
notes_holder::NotesHolderStorage,
|
||||||
subscriptions::{SubKind, Subscriptions},
|
subscriptions::{SubKind, Subscriptions},
|
||||||
thread::Threads,
|
thread::Thread,
|
||||||
timeline::{Timeline, TimelineId, TimelineKind, ViewFilter},
|
timeline::{Timeline, TimelineId, TimelineKind, ViewFilter},
|
||||||
ui::{self, DesktopSidePanel},
|
ui::{self, DesktopSidePanel},
|
||||||
unknowns::UnknownIds,
|
unknowns::UnknownIds,
|
||||||
@@ -53,7 +54,7 @@ pub struct Damus {
|
|||||||
pub view_state: ViewState,
|
pub view_state: ViewState,
|
||||||
pub unknown_ids: UnknownIds,
|
pub unknown_ids: UnknownIds,
|
||||||
pub drafts: Drafts,
|
pub drafts: Drafts,
|
||||||
pub threads: Threads,
|
pub threads: NotesHolderStorage<Thread>,
|
||||||
pub img_cache: ImageCache,
|
pub img_cache: ImageCache,
|
||||||
pub accounts: AccountManager,
|
pub accounts: AccountManager,
|
||||||
pub subscriptions: Subscriptions,
|
pub subscriptions: Subscriptions,
|
||||||
@@ -709,7 +710,7 @@ impl Damus {
|
|||||||
unknown_ids: UnknownIds::default(),
|
unknown_ids: UnknownIds::default(),
|
||||||
subscriptions: Subscriptions::default(),
|
subscriptions: Subscriptions::default(),
|
||||||
since_optimize: parsed_args.since_optimize,
|
since_optimize: parsed_args.since_optimize,
|
||||||
threads: Threads::default(),
|
threads: NotesHolderStorage::default(),
|
||||||
drafts: Drafts::default(),
|
drafts: Drafts::default(),
|
||||||
state: DamusState::Initializing,
|
state: DamusState::Initializing,
|
||||||
img_cache: ImageCache::new(imgcache_dir.into()),
|
img_cache: ImageCache::new(imgcache_dir.into()),
|
||||||
@@ -790,7 +791,7 @@ impl Damus {
|
|||||||
unknown_ids: UnknownIds::default(),
|
unknown_ids: UnknownIds::default(),
|
||||||
subscriptions: Subscriptions::default(),
|
subscriptions: Subscriptions::default(),
|
||||||
since_optimize: true,
|
since_optimize: true,
|
||||||
threads: Threads::default(),
|
threads: NotesHolderStorage::default(),
|
||||||
drafts: Drafts::default(),
|
drafts: Drafts::default(),
|
||||||
state: DamusState::Initializing,
|
state: DamusState::Initializing,
|
||||||
pool: RelayPool::new(),
|
pool: RelayPool::new(),
|
||||||
@@ -817,11 +818,11 @@ impl Damus {
|
|||||||
&mut self.unknown_ids
|
&mut self.unknown_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn threads(&self) -> &Threads {
|
pub fn threads(&self) -> &NotesHolderStorage<Thread> {
|
||||||
&self.threads
|
&self.threads
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn threads_mut(&mut self) -> &mut Threads {
|
pub fn threads_mut(&mut self) -> &mut NotesHolderStorage<Thread> {
|
||||||
&mut self.threads
|
&mut self.threads
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub mod ui;
|
|||||||
mod unknowns;
|
mod unknowns;
|
||||||
mod user_account;
|
mod user_account;
|
||||||
mod view_state;
|
mod view_state;
|
||||||
|
mod notes_holder;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|||||||
22
src/nav.rs
22
src/nav.rs
@@ -2,9 +2,10 @@ use crate::{
|
|||||||
account_manager::render_accounts_route,
|
account_manager::render_accounts_route,
|
||||||
app_style::{get_font_size, NotedeckTextStyle},
|
app_style::{get_font_size, NotedeckTextStyle},
|
||||||
fonts::NamedFontFamily,
|
fonts::NamedFontFamily,
|
||||||
|
notes_holder::NotesHolder,
|
||||||
relay_pool_manager::RelayPoolManager,
|
relay_pool_manager::RelayPoolManager,
|
||||||
route::Route,
|
route::Route,
|
||||||
thread::thread_unsubscribe,
|
thread::Thread,
|
||||||
timeline::{
|
timeline::{
|
||||||
route::{render_profile_route, render_timeline_route, AfterRouteExecution, TimelineRoute},
|
route::{render_profile_route, render_timeline_route, AfterRouteExecution, TimelineRoute},
|
||||||
PubkeySource, Timeline, TimelineKind,
|
PubkeySource, Timeline, TimelineKind,
|
||||||
@@ -21,7 +22,7 @@ use crate::{
|
|||||||
|
|
||||||
use egui::{pos2, Color32, InnerResponse, Stroke};
|
use egui::{pos2, Color32, InnerResponse, Stroke};
|
||||||
use egui_nav::{Nav, NavAction, TitleBarResponse};
|
use egui_nav::{Nav, NavAction, TitleBarResponse};
|
||||||
use nostrdb::Ndb;
|
use nostrdb::{Ndb, Transaction};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
|
pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
|
||||||
@@ -163,13 +164,16 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
|
|||||||
if let Some(NavAction::Returned) = nav_response.action {
|
if let Some(NavAction::Returned) = nav_response.action {
|
||||||
let r = app.columns_mut().column_mut(col).router_mut().pop();
|
let r = app.columns_mut().column_mut(col).router_mut().pop();
|
||||||
if let Some(Route::Timeline(TimelineRoute::Thread(id))) = r {
|
if let Some(Route::Timeline(TimelineRoute::Thread(id))) = r {
|
||||||
thread_unsubscribe(
|
let txn = Transaction::new(&app.ndb).expect("txn");
|
||||||
&app.ndb,
|
let root_id = {
|
||||||
&mut app.threads,
|
crate::note::root_note_id_from_selected_id(
|
||||||
&mut app.pool,
|
&app.ndb,
|
||||||
&mut app.note_cache,
|
&mut app.note_cache,
|
||||||
id.bytes(),
|
&txn,
|
||||||
);
|
id.bytes(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Thread::unsubscribe_locally(&txn, &app.ndb, &mut app.threads, &mut app.pool, root_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(Route::Profile(_, id)) = r {
|
if let Some(Route::Profile(_, id)) = r {
|
||||||
|
|||||||
189
src/notes_holder.rs
Normal file
189
src/notes_holder.rs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use enostr::{Filter, RelayPool};
|
||||||
|
use nostrdb::{Ndb, Transaction};
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
actionbar::NotesHolderResult, multi_subscriber::MultiSubscriber, note::NoteRef,
|
||||||
|
timeline::TimelineTab, Error, Result,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NotesHolderStorage<M: NotesHolder> {
|
||||||
|
pub id_to_object: HashMap<[u8; 32], M>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Vitality<'a, M> {
|
||||||
|
Fresh(&'a mut M),
|
||||||
|
Stale(&'a mut M),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, M> Vitality<'a, M> {
|
||||||
|
pub fn get_ptr(self) -> &'a mut M {
|
||||||
|
match self {
|
||||||
|
Self::Fresh(ptr) => ptr,
|
||||||
|
Self::Stale(ptr) => ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stale(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Fresh(_ptr) => false,
|
||||||
|
Self::Stale(_ptr) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: NotesHolder> NotesHolderStorage<M> {
|
||||||
|
pub fn notes_holder_expected_mut(&mut self, id: &[u8; 32]) -> &mut M {
|
||||||
|
self.id_to_object
|
||||||
|
.get_mut(id)
|
||||||
|
.expect("thread_expected_mut used but there was no thread")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notes_holder_mutated<'a>(
|
||||||
|
&'a mut self,
|
||||||
|
ndb: &Ndb,
|
||||||
|
txn: &Transaction,
|
||||||
|
id: &[u8; 32],
|
||||||
|
) -> Vitality<'a, M> {
|
||||||
|
// we can't use the naive hashmap entry API here because lookups
|
||||||
|
// require a copy, wait until we have a raw entry api. We could
|
||||||
|
// also use hashbrown?
|
||||||
|
|
||||||
|
if self.id_to_object.contains_key(id) {
|
||||||
|
return Vitality::Stale(self.notes_holder_expected_mut(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't have the note holder, query for it!
|
||||||
|
let filters = M::filters(id);
|
||||||
|
|
||||||
|
let notes = if let Ok(results) = ndb.query(txn, &filters, 1000) {
|
||||||
|
results
|
||||||
|
.into_iter()
|
||||||
|
.map(NoteRef::from_query_result)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
debug!("got no results from thread lookup for {}", hex::encode(id));
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
if notes.is_empty() {
|
||||||
|
warn!("thread query returned 0 notes? ")
|
||||||
|
} else {
|
||||||
|
debug!("found thread with {} notes", notes.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.id_to_object
|
||||||
|
.insert(id.to_owned(), M::new_notes_holder(notes));
|
||||||
|
Vitality::Fresh(self.id_to_object.get_mut(id).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NotesHolder {
|
||||||
|
fn get_multi_subscriber(&mut self) -> Option<&mut MultiSubscriber>;
|
||||||
|
fn set_multi_subscriber(&mut self, subscriber: MultiSubscriber);
|
||||||
|
fn get_view(&mut self) -> &mut TimelineTab;
|
||||||
|
fn filters(for_id: &[u8; 32]) -> Vec<Filter>;
|
||||||
|
fn filters_since(for_id: &[u8; 32], since: u64) -> Vec<Filter>;
|
||||||
|
fn new_notes_holder(notes: Vec<NoteRef>) -> Self;
|
||||||
|
|
||||||
|
#[must_use = "UnknownIds::update_from_note_refs should be used on this result"]
|
||||||
|
fn poll_notes_into_view(&mut self, txn: &Transaction, ndb: &Ndb) -> Result<()> {
|
||||||
|
if let Some(multi_subscriber) = self.get_multi_subscriber() {
|
||||||
|
let reversed = true;
|
||||||
|
let note_refs: Vec<NoteRef> = multi_subscriber.poll_for_notes(ndb, txn)?;
|
||||||
|
self.get_view().insert(¬e_refs, reversed);
|
||||||
|
} else {
|
||||||
|
return Err(Error::Generic(
|
||||||
|
"Thread unexpectedly has no MultiSubscriber".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look for new thread notes since our last fetch
|
||||||
|
fn new_notes(notes: &[NoteRef], id: &[u8; 32], txn: &Transaction, ndb: &Ndb) -> Vec<NoteRef> {
|
||||||
|
if notes.is_empty() {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_note = notes[0];
|
||||||
|
let filters = Self::filters_since(id, last_note.created_at + 1);
|
||||||
|
|
||||||
|
if let Ok(results) = ndb.query(txn, &filters, 1000) {
|
||||||
|
debug!("got {} results from thread update", results.len());
|
||||||
|
results
|
||||||
|
.into_iter()
|
||||||
|
.map(NoteRef::from_query_result)
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
debug!("got no results from thread update",);
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Local thread unsubscribe
|
||||||
|
fn unsubscribe_locally<M: NotesHolder>(
|
||||||
|
txn: &Transaction,
|
||||||
|
ndb: &Ndb,
|
||||||
|
notes_holder_storage: &mut NotesHolderStorage<M>,
|
||||||
|
pool: &mut RelayPool,
|
||||||
|
id: &[u8; 32],
|
||||||
|
) {
|
||||||
|
let notes_holder = notes_holder_storage
|
||||||
|
.notes_holder_mutated(ndb, txn, id)
|
||||||
|
.get_ptr();
|
||||||
|
|
||||||
|
if let Some(multi_subscriber) = notes_holder.get_multi_subscriber() {
|
||||||
|
multi_subscriber.unsubscribe(ndb, pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open<M: NotesHolder>(
|
||||||
|
ndb: &Ndb,
|
||||||
|
txn: &Transaction,
|
||||||
|
pool: &mut RelayPool,
|
||||||
|
storage: &mut NotesHolderStorage<M>,
|
||||||
|
id: &[u8; 32],
|
||||||
|
) -> Option<NotesHolderResult> {
|
||||||
|
let vitality = storage.notes_holder_mutated(ndb, txn, id);
|
||||||
|
|
||||||
|
let (holder, result) = match vitality {
|
||||||
|
Vitality::Stale(holder) => {
|
||||||
|
// The thread is stale, let's update it
|
||||||
|
let notes = M::new_notes(&holder.get_view().notes, id, txn, ndb);
|
||||||
|
let holder_result = if notes.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(NotesHolderResult::new_notes(notes, id.to_owned()))
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// we can't insert and update the VirtualList now, because we
|
||||||
|
// are already borrowing it mutably. Let's pass it as a
|
||||||
|
// result instead
|
||||||
|
//
|
||||||
|
// thread.view.insert(¬es); <-- no
|
||||||
|
//
|
||||||
|
(holder, holder_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
Vitality::Fresh(thread) => (thread, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let multi_subscriber = if let Some(multi_subscriber) = holder.get_multi_subscriber() {
|
||||||
|
multi_subscriber
|
||||||
|
} else {
|
||||||
|
let filters = M::filters(id);
|
||||||
|
holder.set_multi_subscriber(MultiSubscriber::new(filters));
|
||||||
|
holder.get_multi_subscriber().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
multi_subscriber.subscribe(ndb, pool);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
162
src/thread.rs
162
src/thread.rs
@@ -1,14 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
multi_subscriber::MultiSubscriber,
|
multi_subscriber::MultiSubscriber,
|
||||||
note::NoteRef,
|
note::NoteRef,
|
||||||
notecache::NoteCache,
|
notes_holder::NotesHolder,
|
||||||
timeline::{TimelineTab, ViewFilter},
|
timeline::{TimelineTab, ViewFilter},
|
||||||
Error, Result,
|
|
||||||
};
|
};
|
||||||
use enostr::RelayPool;
|
use nostrdb::{Filter, FilterBuilder};
|
||||||
use nostrdb::{Filter, FilterBuilder, Ndb, Transaction};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use tracing::{debug, warn};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Thread {
|
pub struct Thread {
|
||||||
@@ -39,47 +35,6 @@ impl Thread {
|
|||||||
&mut self.view
|
&mut self.view
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use = "UnknownIds::update_from_note_refs should be used on this result"]
|
|
||||||
pub fn poll_notes_into_view(&mut self, txn: &Transaction, ndb: &Ndb) -> Result<()> {
|
|
||||||
if let Some(multi_subscriber) = &mut self.multi_subscriber {
|
|
||||||
let reversed = true;
|
|
||||||
let note_refs: Vec<NoteRef> = multi_subscriber.poll_for_notes(ndb, txn)?;
|
|
||||||
self.view.insert(¬e_refs, reversed);
|
|
||||||
} else {
|
|
||||||
return Err(Error::Generic(
|
|
||||||
"Thread unexpectedly has no MultiSubscriber".to_owned(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look for new thread notes since our last fetch
|
|
||||||
pub fn new_notes(
|
|
||||||
notes: &[NoteRef],
|
|
||||||
root_id: &[u8; 32],
|
|
||||||
txn: &Transaction,
|
|
||||||
ndb: &Ndb,
|
|
||||||
) -> Vec<NoteRef> {
|
|
||||||
if notes.is_empty() {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
let last_note = notes[0];
|
|
||||||
let filters = Thread::filters_since(root_id, last_note.created_at + 1);
|
|
||||||
|
|
||||||
if let Ok(results) = ndb.query(txn, &filters, 1000) {
|
|
||||||
debug!("got {} results from thread update", results.len());
|
|
||||||
results
|
|
||||||
.into_iter()
|
|
||||||
.map(NoteRef::from_query_result)
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
debug!("got no results from thread update",);
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filters_raw(root: &[u8; 32]) -> Vec<FilterBuilder> {
|
fn filters_raw(root: &[u8; 32]) -> Vec<FilterBuilder> {
|
||||||
vec![
|
vec![
|
||||||
nostrdb::Filter::new().kinds([1]).event(root),
|
nostrdb::Filter::new().kinds([1]).event(root),
|
||||||
@@ -102,99 +57,28 @@ impl Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
impl NotesHolder for Thread {
|
||||||
pub struct Threads {
|
fn get_multi_subscriber(&mut self) -> Option<&mut MultiSubscriber> {
|
||||||
/// root id to thread
|
self.multi_subscriber.as_mut()
|
||||||
pub root_id_to_thread: HashMap<[u8; 32], Thread>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ThreadResult<'a> {
|
|
||||||
Fresh(&'a mut Thread),
|
|
||||||
Stale(&'a mut Thread),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ThreadResult<'a> {
|
|
||||||
pub fn get_ptr(self) -> &'a mut Thread {
|
|
||||||
match self {
|
|
||||||
Self::Fresh(ptr) => ptr,
|
|
||||||
Self::Stale(ptr) => ptr,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_stale(&self) -> bool {
|
fn filters(for_id: &[u8; 32]) -> Vec<Filter> {
|
||||||
match self {
|
Thread::filters(for_id)
|
||||||
Self::Fresh(_ptr) => false,
|
}
|
||||||
Self::Stale(_ptr) => true,
|
|
||||||
}
|
fn new_notes_holder(notes: Vec<NoteRef>) -> Self {
|
||||||
}
|
Thread::new(notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Threads {
|
fn get_view(&mut self) -> &mut TimelineTab {
|
||||||
pub fn thread_expected_mut(&mut self, root_id: &[u8; 32]) -> &mut Thread {
|
&mut self.view
|
||||||
self.root_id_to_thread
|
}
|
||||||
.get_mut(root_id)
|
|
||||||
.expect("thread_expected_mut used but there was no thread")
|
fn filters_since(for_id: &[u8; 32], since: u64) -> Vec<Filter> {
|
||||||
}
|
Thread::filters_since(for_id, since)
|
||||||
|
}
|
||||||
pub fn thread_mut<'a>(
|
|
||||||
&'a mut self,
|
fn set_multi_subscriber(&mut self, subscriber: MultiSubscriber) {
|
||||||
ndb: &Ndb,
|
self.multi_subscriber = Some(subscriber);
|
||||||
txn: &Transaction,
|
|
||||||
root_id: &[u8; 32],
|
|
||||||
) -> ThreadResult<'a> {
|
|
||||||
// we can't use the naive hashmap entry API here because lookups
|
|
||||||
// require a copy, wait until we have a raw entry api. We could
|
|
||||||
// also use hashbrown?
|
|
||||||
|
|
||||||
if self.root_id_to_thread.contains_key(root_id) {
|
|
||||||
return ThreadResult::Stale(self.thread_expected_mut(root_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// we don't have the thread, query for it!
|
|
||||||
let filters = Thread::filters(root_id);
|
|
||||||
|
|
||||||
let notes = if let Ok(results) = ndb.query(txn, &filters, 1000) {
|
|
||||||
results
|
|
||||||
.into_iter()
|
|
||||||
.map(NoteRef::from_query_result)
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
"got no results from thread lookup for {}",
|
|
||||||
hex::encode(root_id)
|
|
||||||
);
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
if notes.is_empty() {
|
|
||||||
warn!("thread query returned 0 notes? ")
|
|
||||||
} else {
|
|
||||||
debug!("found thread with {} notes", notes.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.root_id_to_thread
|
|
||||||
.insert(root_id.to_owned(), Thread::new(notes));
|
|
||||||
ThreadResult::Fresh(self.root_id_to_thread.get_mut(root_id).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
//fn thread_by_id(&self, ndb: &Ndb, id: &[u8; 32]) -> &mut Thread {
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Local thread unsubscribe
|
|
||||||
pub fn thread_unsubscribe(
|
|
||||||
ndb: &Ndb,
|
|
||||||
threads: &mut Threads,
|
|
||||||
pool: &mut RelayPool,
|
|
||||||
note_cache: &mut NoteCache,
|
|
||||||
id: &[u8; 32],
|
|
||||||
) {
|
|
||||||
let txn = Transaction::new(ndb).expect("txn");
|
|
||||||
let root_id = crate::note::root_note_id_from_selected_id(ndb, note_cache, &txn, id);
|
|
||||||
|
|
||||||
let thread = threads.thread_mut(ndb, &txn, root_id).get_ptr();
|
|
||||||
|
|
||||||
if let Some(multi_subscriber) = &mut thread.multi_subscriber {
|
|
||||||
multi_subscriber.unsubscribe(ndb, pool);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ use crate::{
|
|||||||
draft::Drafts,
|
draft::Drafts,
|
||||||
imgcache::ImageCache,
|
imgcache::ImageCache,
|
||||||
notecache::NoteCache,
|
notecache::NoteCache,
|
||||||
thread::Threads,
|
notes_holder::NotesHolderStorage,
|
||||||
|
thread::Thread,
|
||||||
timeline::TimelineId,
|
timeline::TimelineId,
|
||||||
ui::{
|
ui::{
|
||||||
self,
|
self,
|
||||||
@@ -46,7 +47,7 @@ pub fn render_timeline_route(
|
|||||||
drafts: &mut Drafts,
|
drafts: &mut Drafts,
|
||||||
img_cache: &mut ImageCache,
|
img_cache: &mut ImageCache,
|
||||||
note_cache: &mut NoteCache,
|
note_cache: &mut NoteCache,
|
||||||
threads: &mut Threads,
|
threads: &mut NotesHolderStorage<Thread>,
|
||||||
accounts: &mut AccountManager,
|
accounts: &mut AccountManager,
|
||||||
route: TimelineRoute,
|
route: TimelineRoute,
|
||||||
col: usize,
|
col: usize,
|
||||||
@@ -162,7 +163,7 @@ pub fn render_profile_route(
|
|||||||
pool: &mut RelayPool,
|
pool: &mut RelayPool,
|
||||||
img_cache: &mut ImageCache,
|
img_cache: &mut ImageCache,
|
||||||
note_cache: &mut NoteCache,
|
note_cache: &mut NoteCache,
|
||||||
threads: &mut Threads,
|
threads: &mut NotesHolderStorage<Thread>,
|
||||||
col: usize,
|
col: usize,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
) -> Option<AfterRouteExecution> {
|
) -> Option<AfterRouteExecution> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
actionbar::TimelineResponse, imgcache::ImageCache, notecache::NoteCache, thread::Threads,
|
actionbar::TimelineResponse, imgcache::ImageCache, notecache::NoteCache,
|
||||||
|
notes_holder::{NotesHolder, NotesHolderStorage}, thread::Thread,
|
||||||
};
|
};
|
||||||
use nostrdb::{Ndb, NoteKey, Transaction};
|
use nostrdb::{Ndb, NoteKey, Transaction};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
@@ -7,7 +8,7 @@ use tracing::error;
|
|||||||
use super::timeline::TimelineTabView;
|
use super::timeline::TimelineTabView;
|
||||||
|
|
||||||
pub struct ThreadView<'a> {
|
pub struct ThreadView<'a> {
|
||||||
threads: &'a mut Threads,
|
threads: &'a mut NotesHolderStorage<Thread>,
|
||||||
ndb: &'a Ndb,
|
ndb: &'a Ndb,
|
||||||
note_cache: &'a mut NoteCache,
|
note_cache: &'a mut NoteCache,
|
||||||
img_cache: &'a mut ImageCache,
|
img_cache: &'a mut ImageCache,
|
||||||
@@ -19,7 +20,7 @@ pub struct ThreadView<'a> {
|
|||||||
impl<'a> ThreadView<'a> {
|
impl<'a> ThreadView<'a> {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
threads: &'a mut Threads,
|
threads: &'a mut NotesHolderStorage<Thread>,
|
||||||
ndb: &'a Ndb,
|
ndb: &'a Ndb,
|
||||||
note_cache: &'a mut NoteCache,
|
note_cache: &'a mut NoteCache,
|
||||||
img_cache: &'a mut ImageCache,
|
img_cache: &'a mut ImageCache,
|
||||||
@@ -86,7 +87,10 @@ impl<'a> ThreadView<'a> {
|
|||||||
.map_or_else(|| self.selected_note_id, |nr| nr.id)
|
.map_or_else(|| self.selected_note_id, |nr| nr.id)
|
||||||
};
|
};
|
||||||
|
|
||||||
let thread = self.threads.thread_mut(self.ndb, &txn, root_id).get_ptr();
|
let thread = self
|
||||||
|
.threads
|
||||||
|
.notes_holder_mutated(self.ndb, &txn, root_id)
|
||||||
|
.get_ptr();
|
||||||
|
|
||||||
// TODO(jb55): skip poll if ThreadResult is fresh?
|
// TODO(jb55): skip poll if ThreadResult is fresh?
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user