1#![allow(clippy::assign_op_pattern)] mod call;
18mod create;
19mod display_name;
20mod encryption;
21mod knock;
22mod latest_event;
23mod members;
24mod room_info;
25mod state;
26mod tags;
27mod tombstone;
28
29#[cfg(feature = "e2e-encryption")]
30use std::sync::RwLock as SyncRwLock;
31use std::{
32 collections::{BTreeMap, HashSet},
33 sync::Arc,
34};
35
36pub use create::*;
37pub use display_name::{RoomDisplayName, RoomHero};
38pub(crate) use display_name::{RoomSummary, UpdatedRoomDisplayName};
39pub use encryption::EncryptionState;
40use eyeball::{AsyncLock, SharedObservable};
41use futures_util::{Stream, StreamExt};
42#[cfg(feature = "e2e-encryption")]
43use matrix_sdk_common::ring_buffer::RingBuffer;
44pub use members::{RoomMember, RoomMembersUpdate, RoomMemberships};
45pub(crate) use room_info::SyncInfo;
46pub use room_info::{
47 apply_redaction, BaseRoomInfo, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
48};
49#[cfg(feature = "e2e-encryption")]
50use ruma::{events::AnySyncTimelineEvent, serde::Raw};
51use ruma::{
52 events::{
53 direct::OwnedDirectUserIdentifier,
54 receipt::{Receipt, ReceiptThread, ReceiptType},
55 room::{
56 avatar,
57 guest_access::GuestAccess,
58 history_visibility::HistoryVisibility,
59 join_rules::JoinRule,
60 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
61 },
62 },
63 room::RoomType,
64 EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, UserId,
65};
66use serde::{Deserialize, Serialize};
67pub use state::{RoomState, RoomStateFilter};
68pub(crate) use tags::RoomNotableTags;
69use tokio::sync::broadcast;
70pub use tombstone::{PredecessorRoom, SuccessorRoom};
71use tracing::{info, instrument, warn};
72
73use crate::{
74 deserialized_responses::MemberEvent,
75 notification_settings::RoomNotificationMode,
76 read_receipts::RoomReadReceipts,
77 store::{DynStateStore, Result as StoreResult, StateStoreExt},
78 sync::UnreadNotificationsCount,
79 Error, MinimalStateEvent,
80};
81
82#[derive(Debug, Clone)]
85pub struct Room {
86 pub(super) room_id: OwnedRoomId,
88
89 pub(super) own_user_id: OwnedUserId,
91
92 pub(super) inner: SharedObservable<RoomInfo>,
93 pub(super) room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
94 pub(super) store: Arc<DynStateStore>,
95
96 #[cfg(feature = "e2e-encryption")]
106 pub latest_encrypted_events: Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>,
107
108 pub seen_knock_request_ids_map:
112 SharedObservable<Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
113
114 pub room_member_updates_sender: broadcast::Sender<RoomMembersUpdate>,
116}
117
118impl Room {
119 pub(crate) fn new(
120 own_user_id: &UserId,
121 store: Arc<DynStateStore>,
122 room_id: &RoomId,
123 room_state: RoomState,
124 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
125 ) -> Self {
126 let room_info = RoomInfo::new(room_id, room_state);
127 Self::restore(own_user_id, store, room_info, room_info_notable_update_sender)
128 }
129
130 pub(crate) fn restore(
131 own_user_id: &UserId,
132 store: Arc<DynStateStore>,
133 room_info: RoomInfo,
134 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
135 ) -> Self {
136 let (room_member_updates_sender, _) = broadcast::channel(10);
137 Self {
138 own_user_id: own_user_id.into(),
139 room_id: room_info.room_id.clone(),
140 store,
141 inner: SharedObservable::new(room_info),
142 #[cfg(feature = "e2e-encryption")]
143 latest_encrypted_events: Arc::new(SyncRwLock::new(RingBuffer::new(
144 Self::MAX_ENCRYPTED_EVENTS,
145 ))),
146 room_info_notable_update_sender,
147 seen_knock_request_ids_map: SharedObservable::new_async(None),
148 room_member_updates_sender,
149 }
150 }
151
152 pub fn room_id(&self) -> &RoomId {
154 &self.room_id
155 }
156
157 pub fn creator(&self) -> Option<OwnedUserId> {
159 self.inner.read().creator().map(ToOwned::to_owned)
160 }
161
162 pub fn own_user_id(&self) -> &UserId {
164 &self.own_user_id
165 }
166
167 pub fn is_space(&self) -> bool {
169 self.inner.read().room_type().is_some_and(|t| *t == RoomType::Space)
170 }
171
172 pub fn room_type(&self) -> Option<RoomType> {
175 self.inner.read().room_type().map(ToOwned::to_owned)
176 }
177
178 pub fn unread_notification_counts(&self) -> UnreadNotificationsCount {
180 self.inner.read().notification_counts
181 }
182
183 pub fn num_unread_messages(&self) -> u64 {
188 self.inner.read().read_receipts.num_unread
189 }
190
191 pub fn read_receipts(&self) -> RoomReadReceipts {
193 self.inner.read().read_receipts.clone()
194 }
195
196 pub fn num_unread_notifications(&self) -> u64 {
201 self.inner.read().read_receipts.num_notifications
202 }
203
204 pub fn num_unread_mentions(&self) -> u64 {
210 self.inner.read().read_receipts.num_mentions
211 }
212
213 pub fn is_state_fully_synced(&self) -> bool {
221 self.inner.read().sync_info == SyncInfo::FullySynced
222 }
223
224 pub fn is_state_partially_or_fully_synced(&self) -> bool {
228 self.inner.read().sync_info != SyncInfo::NoState
229 }
230
231 pub fn last_prev_batch(&self) -> Option<String> {
234 self.inner.read().last_prev_batch.clone()
235 }
236
237 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
239 self.inner.read().avatar_url().map(ToOwned::to_owned)
240 }
241
242 pub fn avatar_info(&self) -> Option<avatar::ImageInfo> {
244 self.inner.read().avatar_info().map(ToOwned::to_owned)
245 }
246
247 pub fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
249 self.inner.read().canonical_alias().map(ToOwned::to_owned)
250 }
251
252 pub fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
254 self.inner.read().alt_aliases().to_owned()
255 }
256
257 pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
267 match self.inner.read().base_info.create.as_ref()? {
268 MinimalStateEvent::Original(ev) => Some(ev.content.clone()),
269 MinimalStateEvent::Redacted(ev) => Some(ev.content.clone()),
270 }
271 }
272
273 #[instrument(skip_all, fields(room_id = ?self.room_id))]
277 pub async fn is_direct(&self) -> StoreResult<bool> {
278 match self.state() {
279 RoomState::Joined | RoomState::Left | RoomState::Banned => {
280 Ok(!self.inner.read().base_info.dm_targets.is_empty())
281 }
282
283 RoomState::Invited => {
284 let member = self.get_member(self.own_user_id()).await?;
285
286 match member {
287 None => {
288 info!("RoomMember not found for the user's own id");
289 Ok(false)
290 }
291 Some(member) => match member.event.as_ref() {
292 MemberEvent::Sync(_) => {
293 warn!("Got MemberEvent::Sync in an invited room");
294 Ok(false)
295 }
296 MemberEvent::Stripped(event) => {
297 Ok(event.content.is_direct.unwrap_or(false))
298 }
299 },
300 }
301 }
302
303 RoomState::Knocked => Ok(false),
305 }
306 }
307
308 pub fn direct_targets(&self) -> HashSet<OwnedDirectUserIdentifier> {
317 self.inner.read().base_info.dm_targets.clone()
318 }
319
320 pub fn direct_targets_length(&self) -> usize {
323 self.inner.read().base_info.dm_targets.len()
324 }
325
326 pub fn guest_access(&self) -> GuestAccess {
328 self.inner.read().guest_access().clone()
329 }
330
331 pub fn history_visibility(&self) -> Option<HistoryVisibility> {
333 self.inner.read().history_visibility().cloned()
334 }
335
336 pub fn history_visibility_or_default(&self) -> HistoryVisibility {
339 self.inner.read().history_visibility_or_default().clone()
340 }
341
342 pub fn is_public(&self) -> Option<bool> {
346 self.inner.read().join_rule().map(|join_rule| matches!(join_rule, JoinRule::Public))
347 }
348
349 pub fn join_rule(&self) -> Option<JoinRule> {
351 self.inner.read().join_rule().cloned()
352 }
353
354 pub fn max_power_level(&self) -> i64 {
359 self.inner.read().base_info.max_power_level
360 }
361
362 pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
364 Ok(self
365 .store
366 .get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
367 .await?
368 .ok_or(Error::InsufficientData)?
369 .deserialize()?
370 .power_levels())
371 }
372
373 pub fn name(&self) -> Option<String> {
378 self.inner.read().name().map(ToOwned::to_owned)
379 }
380
381 pub fn topic(&self) -> Option<String> {
383 self.inner.read().topic().map(ToOwned::to_owned)
384 }
385
386 pub fn update_cached_user_defined_notification_mode(&self, mode: RoomNotificationMode) {
392 self.inner.update_if(|info| {
393 if info.cached_user_defined_notification_mode.as_ref() != Some(&mode) {
394 info.cached_user_defined_notification_mode = Some(mode);
395
396 true
397 } else {
398 false
399 }
400 });
401 }
402
403 pub fn cached_user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
408 self.inner.read().cached_user_defined_notification_mode
409 }
410
411 pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
414 self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
415 }
416
417 pub fn heroes(&self) -> Vec<RoomHero> {
419 self.inner.read().heroes().to_vec()
420 }
421
422 pub async fn load_user_receipt(
425 &self,
426 receipt_type: ReceiptType,
427 thread: ReceiptThread,
428 user_id: &UserId,
429 ) -> StoreResult<Option<(OwnedEventId, Receipt)>> {
430 self.store.get_user_room_receipt_event(self.room_id(), receipt_type, thread, user_id).await
431 }
432
433 pub async fn load_event_receipts(
437 &self,
438 receipt_type: ReceiptType,
439 thread: ReceiptThread,
440 event_id: &EventId,
441 ) -> StoreResult<Vec<(OwnedUserId, Receipt)>> {
442 self.store
443 .get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
444 .await
445 }
446
447 pub fn is_marked_unread(&self) -> bool {
450 self.inner.read().base_info.is_marked_unread
451 }
452
453 pub fn recency_stamp(&self) -> Option<u64> {
457 self.inner.read().recency_stamp
458 }
459
460 pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> {
463 self.inner
464 .subscribe()
465 .map(|i| i.base_info.pinned_events.map(|c| c.pinned).unwrap_or_default())
466 }
467
468 pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
470 self.inner.read().pinned_event_ids()
471 }
472}
473
474#[cfg(not(feature = "test-send-sync"))]
476unsafe impl Send for Room {}
477
478#[cfg(not(feature = "test-send-sync"))]
480unsafe impl Sync for Room {}
481
482#[cfg(feature = "test-send-sync")]
483#[test]
484fn test_send_sync_for_room() {
486 fn assert_send_sync<
487 T: matrix_sdk_common::SendOutsideWasm + matrix_sdk_common::SyncOutsideWasm,
488 >() {
489 }
490
491 assert_send_sync::<Room>();
492}
493
494#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
496pub(crate) enum AccountDataSource {
497 Stable,
499
500 #[default]
502 Unstable,
503}