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;
27
28#[cfg(feature = "e2e-encryption")]
29use std::sync::RwLock as SyncRwLock;
30use std::{
31 collections::{BTreeMap, HashSet},
32 sync::Arc,
33};
34
35pub use create::*;
36pub use display_name::{RoomDisplayName, RoomHero};
37pub(crate) use display_name::{RoomSummary, UpdatedRoomDisplayName};
38pub use encryption::EncryptionState;
39use eyeball::{AsyncLock, SharedObservable};
40use futures_util::{Stream, StreamExt};
41#[cfg(feature = "e2e-encryption")]
42use matrix_sdk_common::ring_buffer::RingBuffer;
43pub use members::{RoomMember, RoomMembersUpdate, RoomMemberships};
44pub(crate) use room_info::SyncInfo;
45pub use room_info::{
46 apply_redaction, BaseRoomInfo, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
47};
48#[cfg(feature = "e2e-encryption")]
49use ruma::{events::AnySyncTimelineEvent, serde::Raw};
50use ruma::{
51 events::{
52 direct::OwnedDirectUserIdentifier,
53 receipt::{Receipt, ReceiptThread, ReceiptType},
54 room::{
55 avatar::{self},
56 guest_access::GuestAccess,
57 history_visibility::HistoryVisibility,
58 join_rules::JoinRule,
59 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
60 tombstone::RoomTombstoneEventContent,
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;
70use tracing::{info, instrument, warn};
71
72use crate::{
73 deserialized_responses::MemberEvent,
74 notification_settings::RoomNotificationMode,
75 read_receipts::RoomReadReceipts,
76 store::{DynStateStore, Result as StoreResult, StateStoreExt},
77 sync::UnreadNotificationsCount,
78 Error, MinimalStateEvent,
79};
80
81#[derive(Debug, Clone)]
84pub struct Room {
85 pub(super) room_id: OwnedRoomId,
87
88 pub(super) own_user_id: OwnedUserId,
90
91 pub(super) inner: SharedObservable<RoomInfo>,
92 pub(super) room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
93 pub(super) store: Arc<DynStateStore>,
94
95 #[cfg(feature = "e2e-encryption")]
105 pub latest_encrypted_events: Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>,
106
107 pub seen_knock_request_ids_map:
111 SharedObservable<Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
112
113 pub room_member_updates_sender: broadcast::Sender<RoomMembersUpdate>,
115}
116
117impl Room {
118 pub(crate) fn new(
119 own_user_id: &UserId,
120 store: Arc<DynStateStore>,
121 room_id: &RoomId,
122 room_state: RoomState,
123 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
124 ) -> Self {
125 let room_info = RoomInfo::new(room_id, room_state);
126 Self::restore(own_user_id, store, room_info, room_info_notable_update_sender)
127 }
128
129 pub(crate) fn restore(
130 own_user_id: &UserId,
131 store: Arc<DynStateStore>,
132 room_info: RoomInfo,
133 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
134 ) -> Self {
135 let (room_member_updates_sender, _) = broadcast::channel(10);
136 Self {
137 own_user_id: own_user_id.into(),
138 room_id: room_info.room_id.clone(),
139 store,
140 inner: SharedObservable::new(room_info),
141 #[cfg(feature = "e2e-encryption")]
142 latest_encrypted_events: Arc::new(SyncRwLock::new(RingBuffer::new(
143 Self::MAX_ENCRYPTED_EVENTS,
144 ))),
145 room_info_notable_update_sender,
146 seen_knock_request_ids_map: SharedObservable::new_async(None),
147 room_member_updates_sender,
148 }
149 }
150
151 pub fn room_id(&self) -> &RoomId {
153 &self.room_id
154 }
155
156 pub fn creator(&self) -> Option<OwnedUserId> {
158 self.inner.read().creator().map(ToOwned::to_owned)
159 }
160
161 pub fn own_user_id(&self) -> &UserId {
163 &self.own_user_id
164 }
165
166 pub fn is_space(&self) -> bool {
168 self.inner.read().room_type().is_some_and(|t| *t == RoomType::Space)
169 }
170
171 pub fn room_type(&self) -> Option<RoomType> {
174 self.inner.read().room_type().map(ToOwned::to_owned)
175 }
176
177 pub fn unread_notification_counts(&self) -> UnreadNotificationsCount {
179 self.inner.read().notification_counts
180 }
181
182 pub fn num_unread_messages(&self) -> u64 {
187 self.inner.read().read_receipts.num_unread
188 }
189
190 pub fn read_receipts(&self) -> RoomReadReceipts {
192 self.inner.read().read_receipts.clone()
193 }
194
195 pub fn num_unread_notifications(&self) -> u64 {
200 self.inner.read().read_receipts.num_notifications
201 }
202
203 pub fn num_unread_mentions(&self) -> u64 {
209 self.inner.read().read_receipts.num_mentions
210 }
211
212 pub fn is_state_fully_synced(&self) -> bool {
220 self.inner.read().sync_info == SyncInfo::FullySynced
221 }
222
223 pub fn is_state_partially_or_fully_synced(&self) -> bool {
227 self.inner.read().sync_info != SyncInfo::NoState
228 }
229
230 pub fn last_prev_batch(&self) -> Option<String> {
233 self.inner.read().last_prev_batch.clone()
234 }
235
236 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
238 self.inner.read().avatar_url().map(ToOwned::to_owned)
239 }
240
241 pub fn avatar_info(&self) -> Option<avatar::ImageInfo> {
243 self.inner.read().avatar_info().map(ToOwned::to_owned)
244 }
245
246 pub fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
248 self.inner.read().canonical_alias().map(ToOwned::to_owned)
249 }
250
251 pub fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
253 self.inner.read().alt_aliases().to_owned()
254 }
255
256 pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
266 match self.inner.read().base_info.create.as_ref()? {
267 MinimalStateEvent::Original(ev) => Some(ev.content.clone()),
268 MinimalStateEvent::Redacted(ev) => Some(ev.content.clone()),
269 }
270 }
271
272 #[instrument(skip_all, fields(room_id = ?self.room_id))]
276 pub async fn is_direct(&self) -> StoreResult<bool> {
277 match self.state() {
278 RoomState::Joined | RoomState::Left | RoomState::Banned => {
279 Ok(!self.inner.read().base_info.dm_targets.is_empty())
280 }
281
282 RoomState::Invited => {
283 let member = self.get_member(self.own_user_id()).await?;
284
285 match member {
286 None => {
287 info!("RoomMember not found for the user's own id");
288 Ok(false)
289 }
290 Some(member) => match member.event.as_ref() {
291 MemberEvent::Sync(_) => {
292 warn!("Got MemberEvent::Sync in an invited room");
293 Ok(false)
294 }
295 MemberEvent::Stripped(event) => {
296 Ok(event.content.is_direct.unwrap_or(false))
297 }
298 },
299 }
300 }
301
302 RoomState::Knocked => Ok(false),
304 }
305 }
306
307 pub fn direct_targets(&self) -> HashSet<OwnedDirectUserIdentifier> {
316 self.inner.read().base_info.dm_targets.clone()
317 }
318
319 pub fn direct_targets_length(&self) -> usize {
322 self.inner.read().base_info.dm_targets.len()
323 }
324
325 pub fn guest_access(&self) -> GuestAccess {
327 self.inner.read().guest_access().clone()
328 }
329
330 pub fn history_visibility(&self) -> Option<HistoryVisibility> {
332 self.inner.read().history_visibility().cloned()
333 }
334
335 pub fn history_visibility_or_default(&self) -> HistoryVisibility {
338 self.inner.read().history_visibility_or_default().clone()
339 }
340
341 pub fn is_public(&self) -> bool {
343 matches!(self.join_rule(), JoinRule::Public)
344 }
345
346 pub fn join_rule(&self) -> JoinRule {
348 self.inner.read().join_rule().clone()
349 }
350
351 pub fn max_power_level(&self) -> i64 {
356 self.inner.read().base_info.max_power_level
357 }
358
359 pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
361 Ok(self
362 .store
363 .get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
364 .await?
365 .ok_or(Error::InsufficientData)?
366 .deserialize()?
367 .power_levels())
368 }
369
370 pub fn name(&self) -> Option<String> {
375 self.inner.read().name().map(ToOwned::to_owned)
376 }
377
378 pub fn is_tombstoned(&self) -> bool {
380 self.inner.read().base_info.tombstone.is_some()
381 }
382
383 pub fn tombstone(&self) -> Option<RoomTombstoneEventContent> {
385 self.inner.read().tombstone().cloned()
386 }
387
388 pub fn topic(&self) -> Option<String> {
390 self.inner.read().topic().map(ToOwned::to_owned)
391 }
392
393 pub fn update_cached_user_defined_notification_mode(&self, mode: RoomNotificationMode) {
399 self.inner.update_if(|info| {
400 if info.cached_user_defined_notification_mode.as_ref() != Some(&mode) {
401 info.cached_user_defined_notification_mode = Some(mode);
402
403 true
404 } else {
405 false
406 }
407 });
408 }
409
410 pub fn cached_user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
415 self.inner.read().cached_user_defined_notification_mode
416 }
417
418 pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
421 self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
422 }
423
424 pub fn heroes(&self) -> Vec<RoomHero> {
426 self.inner.read().heroes().to_vec()
427 }
428
429 pub async fn load_user_receipt(
432 &self,
433 receipt_type: ReceiptType,
434 thread: ReceiptThread,
435 user_id: &UserId,
436 ) -> StoreResult<Option<(OwnedEventId, Receipt)>> {
437 self.store.get_user_room_receipt_event(self.room_id(), receipt_type, thread, user_id).await
438 }
439
440 pub async fn load_event_receipts(
444 &self,
445 receipt_type: ReceiptType,
446 thread: ReceiptThread,
447 event_id: &EventId,
448 ) -> StoreResult<Vec<(OwnedUserId, Receipt)>> {
449 self.store
450 .get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
451 .await
452 }
453
454 pub fn is_marked_unread(&self) -> bool {
457 self.inner.read().base_info.is_marked_unread
458 }
459
460 pub fn recency_stamp(&self) -> Option<u64> {
464 self.inner.read().recency_stamp
465 }
466
467 pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> {
470 self.inner
471 .subscribe()
472 .map(|i| i.base_info.pinned_events.map(|c| c.pinned).unwrap_or_default())
473 }
474
475 pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
477 self.inner.read().pinned_event_ids()
478 }
479}
480
481#[cfg(not(feature = "test-send-sync"))]
483unsafe impl Send for Room {}
484
485#[cfg(not(feature = "test-send-sync"))]
487unsafe impl Sync for Room {}
488
489#[cfg(feature = "test-send-sync")]
490#[test]
491fn test_send_sync_for_room() {
493 fn assert_send_sync<T: Send + Sync>() {}
494
495 assert_send_sync::<Room>();
496}
497
498#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
500pub(crate) enum AccountDataSource {
501 Stable,
503
504 #[default]
506 Unstable,
507}