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 BaseRoomInfo, InviteAcceptanceDetails, RoomInfo, RoomInfoNotableUpdate,
48 RoomInfoNotableUpdateReasons, apply_redaction,
49};
50use ruma::{
51 EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId,
52 RoomVersionId, UserId,
53 events::{
54 direct::OwnedDirectUserIdentifier,
55 receipt::{Receipt, ReceiptThread, ReceiptType},
56 room::{
57 avatar,
58 guest_access::GuestAccess,
59 history_visibility::HistoryVisibility,
60 join_rules::JoinRule,
61 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource},
62 },
63 },
64 room::RoomType,
65};
66#[cfg(feature = "e2e-encryption")]
67use ruma::{events::AnySyncTimelineEvent, serde::Raw};
68use serde::{Deserialize, Serialize};
69pub use state::{RoomState, RoomStateFilter};
70pub(crate) use tags::RoomNotableTags;
71use tokio::sync::broadcast;
72pub use tombstone::{PredecessorRoom, SuccessorRoom};
73use tracing::{info, instrument, warn};
74
75use crate::{
76 Error, MinimalStateEvent,
77 deserialized_responses::MemberEvent,
78 notification_settings::RoomNotificationMode,
79 read_receipts::RoomReadReceipts,
80 store::{DynStateStore, Result as StoreResult, StateStoreExt},
81 sync::UnreadNotificationsCount,
82};
83
84#[derive(Debug, Clone)]
87pub struct Room {
88 pub(super) room_id: OwnedRoomId,
90
91 pub(super) own_user_id: OwnedUserId,
93
94 pub(super) inner: SharedObservable<RoomInfo>,
95 pub(super) room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
96 pub(super) store: Arc<DynStateStore>,
97
98 #[cfg(feature = "e2e-encryption")]
108 pub latest_encrypted_events: Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>,
109
110 pub seen_knock_request_ids_map:
114 SharedObservable<Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
115
116 pub room_member_updates_sender: broadcast::Sender<RoomMembersUpdate>,
118}
119
120impl Room {
121 pub(crate) fn new(
122 own_user_id: &UserId,
123 store: Arc<DynStateStore>,
124 room_id: &RoomId,
125 room_state: RoomState,
126 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
127 ) -> Self {
128 let room_info = RoomInfo::new(room_id, room_state);
129 Self::restore(own_user_id, store, room_info, room_info_notable_update_sender)
130 }
131
132 pub(crate) fn restore(
133 own_user_id: &UserId,
134 store: Arc<DynStateStore>,
135 room_info: RoomInfo,
136 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
137 ) -> Self {
138 let (room_member_updates_sender, _) = broadcast::channel(10);
139 Self {
140 own_user_id: own_user_id.into(),
141 room_id: room_info.room_id.clone(),
142 store,
143 inner: SharedObservable::new(room_info),
144 #[cfg(feature = "e2e-encryption")]
145 latest_encrypted_events: Arc::new(SyncRwLock::new(RingBuffer::new(
146 Self::MAX_ENCRYPTED_EVENTS,
147 ))),
148 room_info_notable_update_sender,
149 seen_knock_request_ids_map: SharedObservable::new_async(None),
150 room_member_updates_sender,
151 }
152 }
153
154 pub fn room_id(&self) -> &RoomId {
156 &self.room_id
157 }
158
159 pub fn creators(&self) -> Option<Vec<OwnedUserId>> {
161 self.inner.read().creators()
162 }
163
164 pub fn own_user_id(&self) -> &UserId {
166 &self.own_user_id
167 }
168
169 pub fn is_space(&self) -> bool {
171 self.inner.read().room_type().is_some_and(|t| *t == RoomType::Space)
172 }
173
174 pub fn room_type(&self) -> Option<RoomType> {
177 self.inner.read().room_type().map(ToOwned::to_owned)
178 }
179
180 pub fn unread_notification_counts(&self) -> UnreadNotificationsCount {
182 self.inner.read().notification_counts
183 }
184
185 pub fn num_unread_messages(&self) -> u64 {
190 self.inner.read().read_receipts.num_unread
191 }
192
193 pub fn read_receipts(&self) -> RoomReadReceipts {
195 self.inner.read().read_receipts.clone()
196 }
197
198 pub fn num_unread_notifications(&self) -> u64 {
203 self.inner.read().read_receipts.num_notifications
204 }
205
206 pub fn num_unread_mentions(&self) -> u64 {
212 self.inner.read().read_receipts.num_mentions
213 }
214
215 pub fn is_state_fully_synced(&self) -> bool {
223 self.inner.read().sync_info == SyncInfo::FullySynced
224 }
225
226 pub fn is_state_partially_or_fully_synced(&self) -> bool {
230 self.inner.read().sync_info != SyncInfo::NoState
231 }
232
233 pub fn last_prev_batch(&self) -> Option<String> {
236 self.inner.read().last_prev_batch.clone()
237 }
238
239 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
241 self.inner.read().avatar_url().map(ToOwned::to_owned)
242 }
243
244 pub fn avatar_info(&self) -> Option<avatar::ImageInfo> {
246 self.inner.read().avatar_info().map(ToOwned::to_owned)
247 }
248
249 pub fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
251 self.inner.read().canonical_alias().map(ToOwned::to_owned)
252 }
253
254 pub fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
256 self.inner.read().alt_aliases().to_owned()
257 }
258
259 pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
269 match self.inner.read().base_info.create.as_ref()? {
270 MinimalStateEvent::Original(ev) => Some(ev.content.clone()),
271 MinimalStateEvent::Redacted(ev) => Some(ev.content.clone()),
272 }
273 }
274
275 #[instrument(skip_all, fields(room_id = ?self.room_id))]
279 pub async fn is_direct(&self) -> StoreResult<bool> {
280 match self.state() {
281 RoomState::Joined | RoomState::Left | RoomState::Banned => {
282 Ok(!self.inner.read().base_info.dm_targets.is_empty())
283 }
284
285 RoomState::Invited => {
286 let member = self.get_member(self.own_user_id()).await?;
287
288 match member {
289 None => {
290 info!("RoomMember not found for the user's own id");
291 Ok(false)
292 }
293 Some(member) => match member.event.as_ref() {
294 MemberEvent::Sync(_) => {
295 warn!("Got MemberEvent::Sync in an invited room");
296 Ok(false)
297 }
298 MemberEvent::Stripped(event) => {
299 Ok(event.content.is_direct.unwrap_or(false))
300 }
301 },
302 }
303 }
304
305 RoomState::Knocked => Ok(false),
307 }
308 }
309
310 pub fn direct_targets(&self) -> HashSet<OwnedDirectUserIdentifier> {
319 self.inner.read().base_info.dm_targets.clone()
320 }
321
322 pub fn direct_targets_length(&self) -> usize {
325 self.inner.read().base_info.dm_targets.len()
326 }
327
328 pub fn guest_access(&self) -> GuestAccess {
330 self.inner.read().guest_access().clone()
331 }
332
333 pub fn history_visibility(&self) -> Option<HistoryVisibility> {
335 self.inner.read().history_visibility().cloned()
336 }
337
338 pub fn history_visibility_or_default(&self) -> HistoryVisibility {
341 self.inner.read().history_visibility_or_default().clone()
342 }
343
344 pub fn is_public(&self) -> Option<bool> {
348 self.inner.read().join_rule().map(|join_rule| matches!(join_rule, JoinRule::Public))
349 }
350
351 pub fn join_rule(&self) -> Option<JoinRule> {
353 self.inner.read().join_rule().cloned()
354 }
355
356 pub fn max_power_level(&self) -> i64 {
361 self.inner.read().base_info.max_power_level
362 }
363
364 pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
366 let power_levels_content = self
367 .store
368 .get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
369 .await?
370 .ok_or(Error::InsufficientData)?
371 .deserialize()?;
372 let creators = self.creators().ok_or(Error::InsufficientData)?;
373 let rules = self.inner.read().room_version_rules_or_default();
374
375 Ok(power_levels_content.power_levels(&rules.authorization, creators))
376 }
377
378 pub async fn power_levels_or_default(&self) -> RoomPowerLevels {
381 if let Ok(power_levels) = self.power_levels().await {
382 return power_levels;
383 }
384
385 let rules = self.inner.read().room_version_rules_or_default();
387 RoomPowerLevels::new(
388 RoomPowerLevelsSource::None,
389 &rules.authorization,
390 self.creators().into_iter().flatten(),
391 )
392 }
393
394 pub fn name(&self) -> Option<String> {
399 self.inner.read().name().map(ToOwned::to_owned)
400 }
401
402 pub fn topic(&self) -> Option<String> {
404 self.inner.read().topic().map(ToOwned::to_owned)
405 }
406
407 pub fn update_cached_user_defined_notification_mode(&self, mode: RoomNotificationMode) {
413 self.inner.update_if(|info| {
414 if info.cached_user_defined_notification_mode.as_ref() != Some(&mode) {
415 info.cached_user_defined_notification_mode = Some(mode);
416
417 true
418 } else {
419 false
420 }
421 });
422 }
423
424 pub fn cached_user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
429 self.inner.read().cached_user_defined_notification_mode
430 }
431
432 pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
435 self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
436 }
437
438 pub fn heroes(&self) -> Vec<RoomHero> {
440 self.inner.read().heroes().to_vec()
441 }
442
443 pub async fn load_user_receipt(
446 &self,
447 receipt_type: ReceiptType,
448 thread: ReceiptThread,
449 user_id: &UserId,
450 ) -> StoreResult<Option<(OwnedEventId, Receipt)>> {
451 self.store.get_user_room_receipt_event(self.room_id(), receipt_type, thread, user_id).await
452 }
453
454 pub async fn load_event_receipts(
458 &self,
459 receipt_type: ReceiptType,
460 thread: ReceiptThread,
461 event_id: &EventId,
462 ) -> StoreResult<Vec<(OwnedUserId, Receipt)>> {
463 self.store
464 .get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
465 .await
466 }
467
468 pub fn is_marked_unread(&self) -> bool {
471 self.inner.read().base_info.is_marked_unread
472 }
473
474 pub fn version(&self) -> Option<RoomVersionId> {
476 self.inner.read().room_version().cloned()
477 }
478
479 pub fn recency_stamp(&self) -> Option<u64> {
483 self.inner.read().recency_stamp
484 }
485
486 pub fn invite_acceptance_details(&self) -> Option<InviteAcceptanceDetails> {
494 self.inner.read().invite_acceptance_details.clone()
495 }
496
497 pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> + use<> {
500 self.inner
501 .subscribe()
502 .map(|i| i.base_info.pinned_events.map(|c| c.pinned).unwrap_or_default())
503 }
504
505 pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
507 self.inner.read().pinned_event_ids()
508 }
509}
510
511#[cfg(not(feature = "test-send-sync"))]
513unsafe impl Send for Room {}
514
515#[cfg(not(feature = "test-send-sync"))]
517unsafe impl Sync for Room {}
518
519#[cfg(feature = "test-send-sync")]
520#[test]
521fn test_send_sync_for_room() {
523 fn assert_send_sync<
524 T: matrix_sdk_common::SendOutsideWasm + matrix_sdk_common::SyncOutsideWasm,
525 >() {
526 }
527
528 assert_send_sync::<Room>();
529}
530
531#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
533pub(crate) enum AccountDataSource {
534 Stable,
536
537 #[default]
539 Unstable,
540}