1#[cfg(feature = "e2e-encryption")]
16use std::sync::RwLock as SyncRwLock;
17use std::{
18 collections::{BTreeMap, BTreeSet, HashSet},
19 mem,
20 sync::{atomic::AtomicBool, Arc},
21};
22
23use as_variant::as_variant;
24use bitflags::bitflags;
25use eyeball::{AsyncLock, ObservableWriteGuard, SharedObservable, Subscriber};
26use futures_util::{Stream, StreamExt};
27use matrix_sdk_common::deserialized_responses::TimelineEventKind;
28#[cfg(feature = "e2e-encryption")]
29use matrix_sdk_common::ring_buffer::RingBuffer;
30use ruma::{
31 api::client::sync::sync_events::v3::RoomSummary as RumaSummary,
32 events::{
33 call::member::{CallMemberStateKey, MembershipData},
34 direct::OwnedDirectUserIdentifier,
35 ignored_user_list::IgnoredUserListEventContent,
36 member_hints::MemberHintsEventContent,
37 receipt::{Receipt, ReceiptThread, ReceiptType},
38 room::{
39 avatar::{self, RoomAvatarEventContent},
40 encryption::RoomEncryptionEventContent,
41 guest_access::GuestAccess,
42 history_visibility::HistoryVisibility,
43 join_rules::JoinRule,
44 member::{MembershipState, RoomMemberEventContent},
45 pinned_events::RoomPinnedEventsEventContent,
46 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
47 redaction::SyncRoomRedactionEvent,
48 tombstone::RoomTombstoneEventContent,
49 },
50 tag::{TagEventContent, Tags},
51 AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
52 RoomAccountDataEventType, StateEventType, SyncStateEvent,
53 },
54 room::RoomType,
55 serde::Raw,
56 EventId, MxcUri, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId,
57 RoomAliasId, RoomId, RoomVersionId, UserId,
58};
59use serde::{Deserialize, Serialize};
60use tokio::sync::broadcast;
61use tracing::{debug, field::debug, info, instrument, trace, warn};
62
63use super::{
64 members::MemberRoomInfo, BaseRoomInfo, RoomCreateWithCreatorEventContent, RoomDisplayName,
65 RoomMember, RoomNotableTags,
66};
67use crate::{
68 deserialized_responses::{
69 DisplayName, MemberEvent, RawMemberEvent, RawSyncOrStrippedState, SyncOrStrippedState,
70 },
71 latest_event::LatestEvent,
72 notification_settings::RoomNotificationMode,
73 read_receipts::RoomReadReceipts,
74 store::{DynStateStore, Result as StoreResult, StateStoreExt},
75 sync::UnreadNotificationsCount,
76 Error, MinimalStateEvent, OriginalMinimalStateEvent, RoomMemberships, StateStoreDataKey,
77 StateStoreDataValue, StoreError,
78};
79
80#[derive(Debug, Clone)]
90pub struct RoomInfoNotableUpdate {
91 pub room_id: OwnedRoomId,
93
94 pub reasons: RoomInfoNotableUpdateReasons,
96}
97
98bitflags! {
99 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
101 pub struct RoomInfoNotableUpdateReasons: u8 {
102 const RECENCY_STAMP = 0b0000_0001;
104
105 const LATEST_EVENT = 0b0000_0010;
107
108 const READ_RECEIPT = 0b0000_0100;
110
111 const UNREAD_MARKER = 0b0000_1000;
113
114 const MEMBERSHIP = 0b0001_0000;
116 }
117}
118
119struct ComputedSummary {
126 heroes: Vec<String>,
129 num_service_members: u64,
131 num_joined_invited_guess: u64,
134}
135
136impl Default for RoomInfoNotableUpdateReasons {
137 fn default() -> Self {
138 Self::empty()
139 }
140}
141
142#[derive(Debug, Clone)]
145pub struct Room {
146 room_id: OwnedRoomId,
148
149 own_user_id: OwnedUserId,
151
152 inner: SharedObservable<RoomInfo>,
153 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
154 store: Arc<DynStateStore>,
155
156 #[cfg(feature = "e2e-encryption")]
166 pub latest_encrypted_events: Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>,
167
168 pub seen_knock_request_ids_map:
172 SharedObservable<Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
173
174 pub room_member_updates_sender: broadcast::Sender<RoomMembersUpdate>,
176}
177
178#[derive(Clone, Debug, Default, Serialize, Deserialize)]
181pub struct RoomSummary {
182 #[serde(default, skip_serializing_if = "Vec::is_empty")]
190 pub(crate) room_heroes: Vec<RoomHero>,
191 pub(crate) joined_member_count: u64,
193 pub(crate) invited_member_count: u64,
195}
196
197#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
199pub struct RoomHero {
200 pub user_id: OwnedUserId,
202 pub display_name: Option<String>,
204 pub avatar_url: Option<OwnedMxcUri>,
206}
207
208#[cfg(test)]
209impl RoomSummary {
210 pub(crate) fn heroes(&self) -> &[RoomHero] {
211 &self.room_heroes
212 }
213}
214
215#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
218pub enum RoomState {
219 Joined,
221 Left,
223 Invited,
225 Knocked,
227 Banned,
229}
230
231impl From<&MembershipState> for RoomState {
232 fn from(membership_state: &MembershipState) -> Self {
233 match membership_state {
234 MembershipState::Ban => Self::Banned,
235 MembershipState::Invite => Self::Invited,
236 MembershipState::Join => Self::Joined,
237 MembershipState::Knock => Self::Knocked,
238 MembershipState::Leave => Self::Left,
239 _ => panic!("Unexpected MembershipState: {}", membership_state),
240 }
241 }
242}
243
244const NUM_HEROES: usize = 5;
251
252fn heroes_filter<'a>(
258 own_user_id: &'a UserId,
259 member_hints: &'a MemberHintsEventContent,
260) -> impl Fn(&UserId) -> bool + use<'a> {
261 move |user_id| user_id != own_user_id && !member_hints.service_members.contains(user_id)
262}
263
264#[derive(Debug, Clone)]
266pub enum RoomMembersUpdate {
267 FullReload,
269 Partial(BTreeSet<OwnedUserId>),
271}
272
273impl Room {
274 #[cfg(feature = "e2e-encryption")]
276 const MAX_ENCRYPTED_EVENTS: std::num::NonZeroUsize = std::num::NonZeroUsize::new(10).unwrap();
277
278 pub(crate) fn new(
279 own_user_id: &UserId,
280 store: Arc<DynStateStore>,
281 room_id: &RoomId,
282 room_state: RoomState,
283 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
284 ) -> Self {
285 let room_info = RoomInfo::new(room_id, room_state);
286 Self::restore(own_user_id, store, room_info, room_info_notable_update_sender)
287 }
288
289 pub(crate) fn restore(
290 own_user_id: &UserId,
291 store: Arc<DynStateStore>,
292 room_info: RoomInfo,
293 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
294 ) -> Self {
295 let (room_member_updates_sender, _) = broadcast::channel(10);
296 Self {
297 own_user_id: own_user_id.into(),
298 room_id: room_info.room_id.clone(),
299 store,
300 inner: SharedObservable::new(room_info),
301 #[cfg(feature = "e2e-encryption")]
302 latest_encrypted_events: Arc::new(SyncRwLock::new(RingBuffer::new(
303 Self::MAX_ENCRYPTED_EVENTS,
304 ))),
305 room_info_notable_update_sender,
306 seen_knock_request_ids_map: SharedObservable::new_async(None),
307 room_member_updates_sender,
308 }
309 }
310
311 pub fn room_id(&self) -> &RoomId {
313 &self.room_id
314 }
315
316 pub fn creator(&self) -> Option<OwnedUserId> {
318 self.inner.read().creator().map(ToOwned::to_owned)
319 }
320
321 pub fn own_user_id(&self) -> &UserId {
323 &self.own_user_id
324 }
325
326 pub fn state(&self) -> RoomState {
328 self.inner.read().room_state
329 }
330
331 pub fn prev_state(&self) -> Option<RoomState> {
333 self.inner.read().prev_room_state
334 }
335
336 pub fn is_space(&self) -> bool {
338 self.inner.read().room_type().is_some_and(|t| *t == RoomType::Space)
339 }
340
341 pub fn room_type(&self) -> Option<RoomType> {
344 self.inner.read().room_type().map(ToOwned::to_owned)
345 }
346
347 pub fn unread_notification_counts(&self) -> UnreadNotificationsCount {
349 self.inner.read().notification_counts
350 }
351
352 pub fn num_unread_messages(&self) -> u64 {
357 self.inner.read().read_receipts.num_unread
358 }
359
360 pub fn read_receipts(&self) -> RoomReadReceipts {
362 self.inner.read().read_receipts.clone()
363 }
364
365 pub fn num_unread_notifications(&self) -> u64 {
370 self.inner.read().read_receipts.num_notifications
371 }
372
373 pub fn num_unread_mentions(&self) -> u64 {
379 self.inner.read().read_receipts.num_mentions
380 }
381
382 pub fn are_members_synced(&self) -> bool {
389 self.inner.read().members_synced
390 }
391
392 #[cfg(feature = "testing")]
397 pub fn mark_members_synced(&self) {
398 self.inner.update(|info| {
399 info.members_synced = true;
400 });
401 }
402
403 pub fn mark_members_missing(&self) {
405 self.inner.update_if(|info| {
406 mem::replace(&mut info.members_synced, false)
408 })
409 }
410
411 pub fn is_state_fully_synced(&self) -> bool {
419 self.inner.read().sync_info == SyncInfo::FullySynced
420 }
421
422 pub fn is_state_partially_or_fully_synced(&self) -> bool {
426 self.inner.read().sync_info != SyncInfo::NoState
427 }
428
429 pub fn last_prev_batch(&self) -> Option<String> {
432 self.inner.read().last_prev_batch.clone()
433 }
434
435 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
437 self.inner.read().avatar_url().map(ToOwned::to_owned)
438 }
439
440 pub fn avatar_info(&self) -> Option<avatar::ImageInfo> {
442 self.inner.read().avatar_info().map(ToOwned::to_owned)
443 }
444
445 pub fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
447 self.inner.read().canonical_alias().map(ToOwned::to_owned)
448 }
449
450 pub fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
452 self.inner.read().alt_aliases().to_owned()
453 }
454
455 pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
465 match self.inner.read().base_info.create.as_ref()? {
466 MinimalStateEvent::Original(ev) => Some(ev.content.clone()),
467 MinimalStateEvent::Redacted(ev) => Some(ev.content.clone()),
468 }
469 }
470
471 #[instrument(skip_all, fields(room_id = ?self.room_id))]
475 pub async fn is_direct(&self) -> StoreResult<bool> {
476 match self.state() {
477 RoomState::Joined | RoomState::Left | RoomState::Banned => {
478 Ok(!self.inner.read().base_info.dm_targets.is_empty())
479 }
480
481 RoomState::Invited => {
482 let member = self.get_member(self.own_user_id()).await?;
483
484 match member {
485 None => {
486 info!("RoomMember not found for the user's own id");
487 Ok(false)
488 }
489 Some(member) => match member.event.as_ref() {
490 MemberEvent::Sync(_) => {
491 warn!("Got MemberEvent::Sync in an invited room");
492 Ok(false)
493 }
494 MemberEvent::Stripped(event) => {
495 Ok(event.content.is_direct.unwrap_or(false))
496 }
497 },
498 }
499 }
500
501 RoomState::Knocked => Ok(false),
503 }
504 }
505
506 pub fn direct_targets(&self) -> HashSet<OwnedDirectUserIdentifier> {
515 self.inner.read().base_info.dm_targets.clone()
516 }
517
518 pub fn direct_targets_length(&self) -> usize {
521 self.inner.read().base_info.dm_targets.len()
522 }
523
524 pub fn encryption_state(&self) -> EncryptionState {
526 self.inner.read().encryption_state()
527 }
528
529 pub fn encryption_settings(&self) -> Option<RoomEncryptionEventContent> {
532 self.inner.read().base_info.encryption.clone()
533 }
534
535 pub fn guest_access(&self) -> GuestAccess {
537 self.inner.read().guest_access().clone()
538 }
539
540 pub fn history_visibility(&self) -> Option<HistoryVisibility> {
542 self.inner.read().history_visibility().cloned()
543 }
544
545 pub fn history_visibility_or_default(&self) -> HistoryVisibility {
548 self.inner.read().history_visibility_or_default().clone()
549 }
550
551 pub fn is_public(&self) -> bool {
553 matches!(self.join_rule(), JoinRule::Public)
554 }
555
556 pub fn join_rule(&self) -> JoinRule {
558 self.inner.read().join_rule().clone()
559 }
560
561 pub fn max_power_level(&self) -> i64 {
566 self.inner.read().base_info.max_power_level
567 }
568
569 pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
571 Ok(self
572 .store
573 .get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
574 .await?
575 .ok_or(Error::InsufficientData)?
576 .deserialize()?
577 .power_levels())
578 }
579
580 pub fn name(&self) -> Option<String> {
585 self.inner.read().name().map(ToOwned::to_owned)
586 }
587
588 pub fn is_tombstoned(&self) -> bool {
590 self.inner.read().base_info.tombstone.is_some()
591 }
592
593 pub fn tombstone(&self) -> Option<RoomTombstoneEventContent> {
595 self.inner.read().tombstone().cloned()
596 }
597
598 pub fn topic(&self) -> Option<String> {
600 self.inner.read().topic().map(ToOwned::to_owned)
601 }
602
603 pub fn has_active_room_call(&self) -> bool {
606 self.inner.read().has_active_room_call()
607 }
608
609 pub fn active_room_call_participants(&self) -> Vec<OwnedUserId> {
618 self.inner.read().active_room_call_participants()
619 }
620
621 pub async fn display_name(&self) -> StoreResult<RoomDisplayName> {
636 if let Some(name) = self.cached_display_name() {
637 Ok(name)
638 } else {
639 self.compute_display_name().await
640 }
641 }
642
643 pub(crate) async fn compute_display_name(&self) -> StoreResult<RoomDisplayName> {
655 enum DisplayNameOrSummary {
656 Summary(RoomSummary),
657 DisplayName(RoomDisplayName),
658 }
659
660 let display_name_or_summary = {
661 let inner = self.inner.read();
662
663 match (inner.name(), inner.canonical_alias()) {
664 (Some(name), _) => {
665 let name = RoomDisplayName::Named(name.trim().to_owned());
666 DisplayNameOrSummary::DisplayName(name)
667 }
668 (None, Some(alias)) => {
669 let name = RoomDisplayName::Aliased(alias.alias().trim().to_owned());
670 DisplayNameOrSummary::DisplayName(name)
671 }
672 (None, None) => DisplayNameOrSummary::Summary(inner.summary.clone()),
677 }
678 };
679
680 let display_name = match display_name_or_summary {
681 DisplayNameOrSummary::Summary(summary) => {
682 self.compute_display_name_from_summary(summary).await?
683 }
684 DisplayNameOrSummary::DisplayName(display_name) => display_name,
685 };
686
687 self.inner.update_if(|info| {
689 if info.cached_display_name.as_ref() != Some(&display_name) {
690 info.cached_display_name = Some(display_name.clone());
691 true
692 } else {
693 false
694 }
695 });
696
697 Ok(display_name)
698 }
699
700 async fn compute_display_name_from_summary(
702 &self,
703 summary: RoomSummary,
704 ) -> StoreResult<RoomDisplayName> {
705 let computed_summary = if !summary.room_heroes.is_empty() {
706 self.extract_and_augment_summary(&summary).await?
707 } else {
708 self.compute_summary().await?
709 };
710
711 let ComputedSummary { heroes, num_service_members, num_joined_invited_guess } =
712 computed_summary;
713
714 let summary_member_count = (summary.joined_member_count + summary.invited_member_count)
715 .saturating_sub(num_service_members);
716
717 let num_joined_invited = if self.state() == RoomState::Invited {
718 heroes.len() as u64 + 1
721 } else if summary_member_count == 0 {
722 num_joined_invited_guess
723 } else {
724 summary_member_count
725 };
726
727 debug!(
728 room_id = ?self.room_id(),
729 own_user = ?self.own_user_id,
730 num_joined_invited,
731 heroes = ?heroes,
732 "Calculating name for a room based on heroes",
733 );
734
735 let display_name = compute_display_name_from_heroes(
736 num_joined_invited,
737 heroes.iter().map(|hero| hero.as_str()).collect(),
738 );
739
740 Ok(display_name)
741 }
742
743 async fn extract_and_augment_summary(
752 &self,
753 summary: &RoomSummary,
754 ) -> StoreResult<ComputedSummary> {
755 let heroes = &summary.room_heroes;
756
757 let mut names = Vec::with_capacity(heroes.len());
758 let own_user_id = self.own_user_id();
759 let member_hints = self.get_member_hints().await?;
760
761 let num_service_members = heroes
766 .iter()
767 .filter(|hero| member_hints.service_members.contains(&hero.user_id))
768 .count() as u64;
769
770 let heroes_filter = heroes_filter(own_user_id, &member_hints);
773 let heroes_filter = |hero: &&RoomHero| heroes_filter(&hero.user_id);
774
775 for hero in heroes.iter().filter(heroes_filter) {
776 if let Some(display_name) = &hero.display_name {
777 names.push(display_name.clone());
778 } else {
779 match self.get_member(&hero.user_id).await {
780 Ok(Some(member)) => {
781 names.push(member.name().to_owned());
782 }
783 Ok(None) => {
784 warn!("Ignoring hero, no member info for {}", hero.user_id);
785 }
786 Err(error) => {
787 warn!("Ignoring hero, error getting member: {}", error);
788 }
789 }
790 }
791 }
792
793 let num_joined_invited_guess = summary.joined_member_count + summary.invited_member_count;
794
795 let num_joined_invited_guess = if num_joined_invited_guess == 0 {
798 let guess = self
799 .store
800 .get_user_ids(self.room_id(), RoomMemberships::JOIN | RoomMemberships::INVITE)
801 .await?
802 .len() as u64;
803
804 guess.saturating_sub(num_service_members)
805 } else {
806 num_joined_invited_guess
808 };
809
810 Ok(ComputedSummary { heroes: names, num_service_members, num_joined_invited_guess })
811 }
812
813 async fn compute_summary(&self) -> StoreResult<ComputedSummary> {
819 let member_hints = self.get_member_hints().await?;
820
821 let heroes_filter = heroes_filter(&self.own_user_id, &member_hints);
824 let heroes_filter = |u: &RoomMember| heroes_filter(u.user_id());
825
826 let mut members = self.members(RoomMemberships::JOIN | RoomMemberships::INVITE).await?;
827
828 let num_service_members = members
832 .iter()
833 .filter(|member| member_hints.service_members.contains(member.user_id()))
834 .count();
835
836 let num_joined_invited = members.len() - num_service_members;
843
844 if num_joined_invited == 0
845 || (num_joined_invited == 1 && members[0].user_id() == self.own_user_id)
846 {
847 members = self.members(RoomMemberships::LEAVE | RoomMemberships::BAN).await?;
849 }
850
851 members.sort_unstable_by(|lhs, rhs| lhs.name().cmp(rhs.name()));
853
854 let heroes = members
855 .into_iter()
856 .filter(heroes_filter)
857 .take(NUM_HEROES)
858 .map(|u| u.name().to_owned())
859 .collect();
860
861 trace!(
862 ?heroes,
863 num_joined_invited,
864 num_service_members,
865 "Computed a room summary since we didn't receive one."
866 );
867
868 let num_service_members = num_service_members as u64;
869 let num_joined_invited_guess = num_joined_invited as u64;
870
871 Ok(ComputedSummary { heroes, num_service_members, num_joined_invited_guess })
872 }
873
874 async fn get_member_hints(&self) -> StoreResult<MemberHintsEventContent> {
875 Ok(self
876 .store
877 .get_state_event_static::<MemberHintsEventContent>(self.room_id())
878 .await?
879 .and_then(|event| {
880 event
881 .deserialize()
882 .inspect_err(|e| warn!("Couldn't deserialize the member hints event: {e}"))
883 .ok()
884 })
885 .and_then(|event| as_variant!(event, SyncOrStrippedState::Sync(SyncStateEvent::Original(e)) => e.content))
886 .unwrap_or_default())
887 }
888
889 pub fn cached_display_name(&self) -> Option<RoomDisplayName> {
893 self.inner.read().cached_display_name.clone()
894 }
895
896 pub fn update_cached_user_defined_notification_mode(&self, mode: RoomNotificationMode) {
902 self.inner.update_if(|info| {
903 if info.cached_user_defined_notification_mode.as_ref() != Some(&mode) {
904 info.cached_user_defined_notification_mode = Some(mode);
905
906 true
907 } else {
908 false
909 }
910 });
911 }
912
913 pub fn cached_user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
918 self.inner.read().cached_user_defined_notification_mode
919 }
920
921 pub fn latest_event(&self) -> Option<LatestEvent> {
924 self.inner.read().latest_event.as_deref().cloned()
925 }
926
927 #[cfg(feature = "e2e-encryption")]
932 pub(crate) fn latest_encrypted_events(&self) -> Vec<Raw<AnySyncTimelineEvent>> {
933 self.latest_encrypted_events.read().unwrap().iter().cloned().collect()
934 }
935
936 #[cfg(feature = "e2e-encryption")]
947 pub(crate) fn on_latest_event_decrypted(
948 &self,
949 latest_event: Box<LatestEvent>,
950 index: usize,
951 changes: &mut crate::StateChanges,
952 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
953 ) {
954 self.latest_encrypted_events.write().unwrap().drain(0..=index);
955
956 let room_info = changes
957 .room_infos
958 .entry(self.room_id().to_owned())
959 .or_insert_with(|| self.clone_info());
960
961 room_info.latest_event = Some(latest_event);
962
963 room_info_notable_updates
964 .entry(self.room_id().to_owned())
965 .or_default()
966 .insert(RoomInfoNotableUpdateReasons::LATEST_EVENT);
967 }
968
969 pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
972 self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
973 }
974
975 pub async fn members(&self, memberships: RoomMemberships) -> StoreResult<Vec<RoomMember>> {
978 let user_ids = self.store.get_user_ids(self.room_id(), memberships).await?;
979
980 if user_ids.is_empty() {
981 return Ok(Vec::new());
982 }
983
984 let member_events = self
985 .store
986 .get_state_events_for_keys_static::<RoomMemberEventContent, _, _>(
987 self.room_id(),
988 &user_ids,
989 )
990 .await?
991 .into_iter()
992 .map(|raw_event| raw_event.deserialize())
993 .collect::<Result<Vec<_>, _>>()?;
994
995 let mut profiles = self.store.get_profiles(self.room_id(), &user_ids).await?;
996
997 let mut presences = self
998 .store
999 .get_presence_events(&user_ids)
1000 .await?
1001 .into_iter()
1002 .filter_map(|e| {
1003 e.deserialize().ok().map(|presence| (presence.sender.clone(), presence))
1004 })
1005 .collect::<BTreeMap<_, _>>();
1006
1007 let display_names = member_events.iter().map(|e| e.display_name()).collect::<Vec<_>>();
1008 let room_info = self.member_room_info(&display_names).await?;
1009
1010 let mut members = Vec::new();
1011
1012 for event in member_events {
1013 let profile = profiles.remove(event.user_id());
1014 let presence = presences.remove(event.user_id());
1015 members.push(RoomMember::from_parts(event, profile, presence, &room_info))
1016 }
1017
1018 Ok(members)
1019 }
1020
1021 pub fn heroes(&self) -> Vec<RoomHero> {
1023 self.inner.read().heroes().to_vec()
1024 }
1025
1026 pub fn active_members_count(&self) -> u64 {
1029 self.inner.read().active_members_count()
1030 }
1031
1032 pub fn invited_members_count(&self) -> u64 {
1034 self.inner.read().invited_members_count()
1035 }
1036
1037 pub fn joined_members_count(&self) -> u64 {
1039 self.inner.read().joined_members_count()
1040 }
1041
1042 pub fn subscribe_info(&self) -> Subscriber<RoomInfo> {
1044 self.inner.subscribe()
1045 }
1046
1047 pub fn clone_info(&self) -> RoomInfo {
1049 self.inner.get()
1050 }
1051
1052 pub fn set_room_info(
1054 &self,
1055 room_info: RoomInfo,
1056 room_info_notable_update_reasons: RoomInfoNotableUpdateReasons,
1057 ) {
1058 self.inner.set(room_info);
1059
1060 let _ = self.room_info_notable_update_sender.send(RoomInfoNotableUpdate {
1062 room_id: self.room_id.clone(),
1063 reasons: room_info_notable_update_reasons,
1064 });
1065 }
1066
1067 pub async fn get_member(&self, user_id: &UserId) -> StoreResult<Option<RoomMember>> {
1075 let Some(raw_event) = self.store.get_member_event(self.room_id(), user_id).await? else {
1076 debug!(%user_id, "Member event not found in state store");
1077 return Ok(None);
1078 };
1079
1080 let event = raw_event.deserialize()?;
1081
1082 let presence =
1083 self.store.get_presence_event(user_id).await?.and_then(|e| e.deserialize().ok());
1084
1085 let profile = self.store.get_profile(self.room_id(), user_id).await?;
1086
1087 let display_names = [event.display_name()];
1088 let room_info = self.member_room_info(&display_names).await?;
1089
1090 Ok(Some(RoomMember::from_parts(event, profile, presence, &room_info)))
1091 }
1092
1093 async fn member_room_info<'a>(
1097 &self,
1098 display_names: &'a [DisplayName],
1099 ) -> StoreResult<MemberRoomInfo<'a>> {
1100 let max_power_level = self.max_power_level();
1101 let room_creator = self.inner.read().creator().map(ToOwned::to_owned);
1102
1103 let power_levels = self
1104 .store
1105 .get_state_event_static(self.room_id())
1106 .await?
1107 .and_then(|e| e.deserialize().ok());
1108
1109 let users_display_names =
1110 self.store.get_users_with_display_names(self.room_id(), display_names).await?;
1111
1112 let ignored_users = self
1113 .store
1114 .get_account_data_event_static::<IgnoredUserListEventContent>()
1115 .await?
1116 .map(|c| c.deserialize())
1117 .transpose()?
1118 .map(|e| e.content.ignored_users.into_keys().collect());
1119
1120 Ok(MemberRoomInfo {
1121 power_levels: power_levels.into(),
1122 max_power_level,
1123 room_creator,
1124 users_display_names,
1125 ignored_users,
1126 })
1127 }
1128
1129 pub async fn tags(&self) -> StoreResult<Option<Tags>> {
1131 if let Some(AnyRoomAccountDataEvent::Tag(event)) = self
1132 .store
1133 .get_room_account_data_event(self.room_id(), RoomAccountDataEventType::Tag)
1134 .await?
1135 .and_then(|r| r.deserialize().ok())
1136 {
1137 Ok(Some(event.content.tags))
1138 } else {
1139 Ok(None)
1140 }
1141 }
1142
1143 pub fn is_favourite(&self) -> bool {
1147 self.inner.read().base_info.notable_tags.contains(RoomNotableTags::FAVOURITE)
1148 }
1149
1150 pub fn is_low_priority(&self) -> bool {
1155 self.inner.read().base_info.notable_tags.contains(RoomNotableTags::LOW_PRIORITY)
1156 }
1157
1158 pub async fn load_user_receipt(
1161 &self,
1162 receipt_type: ReceiptType,
1163 thread: ReceiptThread,
1164 user_id: &UserId,
1165 ) -> StoreResult<Option<(OwnedEventId, Receipt)>> {
1166 self.store.get_user_room_receipt_event(self.room_id(), receipt_type, thread, user_id).await
1167 }
1168
1169 pub async fn load_event_receipts(
1173 &self,
1174 receipt_type: ReceiptType,
1175 thread: ReceiptThread,
1176 event_id: &EventId,
1177 ) -> StoreResult<Vec<(OwnedUserId, Receipt)>> {
1178 self.store
1179 .get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
1180 .await
1181 }
1182
1183 pub fn is_marked_unread(&self) -> bool {
1186 self.inner.read().base_info.is_marked_unread
1187 }
1188
1189 pub fn recency_stamp(&self) -> Option<u64> {
1193 self.inner.read().recency_stamp
1194 }
1195
1196 pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> {
1199 self.inner
1200 .subscribe()
1201 .map(|i| i.base_info.pinned_events.map(|c| c.pinned).unwrap_or_default())
1202 }
1203
1204 pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
1206 self.inner.read().pinned_event_ids()
1207 }
1208
1209 pub async fn mark_knock_requests_as_seen(&self, user_ids: &[OwnedUserId]) -> StoreResult<()> {
1212 let raw_user_ids: Vec<&str> = user_ids.iter().map(|id| id.as_str()).collect();
1213 let member_raw_events = self
1214 .store
1215 .get_state_events_for_keys(self.room_id(), StateEventType::RoomMember, &raw_user_ids)
1216 .await?;
1217 let mut event_to_user_ids = Vec::with_capacity(member_raw_events.len());
1218
1219 for raw_event in member_raw_events {
1222 let event = raw_event.cast::<RoomMemberEventContent>().deserialize()?;
1223 match event {
1224 SyncOrStrippedState::Sync(SyncStateEvent::Original(event)) => {
1225 if event.content.membership == MembershipState::Knock {
1226 event_to_user_ids.push((event.event_id, event.state_key))
1227 } else {
1228 warn!("Could not mark knock event as seen: event {} for user {} is not in Knock membership state.", event.event_id, event.state_key);
1229 }
1230 }
1231 _ => warn!(
1232 "Could not mark knock event as seen: event for user {} is not valid.",
1233 event.state_key()
1234 ),
1235 }
1236 }
1237
1238 let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?;
1239 let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default();
1240
1241 current_seen_events.extend(event_to_user_ids);
1242
1243 self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?;
1244
1245 Ok(())
1246 }
1247
1248 pub async fn remove_outdated_seen_knock_requests_ids(&self) -> StoreResult<()> {
1251 let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?;
1252 let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default();
1253
1254 let keys: Vec<OwnedUserId> = current_seen_events.values().map(|id| id.to_owned()).collect();
1256 let raw_member_events: Vec<RawMemberEvent> =
1257 self.store.get_state_events_for_keys_static(self.room_id(), &keys).await?;
1258 let member_events = raw_member_events
1259 .into_iter()
1260 .map(|raw| raw.deserialize())
1261 .collect::<Result<Vec<MemberEvent>, _>>()?;
1262
1263 let mut ids_to_remove = Vec::new();
1264
1265 for (event_id, user_id) in current_seen_events.iter() {
1266 let matching_member = member_events.iter().find(|event| event.user_id() == user_id);
1269
1270 if let Some(member) = matching_member {
1271 let member_event_id = member.event_id();
1272 if *member.membership() != MembershipState::Knock
1274 || member_event_id.is_some_and(|id| id != event_id)
1275 {
1276 ids_to_remove.push(event_id.to_owned());
1277 }
1278 } else {
1279 ids_to_remove.push(event_id.to_owned());
1280 }
1281 }
1282
1283 if ids_to_remove.is_empty() {
1285 return Ok(());
1286 }
1287
1288 for event_id in ids_to_remove {
1289 current_seen_events.remove(&event_id);
1290 }
1291
1292 self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?;
1293
1294 Ok(())
1295 }
1296
1297 pub async fn get_seen_knock_request_ids(
1299 &self,
1300 ) -> Result<BTreeMap<OwnedEventId, OwnedUserId>, StoreError> {
1301 Ok(self.get_write_guarded_current_knock_request_ids().await?.clone().unwrap_or_default())
1302 }
1303
1304 async fn get_write_guarded_current_knock_request_ids(
1305 &self,
1306 ) -> StoreResult<ObservableWriteGuard<'_, Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>>
1307 {
1308 let mut guard = self.seen_knock_request_ids_map.write().await;
1309 if guard.is_none() {
1311 let updated_seen_ids = self
1313 .store
1314 .get_kv_data(StateStoreDataKey::SeenKnockRequests(self.room_id()))
1315 .await?
1316 .and_then(|v| v.into_seen_knock_requests())
1317 .unwrap_or_default();
1318
1319 ObservableWriteGuard::set(&mut guard, Some(updated_seen_ids));
1320 }
1321 Ok(guard)
1322 }
1323
1324 async fn update_seen_knock_request_ids(
1325 &self,
1326 mut guard: ObservableWriteGuard<'_, Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
1327 new_value: BTreeMap<OwnedEventId, OwnedUserId>,
1328 ) -> StoreResult<()> {
1329 ObservableWriteGuard::set(&mut guard, Some(new_value.clone()));
1331
1332 self.store
1334 .set_kv_data(
1335 StateStoreDataKey::SeenKnockRequests(self.room_id()),
1336 StateStoreDataValue::SeenKnockRequests(new_value),
1337 )
1338 .await?;
1339
1340 Ok(())
1341 }
1342}
1343
1344#[cfg(not(feature = "test-send-sync"))]
1346unsafe impl Send for Room {}
1347
1348#[cfg(not(feature = "test-send-sync"))]
1350unsafe impl Sync for Room {}
1351
1352#[cfg(feature = "test-send-sync")]
1353#[test]
1354fn test_send_sync_for_room() {
1356 fn assert_send_sync<T: Send + Sync>() {}
1357
1358 assert_send_sync::<Room>();
1359}
1360
1361#[derive(Clone, Debug, Serialize, Deserialize)]
1365pub struct RoomInfo {
1366 #[serde(default)]
1368 pub(crate) version: u8,
1369
1370 pub(crate) room_id: OwnedRoomId,
1372
1373 pub(crate) room_state: RoomState,
1375
1376 pub(crate) prev_room_state: Option<RoomState>,
1378
1379 pub(crate) notification_counts: UnreadNotificationsCount,
1384
1385 pub(crate) summary: RoomSummary,
1387
1388 pub(crate) members_synced: bool,
1390
1391 pub(crate) last_prev_batch: Option<String>,
1393
1394 pub(crate) sync_info: SyncInfo,
1396
1397 pub(crate) encryption_state_synced: bool,
1399
1400 pub(crate) latest_event: Option<Box<LatestEvent>>,
1402
1403 #[serde(default)]
1405 pub(crate) read_receipts: RoomReadReceipts,
1406
1407 pub(crate) base_info: Box<BaseRoomInfo>,
1410
1411 #[serde(skip)]
1415 pub(crate) warned_about_unknown_room_version: Arc<AtomicBool>,
1416
1417 #[serde(default, skip_serializing_if = "Option::is_none")]
1422 pub(crate) cached_display_name: Option<RoomDisplayName>,
1423
1424 #[serde(default, skip_serializing_if = "Option::is_none")]
1426 pub(crate) cached_user_defined_notification_mode: Option<RoomNotificationMode>,
1427
1428 #[serde(default)]
1435 pub(crate) recency_stamp: Option<u64>,
1436}
1437
1438#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
1439pub(crate) enum SyncInfo {
1440 NoState,
1446
1447 PartiallySynced,
1450
1451 FullySynced,
1453}
1454
1455impl RoomInfo {
1456 #[doc(hidden)] pub fn new(room_id: &RoomId, room_state: RoomState) -> Self {
1458 Self {
1459 version: 1,
1460 room_id: room_id.into(),
1461 room_state,
1462 prev_room_state: None,
1463 notification_counts: Default::default(),
1464 summary: Default::default(),
1465 members_synced: false,
1466 last_prev_batch: None,
1467 sync_info: SyncInfo::NoState,
1468 encryption_state_synced: false,
1469 latest_event: None,
1470 read_receipts: Default::default(),
1471 base_info: Box::new(BaseRoomInfo::new()),
1472 warned_about_unknown_room_version: Arc::new(false.into()),
1473 cached_display_name: None,
1474 cached_user_defined_notification_mode: None,
1475 recency_stamp: None,
1476 }
1477 }
1478
1479 pub fn mark_as_joined(&mut self) {
1481 self.set_state(RoomState::Joined);
1482 }
1483
1484 pub fn mark_as_left(&mut self) {
1486 self.set_state(RoomState::Left);
1487 }
1488
1489 pub fn mark_as_invited(&mut self) {
1491 self.set_state(RoomState::Invited);
1492 }
1493
1494 pub fn mark_as_knocked(&mut self) {
1496 self.set_state(RoomState::Knocked);
1497 }
1498
1499 pub fn mark_as_banned(&mut self) {
1501 self.set_state(RoomState::Banned);
1502 }
1503
1504 pub fn set_state(&mut self, room_state: RoomState) {
1506 if room_state != self.room_state {
1507 self.prev_room_state = Some(self.room_state);
1508 self.room_state = room_state;
1509 }
1510 }
1511
1512 pub fn mark_members_synced(&mut self) {
1514 self.members_synced = true;
1515 }
1516
1517 pub fn mark_members_missing(&mut self) {
1519 self.members_synced = false;
1520 }
1521
1522 pub fn are_members_synced(&self) -> bool {
1524 self.members_synced
1525 }
1526
1527 pub fn mark_state_partially_synced(&mut self) {
1529 self.sync_info = SyncInfo::PartiallySynced;
1530 }
1531
1532 pub fn mark_state_fully_synced(&mut self) {
1534 self.sync_info = SyncInfo::FullySynced;
1535 }
1536
1537 pub fn mark_state_not_synced(&mut self) {
1539 self.sync_info = SyncInfo::NoState;
1540 }
1541
1542 pub fn mark_encryption_state_synced(&mut self) {
1544 self.encryption_state_synced = true;
1545 }
1546
1547 pub fn mark_encryption_state_missing(&mut self) {
1549 self.encryption_state_synced = false;
1550 }
1551
1552 pub fn set_prev_batch(&mut self, prev_batch: Option<&str>) -> bool {
1556 if self.last_prev_batch.as_deref() != prev_batch {
1557 self.last_prev_batch = prev_batch.map(|p| p.to_owned());
1558 true
1559 } else {
1560 false
1561 }
1562 }
1563
1564 pub fn state(&self) -> RoomState {
1566 self.room_state
1567 }
1568
1569 pub fn encryption_state(&self) -> EncryptionState {
1571 if !self.encryption_state_synced {
1572 EncryptionState::Unknown
1573 } else if self.base_info.encryption.is_some() {
1574 EncryptionState::Encrypted
1575 } else {
1576 EncryptionState::NotEncrypted
1577 }
1578 }
1579
1580 pub fn set_encryption_event(&mut self, event: Option<RoomEncryptionEventContent>) {
1582 self.base_info.encryption = event;
1583 }
1584
1585 pub fn handle_encryption_state(
1587 &mut self,
1588 requested_required_states: &[(StateEventType, String)],
1589 ) {
1590 if requested_required_states
1591 .iter()
1592 .any(|(state_event, _)| state_event == &StateEventType::RoomEncryption)
1593 {
1594 self.mark_encryption_state_synced();
1600 }
1601 }
1602
1603 pub fn handle_state_event(&mut self, event: &AnySyncStateEvent) -> bool {
1607 let base_info_has_been_modified = self.base_info.handle_state_event(event);
1609
1610 if let AnySyncStateEvent::RoomEncryption(_) = event {
1611 self.mark_encryption_state_synced();
1617 }
1618
1619 base_info_has_been_modified
1620 }
1621
1622 pub fn handle_stripped_state_event(&mut self, event: &AnyStrippedStateEvent) -> bool {
1626 self.base_info.handle_stripped_state_event(event)
1627 }
1628
1629 #[instrument(skip_all, fields(redacts))]
1631 pub fn handle_redaction(
1632 &mut self,
1633 event: &SyncRoomRedactionEvent,
1634 _raw: &Raw<SyncRoomRedactionEvent>,
1635 ) {
1636 let room_version = self.base_info.room_version().unwrap_or(&RoomVersionId::V1);
1637
1638 let Some(redacts) = event.redacts(room_version) else {
1639 info!("Can't apply redaction, redacts field is missing");
1640 return;
1641 };
1642 tracing::Span::current().record("redacts", debug(redacts));
1643
1644 if let Some(latest_event) = &mut self.latest_event {
1645 tracing::trace!("Checking if redaction applies to latest event");
1646 if latest_event.event_id().as_deref() == Some(redacts) {
1647 match apply_redaction(latest_event.event().raw(), _raw, room_version) {
1648 Some(redacted) => {
1649 latest_event.event_mut().kind =
1652 TimelineEventKind::PlainText { event: redacted };
1653 debug!("Redacted latest event");
1654 }
1655 None => {
1656 self.latest_event = None;
1657 debug!("Removed latest event");
1658 }
1659 }
1660 }
1661 }
1662
1663 self.base_info.handle_redaction(redacts);
1664 }
1665
1666 pub fn avatar_url(&self) -> Option<&MxcUri> {
1668 self.base_info
1669 .avatar
1670 .as_ref()
1671 .and_then(|e| e.as_original().and_then(|e| e.content.url.as_deref()))
1672 }
1673
1674 pub fn update_avatar(&mut self, url: Option<OwnedMxcUri>) {
1676 self.base_info.avatar = url.map(|url| {
1677 let mut content = RoomAvatarEventContent::new();
1678 content.url = Some(url);
1679
1680 MinimalStateEvent::Original(OriginalMinimalStateEvent { content, event_id: None })
1681 });
1682 }
1683
1684 pub fn avatar_info(&self) -> Option<&avatar::ImageInfo> {
1686 self.base_info
1687 .avatar
1688 .as_ref()
1689 .and_then(|e| e.as_original().and_then(|e| e.content.info.as_deref()))
1690 }
1691
1692 pub fn update_notification_count(&mut self, notification_counts: UnreadNotificationsCount) {
1694 self.notification_counts = notification_counts;
1695 }
1696
1697 pub fn update_from_ruma_summary(&mut self, summary: &RumaSummary) -> bool {
1701 let mut changed = false;
1702
1703 if !summary.is_empty() {
1704 if !summary.heroes.is_empty() {
1705 self.summary.room_heroes = summary
1706 .heroes
1707 .iter()
1708 .map(|hero_id| RoomHero {
1709 user_id: hero_id.to_owned(),
1710 display_name: None,
1711 avatar_url: None,
1712 })
1713 .collect();
1714
1715 changed = true;
1716 }
1717
1718 if let Some(joined) = summary.joined_member_count {
1719 self.summary.joined_member_count = joined.into();
1720 changed = true;
1721 }
1722
1723 if let Some(invited) = summary.invited_member_count {
1724 self.summary.invited_member_count = invited.into();
1725 changed = true;
1726 }
1727 }
1728
1729 changed
1730 }
1731
1732 pub(crate) fn update_joined_member_count(&mut self, count: u64) {
1734 self.summary.joined_member_count = count;
1735 }
1736
1737 pub(crate) fn update_invited_member_count(&mut self, count: u64) {
1739 self.summary.invited_member_count = count;
1740 }
1741
1742 pub(crate) fn update_heroes(&mut self, heroes: Vec<RoomHero>) {
1744 self.summary.room_heroes = heroes;
1745 }
1746
1747 pub fn heroes(&self) -> &[RoomHero] {
1749 &self.summary.room_heroes
1750 }
1751
1752 pub fn active_members_count(&self) -> u64 {
1756 self.summary.joined_member_count.saturating_add(self.summary.invited_member_count)
1757 }
1758
1759 pub fn invited_members_count(&self) -> u64 {
1761 self.summary.invited_member_count
1762 }
1763
1764 pub fn joined_members_count(&self) -> u64 {
1766 self.summary.joined_member_count
1767 }
1768
1769 pub fn canonical_alias(&self) -> Option<&RoomAliasId> {
1771 self.base_info.canonical_alias.as_ref()?.as_original()?.content.alias.as_deref()
1772 }
1773
1774 pub fn alt_aliases(&self) -> &[OwnedRoomAliasId] {
1776 self.base_info
1777 .canonical_alias
1778 .as_ref()
1779 .and_then(|ev| ev.as_original())
1780 .map(|ev| ev.content.alt_aliases.as_ref())
1781 .unwrap_or_default()
1782 }
1783
1784 pub fn room_id(&self) -> &RoomId {
1786 &self.room_id
1787 }
1788
1789 pub fn room_version(&self) -> Option<&RoomVersionId> {
1791 self.base_info.room_version()
1792 }
1793
1794 pub fn room_version_or_default(&self) -> RoomVersionId {
1799 use std::sync::atomic::Ordering;
1800
1801 self.base_info.room_version().cloned().unwrap_or_else(|| {
1802 if self
1803 .warned_about_unknown_room_version
1804 .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
1805 .is_ok()
1806 {
1807 warn!("Unknown room version, falling back to v10");
1808 }
1809
1810 RoomVersionId::V10
1811 })
1812 }
1813
1814 pub fn room_type(&self) -> Option<&RoomType> {
1816 match self.base_info.create.as_ref()? {
1817 MinimalStateEvent::Original(ev) => ev.content.room_type.as_ref(),
1818 MinimalStateEvent::Redacted(ev) => ev.content.room_type.as_ref(),
1819 }
1820 }
1821
1822 pub fn creator(&self) -> Option<&UserId> {
1824 match self.base_info.create.as_ref()? {
1825 MinimalStateEvent::Original(ev) => Some(&ev.content.creator),
1826 MinimalStateEvent::Redacted(ev) => Some(&ev.content.creator),
1827 }
1828 }
1829
1830 fn guest_access(&self) -> &GuestAccess {
1831 match &self.base_info.guest_access {
1832 Some(MinimalStateEvent::Original(ev)) => &ev.content.guest_access,
1833 _ => &GuestAccess::Forbidden,
1834 }
1835 }
1836
1837 pub fn history_visibility(&self) -> Option<&HistoryVisibility> {
1841 match &self.base_info.history_visibility {
1842 Some(MinimalStateEvent::Original(ev)) => Some(&ev.content.history_visibility),
1843 _ => None,
1844 }
1845 }
1846
1847 pub fn history_visibility_or_default(&self) -> &HistoryVisibility {
1854 match &self.base_info.history_visibility {
1855 Some(MinimalStateEvent::Original(ev)) => &ev.content.history_visibility,
1856 _ => &HistoryVisibility::Shared,
1857 }
1858 }
1859
1860 pub fn join_rule(&self) -> &JoinRule {
1864 match &self.base_info.join_rules {
1865 Some(MinimalStateEvent::Original(ev)) => &ev.content.join_rule,
1866 _ => &JoinRule::Public,
1867 }
1868 }
1869
1870 pub fn name(&self) -> Option<&str> {
1872 let name = &self.base_info.name.as_ref()?.as_original()?.content.name;
1873 (!name.is_empty()).then_some(name)
1874 }
1875
1876 fn tombstone(&self) -> Option<&RoomTombstoneEventContent> {
1877 Some(&self.base_info.tombstone.as_ref()?.as_original()?.content)
1878 }
1879
1880 pub fn topic(&self) -> Option<&str> {
1882 Some(&self.base_info.topic.as_ref()?.as_original()?.content.topic)
1883 }
1884
1885 fn active_matrix_rtc_memberships(&self) -> Vec<(CallMemberStateKey, MembershipData<'_>)> {
1890 let mut v = self
1891 .base_info
1892 .rtc_member_events
1893 .iter()
1894 .filter_map(|(user_id, ev)| {
1895 ev.as_original().map(|ev| {
1896 ev.content
1897 .active_memberships(None)
1898 .into_iter()
1899 .map(move |m| (user_id.clone(), m))
1900 })
1901 })
1902 .flatten()
1903 .collect::<Vec<_>>();
1904 v.sort_by_key(|(_, m)| m.created_ts());
1905 v
1906 }
1907
1908 fn active_room_call_memberships(&self) -> Vec<(CallMemberStateKey, MembershipData<'_>)> {
1914 self.active_matrix_rtc_memberships()
1915 .into_iter()
1916 .filter(|(_user_id, m)| m.is_room_call())
1917 .collect()
1918 }
1919
1920 pub fn has_active_room_call(&self) -> bool {
1923 !self.active_room_call_memberships().is_empty()
1924 }
1925
1926 pub fn active_room_call_participants(&self) -> Vec<OwnedUserId> {
1935 self.active_room_call_memberships()
1936 .iter()
1937 .map(|(call_member_state_key, _)| call_member_state_key.user_id().to_owned())
1938 .collect()
1939 }
1940
1941 pub fn latest_event(&self) -> Option<&LatestEvent> {
1943 self.latest_event.as_deref()
1944 }
1945
1946 pub(crate) fn update_recency_stamp(&mut self, stamp: u64) {
1950 self.recency_stamp = Some(stamp);
1951 }
1952
1953 pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
1955 self.base_info.pinned_events.clone().map(|c| c.pinned)
1956 }
1957
1958 pub fn is_pinned_event(&self, event_id: &EventId) -> bool {
1964 self.base_info
1965 .pinned_events
1966 .as_ref()
1967 .map(|p| p.pinned.contains(&event_id.to_owned()))
1968 .unwrap_or_default()
1969 }
1970
1971 #[instrument(skip_all, fields(room_id = ?self.room_id))]
1979 pub(crate) async fn apply_migrations(&mut self, store: Arc<DynStateStore>) -> bool {
1980 let mut migrated = false;
1981
1982 if self.version < 1 {
1983 info!("Migrating room info to version 1");
1984
1985 match store.get_room_account_data_event_static::<TagEventContent>(&self.room_id).await {
1987 Ok(Some(raw_event)) => match raw_event.deserialize() {
1989 Ok(event) => {
1990 self.base_info.handle_notable_tags(&event.content.tags);
1991 }
1992 Err(error) => {
1993 warn!("Failed to deserialize room tags: {error}");
1994 }
1995 },
1996 Ok(_) => {
1997 }
1999 Err(error) => {
2000 warn!("Failed to load room tags: {error}");
2001 }
2002 }
2003
2004 match store.get_state_event_static::<RoomPinnedEventsEventContent>(&self.room_id).await
2006 {
2007 Ok(Some(RawSyncOrStrippedState::Sync(raw_event))) => {
2009 match raw_event.deserialize() {
2010 Ok(event) => {
2011 self.handle_state_event(&event.into());
2012 }
2013 Err(error) => {
2014 warn!("Failed to deserialize room pinned events: {error}");
2015 }
2016 }
2017 }
2018 Ok(_) => {
2019 }
2021 Err(error) => {
2022 warn!("Failed to load room pinned events: {error}");
2023 }
2024 }
2025
2026 self.version = 1;
2027 migrated = true;
2028 }
2029
2030 migrated
2031 }
2032}
2033
2034pub fn apply_redaction(
2037 event: &Raw<AnySyncTimelineEvent>,
2038 raw_redaction: &Raw<SyncRoomRedactionEvent>,
2039 room_version: &RoomVersionId,
2040) -> Option<Raw<AnySyncTimelineEvent>> {
2041 use ruma::canonical_json::{redact_in_place, RedactedBecause};
2042
2043 let mut event_json = match event.deserialize_as() {
2044 Ok(json) => json,
2045 Err(e) => {
2046 warn!("Failed to deserialize latest event: {e}");
2047 return None;
2048 }
2049 };
2050
2051 let redacted_because = match RedactedBecause::from_raw_event(raw_redaction) {
2052 Ok(rb) => rb,
2053 Err(e) => {
2054 warn!("Redaction event is not valid canonical JSON: {e}");
2055 return None;
2056 }
2057 };
2058
2059 let redact_result = redact_in_place(&mut event_json, room_version, Some(redacted_because));
2060
2061 if let Err(e) = redact_result {
2062 warn!("Failed to redact event: {e}");
2063 return None;
2064 }
2065
2066 let raw = Raw::new(&event_json).expect("CanonicalJsonObject must be serializable");
2067 Some(raw.cast())
2068}
2069
2070bitflags! {
2071 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2076 pub struct RoomStateFilter: u16 {
2077 const JOINED = 0b00000001;
2079 const INVITED = 0b00000010;
2081 const LEFT = 0b00000100;
2083 const KNOCKED = 0b00001000;
2085 const BANNED = 0b00010000;
2087 }
2088}
2089
2090impl RoomStateFilter {
2091 pub fn matches(&self, state: RoomState) -> bool {
2093 if self.is_empty() {
2094 return true;
2095 }
2096
2097 let bit_state = match state {
2098 RoomState::Joined => Self::JOINED,
2099 RoomState::Left => Self::LEFT,
2100 RoomState::Invited => Self::INVITED,
2101 RoomState::Knocked => Self::KNOCKED,
2102 RoomState::Banned => Self::BANNED,
2103 };
2104
2105 self.contains(bit_state)
2106 }
2107
2108 pub fn as_vec(&self) -> Vec<RoomState> {
2110 let mut states = Vec::new();
2111
2112 if self.contains(Self::JOINED) {
2113 states.push(RoomState::Joined);
2114 }
2115 if self.contains(Self::LEFT) {
2116 states.push(RoomState::Left);
2117 }
2118 if self.contains(Self::INVITED) {
2119 states.push(RoomState::Invited);
2120 }
2121 if self.contains(Self::KNOCKED) {
2122 states.push(RoomState::Knocked);
2123 }
2124 if self.contains(Self::BANNED) {
2125 states.push(RoomState::Banned);
2126 }
2127
2128 states
2129 }
2130}
2131
2132fn compute_display_name_from_heroes(
2136 num_joined_invited: u64,
2137 mut heroes: Vec<&str>,
2138) -> RoomDisplayName {
2139 let num_heroes = heroes.len() as u64;
2140 let num_joined_invited_except_self = num_joined_invited.saturating_sub(1);
2141
2142 heroes.sort_unstable();
2144
2145 let names = if num_heroes == 0 && num_joined_invited > 1 {
2146 format!("{} people", num_joined_invited)
2147 } else if num_heroes >= num_joined_invited_except_self {
2148 heroes.join(", ")
2149 } else if num_heroes < num_joined_invited_except_self && num_joined_invited > 1 {
2150 format!("{}, and {} others", heroes.join(", "), (num_joined_invited - num_heroes))
2153 } else {
2154 "".to_owned()
2155 };
2156
2157 if num_joined_invited <= 1 {
2159 if names.is_empty() {
2160 RoomDisplayName::Empty
2161 } else {
2162 RoomDisplayName::EmptyWas(names)
2163 }
2164 } else {
2165 RoomDisplayName::Calculated(names)
2166 }
2167}
2168
2169#[derive(Debug)]
2171#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2172pub enum EncryptionState {
2173 Encrypted,
2175
2176 NotEncrypted,
2178
2179 Unknown,
2182}
2183
2184impl EncryptionState {
2185 pub fn is_encrypted(&self) -> bool {
2187 matches!(self, Self::Encrypted)
2188 }
2189
2190 pub fn is_unknown(&self) -> bool {
2192 matches!(self, Self::Unknown)
2193 }
2194}
2195
2196#[cfg(test)]
2197mod tests {
2198 use std::{
2199 collections::BTreeSet,
2200 ops::{Not, Sub},
2201 str::FromStr,
2202 sync::Arc,
2203 time::Duration,
2204 };
2205
2206 use assert_matches::assert_matches;
2207 use assign::assign;
2208 use matrix_sdk_common::deserialized_responses::TimelineEvent;
2209 use matrix_sdk_test::{
2210 async_test,
2211 event_factory::EventFactory,
2212 test_json::{sync_events::PINNED_EVENTS, TAG},
2213 ALICE, BOB, CAROL,
2214 };
2215 use ruma::{
2216 api::client::sync::sync_events::v3::RoomSummary as RumaSummary,
2217 device_id, event_id,
2218 events::{
2219 call::member::{
2220 ActiveFocus, ActiveLivekitFocus, Application, CallApplicationContent,
2221 CallMemberEventContent, CallMemberStateKey, Focus, LegacyMembershipData,
2222 LegacyMembershipDataInit, LivekitFocus, OriginalSyncCallMemberEvent,
2223 },
2224 room::{
2225 canonical_alias::RoomCanonicalAliasEventContent,
2226 encryption::{OriginalSyncRoomEncryptionEvent, RoomEncryptionEventContent},
2227 member::{MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent},
2228 name::RoomNameEventContent,
2229 pinned_events::RoomPinnedEventsEventContent,
2230 },
2231 AnySyncStateEvent, EmptyStateKey, StateEventType, StateUnsigned, SyncStateEvent,
2232 },
2233 owned_event_id, owned_room_id, owned_user_id, room_alias_id, room_id,
2234 serde::Raw,
2235 time::SystemTime,
2236 user_id, DeviceId, EventEncryptionAlgorithm, EventId, MilliSecondsSinceUnixEpoch,
2237 OwnedEventId, OwnedUserId, UserId,
2238 };
2239 use serde_json::json;
2240 use similar_asserts::assert_eq;
2241 use stream_assert::{assert_pending, assert_ready};
2242
2243 use super::{
2244 compute_display_name_from_heroes, EncryptionState, Room, RoomHero, RoomInfo, RoomState,
2245 SyncInfo,
2246 };
2247 use crate::{
2248 latest_event::LatestEvent,
2249 rooms::RoomNotableTags,
2250 store::{
2251 IntoStateStore, MemoryStore, RoomLoadSettings, StateChanges, StateStore, StoreConfig,
2252 },
2253 test_utils::logged_in_base_client,
2254 BaseClient, MinimalStateEvent, OriginalMinimalStateEvent, RoomDisplayName,
2255 RoomInfoNotableUpdateReasons, RoomStateFilter, SessionMeta,
2256 };
2257
2258 #[test]
2259 fn test_room_info_serialization() {
2260 use ruma::owned_user_id;
2264
2265 use super::RoomSummary;
2266 use crate::{rooms::BaseRoomInfo, sync::UnreadNotificationsCount};
2267
2268 let info = RoomInfo {
2269 version: 1,
2270 room_id: room_id!("!gda78o:server.tld").into(),
2271 room_state: RoomState::Invited,
2272 prev_room_state: None,
2273 notification_counts: UnreadNotificationsCount {
2274 highlight_count: 1,
2275 notification_count: 2,
2276 },
2277 summary: RoomSummary {
2278 room_heroes: vec![RoomHero {
2279 user_id: owned_user_id!("@somebody:example.org"),
2280 display_name: None,
2281 avatar_url: None,
2282 }],
2283 joined_member_count: 5,
2284 invited_member_count: 0,
2285 },
2286 members_synced: true,
2287 last_prev_batch: Some("pb".to_owned()),
2288 sync_info: SyncInfo::FullySynced,
2289 encryption_state_synced: true,
2290 latest_event: Some(Box::new(LatestEvent::new(TimelineEvent::new(
2291 Raw::from_json_string(json!({"sender": "@u:i.uk"}).to_string()).unwrap(),
2292 )))),
2293 base_info: Box::new(
2294 assign!(BaseRoomInfo::new(), { pinned_events: Some(RoomPinnedEventsEventContent::new(vec![owned_event_id!("$a")])) }),
2295 ),
2296 read_receipts: Default::default(),
2297 warned_about_unknown_room_version: Arc::new(false.into()),
2298 cached_display_name: None,
2299 cached_user_defined_notification_mode: None,
2300 recency_stamp: Some(42),
2301 };
2302
2303 let info_json = json!({
2304 "version": 1,
2305 "room_id": "!gda78o:server.tld",
2306 "room_state": "Invited",
2307 "prev_room_state": null,
2308 "notification_counts": {
2309 "highlight_count": 1,
2310 "notification_count": 2,
2311 },
2312 "summary": {
2313 "room_heroes": [{
2314 "user_id": "@somebody:example.org",
2315 "display_name": null,
2316 "avatar_url": null
2317 }],
2318 "joined_member_count": 5,
2319 "invited_member_count": 0,
2320 },
2321 "members_synced": true,
2322 "last_prev_batch": "pb",
2323 "sync_info": "FullySynced",
2324 "encryption_state_synced": true,
2325 "latest_event": {
2326 "event": {
2327 "kind": {"PlainText": {"event": {"sender": "@u:i.uk"}}},
2328 },
2329 },
2330 "base_info": {
2331 "avatar": null,
2332 "canonical_alias": null,
2333 "create": null,
2334 "dm_targets": [],
2335 "encryption": null,
2336 "guest_access": null,
2337 "history_visibility": null,
2338 "is_marked_unread": false,
2339 "join_rules": null,
2340 "max_power_level": 100,
2341 "name": null,
2342 "tombstone": null,
2343 "topic": null,
2344 "pinned_events": {
2345 "pinned": ["$a"]
2346 },
2347 },
2348 "read_receipts": {
2349 "num_unread": 0,
2350 "num_mentions": 0,
2351 "num_notifications": 0,
2352 "latest_active": null,
2353 "pending": []
2354 },
2355 "recency_stamp": 42,
2356 });
2357
2358 assert_eq!(serde_json::to_value(info).unwrap(), info_json);
2359 }
2360
2361 #[test]
2368 fn test_room_info_deserialization_without_optional_items() {
2369 use ruma::{owned_mxc_uri, owned_user_id};
2370
2371 let info_json = json!({
2374 "room_id": "!gda78o:server.tld",
2375 "room_state": "Invited",
2376 "prev_room_state": null,
2377 "notification_counts": {
2378 "highlight_count": 1,
2379 "notification_count": 2,
2380 },
2381 "summary": {
2382 "room_heroes": [{
2383 "user_id": "@somebody:example.org",
2384 "display_name": "Somebody",
2385 "avatar_url": "mxc://example.org/abc"
2386 }],
2387 "joined_member_count": 5,
2388 "invited_member_count": 0,
2389 },
2390 "members_synced": true,
2391 "last_prev_batch": "pb",
2392 "sync_info": "FullySynced",
2393 "encryption_state_synced": true,
2394 "base_info": {
2395 "avatar": null,
2396 "canonical_alias": null,
2397 "create": null,
2398 "dm_targets": [],
2399 "encryption": null,
2400 "guest_access": null,
2401 "history_visibility": null,
2402 "join_rules": null,
2403 "max_power_level": 100,
2404 "name": null,
2405 "tombstone": null,
2406 "topic": null,
2407 },
2408 });
2409
2410 let info: RoomInfo = serde_json::from_value(info_json).unwrap();
2411
2412 assert_eq!(info.room_id, room_id!("!gda78o:server.tld"));
2413 assert_eq!(info.room_state, RoomState::Invited);
2414 assert_eq!(info.notification_counts.highlight_count, 1);
2415 assert_eq!(info.notification_counts.notification_count, 2);
2416 assert_eq!(
2417 info.summary.room_heroes,
2418 vec![RoomHero {
2419 user_id: owned_user_id!("@somebody:example.org"),
2420 display_name: Some("Somebody".to_owned()),
2421 avatar_url: Some(owned_mxc_uri!("mxc://example.org/abc")),
2422 }]
2423 );
2424 assert_eq!(info.summary.joined_member_count, 5);
2425 assert_eq!(info.summary.invited_member_count, 0);
2426 assert!(info.members_synced);
2427 assert_eq!(info.last_prev_batch, Some("pb".to_owned()));
2428 assert_eq!(info.sync_info, SyncInfo::FullySynced);
2429 assert!(info.encryption_state_synced);
2430 assert!(info.base_info.avatar.is_none());
2431 assert!(info.base_info.canonical_alias.is_none());
2432 assert!(info.base_info.create.is_none());
2433 assert_eq!(info.base_info.dm_targets.len(), 0);
2434 assert!(info.base_info.encryption.is_none());
2435 assert!(info.base_info.guest_access.is_none());
2436 assert!(info.base_info.history_visibility.is_none());
2437 assert!(info.base_info.join_rules.is_none());
2438 assert_eq!(info.base_info.max_power_level, 100);
2439 assert!(info.base_info.name.is_none());
2440 assert!(info.base_info.tombstone.is_none());
2441 assert!(info.base_info.topic.is_none());
2442 }
2443
2444 #[test]
2445 fn test_room_info_deserialization() {
2446 use ruma::{owned_mxc_uri, owned_user_id};
2447
2448 use crate::notification_settings::RoomNotificationMode;
2449
2450 let info_json = json!({
2451 "room_id": "!gda78o:server.tld",
2452 "room_state": "Joined",
2453 "prev_room_state": "Invited",
2454 "notification_counts": {
2455 "highlight_count": 1,
2456 "notification_count": 2,
2457 },
2458 "summary": {
2459 "room_heroes": [{
2460 "user_id": "@somebody:example.org",
2461 "display_name": "Somebody",
2462 "avatar_url": "mxc://example.org/abc"
2463 }],
2464 "joined_member_count": 5,
2465 "invited_member_count": 0,
2466 },
2467 "members_synced": true,
2468 "last_prev_batch": "pb",
2469 "sync_info": "FullySynced",
2470 "encryption_state_synced": true,
2471 "base_info": {
2472 "avatar": null,
2473 "canonical_alias": null,
2474 "create": null,
2475 "dm_targets": [],
2476 "encryption": null,
2477 "guest_access": null,
2478 "history_visibility": null,
2479 "join_rules": null,
2480 "max_power_level": 100,
2481 "name": null,
2482 "tombstone": null,
2483 "topic": null,
2484 },
2485 "cached_display_name": { "Calculated": "lol" },
2486 "cached_user_defined_notification_mode": "Mute",
2487 "recency_stamp": 42,
2488 });
2489
2490 let info: RoomInfo = serde_json::from_value(info_json).unwrap();
2491
2492 assert_eq!(info.room_id, room_id!("!gda78o:server.tld"));
2493 assert_eq!(info.room_state, RoomState::Joined);
2494 assert_eq!(info.prev_room_state, Some(RoomState::Invited));
2495 assert_eq!(info.notification_counts.highlight_count, 1);
2496 assert_eq!(info.notification_counts.notification_count, 2);
2497 assert_eq!(
2498 info.summary.room_heroes,
2499 vec![RoomHero {
2500 user_id: owned_user_id!("@somebody:example.org"),
2501 display_name: Some("Somebody".to_owned()),
2502 avatar_url: Some(owned_mxc_uri!("mxc://example.org/abc")),
2503 }]
2504 );
2505 assert_eq!(info.summary.joined_member_count, 5);
2506 assert_eq!(info.summary.invited_member_count, 0);
2507 assert!(info.members_synced);
2508 assert_eq!(info.last_prev_batch, Some("pb".to_owned()));
2509 assert_eq!(info.sync_info, SyncInfo::FullySynced);
2510 assert!(info.encryption_state_synced);
2511 assert!(info.latest_event.is_none());
2512 assert!(info.base_info.avatar.is_none());
2513 assert!(info.base_info.canonical_alias.is_none());
2514 assert!(info.base_info.create.is_none());
2515 assert_eq!(info.base_info.dm_targets.len(), 0);
2516 assert!(info.base_info.encryption.is_none());
2517 assert!(info.base_info.guest_access.is_none());
2518 assert!(info.base_info.history_visibility.is_none());
2519 assert!(info.base_info.join_rules.is_none());
2520 assert_eq!(info.base_info.max_power_level, 100);
2521 assert!(info.base_info.name.is_none());
2522 assert!(info.base_info.tombstone.is_none());
2523 assert!(info.base_info.topic.is_none());
2524
2525 assert_eq!(
2526 info.cached_display_name.as_ref(),
2527 Some(&RoomDisplayName::Calculated("lol".to_owned())),
2528 );
2529 assert_eq!(
2530 info.cached_user_defined_notification_mode.as_ref(),
2531 Some(&RoomNotificationMode::Mute)
2532 );
2533 assert_eq!(info.recency_stamp.as_ref(), Some(&42));
2534 }
2535
2536 #[async_test]
2537 async fn test_is_favourite() {
2538 let client =
2540 BaseClient::new(StoreConfig::new("cross-process-store-locks-holder-name".to_owned()));
2541
2542 client
2543 .activate(
2544 SessionMeta {
2545 user_id: user_id!("@alice:example.org").into(),
2546 device_id: ruma::device_id!("AYEAYEAYE").into(),
2547 },
2548 RoomLoadSettings::default(),
2549 #[cfg(feature = "e2e-encryption")]
2550 None,
2551 )
2552 .await
2553 .unwrap();
2554
2555 let room_id = room_id!("!test:localhost");
2556 let room = client.get_or_create_room(room_id, RoomState::Joined);
2557
2558 assert!(room.is_favourite().not());
2560
2561 let mut room_info_subscriber = room.subscribe_info();
2563
2564 assert_pending!(room_info_subscriber);
2565
2566 let tag_raw = Raw::new(&json!({
2568 "content": {
2569 "tags": {
2570 "m.favourite": {
2571 "order": 0.0
2572 },
2573 },
2574 },
2575 "type": "m.tag",
2576 }))
2577 .unwrap()
2578 .cast();
2579
2580 let mut changes = StateChanges::default();
2582 client
2583 .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2584 .await;
2585 client.apply_changes(&changes, Default::default(), None);
2586
2587 assert_ready!(room_info_subscriber);
2589 assert_pending!(room_info_subscriber);
2590
2591 assert!(room.is_favourite());
2593
2594 let tag_raw = Raw::new(&json!({
2596 "content": {
2597 "tags": {},
2598 },
2599 "type": "m.tag"
2600 }))
2601 .unwrap()
2602 .cast();
2603 client
2604 .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2605 .await;
2606 client.apply_changes(&changes, Default::default(), None);
2607
2608 assert_ready!(room_info_subscriber);
2610 assert_pending!(room_info_subscriber);
2611
2612 assert!(room.is_favourite().not());
2614 }
2615
2616 #[async_test]
2617 async fn test_is_low_priority() {
2618 let client =
2620 BaseClient::new(StoreConfig::new("cross-process-store-locks-holder-name".to_owned()));
2621
2622 client
2623 .activate(
2624 SessionMeta {
2625 user_id: user_id!("@alice:example.org").into(),
2626 device_id: ruma::device_id!("AYEAYEAYE").into(),
2627 },
2628 RoomLoadSettings::default(),
2629 #[cfg(feature = "e2e-encryption")]
2630 None,
2631 )
2632 .await
2633 .unwrap();
2634
2635 let room_id = room_id!("!test:localhost");
2636 let room = client.get_or_create_room(room_id, RoomState::Joined);
2637
2638 assert!(!room.is_low_priority());
2640
2641 let mut room_info_subscriber = room.subscribe_info();
2643
2644 assert_pending!(room_info_subscriber);
2645
2646 let tag_raw = Raw::new(&json!({
2648 "content": {
2649 "tags": {
2650 "m.lowpriority": {
2651 "order": 0.0
2652 },
2653 }
2654 },
2655 "type": "m.tag"
2656 }))
2657 .unwrap()
2658 .cast();
2659
2660 let mut changes = StateChanges::default();
2662 client
2663 .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2664 .await;
2665 client.apply_changes(&changes, Default::default(), None);
2666
2667 assert_ready!(room_info_subscriber);
2669 assert_pending!(room_info_subscriber);
2670
2671 assert!(room.is_low_priority());
2673
2674 let tag_raw = Raw::new(&json!({
2676 "content": {
2677 "tags": {},
2678 },
2679 "type": "m.tag"
2680 }))
2681 .unwrap()
2682 .cast();
2683 client
2684 .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2685 .await;
2686 client.apply_changes(&changes, Default::default(), None);
2687
2688 assert_ready!(room_info_subscriber);
2690 assert_pending!(room_info_subscriber);
2691
2692 assert!(room.is_low_priority().not());
2694 }
2695
2696 fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
2697 let store = Arc::new(MemoryStore::new());
2698 let user_id = user_id!("@me:example.org");
2699 let room_id = room_id!("!test:localhost");
2700 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2701
2702 (store.clone(), Room::new(user_id, store, room_id, room_type, sender))
2703 }
2704
2705 fn make_stripped_member_event(user_id: &UserId, name: &str) -> Raw<StrippedRoomMemberEvent> {
2706 let ev_json = json!({
2707 "type": "m.room.member",
2708 "content": assign!(RoomMemberEventContent::new(MembershipState::Join), {
2709 displayname: Some(name.to_owned())
2710 }),
2711 "sender": user_id,
2712 "state_key": user_id,
2713 });
2714
2715 Raw::new(&ev_json).unwrap().cast()
2716 }
2717
2718 #[async_test]
2719 async fn test_display_name_for_joined_room_is_empty_if_no_info() {
2720 let (_, room) = make_room_test_helper(RoomState::Joined);
2721 assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2722 }
2723
2724 #[async_test]
2725 async fn test_display_name_for_joined_room_uses_canonical_alias_if_available() {
2726 let (_, room) = make_room_test_helper(RoomState::Joined);
2727 room.inner
2728 .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2729 assert_eq!(
2730 room.compute_display_name().await.unwrap(),
2731 RoomDisplayName::Aliased("test".to_owned())
2732 );
2733 }
2734
2735 #[async_test]
2736 async fn test_display_name_for_joined_room_prefers_name_over_alias() {
2737 let (_, room) = make_room_test_helper(RoomState::Joined);
2738 room.inner
2739 .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2740 assert_eq!(
2741 room.compute_display_name().await.unwrap(),
2742 RoomDisplayName::Aliased("test".to_owned())
2743 );
2744 room.inner.update(|info| info.base_info.name = Some(make_name_event()));
2745 assert_eq!(
2747 room.compute_display_name().await.unwrap(),
2748 RoomDisplayName::Named("Test Room".to_owned())
2749 );
2750 }
2751
2752 #[async_test]
2753 async fn test_display_name_for_invited_room_is_empty_if_no_info() {
2754 let (_, room) = make_room_test_helper(RoomState::Invited);
2755 assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2756 }
2757
2758 #[async_test]
2759 async fn test_display_name_for_invited_room_is_empty_if_room_name_empty() {
2760 let (_, room) = make_room_test_helper(RoomState::Invited);
2761
2762 let room_name = MinimalStateEvent::Original(OriginalMinimalStateEvent {
2763 content: RoomNameEventContent::new(String::new()),
2764 event_id: None,
2765 });
2766 room.inner.update(|info| info.base_info.name = Some(room_name));
2767
2768 assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2769 }
2770
2771 #[async_test]
2772 async fn test_display_name_for_invited_room_uses_canonical_alias_if_available() {
2773 let (_, room) = make_room_test_helper(RoomState::Invited);
2774 room.inner
2775 .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2776 assert_eq!(
2777 room.compute_display_name().await.unwrap(),
2778 RoomDisplayName::Aliased("test".to_owned())
2779 );
2780 }
2781
2782 #[async_test]
2783 async fn test_display_name_for_invited_room_prefers_name_over_alias() {
2784 let (_, room) = make_room_test_helper(RoomState::Invited);
2785 room.inner
2786 .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2787 assert_eq!(
2788 room.compute_display_name().await.unwrap(),
2789 RoomDisplayName::Aliased("test".to_owned())
2790 );
2791 room.inner.update(|info| info.base_info.name = Some(make_name_event()));
2792 assert_eq!(
2794 room.compute_display_name().await.unwrap(),
2795 RoomDisplayName::Named("Test Room".to_owned())
2796 );
2797 }
2798
2799 fn make_canonical_alias_event() -> MinimalStateEvent<RoomCanonicalAliasEventContent> {
2800 MinimalStateEvent::Original(OriginalMinimalStateEvent {
2801 content: assign!(RoomCanonicalAliasEventContent::new(), {
2802 alias: Some(room_alias_id!("#test:example.com").to_owned()),
2803 }),
2804 event_id: None,
2805 })
2806 }
2807
2808 fn make_name_event() -> MinimalStateEvent<RoomNameEventContent> {
2809 MinimalStateEvent::Original(OriginalMinimalStateEvent {
2810 content: RoomNameEventContent::new("Test Room".to_owned()),
2811 event_id: None,
2812 })
2813 }
2814
2815 #[async_test]
2816 async fn test_display_name_dm_invited() {
2817 let (store, room) = make_room_test_helper(RoomState::Invited);
2818 let room_id = room_id!("!test:localhost");
2819 let matthew = user_id!("@matthew:example.org");
2820 let me = user_id!("@me:example.org");
2821 let mut changes = StateChanges::new("".to_owned());
2822 let summary = assign!(RumaSummary::new(), {
2823 heroes: vec![me.to_owned(), matthew.to_owned()],
2824 });
2825
2826 changes.add_stripped_member(
2827 room_id,
2828 matthew,
2829 make_stripped_member_event(matthew, "Matthew"),
2830 );
2831 changes.add_stripped_member(room_id, me, make_stripped_member_event(me, "Me"));
2832 store.save_changes(&changes).await.unwrap();
2833
2834 room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2835 assert_eq!(
2836 room.compute_display_name().await.unwrap(),
2837 RoomDisplayName::Calculated("Matthew".to_owned())
2838 );
2839 }
2840
2841 #[async_test]
2842 async fn test_display_name_dm_invited_no_heroes() {
2843 let (store, room) = make_room_test_helper(RoomState::Invited);
2844 let room_id = room_id!("!test:localhost");
2845 let matthew = user_id!("@matthew:example.org");
2846 let me = user_id!("@me:example.org");
2847 let mut changes = StateChanges::new("".to_owned());
2848
2849 changes.add_stripped_member(
2850 room_id,
2851 matthew,
2852 make_stripped_member_event(matthew, "Matthew"),
2853 );
2854 changes.add_stripped_member(room_id, me, make_stripped_member_event(me, "Me"));
2855 store.save_changes(&changes).await.unwrap();
2856
2857 assert_eq!(
2858 room.compute_display_name().await.unwrap(),
2859 RoomDisplayName::Calculated("Matthew".to_owned())
2860 );
2861 }
2862
2863 #[async_test]
2864 async fn test_display_name_dm_joined() {
2865 let (store, room) = make_room_test_helper(RoomState::Joined);
2866 let room_id = room_id!("!test:localhost");
2867 let matthew = user_id!("@matthew:example.org");
2868 let me = user_id!("@me:example.org");
2869
2870 let mut changes = StateChanges::new("".to_owned());
2871 let summary = assign!(RumaSummary::new(), {
2872 joined_member_count: Some(2u32.into()),
2873 heroes: vec![me.to_owned(), matthew.to_owned()],
2874 });
2875
2876 let f = EventFactory::new().room(room_id!("!test:localhost"));
2877
2878 let members = changes
2879 .state
2880 .entry(room_id.to_owned())
2881 .or_default()
2882 .entry(StateEventType::RoomMember)
2883 .or_default();
2884 members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
2885 members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2886
2887 store.save_changes(&changes).await.unwrap();
2888
2889 room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2890 assert_eq!(
2891 room.compute_display_name().await.unwrap(),
2892 RoomDisplayName::Calculated("Matthew".to_owned())
2893 );
2894 }
2895
2896 #[async_test]
2897 async fn test_display_name_dm_joined_service_members() {
2898 let (store, room) = make_room_test_helper(RoomState::Joined);
2899 let room_id = room_id!("!test:localhost");
2900
2901 let matthew = user_id!("@sahasrhala:example.org");
2902 let me = user_id!("@me:example.org");
2903 let bot = user_id!("@bot:example.org");
2904
2905 let mut changes = StateChanges::new("".to_owned());
2906 let summary = assign!(RumaSummary::new(), {
2907 joined_member_count: Some(3u32.into()),
2908 heroes: vec![me.to_owned(), matthew.to_owned(), bot.to_owned()],
2909 });
2910
2911 let f = EventFactory::new().room(room_id!("!test:localhost"));
2912
2913 let members = changes
2914 .state
2915 .entry(room_id.to_owned())
2916 .or_default()
2917 .entry(StateEventType::RoomMember)
2918 .or_default();
2919 members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
2920 members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2921 members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
2922
2923 let member_hints_content =
2924 f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
2925 changes
2926 .state
2927 .entry(room_id.to_owned())
2928 .or_default()
2929 .entry(StateEventType::MemberHints)
2930 .or_default()
2931 .insert("".to_owned(), member_hints_content);
2932
2933 store.save_changes(&changes).await.unwrap();
2934
2935 room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2936 assert_eq!(
2938 room.compute_display_name().await.unwrap(),
2939 RoomDisplayName::Calculated("Matthew".to_owned())
2940 );
2941 }
2942
2943 #[async_test]
2944 async fn test_display_name_dm_joined_alone_with_service_members() {
2945 let (store, room) = make_room_test_helper(RoomState::Joined);
2946 let room_id = room_id!("!test:localhost");
2947
2948 let me = user_id!("@me:example.org");
2949 let bot = user_id!("@bot:example.org");
2950
2951 let mut changes = StateChanges::new("".to_owned());
2952 let summary = assign!(RumaSummary::new(), {
2953 joined_member_count: Some(2u32.into()),
2954 heroes: vec![me.to_owned(), bot.to_owned()],
2955 });
2956
2957 let f = EventFactory::new().room(room_id!("!test:localhost"));
2958
2959 let members = changes
2960 .state
2961 .entry(room_id.to_owned())
2962 .or_default()
2963 .entry(StateEventType::RoomMember)
2964 .or_default();
2965 members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2966 members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
2967
2968 let member_hints_content =
2969 f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
2970 changes
2971 .state
2972 .entry(room_id.to_owned())
2973 .or_default()
2974 .entry(StateEventType::MemberHints)
2975 .or_default()
2976 .insert("".to_owned(), member_hints_content);
2977
2978 store.save_changes(&changes).await.unwrap();
2979
2980 room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2981 assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2983 }
2984
2985 #[async_test]
2986 async fn test_display_name_dm_joined_no_heroes() {
2987 let (store, room) = make_room_test_helper(RoomState::Joined);
2988 let room_id = room_id!("!test:localhost");
2989 let matthew = user_id!("@matthew:example.org");
2990 let me = user_id!("@me:example.org");
2991 let mut changes = StateChanges::new("".to_owned());
2992
2993 let f = EventFactory::new().room(room_id!("!test:localhost"));
2994
2995 let members = changes
2996 .state
2997 .entry(room_id.to_owned())
2998 .or_default()
2999 .entry(StateEventType::RoomMember)
3000 .or_default();
3001 members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
3002 members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3003
3004 store.save_changes(&changes).await.unwrap();
3005
3006 assert_eq!(
3007 room.compute_display_name().await.unwrap(),
3008 RoomDisplayName::Calculated("Matthew".to_owned())
3009 );
3010 }
3011
3012 #[async_test]
3013 async fn test_display_name_dm_joined_no_heroes_service_members() {
3014 let (store, room) = make_room_test_helper(RoomState::Joined);
3015 let room_id = room_id!("!test:localhost");
3016
3017 let matthew = user_id!("@matthew:example.org");
3018 let me = user_id!("@me:example.org");
3019 let bot = user_id!("@bot:example.org");
3020
3021 let mut changes = StateChanges::new("".to_owned());
3022
3023 let f = EventFactory::new().room(room_id!("!test:localhost"));
3024
3025 let members = changes
3026 .state
3027 .entry(room_id.to_owned())
3028 .or_default()
3029 .entry(StateEventType::RoomMember)
3030 .or_default();
3031 members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
3032 members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3033 members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
3034
3035 let member_hints_content =
3036 f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
3037 changes
3038 .state
3039 .entry(room_id.to_owned())
3040 .or_default()
3041 .entry(StateEventType::MemberHints)
3042 .or_default()
3043 .insert("".to_owned(), member_hints_content);
3044
3045 store.save_changes(&changes).await.unwrap();
3046
3047 assert_eq!(
3048 room.compute_display_name().await.unwrap(),
3049 RoomDisplayName::Calculated("Matthew".to_owned())
3050 );
3051 }
3052
3053 #[async_test]
3054 async fn test_display_name_deterministic() {
3055 let (store, room) = make_room_test_helper(RoomState::Joined);
3056
3057 let alice = user_id!("@alice:example.org");
3058 let bob = user_id!("@bob:example.org");
3059 let carol = user_id!("@carol:example.org");
3060 let denis = user_id!("@denis:example.org");
3061 let erica = user_id!("@erica:example.org");
3062 let fred = user_id!("@fred:example.org");
3063 let me = user_id!("@me:example.org");
3064
3065 let mut changes = StateChanges::new("".to_owned());
3066
3067 let f = EventFactory::new().room(room_id!("!test:localhost"));
3068
3069 {
3072 let members = changes
3073 .state
3074 .entry(room.room_id().to_owned())
3075 .or_default()
3076 .entry(StateEventType::RoomMember)
3077 .or_default();
3078 members.insert(carol.into(), f.member(carol).display_name("Carol").into_raw());
3079 members.insert(bob.into(), f.member(bob).display_name("Bob").into_raw());
3080 members.insert(fred.into(), f.member(fred).display_name("Fred").into_raw());
3081 members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3082 store.save_changes(&changes).await.unwrap();
3083 }
3084
3085 {
3086 let members = changes
3087 .state
3088 .entry(room.room_id().to_owned())
3089 .or_default()
3090 .entry(StateEventType::RoomMember)
3091 .or_default();
3092 members.insert(alice.into(), f.member(alice).display_name("Alice").into_raw());
3093 members.insert(erica.into(), f.member(erica).display_name("Erica").into_raw());
3094 members.insert(denis.into(), f.member(denis).display_name("Denis").into_raw());
3095 store.save_changes(&changes).await.unwrap();
3096 }
3097
3098 let summary = assign!(RumaSummary::new(), {
3099 joined_member_count: Some(7u32.into()),
3100 heroes: vec![denis.to_owned(), carol.to_owned(), bob.to_owned(), erica.to_owned()],
3101 });
3102 room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
3103
3104 assert_eq!(
3105 room.compute_display_name().await.unwrap(),
3106 RoomDisplayName::Calculated("Bob, Carol, Denis, Erica, and 3 others".to_owned())
3107 );
3108 }
3109
3110 #[async_test]
3111 async fn test_display_name_deterministic_no_heroes() {
3112 let (store, room) = make_room_test_helper(RoomState::Joined);
3113
3114 let alice = user_id!("@alice:example.org");
3115 let bob = user_id!("@bob:example.org");
3116 let carol = user_id!("@carol:example.org");
3117 let denis = user_id!("@denis:example.org");
3118 let erica = user_id!("@erica:example.org");
3119 let fred = user_id!("@fred:example.org");
3120 let me = user_id!("@me:example.org");
3121
3122 let f = EventFactory::new().room(room_id!("!test:localhost"));
3123
3124 let mut changes = StateChanges::new("".to_owned());
3125
3126 {
3129 let members = changes
3130 .state
3131 .entry(room.room_id().to_owned())
3132 .or_default()
3133 .entry(StateEventType::RoomMember)
3134 .or_default();
3135 members.insert(carol.into(), f.member(carol).display_name("Carol").into_raw());
3136 members.insert(bob.into(), f.member(bob).display_name("Bob").into_raw());
3137 members.insert(fred.into(), f.member(fred).display_name("Fred").into_raw());
3138 members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3139
3140 store.save_changes(&changes).await.unwrap();
3141 }
3142
3143 {
3144 let members = changes
3145 .state
3146 .entry(room.room_id().to_owned())
3147 .or_default()
3148 .entry(StateEventType::RoomMember)
3149 .or_default();
3150 members.insert(alice.into(), f.member(alice).display_name("Alice").into_raw());
3151 members.insert(erica.into(), f.member(erica).display_name("Erica").into_raw());
3152 members.insert(denis.into(), f.member(denis).display_name("Denis").into_raw());
3153 store.save_changes(&changes).await.unwrap();
3154 }
3155
3156 assert_eq!(
3157 room.compute_display_name().await.unwrap(),
3158 RoomDisplayName::Calculated("Alice, Bob, Carol, Denis, Erica, and 2 others".to_owned())
3159 );
3160 }
3161
3162 #[async_test]
3163 async fn test_display_name_dm_alone() {
3164 let (store, room) = make_room_test_helper(RoomState::Joined);
3165 let room_id = room_id!("!test:localhost");
3166 let matthew = user_id!("@matthew:example.org");
3167 let me = user_id!("@me:example.org");
3168 let mut changes = StateChanges::new("".to_owned());
3169 let summary = assign!(RumaSummary::new(), {
3170 joined_member_count: Some(1u32.into()),
3171 heroes: vec![me.to_owned(), matthew.to_owned()],
3172 });
3173
3174 let f = EventFactory::new().room(room_id!("!test:localhost"));
3175
3176 let members = changes
3177 .state
3178 .entry(room_id.to_owned())
3179 .or_default()
3180 .entry(StateEventType::RoomMember)
3181 .or_default();
3182 members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
3183 members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3184
3185 store.save_changes(&changes).await.unwrap();
3186
3187 room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
3188 assert_eq!(
3189 room.compute_display_name().await.unwrap(),
3190 RoomDisplayName::EmptyWas("Matthew".to_owned())
3191 );
3192 }
3193
3194 #[cfg(feature = "e2e-encryption")]
3195 #[async_test]
3196 async fn test_setting_the_latest_event_doesnt_cause_a_room_info_notable_update() {
3197 use std::collections::BTreeMap;
3198
3199 use assert_matches::assert_matches;
3200
3201 use crate::{RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons};
3202
3203 let client =
3205 BaseClient::new(StoreConfig::new("cross-process-store-locks-holder-name".to_owned()));
3206
3207 client
3208 .activate(
3209 SessionMeta {
3210 user_id: user_id!("@alice:example.org").into(),
3211 device_id: ruma::device_id!("AYEAYEAYE").into(),
3212 },
3213 RoomLoadSettings::default(),
3214 None,
3215 )
3216 .await
3217 .unwrap();
3218
3219 let room_id = room_id!("!test:localhost");
3220 let room = client.get_or_create_room(room_id, RoomState::Joined);
3221
3222 add_encrypted_event(&room, "$A");
3224 assert!(room.latest_event().is_none());
3226
3227 let mut room_info_notable_update = client.room_info_notable_update_receiver();
3229
3230 let event = make_latest_event("$A");
3232
3233 let mut changes = StateChanges::default();
3234 let mut room_info_notable_updates = BTreeMap::new();
3235 room.on_latest_event_decrypted(
3236 event.clone(),
3237 0,
3238 &mut changes,
3239 &mut room_info_notable_updates,
3240 );
3241
3242 assert!(room_info_notable_updates.contains_key(room_id));
3243
3244 assert!(room_info_notable_update.try_recv().is_err());
3246
3247 client.apply_changes(&changes, room_info_notable_updates, None);
3249 assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
3250
3251 assert_matches!(
3253 room_info_notable_update.recv().await,
3254 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
3255 assert_eq!(received_room_id, room_id);
3256 assert!(reasons.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
3257 }
3258 );
3259 }
3260
3261 #[cfg(feature = "e2e-encryption")]
3262 #[async_test]
3263 async fn test_when_we_provide_a_newly_decrypted_event_it_replaces_latest_event() {
3264 use std::collections::BTreeMap;
3265
3266 let (_store, room) = make_room_test_helper(RoomState::Joined);
3268 add_encrypted_event(&room, "$A");
3269 assert!(room.latest_event().is_none());
3271
3272 let event = make_latest_event("$A");
3274 let mut changes = StateChanges::default();
3275 let mut room_info_notable_updates = BTreeMap::new();
3276 room.on_latest_event_decrypted(
3277 event.clone(),
3278 0,
3279 &mut changes,
3280 &mut room_info_notable_updates,
3281 );
3282 room.set_room_info(
3283 changes.room_infos.get(room.room_id()).cloned().unwrap(),
3284 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3285 );
3286
3287 assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
3289 }
3290
3291 #[cfg(feature = "e2e-encryption")]
3292 #[async_test]
3293 async fn test_when_a_newly_decrypted_event_appears_we_delete_all_older_encrypted_events() {
3294 use std::collections::BTreeMap;
3295
3296 let (_store, room) = make_room_test_helper(RoomState::Joined);
3298 room.inner.update(|info| info.latest_event = Some(make_latest_event("$A")));
3299 add_encrypted_event(&room, "$0");
3300 add_encrypted_event(&room, "$1");
3301 add_encrypted_event(&room, "$2");
3302 add_encrypted_event(&room, "$3");
3303
3304 let new_event = make_latest_event("$1");
3306 let new_event_index = 1;
3307 let mut changes = StateChanges::default();
3308 let mut room_info_notable_updates = BTreeMap::new();
3309 room.on_latest_event_decrypted(
3310 new_event.clone(),
3311 new_event_index,
3312 &mut changes,
3313 &mut room_info_notable_updates,
3314 );
3315 room.set_room_info(
3316 changes.room_infos.get(room.room_id()).cloned().unwrap(),
3317 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3318 );
3319
3320 let enc_evs = room.latest_encrypted_events();
3322 assert_eq!(enc_evs.len(), 2);
3323 assert_eq!(enc_evs[0].get_field::<&str>("event_id").unwrap().unwrap(), "$2");
3324 assert_eq!(enc_evs[1].get_field::<&str>("event_id").unwrap().unwrap(), "$3");
3325
3326 assert_eq!(room.latest_event().unwrap().event_id(), new_event.event_id());
3328 }
3329
3330 #[cfg(feature = "e2e-encryption")]
3331 #[async_test]
3332 async fn test_replacing_the_newest_event_leaves_none_left() {
3333 use std::collections::BTreeMap;
3334
3335 let (_store, room) = make_room_test_helper(RoomState::Joined);
3337 add_encrypted_event(&room, "$0");
3338 add_encrypted_event(&room, "$1");
3339 add_encrypted_event(&room, "$2");
3340 add_encrypted_event(&room, "$3");
3341
3342 let new_event = make_latest_event("$3");
3344 let new_event_index = 3;
3345 let mut changes = StateChanges::default();
3346 let mut room_info_notable_updates = BTreeMap::new();
3347 room.on_latest_event_decrypted(
3348 new_event,
3349 new_event_index,
3350 &mut changes,
3351 &mut room_info_notable_updates,
3352 );
3353 room.set_room_info(
3354 changes.room_infos.get(room.room_id()).cloned().unwrap(),
3355 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3356 );
3357
3358 let enc_evs = room.latest_encrypted_events();
3360 assert_eq!(enc_evs.len(), 0);
3361 }
3362
3363 #[cfg(feature = "e2e-encryption")]
3364 fn add_encrypted_event(room: &Room, event_id: &str) {
3365 room.latest_encrypted_events
3366 .write()
3367 .unwrap()
3368 .push(Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap());
3369 }
3370
3371 #[cfg(feature = "e2e-encryption")]
3372 fn make_latest_event(event_id: &str) -> Box<LatestEvent> {
3373 Box::new(LatestEvent::new(TimelineEvent::new(
3374 Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap(),
3375 )))
3376 }
3377
3378 fn timestamp(minutes_ago: u32) -> MilliSecondsSinceUnixEpoch {
3379 MilliSecondsSinceUnixEpoch::from_system_time(
3380 SystemTime::now().sub(Duration::from_secs((60 * minutes_ago).into())),
3381 )
3382 .expect("date out of range")
3383 }
3384
3385 fn legacy_membership_for_my_call(
3386 device_id: &DeviceId,
3387 membership_id: &str,
3388 minutes_ago: u32,
3389 ) -> LegacyMembershipData {
3390 let (application, foci) = foci_and_application();
3391 assign!(
3392 LegacyMembershipData::from(LegacyMembershipDataInit {
3393 application,
3394 device_id: device_id.to_owned(),
3395 expires: Duration::from_millis(3_600_000),
3396 foci_active: foci,
3397 membership_id: membership_id.to_owned(),
3398 }),
3399 { created_ts: Some(timestamp(minutes_ago)) }
3400 )
3401 }
3402
3403 fn legacy_member_state_event(
3404 memberships: Vec<LegacyMembershipData>,
3405 ev_id: &EventId,
3406 user_id: &UserId,
3407 ) -> AnySyncStateEvent {
3408 let content = CallMemberEventContent::new_legacy(memberships);
3409
3410 AnySyncStateEvent::CallMember(SyncStateEvent::Original(OriginalSyncCallMemberEvent {
3411 content,
3412 event_id: ev_id.to_owned(),
3413 sender: user_id.to_owned(),
3414 origin_server_ts: timestamp(0),
3417 state_key: CallMemberStateKey::new(user_id.to_owned(), None, false),
3418 unsigned: StateUnsigned::new(),
3419 }))
3420 }
3421
3422 struct InitData<'a> {
3423 device_id: &'a DeviceId,
3424 minutes_ago: u32,
3425 }
3426
3427 fn session_member_state_event(
3428 ev_id: &EventId,
3429 user_id: &UserId,
3430 init_data: Option<InitData<'_>>,
3431 ) -> AnySyncStateEvent {
3432 let application = Application::Call(CallApplicationContent::new(
3433 "my_call_id_1".to_owned(),
3434 ruma::events::call::member::CallScope::Room,
3435 ));
3436 let foci_preferred = vec![Focus::Livekit(LivekitFocus::new(
3437 "my_call_foci_alias".to_owned(),
3438 "https://lk.org".to_owned(),
3439 ))];
3440 let focus_active = ActiveFocus::Livekit(ActiveLivekitFocus::new());
3441 let (content, state_key) = match init_data {
3442 Some(InitData { device_id, minutes_ago }) => (
3443 CallMemberEventContent::new(
3444 application,
3445 device_id.to_owned(),
3446 focus_active,
3447 foci_preferred,
3448 Some(timestamp(minutes_ago)),
3449 ),
3450 CallMemberStateKey::new(user_id.to_owned(), Some(device_id.to_owned()), false),
3451 ),
3452 None => (
3453 CallMemberEventContent::new_empty(None),
3454 CallMemberStateKey::new(user_id.to_owned(), None, false),
3455 ),
3456 };
3457
3458 AnySyncStateEvent::CallMember(SyncStateEvent::Original(OriginalSyncCallMemberEvent {
3459 content,
3460 event_id: ev_id.to_owned(),
3461 sender: user_id.to_owned(),
3462 origin_server_ts: timestamp(0),
3465 state_key,
3466 unsigned: StateUnsigned::new(),
3467 }))
3468 }
3469
3470 fn foci_and_application() -> (Application, Vec<Focus>) {
3471 (
3472 Application::Call(CallApplicationContent::new(
3473 "my_call_id_1".to_owned(),
3474 ruma::events::call::member::CallScope::Room,
3475 )),
3476 vec![Focus::Livekit(LivekitFocus::new(
3477 "my_call_foci_alias".to_owned(),
3478 "https://lk.org".to_owned(),
3479 ))],
3480 )
3481 }
3482
3483 fn receive_state_events(room: &Room, events: Vec<&AnySyncStateEvent>) {
3484 room.inner.update_if(|info| {
3485 let mut res = false;
3486 for ev in events {
3487 res |= info.handle_state_event(ev);
3488 }
3489 res
3490 });
3491 }
3492
3493 fn legacy_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
3497 let (_, room) = make_room_test_helper(RoomState::Joined);
3498
3499 let a_empty = legacy_member_state_event(Vec::new(), event_id!("$1234"), a);
3500
3501 let m_init_b = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 1);
3503 let b_one = legacy_member_state_event(vec![m_init_b], event_id!("$12345"), b);
3504
3505 let m_init_c1 = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 10);
3507 let m_init_c2 = legacy_membership_for_my_call(device_id!("DEVICE_1"), "0", 20);
3509 let c_two = legacy_member_state_event(vec![m_init_c1, m_init_c2], event_id!("$123456"), c);
3510
3511 receive_state_events(&room, vec![&c_two, &a_empty, &b_one]);
3513
3514 room
3515 }
3516
3517 fn session_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
3521 let (_, room) = make_room_test_helper(RoomState::Joined);
3522
3523 let a_empty = session_member_state_event(event_id!("$1234"), a, None);
3524
3525 let b_one = session_member_state_event(
3527 event_id!("$12345"),
3528 b,
3529 Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 1 }),
3530 );
3531
3532 let m_c1 = session_member_state_event(
3533 event_id!("$123456_0"),
3534 c,
3535 Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 10 }),
3536 );
3537 let m_c2 = session_member_state_event(
3538 event_id!("$123456_1"),
3539 c,
3540 Some(InitData { device_id: "DEVICE_1".into(), minutes_ago: 20 }),
3541 );
3542 receive_state_events(&room, vec![&m_c1, &m_c2, &a_empty, &b_one]);
3544
3545 room
3546 }
3547
3548 #[test]
3549 fn test_show_correct_active_call_state() {
3550 let room_legacy = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3551
3552 assert_eq!(
3556 vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
3557 room_legacy.active_room_call_participants()
3558 );
3559 assert!(room_legacy.has_active_room_call());
3560
3561 let room_session = session_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3562 assert_eq!(
3563 vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
3564 room_session.active_room_call_participants()
3565 );
3566 assert!(room_session.has_active_room_call());
3567 }
3568
3569 #[test]
3570 fn test_active_call_is_false_when_everyone_left() {
3571 let room = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3572
3573 let b_empty_membership = legacy_member_state_event(Vec::new(), event_id!("$1234_1"), &BOB);
3574 let c_empty_membership =
3575 legacy_member_state_event(Vec::new(), event_id!("$12345_1"), &CAROL);
3576
3577 receive_state_events(&room, vec![&b_empty_membership, &c_empty_membership]);
3578
3579 assert_eq!(Vec::<OwnedUserId>::new(), room.active_room_call_participants());
3581 assert!(!room.has_active_room_call());
3582 }
3583
3584 #[test]
3585 fn test_calculate_room_name() {
3586 let mut actual = compute_display_name_from_heroes(2, vec!["a"]);
3587 assert_eq!(RoomDisplayName::Calculated("a".to_owned()), actual);
3588
3589 actual = compute_display_name_from_heroes(3, vec!["a", "b"]);
3590 assert_eq!(RoomDisplayName::Calculated("a, b".to_owned()), actual);
3591
3592 actual = compute_display_name_from_heroes(4, vec!["a", "b", "c"]);
3593 assert_eq!(RoomDisplayName::Calculated("a, b, c".to_owned()), actual);
3594
3595 actual = compute_display_name_from_heroes(5, vec!["a", "b", "c"]);
3596 assert_eq!(RoomDisplayName::Calculated("a, b, c, and 2 others".to_owned()), actual);
3597
3598 actual = compute_display_name_from_heroes(5, vec![]);
3599 assert_eq!(RoomDisplayName::Calculated("5 people".to_owned()), actual);
3600
3601 actual = compute_display_name_from_heroes(0, vec![]);
3602 assert_eq!(RoomDisplayName::Empty, actual);
3603
3604 actual = compute_display_name_from_heroes(1, vec![]);
3605 assert_eq!(RoomDisplayName::Empty, actual);
3606
3607 actual = compute_display_name_from_heroes(1, vec!["a"]);
3608 assert_eq!(RoomDisplayName::EmptyWas("a".to_owned()), actual);
3609
3610 actual = compute_display_name_from_heroes(1, vec!["a", "b"]);
3611 assert_eq!(RoomDisplayName::EmptyWas("a, b".to_owned()), actual);
3612
3613 actual = compute_display_name_from_heroes(1, vec!["a", "b", "c"]);
3614 assert_eq!(RoomDisplayName::EmptyWas("a, b, c".to_owned()), actual);
3615 }
3616
3617 #[test]
3618 fn test_encryption_is_set_when_encryption_event_is_received_encrypted() {
3619 let (_store, room) = make_room_test_helper(RoomState::Joined);
3620
3621 assert_matches!(room.encryption_state(), EncryptionState::Unknown);
3622
3623 let encryption_content =
3624 RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
3625 let encryption_event = AnySyncStateEvent::RoomEncryption(SyncStateEvent::Original(
3626 OriginalSyncRoomEncryptionEvent {
3627 content: encryption_content,
3628 event_id: OwnedEventId::from_str("$1234_1").unwrap(),
3629 sender: ALICE.to_owned(),
3630 origin_server_ts: timestamp(0),
3633 state_key: EmptyStateKey,
3634 unsigned: StateUnsigned::new(),
3635 },
3636 ));
3637 receive_state_events(&room, vec![&encryption_event]);
3638
3639 assert_matches!(room.encryption_state(), EncryptionState::Encrypted);
3640 }
3641
3642 #[test]
3643 fn test_encryption_is_set_when_encryption_event_is_received_not_encrypted() {
3644 let (_store, room) = make_room_test_helper(RoomState::Joined);
3645
3646 assert_matches!(room.encryption_state(), EncryptionState::Unknown);
3647 room.inner.update_if(|info| {
3648 info.mark_encryption_state_synced();
3649
3650 false
3651 });
3652
3653 assert_matches!(room.encryption_state(), EncryptionState::NotEncrypted);
3654 }
3655
3656 #[test]
3657 fn test_encryption_state() {
3658 assert!(EncryptionState::Unknown.is_unknown());
3659 assert!(EncryptionState::Encrypted.is_unknown().not());
3660 assert!(EncryptionState::NotEncrypted.is_unknown().not());
3661
3662 assert!(EncryptionState::Unknown.is_encrypted().not());
3663 assert!(EncryptionState::Encrypted.is_encrypted());
3664 assert!(EncryptionState::NotEncrypted.is_encrypted().not());
3665 }
3666
3667 #[async_test]
3668 async fn test_room_info_migration_v1() {
3669 let store = MemoryStore::new().into_state_store();
3670
3671 let room_info_json = json!({
3672 "room_id": "!gda78o:server.tld",
3673 "room_state": "Joined",
3674 "notification_counts": {
3675 "highlight_count": 1,
3676 "notification_count": 2,
3677 },
3678 "summary": {
3679 "room_heroes": [{
3680 "user_id": "@somebody:example.org",
3681 "display_name": null,
3682 "avatar_url": null
3683 }],
3684 "joined_member_count": 5,
3685 "invited_member_count": 0,
3686 },
3687 "members_synced": true,
3688 "last_prev_batch": "pb",
3689 "sync_info": "FullySynced",
3690 "encryption_state_synced": true,
3691 "latest_event": {
3692 "event": {
3693 "encryption_info": null,
3694 "event": {
3695 "sender": "@u:i.uk",
3696 },
3697 },
3698 },
3699 "base_info": {
3700 "avatar": null,
3701 "canonical_alias": null,
3702 "create": null,
3703 "dm_targets": [],
3704 "encryption": null,
3705 "guest_access": null,
3706 "history_visibility": null,
3707 "join_rules": null,
3708 "max_power_level": 100,
3709 "name": null,
3710 "tombstone": null,
3711 "topic": null,
3712 },
3713 "read_receipts": {
3714 "num_unread": 0,
3715 "num_mentions": 0,
3716 "num_notifications": 0,
3717 "latest_active": null,
3718 "pending": []
3719 },
3720 "recency_stamp": 42,
3721 });
3722 let mut room_info: RoomInfo = serde_json::from_value(room_info_json).unwrap();
3723
3724 assert_eq!(room_info.version, 0);
3725 assert!(room_info.base_info.notable_tags.is_empty());
3726 assert!(room_info.base_info.pinned_events.is_none());
3727
3728 assert!(room_info.apply_migrations(store.clone()).await);
3730
3731 assert_eq!(room_info.version, 1);
3732 assert!(room_info.base_info.notable_tags.is_empty());
3733 assert!(room_info.base_info.pinned_events.is_none());
3734
3735 assert!(!room_info.apply_migrations(store.clone()).await);
3737
3738 assert_eq!(room_info.version, 1);
3739 assert!(room_info.base_info.notable_tags.is_empty());
3740 assert!(room_info.base_info.pinned_events.is_none());
3741
3742 let mut changes = StateChanges::default();
3744
3745 let raw_tag_event = Raw::new(&*TAG).unwrap().cast();
3746 let tag_event = raw_tag_event.deserialize().unwrap();
3747 changes.add_room_account_data(&room_info.room_id, tag_event, raw_tag_event);
3748
3749 let raw_pinned_events_event = Raw::new(&*PINNED_EVENTS).unwrap().cast();
3750 let pinned_events_event = raw_pinned_events_event.deserialize().unwrap();
3751 changes.add_state_event(&room_info.room_id, pinned_events_event, raw_pinned_events_event);
3752
3753 store.save_changes(&changes).await.unwrap();
3754
3755 room_info.version = 0;
3757 assert!(room_info.apply_migrations(store.clone()).await);
3758
3759 assert_eq!(room_info.version, 1);
3760 assert!(room_info.base_info.notable_tags.contains(RoomNotableTags::FAVOURITE));
3761 assert!(room_info.base_info.pinned_events.is_some());
3762
3763 let new_room_info = RoomInfo::new(room_id!("!new_room:localhost"), RoomState::Joined);
3765 assert_eq!(new_room_info.version, 1);
3766 }
3767
3768 #[async_test]
3769 async fn test_prev_room_state_is_updated() {
3770 let (_store, room) = make_room_test_helper(RoomState::Invited);
3771 assert_eq!(room.prev_state(), None);
3772 assert_eq!(room.state(), RoomState::Invited);
3773
3774 let mut room_info = room.clone_info();
3776 room_info.mark_as_joined();
3777 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3778 assert_eq!(room.prev_state(), Some(RoomState::Invited));
3779 assert_eq!(room.state(), RoomState::Joined);
3780
3781 let mut room_info = room.clone_info();
3783 room_info.mark_as_joined();
3784 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3785 assert_eq!(room.prev_state(), Some(RoomState::Invited));
3786 assert_eq!(room.state(), RoomState::Joined);
3787
3788 let mut room_info = room.clone_info();
3790 room_info.mark_as_left();
3791 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3792 assert_eq!(room.prev_state(), Some(RoomState::Joined));
3793 assert_eq!(room.state(), RoomState::Left);
3794
3795 let mut room_info = room.clone_info();
3797 room_info.mark_as_banned();
3798 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3799 assert_eq!(room.prev_state(), Some(RoomState::Left));
3800 assert_eq!(room.state(), RoomState::Banned);
3801 }
3802
3803 #[async_test]
3804 async fn test_room_state_filters() {
3805 let client = logged_in_base_client(None).await;
3806
3807 let joined_room_id = owned_room_id!("!joined:example.org");
3808 client.get_or_create_room(&joined_room_id, RoomState::Joined);
3809
3810 let invited_room_id = owned_room_id!("!invited:example.org");
3811 client.get_or_create_room(&invited_room_id, RoomState::Invited);
3812
3813 let left_room_id = owned_room_id!("!left:example.org");
3814 client.get_or_create_room(&left_room_id, RoomState::Left);
3815
3816 let knocked_room_id = owned_room_id!("!knocked:example.org");
3817 client.get_or_create_room(&knocked_room_id, RoomState::Knocked);
3818
3819 let banned_room_id = owned_room_id!("!banned:example.org");
3820 client.get_or_create_room(&banned_room_id, RoomState::Banned);
3821
3822 let joined_rooms = client.rooms_filtered(RoomStateFilter::JOINED);
3823 assert_eq!(joined_rooms.len(), 1);
3824 assert_eq!(joined_rooms[0].state(), RoomState::Joined);
3825 assert_eq!(joined_rooms[0].room_id, joined_room_id);
3826
3827 let invited_rooms = client.rooms_filtered(RoomStateFilter::INVITED);
3828 assert_eq!(invited_rooms.len(), 1);
3829 assert_eq!(invited_rooms[0].state(), RoomState::Invited);
3830 assert_eq!(invited_rooms[0].room_id, invited_room_id);
3831
3832 let left_rooms = client.rooms_filtered(RoomStateFilter::LEFT);
3833 assert_eq!(left_rooms.len(), 1);
3834 assert_eq!(left_rooms[0].state(), RoomState::Left);
3835 assert_eq!(left_rooms[0].room_id, left_room_id);
3836
3837 let knocked_rooms = client.rooms_filtered(RoomStateFilter::KNOCKED);
3838 assert_eq!(knocked_rooms.len(), 1);
3839 assert_eq!(knocked_rooms[0].state(), RoomState::Knocked);
3840 assert_eq!(knocked_rooms[0].room_id, knocked_room_id);
3841
3842 let banned_rooms = client.rooms_filtered(RoomStateFilter::BANNED);
3843 assert_eq!(banned_rooms.len(), 1);
3844 assert_eq!(banned_rooms[0].state(), RoomState::Banned);
3845 assert_eq!(banned_rooms[0].room_id, banned_room_id);
3846 }
3847
3848 #[test]
3849 fn test_room_state_filters_as_vec() {
3850 assert_eq!(RoomStateFilter::JOINED.as_vec(), vec![RoomState::Joined]);
3851 assert_eq!(RoomStateFilter::LEFT.as_vec(), vec![RoomState::Left]);
3852 assert_eq!(RoomStateFilter::INVITED.as_vec(), vec![RoomState::Invited]);
3853 assert_eq!(RoomStateFilter::KNOCKED.as_vec(), vec![RoomState::Knocked]);
3854 assert_eq!(RoomStateFilter::BANNED.as_vec(), vec![RoomState::Banned]);
3855
3856 assert_eq!(
3858 RoomStateFilter::all().as_vec(),
3859 vec![
3860 RoomState::Joined,
3861 RoomState::Left,
3862 RoomState::Invited,
3863 RoomState::Knocked,
3864 RoomState::Banned
3865 ]
3866 );
3867 }
3868}