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