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
29use std::{
30 collections::{BTreeMap, BTreeSet, HashSet},
31 sync::Arc,
32};
33
34pub use call::CallIntentConsensus;
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};
41pub use members::{RoomMember, RoomMembersUpdate, RoomMemberships};
42pub(crate) use room_info::SyncInfo;
43pub use room_info::{
44 BaseRoomInfo, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomRecencyStamp,
45 apply_redaction,
46};
47use ruma::{
48 EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId,
49 RoomVersionId, UserId,
50 events::{
51 direct::OwnedDirectUserIdentifier,
52 receipt::{Receipt, ReceiptThread, ReceiptType},
53 room::{
54 avatar,
55 guest_access::GuestAccess,
56 history_visibility::HistoryVisibility,
57 join_rules::JoinRule,
58 member::MembershipState,
59 power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource},
60 },
61 },
62 room::RoomType,
63};
64use serde::{Deserialize, Serialize};
65pub use state::{RoomState, RoomStateFilter};
66pub(crate) use tags::RoomNotableTags;
67use tokio::sync::broadcast;
68pub use tombstone::{PredecessorRoom, SuccessorRoom};
69use tracing::{info, instrument, warn};
70
71use crate::{
72 Error,
73 deserialized_responses::MemberEvent,
74 notification_settings::RoomNotificationMode,
75 read_receipts::RoomReadReceipts,
76 store::{DynStateStore, Result as StoreResult, StateStoreExt},
77 sync::UnreadNotificationsCount,
78};
79
80#[derive(Debug, Clone)]
83pub struct Room {
84 pub(super) room_id: OwnedRoomId,
86
87 pub(super) own_user_id: OwnedUserId,
89
90 pub(super) info: SharedObservable<RoomInfo>,
91
92 pub(super) room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
96
97 pub(super) store: Arc<DynStateStore>,
99
100 pub seen_knock_request_ids_map:
104 SharedObservable<Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
105
106 pub room_member_updates_sender: broadcast::Sender<RoomMembersUpdate>,
108}
109
110impl Room {
111 pub(crate) fn new(
112 own_user_id: &UserId,
113 store: Arc<DynStateStore>,
114 room_id: &RoomId,
115 room_state: RoomState,
116 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
117 ) -> Self {
118 let room_info = RoomInfo::new(room_id, room_state);
119 Self::restore(own_user_id, store, room_info, room_info_notable_update_sender)
120 }
121
122 pub(crate) fn restore(
123 own_user_id: &UserId,
124 store: Arc<DynStateStore>,
125 room_info: RoomInfo,
126 room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
127 ) -> Self {
128 let (room_member_updates_sender, _) = broadcast::channel(10);
129 Self {
130 own_user_id: own_user_id.into(),
131 room_id: room_info.room_id.clone(),
132 store,
133 info: SharedObservable::new(room_info),
134 room_info_notable_update_sender,
135 seen_knock_request_ids_map: SharedObservable::new_async(None),
136 room_member_updates_sender,
137 }
138 }
139
140 pub fn room_id(&self) -> &RoomId {
142 &self.room_id
143 }
144
145 pub fn creators(&self) -> Option<Vec<OwnedUserId>> {
147 self.info.read().creators()
148 }
149
150 pub fn own_user_id(&self) -> &UserId {
152 &self.own_user_id
153 }
154
155 pub fn is_space(&self) -> bool {
157 self.info.read().room_type().is_some_and(|t| *t == RoomType::Space)
158 }
159
160 pub fn is_call(&self) -> bool {
164 self.info.read().room_type().is_some_and(|t| *t == RoomType::Call)
165 }
166
167 pub fn room_type(&self) -> Option<RoomType> {
170 self.info.read().room_type().map(ToOwned::to_owned)
171 }
172
173 pub fn unread_notification_counts(&self) -> UnreadNotificationsCount {
182 self.info.read().notification_counts
183 }
184
185 pub fn num_unread_messages(&self) -> u64 {
190 self.info.read().read_receipts.num_unread
191 }
192
193 pub fn num_unread_notifications(&self) -> u64 {
198 self.info.read().read_receipts.num_notifications
199 }
200
201 pub fn num_unread_mentions(&self) -> u64 {
207 self.info.read().read_receipts.num_mentions
208 }
209
210 pub fn read_receipts(&self) -> RoomReadReceipts {
212 self.info.read().read_receipts.clone()
213 }
214
215 pub fn is_state_fully_synced(&self) -> bool {
223 self.info.read().sync_info == SyncInfo::FullySynced
224 }
225
226 pub fn is_state_partially_or_fully_synced(&self) -> bool {
230 self.info.read().sync_info != SyncInfo::NoState
231 }
232
233 pub fn last_prev_batch(&self) -> Option<String> {
236 self.info.read().last_prev_batch.clone()
237 }
238
239 pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
241 self.info.read().avatar_url().map(ToOwned::to_owned)
242 }
243
244 pub fn avatar_info(&self) -> Option<avatar::ImageInfo> {
246 self.info.read().avatar_info().map(ToOwned::to_owned)
247 }
248
249 pub fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
251 self.info.read().canonical_alias().map(ToOwned::to_owned)
252 }
253
254 pub fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
256 self.info.read().alt_aliases().to_owned()
257 }
258
259 pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
269 Some(self.info.read().base_info.create.as_ref()?.content.clone())
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.info.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.info.read().base_info.dm_targets.clone()
317 }
318
319 pub fn direct_targets_length(&self) -> usize {
322 self.info.read().base_info.dm_targets.len()
323 }
324
325 pub fn guest_access(&self) -> GuestAccess {
327 self.info.read().guest_access().clone()
328 }
329
330 pub fn history_visibility(&self) -> Option<HistoryVisibility> {
332 self.info.read().history_visibility().cloned()
333 }
334
335 pub fn history_visibility_or_default(&self) -> HistoryVisibility {
338 self.info.read().history_visibility_or_default().clone()
339 }
340
341 pub fn is_public(&self) -> Option<bool> {
345 self.info.read().join_rule().map(|join_rule| matches!(join_rule, JoinRule::Public))
346 }
347
348 pub fn join_rule(&self) -> Option<JoinRule> {
350 self.info.read().join_rule().cloned()
351 }
352
353 pub fn max_power_level(&self) -> i64 {
358 self.info.read().base_info.max_power_level
359 }
360
361 pub fn service_members(&self) -> Option<BTreeSet<OwnedUserId>> {
363 self.info.read().service_members().cloned()
364 }
365
366 pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
368 let power_levels_content = self
369 .store
370 .get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
371 .await?
372 .ok_or(Error::InsufficientData)?
373 .deserialize()?;
374 let creators = self.creators().ok_or(Error::InsufficientData)?;
375 let rules = self.info.read().room_version_rules_or_default();
376
377 Ok(power_levels_content.power_levels(&rules.authorization, creators))
378 }
379
380 pub async fn power_levels_or_default(&self) -> RoomPowerLevels {
383 if let Ok(power_levels) = self.power_levels().await {
384 return power_levels;
385 }
386
387 let rules = self.info.read().room_version_rules_or_default();
389 RoomPowerLevels::new(
390 RoomPowerLevelsSource::None,
391 &rules.authorization,
392 self.creators().into_iter().flatten(),
393 )
394 }
395
396 pub fn name(&self) -> Option<String> {
401 self.info.read().name().map(ToOwned::to_owned)
402 }
403
404 pub fn topic(&self) -> Option<String> {
406 self.info.read().topic().map(ToOwned::to_owned)
407 }
408
409 pub fn update_cached_user_defined_notification_mode(&self, mode: RoomNotificationMode) {
415 self.info.update_if(|info| {
416 if info.cached_user_defined_notification_mode.as_ref() != Some(&mode) {
417 info.cached_user_defined_notification_mode = Some(mode);
418
419 true
420 } else {
421 false
422 }
423 });
424 }
425
426 pub fn cached_user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
431 self.info.read().cached_user_defined_notification_mode
432 }
433
434 pub fn clear_user_defined_notification_mode(&self) {
437 self.info.update_if(|info| {
438 if info.cached_user_defined_notification_mode.is_some() {
439 info.cached_user_defined_notification_mode = None;
440 true
441 } else {
442 false
443 }
444 })
445 }
446
447 pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
450 self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
451 }
452
453 pub fn heroes(&self) -> Vec<RoomHero> {
455 self.info.read().heroes().to_vec()
456 }
457
458 pub async fn load_user_receipt(
461 &self,
462 receipt_type: ReceiptType,
463 thread: ReceiptThread,
464 user_id: &UserId,
465 ) -> StoreResult<Option<(OwnedEventId, Receipt)>> {
466 self.store.get_user_room_receipt_event(self.room_id(), receipt_type, thread, user_id).await
467 }
468
469 pub async fn load_event_receipts(
473 &self,
474 receipt_type: ReceiptType,
475 thread: ReceiptThread,
476 event_id: &EventId,
477 ) -> StoreResult<Vec<(OwnedUserId, Receipt)>> {
478 self.store
479 .get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
480 .await
481 }
482
483 pub fn is_marked_unread(&self) -> bool {
486 self.info.read().base_info.is_marked_unread
487 }
488
489 pub fn version(&self) -> Option<RoomVersionId> {
491 self.info.read().room_version().cloned()
492 }
493
494 pub fn recency_stamp(&self) -> Option<RoomRecencyStamp> {
498 self.info.read().recency_stamp
499 }
500
501 pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> + use<> {
504 self.info
505 .subscribe()
506 .map(|i| i.base_info.pinned_events.and_then(|c| c.pinned).unwrap_or_default())
507 }
508
509 pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
511 self.info.read().pinned_event_ids()
512 }
513
514 pub async fn active_service_members(&self) -> StoreResult<Option<Vec<RoomMember>>> {
518 if let Some(service_members) = self.service_members() {
519 let mut found = Vec::new();
520 for user_id in service_members {
521 match self.get_member(&user_id).await {
522 Ok(Some(member)) => {
523 if matches!(
525 member.membership(),
526 MembershipState::Join | MembershipState::Invite
527 ) {
528 found.push(member);
529 }
530 }
531 Ok(None) => (),
532 Err(error) => return Err(error),
533 }
534 }
535
536 Ok(Some(found))
537 } else {
538 Ok(None)
539 }
540 }
541}
542
543#[cfg(not(feature = "test-send-sync"))]
545unsafe impl Send for Room {}
546
547#[cfg(not(feature = "test-send-sync"))]
549unsafe impl Sync for Room {}
550
551#[cfg(feature = "test-send-sync")]
552#[test]
553fn test_send_sync_for_room() {
555 fn assert_send_sync<
556 T: matrix_sdk_common::SendOutsideWasm + matrix_sdk_common::SyncOutsideWasm,
557 >() {
558 }
559
560 assert_send_sync::<Room>();
561}
562
563#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
565pub(crate) enum AccountDataSource {
566 Stable,
568
569 #[default]
571 Unstable,
572}