notedeck

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

filter.rs (7635B)


      1 use crate::error::{Error, FilterError};
      2 use crate::note::NoteRef;
      3 use crate::Result;
      4 use nostrdb::{Filter, FilterBuilder, Note, Subscription};
      5 use std::collections::HashMap;
      6 use tracing::{debug, warn};
      7 
      8 /// A unified subscription has a local and remote component. The remote subid
      9 /// tracks data received remotely, and local
     10 #[derive(Debug, Clone)]
     11 pub struct UnifiedSubscription {
     12     pub local: Subscription,
     13     pub remote: String,
     14 }
     15 
     16 /// Each relay can have a different filter state. For example, some
     17 /// relays may have the contact list, some may not. Let's capture all of
     18 /// these states so that some relays don't stop the states of other
     19 /// relays.
     20 #[derive(Debug)]
     21 pub struct FilterStates {
     22     pub initial_state: FilterState,
     23     pub states: HashMap<String, FilterState>,
     24 }
     25 
     26 impl FilterStates {
     27     pub fn get(&mut self, relay: &str) -> &FilterState {
     28         // if our initial state is ready, then just use that
     29         if let FilterState::Ready(_) = self.initial_state {
     30             &self.initial_state
     31         } else {
     32             // otherwise we look at relay states
     33             if !self.states.contains_key(relay) {
     34                 self.states
     35                     .insert(relay.to_string(), self.initial_state.clone());
     36             }
     37             self.states.get(relay).unwrap()
     38         }
     39     }
     40 
     41     pub fn get_any_gotremote(&self) -> Option<(&str, Subscription)> {
     42         for (k, v) in self.states.iter() {
     43             if let FilterState::GotRemote(sub) = v {
     44                 return Some((k, *sub));
     45             }
     46         }
     47 
     48         None
     49     }
     50 
     51     pub fn get_any_ready(&self) -> Option<&Vec<Filter>> {
     52         if let FilterState::Ready(fs) = &self.initial_state {
     53             Some(fs)
     54         } else {
     55             for (_k, v) in self.states.iter() {
     56                 if let FilterState::Ready(ref fs) = v {
     57                     return Some(fs);
     58                 }
     59             }
     60 
     61             None
     62         }
     63     }
     64 
     65     pub fn new(initial_state: FilterState) -> Self {
     66         Self {
     67             initial_state,
     68             states: HashMap::new(),
     69         }
     70     }
     71 
     72     pub fn set_relay_state(&mut self, relay: String, state: FilterState) {
     73         if self.states.contains_key(&relay) {
     74             let current_state = self.states.get(&relay).unwrap();
     75             debug!(
     76                 "set_relay_state: {:?} -> {:?} on {}",
     77                 current_state, state, &relay,
     78             );
     79         }
     80         self.states.insert(relay, state);
     81     }
     82 }
     83 
     84 /// We may need to fetch some data from relays before our filter is ready.
     85 /// [`FilterState`] tracks this.
     86 #[derive(Debug, Clone)]
     87 pub enum FilterState {
     88     NeedsRemote(Vec<Filter>),
     89     FetchingRemote(UnifiedSubscription),
     90     GotRemote(Subscription),
     91     Ready(Vec<Filter>),
     92     Broken(FilterError),
     93 }
     94 
     95 impl FilterState {
     96     /// We tried to fetch a filter but we wither got no data or the data
     97     /// was corrupted, preventing us from getting to the Ready state.
     98     /// Just mark the timeline as broken so that we can signal to the
     99     /// user that something went wrong
    100     pub fn broken(reason: FilterError) -> Self {
    101         Self::Broken(reason)
    102     }
    103 
    104     /// The filter is ready
    105     pub fn ready(filter: Vec<Filter>) -> Self {
    106         Self::Ready(filter)
    107     }
    108 
    109     /// We need some data from relays before we can continue. Example:
    110     /// for home timelines where we don't have a contact list yet. We
    111     /// need to fetch the contact list before we have the right timeline
    112     /// filter.
    113     pub fn needs_remote(filter: Vec<Filter>) -> Self {
    114         Self::NeedsRemote(filter)
    115     }
    116 
    117     /// We got the remote data. Local data should be available to build
    118     /// the filter for the [`FilterState::Ready`] state
    119     pub fn got_remote(local_sub: Subscription) -> Self {
    120         Self::GotRemote(local_sub)
    121     }
    122 
    123     /// We have sent off a remote subscription to get data needed for the
    124     /// filter. The string is the subscription id
    125     pub fn fetching_remote(sub_id: String, local_sub: Subscription) -> Self {
    126         let unified_sub = UnifiedSubscription {
    127             local: local_sub,
    128             remote: sub_id,
    129         };
    130         Self::FetchingRemote(unified_sub)
    131     }
    132 }
    133 
    134 pub fn should_since_optimize(limit: u64, num_notes: usize) -> bool {
    135     // rough heuristic for bailing since optimization if we don't have enough notes
    136     limit as usize <= num_notes
    137 }
    138 
    139 pub fn since_optimize_filter_with(filter: Filter, notes: &[NoteRef], since_gap: u64) -> Filter {
    140     // Get the latest entry in the events
    141     if notes.is_empty() {
    142         return filter;
    143     }
    144 
    145     // get the latest note
    146     let latest = notes[0];
    147     let since = latest.created_at - since_gap;
    148 
    149     filter.since_mut(since)
    150 }
    151 
    152 pub fn since_optimize_filter(filter: Filter, notes: &[NoteRef]) -> Filter {
    153     since_optimize_filter_with(filter, notes, 60)
    154 }
    155 
    156 pub fn default_limit() -> u64 {
    157     500
    158 }
    159 
    160 pub fn default_remote_limit() -> u64 {
    161     250
    162 }
    163 
    164 pub struct FilteredTags {
    165     pub authors: Option<FilterBuilder>,
    166     pub hashtags: Option<FilterBuilder>,
    167 }
    168 
    169 impl FilteredTags {
    170     pub fn into_follow_filter(self) -> Vec<Filter> {
    171         self.into_filter([1], default_limit())
    172     }
    173 
    174     // TODO: make this more general
    175     pub fn into_filter<I>(self, kinds: I, limit: u64) -> Vec<Filter>
    176     where
    177         I: IntoIterator<Item = u64> + Copy,
    178     {
    179         let mut filters: Vec<Filter> = Vec::with_capacity(2);
    180 
    181         if let Some(authors) = self.authors {
    182             filters.push(authors.kinds(kinds).limit(limit).build())
    183         }
    184 
    185         if let Some(hashtags) = self.hashtags {
    186             filters.push(hashtags.kinds(kinds).limit(limit).build())
    187         }
    188 
    189         filters
    190     }
    191 }
    192 
    193 /// Create a filter from tags. This can be used to create a filter
    194 /// from a contact list
    195 pub fn filter_from_tags(note: &Note) -> Result<FilteredTags> {
    196     let mut author_filter = Filter::new();
    197     let mut hashtag_filter = Filter::new();
    198     let mut author_res: Option<FilterBuilder> = None;
    199     let mut hashtag_res: Option<FilterBuilder> = None;
    200     let mut author_count = 0i32;
    201     let mut hashtag_count = 0i32;
    202 
    203     let tags = note.tags();
    204 
    205     author_filter.start_authors_field()?;
    206     hashtag_filter.start_tags_field('t')?;
    207 
    208     for tag in tags {
    209         if tag.count() < 2 {
    210             continue;
    211         }
    212 
    213         let t = if let Some(t) = tag.get_unchecked(0).variant().str() {
    214             t
    215         } else {
    216             continue;
    217         };
    218 
    219         if t == "p" {
    220             let author = if let Some(author) = tag.get_unchecked(1).variant().id() {
    221                 author
    222             } else {
    223                 continue;
    224             };
    225 
    226             author_filter.add_id_element(author)?;
    227             author_count += 1;
    228         } else if t == "t" {
    229             let hashtag = if let Some(hashtag) = tag.get_unchecked(1).variant().str() {
    230                 hashtag
    231             } else {
    232                 continue;
    233             };
    234 
    235             hashtag_filter.add_str_element(hashtag)?;
    236             hashtag_count += 1;
    237         }
    238     }
    239 
    240     author_filter.end_field();
    241     hashtag_filter.end_field();
    242 
    243     if author_count == 0 && hashtag_count == 0 {
    244         warn!("no authors or hashtags found in contact list");
    245         return Err(Error::empty_contact_list());
    246     }
    247 
    248     debug!(
    249         "adding {} authors and {} hashtags to contact filter",
    250         author_count, hashtag_count
    251     );
    252 
    253     // if we hit these ooms, we need to expand filter buffer size
    254     if author_count > 0 {
    255         author_res = Some(author_filter)
    256     }
    257 
    258     if hashtag_count > 0 {
    259         hashtag_res = Some(hashtag_filter)
    260     }
    261 
    262     Ok(FilteredTags {
    263         authors: author_res,
    264         hashtags: hashtag_res,
    265     })
    266 }