1use std::collections::btree_set::IntoIter;
6use std::collections::hash_map::DefaultHasher;
7use std::hash::{Hash, Hasher};
8
9use nostr::{Event, Filter};
10
11use super::tree::{BTreeCappedSet, Capacity, OverCapacityPolicy};
12
13const POLICY: OverCapacityPolicy = OverCapacityPolicy::Last;
15
16#[derive(Debug, Clone)]
18pub struct Events {
19 set: BTreeCappedSet<Event>,
20 hash: u64,
21 prev_not_match: bool,
22}
23
24impl Default for Events {
25 fn default() -> Self {
26 Self {
27 set: BTreeCappedSet::unbounded(),
28 hash: 0,
29 prev_not_match: false,
30 }
31 }
32}
33
34impl PartialEq for Events {
35 fn eq(&self, other: &Self) -> bool {
36 self.set == other.set
37 }
38}
39
40impl Eq for Events {}
41
42impl Events {
43 #[inline]
45 pub fn new(filter: &Filter) -> Self {
46 let mut hasher = DefaultHasher::new();
47 filter.hash(&mut hasher);
48 let hash: u64 = hasher.finish();
49
50 let set: BTreeCappedSet<Event> = match filter.limit {
51 Some(limit) => BTreeCappedSet::bounded_with_policy(limit, POLICY),
52 None => BTreeCappedSet::unbounded(),
53 };
54
55 Self {
56 set,
57 hash,
58 prev_not_match: false,
59 }
60 }
61
62 #[inline]
64 pub fn len(&self) -> usize {
65 self.set.len()
66 }
67
68 #[inline]
70 pub fn is_empty(&self) -> bool {
71 self.set.is_empty()
72 }
73
74 #[inline]
76 pub fn contains(&self, event: &Event) -> bool {
77 self.set.contains(event)
78 }
79
80 #[inline]
86 pub fn insert(&mut self, event: Event) -> bool {
87 self.set.insert(event).inserted
88 }
89
90 #[inline]
95 pub fn force_insert(&mut self, event: Event) -> bool {
96 self.set.force_insert(event).inserted
97 }
98
99 #[inline]
101 pub fn extend<I>(&mut self, events: I)
102 where
103 I: IntoIterator<Item = Event>,
104 {
105 self.set.extend(events);
106 }
107
108 pub fn merge(mut self, other: Self) -> Self {
114 if self.hash != other.hash || self.prev_not_match || other.prev_not_match {
116 self.set.change_capacity(Capacity::Unbounded);
117 self.hash = 0;
118 self.prev_not_match = true;
119 }
120
121 self.extend(other.set);
123
124 self
125 }
126
127 #[inline]
129 pub fn first(&self) -> Option<&Event> {
130 self.set.first()
132 }
133
134 #[inline]
136 pub fn first_owned(self) -> Option<Event> {
137 self.into_iter().next()
139 }
140
141 #[inline]
143 pub fn last(&self) -> Option<&Event> {
144 self.set.last()
146 }
147
148 #[inline]
150 pub fn last_owned(self) -> Option<Event> {
151 self.into_iter().next_back()
153 }
154
155 #[inline]
157 pub fn iter(&self) -> impl Iterator<Item = &Event> {
158 self.set.iter()
160 }
161
162 #[inline]
164 pub fn to_vec(self) -> Vec<Event> {
165 self.into_iter().collect()
166 }
167}
168
169impl IntoIterator for Events {
170 type Item = Event;
171 type IntoIter = IntoIter<Self::Item>;
172
173 fn into_iter(self) -> Self::IntoIter {
174 self.set.into_iter()
176 }
177}
178
179impl FromIterator<Event> for Events {
180 fn from_iter<T>(iter: T) -> Self
181 where
182 T: IntoIterator<Item = Event>,
183 {
184 let mut events: Self = Self::default();
185 events.extend(iter);
186 events
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use nostr::{JsonUtil, Kind};
193
194 use super::*;
195
196 #[test]
197 fn test_events_equality() {
198 {
200 let event1 = Event::from_json(r#"{"content":"Kind 10050 is for DMs, kind 10002 for the other stuff. But both have the same aim. So IMO both have to be under the `gossip` option.","created_at":1732738371,"id":"f2d71a515ce3576d238aaaeaa48fde97388162d08208f729b540a4c3f9723e6b","kind":1,"pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","sig":"d88d3ac21036cfb541809288c12844747dbf1d20a246133dbd37374254b281808c5582bade27c880477759491b2b964d7235142c8b80d233dfb9ae8a50252119","tags":[["e","8262a50cf7832351ae3f21c429e111bb31be0cf754ec437e015534bf5cc2eee8","","root"],["e","0f4bcc83ef2af2febbc7eb9aea5d615a29084ed9e65c467ef2a9387ff79b57e8"],["e","94469431e367b2c16e6d224a4ac2c369c18718a1abdf42759ff591d9816b5ff3","","reply"],["p","68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272"],["p","1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef"],["p","03f9cfd948e95aeb04f780382344f7c1cfc0210d9af3f4006bb6d451c7b08692"],["p","126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f"],["p","13a665157257e79d9dcc960deeb367fd79383be2d0babb3d861679a5701d463b"],["p","ee0d20b47fb298e8a9ed3609108fe7f2296bd71e8b82fb4f9ff8f61f62bbc7a6"],["p","1c71312fb45273956b078e27981dcc15b178db8d55bffd7ad57a8cfaed6b5ab4"],["p","800e0fe3d8638ce3f75a56ed865df9d96fc9d9cd2f75550df0d7f5c1d8468b0b"]]}"#).unwrap();
201 let mut events1 = Events::new(&Filter::new().kind(Kind::TextNote).limit(1));
202 events1.insert(event1);
203
204 let event2 = Event::from_json(r#"{"content":"Kind 10050 is for DMs, kind 10002 for the other stuff. But both have the same aim. So IMO both have to be under the `gossip` option.","created_at":1732738371,"id":"f2d71a515ce3576d238aaaeaa48fde97388162d08208f729b540a4c3f9723e6b","kind":1,"pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","sig":"d88d3ac21036cfb541809288c12844747dbf1d20a246133dbd37374254b281808c5582bade27c880477759491b2b964d7235142c8b80d233dfb9ae8a50252119","tags":[["e","8262a50cf7832351ae3f21c429e111bb31be0cf754ec437e015534bf5cc2eee8","","root"],["e","0f4bcc83ef2af2febbc7eb9aea5d615a29084ed9e65c467ef2a9387ff79b57e8"],["e","94469431e367b2c16e6d224a4ac2c369c18718a1abdf42759ff591d9816b5ff3","","reply"],["p","68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272"],["p","1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef"],["p","03f9cfd948e95aeb04f780382344f7c1cfc0210d9af3f4006bb6d451c7b08692"],["p","126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f"],["p","13a665157257e79d9dcc960deeb367fd79383be2d0babb3d861679a5701d463b"],["p","ee0d20b47fb298e8a9ed3609108fe7f2296bd71e8b82fb4f9ff8f61f62bbc7a6"],["p","1c71312fb45273956b078e27981dcc15b178db8d55bffd7ad57a8cfaed6b5ab4"],["p","800e0fe3d8638ce3f75a56ed865df9d96fc9d9cd2f75550df0d7f5c1d8468b0b"]]}"#).unwrap();
205 let mut events2 = Events::new(&Filter::new().kind(Kind::TextNote).limit(2)); events2.insert(event2);
207
208 assert_eq!(events1, events2);
209 }
210
211 {
213 let event1 = Event::from_json(r#"{"content":"Kind 10050 is for DMs, kind 10002 for the other stuff. But both have the same aim. So IMO both have to be under the `gossip` option.","created_at":1732738371,"id":"f2d71a515ce3576d238aaaeaa48fde97388162d08208f729b540a4c3f9723e6b","kind":1,"pubkey":"68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272","sig":"d88d3ac21036cfb541809288c12844747dbf1d20a246133dbd37374254b281808c5582bade27c880477759491b2b964d7235142c8b80d233dfb9ae8a50252119","tags":[["e","8262a50cf7832351ae3f21c429e111bb31be0cf754ec437e015534bf5cc2eee8","","root"],["e","0f4bcc83ef2af2febbc7eb9aea5d615a29084ed9e65c467ef2a9387ff79b57e8"],["e","94469431e367b2c16e6d224a4ac2c369c18718a1abdf42759ff591d9816b5ff3","","reply"],["p","68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272"],["p","1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef"],["p","03f9cfd948e95aeb04f780382344f7c1cfc0210d9af3f4006bb6d451c7b08692"],["p","126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f"],["p","13a665157257e79d9dcc960deeb367fd79383be2d0babb3d861679a5701d463b"],["p","ee0d20b47fb298e8a9ed3609108fe7f2296bd71e8b82fb4f9ff8f61f62bbc7a6"],["p","1c71312fb45273956b078e27981dcc15b178db8d55bffd7ad57a8cfaed6b5ab4"],["p","800e0fe3d8638ce3f75a56ed865df9d96fc9d9cd2f75550df0d7f5c1d8468b0b"]]}"#).unwrap();
214 let mut events1 = Events::new(&Filter::new().kind(Kind::TextNote).limit(1));
215 events1.insert(event1);
216
217 let event2 = Event::from_json(r#"{"content":"Thank you !","created_at":1732738224,"id":"035a18ba52a9b40137c0c60ed955eb1f1f93e12423082f6d8a83f62726462d21","kind":1,"pubkey":"1c71312fb45273956b078e27981dcc15b178db8d55bffd7ad57a8cfaed6b5ab4","sig":"54921c7a4f972428c67267a0d99df7d5094c7ca4d26fe9c08221de88ffafb0cab347939ff77129ecfdebad6b18cd2c4c229bf67ce8914fe778d24e19bc22be43","tags":[["p","68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272"],["p","1739d937dc8c0c7370aa27585938c119e25c41f6c441a5d34c6d38503e3136ef"],["p","03f9cfd948e95aeb04f780382344f7c1cfc0210d9af3f4006bb6d451c7b08692"],["p","126103bfddc8df256b6e0abfd7f3797c80dcc4ea88f7c2f87dd4104220b4d65f"],["p","13a665157257e79d9dcc960deeb367fd79383be2d0babb3d861679a5701d463b"],["p","ee0d20b47fb298e8a9ed3609108fe7f2296bd71e8b82fb4f9ff8f61f62bbc7a6"],["e","8262a50cf7832351ae3f21c429e111bb31be0cf754ec437e015534bf5cc2eee8","wss://nos.lol/","root"],["e","670303f9cbb24568c705b545c277be1f5172ad84795cc9e700aeea5bb248fd74","wss://n.ok0.org/","reply"]]}"#).unwrap();
218 let mut events2 = Events::new(&Filter::new().kind(Kind::TextNote).limit(2)); events2.insert(event2);
220
221 assert_ne!(events1, events2);
222 }
223 }
224
225 #[test]
226 fn test_merge() {
227 let filter = Filter::new().kind(Kind::TextNote).limit(100);
229
230 let events1 = Events::new(&filter);
231 assert_eq!(
232 events1.set.capacity(),
233 Capacity::Bounded {
234 max: 100,
235 policy: POLICY
236 }
237 );
238
239 let events2 = Events::new(&filter);
240 assert_eq!(
241 events2.set.capacity(),
242 Capacity::Bounded {
243 max: 100,
244 policy: POLICY
245 }
246 );
247
248 let hash1 = events1.hash;
249
250 assert_eq!(events1.hash, events2.hash);
251
252 let events = events1.merge(events2);
253 assert_eq!(events.hash, hash1);
254 assert!(!events.prev_not_match);
255 assert_eq!(
256 events.set.capacity(),
257 Capacity::Bounded {
258 max: 100,
259 policy: POLICY
260 }
261 );
262
263 let filter1 = Filter::new().kind(Kind::TextNote).limit(100);
265 let filter2 = Filter::new().kind(Kind::Metadata).limit(10);
266 let filter3 = Filter::new().kind(Kind::ContactList).limit(1);
267
268 let events1 = Events::new(&filter1);
269 assert_eq!(
270 events1.set.capacity(),
271 Capacity::Bounded {
272 max: 100,
273 policy: POLICY
274 }
275 );
276
277 let events2 = Events::new(&filter2);
278 assert_eq!(
279 events2.set.capacity(),
280 Capacity::Bounded {
281 max: 10,
282 policy: POLICY
283 }
284 );
285
286 let events3 = Events::new(&filter3);
287 assert_eq!(
288 events3.set.capacity(),
289 Capacity::Bounded {
290 max: 1,
291 policy: POLICY
292 }
293 );
294
295 assert_ne!(events1.hash, events2.hash);
296
297 let events = events1.merge(events2);
298 assert_eq!(events.hash, 0);
299 assert!(events.prev_not_match);
300 assert_eq!(events.set.capacity(), Capacity::Unbounded);
301
302 let events = events.merge(events3);
303 assert_eq!(events.hash, 0);
304 assert!(events.prev_not_match);
305 assert_eq!(events.set.capacity(), Capacity::Unbounded);
306 }
307}