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 events::{
131 poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
132 receipt::{ReceiptEventContent, ReceiptThread, ReceiptType},
133 room::message::Relation,
134 AnySyncMessageLikeEvent, AnySyncTimelineEvent, OriginalSyncMessageLikeEvent,
135 SyncMessageLikeEvent,
136 },
137 serde::Raw,
138 EventId, OwnedEventId, OwnedUserId, RoomId, UserId,
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 if event_id == receipt_event_id {
269 trace!("Found the event the receipt was referring to! Starting to count.");
272 self.reset();
273 counting_receipts = true;
274 continue;
275 }
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(receipt) = receipts.get(&ty).and_then(|receipts| receipts.get(user_id))
391 {
392 if matches!(receipt.thread, ReceiptThread::Main | ReceiptThread::Unthreaded) {
393 trace!(%event_id, "found new candidate");
394 if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
395 self.try_select_later(event_id, *event_pos);
396 } else {
397 trace!(%event_id, "stashed as pending");
399 pending.push(event_id.clone());
400 }
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 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, EventId, UserId,
642 };
643
644 use super::compute_unread_counts;
645 use crate::{
646 read_receipts::{marks_as_unread, ReceiptSelector, RoomReadReceipts},
647 ThreadingSupport,
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!(receipts
809 .find_and_process_events(ev0, user_id, &[], ThreadingSupport::Disabled)
810 .not());
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!(receipts
832 .find_and_process_events(
833 ev0,
834 user_id,
835 &[make_event(event_id!("$1"))],
836 ThreadingSupport::Disabled
837 )
838 .not());
839 assert_eq!(receipts.num_unread, 42);
840 assert_eq!(receipts.num_notifications, 13);
841 assert_eq!(receipts.num_mentions, 37);
842
843 let mut receipts = RoomReadReceipts {
847 num_unread: 42,
848 num_notifications: 13,
849 num_mentions: 37,
850 ..Default::default()
851 };
852 assert!(receipts.find_and_process_events(
853 ev0,
854 user_id,
855 &[make_event(ev0)],
856 ThreadingSupport::Disabled
857 ),);
858 assert_eq!(receipts.num_unread, 0);
859 assert_eq!(receipts.num_notifications, 0);
860 assert_eq!(receipts.num_mentions, 0);
861
862 let mut receipts = RoomReadReceipts {
865 num_unread: 42,
866 num_notifications: 13,
867 num_mentions: 37,
868 ..Default::default()
869 };
870 assert!(receipts
871 .find_and_process_events(
872 ev0,
873 user_id,
874 &[
875 make_event(event_id!("$1")),
876 make_event(event_id!("$2")),
877 make_event(event_id!("$3"))
878 ],
879 ThreadingSupport::Disabled
880 )
881 .not());
882 assert_eq!(receipts.num_unread, 42);
883 assert_eq!(receipts.num_notifications, 13);
884 assert_eq!(receipts.num_mentions, 37);
885
886 let mut receipts = RoomReadReceipts {
889 num_unread: 42,
890 num_notifications: 13,
891 num_mentions: 37,
892 ..Default::default()
893 };
894 assert!(receipts.find_and_process_events(
895 ev0,
896 user_id,
897 &[
898 make_event(event_id!("$1")),
899 make_event(ev0),
900 make_event(event_id!("$2")),
901 make_event(event_id!("$3"))
902 ],
903 ThreadingSupport::Disabled
904 ));
905 assert_eq!(receipts.num_unread, 2);
906 assert_eq!(receipts.num_notifications, 0);
907 assert_eq!(receipts.num_mentions, 0);
908
909 let mut receipts = RoomReadReceipts {
911 num_unread: 42,
912 num_notifications: 13,
913 num_mentions: 37,
914 ..Default::default()
915 };
916 assert!(receipts.find_and_process_events(
917 ev0,
918 user_id,
919 &[
920 make_event(ev0),
921 make_event(event_id!("$1")),
922 make_event(ev0),
923 make_event(event_id!("$2")),
924 make_event(event_id!("$3"))
925 ],
926 ThreadingSupport::Disabled
927 ));
928 assert_eq!(receipts.num_unread, 2);
929 assert_eq!(receipts.num_notifications, 0);
930 assert_eq!(receipts.num_mentions, 0);
931 }
932
933 #[test]
935 fn test_basic_compute_unread_counts() {
936 let user_id = user_id!("@alice:example.org");
937 let other_user_id = user_id!("@bob:example.org");
938 let room_id = room_id!("!room:example.org");
939 let receipt_event_id = event_id!("$1");
940
941 let mut previous_events = Vec::new();
942
943 let f = EventFactory::new();
944 let ev1 = f.text_msg("A").sender(other_user_id).event_id(receipt_event_id).into_event();
945 let ev2 = f.text_msg("A").sender(other_user_id).event_id(event_id!("$2")).into_event();
946
947 let receipt_event = f
948 .read_receipts()
949 .add(receipt_event_id, user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
950 .into_content();
951
952 let mut read_receipts = RoomReadReceipts::default();
953 compute_unread_counts(
954 user_id,
955 room_id,
956 Some(&receipt_event),
957 previous_events.clone(),
958 &[ev1.clone(), ev2.clone()],
959 &mut read_receipts,
960 ThreadingSupport::Disabled,
961 );
962
963 assert_eq!(read_receipts.num_unread, 1);
965
966 previous_events.push(ev1);
968 previous_events.push(ev2);
969
970 let new_event =
971 f.text_msg("A").sender(other_user_id).event_id(event_id!("$3")).into_event();
972 compute_unread_counts(
973 user_id,
974 room_id,
975 Some(&receipt_event),
976 previous_events,
977 &[new_event],
978 &mut read_receipts,
979 ThreadingSupport::Disabled,
980 );
981
982 assert_eq!(read_receipts.num_unread, 2);
984 }
985
986 fn make_test_events(user_id: &UserId) -> Vec<TimelineEvent> {
987 let f = EventFactory::new().sender(user_id);
988 let ev1 = f.text_msg("With the lights out, it's less dangerous").event_id(event_id!("$1"));
989 let ev2 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$2"));
990 let ev3 = f.text_msg("I feel stupid and contagious").event_id(event_id!("$3"));
991 let ev4 = f.text_msg("Here we are now, entertain us").event_id(event_id!("$4"));
992 let ev5 = f.text_msg("Hello, hello, hello, how low?").event_id(event_id!("$5"));
993 [ev1, ev2, ev3, ev4, ev5].into_iter().map(Into::into).collect()
994 }
995
996 #[test]
999 fn test_compute_unread_counts_multiple_receipts_in_one_event() {
1000 let user_id = user_id!("@alice:example.org");
1001 let room_id = room_id!("!room:example.org");
1002
1003 let all_events = make_test_events(user_id!("@bob:example.org"));
1004 let head_events: Vec<_> = all_events.iter().take(2).cloned().collect();
1005 let tail_events: Vec<_> = all_events.iter().skip(2).cloned().collect();
1006
1007 let f = EventFactory::new();
1010 for receipt_type_1 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
1011 for receipt_thread_1 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
1012 for receipt_type_2 in &[ReceiptType::Read, ReceiptType::ReadPrivate] {
1013 for receipt_thread_2 in &[ReceiptThread::Unthreaded, ReceiptThread::Main] {
1014 let receipt_event = f
1015 .read_receipts()
1016 .add(
1017 event_id!("$2"),
1018 user_id,
1019 receipt_type_1.clone(),
1020 receipt_thread_1.clone(),
1021 )
1022 .add(
1023 event_id!("$3"),
1024 user_id,
1025 receipt_type_2.clone(),
1026 receipt_thread_2.clone(),
1027 )
1028 .add(
1029 event_id!("$1"),
1030 user_id,
1031 receipt_type_1.clone(),
1032 receipt_thread_2.clone(),
1033 )
1034 .into_content();
1035
1036 let mut read_receipts = RoomReadReceipts::default();
1038
1039 compute_unread_counts(
1040 user_id,
1041 room_id,
1042 Some(&receipt_event),
1043 all_events.clone(),
1044 &[],
1045 &mut read_receipts,
1046 ThreadingSupport::Disabled,
1047 );
1048
1049 assert!(
1050 read_receipts != Default::default(),
1051 "read receipts have been updated"
1052 );
1053
1054 assert_eq!(read_receipts.num_unread, 2);
1056 assert_eq!(read_receipts.num_mentions, 0);
1057 assert_eq!(read_receipts.num_notifications, 0);
1058
1059 let mut read_receipts = RoomReadReceipts::default();
1061 compute_unread_counts(
1062 user_id,
1063 room_id,
1064 Some(&receipt_event),
1065 head_events.clone(),
1066 &tail_events,
1067 &mut read_receipts,
1068 ThreadingSupport::Disabled,
1069 );
1070
1071 assert!(
1072 read_receipts != Default::default(),
1073 "read receipts have been updated"
1074 );
1075
1076 assert_eq!(read_receipts.num_unread, 2);
1078 assert_eq!(read_receipts.num_mentions, 0);
1079 assert_eq!(read_receipts.num_notifications, 0);
1080 }
1081 }
1082 }
1083 }
1084 }
1085
1086 #[test]
1090 fn test_compute_unread_counts_updated_after_field_tracking() {
1091 let user_id = owned_user_id!("@alice:example.org");
1092 let room_id = room_id!("!room:example.org");
1093
1094 let events = make_test_events(user_id!("@bob:example.org"));
1095
1096 let receipt_event = EventFactory::new()
1097 .read_receipts()
1098 .add(event_id!("$6"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1099 .into_content();
1100
1101 let mut read_receipts = RoomReadReceipts::default();
1102 assert!(read_receipts.pending.is_empty());
1103
1104 compute_unread_counts(
1107 &user_id,
1108 room_id,
1109 Some(&receipt_event),
1110 events,
1111 &[], &mut read_receipts,
1113 ThreadingSupport::Disabled,
1114 );
1115
1116 assert_eq!(read_receipts.num_unread, 0);
1118
1119 assert_eq!(read_receipts.pending.len(), 1);
1121 assert!(read_receipts.pending.iter().any(|ev| ev == event_id!("$6")));
1122 }
1123
1124 #[test]
1125 fn test_compute_unread_counts_limited_sync() {
1126 let user_id = owned_user_id!("@alice:example.org");
1127 let room_id = room_id!("!room:example.org");
1128
1129 let events = make_test_events(user_id!("@bob:example.org"));
1130
1131 let receipt_event = EventFactory::new()
1132 .read_receipts()
1133 .add(event_id!("$1"), &user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1134 .into_content();
1135
1136 let mut read_receipts = RoomReadReceipts::default();
1140 assert!(read_receipts.pending.is_empty());
1141
1142 let ev0 = events[0].clone();
1143
1144 compute_unread_counts(
1145 &user_id,
1146 room_id,
1147 Some(&receipt_event),
1148 events,
1149 &[ev0], &mut read_receipts,
1151 ThreadingSupport::Disabled,
1152 );
1153
1154 assert_eq!(read_receipts.num_unread, 0);
1156 assert!(read_receipts.pending.is_empty());
1157 }
1158
1159 #[test]
1160 fn test_receipt_selector_create_sync_index() {
1161 let uid = user_id!("@bob:example.org");
1162
1163 let events = make_test_events(uid);
1164
1165 let ev6 = EventFactory::new().text_msg("yolo").sender(uid).no_event_id().into_event();
1167
1168 let index = ReceiptSelector::create_sync_index(events.iter().chain(&[ev6]));
1169
1170 assert_eq!(*index.get(event_id!("$1")).unwrap(), 0);
1171 assert_eq!(*index.get(event_id!("$2")).unwrap(), 1);
1172 assert_eq!(*index.get(event_id!("$3")).unwrap(), 2);
1173 assert_eq!(*index.get(event_id!("$4")).unwrap(), 3);
1174 assert_eq!(*index.get(event_id!("$5")).unwrap(), 4);
1175 assert_eq!(index.get(event_id!("$6")), None);
1176
1177 assert_eq!(index.len(), 5);
1178
1179 let index = ReceiptSelector::create_sync_index(
1181 [events[1].clone(), events[2].clone(), events[4].clone()].iter(),
1182 );
1183
1184 assert_eq!(*index.get(event_id!("$2")).unwrap(), 0);
1185 assert_eq!(*index.get(event_id!("$3")).unwrap(), 1);
1186 assert_eq!(*index.get(event_id!("$5")).unwrap(), 2);
1187
1188 assert_eq!(index.len(), 3);
1189 }
1190
1191 #[test]
1192 fn test_receipt_selector_try_select_later() {
1193 let events = make_test_events(user_id!("@bob:example.org"));
1194
1195 {
1196 let mut selector = ReceiptSelector::new(&[], None);
1198 selector.try_select_later(event_id!("$1"), 0);
1199 let best_receipt = selector.select();
1200 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1201 }
1202
1203 {
1204 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1206 selector.try_select_later(event_id!("$1"), 0);
1207 let best_receipt = selector.select();
1208 assert!(best_receipt.is_none());
1209 }
1210
1211 {
1212 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$1")));
1215 selector.try_select_later(event_id!("$1"), 0);
1216 let best_receipt = selector.select();
1217 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1218 }
1219
1220 {
1221 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$3")));
1223 selector.try_select_later(event_id!("$4"), 3);
1224 let best_receipt = selector.select();
1225 assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1226 }
1227 }
1228
1229 #[test]
1230 fn test_receipt_selector_handle_pending_receipts_noop() {
1231 let sender = user_id!("@bob:example.org");
1232 let f = EventFactory::new().sender(sender);
1233 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1234 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1235 let events = &[ev1, ev2][..];
1236
1237 {
1238 let mut selector = ReceiptSelector::new(events, None);
1240
1241 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1242 selector.handle_pending_receipts(&mut pending);
1243
1244 assert!(pending.is_empty());
1245
1246 let best_receipt = selector.select();
1247 assert!(best_receipt.is_none());
1248 }
1249
1250 {
1251 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1254
1255 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1256 selector.handle_pending_receipts(&mut pending);
1257
1258 assert!(pending.is_empty());
1259
1260 let best_receipt = selector.select();
1261 assert!(best_receipt.is_none());
1262 }
1263 }
1264
1265 #[test]
1266 fn test_receipt_selector_handle_pending_receipts_doesnt_match_known_events() {
1267 let sender = user_id!("@bob:example.org");
1268 let f = EventFactory::new().sender(sender);
1269 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1270 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1271 let events = &[ev1, ev2][..];
1272
1273 {
1274 let mut selector = ReceiptSelector::new(events, None);
1276
1277 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1278 pending.push(owned_event_id!("$3"));
1279 selector.handle_pending_receipts(&mut pending);
1280
1281 assert_eq!(pending.len(), 1);
1282
1283 let best_receipt = selector.select();
1284 assert!(best_receipt.is_none());
1285 }
1286
1287 {
1288 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1290
1291 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1292 pending.push(owned_event_id!("$3"));
1293 selector.handle_pending_receipts(&mut pending);
1294
1295 assert_eq!(pending.len(), 1);
1296
1297 let best_receipt = selector.select();
1298 assert!(best_receipt.is_none());
1299 }
1300 }
1301
1302 #[test]
1303 fn test_receipt_selector_handle_pending_receipts_matches_known_events_no_initial() {
1304 let sender = user_id!("@bob:example.org");
1305 let f = EventFactory::new().sender(sender);
1306 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1307 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1308 let events = &[ev1, ev2][..];
1309
1310 {
1311 let mut selector = ReceiptSelector::new(events, None);
1313
1314 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1315 pending.push(owned_event_id!("$2"));
1316 selector.handle_pending_receipts(&mut pending);
1317
1318 assert!(pending.is_empty());
1320
1321 let best_receipt = selector.select();
1323 assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1324 }
1325
1326 {
1327 let mut selector = ReceiptSelector::new(events, None);
1329
1330 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1331 pending.push(owned_event_id!("$1"));
1332 pending.push(owned_event_id!("$3"));
1333 selector.handle_pending_receipts(&mut pending);
1334
1335 assert_eq!(pending.len(), 1);
1337 assert!(pending.iter().any(|ev| ev == event_id!("$3")));
1338
1339 let best_receipt = selector.select();
1340 assert_eq!(best_receipt.unwrap().event_id, event_id!("$1"));
1341 }
1342 }
1343
1344 #[test]
1345 fn test_receipt_selector_handle_pending_receipts_matches_known_events_with_initial() {
1346 let sender = user_id!("@bob:example.org");
1347 let f = EventFactory::new().sender(sender);
1348 let ev1 = f.text_msg("yo").event_id(event_id!("$1")).into_event();
1349 let ev2 = f.text_msg("well?").event_id(event_id!("$2")).into_event();
1350 let events = &[ev1, ev2][..];
1351
1352 {
1353 let mut selector = ReceiptSelector::new(events, Some(event_id!("$1")));
1356
1357 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1358 pending.push(owned_event_id!("$2"));
1359 selector.handle_pending_receipts(&mut pending);
1360
1361 assert!(pending.is_empty());
1363
1364 let best_receipt = selector.select();
1366 assert_eq!(best_receipt.unwrap().event_id, event_id!("$2"));
1367 }
1368
1369 {
1370 let mut selector = ReceiptSelector::new(events, Some(event_id!("$2")));
1372
1373 let mut pending = RingBuffer::new(NonZeroUsize::new(16).unwrap());
1374 pending.push(owned_event_id!("$1"));
1375 selector.handle_pending_receipts(&mut pending);
1376
1377 assert!(pending.is_empty());
1379
1380 let best_receipt = selector.select();
1381 assert!(best_receipt.is_none());
1382 }
1383 }
1384
1385 #[test]
1386 fn test_receipt_selector_handle_new_receipt() {
1387 let myself = user_id!("@alice:example.org");
1388 let events = make_test_events(user_id!("@bob:example.org"));
1389
1390 let f = EventFactory::new();
1391 {
1392 let mut selector = ReceiptSelector::new(&events, None);
1394
1395 let receipt_event = f
1396 .read_receipts()
1397 .add(
1398 event_id!("$5"),
1399 myself,
1400 ReceiptType::Read,
1401 ReceiptThread::Thread(owned_event_id!("$2")),
1402 )
1403 .into_content();
1404
1405 let pending = selector.handle_new_receipt(myself, &receipt_event);
1406 assert!(pending.is_empty());
1407
1408 let best_receipt = selector.select();
1409 assert!(best_receipt.is_none());
1410 }
1411
1412 for receipt_type in [ReceiptType::Read, ReceiptType::ReadPrivate] {
1413 for receipt_thread in [ReceiptThread::Main, ReceiptThread::Unthreaded] {
1414 {
1415 let mut selector = ReceiptSelector::new(&events, None);
1418
1419 let receipt_event = f
1420 .read_receipts()
1421 .add(event_id!("$6"), myself, receipt_type.clone(), receipt_thread.clone())
1422 .into_content();
1423
1424 let pending = selector.handle_new_receipt(myself, &receipt_event);
1425 assert_eq!(pending[0], event_id!("$6"));
1426 assert_eq!(pending.len(), 1);
1427
1428 let best_receipt = selector.select();
1429 assert!(best_receipt.is_none());
1430 }
1431
1432 {
1433 let mut selector = ReceiptSelector::new(&events, None);
1436
1437 let receipt_event = f
1438 .read_receipts()
1439 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1440 .into_content();
1441
1442 let pending = selector.handle_new_receipt(myself, &receipt_event);
1443 assert!(pending.is_empty());
1444
1445 let best_receipt = selector.select();
1446 assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1447 }
1448
1449 {
1450 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$4")));
1453
1454 let receipt_event = f
1455 .read_receipts()
1456 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1457 .into_content();
1458
1459 let pending = selector.handle_new_receipt(myself, &receipt_event);
1460 assert!(pending.is_empty());
1461
1462 let best_receipt = selector.select();
1463 assert!(best_receipt.is_none());
1464 }
1465
1466 {
1467 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1470
1471 let receipt_event = f
1472 .read_receipts()
1473 .add(event_id!("$3"), myself, receipt_type.clone(), receipt_thread.clone())
1474 .into_content();
1475
1476 let pending = selector.handle_new_receipt(myself, &receipt_event);
1477 assert!(pending.is_empty());
1478
1479 let best_receipt = selector.select();
1480 assert_eq!(best_receipt.unwrap().event_id, event_id!("$3"));
1481 }
1482 }
1483 } {
1486 let mut selector = ReceiptSelector::new(&events, Some(event_id!("$2")));
1489
1490 let receipt_event = f
1491 .read_receipts()
1492 .add(event_id!("$4"), myself, ReceiptType::ReadPrivate, ReceiptThread::Unthreaded)
1493 .add(event_id!("$6"), myself, ReceiptType::ReadPrivate, ReceiptThread::Main)
1494 .add(event_id!("$3"), myself, ReceiptType::Read, ReceiptThread::Main)
1495 .into_content();
1496
1497 let pending = selector.handle_new_receipt(myself, &receipt_event);
1498 assert_eq!(pending.len(), 1);
1499 assert_eq!(pending[0], event_id!("$6"));
1500
1501 let best_receipt = selector.select();
1502 assert_eq!(best_receipt.unwrap().event_id, event_id!("$4"));
1503 }
1504 }
1505
1506 #[test]
1507 fn test_try_match_implicit() {
1508 let myself = owned_user_id!("@alice:example.org");
1509 let bob = user_id!("@bob:example.org");
1510
1511 let mut events = make_test_events(bob);
1512
1513 let mut selector = ReceiptSelector::new(&events, None);
1515 selector.try_match_implicit(&myself, &events);
1517 let best_receipt = selector.select();
1519 assert!(best_receipt.is_none());
1520
1521 let f = EventFactory::new();
1523 events.push(
1524 f.text_msg("A mulatto, an albino")
1525 .sender(&myself)
1526 .event_id(event_id!("$6"))
1527 .into_event(),
1528 );
1529 events.push(
1530 f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1531 );
1532
1533 let mut selector = ReceiptSelector::new(&events, None);
1534 selector.try_match_implicit(&myself, &events);
1536 let best_receipt = selector.select();
1538 assert_eq!(best_receipt.unwrap().event_id, event_id!("$6"));
1539 }
1540
1541 #[test]
1542 fn test_compute_unread_counts_with_implicit_receipt() {
1543 let user_id = user_id!("@alice:example.org");
1544 let bob = user_id!("@bob:example.org");
1545 let room_id = room_id!("!room:example.org");
1546
1547 let mut events = make_test_events(bob);
1549
1550 let f = EventFactory::new();
1552 events.push(
1553 f.text_msg("A mulatto, an albino")
1554 .sender(user_id)
1555 .event_id(event_id!("$6"))
1556 .into_event(),
1557 );
1558
1559 events.push(
1561 f.text_msg("A mosquito, my libido").sender(bob).event_id(event_id!("$7")).into_event(),
1562 );
1563 events.push(
1564 f.text_msg("A denial, a denial").sender(bob).event_id(event_id!("$8")).into_event(),
1565 );
1566
1567 let events: Vec<_> = events.into_iter().collect();
1568
1569 let receipt_event = f
1571 .read_receipts()
1572 .add(event_id!("$3"), user_id, ReceiptType::Read, ReceiptThread::Unthreaded)
1573 .into_content();
1574
1575 let mut read_receipts = RoomReadReceipts::default();
1576
1577 compute_unread_counts(
1580 user_id,
1581 room_id,
1582 Some(&receipt_event),
1583 Vec::new(),
1584 &events,
1585 &mut read_receipts,
1586 ThreadingSupport::Disabled,
1587 );
1588
1589 assert_eq!(read_receipts.num_unread, 2);
1591
1592 assert!(read_receipts.pending.is_empty());
1594
1595 assert_eq!(read_receipts.latest_active.unwrap().event_id, event_id!("$6"));
1597 }
1598
1599 #[test]
1600 fn test_compute_unread_counts_with_threading_enabled() {
1601 fn make_event(user_id: &UserId, thread_root: &EventId) -> TimelineEvent {
1602 EventFactory::new()
1603 .text_msg("A")
1604 .sender(user_id)
1605 .event_id(event_id!("$ida"))
1606 .in_thread(thread_root, event_id!("$latest_event"))
1607 .into_event()
1608 }
1609
1610 let mut receipts = RoomReadReceipts::default();
1611
1612 let own_alice = user_id!("@alice:example.org");
1613 let bob = user_id!("@bob:example.org");
1614
1615 receipts.process_event(
1618 &make_event(own_alice, event_id!("$some_thread_root")),
1619 own_alice,
1620 ThreadingSupport::Enabled,
1621 );
1622 receipts.process_event(
1623 &make_event(own_alice, event_id!("$some_other_thread_root")),
1624 own_alice,
1625 ThreadingSupport::Enabled,
1626 );
1627
1628 receipts.process_event(
1629 &make_event(bob, event_id!("$some_thread_root")),
1630 own_alice,
1631 ThreadingSupport::Enabled,
1632 );
1633 receipts.process_event(
1634 &make_event(bob, event_id!("$some_other_thread_root")),
1635 own_alice,
1636 ThreadingSupport::Enabled,
1637 );
1638
1639 assert_eq!(receipts.num_unread, 0);
1640 assert_eq!(receipts.num_mentions, 0);
1641 assert_eq!(receipts.num_notifications, 0);
1642
1643 receipts.process_event(
1645 &EventFactory::new().text_msg("A").sender(bob).event_id(event_id!("$ida")).into_event(),
1646 own_alice,
1647 ThreadingSupport::Enabled,
1648 );
1649
1650 assert_eq!(receipts.num_unread, 1);
1651 assert_eq!(receipts.num_mentions, 0);
1652 assert_eq!(receipts.num_notifications, 0);
1653 }
1654}