1#![allow(dead_code)] use std::{
121 collections::{BTreeMap, BTreeSet},
122 num::NonZeroUsize,
123};
124
125use matrix_sdk_common::{deserialized_responses::TimelineEvent, ring_buffer::RingBuffer};
126use ruma::{
127 events::{
128 poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
129 receipt::{ReceiptEventContent, ReceiptThread, ReceiptType},
130 room::message::Relation,
131 AnySyncMessageLikeEvent, AnySyncTimelineEvent, OriginalSyncMessageLikeEvent,
132 SyncMessageLikeEvent,
133 },
134 serde::Raw,
135 EventId, OwnedEventId, OwnedUserId, RoomId, UserId,
136};
137use serde::{Deserialize, Serialize};
138use tracing::{debug, instrument, trace, warn};
139
140#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
141struct LatestReadReceipt {
142 event_id: OwnedEventId,
145}
146
147#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
152pub struct RoomReadReceipts {
153 pub num_unread: u64,
155
156 pub num_notifications: u64,
158
159 pub num_mentions: u64,
162
163 #[serde(default)]
166 latest_active: Option<LatestReadReceipt>,
167
168 #[serde(default = "new_nonempty_ring_buffer")]
177 pending: RingBuffer<OwnedEventId>,
178}
179
180impl Default for RoomReadReceipts {
181 fn default() -> Self {
182 Self {
183 num_unread: Default::default(),
184 num_notifications: Default::default(),
185 num_mentions: Default::default(),
186 latest_active: Default::default(),
187 pending: new_nonempty_ring_buffer(),
188 }
189 }
190}
191
192fn new_nonempty_ring_buffer() -> RingBuffer<OwnedEventId> {
193 RingBuffer::new(NonZeroUsize::new(10).unwrap())
196}
197
198impl RoomReadReceipts {
199 #[inline(always)]
204 fn process_event(&mut self, event: &TimelineEvent, user_id: &UserId) {
205 if marks_as_unread(event.raw(), user_id) {
206 self.num_unread += 1;
207 }
208
209 let mut has_notify = false;
210 let mut has_mention = false;
211
212 let Some(actions) = event.push_actions.as_ref() else {
213 return;
214 };
215
216 for action in actions.iter() {
217 if !has_notify && action.should_notify() {
218 self.num_notifications += 1;
219 has_notify = true;
220 }
221 if !has_mention && action.is_highlight() {
222 self.num_mentions += 1;
223 has_mention = true;
224 }
225 }
226 }
227
228 #[inline(always)]
229 fn reset(&mut self) {
230 self.num_unread = 0;
231 self.num_notifications = 0;
232 self.num_mentions = 0;
233 }
234
235 #[instrument(skip_all)]
238 fn find_and_process_events<'a>(
239 &mut self,
240 receipt_event_id: &EventId,
241 user_id: &UserId,
242 events: impl IntoIterator<Item = &'a TimelineEvent>,
243 ) -> bool {
244 let mut counting_receipts = false;
245
246 for event in events {
247 if let Some(event_id) = event.event_id() {
251 if event_id == receipt_event_id {
252 trace!("Found the event the receipt was referring to! Starting to count.");
255 self.reset();
256 counting_receipts = true;
257 continue;
258 }
259 }
260
261 if counting_receipts {
262 self.process_event(event, user_id);
263 }
264 }
265
266 counting_receipts
267 }
268}
269
270struct ReceiptSelector {
273 event_id_to_pos: BTreeMap<OwnedEventId, usize>,
275 latest_event_with_receipt: Option<OwnedEventId>,
278 latest_event_pos: Option<usize>,
280}
281
282impl ReceiptSelector {
283 fn new(all_events: &[TimelineEvent], latest_active_receipt_event: Option<&EventId>) -> Self {
284 let event_id_to_pos = Self::create_sync_index(all_events.iter());
285
286 let best_pos =
287 latest_active_receipt_event.and_then(|event_id| event_id_to_pos.get(event_id)).copied();
288
289 Self { latest_event_pos: best_pos, latest_event_with_receipt: None, event_id_to_pos }
294 }
295
296 fn create_sync_index<'a>(
299 events: impl Iterator<Item = &'a TimelineEvent> + 'a,
300 ) -> BTreeMap<OwnedEventId, usize> {
301 BTreeMap::from_iter(
303 events
304 .enumerate()
305 .filter_map(|(pos, event)| event.event_id().map(|event_id| (event_id, pos))),
306 )
307 }
308
309 #[instrument(skip(self), fields(prev_pos = ?self.latest_event_pos, prev_receipt = ?self.latest_event_with_receipt))]
311 fn try_select_later(&mut self, event_id: &EventId, event_pos: usize) {
312 if let Some(best_pos) = self.latest_event_pos.as_mut() {
315 if event_pos >= *best_pos {
319 *best_pos = event_pos;
320 self.latest_event_with_receipt = Some(event_id.to_owned());
321 debug!("saving better");
322 } else {
323 trace!("not better, keeping previous");
324 }
325 } else {
326 self.latest_event_pos = Some(event_pos);
329 self.latest_event_with_receipt = Some(event_id.to_owned());
330 debug!("saving for the first time");
331 }
332 }
333
334 #[instrument(skip_all)]
336 fn handle_pending_receipts(&mut self, pending: &mut RingBuffer<OwnedEventId>) {
337 pending.retain(|event_id| {
339 if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
340 trace!(%event_id, "matching event against its stashed receipt");
342 self.try_select_later(event_id, *event_pos);
343
344 false
347 } else {
348 true
350 }
351 });
352 }
353
354 #[instrument(skip_all)]
364 fn handle_new_receipt(
365 &mut self,
366 user_id: &UserId,
367 receipt_event: &ReceiptEventContent,
368 ) -> Vec<OwnedEventId> {
369 let mut pending = Vec::new();
370 for (event_id, receipts) in &receipt_event.0 {
372 for ty in [ReceiptType::Read, ReceiptType::ReadPrivate] {
373 if let Some(receipt) = receipts.get(&ty).and_then(|receipts| receipts.get(user_id))
374 {
375 if matches!(receipt.thread, ReceiptThread::Main | ReceiptThread::Unthreaded) {
376 trace!(%event_id, "found new candidate");
377 if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
378 self.try_select_later(event_id, *event_pos);
379 } else {
380 trace!(%event_id, "stashed as pending");
382 pending.push(event_id.clone());
383 }
384 }
385 }
386 }
387 }
388 pending
389 }
390
391 #[instrument(skip_all)]
394 fn try_match_implicit(&mut self, user_id: &UserId, new_events: &[TimelineEvent]) {
395 for ev in new_events {
396 let Ok(Some(sender)) = ev.raw().get_field::<OwnedUserId>("sender") else { continue };
398 if sender == user_id {
399 let Some(event_id) = ev.event_id() else { continue };
401 if let Some(event_pos) = self.event_id_to_pos.get(&event_id) {
402 trace!(%event_id, "found an implicit receipt candidate");
403 self.try_select_later(&event_id, *event_pos);
404 }
405 }
406 }
407 }
408
409 fn select(self) -> Option<LatestReadReceipt> {
414 self.latest_event_with_receipt.map(|event_id| LatestReadReceipt { event_id })
415 }
416}
417
418fn events_intersects<'a>(
421 previous_events: impl Iterator<Item = &'a TimelineEvent>,
422 new_events: &[TimelineEvent],
423) -> bool {
424 let previous_events_ids = BTreeSet::from_iter(previous_events.filter_map(|ev| ev.event_id()));
425 new_events
426 .iter()
427 .any(|ev| ev.event_id().is_some_and(|event_id| previous_events_ids.contains(&event_id)))
428}
429
430#[instrument(skip_all, fields(room_id = %room_id))]
439pub(crate) fn compute_unread_counts(
440 user_id: &UserId,
441 room_id: &RoomId,
442 receipt_event: Option<&ReceiptEventContent>,
443 mut previous_events: Vec<TimelineEvent>,
444 new_events: &[TimelineEvent],
445 read_receipts: &mut RoomReadReceipts,
446) {
447 debug!(?read_receipts, "Starting");
448
449 let all_events = if events_intersects(previous_events.iter(), new_events) {
450 new_events.to_owned()
456 } else {
457 previous_events.extend(new_events.iter().cloned());
458 previous_events
459 };
460
461 let new_receipt = {
462 let mut selector = ReceiptSelector::new(
463 &all_events,
464 read_receipts.latest_active.as_ref().map(|receipt| &*receipt.event_id),
465 );
466
467 selector.try_match_implicit(user_id, new_events);
468 selector.handle_pending_receipts(&mut read_receipts.pending);
469 if let Some(receipt_event) = receipt_event {
470 let new_pending = selector.handle_new_receipt(user_id, receipt_event);
471 if !new_pending.is_empty() {
472 read_receipts.pending.extend(new_pending);
473 }
474 }
475 selector.select()
476 };
477
478 if let Some(new_receipt) = new_receipt {
479 let event_id = new_receipt.event_id.clone();
485
486 trace!(%event_id, "Saving a new active read receipt");
488 read_receipts.latest_active = Some(new_receipt);
489
490 read_receipts.find_and_process_events(&event_id, user_id, all_events.iter());
493
494 debug!(?read_receipts, "after finding a better receipt");
495 return;
496 }
497
498 for event in new_events {
506 read_receipts.process_event(event, user_id);
507 }
508
509 debug!(?read_receipts, "no better receipt, {} new events", new_events.len());
510}
511
512fn marks_as_unread(event: &Raw<AnySyncTimelineEvent>, user_id: &UserId) -> bool {
514 let event = match event.deserialize() {
515 Ok(event) => event,
516 Err(err) => {
517 warn!(
518 "couldn't deserialize event {:?}: {err}",
519 event.get_field::<String>("event_id").ok().flatten()
520 );
521 return false;
522 }
523 };
524
525 if event.sender() == user_id {
526 return false;
528 }
529
530 match event {
531 AnySyncTimelineEvent::MessageLike(event) => {
532 let Some(content) = event.original_content() else {
534 tracing::trace!("not interesting because redacted");
535 return false;
536 };
537
538 if matches!(
540 content.relation(),
541 Some(ruma::events::room::encrypted::Relation::Replacement(..))
542 ) {
543 tracing::trace!("not interesting because edited");
544 return false;
545 }
546
547 match event {
548 AnySyncMessageLikeEvent::CallAnswer(_)
549 | AnySyncMessageLikeEvent::CallInvite(_)
550 | AnySyncMessageLikeEvent::CallNotify(_)
551 | AnySyncMessageLikeEvent::CallHangup(_)
552 | AnySyncMessageLikeEvent::CallCandidates(_)
553 | AnySyncMessageLikeEvent::CallNegotiate(_)
554 | AnySyncMessageLikeEvent::CallReject(_)
555 | AnySyncMessageLikeEvent::CallSelectAnswer(_)
556 | AnySyncMessageLikeEvent::PollResponse(_)
557 | AnySyncMessageLikeEvent::UnstablePollResponse(_)
558 | AnySyncMessageLikeEvent::Reaction(_)
559 | AnySyncMessageLikeEvent::RoomRedaction(_)
560 | AnySyncMessageLikeEvent::KeyVerificationStart(_)
561 | AnySyncMessageLikeEvent::KeyVerificationReady(_)
562 | AnySyncMessageLikeEvent::KeyVerificationCancel(_)
563 | AnySyncMessageLikeEvent::KeyVerificationAccept(_)
564 | AnySyncMessageLikeEvent::KeyVerificationDone(_)
565 | AnySyncMessageLikeEvent::KeyVerificationMac(_)
566 | AnySyncMessageLikeEvent::KeyVerificationKey(_) => false,
567
568 AnySyncMessageLikeEvent::PollStart(SyncMessageLikeEvent::Original(
570 OriginalSyncMessageLikeEvent {
571 content:
572 PollStartEventContent { relates_to: Some(Relation::Replacement(_)), .. },
573 ..
574 },
575 ))
576 | AnySyncMessageLikeEvent::UnstablePollStart(SyncMessageLikeEvent::Original(
577 OriginalSyncMessageLikeEvent {
578 content: UnstablePollStartEventContent::Replacement(_),
579 ..
580 },
581 )) => false,
582
583 AnySyncMessageLikeEvent::Message(_)
584 | AnySyncMessageLikeEvent::PollStart(_)
585 | AnySyncMessageLikeEvent::UnstablePollStart(_)
586 | AnySyncMessageLikeEvent::PollEnd(_)
587 | AnySyncMessageLikeEvent::UnstablePollEnd(_)
588 | AnySyncMessageLikeEvent::RoomEncrypted(_)
589 | AnySyncMessageLikeEvent::RoomMessage(_)
590 | AnySyncMessageLikeEvent::Sticker(_) => true,
591
592 _ => {
593 warn!("unhandled timeline event type: {}", event.event_type());
595 false
596 }
597 }
598 }
599
600 AnySyncTimelineEvent::State(_) => false,
601 }
602}
603
604#[cfg(test)]
605mod tests {
606 use std::{num::NonZeroUsize, ops::Not as _};
607
608 use matrix_sdk_common::{deserialized_responses::TimelineEvent, ring_buffer::RingBuffer};
609 use matrix_sdk_test::event_factory::EventFactory;
610 use ruma::{
611 event_id,
612 events::{
613 receipt::{ReceiptThread, ReceiptType},
614 room::{member::MembershipState, message::MessageType},
615 },
616 owned_event_id, owned_user_id,
617 push::Action,
618 room_id, user_id, EventId, UserId,
619 };
620
621 use super::compute_unread_counts;
622 use crate::read_receipts::{marks_as_unread, ReceiptSelector, RoomReadReceipts};
623
624 #[test]
625 fn test_room_message_marks_as_unread() {
626 let user_id = user_id!("@alice:example.org");
627 let other_user_id = user_id!("@bob:example.org");
628
629 let f = EventFactory::new();
630
631 let ev = f.text_msg("A").event_id(event_id!("$ida")).sender(other_user_id).into_raw_sync();
633 assert!(marks_as_unread(&ev, user_id));
634
635 let ev = f.text_msg("A").event_id(event_id!("$ida")).sender(user_id).into_raw_sync();
637 assert!(marks_as_unread(&ev, user_id).not());
638 }
639
640 #[test]
641 fn test_room_edit_doesnt_mark_as_unread() {
642 let user_id = user_id!("@alice:example.org");
643 let other_user_id = user_id!("@bob:example.org");
644
645 let ev = EventFactory::new()
647 .text_msg("* edited message")
648 .edit(
649 event_id!("$someeventid:localhost"),
650 MessageType::text_plain("edited message").into(),
651 )
652 .event_id(event_id!("$ida"))
653 .sender(other_user_id)
654 .into_raw_sync();
655
656 assert!(marks_as_unread(&ev, user_id).not());
657 }
658
659 #[test]
660 fn test_redaction_doesnt_mark_room_as_unread() {
661 let user_id = user_id!("@alice:example.org");
662 let other_user_id = user_id!("@bob:example.org");
663
664 let ev = EventFactory::new()
666 .redaction(event_id!("$151957878228ssqrj:localhost"))
667 .sender(other_user_id)
668 .event_id(event_id!("$151957878228ssqrJ:localhost"))
669 .into_raw_sync();
670
671 assert!(marks_as_unread(&ev, user_id).not());
672 }
673
674 #[test]
675 fn test_reaction_doesnt_mark_room_as_unread() {
676 let user_id = user_id!("@alice:example.org");
677 let other_user_id = user_id!("@bob:example.org");
678
679 let ev = EventFactory::new()
681 .reaction(event_id!("$15275047031IXQRj:localhost"), "👍")
682 .sender(other_user_id)
683 .event_id(event_id!("$15275047031IXQRi:localhost"))
684 .into_raw_sync();
685
686 assert!(marks_as_unread(&ev, user_id).not());
687 }
688
689 #[test]
690 fn test_state_event_doesnt_mark_as_unread() {
691 let user_id = user_id!("@alice:example.org");
692 let event_id = event_id!("$1");
693
694 let ev = EventFactory::new()
695 .member(user_id)
696 .membership(MembershipState::Join)
697 .display_name("Alice")
698 .event_id(event_id)
699 .into_raw_sync();
700 assert!(marks_as_unread(&ev, user_id).not());
701
702 let other_user_id = user_id!("@bob:example.org");
703 assert!(marks_as_unread(&ev, other_user_id).not());
704 }
705
706 #[test]
707 fn test_count_unread_and_mentions() {
708 fn make_event(user_id: &UserId, push_actions: Vec<Action>) -> TimelineEvent {
709 let mut ev = EventFactory::new()
710 .text_msg("A")
711 .sender(user_id)
712 .event_id(event_id!("$ida"))
713 .into_event();
714 ev.push_actions = Some(push_actions);
715 ev
716 }
717
718 let user_id = user_id!("@alice:example.org");
719
720 let event = make_event(user_id, Vec::new());
722 let mut receipts = RoomReadReceipts::default();
723 receipts.process_event(&event, user_id);
724 assert_eq!(receipts.num_unread, 0);
725 assert_eq!(receipts.num_mentions, 0);
726 assert_eq!(receipts.num_notifications, 0);
727
728 let event = make_event(user_id!("@bob:example.org"), Vec::new());
730 let mut receipts = RoomReadReceipts::default();
731 receipts.process_event(&event, user_id);
732 assert_eq!(receipts.num_unread, 1);
733 assert_eq!(receipts.num_mentions, 0);
734 assert_eq!(receipts.num_notifications, 0);
735
736 let event = make_event(user_id!("@bob:example.org"), vec![Action::Notify]);
738 let mut receipts = RoomReadReceipts::default();
739 receipts.process_event(&event, user_id);
740 assert_eq!(receipts.num_unread, 1);
741 assert_eq!(receipts.num_mentions, 0);
742 assert_eq!(receipts.num_notifications, 1);
743
744 let event = make_event(
745 user_id!("@bob:example.org"),
746 vec![Action::SetTweak(ruma::push::Tweak::Highlight(true))],
747 );
748 let mut receipts = RoomReadReceipts::default();
749 receipts.process_event(&event, user_id);
750 assert_eq!(receipts.num_unread, 1);
751 assert_eq!(receipts.num_mentions, 1);
752 assert_eq!(receipts.num_notifications, 0);
753
754 let event = make_event(
755 user_id!("@bob:example.org"),
756 vec![Action::SetTweak(ruma::push::Tweak::Highlight(true)), Action::Notify],
757 );
758 let mut receipts = RoomReadReceipts::default();
759 receipts.process_event(&event, user_id);
760 assert_eq!(receipts.num_unread, 1);
761 assert_eq!(receipts.num_mentions, 1);
762 assert_eq!(receipts.num_notifications, 1);
763
764 let event = make_event(user_id!("@bob:example.org"), vec![Action::Notify, Action::Notify]);
767 let mut receipts = RoomReadReceipts::default();
768 receipts.process_event(&event, user_id);
769 assert_eq!(receipts.num_unread, 1);
770 assert_eq!(receipts.num_mentions, 0);
771 assert_eq!(receipts.num_notifications, 1);
772 }
773
774 #[test]
775 fn test_find_and_process_events() {
776 let ev0 = event_id!("$0");
777 let user_id = user_id!("@alice:example.org");
778
779 let mut receipts = RoomReadReceipts::default();
782 assert!(receipts.find_and_process_events(ev0, user_id, &[]).not());
783 assert_eq!(receipts.num_unread, 0);
784 assert_eq!(receipts.num_notifications, 0);
785 assert_eq!(receipts.num_mentions, 0);
786
787 fn make_event(event_id: &EventId) -> TimelineEvent {
790 EventFactory::new()
791 .text_msg("A")
792 .sender(user_id!("@bob:example.org"))
793 .event_id(event_id)
794 .into()
795 }
796
797 let mut receipts = RoomReadReceipts {
798 num_unread: 42,
799 num_notifications: 13,
800 num_mentions: 37,
801 ..Default::default()
802 };
803 assert!(receipts
804 .find_and_process_events(ev0, user_id, &[make_event(event_id!("$1"))],)
805 .not());
806 assert_eq!(receipts.num_unread, 42);
807 assert_eq!(receipts.num_notifications, 13);
808 assert_eq!(receipts.num_mentions, 37);
809
810 let mut receipts = RoomReadReceipts {
814 num_unread: 42,
815 num_notifications: 13,
816 num_mentions: 37,
817 ..Default::default()
818 };
819 assert!(receipts.find_and_process_events(ev0, user_id, &[make_event(ev0)]));
820 assert_eq!(receipts.num_unread, 0);
821 assert_eq!(receipts.num_notifications, 0);
822 assert_eq!(receipts.num_mentions, 0);
823
824 let mut receipts = RoomReadReceipts {
827 num_unread: 42,
828 num_notifications: 13,
829 num_mentions: 37,
830 ..Default::default()
831 };
832 assert!(receipts
833 .find_and_process_events(
834 ev0,
835 user_id,
836 &[
837 make_event(event_id!("$1")),
838 make_event(event_id!("$2")),
839 make_event(event_id!("$3"))
840 ],
841 )
842 .not());
843 assert_eq!(receipts.num_unread, 42);
844 assert_eq!(receipts.num_notifications, 13);
845 assert_eq!(receipts.num_mentions, 37);
846
847 let mut receipts = RoomReadReceipts {
850 num_unread: 42,
851 num_notifications: 13,
852 num_mentions: 37,
853 ..Default::default()
854 };
855 assert!(receipts.find_and_process_events(
856 ev0,
857 user_id,
858 &[
859 make_event(event_id!("$1")),
860 make_event(ev0),
861 make_event(event_id!("$2")),
862 make_event(event_id!("$3"))
863 ],
864 ));
865 assert_eq!(receipts.num_unread, 2);
866 assert_eq!(receipts.num_notifications, 0);
867 assert_eq!(receipts.num_mentions, 0);
868
869 let mut receipts = RoomReadReceipts {
871 num_unread: 42,
872 num_notifications: 13,
873 num_mentions: 37,
874 ..Default::default()
875 };
876 assert!(receipts.find_and_process_events(
877 ev0,
878 user_id,
879 &[
880 make_event(ev0),
881 make_event(event_id!("$1")),
882 make_event(ev0),
883 make_event(event_id!("$2")),
884 make_event(event_id!("$3"))
885 ],
886 ));
887 assert_eq!(receipts.num_unread, 2);
888 assert_eq!(receipts.num_notifications, 0);
889 assert_eq!(receipts.num_mentions, 0);
890 }
891
892 #[test]
894 fn test_basic_compute_unread_counts() {
895 let user_id = user_id!("@alice:example.org");
896 let other_user_id = user_id!("@bob:example.org");
897 let room_id = room_id!("!room:example.org");
898 let receipt_event_id = event_id!("$1");
899
900 let mut previous_events = Vec::new();
901
902 let f = EventFactory::new();
903 let ev1 = f.text_msg("A").sender(other_user_id).event_id(receipt_event_id).into_event();
904 let ev2 = f.text_msg("A").sender(other_user_id).event_id(event_id!("$2")).into_event();
905
906 let receipt_event = f
907 .read_receipts()
908 .add(receipt_event_id, user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
909 .into_content();
910
911 let mut read_receipts = Default::default();
912 compute_unread_counts(
913 user_id,
914 room_id,
915 Some(&receipt_event),
916 previous_events.clone(),
917 &[ev1.clone(), ev2.clone()],
918 &mut read_receipts,
919 );
920
921 assert_eq!(read_receipts.num_unread, 1);
923
924 previous_events.push(ev1);
926 previous_events.push(ev2);
927
928 let new_event =
929 f.text_msg("A").sender(other_user_id).event_id(event_id!("$3")).into_event();
930 compute_unread_counts(
931 user_id,
932 room_id,
933 Some(&receipt_event),
934 previous_events,
935 &[new_event],
936 &mut read_receipts,
937 );
938
939 assert_eq!(read_receipts.num_unread, 2);
941 }
942
943 fn make_test_events(user_id: &UserId) -> Vec<TimelineEvent> {
944 let f = EventFactory::new().sender(user_id);
945 let ev1 = f.text_msg("With the lights out, it's less dangerous").event_id(event_id!("$1"));
946 let ev2 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$2"));
947 let ev3 = f.text_msg("I feel stupid and contagious").event_id(event_id!("$3"));
948 let ev4 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$4"));
949 let ev5 = f.text_msg("Hello, hello, hello, how low?").event_id(event_id!("$5"));
950 [ev1, ev2, ev3, ev4, ev5].into_iter().map(Into::into).collect()
951 }
952
953 #[test]
956 fn test_compute_unread_counts_multiple_receipts_in_one_event() {
957 let user_id = user_id!("@alice:example.org");
958 let room_id = room_id!("!room:example.org");
959
960 let all_events = make_test_events(user_id!("@bob:example.org"));
961 let head_events: Vec<_> = all_events.iter().take(2).cloned().collect();
962 let tail_events: Vec<_> = all_events.iter().skip(2).cloned().collect();
963
964 let f = EventFactory::new();
967 for receipt_type_1 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
968 for receipt_thread_1 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
969 for receipt_type_2 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
970 for receipt_thread_2 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
971 let receipt_event = f
972 .read_receipts()
973 .add(
974 event_id!("$2"),
975 user_id,
976 receipt_type_1.clone(),
977 receipt_thread_1.clone(),
978 )
979 .add(
980 event_id!("$3"),
981 user_id,
982 receipt_type_2.clone(),
983 receipt_thread_2.clone(),
984 )
985 .add(
986 event_id!("$1"),
987 user_id,
988 receipt_type_1.clone(),
989 receipt_thread_2.clone(),
990 )
991 .into_content();
992
993 let mut read_receipts = RoomReadReceipts::default();
995
996 compute_unread_counts(
997 user_id,
998 room_id,
999 Some(&receipt_event),
1000 all_events.clone(),
1001 &[],
1002 &mut read_receipts,
1003 );
1004
1005 assert!(
1006 read_receipts != Default::default(),
1007 "read receipts have been updated"
1008 );
1009
1010 assert_eq!(read_receipts.num_unread, 2);
1012 assert_eq!(read_receipts.num_mentions, 0);
1013 assert_eq!(read_receipts.num_notifications, 0);
1014
1015 let mut read_receipts = RoomReadReceipts::default();
1017 compute_unread_counts(
1018 user_id,
1019 room_id,
1020 Some(&receipt_event),
1021 head_events.clone(),
1022 &tail_events,
1023 &mut read_receipts,
1024 );
1025
1026 assert!(
1027 read_receipts != Default::default(),
1028 "read receipts have been updated"
1029 );
1030
1031 assert_eq!(read_receipts.num_unread, 2);
1033 assert_eq!(read_receipts.num_mentions, 0);
1034 assert_eq!(read_receipts.num_notifications, 0);
1035 }
1036 }
1037 }
1038 }
1039 }
1040
1041 #[test]
1045 fn test_compute_unread_counts_updated_after_field_tracking() {
1046 let user_id = owned_user_id!("@alice:example.org");
1047 let room_id = room_id!("!room:example.org");
1048
1049 let events = make_test_events(user_id!("@bob:example.org"));
1050
1051 let receipt_event = EventFactory::new()
1052 .read_receipts()
1053 .add(event_id!("$6"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1054 .into_content();
1055
1056 let mut read_receipts = RoomReadReceipts::default();
1057 assert!(read_receipts.pending.is_empty());
1058
1059 compute_unread_counts(
1062 &user_id,
1063 room_id,
1064 Some(&receipt_event),
1065 events,
1066 &[], &mut read_receipts,
1068 );
1069
1070 assert_eq!(read_receipts.num_unread, 0);
1072
1073 assert_eq!(read_receipts.pending.len(), 1);
1075 assert!(read_receipts.pending.iter().any(|ev| ev == event_id!("$6")));
1076 }
1077
1078 #[test]
1079 fn test_compute_unread_counts_limited_sync() {
1080 let user_id = owned_user_id!("@alice:example.org");
1081 let room_id = room_id!("!room:example.org");
1082
1083 let events = make_test_events(user_id!("@bob:example.org"));
1084
1085 let receipt_event = EventFactory::new()
1086 .read_receipts()
1087 .add(event_id!("$1"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1088 .into_content();
1089
1090 let mut read_receipts = RoomReadReceipts::default();
1094 assert!(read_receipts.pending.is_empty());
1095
1096 let ev0 = events[0].clone();
1097
1098 compute_unread_counts(
1099 &user_id,
1100 room_id,
1101 Some(&receipt_event),
1102 events,
1103 &[ev0], &mut read_receipts,
1105 );
1106
1107 assert_eq!(read_receipts.num_unread, 0);
1109 assert!(read_receipts.pending.is_empty());
1110 }
1111
1112 #[test]
1113 fn test_receipt_selector_create_sync_index() {
1114 let uid = user_id!("@bob:example.org");
1115
1116 let events = make_test_events(uid);
1117
1118 let ev6 = EventFactory::new().text_msg("yolo").sender(uid).no_event_id().into_event();
1120
1121 let index = ReceiptSelector::create_sync_index(events.iter().chain(&[ev6]));
1122
1123 assert_eq!(*index.get(event_id!("$1")).unwrap(), 0);
1124 assert_eq!(*index.get(event_id!("$2")).unwrap(), 1);
1125 assert_eq!(*index.get(event_id!("$3")).unwrap(), 2);
1126 assert_eq!(*index.get(event_id!("$4")).unwrap(), 3);
1127 assert_eq!(*index.get(event_id!("$5")).unwrap(), 4);
1128 assert_eq!(index.get(event_id!("$6")), None);
1129
1130 assert_eq!(index.len(), 5);
1131
1132 let index = ReceiptSelector::create_sync_index(
1134 [events[1].clone(), events[2].clone(), events[4].clone()].iter(),
1135 );
1136
1137 assert_eq!(*index.get(event_id!("$2")).unwrap(), 0);
1138 assert_eq!(*index.get(event_id!("$3")).unwrap(), 1);
1139 assert_eq!(*index.get(event_id!("$5")).unwrap(), 2);
1140
1141 assert_eq!(index.len(), 3);
1142 }
1143
1144 #[test]
1145 fn test_receipt_selector_try_select_later() {
1146 let events = make_test_events(user_id!("@bob:example.org"));
1147
1148 {
1149 let mut selector = ReceiptSelector::new(&[], None);
1151 selector.try_select_later(event_id!("$1"), 0);
1152 let best_receipt = selector.select();
1153 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1154 }
1155
1156 {
1157 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1159 selector.try_select_later(event_id!("$1"), 0);
1160 let best_receipt = selector.select();
1161 assert!(best_receipt.is_none());
1162 }
1163
1164 {
1165 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$1")));
1168 selector.try_select_later(event_id!("$1"), 0);
1169 let best_receipt = selector.select();
1170 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1171 }
1172
1173 {
1174 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1176 selector.try_select_later(event_id!("$4"), 3);
1177 let best_receipt = selector.select();
1178 assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1179 }
1180 }
1181
1182 #[test]
1183 fn test_receipt_selector_handle_pending_receipts_noop() {
1184 let sender = user_id!("@bob:example.org");
1185 let f = EventFactory::new().sender(sender);
1186 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1187 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1188 let events = &[ev1, ev2][..];
1189
1190 {
1191 let mut selector = ReceiptSelector::new(events, None);
1193
1194 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1195 selector.handle_pending_receipts(&mut pending);
1196
1197 assert!(pending.is_empty());
1198
1199 let best_receipt = selector.select();
1200 assert!(best_receipt.is_none());
1201 }
1202
1203 {
1204 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1207
1208 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1209 selector.handle_pending_receipts(&mut pending);
1210
1211 assert!(pending.is_empty());
1212
1213 let best_receipt = selector.select();
1214 assert!(best_receipt.is_none());
1215 }
1216 }
1217
1218 #[test]
1219 fn test_receipt_selector_handle_pending_receipts_doesnt_match_known_events() {
1220 let sender = user_id!("@bob:example.org");
1221 let f = EventFactory::new().sender(sender);
1222 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1223 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1224 let events = &[ev1, ev2][..];
1225
1226 {
1227 let mut selector = ReceiptSelector::new(events, None);
1229
1230 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1231 pending.push(owned_event_id!("$3"));
1232 selector.handle_pending_receipts(&mut pending);
1233
1234 assert_eq!(pending.len(), 1);
1235
1236 let best_receipt = selector.select();
1237 assert!(best_receipt.is_none());
1238 }
1239
1240 {
1241 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1243
1244 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1245 pending.push(owned_event_id!("$3"));
1246 selector.handle_pending_receipts(&mut pending);
1247
1248 assert_eq!(pending.len(), 1);
1249
1250 let best_receipt = selector.select();
1251 assert!(best_receipt.is_none());
1252 }
1253 }
1254
1255 #[test]
1256 fn test_receipt_selector_handle_pending_receipts_matches_known_events_no_initial() {
1257 let sender = user_id!("@bob:example.org");
1258 let f = EventFactory::new().sender(sender);
1259 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1260 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1261 let events = &[ev1, ev2][..];
1262
1263 {
1264 let mut selector = ReceiptSelector::new(events, None);
1266
1267 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1268 pending.push(owned_event_id!("$2"));
1269 selector.handle_pending_receipts(&mut pending);
1270
1271 assert!(pending.is_empty());
1273
1274 let best_receipt = selector.select();
1276 assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1277 }
1278
1279 {
1280 let mut selector = ReceiptSelector::new(events, None);
1282
1283 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1284 pending.push(owned_event_id!("$1"));
1285 pending.push(owned_event_id!("$3"));
1286 selector.handle_pending_receipts(&mut pending);
1287
1288 assert_eq!(pending.len(), 1);
1290 assert!(pending.iter().any(|ev| ev == event_id!("$3")));
1291
1292 let best_receipt = selector.select();
1293 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1294 }
1295 }
1296
1297 #[test]
1298 fn test_receipt_selector_handle_pending_receipts_matches_known_events_with_initial() {
1299 let sender = user_id!("@bob:example.org");
1300 let f = EventFactory::new().sender(sender);
1301 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1302 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1303 let events = &[ev1, ev2][..];
1304
1305 {
1306 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1309
1310 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1311 pending.push(owned_event_id!("$2"));
1312 selector.handle_pending_receipts(&mut pending);
1313
1314 assert!(pending.is_empty());
1316
1317 let best_receipt = selector.select();
1319 assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1320 }
1321
1322 {
1323 let mut selector = ReceiptSelector::new(events, Some(event_id!("$2")));
1325
1326 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1327 pending.push(owned_event_id!("$1"));
1328 selector.handle_pending_receipts(&mut pending);
1329
1330 assert!(pending.is_empty());
1332
1333 let best_receipt = selector.select();
1334 assert!(best_receipt.is_none());
1335 }
1336 }
1337
1338 #[test]
1339 fn test_receipt_selector_handle_new_receipt() {
1340 let myself = user_id!("@alice:example.org");
1341 let events = make_test_events(user_id!("@bob:example.org"));
1342
1343 let f = EventFactory::new();
1344 {
1345 let mut selector = ReceiptSelector::new(&events, None);
1347
1348 let receipt_event = f
1349 .read_receipts()
1350 .add(
1351 event_id!("$5"),
1352 myself,
1353 ReceiptType::Read,
1354 ReceiptThread::Thread(owned_event_id!("$2")),
1355 )
1356 .into_content();
1357
1358 let pending = selector.handle_new_receipt(myself, &receipt_event);
1359 assert!(pending.is_empty());
1360
1361 let best_receipt = selector.select();
1362 assert!(best_receipt.is_none());
1363 }
1364
1365 for receipt_type in [ReceiptType::Read, ReceiptType::ReadPrivate] {
1366 for receipt_thread in [ReceiptThread::Main, ReceiptThread::Unthreaded] {
1367 {
1368 let mut selector = ReceiptSelector::new(&events, None);
1371
1372 let receipt_event = f
1373 .read_receipts()
1374 .add(event_id!("$6"), myself, receipt_type.clone(), receipt_thread.clone())
1375 .into_content();
1376
1377 let pending = selector.handle_new_receipt(myself, &receipt_event);
1378 assert_eq!(pending[0], event_id!("$6"));
1379 assert_eq!(pending.len(), 1);
1380
1381 let best_receipt = selector.select();
1382 assert!(best_receipt.is_none());
1383 }
1384
1385 {
1386 let mut selector = ReceiptSelector::new(&events, None);
1389
1390 let receipt_event = f
1391 .read_receipts()
1392 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1393 .into_content();
1394
1395 let pending = selector.handle_new_receipt(myself, &receipt_event);
1396 assert!(pending.is_empty());
1397
1398 let best_receipt = selector.select();
1399 assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1400 }
1401
1402 {
1403 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$4")));
1406
1407 let receipt_event = f
1408 .read_receipts()
1409 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1410 .into_content();
1411
1412 let pending = selector.handle_new_receipt(myself, &receipt_event);
1413 assert!(pending.is_empty());
1414
1415 let best_receipt = selector.select();
1416 assert!(best_receipt.is_none());
1417 }
1418
1419 {
1420 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1423
1424 let receipt_event = f
1425 .read_receipts()
1426 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1427 .into_content();
1428
1429 let pending = selector.handle_new_receipt(myself, &receipt_event);
1430 assert!(pending.is_empty());
1431
1432 let best_receipt = selector.select();
1433 assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1434 }
1435 }
1436 } {
1439 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1442
1443 let receipt_event = f
1444 .read_receipts()
1445 .add(event_id!("$4"), myself, ReceiptType::ReadPrivate, ReceiptThread::Unthreaded)
1446 .add(event_id!("$6"), myself, ReceiptType::ReadPrivate, ReceiptThread::Main)
1447 .add(event_id!("$3"), myself, ReceiptType::Read, ReceiptThread::Main)
1448 .into_content();
1449
1450 let pending = selector.handle_new_receipt(myself, &receipt_event);
1451 assert_eq!(pending.len(), 1);
1452 assert_eq!(pending[0], event_id!("$6"));
1453
1454 let best_receipt = selector.select();
1455 assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1456 }
1457 }
1458
1459 #[test]
1460 fn test_try_match_implicit() {
1461 let myself = owned_user_id!("@alice:example.org");
1462 let bob = user_id!("@bob:example.org");
1463
1464 let mut events = make_test_events(bob);
1465
1466 let mut selector = ReceiptSelector::new(&events, None);
1468 selector.try_match_implicit(&myself, &events);
1470 let best_receipt = selector.select();
1472 assert!(best_receipt.is_none());
1473
1474 let f = EventFactory::new();
1476 events.push(
1477 f.text_msg("A mulatto, an albino")
1478 .sender(&myself)
1479 .event_id(event_id!("$6"))
1480 .into_event(),
1481 );
1482 events.push(
1483 f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1484 );
1485
1486 let mut selector = ReceiptSelector::new(&events, None);
1487 selector.try_match_implicit(&myself, &events);
1489 let best_receipt = selector.select();
1491 assert_eq!(best_receipt.unwrap().event_id, event_id!("$6"));
1492 }
1493
1494 #[test]
1495 fn test_compute_unread_counts_with_implicit_receipt() {
1496 let user_id = user_id!("@alice:example.org");
1497 let bob = user_id!("@bob:example.org");
1498 let room_id = room_id!("!room:example.org");
1499
1500 let mut events = make_test_events(bob);
1502
1503 let f = EventFactory::new();
1505 events.push(
1506 f.text_msg("A mulatto, an albino")
1507 .sender(user_id)
1508 .event_id(event_id!("$6"))
1509 .into_event(),
1510 );
1511
1512 events.push(
1514 f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1515 );
1516 events.push(
1517 f.text_msg("A denial, a denial").sender(bob).event_id(event_id!("$8")).into_event(),
1518 );
1519
1520 let events: Vec<_> = events.into_iter().collect();
1521
1522 let receipt_event = f
1524 .read_receipts()
1525 .add(event_id!("$3"), user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1526 .into_content();
1527
1528 let mut read_receipts = RoomReadReceipts::default();
1529
1530 compute_unread_counts(
1533 user_id,
1534 room_id,
1535 Some(&receipt_event),
1536 Vec::new(),
1537 &events,
1538 &mut read_receipts,
1539 );
1540
1541 assert_eq!(read_receipts.num_unread, 2);
1543
1544 assert!(read_receipts.pending.is_empty());
1546
1547 assert_eq!(read_receipts.latest_active.unwrap().event_id, event_id!("$6"));
1549 }
1550}