notes_holder.rs (6704B)
1 use std::collections::HashMap; 2 3 use enostr::{Filter, RelayPool}; 4 use nostrdb::{Ndb, Transaction}; 5 use tracing::{debug, info, warn}; 6 7 use crate::{ 8 actionbar::NotesHolderResult, multi_subscriber::MultiSubscriber, note::NoteRef, 9 notecache::NoteCache, timeline::TimelineTab, unknowns::NoteRefsUnkIdAction, Error, Result, 10 }; 11 12 pub struct NotesHolderStorage<M: NotesHolder> { 13 pub id_to_object: HashMap<[u8; 32], M>, 14 } 15 16 impl<M: NotesHolder> Default for NotesHolderStorage<M> { 17 fn default() -> Self { 18 NotesHolderStorage { 19 id_to_object: HashMap::new(), 20 } 21 } 22 } 23 24 pub enum Vitality<'a, M> { 25 Fresh(&'a mut M), 26 Stale(&'a mut M), 27 } 28 29 impl<'a, M> Vitality<'a, M> { 30 pub fn get_ptr(self) -> &'a mut M { 31 match self { 32 Self::Fresh(ptr) => ptr, 33 Self::Stale(ptr) => ptr, 34 } 35 } 36 37 pub fn is_stale(&self) -> bool { 38 match self { 39 Self::Fresh(_ptr) => false, 40 Self::Stale(_ptr) => true, 41 } 42 } 43 } 44 45 impl<M: NotesHolder> NotesHolderStorage<M> { 46 pub fn notes_holder_expected_mut(&mut self, id: &[u8; 32]) -> &mut M { 47 self.id_to_object 48 .get_mut(id) 49 .expect("notes_holder_expected_mut used but there was no NotesHolder") 50 } 51 52 pub fn notes_holder_mutated<'a>( 53 &'a mut self, 54 ndb: &Ndb, 55 note_cache: &mut NoteCache, 56 txn: &Transaction, 57 id: &[u8; 32], 58 ) -> Vitality<'a, M> { 59 // we can't use the naive hashmap entry API here because lookups 60 // require a copy, wait until we have a raw entry api. We could 61 // also use hashbrown? 62 63 if self.id_to_object.contains_key(id) { 64 return Vitality::Stale(self.notes_holder_expected_mut(id)); 65 } 66 67 // we don't have the note holder, query for it! 68 let filters = M::filters(id); 69 70 let notes = if let Ok(results) = ndb.query(txn, &filters, 1000) { 71 results 72 .into_iter() 73 .map(NoteRef::from_query_result) 74 .collect() 75 } else { 76 debug!( 77 "got no results from NotesHolder lookup for {}", 78 hex::encode(id) 79 ); 80 vec![] 81 }; 82 83 if notes.is_empty() { 84 warn!("NotesHolder query returned 0 notes? ") 85 } else { 86 info!("found NotesHolder with {} notes", notes.len()); 87 } 88 89 self.id_to_object.insert( 90 id.to_owned(), 91 M::new_notes_holder(txn, ndb, note_cache, id, M::filters(id), notes), 92 ); 93 Vitality::Fresh(self.id_to_object.get_mut(id).unwrap()) 94 } 95 } 96 97 pub trait NotesHolder { 98 fn get_multi_subscriber(&mut self) -> Option<&mut MultiSubscriber>; 99 fn set_multi_subscriber(&mut self, subscriber: MultiSubscriber); 100 fn get_view(&mut self) -> &mut TimelineTab; 101 fn filters(for_id: &[u8; 32]) -> Vec<Filter>; 102 fn filters_since(for_id: &[u8; 32], since: u64) -> Vec<Filter>; 103 fn new_notes_holder( 104 txn: &Transaction, 105 ndb: &Ndb, 106 note_cache: &mut NoteCache, 107 id: &[u8; 32], 108 filters: Vec<Filter>, 109 notes: Vec<NoteRef>, 110 ) -> Self; 111 112 #[must_use = "process_action must be handled in the Ok(action) case"] 113 fn poll_notes_into_view( 114 &mut self, 115 txn: &Transaction, 116 ndb: &Ndb, 117 ) -> Result<NoteRefsUnkIdAction> { 118 if let Some(multi_subscriber) = self.get_multi_subscriber() { 119 let reversed = true; 120 let note_refs: Vec<NoteRef> = multi_subscriber.poll_for_notes(ndb, txn)?; 121 self.get_view().insert(¬e_refs, reversed); 122 Ok(NoteRefsUnkIdAction::new(note_refs)) 123 } else { 124 Err(Error::Generic( 125 "NotesHolder unexpectedly has no MultiSubscriber".to_owned(), 126 )) 127 } 128 } 129 130 /// Look for new thread notes since our last fetch 131 fn new_notes(notes: &[NoteRef], id: &[u8; 32], txn: &Transaction, ndb: &Ndb) -> Vec<NoteRef> { 132 if notes.is_empty() { 133 return vec![]; 134 } 135 136 let last_note = notes[0]; 137 let filters = Self::filters_since(id, last_note.created_at + 1); 138 139 if let Ok(results) = ndb.query(txn, &filters, 1000) { 140 debug!("got {} results from NotesHolder update", results.len()); 141 results 142 .into_iter() 143 .map(NoteRef::from_query_result) 144 .collect() 145 } else { 146 debug!("got no results from NotesHolder update",); 147 vec![] 148 } 149 } 150 151 /// Local NotesHolder unsubscribe 152 fn unsubscribe_locally<M: NotesHolder>( 153 txn: &Transaction, 154 ndb: &Ndb, 155 note_cache: &mut NoteCache, 156 notes_holder_storage: &mut NotesHolderStorage<M>, 157 pool: &mut RelayPool, 158 id: &[u8; 32], 159 ) { 160 let notes_holder = notes_holder_storage 161 .notes_holder_mutated(ndb, note_cache, txn, id) 162 .get_ptr(); 163 164 if let Some(multi_subscriber) = notes_holder.get_multi_subscriber() { 165 multi_subscriber.unsubscribe(ndb, pool); 166 } 167 } 168 169 fn open<M: NotesHolder>( 170 ndb: &Ndb, 171 note_cache: &mut NoteCache, 172 txn: &Transaction, 173 pool: &mut RelayPool, 174 storage: &mut NotesHolderStorage<M>, 175 id: &[u8; 32], 176 ) -> Option<NotesHolderResult> { 177 let vitality = storage.notes_holder_mutated(ndb, note_cache, txn, id); 178 179 let (holder, result) = match vitality { 180 Vitality::Stale(holder) => { 181 // The NotesHolder is stale, let's update it 182 let notes = M::new_notes(&holder.get_view().notes, id, txn, ndb); 183 let holder_result = if notes.is_empty() { 184 None 185 } else { 186 Some(NotesHolderResult::new_notes(notes, id.to_owned())) 187 }; 188 189 // 190 // we can't insert and update the VirtualList now, because we 191 // are already borrowing it mutably. Let's pass it as a 192 // result instead 193 // 194 // holder.get_view().insert(¬es); <-- no 195 // 196 (holder, holder_result) 197 } 198 199 Vitality::Fresh(thread) => (thread, None), 200 }; 201 202 let multi_subscriber = if let Some(multi_subscriber) = holder.get_multi_subscriber() { 203 multi_subscriber 204 } else { 205 let filters = M::filters(id); 206 holder.set_multi_subscriber(MultiSubscriber::new(filters)); 207 holder.get_multi_subscriber().unwrap() 208 }; 209 210 multi_subscriber.subscribe(ndb, pool); 211 212 result 213 } 214 }