cache.rs (7571B)
1 use crate::{ 2 actionbar::TimelineOpenResult, 3 error::Error, 4 timeline::{Timeline, TimelineKind}, 5 }; 6 7 use notedeck::{filter, FilterState, NoteCache, NoteRef}; 8 9 use enostr::RelayPool; 10 use nostrdb::{Filter, Ndb, Transaction}; 11 use std::collections::HashMap; 12 use tracing::{debug, error, info, warn}; 13 14 #[derive(Default)] 15 pub struct TimelineCache { 16 timelines: HashMap<TimelineKind, Timeline>, 17 } 18 19 pub enum Vitality<'a, M> { 20 Fresh(&'a mut M), 21 Stale(&'a mut M), 22 } 23 24 impl<'a, M> Vitality<'a, M> { 25 pub fn get_ptr(self) -> &'a mut M { 26 match self { 27 Self::Fresh(ptr) => ptr, 28 Self::Stale(ptr) => ptr, 29 } 30 } 31 32 pub fn is_stale(&self) -> bool { 33 match self { 34 Self::Fresh(_ptr) => false, 35 Self::Stale(_ptr) => true, 36 } 37 } 38 } 39 40 impl<'a> IntoIterator for &'a mut TimelineCache { 41 type Item = (&'a TimelineKind, &'a mut Timeline); 42 type IntoIter = std::collections::hash_map::IterMut<'a, TimelineKind, Timeline>; 43 44 fn into_iter(self) -> Self::IntoIter { 45 self.timelines.iter_mut() 46 } 47 } 48 49 impl TimelineCache { 50 /// Pop a timeline from the timeline cache. This only removes the timeline 51 /// if it has reached 0 subscribers, meaning it was the last one to be 52 /// removed 53 pub fn pop( 54 &mut self, 55 id: &TimelineKind, 56 ndb: &mut Ndb, 57 pool: &mut RelayPool, 58 ) -> Result<(), Error> { 59 let timeline = if let Some(timeline) = self.timelines.get_mut(id) { 60 timeline 61 } else { 62 return Err(Error::TimelineNotFound); 63 }; 64 65 timeline.subscription.unsubscribe_or_decrement(ndb, pool); 66 67 if timeline.subscription.no_sub() { 68 debug!( 69 "popped last timeline {:?}, removing from timeline cache", 70 id 71 ); 72 self.timelines.remove(id); 73 } 74 75 Ok(()) 76 } 77 78 fn get_expected_mut(&mut self, key: &TimelineKind) -> &mut Timeline { 79 self.timelines 80 .get_mut(key) 81 .expect("expected notes in timline cache") 82 } 83 84 /// Insert a new timeline into the cache, based on the TimelineKind 85 #[allow(clippy::too_many_arguments)] 86 fn insert_new( 87 &mut self, 88 id: TimelineKind, 89 txn: &Transaction, 90 ndb: &Ndb, 91 notes: &[NoteRef], 92 note_cache: &mut NoteCache, 93 ) { 94 let mut timeline = if let Some(timeline) = id.clone().into_timeline(txn, ndb) { 95 timeline 96 } else { 97 error!("Error creating timeline from {:?}", &id); 98 return; 99 }; 100 101 // insert initial notes into timeline 102 timeline.insert_new(txn, ndb, note_cache, notes); 103 self.timelines.insert(id, timeline); 104 } 105 106 pub fn insert(&mut self, id: TimelineKind, timeline: Timeline) { 107 if let Some(cur_timeline) = self.timelines.get_mut(&id) { 108 cur_timeline.subscription.increment(); 109 return; 110 }; 111 112 self.timelines.insert(id, timeline); 113 } 114 115 /// Get and/or update the notes associated with this timeline 116 pub fn notes<'a>( 117 &'a mut self, 118 ndb: &Ndb, 119 note_cache: &mut NoteCache, 120 txn: &Transaction, 121 id: &TimelineKind, 122 ) -> Vitality<'a, Timeline> { 123 // we can't use the naive hashmap entry API here because lookups 124 // require a copy, wait until we have a raw entry api. We could 125 // also use hashbrown? 126 127 if self.timelines.contains_key(id) { 128 return Vitality::Stale(self.get_expected_mut(id)); 129 } 130 131 let notes = if let FilterState::Ready(filters) = id.filters(txn, ndb) { 132 if let Ok(results) = ndb.query(txn, filters.local(), 1000) { 133 results 134 .into_iter() 135 .map(NoteRef::from_query_result) 136 .collect() 137 } else { 138 debug!("got no results from TimelineCache lookup for {:?}", id); 139 vec![] 140 } 141 } else { 142 // filter is not ready yet 143 vec![] 144 }; 145 146 if notes.is_empty() { 147 warn!("NotesHolder query returned 0 notes? ") 148 } else { 149 info!("found NotesHolder with {} notes", notes.len()); 150 } 151 152 self.insert_new(id.to_owned(), txn, ndb, ¬es, note_cache); 153 154 Vitality::Fresh(self.get_expected_mut(id)) 155 } 156 157 /// Open a timeline, this is another way of saying insert a timeline 158 /// into the timeline cache. If there exists a timeline already, we 159 /// bump its subscription reference count. If it's new we start a new 160 /// subscription 161 pub fn open( 162 &mut self, 163 ndb: &Ndb, 164 note_cache: &mut NoteCache, 165 txn: &Transaction, 166 pool: &mut RelayPool, 167 id: &TimelineKind, 168 ) -> Option<TimelineOpenResult> { 169 let (open_result, timeline) = match self.notes(ndb, note_cache, txn, id) { 170 Vitality::Stale(timeline) => { 171 // The timeline cache is stale, let's update it 172 let notes = find_new_notes( 173 timeline.all_or_any_notes(), 174 timeline.subscription.get_filter()?.local(), 175 txn, 176 ndb, 177 ); 178 let open_result = if notes.is_empty() { 179 None 180 } else { 181 let new_notes = notes.iter().map(|n| n.key).collect(); 182 Some(TimelineOpenResult::new_notes(new_notes, id.clone())) 183 }; 184 185 // we can't insert and update the VirtualList now, because we 186 // are already borrowing it mutably. Let's pass it as a 187 // result instead 188 // 189 // holder.get_view().insert(¬es); <-- no 190 (open_result, timeline) 191 } 192 193 Vitality::Fresh(timeline) => (None, timeline), 194 }; 195 196 if let Some(filter) = timeline.filter.get_any_ready() { 197 debug!("got open with *new* subscription for {:?}", &timeline.kind); 198 timeline.subscription.try_add_local(ndb, filter); 199 timeline.subscription.try_add_remote(pool, filter); 200 } else { 201 // This should never happen reasoning, self.notes would have 202 // failed above if the filter wasn't ready 203 error!( 204 "open: filter not ready, so could not setup subscription. this should never happen" 205 ); 206 }; 207 208 timeline.subscription.increment(); 209 210 open_result 211 } 212 213 pub fn get(&self, id: &TimelineKind) -> Option<&Timeline> { 214 self.timelines.get(id) 215 } 216 217 pub fn get_mut(&mut self, id: &TimelineKind) -> Option<&mut Timeline> { 218 self.timelines.get_mut(id) 219 } 220 221 pub fn num_timelines(&self) -> usize { 222 self.timelines.len() 223 } 224 } 225 226 /// Look for new thread notes since our last fetch 227 fn find_new_notes( 228 notes: &[NoteRef], 229 filters: &[Filter], 230 txn: &Transaction, 231 ndb: &Ndb, 232 ) -> Vec<NoteRef> { 233 if notes.is_empty() { 234 return vec![]; 235 } 236 237 let last_note = notes[0]; 238 let filters = filter::make_filters_since(filters, last_note.created_at + 1); 239 240 if let Ok(results) = ndb.query(txn, &filters, 1000) { 241 debug!("got {} results from NotesHolder update", results.len()); 242 results 243 .into_iter() 244 .map(NoteRef::from_query_result) 245 .collect() 246 } else { 247 debug!("got no results from NotesHolder update",); 248 vec![] 249 } 250 }