notedeck

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

notes_holder.rs (6886B)


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