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