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 }