notes_holder.rs (6561B)
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, 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!("got no results from NotesHolder lookup for {}", hex::encode(id)); 77 vec![] 78 }; 79 80 if notes.is_empty() { 81 warn!("NotesHolder query returned 0 notes? ") 82 } else { 83 info!("found NotesHolder with {} notes", notes.len()); 84 } 85 86 self.id_to_object.insert( 87 id.to_owned(), 88 M::new_notes_holder(txn, ndb, note_cache, id, M::filters(id), notes), 89 ); 90 Vitality::Fresh(self.id_to_object.get_mut(id).unwrap()) 91 } 92 } 93 94 pub trait NotesHolder { 95 fn get_multi_subscriber(&mut self) -> Option<&mut MultiSubscriber>; 96 fn set_multi_subscriber(&mut self, subscriber: MultiSubscriber); 97 fn get_view(&mut self) -> &mut TimelineTab; 98 fn filters(for_id: &[u8; 32]) -> Vec<Filter>; 99 fn filters_since(for_id: &[u8; 32], since: u64) -> Vec<Filter>; 100 fn new_notes_holder( 101 txn: &Transaction, 102 ndb: &Ndb, 103 note_cache: &mut NoteCache, 104 id: &[u8; 32], 105 filters: Vec<Filter>, 106 notes: Vec<NoteRef>, 107 ) -> Self; 108 109 #[must_use = "UnknownIds::update_from_note_refs should be used on this result"] 110 fn poll_notes_into_view(&mut self, txn: &Transaction, ndb: &Ndb) -> Result<()> { 111 if let Some(multi_subscriber) = self.get_multi_subscriber() { 112 let reversed = true; 113 let note_refs: Vec<NoteRef> = multi_subscriber.poll_for_notes(ndb, txn)?; 114 self.get_view().insert(¬e_refs, reversed); 115 } else { 116 return Err(Error::Generic( 117 "NotesHolder unexpectedly has no MultiSubscriber".to_owned(), 118 )); 119 } 120 121 Ok(()) 122 } 123 124 /// Look for new thread notes since our last fetch 125 fn new_notes(notes: &[NoteRef], id: &[u8; 32], txn: &Transaction, ndb: &Ndb) -> Vec<NoteRef> { 126 if notes.is_empty() { 127 return vec![]; 128 } 129 130 let last_note = notes[0]; 131 let filters = Self::filters_since(id, last_note.created_at + 1); 132 133 if let Ok(results) = ndb.query(txn, &filters, 1000) { 134 debug!("got {} results from NotesHolder update", results.len()); 135 results 136 .into_iter() 137 .map(NoteRef::from_query_result) 138 .collect() 139 } else { 140 debug!("got no results from NotesHolder update",); 141 vec![] 142 } 143 } 144 145 /// Local NotesHolder unsubscribe 146 fn unsubscribe_locally<M: NotesHolder>( 147 txn: &Transaction, 148 ndb: &Ndb, 149 note_cache: &mut NoteCache, 150 notes_holder_storage: &mut NotesHolderStorage<M>, 151 pool: &mut RelayPool, 152 id: &[u8; 32], 153 ) { 154 let notes_holder = notes_holder_storage 155 .notes_holder_mutated(ndb, note_cache, txn, id) 156 .get_ptr(); 157 158 if let Some(multi_subscriber) = notes_holder.get_multi_subscriber() { 159 multi_subscriber.unsubscribe(ndb, pool); 160 } 161 } 162 163 fn open<M: NotesHolder>( 164 ndb: &Ndb, 165 note_cache: &mut NoteCache, 166 txn: &Transaction, 167 pool: &mut RelayPool, 168 storage: &mut NotesHolderStorage<M>, 169 id: &[u8; 32], 170 ) -> Option<NotesHolderResult> { 171 let vitality = storage.notes_holder_mutated(ndb, note_cache, txn, id); 172 173 let (holder, result) = match vitality { 174 Vitality::Stale(holder) => { 175 // The NotesHolder is stale, let's update it 176 let notes = M::new_notes(&holder.get_view().notes, id, txn, ndb); 177 let holder_result = if notes.is_empty() { 178 None 179 } else { 180 Some(NotesHolderResult::new_notes(notes, id.to_owned())) 181 }; 182 183 // 184 // we can't insert and update the VirtualList now, because we 185 // are already borrowing it mutably. Let's pass it as a 186 // result instead 187 // 188 // holder.get_view().insert(¬es); <-- no 189 // 190 (holder, holder_result) 191 } 192 193 Vitality::Fresh(thread) => (thread, None), 194 }; 195 196 let multi_subscriber = if let Some(multi_subscriber) = holder.get_multi_subscriber() { 197 multi_subscriber 198 } else { 199 let filters = M::filters(id); 200 holder.set_multi_subscriber(MultiSubscriber::new(filters)); 201 holder.get_multi_subscriber().unwrap() 202 }; 203 204 multi_subscriber.subscribe(ndb, pool); 205 206 result 207 } 208 }