1#![allow(dead_code)] use std::{
121 collections::{BTreeMap, BTreeSet},
122 num::NonZeroUsize,
123};
124
125use matrix_sdk_common::{
126 deserialized_responses::TimelineEvent, ring_buffer::RingBuffer,
127 serde_helpers::extract_thread_root,
128};
129use ruma::{
130 EventId, OwnedEventId, OwnedUserId, RoomId, UserId,
131 events::{
132 AnySyncMessageLikeEvent, AnySyncTimelineEvent, OriginalSyncMessageLikeEvent,
133 SyncMessageLikeEvent,
134 poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
135 receipt::{ReceiptEventContent, ReceiptThread, ReceiptType},
136 room::message::Relation,
137 },
138 serde::Raw,
139};
140use serde::{Deserialize, Serialize};
141use tracing::{debug, instrument, trace, warn};
142
143use crate::ThreadingSupport;
144
145#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
146struct LatestReadReceipt {
147 event_id: OwnedEventId,
150}
151
152#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
157pub struct RoomReadReceipts {
158 pub num_unread: u64,
160
161 pub num_notifications: u64,
163
164 pub num_mentions: u64,
167
168 #[serde(default)]
171 latest_active: Option<LatestReadReceipt>,
172
173 #[serde(default = "new_nonempty_ring_buffer")]
182 pending: RingBuffer<OwnedEventId>,
183}
184
185impl Default for RoomReadReceipts {
186 fn default() -> Self {
187 Self {
188 num_unread: Default::default(),
189 num_notifications: Default::default(),
190 num_mentions: Default::default(),
191 latest_active: Default::default(),
192 pending: new_nonempty_ring_buffer(),
193 }
194 }
195}
196
197fn new_nonempty_ring_buffer() -> RingBuffer<OwnedEventId> {
198 RingBuffer::new(NonZeroUsize::new(10).unwrap())
201}
202
203impl RoomReadReceipts {
204 #[inline(always)]
209 fn process_event(
210 &mut self,
211 event: &TimelineEvent,
212 user_id: &UserId,
213 threading_support: ThreadingSupport,
214 ) {
215 if matches!(threading_support, ThreadingSupport::Enabled { .. })
216 && extract_thread_root(event.raw()).is_some()
217 {
218 return;
219 }
220
221 if marks_as_unread(event.raw(), user_id) {
222 self.num_unread += 1;
223 }
224
225 let mut has_notify = false;
226 let mut has_mention = false;
227
228 let Some(actions) = event.push_actions() else {
229 return;
230 };
231
232 for action in actions.iter() {
233 if !has_notify && action.should_notify() {
234 self.num_notifications += 1;
235 has_notify = true;
236 }
237 if !has_mention && action.is_highlight() {
238 self.num_mentions += 1;
239 has_mention = true;
240 }
241 }
242 }
243
244 #[inline(always)]
245 fn reset(&mut self) {
246 self.num_unread = 0;
247 self.num_notifications = 0;
248 self.num_mentions = 0;
249 }
250
251 #[instrument(skip_all)]
254 fn find_and_process_events<'a>(
255 &mut self,
256 receipt_event_id: &EventId,
257 user_id: &UserId,
258 events: impl IntoIterator<Item = &'a TimelineEvent>,
259 threading_support: ThreadingSupport,
260 ) -> bool {
261 let mut counting_receipts = false;
262
263 for event in events {
264 if let Some(event_id) = event.event_id()
268 && event_id == receipt_event_id
269 {
270 trace!("Found the event the receipt was referring to! Starting to count.");
273 self.reset();
274 counting_receipts = true;
275 continue;
276 }
277
278 if counting_receipts {
279 self.process_event(event, user_id, threading_support);
280 }
281 }
282
283 counting_receipts
284 }
285}
286
287struct ReceiptSelector {
290 event_id_to_pos: BTreeMap<OwnedEventId, usize>,
292 latest_event_with_receipt: Option<OwnedEventId>,
295 latest_event_pos: Option<usize>,
297}
298
299impl ReceiptSelector {
300 fn new(all_events: &[TimelineEvent], latest_active_receipt_event: Option<&EventId>) -> Self {
301 let event_id_to_pos = Self::create_sync_index(all_events.iter());
302
303 let best_pos =
304 latest_active_receipt_event.and_then(|event_id| event_id_to_pos.get(event_id)).copied();
305
306 Self { latest_event_pos: best_pos, latest_event_with_receipt: None, event_id_to_pos }
311 }
312
313 fn create_sync_index<'a>(
316 events: impl Iterator<Item = &'a TimelineEvent> + 'a,
317 ) -> BTreeMap<OwnedEventId, usize> {
318 BTreeMap::from_iter(
320 events
321 .enumerate()
322 .filter_map(|(pos, event)| event.event_id().map(|event_id| (event_id, pos))),
323 )
324 }
325
326 #[instrument(skip(self), fields(prev_pos = ?self.latest_event_pos, prev_receipt = ?self.latest_event_with_receipt))]
328 fn try_select_later(&mut self, event_id: &EventId, event_pos: usize) {
329 if let Some(best_pos) = self.latest_event_pos.as_mut() {
332 if event_pos >= *best_pos {
336 *best_pos = event_pos;
337 self.latest_event_with_receipt = Some(event_id.to_owned());
338 debug!("saving better");
339 } else {
340 trace!("not better, keeping previous");
341 }
342 } else {
343 self.latest_event_pos = Some(event_pos);
346 self.latest_event_with_receipt = Some(event_id.to_owned());
347 debug!("saving for the first time");
348 }
349 }
350
351 #[instrument(skip_all)]
353 fn handle_pending_receipts(&mut self, pending: &mut RingBuffer<OwnedEventId>) {
354 pending.retain(|event_id| {
356 if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
357 trace!(%event_id, "matching event against its stashed receipt");
359 self.try_select_later(event_id, *event_pos);
360
361 false
364 } else {
365 true
367 }
368 });
369 }
370
371 #[instrument(skip_all)]
381 fn handle_new_receipt(
382 &mut self,
383 user_id: &UserId,
384 receipt_event: &ReceiptEventContent,
385 ) -> Vec<OwnedEventId> {
386 let mut pending = Vec::new();
387 for (event_id, receipts) in &receipt_event.0 {
389 for ty in [ReceiptType::Read, ReceiptType::ReadPrivate] {
390 if let Some(receipts) = receipts.get(&ty)
391 && let Some(receipt) = receipts.get(user_id)
392 && matches!(receipt.thread, ReceiptThread::Main | ReceiptThread::Unthreaded)
393 {
394 trace!(%event_id, "found new candidate");
395 if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
396 self.try_select_later(event_id, *event_pos);
397 } else {
398 trace!(%event_id, "stashed as pending");
400 pending.push(event_id.clone());
401 }
402 }
403 }
404 }
405 pending
406 }
407
408 #[instrument(skip_all)]
411 fn try_match_implicit(&mut self, user_id: &UserId, new_events: &[TimelineEvent]) {
412 for ev in new_events {
413 let Ok(Some(sender)) = ev.raw().get_field::<OwnedUserId>("sender") else { continue };
415 if sender == user_id {
416 let Some(event_id) = ev.event_id() else { continue };
418 if let Some(event_pos) = self.event_id_to_pos.get(&event_id) {
419 trace!(%event_id, "found an implicit receipt candidate");
420 self.try_select_later(&event_id, *event_pos);
421 }
422 }
423 }
424 }
425
426 fn select(self) -> Option<LatestReadReceipt> {
431 self.latest_event_with_receipt.map(|event_id| LatestReadReceipt { event_id })
432 }
433}
434
435fn events_intersects<'a>(
438 previous_events: impl Iterator<Item = &'a TimelineEvent>,
439 new_events: &[TimelineEvent],
440) -> bool {
441 let previous_events_ids = BTreeSet::from_iter(previous_events.filter_map(|ev| ev.event_id()));
442 new_events
443 .iter()
444 .any(|ev| ev.event_id().is_some_and(|event_id| previous_events_ids.contains(&event_id)))
445}
446
447#[instrument(skip_all, fields(room_id = %room_id))]
456pub(crate) fn compute_unread_counts(
457 user_id: &UserId,
458 room_id: &RoomId,
459 receipt_event: Option<&ReceiptEventContent>,
460 mut previous_events: Vec<TimelineEvent>,
461 new_events: &[TimelineEvent],
462 read_receipts: &mut RoomReadReceipts,
463 threading_support: ThreadingSupport,
464) {
465 debug!(?read_receipts, "Starting");
466
467 let all_events = if events_intersects(previous_events.iter(), new_events) {
468 new_events.to_owned()
474 } else {
475 previous_events.extend(new_events.iter().cloned());
476 previous_events
477 };
478
479 let new_receipt = {
480 let mut selector = ReceiptSelector::new(
481 &all_events,
482 read_receipts.latest_active.as_ref().map(|receipt| &*receipt.event_id),
483 );
484
485 selector.try_match_implicit(user_id, new_events);
486 selector.handle_pending_receipts(&mut read_receipts.pending);
487 if let Some(receipt_event) = receipt_event {
488 let new_pending = selector.handle_new_receipt(user_id, receipt_event);
489 if !new_pending.is_empty() {
490 read_receipts.pending.extend(new_pending);
491 }
492 }
493 selector.select()
494 };
495
496 if let Some(new_receipt) = new_receipt {
497 let event_id = new_receipt.event_id.clone();
503
504 trace!(%event_id, "Saving a new active read receipt");
506 read_receipts.latest_active = Some(new_receipt);
507
508 read_receipts.find_and_process_events(
511 &event_id,
512 user_id,
513 all_events.iter(),
514 threading_support,
515 );
516
517 debug!(?read_receipts, "after finding a better receipt");
518 return;
519 }
520
521 for event in new_events {
529 read_receipts.process_event(event, user_id, threading_support);
530 }
531
532 debug!(?read_receipts, "no better receipt, {} new events", new_events.len());
533}
534
535fn marks_as_unread(event: &Raw<AnySyncTimelineEvent>, user_id: &UserId) -> bool {
537 let event = match event.deserialize() {
538 Ok(event) => event,
539 Err(err) => {
540 warn!(
541 "couldn't deserialize event {:?}: {err}",
542 event.get_field::<String>("event_id").ok().flatten()
543 );
544 return false;
545 }
546 };
547
548 if event.sender() == user_id {
549 return false;
551 }
552
553 match event {
554 AnySyncTimelineEvent::MessageLike(event) => {
555 let Some(content) = event.original_content() else {
557 tracing::trace!("not interesting because redacted");
558 return false;
559 };
560
561 if matches!(
563 content.relation(),
564 Some(ruma::events::room::encrypted::Relation::Replacement(..))
565 ) {
566 tracing::trace!("not interesting because edited");
567 return false;
568 }
569
570 match event {
571 AnySyncMessageLikeEvent::CallAnswer(_)
572 | AnySyncMessageLikeEvent::CallInvite(_)
573 | AnySyncMessageLikeEvent::CallNotify(_)
574 | AnySyncMessageLikeEvent::CallHangup(_)
575 | AnySyncMessageLikeEvent::CallCandidates(_)
576 | AnySyncMessageLikeEvent::CallNegotiate(_)
577 | AnySyncMessageLikeEvent::CallReject(_)
578 | AnySyncMessageLikeEvent::CallSelectAnswer(_)
579 | AnySyncMessageLikeEvent::PollResponse(_)
580 | AnySyncMessageLikeEvent::UnstablePollResponse(_)
581 | AnySyncMessageLikeEvent::Reaction(_)
582 | AnySyncMessageLikeEvent::RoomRedaction(_)
583 | AnySyncMessageLikeEvent::KeyVerificationStart(_)
584 | AnySyncMessageLikeEvent::KeyVerificationReady(_)
585 | AnySyncMessageLikeEvent::KeyVerificationCancel(_)
586 | AnySyncMessageLikeEvent::KeyVerificationAccept(_)
587 | AnySyncMessageLikeEvent::KeyVerificationDone(_)
588 | AnySyncMessageLikeEvent::KeyVerificationMac(_)
589 | AnySyncMessageLikeEvent::KeyVerificationKey(_) => false,
590
591 AnySyncMessageLikeEvent::PollStart(SyncMessageLikeEvent::Original(
593 OriginalSyncMessageLikeEvent {
594 content:
595 PollStartEventContent { relates_to: Some(Relation::Replacement(_)), .. },
596 ..
597 },
598 ))
599 | AnySyncMessageLikeEvent::UnstablePollStart(SyncMessageLikeEvent::Original(
600 OriginalSyncMessageLikeEvent {
601 content: UnstablePollStartEventContent::Replacement(_),
602 ..
603 },
604 )) => false,
605
606 AnySyncMessageLikeEvent::Message(_)
607 | AnySyncMessageLikeEvent::PollStart(_)
608 | AnySyncMessageLikeEvent::UnstablePollStart(_)
609 | AnySyncMessageLikeEvent::PollEnd(_)
610 | AnySyncMessageLikeEvent::UnstablePollEnd(_)
611 | AnySyncMessageLikeEvent::RoomEncrypted(_)
612 | AnySyncMessageLikeEvent::RoomMessage(_)
613 | AnySyncMessageLikeEvent::Sticker(_) => true,
614
615 _ => {
616 warn!("unhandled timeline event type: {}", event.event_type());
618 false
619 }
620 }
621 }
622
623 AnySyncTimelineEvent::State(_) => false,
624 }
625}
626
627#[cfg(test)]
628mod tests {
629 use std::{num::NonZeroUsize, ops::Not as _};
630
631 use matrix_sdk_common::{deserialized_responses::TimelineEvent, ring_buffer::RingBuffer};
632 use matrix_sdk_test::event_factory::EventFactory;
633 use ruma::{
634 EventId, UserId, event_id,
635 events::{
636 receipt::{ReceiptThread, ReceiptType},
637 room::{member::MembershipState, message::MessageType},
638 },
639 owned_event_id, owned_user_id,
640 push::Action,
641 room_id, user_id,
642 };
643
644 use super::compute_unread_counts;
645 use crate::{
646 ThreadingSupport,
647 read_receipts::{ReceiptSelector, RoomReadReceipts, marks_as_unread},
648 };
649
650 #[test]
651 fn test_room_message_marks_as_unread() {
652 let user_id = user_id!("@alice:example.org");
653 let other_user_id = user_id!("@bob:example.org");
654
655 let f = EventFactory::new();
656
657 let ev = f.text_msg("A").event_id(event_id!("$ida")).sender(other_user_id).into_raw_sync();
659 assert!(marks_as_unread(&ev, user_id));
660
661 let ev = f.text_msg("A").event_id(event_id!("$ida")).sender(user_id).into_raw_sync();
663 assert!(marks_as_unread(&ev, user_id).not());
664 }
665
666 #[test]
667 fn test_room_edit_doesnt_mark_as_unread() {
668 let user_id = user_id!("@alice:example.org");
669 let other_user_id = user_id!("@bob:example.org");
670
671 let ev = EventFactory::new()
673 .text_msg("* edited message")
674 .edit(
675 event_id!("$someeventid:localhost"),
676 MessageType::text_plain("edited message").into(),
677 )
678 .event_id(event_id!("$ida"))
679 .sender(other_user_id)
680 .into_raw_sync();
681
682 assert!(marks_as_unread(&ev, user_id).not());
683 }
684
685 #[test]
686 fn test_redaction_doesnt_mark_room_as_unread() {
687 let user_id = user_id!("@alice:example.org");
688 let other_user_id = user_id!("@bob:example.org");
689
690 let ev = EventFactory::new()
692 .redaction(event_id!("$151957878228ssqrj:localhost"))
693 .sender(other_user_id)
694 .event_id(event_id!("$151957878228ssqrJ:localhost"))
695 .into_raw_sync();
696
697 assert!(marks_as_unread(&ev, user_id).not());
698 }
699
700 #[test]
701 fn test_reaction_doesnt_mark_room_as_unread() {
702 let user_id = user_id!("@alice:example.org");
703 let other_user_id = user_id!("@bob:example.org");
704
705 let ev = EventFactory::new()
707 .reaction(event_id!("$15275047031IXQRj:localhost"), "👍")
708 .sender(other_user_id)
709 .event_id(event_id!("$15275047031IXQRi:localhost"))
710 .into_raw_sync();
711
712 assert!(marks_as_unread(&ev, user_id).not());
713 }
714
715 #[test]
716 fn test_state_event_doesnt_mark_as_unread() {
717 let user_id = user_id!("@alice:example.org");
718 let event_id = event_id!("$1");
719
720 let ev = EventFactory::new()
721 .member(user_id)
722 .membership(MembershipState::Join)
723 .display_name("Alice")
724 .event_id(event_id)
725 .into_raw_sync();
726 assert!(marks_as_unread(&ev, user_id).not());
727
728 let other_user_id = user_id!("@bob:example.org");
729 assert!(marks_as_unread(&ev, other_user_id).not());
730 }
731
732 #[test]
733 fn test_count_unread_and_mentions() {
734 fn make_event(user_id: &UserId, push_actions: Vec<Action>) -> TimelineEvent {
735 let mut ev = EventFactory::new()
736 .text_msg("A")
737 .sender(user_id)
738 .event_id(event_id!("$ida"))
739 .into_event();
740 ev.set_push_actions(push_actions);
741 ev
742 }
743
744 let user_id = user_id!("@alice:example.org");
745
746 let event = make_event(user_id, Vec::new());
748 let mut receipts = RoomReadReceipts::default();
749 receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
750 assert_eq!(receipts.num_unread, 0);
751 assert_eq!(receipts.num_mentions, 0);
752 assert_eq!(receipts.num_notifications, 0);
753
754 let event = make_event(user_id!("@bob:example.org"), Vec::new());
756 let mut receipts = RoomReadReceipts::default();
757 receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
758 assert_eq!(receipts.num_unread, 1);
759 assert_eq!(receipts.num_mentions, 0);
760 assert_eq!(receipts.num_notifications, 0);
761
762 let event = make_event(user_id!("@bob:example.org"), vec![Action::Notify]);
764 let mut receipts = RoomReadReceipts::default();
765 receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
766 assert_eq!(receipts.num_unread, 1);
767 assert_eq!(receipts.num_mentions, 0);
768 assert_eq!(receipts.num_notifications, 1);
769
770 let event = make_event(
771 user_id!("@bob:example.org"),
772 vec![Action::SetTweak(ruma::push::Tweak::Highlight(true))],
773 );
774 let mut receipts = RoomReadReceipts::default();
775 receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
776 assert_eq!(receipts.num_unread, 1);
777 assert_eq!(receipts.num_mentions, 1);
778 assert_eq!(receipts.num_notifications, 0);
779
780 let event = make_event(
781 user_id!("@bob:example.org"),
782 vec![Action::SetTweak(ruma::push::Tweak::Highlight(true)), Action::Notify],
783 );
784 let mut receipts = RoomReadReceipts::default();
785 receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
786 assert_eq!(receipts.num_unread, 1);
787 assert_eq!(receipts.num_mentions, 1);
788 assert_eq!(receipts.num_notifications, 1);
789
790 let event = make_event(user_id!("@bob:example.org"), vec![Action::Notify, Action::Notify]);
793 let mut receipts = RoomReadReceipts::default();
794 receipts.process_event(&event, user_id, ThreadingSupport::Disabled);
795 assert_eq!(receipts.num_unread, 1);
796 assert_eq!(receipts.num_mentions, 0);
797 assert_eq!(receipts.num_notifications, 1);
798 }
799
800 #[test]
801 fn test_find_and_process_events() {
802 let ev0 = event_id!("$0");
803 let user_id = user_id!("@alice:example.org");
804
805 let mut receipts = RoomReadReceipts::default();
808 assert!(
809 receipts.find_and_process_events(ev0, user_id, &[], ThreadingSupport::Disabled).not()
810 );
811 assert_eq!(receipts.num_unread, 0);
812 assert_eq!(receipts.num_notifications, 0);
813 assert_eq!(receipts.num_mentions, 0);
814
815 fn make_event(event_id: &EventId) -> TimelineEvent {
818 EventFactory::new()
819 .text_msg("A")
820 .sender(user_id!("@bob:example.org"))
821 .event_id(event_id)
822 .into()
823 }
824
825 let mut receipts = RoomReadReceipts {
826 num_unread: 42,
827 num_notifications: 13,
828 num_mentions: 37,
829 ..Default::default()
830 };
831 assert!(
832 receipts
833 .find_and_process_events(
834 ev0,
835 user_id,
836 &[make_event(event_id!("$1"))],
837 ThreadingSupport::Disabled
838 )
839 .not()
840 );
841 assert_eq!(receipts.num_unread, 42);
842 assert_eq!(receipts.num_notifications, 13);
843 assert_eq!(receipts.num_mentions, 37);
844
845 let mut receipts = RoomReadReceipts {
849 num_unread: 42,
850 num_notifications: 13,
851 num_mentions: 37,
852 ..Default::default()
853 };
854 assert!(receipts.find_and_process_events(
855 ev0,
856 user_id,
857 &[make_event(ev0)],
858 ThreadingSupport::Disabled
859 ),);
860 assert_eq!(receipts.num_unread, 0);
861 assert_eq!(receipts.num_notifications, 0);
862 assert_eq!(receipts.num_mentions, 0);
863
864 let mut receipts = RoomReadReceipts {
867 num_unread: 42,
868 num_notifications: 13,
869 num_mentions: 37,
870 ..Default::default()
871 };
872 assert!(
873 receipts
874 .find_and_process_events(
875 ev0,
876 user_id,
877 &[
878 make_event(event_id!("$1")),
879 make_event(event_id!("$2")),
880 make_event(event_id!("$3"))
881 ],
882 ThreadingSupport::Disabled
883 )
884 .not()
885 );
886 assert_eq!(receipts.num_unread, 42);
887 assert_eq!(receipts.num_notifications, 13);
888 assert_eq!(receipts.num_mentions, 37);
889
890 let mut receipts = RoomReadReceipts {
893 num_unread: 42,
894 num_notifications: 13,
895 num_mentions: 37,
896 ..Default::default()
897 };
898 assert!(receipts.find_and_process_events(
899 ev0,
900 user_id,
901 &[
902 make_event(event_id!("$1")),
903 make_event(ev0),
904 make_event(event_id!("$2")),
905 make_event(event_id!("$3"))
906 ],
907 ThreadingSupport::Disabled
908 ));
909 assert_eq!(receipts.num_unread, 2);
910 assert_eq!(receipts.num_notifications, 0);
911 assert_eq!(receipts.num_mentions, 0);
912
913 let mut receipts = RoomReadReceipts {
915 num_unread: 42,
916 num_notifications: 13,
917 num_mentions: 37,
918 ..Default::default()
919 };
920 assert!(receipts.find_and_process_events(
921 ev0,
922 user_id,
923 &[
924 make_event(ev0),
925 make_event(event_id!("$1")),
926 make_event(ev0),
927 make_event(event_id!("$2")),
928 make_event(event_id!("$3"))
929 ],
930 ThreadingSupport::Disabled
931 ));
932 assert_eq!(receipts.num_unread, 2);
933 assert_eq!(receipts.num_notifications, 0);
934 assert_eq!(receipts.num_mentions, 0);
935 }
936
937 #[test]
939 fn test_basic_compute_unread_counts() {
940 let user_id = user_id!("@alice:example.org");
941 let other_user_id = user_id!("@bob:example.org");
942 let room_id = room_id!("!room:example.org");
943 let receipt_event_id = event_id!("$1");
944
945 let mut previous_events = Vec::new();
946
947 let f = EventFactory::new();
948 let ev1 = f.text_msg("A").sender(other_user_id).event_id(receipt_event_id).into_event();
949 let ev2 = f.text_msg("A").sender(other_user_id).event_id(event_id!("$2")).into_event();
950
951 let receipt_event = f
952 .read_receipts()
953 .add(receipt_event_id, user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
954 .into_content();
955
956 let mut read_receipts = RoomReadReceipts::default();
957 compute_unread_counts(
958 user_id,
959 room_id,
960 Some(&receipt_event),
961 previous_events.clone(),
962 &[ev1.clone(), ev2.clone()],
963 &mut read_receipts,
964 ThreadingSupport::Disabled,
965 );
966
967 assert_eq!(read_receipts.num_unread, 1);
969
970 previous_events.push(ev1);
972 previous_events.push(ev2);
973
974 let new_event =
975 f.text_msg("A").sender(other_user_id).event_id(event_id!("$3")).into_event();
976 compute_unread_counts(
977 user_id,
978 room_id,
979 Some(&receipt_event),
980 previous_events,
981 &[new_event],
982 &mut read_receipts,
983 ThreadingSupport::Disabled,
984 );
985
986 assert_eq!(read_receipts.num_unread, 2);
988 }
989
990 fn make_test_events(user_id: &UserId) -> Vec<TimelineEvent> {
991 let f = EventFactory::new().sender(user_id);
992 let ev1 = f.text_msg("With the lights out, it's less dangerous").event_id(event_id!("$1"));
993 let ev2 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$2"));
994 let ev3 = f.text_msg("I feel stupid and contagious").event_id(event_id!("$3"));
995 let ev4 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$4"));
996 let ev5 = f.text_msg("Hello, hello, hello, how low?").event_id(event_id!("$5"));
997 [ev1, ev2, ev3, ev4, ev5].into_iter().map(Into::into).collect()
998 }
999
1000 #[test]
1003 fn test_compute_unread_counts_multiple_receipts_in_one_event() {
1004 let user_id = user_id!("@alice:example.org");
1005 let room_id = room_id!("!room:example.org");
1006
1007 let all_events = make_test_events(user_id!("@bob:example.org"));
1008 let head_events: Vec<_> = all_events.iter().take(2).cloned().collect();
1009 let tail_events: Vec<_> = all_events.iter().skip(2).cloned().collect();
1010
1011 let f = EventFactory::new();
1014 for receipt_type_1 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
1015 for receipt_thread_1 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
1016 for receipt_type_2 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
1017 for receipt_thread_2 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
1018 let receipt_event = f
1019 .read_receipts()
1020 .add(
1021 event_id!("$2"),
1022 user_id,
1023 receipt_type_1.clone(),
1024 receipt_thread_1.clone(),
1025 )
1026 .add(
1027 event_id!("$3"),
1028 user_id,
1029 receipt_type_2.clone(),
1030 receipt_thread_2.clone(),
1031 )
1032 .add(
1033 event_id!("$1"),
1034 user_id,
1035 receipt_type_1.clone(),
1036 receipt_thread_2.clone(),
1037 )
1038 .into_content();
1039
1040 let mut read_receipts = RoomReadReceipts::default();
1042
1043 compute_unread_counts(
1044 user_id,
1045 room_id,
1046 Some(&receipt_event),
1047 all_events.clone(),
1048 &[],
1049 &mut read_receipts,
1050 ThreadingSupport::Disabled,
1051 );
1052
1053 assert!(
1054 read_receipts != Default::default(),
1055 "read receipts have been updated"
1056 );
1057
1058 assert_eq!(read_receipts.num_unread, 2);
1060 assert_eq!(read_receipts.num_mentions, 0);
1061 assert_eq!(read_receipts.num_notifications, 0);
1062
1063 let mut read_receipts = RoomReadReceipts::default();
1065 compute_unread_counts(
1066 user_id,
1067 room_id,
1068 Some(&receipt_event),
1069 head_events.clone(),
1070 &tail_events,
1071 &mut read_receipts,
1072 ThreadingSupport::Disabled,
1073 );
1074
1075 assert!(
1076 read_receipts != Default::default(),
1077 "read receipts have been updated"
1078 );
1079
1080 assert_eq!(read_receipts.num_unread, 2);
1082 assert_eq!(read_receipts.num_mentions, 0);
1083 assert_eq!(read_receipts.num_notifications, 0);
1084 }
1085 }
1086 }
1087 }
1088 }
1089
1090 #[test]
1094 fn test_compute_unread_counts_updated_after_field_tracking() {
1095 let user_id = owned_user_id!("@alice:example.org");
1096 let room_id = room_id!("!room:example.org");
1097
1098 let events = make_test_events(user_id!("@bob:example.org"));
1099
1100 let receipt_event = EventFactory::new()
1101 .read_receipts()
1102 .add(event_id!("$6"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1103 .into_content();
1104
1105 let mut read_receipts = RoomReadReceipts::default();
1106 assert!(read_receipts.pending.is_empty());
1107
1108 compute_unread_counts(
1111 &user_id,
1112 room_id,
1113 Some(&receipt_event),
1114 events,
1115 &[], &mut read_receipts,
1117 ThreadingSupport::Disabled,
1118 );
1119
1120 assert_eq!(read_receipts.num_unread, 0);
1122
1123 assert_eq!(read_receipts.pending.len(), 1);
1125 assert!(read_receipts.pending.iter().any(|ev| ev == event_id!("$6")));
1126 }
1127
1128 #[test]
1129 fn test_compute_unread_counts_limited_sync() {
1130 let user_id = owned_user_id!("@alice:example.org");
1131 let room_id = room_id!("!room:example.org");
1132
1133 let events = make_test_events(user_id!("@bob:example.org"));
1134
1135 let receipt_event = EventFactory::new()
1136 .read_receipts()
1137 .add(event_id!("$1"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1138 .into_content();
1139
1140 let mut read_receipts = RoomReadReceipts::default();
1144 assert!(read_receipts.pending.is_empty());
1145
1146 let ev0 = events[0].clone();
1147
1148 compute_unread_counts(
1149 &user_id,
1150 room_id,
1151 Some(&receipt_event),
1152 events,
1153 &[ev0], &mut read_receipts,
1155 ThreadingSupport::Disabled,
1156 );
1157
1158 assert_eq!(read_receipts.num_unread, 0);
1160 assert!(read_receipts.pending.is_empty());
1161 }
1162
1163 #[test]
1164 fn test_receipt_selector_create_sync_index() {
1165 let uid = user_id!("@bob:example.org");
1166
1167 let events = make_test_events(uid);
1168
1169 let ev6 = EventFactory::new().text_msg("yolo").sender(uid).no_event_id().into_event();
1171
1172 let index = ReceiptSelector::create_sync_index(events.iter().chain(&[ev6]));
1173
1174 assert_eq!(*index.get(event_id!("$1")).unwrap(), 0);
1175 assert_eq!(*index.get(event_id!("$2")).unwrap(), 1);
1176 assert_eq!(*index.get(event_id!("$3")).unwrap(), 2);
1177 assert_eq!(*index.get(event_id!("$4")).unwrap(), 3);
1178 assert_eq!(*index.get(event_id!("$5")).unwrap(), 4);
1179 assert_eq!(index.get(event_id!("$6")), None);
1180
1181 assert_eq!(index.len(), 5);
1182
1183 let index = ReceiptSelector::create_sync_index(
1185 [events[1].clone(), events[2].clone(), events[4].clone()].iter(),
1186 );
1187
1188 assert_eq!(*index.get(event_id!("$2")).unwrap(), 0);
1189 assert_eq!(*index.get(event_id!("$3")).unwrap(), 1);
1190 assert_eq!(*index.get(event_id!("$5")).unwrap(), 2);
1191
1192 assert_eq!(index.len(), 3);
1193 }
1194
1195 #[test]
1196 fn test_receipt_selector_try_select_later() {
1197 let events = make_test_events(user_id!("@bob:example.org"));
1198
1199 {
1200 let mut selector = ReceiptSelector::new(&[], None);
1202 selector.try_select_later(event_id!("$1"), 0);
1203 let best_receipt = selector.select();
1204 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1205 }
1206
1207 {
1208 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1210 selector.try_select_later(event_id!("$1"), 0);
1211 let best_receipt = selector.select();
1212 assert!(best_receipt.is_none());
1213 }
1214
1215 {
1216 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$1")));
1219 selector.try_select_later(event_id!("$1"), 0);
1220 let best_receipt = selector.select();
1221 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1222 }
1223
1224 {
1225 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1227 selector.try_select_later(event_id!("$4"), 3);
1228 let best_receipt = selector.select();
1229 assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1230 }
1231 }
1232
1233 #[test]
1234 fn test_receipt_selector_handle_pending_receipts_noop() {
1235 let sender = user_id!("@bob:example.org");
1236 let f = EventFactory::new().sender(sender);
1237 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1238 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1239 let events = &[ev1, ev2][..];
1240
1241 {
1242 let mut selector = ReceiptSelector::new(events, None);
1244
1245 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1246 selector.handle_pending_receipts(&mut pending);
1247
1248 assert!(pending.is_empty());
1249
1250 let best_receipt = selector.select();
1251 assert!(best_receipt.is_none());
1252 }
1253
1254 {
1255 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1258
1259 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1260 selector.handle_pending_receipts(&mut pending);
1261
1262 assert!(pending.is_empty());
1263
1264 let best_receipt = selector.select();
1265 assert!(best_receipt.is_none());
1266 }
1267 }
1268
1269 #[test]
1270 fn test_receipt_selector_handle_pending_receipts_doesnt_match_known_events() {
1271 let sender = user_id!("@bob:example.org");
1272 let f = EventFactory::new().sender(sender);
1273 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1274 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1275 let events = &[ev1, ev2][..];
1276
1277 {
1278 let mut selector = ReceiptSelector::new(events, None);
1280
1281 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1282 pending.push(owned_event_id!("$3"));
1283 selector.handle_pending_receipts(&mut pending);
1284
1285 assert_eq!(pending.len(), 1);
1286
1287 let best_receipt = selector.select();
1288 assert!(best_receipt.is_none());
1289 }
1290
1291 {
1292 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1294
1295 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1296 pending.push(owned_event_id!("$3"));
1297 selector.handle_pending_receipts(&mut pending);
1298
1299 assert_eq!(pending.len(), 1);
1300
1301 let best_receipt = selector.select();
1302 assert!(best_receipt.is_none());
1303 }
1304 }
1305
1306 #[test]
1307 fn test_receipt_selector_handle_pending_receipts_matches_known_events_no_initial() {
1308 let sender = user_id!("@bob:example.org");
1309 let f = EventFactory::new().sender(sender);
1310 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1311 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1312 let events = &[ev1, ev2][..];
1313
1314 {
1315 let mut selector = ReceiptSelector::new(events, None);
1317
1318 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1319 pending.push(owned_event_id!("$2"));
1320 selector.handle_pending_receipts(&mut pending);
1321
1322 assert!(pending.is_empty());
1324
1325 let best_receipt = selector.select();
1327 assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1328 }
1329
1330 {
1331 let mut selector = ReceiptSelector::new(events, None);
1333
1334 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1335 pending.push(owned_event_id!("$1"));
1336 pending.push(owned_event_id!("$3"));
1337 selector.handle_pending_receipts(&mut pending);
1338
1339 assert_eq!(pending.len(), 1);
1341 assert!(pending.iter().any(|ev| ev == event_id!("$3")));
1342
1343 let best_receipt = selector.select();
1344 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1345 }
1346 }
1347
1348 #[test]
1349 fn test_receipt_selector_handle_pending_receipts_matches_known_events_with_initial() {
1350 let sender = user_id!("@bob:example.org");
1351 let f = EventFactory::new().sender(sender);
1352 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1353 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1354 let events = &[ev1, ev2][..];
1355
1356 {
1357 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1360
1361 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1362 pending.push(owned_event_id!("$2"));
1363 selector.handle_pending_receipts(&mut pending);
1364
1365 assert!(pending.is_empty());
1367
1368 let best_receipt = selector.select();
1370 assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1371 }
1372
1373 {
1374 let mut selector = ReceiptSelector::new(events, Some(event_id!("$2")));
1376
1377 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1378 pending.push(owned_event_id!("$1"));
1379 selector.handle_pending_receipts(&mut pending);
1380
1381 assert!(pending.is_empty());
1383
1384 let best_receipt = selector.select();
1385 assert!(best_receipt.is_none());
1386 }
1387 }
1388
1389 #[test]
1390 fn test_receipt_selector_handle_new_receipt() {
1391 let myself = user_id!("@alice:example.org");
1392 let events = make_test_events(user_id!("@bob:example.org"));
1393
1394 let f = EventFactory::new();
1395 {
1396 let mut selector = ReceiptSelector::new(&events, None);
1398
1399 let receipt_event = f
1400 .read_receipts()
1401 .add(
1402 event_id!("$5"),
1403 myself,
1404 ReceiptType::Read,
1405 ReceiptThread::Thread(owned_event_id!("$2")),
1406 )
1407 .into_content();
1408
1409 let pending = selector.handle_new_receipt(myself, &receipt_event);
1410 assert!(pending.is_empty());
1411
1412 let best_receipt = selector.select();
1413 assert!(best_receipt.is_none());
1414 }
1415
1416 for receipt_type in [ReceiptType::Read, ReceiptType::ReadPrivate] {
1417 for receipt_thread in [ReceiptThread::Main, ReceiptThread::Unthreaded] {
1418 {
1419 let mut selector = ReceiptSelector::new(&events, None);
1422
1423 let receipt_event = f
1424 .read_receipts()
1425 .add(event_id!("$6"), myself, receipt_type.clone(), receipt_thread.clone())
1426 .into_content();
1427
1428 let pending = selector.handle_new_receipt(myself, &receipt_event);
1429 assert_eq!(pending[0], event_id!("$6"));
1430 assert_eq!(pending.len(), 1);
1431
1432 let best_receipt = selector.select();
1433 assert!(best_receipt.is_none());
1434 }
1435
1436 {
1437 let mut selector = ReceiptSelector::new(&events, None);
1440
1441 let receipt_event = f
1442 .read_receipts()
1443 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1444 .into_content();
1445
1446 let pending = selector.handle_new_receipt(myself, &receipt_event);
1447 assert!(pending.is_empty());
1448
1449 let best_receipt = selector.select();
1450 assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1451 }
1452
1453 {
1454 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$4")));
1457
1458 let receipt_event = f
1459 .read_receipts()
1460 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1461 .into_content();
1462
1463 let pending = selector.handle_new_receipt(myself, &receipt_event);
1464 assert!(pending.is_empty());
1465
1466 let best_receipt = selector.select();
1467 assert!(best_receipt.is_none());
1468 }
1469
1470 {
1471 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1474
1475 let receipt_event = f
1476 .read_receipts()
1477 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1478 .into_content();
1479
1480 let pending = selector.handle_new_receipt(myself, &receipt_event);
1481 assert!(pending.is_empty());
1482
1483 let best_receipt = selector.select();
1484 assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1485 }
1486 }
1487 } {
1490 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1493
1494 let receipt_event = f
1495 .read_receipts()
1496 .add(event_id!("$4"), myself, ReceiptType::ReadPrivate, ReceiptThread::Unthreaded)
1497 .add(event_id!("$6"), myself, ReceiptType::ReadPrivate, ReceiptThread::Main)
1498 .add(event_id!("$3"), myself, ReceiptType::Read, ReceiptThread::Main)
1499 .into_content();
1500
1501 let pending = selector.handle_new_receipt(myself, &receipt_event);
1502 assert_eq!(pending.len(), 1);
1503 assert_eq!(pending[0], event_id!("$6"));
1504
1505 let best_receipt = selector.select();
1506 assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1507 }
1508 }
1509
1510 #[test]
1511 fn test_try_match_implicit() {
1512 let myself = owned_user_id!("@alice:example.org");
1513 let bob = user_id!("@bob:example.org");
1514
1515 let mut events = make_test_events(bob);
1516
1517 let mut selector = ReceiptSelector::new(&events, None);
1519 selector.try_match_implicit(&myself, &events);
1521 let best_receipt = selector.select();
1523 assert!(best_receipt.is_none());
1524
1525 let f = EventFactory::new();
1527 events.push(
1528 f.text_msg("A mulatto, an albino")
1529 .sender(&myself)
1530 .event_id(event_id!("$6"))
1531 .into_event(),
1532 );
1533 events.push(
1534 f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1535 );
1536
1537 let mut selector = ReceiptSelector::new(&events, None);
1538 selector.try_match_implicit(&myself, &events);
1540 let best_receipt = selector.select();
1542 assert_eq!(best_receipt.unwrap().event_id, event_id!("$6"));
1543 }
1544
1545 #[test]
1546 fn test_compute_unread_counts_with_implicit_receipt() {
1547 let user_id = user_id!("@alice:example.org");
1548 let bob = user_id!("@bob:example.org");
1549 let room_id = room_id!("!room:example.org");
1550
1551 let mut events = make_test_events(bob);
1553
1554 let f = EventFactory::new();
1556 events.push(
1557 f.text_msg("A mulatto, an albino")
1558 .sender(user_id)
1559 .event_id(event_id!("$6"))
1560 .into_event(),
1561 );
1562
1563 events.push(
1565 f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1566 );
1567 events.push(
1568 f.text_msg("A denial, a denial").sender(bob).event_id(event_id!("$8")).into_event(),
1569 );
1570
1571 let events: Vec<_> = events.into_iter().collect();
1572
1573 let receipt_event = f
1575 .read_receipts()
1576 .add(event_id!("$3"), user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1577 .into_content();
1578
1579 let mut read_receipts = RoomReadReceipts::default();
1580
1581 compute_unread_counts(
1584 user_id,
1585 room_id,
1586 Some(&receipt_event),
1587 Vec::new(),
1588 &events,
1589 &mut read_receipts,
1590 ThreadingSupport::Disabled,
1591 );
1592
1593 assert_eq!(read_receipts.num_unread, 2);
1595
1596 assert!(read_receipts.pending.is_empty());
1598
1599 assert_eq!(read_receipts.latest_active.unwrap().event_id, event_id!("$6"));
1601 }
1602
1603 #[test]
1604 fn test_compute_unread_counts_with_threading_enabled() {
1605 fn make_event(user_id: &UserId, thread_root: &EventId) -> TimelineEvent {
1606 EventFactory::new()
1607 .text_msg("A")
1608 .sender(user_id)
1609 .event_id(event_id!("$ida"))
1610 .in_thread(thread_root, event_id!("$latest_event"))
1611 .into_event()
1612 }
1613
1614 let mut receipts = RoomReadReceipts::default();
1615
1616 let own_alice = user_id!("@alice:example.org");
1617 let bob = user_id!("@bob:example.org");
1618
1619 receipts.process_event(
1622 &make_event(own_alice, event_id!("$some_thread_root")),
1623 own_alice,
1624 ThreadingSupport::Enabled { with_subscriptions: false },
1625 );
1626 receipts.process_event(
1627 &make_event(own_alice, event_id!("$some_other_thread_root")),
1628 own_alice,
1629 ThreadingSupport::Enabled { with_subscriptions: false },
1630 );
1631
1632 receipts.process_event(
1633 &make_event(bob, event_id!("$some_thread_root")),
1634 own_alice,
1635 ThreadingSupport::Enabled { with_subscriptions: false },
1636 );
1637 receipts.process_event(
1638 &make_event(bob, event_id!("$some_other_thread_root")),
1639 own_alice,
1640 ThreadingSupport::Enabled { with_subscriptions: false },
1641 );
1642
1643 assert_eq!(receipts.num_unread, 0);
1644 assert_eq!(receipts.num_mentions, 0);
1645 assert_eq!(receipts.num_notifications, 0);
1646
1647 receipts.process_event(
1649 &EventFactory::new().text_msg("A").sender(bob).event_id(event_id!("$ida")).into_event(),
1650 own_alice,
1651 ThreadingSupport::Enabled { with_subscriptions: false },
1652 );
1653
1654 assert_eq!(receipts.num_unread, 1);
1655 assert_eq!(receipts.num_mentions, 0);
1656 assert_eq!(receipts.num_notifications, 0);
1657 }
1658}