matrix_sdk_base/rooms/
normal.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#[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/// Indicates that a notable update of `RoomInfo` has been applied, and why.
81///
82/// A room info notable update is an update that can be interested for other
83/// parts of the code. This mechanism is used in coordination with
84/// [`BaseClient::room_info_notable_update_receiver`][baseclient] (and
85/// `Room::inner` plus `Room::room_info_notable_update_sender`) where `RoomInfo`
86/// can be observed and some of its updates can be spread to listeners.
87///
88/// [baseclient]: crate::BaseClient::room_info_notable_update_receiver
89#[derive(Debug, Clone)]
90pub struct RoomInfoNotableUpdate {
91    /// The room which was updated.
92    pub room_id: OwnedRoomId,
93
94    /// The reason for this update.
95    pub reasons: RoomInfoNotableUpdateReasons,
96}
97
98bitflags! {
99    /// The reason why a [`RoomInfoNotableUpdate`] is emitted.
100    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
101    pub struct RoomInfoNotableUpdateReasons: u8 {
102        /// The recency stamp of the `Room` has changed.
103        const RECENCY_STAMP = 0b0000_0001;
104
105        /// The latest event of the `Room` has changed.
106        const LATEST_EVENT = 0b0000_0010;
107
108        /// A read receipt has changed.
109        const READ_RECEIPT = 0b0000_0100;
110
111        /// The user-controlled unread marker value has changed.
112        const UNREAD_MARKER = 0b0000_1000;
113
114        /// A membership change happened for the current user.
115        const MEMBERSHIP = 0b0001_0000;
116    }
117}
118
119/// The result of a room summary computation.
120///
121/// If the homeserver does not provide a room summary, we perform a best-effort
122/// computation to generate one ourselves. If the homeserver does provide the
123/// summary, we augment it with additional information about the service members
124/// in the room.
125struct ComputedSummary {
126    /// The list of display names that will be used to calculate the room
127    /// display name.
128    heroes: Vec<String>,
129    /// The number of joined service members in the room.
130    num_service_members: u64,
131    /// The number of joined and invited members, not including any service
132    /// members.
133    num_joined_invited_guess: u64,
134}
135
136impl Default for RoomInfoNotableUpdateReasons {
137    fn default() -> Self {
138        Self::empty()
139    }
140}
141
142/// The underlying room data structure collecting state for joined, left and
143/// invited rooms.
144#[derive(Debug, Clone)]
145pub struct Room {
146    /// The room ID.
147    room_id: OwnedRoomId,
148
149    /// Our own user ID.
150    own_user_id: OwnedUserId,
151
152    inner: SharedObservable<RoomInfo>,
153    room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
154    store: Arc<DynStateStore>,
155
156    /// The most recent few encrypted events. When the keys come through to
157    /// decrypt these, the most recent relevant one will replace
158    /// `latest_event`. (We can't tell which one is relevant until
159    /// they are decrypted.)
160    ///
161    /// Currently, these are held in Room rather than RoomInfo, because we were
162    /// not sure whether holding too many of them might make the cache too
163    /// slow to load on startup. Keeping them here means they are not cached
164    /// to disk but held in memory.
165    #[cfg(feature = "e2e-encryption")]
166    pub latest_encrypted_events: Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>,
167
168    /// A map for ids of room membership events in the knocking state linked to
169    /// the user id of the user affected by the member event, that the current
170    /// user has marked as seen so they can be ignored.
171    pub seen_knock_request_ids_map:
172        SharedObservable<Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
173
174    /// A sender that will notify receivers when room member updates happen.
175    pub room_member_updates_sender: broadcast::Sender<RoomMembersUpdate>,
176}
177
178/// The room summary containing member counts and members that should be used to
179/// calculate the room display name.
180#[derive(Clone, Debug, Default, Serialize, Deserialize)]
181pub struct RoomSummary {
182    /// The heroes of the room, members that can be used as a fallback for the
183    /// room's display name or avatar if these haven't been set.
184    ///
185    /// This was called `heroes` and contained raw `String`s of the `UserId`
186    /// before. Following this it was called `heroes_user_ids` and a
187    /// complimentary `heroes_names` existed too; changing the field's name
188    /// helped with avoiding a migration.
189    #[serde(default, skip_serializing_if = "Vec::is_empty")]
190    pub(crate) room_heroes: Vec<RoomHero>,
191    /// The number of members that are considered to be joined to the room.
192    pub(crate) joined_member_count: u64,
193    /// The number of members that are considered to be invited to the room.
194    pub(crate) invited_member_count: u64,
195}
196
197/// Information about a member considered to be a room hero.
198#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
199pub struct RoomHero {
200    /// The user id of the hero.
201    pub user_id: OwnedUserId,
202    /// The display name of the hero.
203    pub display_name: Option<String>,
204    /// The avatar url of the hero.
205    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/// Enum keeping track in which state the room is, e.g. if our own user is
216/// joined, RoomState::Invited, or has left the room.
217#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
218pub enum RoomState {
219    /// The room is in a joined state.
220    Joined,
221    /// The room is in a left state.
222    Left,
223    /// The room is in an invited state.
224    Invited,
225    /// The room is in a knocked state.
226    Knocked,
227    /// The room is in a banned state.
228    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
244/// The number of heroes chosen to compute a room's name, if the room didn't
245/// have a name set by the users themselves.
246///
247/// A server must return at most 5 heroes, according to the paragraph below
248/// https://spec.matrix.org/v1.10/client-server-api/#get_matrixclientv3sync (grep for "heroes"). We
249/// try to behave similarly here.
250const NUM_HEROES: usize = 5;
251
252/// A filter to remove our own user and the users specified in the member hints
253/// state event, so called service members, from the list of heroes.
254///
255/// The heroes will then be used to calculate a display name for the room if one
256/// wasn't explicitly defined.
257fn 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/// The kind of room member updates that just happened.
265#[derive(Debug, Clone)]
266pub enum RoomMembersUpdate {
267    /// The whole list room members was reloaded.
268    FullReload,
269    /// A few members were updated, their user ids are included.
270    Partial(BTreeSet<OwnedUserId>),
271}
272
273impl Room {
274    /// The size of the latest_encrypted_events RingBuffer
275    // SAFETY: `new_unchecked` is safe because 10 is not zero.
276    #[cfg(feature = "e2e-encryption")]
277    const MAX_ENCRYPTED_EVENTS: std::num::NonZeroUsize =
278        unsafe { std::num::NonZeroUsize::new_unchecked(10) };
279
280    pub(crate) fn new(
281        own_user_id: &UserId,
282        store: Arc<DynStateStore>,
283        room_id: &RoomId,
284        room_state: RoomState,
285        room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
286    ) -> Self {
287        let room_info = RoomInfo::new(room_id, room_state);
288        Self::restore(own_user_id, store, room_info, room_info_notable_update_sender)
289    }
290
291    pub(crate) fn restore(
292        own_user_id: &UserId,
293        store: Arc<DynStateStore>,
294        room_info: RoomInfo,
295        room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
296    ) -> Self {
297        let (room_member_updates_sender, _) = broadcast::channel(10);
298        Self {
299            own_user_id: own_user_id.into(),
300            room_id: room_info.room_id.clone(),
301            store,
302            inner: SharedObservable::new(room_info),
303            #[cfg(feature = "e2e-encryption")]
304            latest_encrypted_events: Arc::new(SyncRwLock::new(RingBuffer::new(
305                Self::MAX_ENCRYPTED_EVENTS,
306            ))),
307            room_info_notable_update_sender,
308            seen_knock_request_ids_map: SharedObservable::new_async(None),
309            room_member_updates_sender,
310        }
311    }
312
313    /// Get the unique room id of the room.
314    pub fn room_id(&self) -> &RoomId {
315        &self.room_id
316    }
317
318    /// Get a copy of the room creator.
319    pub fn creator(&self) -> Option<OwnedUserId> {
320        self.inner.read().creator().map(ToOwned::to_owned)
321    }
322
323    /// Get our own user id.
324    pub fn own_user_id(&self) -> &UserId {
325        &self.own_user_id
326    }
327
328    /// Get the state of the room.
329    pub fn state(&self) -> RoomState {
330        self.inner.read().room_state
331    }
332
333    /// Get the previous state of the room, if it had any.
334    pub fn prev_state(&self) -> Option<RoomState> {
335        self.inner.read().prev_room_state
336    }
337
338    /// Whether this room's [`RoomType`] is `m.space`.
339    pub fn is_space(&self) -> bool {
340        self.inner.read().room_type().is_some_and(|t| *t == RoomType::Space)
341    }
342
343    /// Returns the room's type as defined in its creation event
344    /// (`m.room.create`).
345    pub fn room_type(&self) -> Option<RoomType> {
346        self.inner.read().room_type().map(ToOwned::to_owned)
347    }
348
349    /// Get the unread notification counts.
350    pub fn unread_notification_counts(&self) -> UnreadNotificationsCount {
351        self.inner.read().notification_counts
352    }
353
354    /// Get the number of unread messages (computed client-side).
355    ///
356    /// This might be more precise than [`Self::unread_notification_counts`] for
357    /// encrypted rooms.
358    pub fn num_unread_messages(&self) -> u64 {
359        self.inner.read().read_receipts.num_unread
360    }
361
362    /// Get the detailed information about read receipts for the room.
363    pub fn read_receipts(&self) -> RoomReadReceipts {
364        self.inner.read().read_receipts.clone()
365    }
366
367    /// Get the number of unread notifications (computed client-side).
368    ///
369    /// This might be more precise than [`Self::unread_notification_counts`] for
370    /// encrypted rooms.
371    pub fn num_unread_notifications(&self) -> u64 {
372        self.inner.read().read_receipts.num_notifications
373    }
374
375    /// Get the number of unread mentions (computed client-side), that is,
376    /// messages causing a highlight in a room.
377    ///
378    /// This might be more precise than [`Self::unread_notification_counts`] for
379    /// encrypted rooms.
380    pub fn num_unread_mentions(&self) -> u64 {
381        self.inner.read().read_receipts.num_mentions
382    }
383
384    /// Check if the room has its members fully synced.
385    ///
386    /// Members might be missing if lazy member loading was enabled for the
387    /// sync.
388    ///
389    /// Returns true if no members are missing, false otherwise.
390    pub fn are_members_synced(&self) -> bool {
391        self.inner.read().members_synced
392    }
393
394    /// Mark this Room as holding all member information.
395    ///
396    /// Useful in tests if we want to persuade the Room not to sync when asked
397    /// about its members.
398    #[cfg(feature = "testing")]
399    pub fn mark_members_synced(&self) {
400        self.inner.update(|info| {
401            info.members_synced = true;
402        });
403    }
404
405    /// Mark this Room as still missing member information.
406    pub fn mark_members_missing(&self) {
407        self.inner.update_if(|info| {
408            // notify observable subscribers only if the previous value was false
409            mem::replace(&mut info.members_synced, false)
410        })
411    }
412
413    /// Check if the room states have been synced
414    ///
415    /// States might be missing if we have only seen the room_id of this Room
416    /// so far, for example as the response for a `create_room` request without
417    /// being synced yet.
418    ///
419    /// Returns true if the state is fully synced, false otherwise.
420    pub fn is_state_fully_synced(&self) -> bool {
421        self.inner.read().sync_info == SyncInfo::FullySynced
422    }
423
424    /// Check if the room state has been at least partially synced.
425    ///
426    /// See [`Room::is_state_fully_synced`] for more info.
427    pub fn is_state_partially_or_fully_synced(&self) -> bool {
428        self.inner.read().sync_info != SyncInfo::NoState
429    }
430
431    /// Check if the room has its encryption event synced.
432    ///
433    /// The encryption event can be missing when the room hasn't appeared in
434    /// sync yet.
435    ///
436    /// Returns true if the encryption state is synced, false otherwise.
437    pub fn is_encryption_state_synced(&self) -> bool {
438        self.inner.read().encryption_state_synced
439    }
440
441    /// Get the `prev_batch` token that was received from the last sync. May be
442    /// `None` if the last sync contained the full room history.
443    pub fn last_prev_batch(&self) -> Option<String> {
444        self.inner.read().last_prev_batch.clone()
445    }
446
447    /// Get the avatar url of this room.
448    pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
449        self.inner.read().avatar_url().map(ToOwned::to_owned)
450    }
451
452    /// Get information about the avatar of this room.
453    pub fn avatar_info(&self) -> Option<avatar::ImageInfo> {
454        self.inner.read().avatar_info().map(ToOwned::to_owned)
455    }
456
457    /// Get the canonical alias of this room.
458    pub fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
459        self.inner.read().canonical_alias().map(ToOwned::to_owned)
460    }
461
462    /// Get the canonical alias of this room.
463    pub fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
464        self.inner.read().alt_aliases().to_owned()
465    }
466
467    /// Get the `m.room.create` content of this room.
468    ///
469    /// This usually isn't optional but some servers might not send an
470    /// `m.room.create` event as the first event for a given room, thus this can
471    /// be optional.
472    ///
473    /// For room versions earlier than room version 11, if the event is
474    /// redacted, all fields except `creator` will be set to their default
475    /// value.
476    pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
477        match self.inner.read().base_info.create.as_ref()? {
478            MinimalStateEvent::Original(ev) => Some(ev.content.clone()),
479            MinimalStateEvent::Redacted(ev) => Some(ev.content.clone()),
480        }
481    }
482
483    /// Is this room considered a direct message.
484    ///
485    /// Async because it can read room info from storage.
486    #[instrument(skip_all, fields(room_id = ?self.room_id))]
487    pub async fn is_direct(&self) -> StoreResult<bool> {
488        match self.state() {
489            RoomState::Joined | RoomState::Left | RoomState::Banned => {
490                Ok(!self.inner.read().base_info.dm_targets.is_empty())
491            }
492
493            RoomState::Invited => {
494                let member = self.get_member(self.own_user_id()).await?;
495
496                match member {
497                    None => {
498                        info!("RoomMember not found for the user's own id");
499                        Ok(false)
500                    }
501                    Some(member) => match member.event.as_ref() {
502                        MemberEvent::Sync(_) => {
503                            warn!("Got MemberEvent::Sync in an invited room");
504                            Ok(false)
505                        }
506                        MemberEvent::Stripped(event) => {
507                            Ok(event.content.is_direct.unwrap_or(false))
508                        }
509                    },
510                }
511            }
512
513            // TODO: implement logic once we have the stripped events as we'd have with an Invite
514            RoomState::Knocked => Ok(false),
515        }
516    }
517
518    /// If this room is a direct message, get the members that we're sharing the
519    /// room with.
520    ///
521    /// *Note*: The member list might have been modified in the meantime and
522    /// the targets might not even be in the room anymore. This setting should
523    /// only be considered as guidance. We leave members in this list to allow
524    /// us to re-find a DM with a user even if they have left, since we may
525    /// want to re-invite them.
526    pub fn direct_targets(&self) -> HashSet<OwnedDirectUserIdentifier> {
527        self.inner.read().base_info.dm_targets.clone()
528    }
529
530    /// If this room is a direct message, returns the number of members that
531    /// we're sharing the room with.
532    pub fn direct_targets_length(&self) -> usize {
533        self.inner.read().base_info.dm_targets.len()
534    }
535
536    /// Is the room encrypted.
537    pub fn is_encrypted(&self) -> bool {
538        self.inner.read().is_encrypted()
539    }
540
541    /// Get the `m.room.encryption` content that enabled end to end encryption
542    /// in the room.
543    pub fn encryption_settings(&self) -> Option<RoomEncryptionEventContent> {
544        self.inner.read().base_info.encryption.clone()
545    }
546
547    /// Get the guest access policy of this room.
548    pub fn guest_access(&self) -> GuestAccess {
549        self.inner.read().guest_access().clone()
550    }
551
552    /// Get the history visibility policy of this room.
553    pub fn history_visibility(&self) -> Option<HistoryVisibility> {
554        self.inner.read().history_visibility().cloned()
555    }
556
557    /// Get the history visibility policy of this room, or a sensible default if
558    /// the event is missing.
559    pub fn history_visibility_or_default(&self) -> HistoryVisibility {
560        self.inner.read().history_visibility_or_default().clone()
561    }
562
563    /// Is the room considered to be public.
564    pub fn is_public(&self) -> bool {
565        matches!(self.join_rule(), JoinRule::Public)
566    }
567
568    /// Get the join rule policy of this room.
569    pub fn join_rule(&self) -> JoinRule {
570        self.inner.read().join_rule().clone()
571    }
572
573    /// Get the maximum power level that this room contains.
574    ///
575    /// This is useful if one wishes to normalize the power levels, e.g. from
576    /// 0-100 where 100 would be the max power level.
577    pub fn max_power_level(&self) -> i64 {
578        self.inner.read().base_info.max_power_level
579    }
580
581    /// Get the current power levels of this room.
582    pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
583        Ok(self
584            .store
585            .get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
586            .await?
587            .ok_or(Error::InsufficientData)?
588            .deserialize()?
589            .power_levels())
590    }
591
592    /// Get the `m.room.name` of this room.
593    ///
594    /// The returned string may be empty if the event has been redacted, or it's
595    /// missing from storage.
596    pub fn name(&self) -> Option<String> {
597        self.inner.read().name().map(ToOwned::to_owned)
598    }
599
600    /// Has the room been tombstoned.
601    pub fn is_tombstoned(&self) -> bool {
602        self.inner.read().base_info.tombstone.is_some()
603    }
604
605    /// Get the `m.room.tombstone` content of this room if there is one.
606    pub fn tombstone(&self) -> Option<RoomTombstoneEventContent> {
607        self.inner.read().tombstone().cloned()
608    }
609
610    /// Get the topic of the room.
611    pub fn topic(&self) -> Option<String> {
612        self.inner.read().topic().map(ToOwned::to_owned)
613    }
614
615    /// Is there a non expired membership with application "m.call" and scope
616    /// "m.room" in this room
617    pub fn has_active_room_call(&self) -> bool {
618        self.inner.read().has_active_room_call()
619    }
620
621    /// Returns a Vec of userId's that participate in the room call.
622    ///
623    /// MatrixRTC memberships with application "m.call" and scope "m.room" are
624    /// considered. A user can occur twice if they join with two devices.
625    /// convert to a set depending if the different users are required or the
626    /// amount of sessions.
627    ///
628    /// The vector is ordered by oldest membership user to newest.
629    pub fn active_room_call_participants(&self) -> Vec<OwnedUserId> {
630        self.inner.read().active_room_call_participants()
631    }
632
633    /// Calculate a room's display name, or return the cached value, taking into
634    /// account its name, aliases and members.
635    ///
636    /// The display name is calculated according to [this algorithm][spec].
637    ///
638    /// While the underlying computation can be slow, the result is cached and
639    /// returned on the following calls. The cache is also filled on every
640    /// successful sync, since a sync may cause a change in the display
641    /// name.
642    ///
643    /// If you need a variant that's sync (but with the drawback that it returns
644    /// an `Option`), consider using [`Room::cached_display_name`].
645    ///
646    /// [spec]: <https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room>
647    pub async fn display_name(&self) -> StoreResult<RoomDisplayName> {
648        if let Some(name) = self.cached_display_name() {
649            Ok(name)
650        } else {
651            self.compute_display_name().await
652        }
653    }
654
655    /// Force recalculating a room's display name, taking into account its name,
656    /// aliases and members.
657    ///
658    /// The display name is calculated according to [this algorithm][spec].
659    ///
660    /// ⚠ This may be slowish to compute. As such, the result is cached and can
661    /// be retrieved via [`Room::cached_display_name`] (sync, returns an option)
662    /// or [`Room::display_name`] (async, always returns a value), which should
663    /// be preferred in general.
664    ///
665    /// [spec]: <https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room>
666    pub(crate) async fn compute_display_name(&self) -> StoreResult<RoomDisplayName> {
667        enum DisplayNameOrSummary {
668            Summary(RoomSummary),
669            DisplayName(RoomDisplayName),
670        }
671
672        let display_name_or_summary = {
673            let inner = self.inner.read();
674
675            match (inner.name(), inner.canonical_alias()) {
676                (Some(name), _) => {
677                    let name = RoomDisplayName::Named(name.trim().to_owned());
678                    DisplayNameOrSummary::DisplayName(name)
679                }
680                (None, Some(alias)) => {
681                    let name = RoomDisplayName::Aliased(alias.alias().trim().to_owned());
682                    DisplayNameOrSummary::DisplayName(name)
683                }
684                // We can't directly compute the display name from the summary here because Rust
685                // thinks that the `inner` lock is still held even if we explicitly call `drop()`
686                // on it. So we introduced the DisplayNameOrSummary type and do the computation in
687                // two steps.
688                (None, None) => DisplayNameOrSummary::Summary(inner.summary.clone()),
689            }
690        };
691
692        let display_name = match display_name_or_summary {
693            DisplayNameOrSummary::Summary(summary) => {
694                self.compute_display_name_from_summary(summary).await?
695            }
696            DisplayNameOrSummary::DisplayName(display_name) => display_name,
697        };
698
699        // Update the cached display name before we return the newly computed value.
700        self.inner.update_if(|info| {
701            if info.cached_display_name.as_ref() != Some(&display_name) {
702                info.cached_display_name = Some(display_name.clone());
703                true
704            } else {
705                false
706            }
707        });
708
709        Ok(display_name)
710    }
711
712    /// Compute a [`RoomDisplayName`] from the given [`RoomSummary`].
713    async fn compute_display_name_from_summary(
714        &self,
715        summary: RoomSummary,
716    ) -> StoreResult<RoomDisplayName> {
717        let computed_summary = if !summary.room_heroes.is_empty() {
718            self.extract_and_augment_summary(&summary).await?
719        } else {
720            self.compute_summary().await?
721        };
722
723        let ComputedSummary { heroes, num_service_members, num_joined_invited_guess } =
724            computed_summary;
725
726        let summary_member_count = (summary.joined_member_count + summary.invited_member_count)
727            .saturating_sub(num_service_members);
728
729        let num_joined_invited = if self.state() == RoomState::Invited {
730            // when we were invited we don't have a proper summary, we have to do best
731            // guessing
732            heroes.len() as u64 + 1
733        } else if summary_member_count == 0 {
734            num_joined_invited_guess
735        } else {
736            summary_member_count
737        };
738
739        debug!(
740            room_id = ?self.room_id(),
741            own_user = ?self.own_user_id,
742            num_joined_invited,
743            heroes = ?heroes,
744            "Calculating name for a room based on heroes",
745        );
746
747        let display_name = compute_display_name_from_heroes(
748            num_joined_invited,
749            heroes.iter().map(|hero| hero.as_str()).collect(),
750        );
751
752        Ok(display_name)
753    }
754
755    /// Extracts and enhances the [`RoomSummary`] provided by the homeserver.
756    ///
757    /// This method extracts the relevant data from the [`RoomSummary`] and
758    /// augments it with additional information that may not be included in
759    /// the initial response, such as details about service members in the
760    /// room.
761    ///
762    /// Returns a [`ComputedSummary`].
763    async fn extract_and_augment_summary(
764        &self,
765        summary: &RoomSummary,
766    ) -> StoreResult<ComputedSummary> {
767        let heroes = &summary.room_heroes;
768
769        let mut names = Vec::with_capacity(heroes.len());
770        let own_user_id = self.own_user_id();
771        let member_hints = self.get_member_hints().await?;
772
773        // If we have some service members in the heroes, that means that they are also
774        // part of the joined member counts. They shouldn't be so, otherwise
775        // we'll wrongly assume that there are more members in the room than
776        // they are for the "Bob and 2 others" case.
777        let num_service_members = heroes
778            .iter()
779            .filter(|hero| member_hints.service_members.contains(&hero.user_id))
780            .count() as u64;
781
782        // Construct a filter that is specific to this own user id, set of member hints,
783        // and accepts a `RoomHero` type.
784        let heroes_filter = heroes_filter(own_user_id, &member_hints);
785        let heroes_filter = |hero: &&RoomHero| heroes_filter(&hero.user_id);
786
787        for hero in heroes.iter().filter(heroes_filter) {
788            if let Some(display_name) = &hero.display_name {
789                names.push(display_name.clone());
790            } else {
791                match self.get_member(&hero.user_id).await {
792                    Ok(Some(member)) => {
793                        names.push(member.name().to_owned());
794                    }
795                    Ok(None) => {
796                        warn!("Ignoring hero, no member info for {}", hero.user_id);
797                    }
798                    Err(error) => {
799                        warn!("Ignoring hero, error getting member: {}", error);
800                    }
801                }
802            }
803        }
804
805        let num_joined_invited_guess = summary.joined_member_count + summary.invited_member_count;
806
807        // If the summary doesn't provide the number of joined/invited members, let's
808        // guess something.
809        let num_joined_invited_guess = if num_joined_invited_guess == 0 {
810            let guess = self
811                .store
812                .get_user_ids(self.room_id(), RoomMemberships::JOIN | RoomMemberships::INVITE)
813                .await?
814                .len() as u64;
815
816            guess.saturating_sub(num_service_members)
817        } else {
818            // Otherwise, accept the numbers provided by the summary as the guess.
819            num_joined_invited_guess
820        };
821
822        Ok(ComputedSummary { heroes: names, num_service_members, num_joined_invited_guess })
823    }
824
825    /// Compute the room summary with the data present in the store.
826    ///
827    /// The summary might be incorrect if the database info is outdated.
828    ///
829    /// Returns the [`ComputedSummary`].
830    async fn compute_summary(&self) -> StoreResult<ComputedSummary> {
831        let member_hints = self.get_member_hints().await?;
832
833        // Construct a filter that is specific to this own user id, set of member hints,
834        // and accepts a `RoomMember` type.
835        let heroes_filter = heroes_filter(&self.own_user_id, &member_hints);
836        let heroes_filter = |u: &RoomMember| heroes_filter(u.user_id());
837
838        let mut members = self.members(RoomMemberships::JOIN | RoomMemberships::INVITE).await?;
839
840        // If we have some service members, they shouldn't count to the number of
841        // joined/invited members, otherwise we'll wrongly assume that there are more
842        // members in the room than they are for the "Bob and 2 others" case.
843        let num_service_members = members
844            .iter()
845            .filter(|member| member_hints.service_members.contains(member.user_id()))
846            .count();
847
848        // We can make a good prediction of the total number of joined and invited
849        // members here. This might be incorrect if the database info is
850        // outdated.
851        //
852        // Note: Subtracting here is fine because `num_service_members` is a subset of
853        // `members.len()` due to the above filter operation.
854        let num_joined_invited = members.len() - num_service_members;
855
856        if num_joined_invited == 0
857            || (num_joined_invited == 1 && members[0].user_id() == self.own_user_id)
858        {
859            // No joined or invited members, heroes should be banned and left members.
860            members = self.members(RoomMemberships::LEAVE | RoomMemberships::BAN).await?;
861        }
862
863        // Make the ordering deterministic.
864        members.sort_unstable_by(|lhs, rhs| lhs.name().cmp(rhs.name()));
865
866        let heroes = members
867            .into_iter()
868            .filter(heroes_filter)
869            .take(NUM_HEROES)
870            .map(|u| u.name().to_owned())
871            .collect();
872
873        trace!(
874            ?heroes,
875            num_joined_invited,
876            num_service_members,
877            "Computed a room summary since we didn't receive one."
878        );
879
880        let num_service_members = num_service_members as u64;
881        let num_joined_invited_guess = num_joined_invited as u64;
882
883        Ok(ComputedSummary { heroes, num_service_members, num_joined_invited_guess })
884    }
885
886    async fn get_member_hints(&self) -> StoreResult<MemberHintsEventContent> {
887        Ok(self
888            .store
889            .get_state_event_static::<MemberHintsEventContent>(self.room_id())
890            .await?
891            .and_then(|event| {
892                event
893                    .deserialize()
894                    .inspect_err(|e| warn!("Couldn't deserialize the member hints event: {e}"))
895                    .ok()
896            })
897            .and_then(|event| as_variant!(event, SyncOrStrippedState::Sync(SyncStateEvent::Original(e)) => e.content))
898            .unwrap_or_default())
899    }
900
901    /// Returns the cached computed display name, if available.
902    ///
903    /// This cache is refilled every time we call [`Self::display_name`].
904    pub fn cached_display_name(&self) -> Option<RoomDisplayName> {
905        self.inner.read().cached_display_name.clone()
906    }
907
908    /// Update the cached user defined notification mode.
909    ///
910    /// This is automatically recomputed on every successful sync, and the
911    /// cached result can be retrieved in
912    /// [`Self::cached_user_defined_notification_mode`].
913    pub fn update_cached_user_defined_notification_mode(&self, mode: RoomNotificationMode) {
914        self.inner.update_if(|info| {
915            if info.cached_user_defined_notification_mode.as_ref() != Some(&mode) {
916                info.cached_user_defined_notification_mode = Some(mode);
917
918                true
919            } else {
920                false
921            }
922        });
923    }
924
925    /// Returns the cached user defined notification mode, if available.
926    ///
927    /// This cache is refilled every time we call
928    /// [`Self::update_cached_user_defined_notification_mode`].
929    pub fn cached_user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
930        self.inner.read().cached_user_defined_notification_mode
931    }
932
933    /// Return the last event in this room, if one has been cached during
934    /// sliding sync.
935    pub fn latest_event(&self) -> Option<LatestEvent> {
936        self.inner.read().latest_event.as_deref().cloned()
937    }
938
939    /// Return the most recent few encrypted events. When the keys come through
940    /// to decrypt these, the most recent relevant one will replace
941    /// latest_event. (We can't tell which one is relevant until
942    /// they are decrypted.)
943    #[cfg(feature = "e2e-encryption")]
944    pub(crate) fn latest_encrypted_events(&self) -> Vec<Raw<AnySyncTimelineEvent>> {
945        self.latest_encrypted_events.read().unwrap().iter().cloned().collect()
946    }
947
948    /// Replace our latest_event with the supplied event, and delete it and all
949    /// older encrypted events from latest_encrypted_events, given that the
950    /// new event was at the supplied index in the latest_encrypted_events
951    /// list.
952    ///
953    /// Panics if index is not a valid index in the latest_encrypted_events
954    /// list.
955    ///
956    /// It is the responsibility of the caller to apply the changes into the
957    /// state store after calling this function.
958    #[cfg(feature = "e2e-encryption")]
959    pub(crate) fn on_latest_event_decrypted(
960        &self,
961        latest_event: Box<LatestEvent>,
962        index: usize,
963        changes: &mut crate::StateChanges,
964        room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
965    ) {
966        self.latest_encrypted_events.write().unwrap().drain(0..=index);
967
968        let room_info = changes
969            .room_infos
970            .entry(self.room_id().to_owned())
971            .or_insert_with(|| self.clone_info());
972
973        room_info.latest_event = Some(latest_event);
974
975        room_info_notable_updates
976            .entry(self.room_id().to_owned())
977            .or_default()
978            .insert(RoomInfoNotableUpdateReasons::LATEST_EVENT);
979    }
980
981    /// Get the list of users ids that are considered to be joined members of
982    /// this room.
983    pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
984        self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
985    }
986
987    /// Get the `RoomMember`s of this room that are known to the store, with the
988    /// given memberships.
989    pub async fn members(&self, memberships: RoomMemberships) -> StoreResult<Vec<RoomMember>> {
990        let user_ids = self.store.get_user_ids(self.room_id(), memberships).await?;
991
992        if user_ids.is_empty() {
993            return Ok(Vec::new());
994        }
995
996        let member_events = self
997            .store
998            .get_state_events_for_keys_static::<RoomMemberEventContent, _, _>(
999                self.room_id(),
1000                &user_ids,
1001            )
1002            .await?
1003            .into_iter()
1004            .map(|raw_event| raw_event.deserialize())
1005            .collect::<Result<Vec<_>, _>>()?;
1006
1007        let mut profiles = self.store.get_profiles(self.room_id(), &user_ids).await?;
1008
1009        let mut presences = self
1010            .store
1011            .get_presence_events(&user_ids)
1012            .await?
1013            .into_iter()
1014            .filter_map(|e| {
1015                e.deserialize().ok().map(|presence| (presence.sender.clone(), presence))
1016            })
1017            .collect::<BTreeMap<_, _>>();
1018
1019        let display_names = member_events.iter().map(|e| e.display_name()).collect::<Vec<_>>();
1020        let room_info = self.member_room_info(&display_names).await?;
1021
1022        let mut members = Vec::new();
1023
1024        for event in member_events {
1025            let profile = profiles.remove(event.user_id());
1026            let presence = presences.remove(event.user_id());
1027            members.push(RoomMember::from_parts(event, profile, presence, &room_info))
1028        }
1029
1030        Ok(members)
1031    }
1032
1033    /// Get the heroes for this room.
1034    pub fn heroes(&self) -> Vec<RoomHero> {
1035        self.inner.read().heroes().to_vec()
1036    }
1037
1038    /// Returns the number of members who have joined or been invited to the
1039    /// room.
1040    pub fn active_members_count(&self) -> u64 {
1041        self.inner.read().active_members_count()
1042    }
1043
1044    /// Returns the number of members who have been invited to the room.
1045    pub fn invited_members_count(&self) -> u64 {
1046        self.inner.read().invited_members_count()
1047    }
1048
1049    /// Returns the number of members who have joined the room.
1050    pub fn joined_members_count(&self) -> u64 {
1051        self.inner.read().joined_members_count()
1052    }
1053
1054    /// Subscribe to the inner `RoomInfo`.
1055    pub fn subscribe_info(&self) -> Subscriber<RoomInfo> {
1056        self.inner.subscribe()
1057    }
1058
1059    /// Clone the inner `RoomInfo`.
1060    pub fn clone_info(&self) -> RoomInfo {
1061        self.inner.get()
1062    }
1063
1064    /// Update the summary with given RoomInfo.
1065    pub fn set_room_info(
1066        &self,
1067        room_info: RoomInfo,
1068        room_info_notable_update_reasons: RoomInfoNotableUpdateReasons,
1069    ) {
1070        self.inner.set(room_info);
1071
1072        // Ignore error if no receiver exists.
1073        let _ = self.room_info_notable_update_sender.send(RoomInfoNotableUpdate {
1074            room_id: self.room_id.clone(),
1075            reasons: room_info_notable_update_reasons,
1076        });
1077    }
1078
1079    /// Get the `RoomMember` with the given `user_id`.
1080    ///
1081    /// Returns `None` if the member was never part of this room, otherwise
1082    /// return a `RoomMember` that can be in a joined, RoomState::Invited, left,
1083    /// banned state.
1084    ///
1085    /// Async because it can read from storage.
1086    pub async fn get_member(&self, user_id: &UserId) -> StoreResult<Option<RoomMember>> {
1087        let Some(raw_event) = self.store.get_member_event(self.room_id(), user_id).await? else {
1088            debug!(%user_id, "Member event not found in state store");
1089            return Ok(None);
1090        };
1091
1092        let event = raw_event.deserialize()?;
1093
1094        let presence =
1095            self.store.get_presence_event(user_id).await?.and_then(|e| e.deserialize().ok());
1096
1097        let profile = self.store.get_profile(self.room_id(), user_id).await?;
1098
1099        let display_names = [event.display_name()];
1100        let room_info = self.member_room_info(&display_names).await?;
1101
1102        Ok(Some(RoomMember::from_parts(event, profile, presence, &room_info)))
1103    }
1104
1105    /// The current `MemberRoomInfo` for this room.
1106    ///
1107    /// Async because it can read from storage.
1108    async fn member_room_info<'a>(
1109        &self,
1110        display_names: &'a [DisplayName],
1111    ) -> StoreResult<MemberRoomInfo<'a>> {
1112        let max_power_level = self.max_power_level();
1113        let room_creator = self.inner.read().creator().map(ToOwned::to_owned);
1114
1115        let power_levels = self
1116            .store
1117            .get_state_event_static(self.room_id())
1118            .await?
1119            .and_then(|e| e.deserialize().ok());
1120
1121        let users_display_names =
1122            self.store.get_users_with_display_names(self.room_id(), display_names).await?;
1123
1124        let ignored_users = self
1125            .store
1126            .get_account_data_event_static::<IgnoredUserListEventContent>()
1127            .await?
1128            .map(|c| c.deserialize())
1129            .transpose()?
1130            .map(|e| e.content.ignored_users.into_keys().collect());
1131
1132        Ok(MemberRoomInfo {
1133            power_levels: power_levels.into(),
1134            max_power_level,
1135            room_creator,
1136            users_display_names,
1137            ignored_users,
1138        })
1139    }
1140
1141    /// Get the `Tags` for this room.
1142    pub async fn tags(&self) -> StoreResult<Option<Tags>> {
1143        if let Some(AnyRoomAccountDataEvent::Tag(event)) = self
1144            .store
1145            .get_room_account_data_event(self.room_id(), RoomAccountDataEventType::Tag)
1146            .await?
1147            .and_then(|r| r.deserialize().ok())
1148        {
1149            Ok(Some(event.content.tags))
1150        } else {
1151            Ok(None)
1152        }
1153    }
1154
1155    /// Check whether the room is marked as favourite.
1156    ///
1157    /// A room is considered favourite if it has received the `m.favourite` tag.
1158    pub fn is_favourite(&self) -> bool {
1159        self.inner.read().base_info.notable_tags.contains(RoomNotableTags::FAVOURITE)
1160    }
1161
1162    /// Check whether the room is marked as low priority.
1163    ///
1164    /// A room is considered low priority if it has received the `m.lowpriority`
1165    /// tag.
1166    pub fn is_low_priority(&self) -> bool {
1167        self.inner.read().base_info.notable_tags.contains(RoomNotableTags::LOW_PRIORITY)
1168    }
1169
1170    /// Get the receipt as an `OwnedEventId` and `Receipt` tuple for the given
1171    /// `receipt_type`, `thread` and `user_id` in this room.
1172    pub async fn load_user_receipt(
1173        &self,
1174        receipt_type: ReceiptType,
1175        thread: ReceiptThread,
1176        user_id: &UserId,
1177    ) -> StoreResult<Option<(OwnedEventId, Receipt)>> {
1178        self.store.get_user_room_receipt_event(self.room_id(), receipt_type, thread, user_id).await
1179    }
1180
1181    /// Load from storage the receipts as a list of `OwnedUserId` and `Receipt`
1182    /// tuples for the given `receipt_type`, `thread` and `event_id` in this
1183    /// room.
1184    pub async fn load_event_receipts(
1185        &self,
1186        receipt_type: ReceiptType,
1187        thread: ReceiptThread,
1188        event_id: &EventId,
1189    ) -> StoreResult<Vec<(OwnedUserId, Receipt)>> {
1190        self.store
1191            .get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
1192            .await
1193    }
1194
1195    /// Returns a boolean indicating if this room has been manually marked as
1196    /// unread
1197    pub fn is_marked_unread(&self) -> bool {
1198        self.inner.read().base_info.is_marked_unread
1199    }
1200
1201    /// Returns the recency stamp of the room.
1202    ///
1203    /// Please read `RoomInfo::recency_stamp` to learn more.
1204    pub fn recency_stamp(&self) -> Option<u64> {
1205        self.inner.read().recency_stamp
1206    }
1207
1208    /// Get a `Stream` of loaded pinned events for this room.
1209    /// If no pinned events are found a single empty `Vec` will be returned.
1210    pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> {
1211        self.inner
1212            .subscribe()
1213            .map(|i| i.base_info.pinned_events.map(|c| c.pinned).unwrap_or_default())
1214    }
1215
1216    /// Returns the current pinned event ids for this room.
1217    pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
1218        self.inner.read().pinned_event_ids()
1219    }
1220
1221    /// Mark a list of requests to join the room as seen, given their state
1222    /// event ids.
1223    pub async fn mark_knock_requests_as_seen(&self, user_ids: &[OwnedUserId]) -> StoreResult<()> {
1224        let raw_user_ids: Vec<&str> = user_ids.iter().map(|id| id.as_str()).collect();
1225        let member_raw_events = self
1226            .store
1227            .get_state_events_for_keys(self.room_id(), StateEventType::RoomMember, &raw_user_ids)
1228            .await?;
1229        let mut event_to_user_ids = Vec::with_capacity(member_raw_events.len());
1230
1231        // Map the list of events ids to their user ids, if they are event ids for knock
1232        // membership events. Log an error and continue otherwise.
1233        for raw_event in member_raw_events {
1234            let event = raw_event.cast::<RoomMemberEventContent>().deserialize()?;
1235            match event {
1236                SyncOrStrippedState::Sync(SyncStateEvent::Original(event)) => {
1237                    if event.content.membership == MembershipState::Knock {
1238                        event_to_user_ids.push((event.event_id, event.state_key))
1239                    } else {
1240                        warn!("Could not mark knock event as seen: event {} for user {} is not in Knock membership state.", event.event_id, event.state_key);
1241                    }
1242                }
1243                _ => warn!(
1244                    "Could not mark knock event as seen: event for user {} is not valid.",
1245                    event.state_key()
1246                ),
1247            }
1248        }
1249
1250        let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?;
1251        let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default();
1252
1253        current_seen_events.extend(event_to_user_ids);
1254
1255        self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?;
1256
1257        Ok(())
1258    }
1259
1260    /// Removes the seen knock request ids that are no longer valid given the
1261    /// current room members.
1262    pub async fn remove_outdated_seen_knock_requests_ids(&self) -> StoreResult<()> {
1263        let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?;
1264        let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default();
1265
1266        // Get and deserialize the member events for the seen knock requests
1267        let keys: Vec<OwnedUserId> = current_seen_events.values().map(|id| id.to_owned()).collect();
1268        let raw_member_events: Vec<RawMemberEvent> =
1269            self.store.get_state_events_for_keys_static(self.room_id(), &keys).await?;
1270        let member_events = raw_member_events
1271            .into_iter()
1272            .map(|raw| raw.deserialize())
1273            .collect::<Result<Vec<MemberEvent>, _>>()?;
1274
1275        let mut ids_to_remove = Vec::new();
1276
1277        for (event_id, user_id) in current_seen_events.iter() {
1278            // Check the seen knock request ids against the current room member events for
1279            // the room members associated to them
1280            let matching_member = member_events.iter().find(|event| event.user_id() == user_id);
1281
1282            if let Some(member) = matching_member {
1283                let member_event_id = member.event_id();
1284                // If the member event is not a knock or it's different knock, it's outdated
1285                if *member.membership() != MembershipState::Knock
1286                    || member_event_id.is_some_and(|id| id != event_id)
1287                {
1288                    ids_to_remove.push(event_id.to_owned());
1289                }
1290            } else {
1291                ids_to_remove.push(event_id.to_owned());
1292            }
1293        }
1294
1295        // If there are no ids to remove, do nothing
1296        if ids_to_remove.is_empty() {
1297            return Ok(());
1298        }
1299
1300        for event_id in ids_to_remove {
1301            current_seen_events.remove(&event_id);
1302        }
1303
1304        self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?;
1305
1306        Ok(())
1307    }
1308
1309    /// Get the list of seen knock request event ids in this room.
1310    pub async fn get_seen_knock_request_ids(
1311        &self,
1312    ) -> Result<BTreeMap<OwnedEventId, OwnedUserId>, StoreError> {
1313        Ok(self.get_write_guarded_current_knock_request_ids().await?.clone().unwrap_or_default())
1314    }
1315
1316    async fn get_write_guarded_current_knock_request_ids(
1317        &self,
1318    ) -> StoreResult<ObservableWriteGuard<'_, Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>>
1319    {
1320        let mut guard = self.seen_knock_request_ids_map.write().await;
1321        // If there are no loaded request ids yet
1322        if guard.is_none() {
1323            // Load the values from the store and update the shared observable contents
1324            let updated_seen_ids = self
1325                .store
1326                .get_kv_data(StateStoreDataKey::SeenKnockRequests(self.room_id()))
1327                .await?
1328                .and_then(|v| v.into_seen_knock_requests())
1329                .unwrap_or_default();
1330
1331            ObservableWriteGuard::set(&mut guard, Some(updated_seen_ids));
1332        }
1333        Ok(guard)
1334    }
1335
1336    async fn update_seen_knock_request_ids(
1337        &self,
1338        mut guard: ObservableWriteGuard<'_, Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
1339        new_value: BTreeMap<OwnedEventId, OwnedUserId>,
1340    ) -> StoreResult<()> {
1341        // Save the new values to the shared observable
1342        ObservableWriteGuard::set(&mut guard, Some(new_value.clone()));
1343
1344        // Save them into the store too
1345        self.store
1346            .set_kv_data(
1347                StateStoreDataKey::SeenKnockRequests(self.room_id()),
1348                StateStoreDataValue::SeenKnockRequests(new_value),
1349            )
1350            .await?;
1351
1352        Ok(())
1353    }
1354}
1355
1356// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
1357#[cfg(not(feature = "test-send-sync"))]
1358unsafe impl Send for Room {}
1359
1360// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
1361#[cfg(not(feature = "test-send-sync"))]
1362unsafe impl Sync for Room {}
1363
1364#[cfg(feature = "test-send-sync")]
1365#[test]
1366// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
1367fn test_send_sync_for_room() {
1368    fn assert_send_sync<T: Send + Sync>() {}
1369
1370    assert_send_sync::<Room>();
1371}
1372
1373/// The underlying pure data structure for joined and left rooms.
1374///
1375/// Holds all the info needed to persist a room into the state store.
1376#[derive(Clone, Debug, Serialize, Deserialize)]
1377pub struct RoomInfo {
1378    /// The version of the room info.
1379    #[serde(default)]
1380    pub(crate) version: u8,
1381
1382    /// The unique room id of the room.
1383    pub(crate) room_id: OwnedRoomId,
1384
1385    /// The state of the room.
1386    pub(crate) room_state: RoomState,
1387
1388    /// The previous state of the room, if any.
1389    pub(crate) prev_room_state: Option<RoomState>,
1390
1391    /// The unread notifications counts, as returned by the server.
1392    ///
1393    /// These might be incorrect for encrypted rooms, since the server doesn't
1394    /// have access to the content of the encrypted events.
1395    pub(crate) notification_counts: UnreadNotificationsCount,
1396
1397    /// The summary of this room.
1398    pub(crate) summary: RoomSummary,
1399
1400    /// Flag remembering if the room members are synced.
1401    pub(crate) members_synced: bool,
1402
1403    /// The prev batch of this room we received during the last sync.
1404    pub(crate) last_prev_batch: Option<String>,
1405
1406    /// How much we know about this room.
1407    pub(crate) sync_info: SyncInfo,
1408
1409    /// Whether or not the encryption info was been synced.
1410    pub(crate) encryption_state_synced: bool,
1411
1412    /// The last event send by sliding sync
1413    pub(crate) latest_event: Option<Box<LatestEvent>>,
1414
1415    /// Information about read receipts for this room.
1416    #[serde(default)]
1417    pub(crate) read_receipts: RoomReadReceipts,
1418
1419    /// Base room info which holds some basic event contents important for the
1420    /// room state.
1421    pub(crate) base_info: Box<BaseRoomInfo>,
1422
1423    /// Did we already warn about an unknown room version in
1424    /// [`RoomInfo::room_version_or_default`]? This is done to avoid
1425    /// spamming about unknown room versions in the log for the same room.
1426    #[serde(skip)]
1427    pub(crate) warned_about_unknown_room_version: Arc<AtomicBool>,
1428
1429    /// Cached display name, useful for sync access.
1430    ///
1431    /// Filled by calling [`Room::compute_display_name`]. It's automatically
1432    /// filled at start when creating a room, or on every successful sync.
1433    #[serde(default, skip_serializing_if = "Option::is_none")]
1434    pub(crate) cached_display_name: Option<RoomDisplayName>,
1435
1436    /// Cached user defined notification mode.
1437    #[serde(default, skip_serializing_if = "Option::is_none")]
1438    pub(crate) cached_user_defined_notification_mode: Option<RoomNotificationMode>,
1439
1440    /// The recency stamp of this room.
1441    ///
1442    /// It's not to be confused with `origin_server_ts` of the latest event.
1443    /// Sliding Sync might "ignore” some events when computing the recency
1444    /// stamp of the room. Thus, using this `recency_stamp` value is
1445    /// more accurate than relying on the latest event.
1446    #[serde(default)]
1447    pub(crate) recency_stamp: Option<u64>,
1448}
1449
1450#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
1451pub(crate) enum SyncInfo {
1452    /// We only know the room exists and whether it is in invite / joined / left
1453    /// state.
1454    ///
1455    /// This is the case when we have a limited sync or only seen the room
1456    /// because of a request we've done, like a room creation event.
1457    NoState,
1458
1459    /// Some states have been synced, but they might have been filtered or is
1460    /// stale, as it is from a room we've left.
1461    PartiallySynced,
1462
1463    /// We have all the latest state events.
1464    FullySynced,
1465}
1466
1467impl RoomInfo {
1468    #[doc(hidden)] // used by store tests, otherwise it would be pub(crate)
1469    pub fn new(room_id: &RoomId, room_state: RoomState) -> Self {
1470        Self {
1471            version: 1,
1472            room_id: room_id.into(),
1473            room_state,
1474            prev_room_state: None,
1475            notification_counts: Default::default(),
1476            summary: Default::default(),
1477            members_synced: false,
1478            last_prev_batch: None,
1479            sync_info: SyncInfo::NoState,
1480            encryption_state_synced: false,
1481            latest_event: None,
1482            read_receipts: Default::default(),
1483            base_info: Box::new(BaseRoomInfo::new()),
1484            warned_about_unknown_room_version: Arc::new(false.into()),
1485            cached_display_name: None,
1486            cached_user_defined_notification_mode: None,
1487            recency_stamp: None,
1488        }
1489    }
1490
1491    /// Mark this Room as joined.
1492    pub fn mark_as_joined(&mut self) {
1493        self.set_state(RoomState::Joined);
1494    }
1495
1496    /// Mark this Room as left.
1497    pub fn mark_as_left(&mut self) {
1498        self.set_state(RoomState::Left);
1499    }
1500
1501    /// Mark this Room as invited.
1502    pub fn mark_as_invited(&mut self) {
1503        self.set_state(RoomState::Invited);
1504    }
1505
1506    /// Mark this Room as knocked.
1507    pub fn mark_as_knocked(&mut self) {
1508        self.set_state(RoomState::Knocked);
1509    }
1510
1511    /// Mark this Room as banned.
1512    pub fn mark_as_banned(&mut self) {
1513        self.set_state(RoomState::Banned);
1514    }
1515
1516    /// Set the membership RoomState of this Room
1517    pub fn set_state(&mut self, room_state: RoomState) {
1518        if room_state != self.room_state {
1519            self.prev_room_state = Some(self.room_state);
1520            self.room_state = room_state;
1521        }
1522    }
1523
1524    /// Mark this Room as having all the members synced.
1525    pub fn mark_members_synced(&mut self) {
1526        self.members_synced = true;
1527    }
1528
1529    /// Mark this Room as still missing member information.
1530    pub fn mark_members_missing(&mut self) {
1531        self.members_synced = false;
1532    }
1533
1534    /// Returns whether the room members are synced.
1535    pub fn are_members_synced(&self) -> bool {
1536        self.members_synced
1537    }
1538
1539    /// Mark this Room as still missing some state information.
1540    pub fn mark_state_partially_synced(&mut self) {
1541        self.sync_info = SyncInfo::PartiallySynced;
1542    }
1543
1544    /// Mark this Room as still having all state synced.
1545    pub fn mark_state_fully_synced(&mut self) {
1546        self.sync_info = SyncInfo::FullySynced;
1547    }
1548
1549    /// Mark this Room as still having no state synced.
1550    pub fn mark_state_not_synced(&mut self) {
1551        self.sync_info = SyncInfo::NoState;
1552    }
1553
1554    /// Mark this Room as having the encryption state synced.
1555    pub fn mark_encryption_state_synced(&mut self) {
1556        self.encryption_state_synced = true;
1557    }
1558
1559    /// Mark this Room as still missing encryption state information.
1560    pub fn mark_encryption_state_missing(&mut self) {
1561        self.encryption_state_synced = false;
1562    }
1563
1564    /// Set the `prev_batch`-token.
1565    /// Returns whether the token has differed and thus has been upgraded:
1566    /// `false` means no update was applied as the were the same
1567    pub fn set_prev_batch(&mut self, prev_batch: Option<&str>) -> bool {
1568        if self.last_prev_batch.as_deref() != prev_batch {
1569            self.last_prev_batch = prev_batch.map(|p| p.to_owned());
1570            true
1571        } else {
1572            false
1573        }
1574    }
1575
1576    /// Returns the state this room is in.
1577    pub fn state(&self) -> RoomState {
1578        self.room_state
1579    }
1580
1581    /// Returns whether this is an encrypted room.
1582    pub fn is_encrypted(&self) -> bool {
1583        self.base_info.encryption.is_some()
1584    }
1585
1586    /// Set the encryption event content in this room.
1587    pub fn set_encryption_event(&mut self, event: Option<RoomEncryptionEventContent>) {
1588        self.base_info.encryption = event;
1589    }
1590
1591    /// Handle the given state event.
1592    ///
1593    /// Returns true if the event modified the info, false otherwise.
1594    pub fn handle_state_event(&mut self, event: &AnySyncStateEvent) -> bool {
1595        let ret = self.base_info.handle_state_event(event);
1596
1597        // If we received an `m.room.encryption` event here, and encryption got enabled,
1598        // then we can be certain that we have synced the encryption state event, so
1599        // mark it here as synced.
1600        if let AnySyncStateEvent::RoomEncryption(_) = event {
1601            if self.is_encrypted() {
1602                self.mark_encryption_state_synced();
1603            }
1604        }
1605
1606        ret
1607    }
1608
1609    /// Handle the given stripped state event.
1610    ///
1611    /// Returns true if the event modified the info, false otherwise.
1612    pub fn handle_stripped_state_event(&mut self, event: &AnyStrippedStateEvent) -> bool {
1613        self.base_info.handle_stripped_state_event(event)
1614    }
1615
1616    /// Handle the given redaction.
1617    #[instrument(skip_all, fields(redacts))]
1618    pub fn handle_redaction(
1619        &mut self,
1620        event: &SyncRoomRedactionEvent,
1621        _raw: &Raw<SyncRoomRedactionEvent>,
1622    ) {
1623        let room_version = self.base_info.room_version().unwrap_or(&RoomVersionId::V1);
1624
1625        let Some(redacts) = event.redacts(room_version) else {
1626            info!("Can't apply redaction, redacts field is missing");
1627            return;
1628        };
1629        tracing::Span::current().record("redacts", debug(redacts));
1630
1631        if let Some(latest_event) = &mut self.latest_event {
1632            tracing::trace!("Checking if redaction applies to latest event");
1633            if latest_event.event_id().as_deref() == Some(redacts) {
1634                match apply_redaction(latest_event.event().raw(), _raw, room_version) {
1635                    Some(redacted) => {
1636                        // Even if the original event was encrypted, redaction removes all its
1637                        // fields so it cannot possibly be successfully decrypted after redaction.
1638                        latest_event.event_mut().kind =
1639                            TimelineEventKind::PlainText { event: redacted };
1640                        debug!("Redacted latest event");
1641                    }
1642                    None => {
1643                        self.latest_event = None;
1644                        debug!("Removed latest event");
1645                    }
1646                }
1647            }
1648        }
1649
1650        self.base_info.handle_redaction(redacts);
1651    }
1652
1653    /// Returns the current room avatar.
1654    pub fn avatar_url(&self) -> Option<&MxcUri> {
1655        self.base_info
1656            .avatar
1657            .as_ref()
1658            .and_then(|e| e.as_original().and_then(|e| e.content.url.as_deref()))
1659    }
1660
1661    /// Update the room avatar.
1662    pub fn update_avatar(&mut self, url: Option<OwnedMxcUri>) {
1663        self.base_info.avatar = url.map(|url| {
1664            let mut content = RoomAvatarEventContent::new();
1665            content.url = Some(url);
1666
1667            MinimalStateEvent::Original(OriginalMinimalStateEvent { content, event_id: None })
1668        });
1669    }
1670
1671    /// Returns information about the current room avatar.
1672    pub fn avatar_info(&self) -> Option<&avatar::ImageInfo> {
1673        self.base_info
1674            .avatar
1675            .as_ref()
1676            .and_then(|e| e.as_original().and_then(|e| e.content.info.as_deref()))
1677    }
1678
1679    /// Update the notifications count.
1680    pub fn update_notification_count(&mut self, notification_counts: UnreadNotificationsCount) {
1681        self.notification_counts = notification_counts;
1682    }
1683
1684    /// Update the RoomSummary from a Ruma `RoomSummary`.
1685    ///
1686    /// Returns true if any field has been updated, false otherwise.
1687    pub fn update_from_ruma_summary(&mut self, summary: &RumaSummary) -> bool {
1688        let mut changed = false;
1689
1690        if !summary.is_empty() {
1691            if !summary.heroes.is_empty() {
1692                self.summary.room_heroes = summary
1693                    .heroes
1694                    .iter()
1695                    .map(|hero_id| RoomHero {
1696                        user_id: hero_id.to_owned(),
1697                        display_name: None,
1698                        avatar_url: None,
1699                    })
1700                    .collect();
1701
1702                changed = true;
1703            }
1704
1705            if let Some(joined) = summary.joined_member_count {
1706                self.summary.joined_member_count = joined.into();
1707                changed = true;
1708            }
1709
1710            if let Some(invited) = summary.invited_member_count {
1711                self.summary.invited_member_count = invited.into();
1712                changed = true;
1713            }
1714        }
1715
1716        changed
1717    }
1718
1719    /// Updates the joined member count.
1720    pub(crate) fn update_joined_member_count(&mut self, count: u64) {
1721        self.summary.joined_member_count = count;
1722    }
1723
1724    /// Updates the invited member count.
1725    pub(crate) fn update_invited_member_count(&mut self, count: u64) {
1726        self.summary.invited_member_count = count;
1727    }
1728
1729    /// Updates the room heroes.
1730    pub(crate) fn update_heroes(&mut self, heroes: Vec<RoomHero>) {
1731        self.summary.room_heroes = heroes;
1732    }
1733
1734    /// The heroes for this room.
1735    pub fn heroes(&self) -> &[RoomHero] {
1736        &self.summary.room_heroes
1737    }
1738
1739    /// The number of active members (invited + joined) in the room.
1740    ///
1741    /// The return value is saturated at `u64::MAX`.
1742    pub fn active_members_count(&self) -> u64 {
1743        self.summary.joined_member_count.saturating_add(self.summary.invited_member_count)
1744    }
1745
1746    /// The number of invited members in the room
1747    pub fn invited_members_count(&self) -> u64 {
1748        self.summary.invited_member_count
1749    }
1750
1751    /// The number of joined members in the room
1752    pub fn joined_members_count(&self) -> u64 {
1753        self.summary.joined_member_count
1754    }
1755
1756    /// Get the canonical alias of this room.
1757    pub fn canonical_alias(&self) -> Option<&RoomAliasId> {
1758        self.base_info.canonical_alias.as_ref()?.as_original()?.content.alias.as_deref()
1759    }
1760
1761    /// Get the alternative aliases of this room.
1762    pub fn alt_aliases(&self) -> &[OwnedRoomAliasId] {
1763        self.base_info
1764            .canonical_alias
1765            .as_ref()
1766            .and_then(|ev| ev.as_original())
1767            .map(|ev| ev.content.alt_aliases.as_ref())
1768            .unwrap_or_default()
1769    }
1770
1771    /// Get the room ID of this room.
1772    pub fn room_id(&self) -> &RoomId {
1773        &self.room_id
1774    }
1775
1776    /// Get the room version of this room.
1777    pub fn room_version(&self) -> Option<&RoomVersionId> {
1778        self.base_info.room_version()
1779    }
1780
1781    /// Get the room version of this room, or a sensible default.
1782    ///
1783    /// Will warn (at most once) if the room creation event is missing from this
1784    /// [`RoomInfo`].
1785    pub fn room_version_or_default(&self) -> RoomVersionId {
1786        use std::sync::atomic::Ordering;
1787
1788        self.base_info.room_version().cloned().unwrap_or_else(|| {
1789            if self
1790                .warned_about_unknown_room_version
1791                .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
1792                .is_ok()
1793            {
1794                warn!("Unknown room version, falling back to v10");
1795            }
1796
1797            RoomVersionId::V10
1798        })
1799    }
1800
1801    /// Get the room type of this room.
1802    pub fn room_type(&self) -> Option<&RoomType> {
1803        match self.base_info.create.as_ref()? {
1804            MinimalStateEvent::Original(ev) => ev.content.room_type.as_ref(),
1805            MinimalStateEvent::Redacted(ev) => ev.content.room_type.as_ref(),
1806        }
1807    }
1808
1809    /// Get the creator of this room.
1810    pub fn creator(&self) -> Option<&UserId> {
1811        match self.base_info.create.as_ref()? {
1812            MinimalStateEvent::Original(ev) => Some(&ev.content.creator),
1813            MinimalStateEvent::Redacted(ev) => Some(&ev.content.creator),
1814        }
1815    }
1816
1817    fn guest_access(&self) -> &GuestAccess {
1818        match &self.base_info.guest_access {
1819            Some(MinimalStateEvent::Original(ev)) => &ev.content.guest_access,
1820            _ => &GuestAccess::Forbidden,
1821        }
1822    }
1823
1824    /// Returns the history visibility for this room.
1825    ///
1826    /// Returns None if the event was never seen during sync.
1827    pub fn history_visibility(&self) -> Option<&HistoryVisibility> {
1828        match &self.base_info.history_visibility {
1829            Some(MinimalStateEvent::Original(ev)) => Some(&ev.content.history_visibility),
1830            _ => None,
1831        }
1832    }
1833
1834    /// Returns the history visibility for this room, or a sensible default.
1835    ///
1836    /// Returns `Shared`, the default specified by the [spec], when the event is
1837    /// missing.
1838    ///
1839    /// [spec]: https://spec.matrix.org/latest/client-server-api/#server-behaviour-7
1840    pub fn history_visibility_or_default(&self) -> &HistoryVisibility {
1841        match &self.base_info.history_visibility {
1842            Some(MinimalStateEvent::Original(ev)) => &ev.content.history_visibility,
1843            _ => &HistoryVisibility::Shared,
1844        }
1845    }
1846
1847    /// Returns the join rule for this room.
1848    ///
1849    /// Defaults to `Public`, if missing.
1850    pub fn join_rule(&self) -> &JoinRule {
1851        match &self.base_info.join_rules {
1852            Some(MinimalStateEvent::Original(ev)) => &ev.content.join_rule,
1853            _ => &JoinRule::Public,
1854        }
1855    }
1856
1857    /// Get the name of this room.
1858    pub fn name(&self) -> Option<&str> {
1859        let name = &self.base_info.name.as_ref()?.as_original()?.content.name;
1860        (!name.is_empty()).then_some(name)
1861    }
1862
1863    fn tombstone(&self) -> Option<&RoomTombstoneEventContent> {
1864        Some(&self.base_info.tombstone.as_ref()?.as_original()?.content)
1865    }
1866
1867    /// Returns the topic for this room, if set.
1868    pub fn topic(&self) -> Option<&str> {
1869        Some(&self.base_info.topic.as_ref()?.as_original()?.content.topic)
1870    }
1871
1872    /// Get a list of all the valid (non expired) matrixRTC memberships and
1873    /// associated UserId's in this room.
1874    ///
1875    /// The vector is ordered by oldest membership to newest.
1876    fn active_matrix_rtc_memberships(&self) -> Vec<(CallMemberStateKey, MembershipData<'_>)> {
1877        let mut v = self
1878            .base_info
1879            .rtc_member_events
1880            .iter()
1881            .filter_map(|(user_id, ev)| {
1882                ev.as_original().map(|ev| {
1883                    ev.content
1884                        .active_memberships(None)
1885                        .into_iter()
1886                        .map(move |m| (user_id.clone(), m))
1887                })
1888            })
1889            .flatten()
1890            .collect::<Vec<_>>();
1891        v.sort_by_key(|(_, m)| m.created_ts());
1892        v
1893    }
1894
1895    /// Similar to
1896    /// [`matrix_rtc_memberships`](Self::active_matrix_rtc_memberships) but only
1897    /// returns Memberships with application "m.call" and scope "m.room".
1898    ///
1899    /// The vector is ordered by oldest membership user to newest.
1900    fn active_room_call_memberships(&self) -> Vec<(CallMemberStateKey, MembershipData<'_>)> {
1901        self.active_matrix_rtc_memberships()
1902            .into_iter()
1903            .filter(|(_user_id, m)| m.is_room_call())
1904            .collect()
1905    }
1906
1907    /// Is there a non expired membership with application "m.call" and scope
1908    /// "m.room" in this room.
1909    pub fn has_active_room_call(&self) -> bool {
1910        !self.active_room_call_memberships().is_empty()
1911    }
1912
1913    /// Returns a Vec of userId's that participate in the room call.
1914    ///
1915    /// matrix_rtc memberships with application "m.call" and scope "m.room" are
1916    /// considered. A user can occur twice if they join with two devices.
1917    /// convert to a set depending if the different users are required or the
1918    /// amount of sessions.
1919    ///
1920    /// The vector is ordered by oldest membership user to newest.
1921    pub fn active_room_call_participants(&self) -> Vec<OwnedUserId> {
1922        self.active_room_call_memberships()
1923            .iter()
1924            .map(|(call_member_state_key, _)| call_member_state_key.user_id().to_owned())
1925            .collect()
1926    }
1927
1928    /// Returns the latest (decrypted) event recorded for this room.
1929    pub fn latest_event(&self) -> Option<&LatestEvent> {
1930        self.latest_event.as_deref()
1931    }
1932
1933    /// Updates the recency stamp of this room.
1934    ///
1935    /// Please read [`Self::recency_stamp`] to learn more.
1936    pub(crate) fn update_recency_stamp(&mut self, stamp: u64) {
1937        self.recency_stamp = Some(stamp);
1938    }
1939
1940    /// Returns the current pinned event ids for this room.
1941    pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
1942        self.base_info.pinned_events.clone().map(|c| c.pinned)
1943    }
1944
1945    /// Checks if an `EventId` is currently pinned.
1946    /// It avoids having to clone the whole list of event ids to check a single
1947    /// value.
1948    ///
1949    /// Returns `true` if the provided `event_id` is pinned, `false` otherwise.
1950    pub fn is_pinned_event(&self, event_id: &EventId) -> bool {
1951        self.base_info
1952            .pinned_events
1953            .as_ref()
1954            .map(|p| p.pinned.contains(&event_id.to_owned()))
1955            .unwrap_or_default()
1956    }
1957
1958    /// Apply migrations to this `RoomInfo` if needed.
1959    ///
1960    /// This should be used to populate new fields with data from the state
1961    /// store.
1962    ///
1963    /// Returns `true` if migrations were applied and this `RoomInfo` needs to
1964    /// be persisted to the state store.
1965    #[instrument(skip_all, fields(room_id = ?self.room_id))]
1966    pub(crate) async fn apply_migrations(&mut self, store: Arc<DynStateStore>) -> bool {
1967        let mut migrated = false;
1968
1969        if self.version < 1 {
1970            info!("Migrating room info to version 1");
1971
1972            // notable_tags
1973            match store.get_room_account_data_event_static::<TagEventContent>(&self.room_id).await {
1974                // Pinned events are never in stripped state.
1975                Ok(Some(raw_event)) => match raw_event.deserialize() {
1976                    Ok(event) => {
1977                        self.base_info.handle_notable_tags(&event.content.tags);
1978                    }
1979                    Err(error) => {
1980                        warn!("Failed to deserialize room tags: {error}");
1981                    }
1982                },
1983                Ok(_) => {
1984                    // Nothing to do.
1985                }
1986                Err(error) => {
1987                    warn!("Failed to load room tags: {error}");
1988                }
1989            }
1990
1991            // pinned_events
1992            match store.get_state_event_static::<RoomPinnedEventsEventContent>(&self.room_id).await
1993            {
1994                // Pinned events are never in stripped state.
1995                Ok(Some(RawSyncOrStrippedState::Sync(raw_event))) => {
1996                    match raw_event.deserialize() {
1997                        Ok(event) => {
1998                            self.handle_state_event(&event.into());
1999                        }
2000                        Err(error) => {
2001                            warn!("Failed to deserialize room pinned events: {error}");
2002                        }
2003                    }
2004                }
2005                Ok(_) => {
2006                    // Nothing to do.
2007                }
2008                Err(error) => {
2009                    warn!("Failed to load room pinned events: {error}");
2010                }
2011            }
2012
2013            self.version = 1;
2014            migrated = true;
2015        }
2016
2017        migrated
2018    }
2019}
2020
2021/// Apply a redaction to the given target `event`, given the raw redaction event
2022/// and the room version.
2023pub fn apply_redaction(
2024    event: &Raw<AnySyncTimelineEvent>,
2025    raw_redaction: &Raw<SyncRoomRedactionEvent>,
2026    room_version: &RoomVersionId,
2027) -> Option<Raw<AnySyncTimelineEvent>> {
2028    use ruma::canonical_json::{redact_in_place, RedactedBecause};
2029
2030    let mut event_json = match event.deserialize_as() {
2031        Ok(json) => json,
2032        Err(e) => {
2033            warn!("Failed to deserialize latest event: {e}");
2034            return None;
2035        }
2036    };
2037
2038    let redacted_because = match RedactedBecause::from_raw_event(raw_redaction) {
2039        Ok(rb) => rb,
2040        Err(e) => {
2041            warn!("Redaction event is not valid canonical JSON: {e}");
2042            return None;
2043        }
2044    };
2045
2046    let redact_result = redact_in_place(&mut event_json, room_version, Some(redacted_because));
2047
2048    if let Err(e) = redact_result {
2049        warn!("Failed to redact event: {e}");
2050        return None;
2051    }
2052
2053    let raw = Raw::new(&event_json).expect("CanonicalJsonObject must be serializable");
2054    Some(raw.cast())
2055}
2056
2057bitflags! {
2058    /// Room state filter as a bitset.
2059    ///
2060    /// Note that [`RoomStateFilter::empty()`] doesn't filter the results and
2061    /// is equivalent to [`RoomStateFilter::all()`].
2062    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2063    pub struct RoomStateFilter: u16 {
2064        /// The room is in a joined state.
2065        const JOINED   = 0b00000001;
2066        /// The room is in an invited state.
2067        const INVITED  = 0b00000010;
2068        /// The room is in a left state.
2069        const LEFT     = 0b00000100;
2070        /// The room is in a knocked state.
2071        const KNOCKED  = 0b00001000;
2072        /// The room is in a banned state.
2073        const BANNED   = 0b00010000;
2074    }
2075}
2076
2077impl RoomStateFilter {
2078    /// Whether the given room state matches this `RoomStateFilter`.
2079    pub fn matches(&self, state: RoomState) -> bool {
2080        if self.is_empty() {
2081            return true;
2082        }
2083
2084        let bit_state = match state {
2085            RoomState::Joined => Self::JOINED,
2086            RoomState::Left => Self::LEFT,
2087            RoomState::Invited => Self::INVITED,
2088            RoomState::Knocked => Self::KNOCKED,
2089            RoomState::Banned => Self::BANNED,
2090        };
2091
2092        self.contains(bit_state)
2093    }
2094
2095    /// Get this `RoomStateFilter` as a list of matching [`RoomState`]s.
2096    pub fn as_vec(&self) -> Vec<RoomState> {
2097        let mut states = Vec::new();
2098
2099        if self.contains(Self::JOINED) {
2100            states.push(RoomState::Joined);
2101        }
2102        if self.contains(Self::LEFT) {
2103            states.push(RoomState::Left);
2104        }
2105        if self.contains(Self::INVITED) {
2106            states.push(RoomState::Invited);
2107        }
2108        if self.contains(Self::KNOCKED) {
2109            states.push(RoomState::Knocked);
2110        }
2111        if self.contains(Self::BANNED) {
2112            states.push(RoomState::Banned);
2113        }
2114
2115        states
2116    }
2117}
2118
2119/// Calculate room name according to step 3 of the [naming algorithm].
2120///
2121/// [naming algorithm]: https://spec.matrix.org/latest/client-server-api/#calculating-the-display-name-for-a-room
2122fn compute_display_name_from_heroes(
2123    num_joined_invited: u64,
2124    mut heroes: Vec<&str>,
2125) -> RoomDisplayName {
2126    let num_heroes = heroes.len() as u64;
2127    let num_joined_invited_except_self = num_joined_invited.saturating_sub(1);
2128
2129    // Stabilize ordering.
2130    heroes.sort_unstable();
2131
2132    let names = if num_heroes == 0 && num_joined_invited > 1 {
2133        format!("{} people", num_joined_invited)
2134    } else if num_heroes >= num_joined_invited_except_self {
2135        heroes.join(", ")
2136    } else if num_heroes < num_joined_invited_except_self && num_joined_invited > 1 {
2137        // TODO: What length does the spec want us to use here and in
2138        // the `else`?
2139        format!("{}, and {} others", heroes.join(", "), (num_joined_invited - num_heroes))
2140    } else {
2141        "".to_owned()
2142    };
2143
2144    // User is alone.
2145    if num_joined_invited <= 1 {
2146        if names.is_empty() {
2147            RoomDisplayName::Empty
2148        } else {
2149            RoomDisplayName::EmptyWas(names)
2150        }
2151    } else {
2152        RoomDisplayName::Calculated(names)
2153    }
2154}
2155
2156#[cfg(test)]
2157mod tests {
2158    use std::{
2159        collections::BTreeSet,
2160        ops::{Not, Sub},
2161        str::FromStr,
2162        sync::Arc,
2163        time::Duration,
2164    };
2165
2166    use assign::assign;
2167    use matrix_sdk_common::deserialized_responses::TimelineEvent;
2168    use matrix_sdk_test::{
2169        async_test,
2170        event_factory::EventFactory,
2171        test_json::{sync_events::PINNED_EVENTS, TAG},
2172        ALICE, BOB, CAROL,
2173    };
2174    use ruma::{
2175        api::client::sync::sync_events::v3::RoomSummary as RumaSummary,
2176        device_id, event_id,
2177        events::{
2178            call::member::{
2179                ActiveFocus, ActiveLivekitFocus, Application, CallApplicationContent,
2180                CallMemberEventContent, CallMemberStateKey, Focus, LegacyMembershipData,
2181                LegacyMembershipDataInit, LivekitFocus, OriginalSyncCallMemberEvent,
2182            },
2183            room::{
2184                canonical_alias::RoomCanonicalAliasEventContent,
2185                encryption::{OriginalSyncRoomEncryptionEvent, RoomEncryptionEventContent},
2186                member::{MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent},
2187                name::RoomNameEventContent,
2188                pinned_events::RoomPinnedEventsEventContent,
2189            },
2190            AnySyncStateEvent, EmptyStateKey, StateEventType, StateUnsigned, SyncStateEvent,
2191        },
2192        owned_event_id, owned_room_id, owned_user_id, room_alias_id, room_id,
2193        serde::Raw,
2194        time::SystemTime,
2195        user_id, DeviceId, EventEncryptionAlgorithm, EventId, MilliSecondsSinceUnixEpoch,
2196        OwnedEventId, OwnedUserId, UserId,
2197    };
2198    use serde_json::json;
2199    use similar_asserts::assert_eq;
2200    use stream_assert::{assert_pending, assert_ready};
2201
2202    use super::{compute_display_name_from_heroes, Room, RoomHero, RoomInfo, RoomState, SyncInfo};
2203    use crate::{
2204        latest_event::LatestEvent,
2205        rooms::RoomNotableTags,
2206        store::{IntoStateStore, MemoryStore, StateChanges, StateStore, StoreConfig},
2207        test_utils::logged_in_base_client,
2208        BaseClient, MinimalStateEvent, OriginalMinimalStateEvent, RoomDisplayName,
2209        RoomInfoNotableUpdateReasons, RoomStateFilter, SessionMeta,
2210    };
2211
2212    #[test]
2213    fn test_room_info_serialization() {
2214        // This test exists to make sure we don't accidentally change the
2215        // serialized format for `RoomInfo`.
2216
2217        use ruma::owned_user_id;
2218
2219        use super::RoomSummary;
2220        use crate::{rooms::BaseRoomInfo, sync::UnreadNotificationsCount};
2221
2222        let info = RoomInfo {
2223            version: 1,
2224            room_id: room_id!("!gda78o:server.tld").into(),
2225            room_state: RoomState::Invited,
2226            prev_room_state: None,
2227            notification_counts: UnreadNotificationsCount {
2228                highlight_count: 1,
2229                notification_count: 2,
2230            },
2231            summary: RoomSummary {
2232                room_heroes: vec![RoomHero {
2233                    user_id: owned_user_id!("@somebody:example.org"),
2234                    display_name: None,
2235                    avatar_url: None,
2236                }],
2237                joined_member_count: 5,
2238                invited_member_count: 0,
2239            },
2240            members_synced: true,
2241            last_prev_batch: Some("pb".to_owned()),
2242            sync_info: SyncInfo::FullySynced,
2243            encryption_state_synced: true,
2244            latest_event: Some(Box::new(LatestEvent::new(TimelineEvent::new(
2245                Raw::from_json_string(json!({"sender": "@u:i.uk"}).to_string()).unwrap(),
2246            )))),
2247            base_info: Box::new(
2248                assign!(BaseRoomInfo::new(), { pinned_events: Some(RoomPinnedEventsEventContent::new(vec![owned_event_id!("$a")])) }),
2249            ),
2250            read_receipts: Default::default(),
2251            warned_about_unknown_room_version: Arc::new(false.into()),
2252            cached_display_name: None,
2253            cached_user_defined_notification_mode: None,
2254            recency_stamp: Some(42),
2255        };
2256
2257        let info_json = json!({
2258            "version": 1,
2259            "room_id": "!gda78o:server.tld",
2260            "room_state": "Invited",
2261            "prev_room_state": null,
2262            "notification_counts": {
2263                "highlight_count": 1,
2264                "notification_count": 2,
2265            },
2266            "summary": {
2267                "room_heroes": [{
2268                    "user_id": "@somebody:example.org",
2269                    "display_name": null,
2270                    "avatar_url": null
2271                }],
2272                "joined_member_count": 5,
2273                "invited_member_count": 0,
2274            },
2275            "members_synced": true,
2276            "last_prev_batch": "pb",
2277            "sync_info": "FullySynced",
2278            "encryption_state_synced": true,
2279            "latest_event": {
2280                "event": {
2281                    "kind": {"PlainText": {"event": {"sender": "@u:i.uk"}}},
2282                },
2283            },
2284            "base_info": {
2285                "avatar": null,
2286                "canonical_alias": null,
2287                "create": null,
2288                "dm_targets": [],
2289                "encryption": null,
2290                "guest_access": null,
2291                "history_visibility": null,
2292                "is_marked_unread": false,
2293                "join_rules": null,
2294                "max_power_level": 100,
2295                "name": null,
2296                "tombstone": null,
2297                "topic": null,
2298                "pinned_events": {
2299                    "pinned": ["$a"]
2300                },
2301            },
2302            "read_receipts": {
2303                "num_unread": 0,
2304                "num_mentions": 0,
2305                "num_notifications": 0,
2306                "latest_active": null,
2307                "pending": []
2308            },
2309            "recency_stamp": 42,
2310        });
2311
2312        assert_eq!(serde_json::to_value(info).unwrap(), info_json);
2313    }
2314
2315    // Ensure we can still deserialize RoomInfos before we added things to its
2316    // schema
2317    //
2318    // In an ideal world, we must not change this test. Please see
2319    // [`test_room_info_serialization`] if you want to test a “recent” `RoomInfo`
2320    // deserialization.
2321    #[test]
2322    fn test_room_info_deserialization_without_optional_items() {
2323        use ruma::{owned_mxc_uri, owned_user_id};
2324
2325        // The following JSON should never change if we want to be able to read in old
2326        // cached state
2327        let info_json = json!({
2328            "room_id": "!gda78o:server.tld",
2329            "room_state": "Invited",
2330            "prev_room_state": null,
2331            "notification_counts": {
2332                "highlight_count": 1,
2333                "notification_count": 2,
2334            },
2335            "summary": {
2336                "room_heroes": [{
2337                    "user_id": "@somebody:example.org",
2338                    "display_name": "Somebody",
2339                    "avatar_url": "mxc://example.org/abc"
2340                }],
2341                "joined_member_count": 5,
2342                "invited_member_count": 0,
2343            },
2344            "members_synced": true,
2345            "last_prev_batch": "pb",
2346            "sync_info": "FullySynced",
2347            "encryption_state_synced": true,
2348            "base_info": {
2349                "avatar": null,
2350                "canonical_alias": null,
2351                "create": null,
2352                "dm_targets": [],
2353                "encryption": null,
2354                "guest_access": null,
2355                "history_visibility": null,
2356                "join_rules": null,
2357                "max_power_level": 100,
2358                "name": null,
2359                "tombstone": null,
2360                "topic": null,
2361            },
2362        });
2363
2364        let info: RoomInfo = serde_json::from_value(info_json).unwrap();
2365
2366        assert_eq!(info.room_id, room_id!("!gda78o:server.tld"));
2367        assert_eq!(info.room_state, RoomState::Invited);
2368        assert_eq!(info.notification_counts.highlight_count, 1);
2369        assert_eq!(info.notification_counts.notification_count, 2);
2370        assert_eq!(
2371            info.summary.room_heroes,
2372            vec![RoomHero {
2373                user_id: owned_user_id!("@somebody:example.org"),
2374                display_name: Some("Somebody".to_owned()),
2375                avatar_url: Some(owned_mxc_uri!("mxc://example.org/abc")),
2376            }]
2377        );
2378        assert_eq!(info.summary.joined_member_count, 5);
2379        assert_eq!(info.summary.invited_member_count, 0);
2380        assert!(info.members_synced);
2381        assert_eq!(info.last_prev_batch, Some("pb".to_owned()));
2382        assert_eq!(info.sync_info, SyncInfo::FullySynced);
2383        assert!(info.encryption_state_synced);
2384        assert!(info.base_info.avatar.is_none());
2385        assert!(info.base_info.canonical_alias.is_none());
2386        assert!(info.base_info.create.is_none());
2387        assert_eq!(info.base_info.dm_targets.len(), 0);
2388        assert!(info.base_info.encryption.is_none());
2389        assert!(info.base_info.guest_access.is_none());
2390        assert!(info.base_info.history_visibility.is_none());
2391        assert!(info.base_info.join_rules.is_none());
2392        assert_eq!(info.base_info.max_power_level, 100);
2393        assert!(info.base_info.name.is_none());
2394        assert!(info.base_info.tombstone.is_none());
2395        assert!(info.base_info.topic.is_none());
2396    }
2397
2398    #[test]
2399    fn test_room_info_deserialization() {
2400        use ruma::{owned_mxc_uri, owned_user_id};
2401
2402        use crate::notification_settings::RoomNotificationMode;
2403
2404        let info_json = json!({
2405            "room_id": "!gda78o:server.tld",
2406            "room_state": "Joined",
2407            "prev_room_state": "Invited",
2408            "notification_counts": {
2409                "highlight_count": 1,
2410                "notification_count": 2,
2411            },
2412            "summary": {
2413                "room_heroes": [{
2414                    "user_id": "@somebody:example.org",
2415                    "display_name": "Somebody",
2416                    "avatar_url": "mxc://example.org/abc"
2417                }],
2418                "joined_member_count": 5,
2419                "invited_member_count": 0,
2420            },
2421            "members_synced": true,
2422            "last_prev_batch": "pb",
2423            "sync_info": "FullySynced",
2424            "encryption_state_synced": true,
2425            "base_info": {
2426                "avatar": null,
2427                "canonical_alias": null,
2428                "create": null,
2429                "dm_targets": [],
2430                "encryption": null,
2431                "guest_access": null,
2432                "history_visibility": null,
2433                "join_rules": null,
2434                "max_power_level": 100,
2435                "name": null,
2436                "tombstone": null,
2437                "topic": null,
2438            },
2439            "cached_display_name": { "Calculated": "lol" },
2440            "cached_user_defined_notification_mode": "Mute",
2441            "recency_stamp": 42,
2442        });
2443
2444        let info: RoomInfo = serde_json::from_value(info_json).unwrap();
2445
2446        assert_eq!(info.room_id, room_id!("!gda78o:server.tld"));
2447        assert_eq!(info.room_state, RoomState::Joined);
2448        assert_eq!(info.prev_room_state, Some(RoomState::Invited));
2449        assert_eq!(info.notification_counts.highlight_count, 1);
2450        assert_eq!(info.notification_counts.notification_count, 2);
2451        assert_eq!(
2452            info.summary.room_heroes,
2453            vec![RoomHero {
2454                user_id: owned_user_id!("@somebody:example.org"),
2455                display_name: Some("Somebody".to_owned()),
2456                avatar_url: Some(owned_mxc_uri!("mxc://example.org/abc")),
2457            }]
2458        );
2459        assert_eq!(info.summary.joined_member_count, 5);
2460        assert_eq!(info.summary.invited_member_count, 0);
2461        assert!(info.members_synced);
2462        assert_eq!(info.last_prev_batch, Some("pb".to_owned()));
2463        assert_eq!(info.sync_info, SyncInfo::FullySynced);
2464        assert!(info.encryption_state_synced);
2465        assert!(info.latest_event.is_none());
2466        assert!(info.base_info.avatar.is_none());
2467        assert!(info.base_info.canonical_alias.is_none());
2468        assert!(info.base_info.create.is_none());
2469        assert_eq!(info.base_info.dm_targets.len(), 0);
2470        assert!(info.base_info.encryption.is_none());
2471        assert!(info.base_info.guest_access.is_none());
2472        assert!(info.base_info.history_visibility.is_none());
2473        assert!(info.base_info.join_rules.is_none());
2474        assert_eq!(info.base_info.max_power_level, 100);
2475        assert!(info.base_info.name.is_none());
2476        assert!(info.base_info.tombstone.is_none());
2477        assert!(info.base_info.topic.is_none());
2478
2479        assert_eq!(
2480            info.cached_display_name.as_ref(),
2481            Some(&RoomDisplayName::Calculated("lol".to_owned())),
2482        );
2483        assert_eq!(
2484            info.cached_user_defined_notification_mode.as_ref(),
2485            Some(&RoomNotificationMode::Mute)
2486        );
2487        assert_eq!(info.recency_stamp.as_ref(), Some(&42));
2488    }
2489
2490    #[async_test]
2491    async fn test_is_favourite() {
2492        // Given a room,
2493        let client = BaseClient::with_store_config(StoreConfig::new(
2494            "cross-process-store-locks-holder-name".to_owned(),
2495        ));
2496
2497        client
2498            .set_session_meta(
2499                SessionMeta {
2500                    user_id: user_id!("@alice:example.org").into(),
2501                    device_id: ruma::device_id!("AYEAYEAYE").into(),
2502                },
2503                #[cfg(feature = "e2e-encryption")]
2504                None,
2505            )
2506            .await
2507            .unwrap();
2508
2509        let room_id = room_id!("!test:localhost");
2510        let room = client.get_or_create_room(room_id, RoomState::Joined);
2511
2512        // Sanity checks to ensure the room isn't marked as favourite.
2513        assert!(room.is_favourite().not());
2514
2515        // Subscribe to the `RoomInfo`.
2516        let mut room_info_subscriber = room.subscribe_info();
2517
2518        assert_pending!(room_info_subscriber);
2519
2520        // Create the tag.
2521        let tag_raw = Raw::new(&json!({
2522            "content": {
2523                "tags": {
2524                    "m.favourite": {
2525                        "order": 0.0
2526                    },
2527                },
2528            },
2529            "type": "m.tag",
2530        }))
2531        .unwrap()
2532        .cast();
2533
2534        // When the new tag is handled and applied.
2535        let mut changes = StateChanges::default();
2536        client
2537            .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2538            .await;
2539        client.apply_changes(&changes, Default::default());
2540
2541        // The `RoomInfo` is getting notified.
2542        assert_ready!(room_info_subscriber);
2543        assert_pending!(room_info_subscriber);
2544
2545        // The room is now marked as favourite.
2546        assert!(room.is_favourite());
2547
2548        // Now, let's remove the tag.
2549        let tag_raw = Raw::new(&json!({
2550            "content": {
2551                "tags": {},
2552            },
2553            "type": "m.tag"
2554        }))
2555        .unwrap()
2556        .cast();
2557        client
2558            .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2559            .await;
2560        client.apply_changes(&changes, Default::default());
2561
2562        // The `RoomInfo` is getting notified.
2563        assert_ready!(room_info_subscriber);
2564        assert_pending!(room_info_subscriber);
2565
2566        // The room is now marked as _not_ favourite.
2567        assert!(room.is_favourite().not());
2568    }
2569
2570    #[async_test]
2571    async fn test_is_low_priority() {
2572        // Given a room,
2573        let client = BaseClient::with_store_config(StoreConfig::new(
2574            "cross-process-store-locks-holder-name".to_owned(),
2575        ));
2576
2577        client
2578            .set_session_meta(
2579                SessionMeta {
2580                    user_id: user_id!("@alice:example.org").into(),
2581                    device_id: ruma::device_id!("AYEAYEAYE").into(),
2582                },
2583                #[cfg(feature = "e2e-encryption")]
2584                None,
2585            )
2586            .await
2587            .unwrap();
2588
2589        let room_id = room_id!("!test:localhost");
2590        let room = client.get_or_create_room(room_id, RoomState::Joined);
2591
2592        // Sanity checks to ensure the room isn't marked as low priority.
2593        assert!(!room.is_low_priority());
2594
2595        // Subscribe to the `RoomInfo`.
2596        let mut room_info_subscriber = room.subscribe_info();
2597
2598        assert_pending!(room_info_subscriber);
2599
2600        // Create the tag.
2601        let tag_raw = Raw::new(&json!({
2602            "content": {
2603                "tags": {
2604                    "m.lowpriority": {
2605                        "order": 0.0
2606                    },
2607                }
2608            },
2609            "type": "m.tag"
2610        }))
2611        .unwrap()
2612        .cast();
2613
2614        // When the new tag is handled and applied.
2615        let mut changes = StateChanges::default();
2616        client
2617            .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2618            .await;
2619        client.apply_changes(&changes, Default::default());
2620
2621        // The `RoomInfo` is getting notified.
2622        assert_ready!(room_info_subscriber);
2623        assert_pending!(room_info_subscriber);
2624
2625        // The room is now marked as low priority.
2626        assert!(room.is_low_priority());
2627
2628        // Now, let's remove the tag.
2629        let tag_raw = Raw::new(&json!({
2630            "content": {
2631                "tags": {},
2632            },
2633            "type": "m.tag"
2634        }))
2635        .unwrap()
2636        .cast();
2637        client
2638            .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2639            .await;
2640        client.apply_changes(&changes, Default::default());
2641
2642        // The `RoomInfo` is getting notified.
2643        assert_ready!(room_info_subscriber);
2644        assert_pending!(room_info_subscriber);
2645
2646        // The room is now marked as _not_ low priority.
2647        assert!(room.is_low_priority().not());
2648    }
2649
2650    fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
2651        let store = Arc::new(MemoryStore::new());
2652        let user_id = user_id!("@me:example.org");
2653        let room_id = room_id!("!test:localhost");
2654        let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2655
2656        (store.clone(), Room::new(user_id, store, room_id, room_type, sender))
2657    }
2658
2659    fn make_stripped_member_event(user_id: &UserId, name: &str) -> Raw<StrippedRoomMemberEvent> {
2660        let ev_json = json!({
2661            "type": "m.room.member",
2662            "content": assign!(RoomMemberEventContent::new(MembershipState::Join), {
2663                displayname: Some(name.to_owned())
2664            }),
2665            "sender": user_id,
2666            "state_key": user_id,
2667        });
2668
2669        Raw::new(&ev_json).unwrap().cast()
2670    }
2671
2672    #[async_test]
2673    async fn test_display_name_for_joined_room_is_empty_if_no_info() {
2674        let (_, room) = make_room_test_helper(RoomState::Joined);
2675        assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2676    }
2677
2678    #[async_test]
2679    async fn test_display_name_for_joined_room_uses_canonical_alias_if_available() {
2680        let (_, room) = make_room_test_helper(RoomState::Joined);
2681        room.inner
2682            .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2683        assert_eq!(
2684            room.compute_display_name().await.unwrap(),
2685            RoomDisplayName::Aliased("test".to_owned())
2686        );
2687    }
2688
2689    #[async_test]
2690    async fn test_display_name_for_joined_room_prefers_name_over_alias() {
2691        let (_, room) = make_room_test_helper(RoomState::Joined);
2692        room.inner
2693            .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2694        assert_eq!(
2695            room.compute_display_name().await.unwrap(),
2696            RoomDisplayName::Aliased("test".to_owned())
2697        );
2698        room.inner.update(|info| info.base_info.name = Some(make_name_event()));
2699        // Display name wasn't cached when we asked for it above, and name overrides
2700        assert_eq!(
2701            room.compute_display_name().await.unwrap(),
2702            RoomDisplayName::Named("Test Room".to_owned())
2703        );
2704    }
2705
2706    #[async_test]
2707    async fn test_display_name_for_invited_room_is_empty_if_no_info() {
2708        let (_, room) = make_room_test_helper(RoomState::Invited);
2709        assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2710    }
2711
2712    #[async_test]
2713    async fn test_display_name_for_invited_room_is_empty_if_room_name_empty() {
2714        let (_, room) = make_room_test_helper(RoomState::Invited);
2715
2716        let room_name = MinimalStateEvent::Original(OriginalMinimalStateEvent {
2717            content: RoomNameEventContent::new(String::new()),
2718            event_id: None,
2719        });
2720        room.inner.update(|info| info.base_info.name = Some(room_name));
2721
2722        assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2723    }
2724
2725    #[async_test]
2726    async fn test_display_name_for_invited_room_uses_canonical_alias_if_available() {
2727        let (_, room) = make_room_test_helper(RoomState::Invited);
2728        room.inner
2729            .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2730        assert_eq!(
2731            room.compute_display_name().await.unwrap(),
2732            RoomDisplayName::Aliased("test".to_owned())
2733        );
2734    }
2735
2736    #[async_test]
2737    async fn test_display_name_for_invited_room_prefers_name_over_alias() {
2738        let (_, room) = make_room_test_helper(RoomState::Invited);
2739        room.inner
2740            .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2741        assert_eq!(
2742            room.compute_display_name().await.unwrap(),
2743            RoomDisplayName::Aliased("test".to_owned())
2744        );
2745        room.inner.update(|info| info.base_info.name = Some(make_name_event()));
2746        // Display name wasn't cached when we asked for it above, and name overrides
2747        assert_eq!(
2748            room.compute_display_name().await.unwrap(),
2749            RoomDisplayName::Named("Test Room".to_owned())
2750        );
2751    }
2752
2753    fn make_canonical_alias_event() -> MinimalStateEvent<RoomCanonicalAliasEventContent> {
2754        MinimalStateEvent::Original(OriginalMinimalStateEvent {
2755            content: assign!(RoomCanonicalAliasEventContent::new(), {
2756                alias: Some(room_alias_id!("#test:example.com").to_owned()),
2757            }),
2758            event_id: None,
2759        })
2760    }
2761
2762    fn make_name_event() -> MinimalStateEvent<RoomNameEventContent> {
2763        MinimalStateEvent::Original(OriginalMinimalStateEvent {
2764            content: RoomNameEventContent::new("Test Room".to_owned()),
2765            event_id: None,
2766        })
2767    }
2768
2769    #[async_test]
2770    async fn test_display_name_dm_invited() {
2771        let (store, room) = make_room_test_helper(RoomState::Invited);
2772        let room_id = room_id!("!test:localhost");
2773        let matthew = user_id!("@matthew:example.org");
2774        let me = user_id!("@me:example.org");
2775        let mut changes = StateChanges::new("".to_owned());
2776        let summary = assign!(RumaSummary::new(), {
2777            heroes: vec![me.to_owned(), matthew.to_owned()],
2778        });
2779
2780        changes.add_stripped_member(
2781            room_id,
2782            matthew,
2783            make_stripped_member_event(matthew, "Matthew"),
2784        );
2785        changes.add_stripped_member(room_id, me, make_stripped_member_event(me, "Me"));
2786        store.save_changes(&changes).await.unwrap();
2787
2788        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2789        assert_eq!(
2790            room.compute_display_name().await.unwrap(),
2791            RoomDisplayName::Calculated("Matthew".to_owned())
2792        );
2793    }
2794
2795    #[async_test]
2796    async fn test_display_name_dm_invited_no_heroes() {
2797        let (store, room) = make_room_test_helper(RoomState::Invited);
2798        let room_id = room_id!("!test:localhost");
2799        let matthew = user_id!("@matthew:example.org");
2800        let me = user_id!("@me:example.org");
2801        let mut changes = StateChanges::new("".to_owned());
2802
2803        changes.add_stripped_member(
2804            room_id,
2805            matthew,
2806            make_stripped_member_event(matthew, "Matthew"),
2807        );
2808        changes.add_stripped_member(room_id, me, make_stripped_member_event(me, "Me"));
2809        store.save_changes(&changes).await.unwrap();
2810
2811        assert_eq!(
2812            room.compute_display_name().await.unwrap(),
2813            RoomDisplayName::Calculated("Matthew".to_owned())
2814        );
2815    }
2816
2817    #[async_test]
2818    async fn test_display_name_dm_joined() {
2819        let (store, room) = make_room_test_helper(RoomState::Joined);
2820        let room_id = room_id!("!test:localhost");
2821        let matthew = user_id!("@matthew:example.org");
2822        let me = user_id!("@me:example.org");
2823
2824        let mut changes = StateChanges::new("".to_owned());
2825        let summary = assign!(RumaSummary::new(), {
2826            joined_member_count: Some(2u32.into()),
2827            heroes: vec![me.to_owned(), matthew.to_owned()],
2828        });
2829
2830        let f = EventFactory::new().room(room_id!("!test:localhost"));
2831
2832        let members = changes
2833            .state
2834            .entry(room_id.to_owned())
2835            .or_default()
2836            .entry(StateEventType::RoomMember)
2837            .or_default();
2838        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
2839        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2840
2841        store.save_changes(&changes).await.unwrap();
2842
2843        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2844        assert_eq!(
2845            room.compute_display_name().await.unwrap(),
2846            RoomDisplayName::Calculated("Matthew".to_owned())
2847        );
2848    }
2849
2850    #[async_test]
2851    async fn test_display_name_dm_joined_service_members() {
2852        let (store, room) = make_room_test_helper(RoomState::Joined);
2853        let room_id = room_id!("!test:localhost");
2854
2855        let matthew = user_id!("@sahasrhala:example.org");
2856        let me = user_id!("@me:example.org");
2857        let bot = user_id!("@bot:example.org");
2858
2859        let mut changes = StateChanges::new("".to_owned());
2860        let summary = assign!(RumaSummary::new(), {
2861            joined_member_count: Some(3u32.into()),
2862            heroes: vec![me.to_owned(), matthew.to_owned(), bot.to_owned()],
2863        });
2864
2865        let f = EventFactory::new().room(room_id!("!test:localhost"));
2866
2867        let members = changes
2868            .state
2869            .entry(room_id.to_owned())
2870            .or_default()
2871            .entry(StateEventType::RoomMember)
2872            .or_default();
2873        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
2874        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2875        members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
2876
2877        let member_hints_content =
2878            f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
2879        changes
2880            .state
2881            .entry(room_id.to_owned())
2882            .or_default()
2883            .entry(StateEventType::MemberHints)
2884            .or_default()
2885            .insert("".to_owned(), member_hints_content);
2886
2887        store.save_changes(&changes).await.unwrap();
2888
2889        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2890        // Bot should not contribute to the display name.
2891        assert_eq!(
2892            room.compute_display_name().await.unwrap(),
2893            RoomDisplayName::Calculated("Matthew".to_owned())
2894        );
2895    }
2896
2897    #[async_test]
2898    async fn test_display_name_dm_joined_alone_with_service_members() {
2899        let (store, room) = make_room_test_helper(RoomState::Joined);
2900        let room_id = room_id!("!test:localhost");
2901
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(2u32.into()),
2908            heroes: vec![me.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(me.into(), f.member(me).display_name("Me").into_raw());
2920        members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
2921
2922        let member_hints_content =
2923            f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
2924        changes
2925            .state
2926            .entry(room_id.to_owned())
2927            .or_default()
2928            .entry(StateEventType::MemberHints)
2929            .or_default()
2930            .insert("".to_owned(), member_hints_content);
2931
2932        store.save_changes(&changes).await.unwrap();
2933
2934        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2935        // Bot should not contribute to the display name.
2936        assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2937    }
2938
2939    #[async_test]
2940    async fn test_display_name_dm_joined_no_heroes() {
2941        let (store, room) = make_room_test_helper(RoomState::Joined);
2942        let room_id = room_id!("!test:localhost");
2943        let matthew = user_id!("@matthew:example.org");
2944        let me = user_id!("@me:example.org");
2945        let mut changes = StateChanges::new("".to_owned());
2946
2947        let f = EventFactory::new().room(room_id!("!test:localhost"));
2948
2949        let members = changes
2950            .state
2951            .entry(room_id.to_owned())
2952            .or_default()
2953            .entry(StateEventType::RoomMember)
2954            .or_default();
2955        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
2956        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2957
2958        store.save_changes(&changes).await.unwrap();
2959
2960        assert_eq!(
2961            room.compute_display_name().await.unwrap(),
2962            RoomDisplayName::Calculated("Matthew".to_owned())
2963        );
2964    }
2965
2966    #[async_test]
2967    async fn test_display_name_dm_joined_no_heroes_service_members() {
2968        let (store, room) = make_room_test_helper(RoomState::Joined);
2969        let room_id = room_id!("!test:localhost");
2970
2971        let matthew = user_id!("@matthew:example.org");
2972        let me = user_id!("@me:example.org");
2973        let bot = user_id!("@bot:example.org");
2974
2975        let mut changes = StateChanges::new("".to_owned());
2976
2977        let f = EventFactory::new().room(room_id!("!test:localhost"));
2978
2979        let members = changes
2980            .state
2981            .entry(room_id.to_owned())
2982            .or_default()
2983            .entry(StateEventType::RoomMember)
2984            .or_default();
2985        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
2986        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2987        members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
2988
2989        let member_hints_content =
2990            f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
2991        changes
2992            .state
2993            .entry(room_id.to_owned())
2994            .or_default()
2995            .entry(StateEventType::MemberHints)
2996            .or_default()
2997            .insert("".to_owned(), member_hints_content);
2998
2999        store.save_changes(&changes).await.unwrap();
3000
3001        assert_eq!(
3002            room.compute_display_name().await.unwrap(),
3003            RoomDisplayName::Calculated("Matthew".to_owned())
3004        );
3005    }
3006
3007    #[async_test]
3008    async fn test_display_name_deterministic() {
3009        let (store, room) = make_room_test_helper(RoomState::Joined);
3010
3011        let alice = user_id!("@alice:example.org");
3012        let bob = user_id!("@bob:example.org");
3013        let carol = user_id!("@carol:example.org");
3014        let denis = user_id!("@denis:example.org");
3015        let erica = user_id!("@erica:example.org");
3016        let fred = user_id!("@fred:example.org");
3017        let me = user_id!("@me:example.org");
3018
3019        let mut changes = StateChanges::new("".to_owned());
3020
3021        let f = EventFactory::new().room(room_id!("!test:localhost"));
3022
3023        // Save members in two batches, so that there's no implied ordering in the
3024        // store.
3025        {
3026            let members = changes
3027                .state
3028                .entry(room.room_id().to_owned())
3029                .or_default()
3030                .entry(StateEventType::RoomMember)
3031                .or_default();
3032            members.insert(carol.into(), f.member(carol).display_name("Carol").into_raw());
3033            members.insert(bob.into(), f.member(bob).display_name("Bob").into_raw());
3034            members.insert(fred.into(), f.member(fred).display_name("Fred").into_raw());
3035            members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3036            store.save_changes(&changes).await.unwrap();
3037        }
3038
3039        {
3040            let members = changes
3041                .state
3042                .entry(room.room_id().to_owned())
3043                .or_default()
3044                .entry(StateEventType::RoomMember)
3045                .or_default();
3046            members.insert(alice.into(), f.member(alice).display_name("Alice").into_raw());
3047            members.insert(erica.into(), f.member(erica).display_name("Erica").into_raw());
3048            members.insert(denis.into(), f.member(denis).display_name("Denis").into_raw());
3049            store.save_changes(&changes).await.unwrap();
3050        }
3051
3052        let summary = assign!(RumaSummary::new(), {
3053            joined_member_count: Some(7u32.into()),
3054            heroes: vec![denis.to_owned(), carol.to_owned(), bob.to_owned(), erica.to_owned()],
3055        });
3056        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
3057
3058        assert_eq!(
3059            room.compute_display_name().await.unwrap(),
3060            RoomDisplayName::Calculated("Bob, Carol, Denis, Erica, and 3 others".to_owned())
3061        );
3062    }
3063
3064    #[async_test]
3065    async fn test_display_name_deterministic_no_heroes() {
3066        let (store, room) = make_room_test_helper(RoomState::Joined);
3067
3068        let alice = user_id!("@alice:example.org");
3069        let bob = user_id!("@bob:example.org");
3070        let carol = user_id!("@carol:example.org");
3071        let denis = user_id!("@denis:example.org");
3072        let erica = user_id!("@erica:example.org");
3073        let fred = user_id!("@fred:example.org");
3074        let me = user_id!("@me:example.org");
3075
3076        let f = EventFactory::new().room(room_id!("!test:localhost"));
3077
3078        let mut changes = StateChanges::new("".to_owned());
3079
3080        // Save members in two batches, so that there's no implied ordering in the
3081        // store.
3082        {
3083            let members = changes
3084                .state
3085                .entry(room.room_id().to_owned())
3086                .or_default()
3087                .entry(StateEventType::RoomMember)
3088                .or_default();
3089            members.insert(carol.into(), f.member(carol).display_name("Carol").into_raw());
3090            members.insert(bob.into(), f.member(bob).display_name("Bob").into_raw());
3091            members.insert(fred.into(), f.member(fred).display_name("Fred").into_raw());
3092            members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3093
3094            store.save_changes(&changes).await.unwrap();
3095        }
3096
3097        {
3098            let members = changes
3099                .state
3100                .entry(room.room_id().to_owned())
3101                .or_default()
3102                .entry(StateEventType::RoomMember)
3103                .or_default();
3104            members.insert(alice.into(), f.member(alice).display_name("Alice").into_raw());
3105            members.insert(erica.into(), f.member(erica).display_name("Erica").into_raw());
3106            members.insert(denis.into(), f.member(denis).display_name("Denis").into_raw());
3107            store.save_changes(&changes).await.unwrap();
3108        }
3109
3110        assert_eq!(
3111            room.compute_display_name().await.unwrap(),
3112            RoomDisplayName::Calculated("Alice, Bob, Carol, Denis, Erica, and 2 others".to_owned())
3113        );
3114    }
3115
3116    #[async_test]
3117    async fn test_display_name_dm_alone() {
3118        let (store, room) = make_room_test_helper(RoomState::Joined);
3119        let room_id = room_id!("!test:localhost");
3120        let matthew = user_id!("@matthew:example.org");
3121        let me = user_id!("@me:example.org");
3122        let mut changes = StateChanges::new("".to_owned());
3123        let summary = assign!(RumaSummary::new(), {
3124            joined_member_count: Some(1u32.into()),
3125            heroes: vec![me.to_owned(), matthew.to_owned()],
3126        });
3127
3128        let f = EventFactory::new().room(room_id!("!test:localhost"));
3129
3130        let members = changes
3131            .state
3132            .entry(room_id.to_owned())
3133            .or_default()
3134            .entry(StateEventType::RoomMember)
3135            .or_default();
3136        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
3137        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3138
3139        store.save_changes(&changes).await.unwrap();
3140
3141        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
3142        assert_eq!(
3143            room.compute_display_name().await.unwrap(),
3144            RoomDisplayName::EmptyWas("Matthew".to_owned())
3145        );
3146    }
3147
3148    #[cfg(feature = "e2e-encryption")]
3149    #[async_test]
3150    async fn test_setting_the_latest_event_doesnt_cause_a_room_info_notable_update() {
3151        use std::collections::BTreeMap;
3152
3153        use assert_matches::assert_matches;
3154
3155        use crate::{RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons};
3156
3157        // Given a room,
3158        let client = BaseClient::with_store_config(StoreConfig::new(
3159            "cross-process-store-locks-holder-name".to_owned(),
3160        ));
3161
3162        client
3163            .set_session_meta(
3164                SessionMeta {
3165                    user_id: user_id!("@alice:example.org").into(),
3166                    device_id: ruma::device_id!("AYEAYEAYE").into(),
3167                },
3168                None,
3169            )
3170            .await
3171            .unwrap();
3172
3173        let room_id = room_id!("!test:localhost");
3174        let room = client.get_or_create_room(room_id, RoomState::Joined);
3175
3176        // That has an encrypted event,
3177        add_encrypted_event(&room, "$A");
3178        // Sanity: it has no latest_event
3179        assert!(room.latest_event().is_none());
3180
3181        // When I set up an observer on the latest_event,
3182        let mut room_info_notable_update = client.room_info_notable_update_receiver();
3183
3184        // And I provide a decrypted event to replace the encrypted one,
3185        let event = make_latest_event("$A");
3186
3187        let mut changes = StateChanges::default();
3188        let mut room_info_notable_updates = BTreeMap::new();
3189        room.on_latest_event_decrypted(
3190            event.clone(),
3191            0,
3192            &mut changes,
3193            &mut room_info_notable_updates,
3194        );
3195
3196        assert!(room_info_notable_updates.contains_key(room_id));
3197
3198        // The subscriber isn't notified at this point.
3199        assert!(room_info_notable_update.try_recv().is_err());
3200
3201        // Then updating the room info will store the event,
3202        client.apply_changes(&changes, room_info_notable_updates);
3203        assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
3204
3205        // And wake up the subscriber.
3206        assert_matches!(
3207            room_info_notable_update.recv().await,
3208            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
3209                assert_eq!(received_room_id, room_id);
3210                assert!(reasons.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
3211            }
3212        );
3213    }
3214
3215    #[cfg(feature = "e2e-encryption")]
3216    #[async_test]
3217    async fn test_when_we_provide_a_newly_decrypted_event_it_replaces_latest_event() {
3218        use std::collections::BTreeMap;
3219
3220        // Given a room with an encrypted event
3221        let (_store, room) = make_room_test_helper(RoomState::Joined);
3222        add_encrypted_event(&room, "$A");
3223        // Sanity: it has no latest_event
3224        assert!(room.latest_event().is_none());
3225
3226        // When I provide a decrypted event to replace the encrypted one
3227        let event = make_latest_event("$A");
3228        let mut changes = StateChanges::default();
3229        let mut room_info_notable_updates = BTreeMap::new();
3230        room.on_latest_event_decrypted(
3231            event.clone(),
3232            0,
3233            &mut changes,
3234            &mut room_info_notable_updates,
3235        );
3236        room.set_room_info(
3237            changes.room_infos.get(room.room_id()).cloned().unwrap(),
3238            room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3239        );
3240
3241        // Then is it stored
3242        assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
3243    }
3244
3245    #[cfg(feature = "e2e-encryption")]
3246    #[async_test]
3247    async fn test_when_a_newly_decrypted_event_appears_we_delete_all_older_encrypted_events() {
3248        use std::collections::BTreeMap;
3249
3250        // Given a room with some encrypted events and a latest event
3251        let (_store, room) = make_room_test_helper(RoomState::Joined);
3252        room.inner.update(|info| info.latest_event = Some(make_latest_event("$A")));
3253        add_encrypted_event(&room, "$0");
3254        add_encrypted_event(&room, "$1");
3255        add_encrypted_event(&room, "$2");
3256        add_encrypted_event(&room, "$3");
3257
3258        // When I provide a latest event
3259        let new_event = make_latest_event("$1");
3260        let new_event_index = 1;
3261        let mut changes = StateChanges::default();
3262        let mut room_info_notable_updates = BTreeMap::new();
3263        room.on_latest_event_decrypted(
3264            new_event.clone(),
3265            new_event_index,
3266            &mut changes,
3267            &mut room_info_notable_updates,
3268        );
3269        room.set_room_info(
3270            changes.room_infos.get(room.room_id()).cloned().unwrap(),
3271            room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3272        );
3273
3274        // Then the encrypted events list is shortened to only newer events
3275        let enc_evs = room.latest_encrypted_events();
3276        assert_eq!(enc_evs.len(), 2);
3277        assert_eq!(enc_evs[0].get_field::<&str>("event_id").unwrap().unwrap(), "$2");
3278        assert_eq!(enc_evs[1].get_field::<&str>("event_id").unwrap().unwrap(), "$3");
3279
3280        // And the event is stored
3281        assert_eq!(room.latest_event().unwrap().event_id(), new_event.event_id());
3282    }
3283
3284    #[cfg(feature = "e2e-encryption")]
3285    #[async_test]
3286    async fn test_replacing_the_newest_event_leaves_none_left() {
3287        use std::collections::BTreeMap;
3288
3289        // Given a room with some encrypted events
3290        let (_store, room) = make_room_test_helper(RoomState::Joined);
3291        add_encrypted_event(&room, "$0");
3292        add_encrypted_event(&room, "$1");
3293        add_encrypted_event(&room, "$2");
3294        add_encrypted_event(&room, "$3");
3295
3296        // When I provide a latest event and say it was the very latest
3297        let new_event = make_latest_event("$3");
3298        let new_event_index = 3;
3299        let mut changes = StateChanges::default();
3300        let mut room_info_notable_updates = BTreeMap::new();
3301        room.on_latest_event_decrypted(
3302            new_event,
3303            new_event_index,
3304            &mut changes,
3305            &mut room_info_notable_updates,
3306        );
3307        room.set_room_info(
3308            changes.room_infos.get(room.room_id()).cloned().unwrap(),
3309            room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3310        );
3311
3312        // Then the encrypted events list ie empty
3313        let enc_evs = room.latest_encrypted_events();
3314        assert_eq!(enc_evs.len(), 0);
3315    }
3316
3317    #[cfg(feature = "e2e-encryption")]
3318    fn add_encrypted_event(room: &Room, event_id: &str) {
3319        room.latest_encrypted_events
3320            .write()
3321            .unwrap()
3322            .push(Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap());
3323    }
3324
3325    #[cfg(feature = "e2e-encryption")]
3326    fn make_latest_event(event_id: &str) -> Box<LatestEvent> {
3327        Box::new(LatestEvent::new(TimelineEvent::new(
3328            Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap(),
3329        )))
3330    }
3331
3332    fn timestamp(minutes_ago: u32) -> MilliSecondsSinceUnixEpoch {
3333        MilliSecondsSinceUnixEpoch::from_system_time(
3334            SystemTime::now().sub(Duration::from_secs((60 * minutes_ago).into())),
3335        )
3336        .expect("date out of range")
3337    }
3338
3339    fn legacy_membership_for_my_call(
3340        device_id: &DeviceId,
3341        membership_id: &str,
3342        minutes_ago: u32,
3343    ) -> LegacyMembershipData {
3344        let (application, foci) = foci_and_application();
3345        assign!(
3346            LegacyMembershipData::from(LegacyMembershipDataInit {
3347                application,
3348                device_id: device_id.to_owned(),
3349                expires: Duration::from_millis(3_600_000),
3350                foci_active: foci,
3351                membership_id: membership_id.to_owned(),
3352            }),
3353            { created_ts: Some(timestamp(minutes_ago)) }
3354        )
3355    }
3356
3357    fn legacy_member_state_event(
3358        memberships: Vec<LegacyMembershipData>,
3359        ev_id: &EventId,
3360        user_id: &UserId,
3361    ) -> AnySyncStateEvent {
3362        let content = CallMemberEventContent::new_legacy(memberships);
3363
3364        AnySyncStateEvent::CallMember(SyncStateEvent::Original(OriginalSyncCallMemberEvent {
3365            content,
3366            event_id: ev_id.to_owned(),
3367            sender: user_id.to_owned(),
3368            // we can simply use now here since this will be dropped when using a MinimalStateEvent
3369            // in the roomInfo
3370            origin_server_ts: timestamp(0),
3371            state_key: CallMemberStateKey::new(user_id.to_owned(), None, false),
3372            unsigned: StateUnsigned::new(),
3373        }))
3374    }
3375
3376    struct InitData<'a> {
3377        device_id: &'a DeviceId,
3378        minutes_ago: u32,
3379    }
3380
3381    fn session_member_state_event(
3382        ev_id: &EventId,
3383        user_id: &UserId,
3384        init_data: Option<InitData<'_>>,
3385    ) -> AnySyncStateEvent {
3386        let application = Application::Call(CallApplicationContent::new(
3387            "my_call_id_1".to_owned(),
3388            ruma::events::call::member::CallScope::Room,
3389        ));
3390        let foci_preferred = vec![Focus::Livekit(LivekitFocus::new(
3391            "my_call_foci_alias".to_owned(),
3392            "https://lk.org".to_owned(),
3393        ))];
3394        let focus_active = ActiveFocus::Livekit(ActiveLivekitFocus::new());
3395        let (content, state_key) = match init_data {
3396            Some(InitData { device_id, minutes_ago }) => (
3397                CallMemberEventContent::new(
3398                    application,
3399                    device_id.to_owned(),
3400                    focus_active,
3401                    foci_preferred,
3402                    Some(timestamp(minutes_ago)),
3403                ),
3404                CallMemberStateKey::new(user_id.to_owned(), Some(device_id.to_owned()), false),
3405            ),
3406            None => (
3407                CallMemberEventContent::new_empty(None),
3408                CallMemberStateKey::new(user_id.to_owned(), None, false),
3409            ),
3410        };
3411
3412        AnySyncStateEvent::CallMember(SyncStateEvent::Original(OriginalSyncCallMemberEvent {
3413            content,
3414            event_id: ev_id.to_owned(),
3415            sender: user_id.to_owned(),
3416            // we can simply use now here since this will be dropped when using a MinimalStateEvent
3417            // in the roomInfo
3418            origin_server_ts: timestamp(0),
3419            state_key,
3420            unsigned: StateUnsigned::new(),
3421        }))
3422    }
3423
3424    fn foci_and_application() -> (Application, Vec<Focus>) {
3425        (
3426            Application::Call(CallApplicationContent::new(
3427                "my_call_id_1".to_owned(),
3428                ruma::events::call::member::CallScope::Room,
3429            )),
3430            vec![Focus::Livekit(LivekitFocus::new(
3431                "my_call_foci_alias".to_owned(),
3432                "https://lk.org".to_owned(),
3433            ))],
3434        )
3435    }
3436
3437    fn receive_state_events(room: &Room, events: Vec<&AnySyncStateEvent>) {
3438        room.inner.update_if(|info| {
3439            let mut res = false;
3440            for ev in events {
3441                res |= info.handle_state_event(ev);
3442            }
3443            res
3444        });
3445    }
3446
3447    /// `user_a`: empty memberships
3448    /// `user_b`: one membership
3449    /// `user_c`: two memberships (two devices)
3450    fn legacy_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
3451        let (_, room) = make_room_test_helper(RoomState::Joined);
3452
3453        let a_empty = legacy_member_state_event(Vec::new(), event_id!("$1234"), a);
3454
3455        // make b 10min old
3456        let m_init_b = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 1);
3457        let b_one = legacy_member_state_event(vec![m_init_b], event_id!("$12345"), b);
3458
3459        // c1 1min old
3460        let m_init_c1 = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 10);
3461        // c2 20min old
3462        let m_init_c2 = legacy_membership_for_my_call(device_id!("DEVICE_1"), "0", 20);
3463        let c_two = legacy_member_state_event(vec![m_init_c1, m_init_c2], event_id!("$123456"), c);
3464
3465        // Intentionally use a non time sorted receive order.
3466        receive_state_events(&room, vec![&c_two, &a_empty, &b_one]);
3467
3468        room
3469    }
3470
3471    /// `user_a`: empty memberships
3472    /// `user_b`: one membership
3473    /// `user_c`: two memberships (two devices)
3474    fn session_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
3475        let (_, room) = make_room_test_helper(RoomState::Joined);
3476
3477        let a_empty = session_member_state_event(event_id!("$1234"), a, None);
3478
3479        // make b 10min old
3480        let b_one = session_member_state_event(
3481            event_id!("$12345"),
3482            b,
3483            Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 1 }),
3484        );
3485
3486        let m_c1 = session_member_state_event(
3487            event_id!("$123456_0"),
3488            c,
3489            Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 10 }),
3490        );
3491        let m_c2 = session_member_state_event(
3492            event_id!("$123456_1"),
3493            c,
3494            Some(InitData { device_id: "DEVICE_1".into(), minutes_ago: 20 }),
3495        );
3496        // Intentionally use a non time sorted receive order1
3497        receive_state_events(&room, vec![&m_c1, &m_c2, &a_empty, &b_one]);
3498
3499        room
3500    }
3501
3502    #[test]
3503    fn test_show_correct_active_call_state() {
3504        let room_legacy = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3505
3506        // This check also tests the ordering.
3507        // We want older events to be in the front.
3508        // user_b (Bob) is 1min old, c1 (CAROL) 10min old, c2 (CAROL) 20min old
3509        assert_eq!(
3510            vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
3511            room_legacy.active_room_call_participants()
3512        );
3513        assert!(room_legacy.has_active_room_call());
3514
3515        let room_session = session_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3516        assert_eq!(
3517            vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
3518            room_session.active_room_call_participants()
3519        );
3520        assert!(room_session.has_active_room_call());
3521    }
3522
3523    #[test]
3524    fn test_active_call_is_false_when_everyone_left() {
3525        let room = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3526
3527        let b_empty_membership = legacy_member_state_event(Vec::new(), event_id!("$1234_1"), &BOB);
3528        let c_empty_membership =
3529            legacy_member_state_event(Vec::new(), event_id!("$12345_1"), &CAROL);
3530
3531        receive_state_events(&room, vec![&b_empty_membership, &c_empty_membership]);
3532
3533        // We have no active call anymore after emptying the memberships
3534        assert_eq!(Vec::<OwnedUserId>::new(), room.active_room_call_participants());
3535        assert!(!room.has_active_room_call());
3536    }
3537
3538    #[test]
3539    fn test_calculate_room_name() {
3540        let mut actual = compute_display_name_from_heroes(2, vec!["a"]);
3541        assert_eq!(RoomDisplayName::Calculated("a".to_owned()), actual);
3542
3543        actual = compute_display_name_from_heroes(3, vec!["a", "b"]);
3544        assert_eq!(RoomDisplayName::Calculated("a, b".to_owned()), actual);
3545
3546        actual = compute_display_name_from_heroes(4, vec!["a", "b", "c"]);
3547        assert_eq!(RoomDisplayName::Calculated("a, b, c".to_owned()), actual);
3548
3549        actual = compute_display_name_from_heroes(5, vec!["a", "b", "c"]);
3550        assert_eq!(RoomDisplayName::Calculated("a, b, c, and 2 others".to_owned()), actual);
3551
3552        actual = compute_display_name_from_heroes(5, vec![]);
3553        assert_eq!(RoomDisplayName::Calculated("5 people".to_owned()), actual);
3554
3555        actual = compute_display_name_from_heroes(0, vec![]);
3556        assert_eq!(RoomDisplayName::Empty, actual);
3557
3558        actual = compute_display_name_from_heroes(1, vec![]);
3559        assert_eq!(RoomDisplayName::Empty, actual);
3560
3561        actual = compute_display_name_from_heroes(1, vec!["a"]);
3562        assert_eq!(RoomDisplayName::EmptyWas("a".to_owned()), actual);
3563
3564        actual = compute_display_name_from_heroes(1, vec!["a", "b"]);
3565        assert_eq!(RoomDisplayName::EmptyWas("a, b".to_owned()), actual);
3566
3567        actual = compute_display_name_from_heroes(1, vec!["a", "b", "c"]);
3568        assert_eq!(RoomDisplayName::EmptyWas("a, b, c".to_owned()), actual);
3569    }
3570
3571    #[test]
3572    fn test_encryption_is_set_when_encryption_event_is_received() {
3573        let (_store, room) = make_room_test_helper(RoomState::Joined);
3574
3575        assert!(room.is_encryption_state_synced().not());
3576        assert!(room.is_encrypted().not());
3577
3578        let encryption_content =
3579            RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
3580        let encryption_event = AnySyncStateEvent::RoomEncryption(SyncStateEvent::Original(
3581            OriginalSyncRoomEncryptionEvent {
3582                content: encryption_content,
3583                event_id: OwnedEventId::from_str("$1234_1").unwrap(),
3584                sender: ALICE.to_owned(),
3585                // we can simply use now here since this will be dropped when using a
3586                // MinimalStateEvent in the roomInfo
3587                origin_server_ts: timestamp(0),
3588                state_key: EmptyStateKey,
3589                unsigned: StateUnsigned::new(),
3590            },
3591        ));
3592        receive_state_events(&room, vec![&encryption_event]);
3593
3594        assert!(room.is_encryption_state_synced());
3595        assert!(room.is_encrypted());
3596    }
3597
3598    #[async_test]
3599    async fn test_room_info_migration_v1() {
3600        let store = MemoryStore::new().into_state_store();
3601
3602        let room_info_json = json!({
3603            "room_id": "!gda78o:server.tld",
3604            "room_state": "Joined",
3605            "notification_counts": {
3606                "highlight_count": 1,
3607                "notification_count": 2,
3608            },
3609            "summary": {
3610                "room_heroes": [{
3611                    "user_id": "@somebody:example.org",
3612                    "display_name": null,
3613                    "avatar_url": null
3614                }],
3615                "joined_member_count": 5,
3616                "invited_member_count": 0,
3617            },
3618            "members_synced": true,
3619            "last_prev_batch": "pb",
3620            "sync_info": "FullySynced",
3621            "encryption_state_synced": true,
3622            "latest_event": {
3623                "event": {
3624                    "encryption_info": null,
3625                    "event": {
3626                        "sender": "@u:i.uk",
3627                    },
3628                },
3629            },
3630            "base_info": {
3631                "avatar": null,
3632                "canonical_alias": null,
3633                "create": null,
3634                "dm_targets": [],
3635                "encryption": null,
3636                "guest_access": null,
3637                "history_visibility": null,
3638                "join_rules": null,
3639                "max_power_level": 100,
3640                "name": null,
3641                "tombstone": null,
3642                "topic": null,
3643            },
3644            "read_receipts": {
3645                "num_unread": 0,
3646                "num_mentions": 0,
3647                "num_notifications": 0,
3648                "latest_active": null,
3649                "pending": []
3650            },
3651            "recency_stamp": 42,
3652        });
3653        let mut room_info: RoomInfo = serde_json::from_value(room_info_json).unwrap();
3654
3655        assert_eq!(room_info.version, 0);
3656        assert!(room_info.base_info.notable_tags.is_empty());
3657        assert!(room_info.base_info.pinned_events.is_none());
3658
3659        // Apply migrations with an empty store.
3660        assert!(room_info.apply_migrations(store.clone()).await);
3661
3662        assert_eq!(room_info.version, 1);
3663        assert!(room_info.base_info.notable_tags.is_empty());
3664        assert!(room_info.base_info.pinned_events.is_none());
3665
3666        // Applying migrations again has no effect.
3667        assert!(!room_info.apply_migrations(store.clone()).await);
3668
3669        assert_eq!(room_info.version, 1);
3670        assert!(room_info.base_info.notable_tags.is_empty());
3671        assert!(room_info.base_info.pinned_events.is_none());
3672
3673        // Add events to the store.
3674        let mut changes = StateChanges::default();
3675
3676        let raw_tag_event = Raw::new(&*TAG).unwrap().cast();
3677        let tag_event = raw_tag_event.deserialize().unwrap();
3678        changes.add_room_account_data(&room_info.room_id, tag_event, raw_tag_event);
3679
3680        let raw_pinned_events_event = Raw::new(&*PINNED_EVENTS).unwrap().cast();
3681        let pinned_events_event = raw_pinned_events_event.deserialize().unwrap();
3682        changes.add_state_event(&room_info.room_id, pinned_events_event, raw_pinned_events_event);
3683
3684        store.save_changes(&changes).await.unwrap();
3685
3686        // Reset to version 0 and reapply migrations.
3687        room_info.version = 0;
3688        assert!(room_info.apply_migrations(store.clone()).await);
3689
3690        assert_eq!(room_info.version, 1);
3691        assert!(room_info.base_info.notable_tags.contains(RoomNotableTags::FAVOURITE));
3692        assert!(room_info.base_info.pinned_events.is_some());
3693
3694        // Creating a new room info initializes it to version 1.
3695        let new_room_info = RoomInfo::new(room_id!("!new_room:localhost"), RoomState::Joined);
3696        assert_eq!(new_room_info.version, 1);
3697    }
3698
3699    #[async_test]
3700    async fn test_prev_room_state_is_updated() {
3701        let (_store, room) = make_room_test_helper(RoomState::Invited);
3702        assert_eq!(room.prev_state(), None);
3703        assert_eq!(room.state(), RoomState::Invited);
3704
3705        // Invited -> Joined
3706        let mut room_info = room.clone_info();
3707        room_info.mark_as_joined();
3708        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3709        assert_eq!(room.prev_state(), Some(RoomState::Invited));
3710        assert_eq!(room.state(), RoomState::Joined);
3711
3712        // No change when the same state is used
3713        let mut room_info = room.clone_info();
3714        room_info.mark_as_joined();
3715        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3716        assert_eq!(room.prev_state(), Some(RoomState::Invited));
3717        assert_eq!(room.state(), RoomState::Joined);
3718
3719        // Joined -> Left
3720        let mut room_info = room.clone_info();
3721        room_info.mark_as_left();
3722        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3723        assert_eq!(room.prev_state(), Some(RoomState::Joined));
3724        assert_eq!(room.state(), RoomState::Left);
3725
3726        // Left -> Banned
3727        let mut room_info = room.clone_info();
3728        room_info.mark_as_banned();
3729        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3730        assert_eq!(room.prev_state(), Some(RoomState::Left));
3731        assert_eq!(room.state(), RoomState::Banned);
3732    }
3733
3734    #[async_test]
3735    async fn test_room_state_filters() {
3736        let client = logged_in_base_client(None).await;
3737
3738        let joined_room_id = owned_room_id!("!joined:example.org");
3739        client.get_or_create_room(&joined_room_id, RoomState::Joined);
3740
3741        let invited_room_id = owned_room_id!("!invited:example.org");
3742        client.get_or_create_room(&invited_room_id, RoomState::Invited);
3743
3744        let left_room_id = owned_room_id!("!left:example.org");
3745        client.get_or_create_room(&left_room_id, RoomState::Left);
3746
3747        let knocked_room_id = owned_room_id!("!knocked:example.org");
3748        client.get_or_create_room(&knocked_room_id, RoomState::Knocked);
3749
3750        let banned_room_id = owned_room_id!("!banned:example.org");
3751        client.get_or_create_room(&banned_room_id, RoomState::Banned);
3752
3753        let joined_rooms = client.rooms_filtered(RoomStateFilter::JOINED);
3754        assert_eq!(joined_rooms.len(), 1);
3755        assert_eq!(joined_rooms[0].state(), RoomState::Joined);
3756        assert_eq!(joined_rooms[0].room_id, joined_room_id);
3757
3758        let invited_rooms = client.rooms_filtered(RoomStateFilter::INVITED);
3759        assert_eq!(invited_rooms.len(), 1);
3760        assert_eq!(invited_rooms[0].state(), RoomState::Invited);
3761        assert_eq!(invited_rooms[0].room_id, invited_room_id);
3762
3763        let left_rooms = client.rooms_filtered(RoomStateFilter::LEFT);
3764        assert_eq!(left_rooms.len(), 1);
3765        assert_eq!(left_rooms[0].state(), RoomState::Left);
3766        assert_eq!(left_rooms[0].room_id, left_room_id);
3767
3768        let knocked_rooms = client.rooms_filtered(RoomStateFilter::KNOCKED);
3769        assert_eq!(knocked_rooms.len(), 1);
3770        assert_eq!(knocked_rooms[0].state(), RoomState::Knocked);
3771        assert_eq!(knocked_rooms[0].room_id, knocked_room_id);
3772
3773        let banned_rooms = client.rooms_filtered(RoomStateFilter::BANNED);
3774        assert_eq!(banned_rooms.len(), 1);
3775        assert_eq!(banned_rooms[0].state(), RoomState::Banned);
3776        assert_eq!(banned_rooms[0].room_id, banned_room_id);
3777    }
3778
3779    #[test]
3780    fn test_room_state_filters_as_vec() {
3781        assert_eq!(RoomStateFilter::JOINED.as_vec(), vec![RoomState::Joined]);
3782        assert_eq!(RoomStateFilter::LEFT.as_vec(), vec![RoomState::Left]);
3783        assert_eq!(RoomStateFilter::INVITED.as_vec(), vec![RoomState::Invited]);
3784        assert_eq!(RoomStateFilter::KNOCKED.as_vec(), vec![RoomState::Knocked]);
3785        assert_eq!(RoomStateFilter::BANNED.as_vec(), vec![RoomState::Banned]);
3786
3787        // Check all filters are taken into account
3788        assert_eq!(
3789            RoomStateFilter::all().as_vec(),
3790            vec![
3791                RoomState::Joined,
3792                RoomState::Left,
3793                RoomState::Invited,
3794                RoomState::Knocked,
3795                RoomState::Banned
3796            ]
3797        );
3798    }
3799}