notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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(&note_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(&notes); <-- 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 }