matrix_sdk_base/response_processors/room/msc4186/
mod.rs1pub mod extensions;
16
17use std::collections::BTreeMap;
18#[cfg(feature = "e2e-encryption")]
19use std::collections::BTreeSet;
20
21#[cfg(feature = "e2e-encryption")]
22use matrix_sdk_common::deserialized_responses::TimelineEvent;
23#[cfg(feature = "e2e-encryption")]
24use ruma::events::StateEventType;
25use ruma::{
26 api::client::sync::sync_events::{
27 v3::{InviteState, InvitedRoom, KnockState, KnockedRoom},
28 v5 as http,
29 },
30 assign,
31 events::{
32 room::member::{MembershipState, RoomMemberEventContent},
33 AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent,
34 },
35 serde::Raw,
36 JsOption, OwnedRoomId, RoomId, UserId,
37};
38use tokio::sync::broadcast::Sender;
39
40#[cfg(feature = "e2e-encryption")]
41use super::super::e2ee;
42use super::{
43 super::{notification, state_events, timeline, Context},
44 RoomCreationData,
45};
46#[cfg(feature = "e2e-encryption")]
47use crate::StateChanges;
48use crate::{
49 store::BaseStateStore,
50 sync::{InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate},
51 Result, Room, RoomHero, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
52 RoomState,
53};
54
55pub enum RoomUpdateKind {
57 Joined(JoinedRoomUpdate),
58 Left(LeftRoomUpdate),
59 Invited(InvitedRoomUpdate),
60 Knocked(KnockedRoomUpdate),
61}
62
63pub async fn update_any_room(
64 context: &mut Context,
65 user_id: &UserId,
66 room_creation_data: RoomCreationData<'_>,
67 room_response: &http::response::Room,
68 rooms_account_data: &BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
69 #[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'_>,
70 notification: notification::Notification<'_>,
71) -> Result<Option<(RoomInfo, RoomUpdateKind)>> {
72 let RoomCreationData {
73 room_id,
74 room_info_notable_update_sender,
75 requested_required_states,
76 ambiguity_cache,
77 } = room_creation_data;
78
79 let (raw_state_events, state_events) =
85 state_events::sync::collect(&room_response.required_state);
86
87 let state_store = notification.state_store;
88
89 let is_new_room = !state_store.room_exists(room_id);
91
92 let invite_state_events =
93 room_response.invite_state.as_ref().map(|events| state_events::stripped::collect(events));
94
95 #[allow(unused_mut)] let (mut room, mut room_info, maybe_room_update_kind) = membership(
97 context,
98 &state_events,
99 &invite_state_events,
100 state_store,
101 user_id,
102 room_id,
103 room_info_notable_update_sender,
104 );
105
106 room_info.mark_state_partially_synced();
107 room_info.handle_encryption_state(requested_required_states.for_room(room_id));
108
109 #[cfg(feature = "e2e-encryption")]
110 let mut new_user_ids = BTreeSet::new();
111
112 #[cfg(not(feature = "e2e-encryption"))]
113 let mut new_user_ids = ();
114
115 state_events::sync::dispatch(
116 context,
117 (&raw_state_events, &state_events),
118 &mut room_info,
119 ambiguity_cache,
120 &mut new_user_ids,
121 state_store,
122 )
123 .await?;
124
125 if let Some((raw_events, events)) = invite_state_events {
127 state_events::stripped::dispatch_invite_or_knock(
128 context,
129 (&raw_events, &events),
130 &room,
131 &mut room_info,
132 notification::Notification::new(
133 notification.push_rules,
134 notification.notifications,
135 notification.state_store,
136 ),
137 )
138 .await?;
139 }
140
141 properties(context, room_id, room_response, &mut room_info, is_new_room);
142
143 let timeline = timeline::build(
144 context,
145 &room,
146 &mut room_info,
147 timeline::builder::Timeline::from(room_response),
148 notification,
149 #[cfg(feature = "e2e-encryption")]
150 e2ee.clone(),
151 )
152 .await?;
153
154 #[cfg(feature = "e2e-encryption")]
157 cache_latest_events(
158 &room,
159 &mut room_info,
160 &timeline.events,
161 Some(&context.state_changes),
162 Some(state_store),
163 )
164 .await;
165
166 #[cfg(feature = "e2e-encryption")]
167 e2ee::tracked_users::update_or_set_if_room_is_newly_encrypted(
168 e2ee.olm_machine,
169 &new_user_ids,
170 room_info.encryption_state(),
171 room.encryption_state(),
172 room_id,
173 state_store,
174 )
175 .await?;
176
177 let notification_count = room_response.unread_notifications.clone().into();
178 room_info.update_notification_count(notification_count);
179
180 let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
181 let room_account_data = rooms_account_data.get(room_id);
182
183 match (room_info.state(), maybe_room_update_kind) {
184 (RoomState::Joined, None) => {
185 let ephemeral = Vec::new();
189
190 Ok(Some((
191 room_info,
192 RoomUpdateKind::Joined(JoinedRoomUpdate::new(
193 timeline,
194 raw_state_events,
195 room_account_data.cloned().unwrap_or_default(),
196 ephemeral,
197 notification_count,
198 ambiguity_changes,
199 )),
200 )))
201 }
202
203 (RoomState::Left, None) | (RoomState::Banned, None) => Ok(Some((
204 room_info,
205 RoomUpdateKind::Left(LeftRoomUpdate::new(
206 timeline,
207 raw_state_events,
208 room_account_data.cloned().unwrap_or_default(),
209 ambiguity_changes,
210 )),
211 ))),
212
213 (RoomState::Invited, Some(update @ RoomUpdateKind::Invited(_)))
214 | (RoomState::Knocked, Some(update @ RoomUpdateKind::Knocked(_))) => {
215 Ok(Some((room_info, update)))
216 }
217
218 _ => Ok(None),
219 }
220}
221
222fn membership(
228 context: &mut Context,
229 state_events: &[AnySyncStateEvent],
230 invite_state_events: &Option<(Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>)>,
231 store: &BaseStateStore,
232 user_id: &UserId,
233 room_id: &RoomId,
234 room_info_notable_update_sender: Sender<RoomInfoNotableUpdate>,
235) -> (Room, RoomInfo, Option<RoomUpdateKind>) {
236 if let Some(state_events) = invite_state_events {
243 let membership_event = state_events.1.iter().find_map(|event| {
246 if let AnyStrippedStateEvent::RoomMember(membership_event) = event {
247 if membership_event.state_key == user_id {
248 return Some(membership_event.content.clone());
249 }
250 }
251 None
252 });
253
254 match membership_event {
255 Some(RoomMemberEventContent { membership: MembershipState::Knock, .. }) => {
257 let room = store.get_or_create_room(
258 room_id,
259 RoomState::Knocked,
260 room_info_notable_update_sender,
261 );
262 let mut room_info = room.clone_info();
263 room_info.mark_as_knocked();
265
266 let raw_events = state_events.0.clone();
267 let knock_state = assign!(KnockState::default(), { events: raw_events });
268 let knocked_room = assign!(KnockedRoom::default(), { knock_state: knock_state });
269
270 (room, room_info, Some(RoomUpdateKind::Knocked(knocked_room)))
271 }
272
273 _ => {
275 let room = store.get_or_create_room(
276 room_id,
277 RoomState::Invited,
278 room_info_notable_update_sender,
279 );
280 let mut room_info = room.clone_info();
281 room_info.mark_as_invited();
283
284 let raw_events = state_events.0.clone();
285 let invited_room = InvitedRoom::from(InviteState::from(raw_events));
286
287 (room, room_info, Some(RoomUpdateKind::Invited(invited_room)))
288 }
289 }
290 }
291 else {
294 let room =
295 store.get_or_create_room(room_id, RoomState::Joined, room_info_notable_update_sender);
296 let mut room_info = room.clone_info();
297
298 room_info.mark_as_joined();
303
304 own_membership(context, user_id, state_events, &mut room_info);
310
311 (room, room_info, None)
312 }
313}
314
315fn own_membership(
318 context: &mut Context,
319 user_id: &UserId,
320 state_events: &[AnySyncStateEvent],
321 room_info: &mut RoomInfo,
322) {
323 for event in state_events.iter().rev() {
327 if let AnySyncStateEvent::RoomMember(member) = &event {
328 if member.state_key() == user_id.as_str() {
331 let new_state: RoomState = member.membership().into();
332
333 if new_state != room_info.state() {
334 room_info.set_state(new_state);
335 context
337 .room_info_notable_updates
338 .entry(room_info.room_id.to_owned())
339 .or_default()
340 .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
341 }
342
343 break;
344 }
345 }
346 }
347}
348
349fn properties(
350 context: &mut Context,
351 room_id: &RoomId,
352 room_response: &http::response::Room,
353 room_info: &mut RoomInfo,
354 is_new_room: bool,
355) {
356 match &room_response.avatar {
362 JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
364 JsOption::Null => room_info.update_avatar(None),
366 JsOption::Undefined => {}
368 }
369
370 if let Some(count) = room_response.joined_count {
373 room_info.update_joined_member_count(count.into());
374 }
375 if let Some(count) = room_response.invited_count {
376 room_info.update_invited_member_count(count.into());
377 }
378
379 if let Some(heroes) = &room_response.heroes {
380 room_info.update_heroes(
381 heroes
382 .iter()
383 .map(|hero| RoomHero {
384 user_id: hero.user_id.clone(),
385 display_name: hero.name.clone(),
386 avatar_url: hero.avatar.clone(),
387 })
388 .collect(),
389 );
390 }
391
392 room_info.set_prev_batch(room_response.prev_batch.as_deref());
393
394 if room_response.limited {
395 room_info.mark_members_missing();
396 }
397
398 if let Some(recency_stamp) = &room_response.bump_stamp {
399 let recency_stamp: u64 = (*recency_stamp).into();
400
401 if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
402 room_info.update_recency_stamp(recency_stamp);
403
404 if !is_new_room {
408 context
409 .room_info_notable_updates
410 .entry(room_id.to_owned())
411 .or_default()
412 .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
413 }
414 }
415 }
416}
417
418#[cfg(feature = "e2e-encryption")]
426pub(crate) async fn cache_latest_events(
427 room: &Room,
428 room_info: &mut RoomInfo,
429 events: &[TimelineEvent],
430 changes: Option<&StateChanges>,
431 store: Option<&BaseStateStore>,
432) {
433 use tracing::warn;
434
435 use crate::{
436 deserialized_responses::DisplayName,
437 latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent},
438 store::ambiguity_map::is_display_name_ambiguous,
439 };
440
441 let mut encrypted_events =
442 Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
443
444 let power_levels_from_changes = || {
446 let state_changes = changes?.state.get(room_info.room_id())?;
447 let room_power_levels_state =
448 state_changes.get(&StateEventType::RoomPowerLevels)?.values().next()?;
449 match room_power_levels_state.deserialize().ok()? {
450 AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.power_levels()),
451 _ => None,
452 }
453 };
454
455 let power_levels = match power_levels_from_changes() {
457 Some(power_levels) => Some(power_levels),
458 None => room.power_levels().await.ok(),
459 };
460
461 let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
462
463 for event in events.iter().rev() {
464 if let Ok(timeline_event) = event.raw().deserialize() {
465 match is_suitable_for_latest_event(&timeline_event, power_levels_info) {
466 PossibleLatestEvent::YesRoomMessage(_)
467 | PossibleLatestEvent::YesPoll(_)
468 | PossibleLatestEvent::YesCallInvite(_)
469 | PossibleLatestEvent::YesCallNotify(_)
470 | PossibleLatestEvent::YesSticker(_)
471 | PossibleLatestEvent::YesKnockedStateEvent(_) => {
472 let mut sender_profile = None;
480 let mut sender_name_is_ambiguous = None;
481
482 if let Some(changes) = changes {
485 sender_profile = changes
486 .profiles
487 .get(room.room_id())
488 .and_then(|profiles_by_user| {
489 profiles_by_user.get(timeline_event.sender())
490 })
491 .cloned();
492
493 if let Some(sender_profile) = sender_profile.as_ref() {
494 sender_name_is_ambiguous = sender_profile
495 .as_original()
496 .and_then(|profile| profile.content.displayname.as_ref())
497 .and_then(|display_name| {
498 let display_name = DisplayName::new(display_name);
499
500 changes.ambiguity_maps.get(room.room_id()).and_then(
501 |map_for_room| {
502 map_for_room.get(&display_name).map(|users| {
503 is_display_name_ambiguous(&display_name, users)
504 })
505 },
506 )
507 });
508 }
509 }
510
511 if sender_profile.is_none() {
513 if let Some(store) = store {
514 sender_profile = store
515 .get_profile(room.room_id(), timeline_event.sender())
516 .await
517 .ok()
518 .flatten();
519
520 }
523 }
524
525 let latest_event = Box::new(LatestEvent::new_with_sender_details(
526 event.clone(),
527 sender_profile,
528 sender_name_is_ambiguous,
529 ));
530
531 room_info.latest_event = Some(latest_event);
533 room.latest_encrypted_events.write().unwrap().clear();
536 break;
539 }
540 PossibleLatestEvent::NoEncrypted => {
541 if encrypted_events.len() < encrypted_events.capacity() {
547 encrypted_events.push(event.raw().clone());
548 }
549 }
550 _ => {
551 }
553 }
554 } else {
555 warn!(
556 "Failed to deserialize event as AnySyncTimelineEvent. ID={}",
557 event.event_id().expect("Event has no ID!")
558 );
559 }
560 }
561
562 room.latest_encrypted_events.write().unwrap().extend(encrypted_events.into_iter().rev());
565}