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