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, InviteAcceptanceDetails, RoomInfo, RoomInfoNotableUpdate,
48 RoomInfoNotableUpdateReasons,
49};
50use ruma::{
51 assign,
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 int,
64 room::RoomType,
65 EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, UserId,
66};
67#[cfg(feature = "e2e-encryption")]
68use ruma::{events::AnySyncTimelineEvent, serde::Raw};
69use serde::{Deserialize, Serialize};
70pub use state::{RoomState, RoomStateFilter};
71pub(crate) use tags::RoomNotableTags;
72use tokio::sync::broadcast;
73pub use tombstone::{PredecessorRoom, SuccessorRoom};
74use tracing::{info, instrument, warn};
75
76use crate::{
77 deserialized_responses::MemberEvent,
78 notification_settings::RoomNotificationMode,
79 read_receipts::RoomReadReceipts,
80 store::{DynStateStore, Result as StoreResult, StateStoreExt},
81 sync::UnreadNotificationsCount,
82 Error, MinimalStateEvent,
83};
84
85#[derive(Debug, Clone)]
88pub struct Room {
89 pub(super) room_id: OwnedRoomId,
91
92 pub(super) own_user_id: OwnedUserId,
94
95 pub(super) inner: SharedObservable<RoomInfo>,
96 pub(super) room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
97 pub(super) store: Arc<DynStateStore>,
98
99 #[cfg(feature = "e2e-encryption")]
109 pub latest_encrypted_events: Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>,
110
111 pub seen_knock_request_ids_map:
115 SharedObservable<Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
116
117 pub room_member_updates_sender: broadcast::Sender<RoomMembersUpdate>,
119}
120
121impl Room {
122 pub(crate) fn new(
123 own_user_id: &UserId,
124 store: Arc<DynStateStore>,
125 room_id: &RoomId,
126 room_state: RoomState,
127 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
128 ) -> Self {
129 let room_info = RoomInfo::new(room_id, room_state);
130 Self::restore(own_user_id, store, room_info, room_info_notable_update_sender)
131 }
132
133 pub(crate) fn restore(
134 own_user_id: &UserId,
135 store: Arc<DynStateStore>,
136 room_info: RoomInfo,
137 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
138 ) -> Self {
139 let (room_member_updates_sender, _) = broadcast::channel(10);
140 Self {
141 own_user_id: own_user_id.into(),
142 room_id: room_info.room_id.clone(),
143 store,
144 inner: SharedObservable::new(room_info),
145 #[cfg(feature = "e2e-encryption")]
146 latest_encrypted_events: Arc::new(SyncRwLock::new(RingBuffer::new(
147 Self::MAX_ENCRYPTED_EVENTS,
148 ))),
149 room_info_notable_update_sender,
150 seen_knock_request_ids_map: SharedObservable::new_async(None),
151 room_member_updates_sender,
152 }
153 }
154
155 pub fn room_id(&self) -> &RoomId {
157 &self.room_id
158 }
159
160 pub fn creator(&self) -> Option<OwnedUserId> {
162 self.inner.read().creator().map(ToOwned::to_owned)
163 }
164
165 pub fn own_user_id(&self) -> &UserId {
167 &self.own_user_id
168 }
169
170 pub fn is_space(&self) -> bool {
172 self.inner.read().room_type().is_some_and(|t| *t == RoomType::Space)
173 }
174
175 pub fn room_type(&self) -> Option<RoomType> {
178 self.inner.read().room_type().map(ToOwned::to_owned)
179 }
180
181 pub fn unread_notification_counts(&self) -> UnreadNotificationsCount {
183 self.inner.read().notification_counts
184 }
185
186 pub fn num_unread_messages(&self) -> u64 {
191 self.inner.read().read_receipts.num_unread
192 }
193
194 pub fn read_receipts(&self) -> RoomReadReceipts {
196 self.inner.read().read_receipts.clone()
197 }
198
199 pub fn num_unread_notifications(&self) -> u64 {
204 self.inner.read().read_receipts.num_notifications
205 }
206
207 pub fn num_unread_mentions(&self) -> u64 {
213 self.inner.read().read_receipts.num_mentions
214 }
215
216 pub fn is_state_fully_synced(&self) -> bool {
224 self.inner.read().sync_info == SyncInfo::FullySynced
225 }
226
227 pub fn is_state_partially_or_fully_synced(&self) -> bool {
231 self.inner.read().sync_info != SyncInfo::NoState
232 }
233
234 pub fn last_prev_batch(&self) -> Option<String> {
237 self.inner.read().last_prev_batch.clone()
238 }
239
240 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
242 self.inner.read().avatar_url().map(ToOwned::to_owned)
243 }
244
245 pub fn avatar_info(&self) -> Option<avatar::ImageInfo> {
247 self.inner.read().avatar_info().map(ToOwned::to_owned)
248 }
249
250 pub fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
252 self.inner.read().canonical_alias().map(ToOwned::to_owned)
253 }
254
255 pub fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
257 self.inner.read().alt_aliases().to_owned()
258 }
259
260 pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
270 match self.inner.read().base_info.create.as_ref()? {
271 MinimalStateEvent::Original(ev) => Some(ev.content.clone()),
272 MinimalStateEvent::Redacted(ev) => Some(ev.content.clone()),
273 }
274 }
275
276 #[instrument(skip_all, fields(room_id = ?self.room_id))]
280 pub async fn is_direct(&self) -> StoreResult<bool> {
281 match self.state() {
282 RoomState::Joined | RoomState::Left | RoomState::Banned => {
283 Ok(!self.inner.read().base_info.dm_targets.is_empty())
284 }
285
286 RoomState::Invited => {
287 let member = self.get_member(self.own_user_id()).await?;
288
289 match member {
290 None => {
291 info!("RoomMember not found for the user's own id");
292 Ok(false)
293 }
294 Some(member) => match member.event.as_ref() {
295 MemberEvent::Sync(_) => {
296 warn!("Got MemberEvent::Sync in an invited room");
297 Ok(false)
298 }
299 MemberEvent::Stripped(event) => {
300 Ok(event.content.is_direct.unwrap_or(false))
301 }
302 },
303 }
304 }
305
306 RoomState::Knocked => Ok(false),
308 }
309 }
310
311 pub fn direct_targets(&self) -> HashSet<OwnedDirectUserIdentifier> {
320 self.inner.read().base_info.dm_targets.clone()
321 }
322
323 pub fn direct_targets_length(&self) -> usize {
326 self.inner.read().base_info.dm_targets.len()
327 }
328
329 pub fn guest_access(&self) -> GuestAccess {
331 self.inner.read().guest_access().clone()
332 }
333
334 pub fn history_visibility(&self) -> Option<HistoryVisibility> {
336 self.inner.read().history_visibility().cloned()
337 }
338
339 pub fn history_visibility_or_default(&self) -> HistoryVisibility {
342 self.inner.read().history_visibility_or_default().clone()
343 }
344
345 pub fn is_public(&self) -> Option<bool> {
349 self.inner.read().join_rule().map(|join_rule| matches!(join_rule, JoinRule::Public))
350 }
351
352 pub fn join_rule(&self) -> Option<JoinRule> {
354 self.inner.read().join_rule().cloned()
355 }
356
357 pub fn max_power_level(&self) -> i64 {
362 self.inner.read().base_info.max_power_level
363 }
364
365 pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
367 Ok(self
368 .store
369 .get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
370 .await?
371 .ok_or(Error::InsufficientData)?
372 .deserialize()?
373 .power_levels())
374 }
375
376 pub async fn power_levels_or_default(&self) -> RoomPowerLevels {
379 if let Ok(power_levels) = self.power_levels().await {
380 return power_levels;
381 }
382
383 let creator = self.creator();
386 assign!(
387 RoomPowerLevelsEventContent::new(),
388 { users: creator.into_iter().map(|user_id| (user_id, int!(100))).collect() }
389 )
390 .into()
391 }
392
393 pub fn name(&self) -> Option<String> {
398 self.inner.read().name().map(ToOwned::to_owned)
399 }
400
401 pub fn topic(&self) -> Option<String> {
403 self.inner.read().topic().map(ToOwned::to_owned)
404 }
405
406 pub fn update_cached_user_defined_notification_mode(&self, mode: RoomNotificationMode) {
412 self.inner.update_if(|info| {
413 if info.cached_user_defined_notification_mode.as_ref() != Some(&mode) {
414 info.cached_user_defined_notification_mode = Some(mode);
415
416 true
417 } else {
418 false
419 }
420 });
421 }
422
423 pub fn cached_user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
428 self.inner.read().cached_user_defined_notification_mode
429 }
430
431 pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
434 self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
435 }
436
437 pub fn heroes(&self) -> Vec<RoomHero> {
439 self.inner.read().heroes().to_vec()
440 }
441
442 pub async fn load_user_receipt(
445 &self,
446 receipt_type: ReceiptType,
447 thread: ReceiptThread,
448 user_id: &UserId,
449 ) -> StoreResult<Option<(OwnedEventId, Receipt)>> {
450 self.store.get_user_room_receipt_event(self.room_id(), receipt_type, thread, user_id).await
451 }
452
453 pub async fn load_event_receipts(
457 &self,
458 receipt_type: ReceiptType,
459 thread: ReceiptThread,
460 event_id: &EventId,
461 ) -> StoreResult<Vec<(OwnedUserId, Receipt)>> {
462 self.store
463 .get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
464 .await
465 }
466
467 pub fn is_marked_unread(&self) -> bool {
470 self.inner.read().base_info.is_marked_unread
471 }
472
473 pub fn recency_stamp(&self) -> Option<u64> {
477 self.inner.read().recency_stamp
478 }
479
480 pub fn invite_acceptance_details(&self) -> Option<InviteAcceptanceDetails> {
488 self.inner.read().invite_acceptance_details.clone()
489 }
490
491 pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> {
494 self.inner
495 .subscribe()
496 .map(|i| i.base_info.pinned_events.map(|c| c.pinned).unwrap_or_default())
497 }
498
499 pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
501 self.inner.read().pinned_event_ids()
502 }
503}
504
505#[cfg(not(feature = "test-send-sync"))]
507unsafe impl Send for Room {}
508
509#[cfg(not(feature = "test-send-sync"))]
511unsafe impl Sync for Room {}
512
513#[cfg(feature = "test-send-sync")]
514#[test]
515fn test_send_sync_for_room() {
517 fn assert_send_sync<
518 T: matrix_sdk_common::SendOutsideWasm + matrix_sdk_common::SyncOutsideWasm,
519 >() {
520 }
521
522 assert_send_sync::<Room>();
523}
524
525#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
527pub(crate) enum AccountDataSource {
528 Stable,
530
531 #[default]
533 Unstable,
534}