1pub mod extensions;
16
17use std::collections::BTreeMap;
18
19#[cfg(feature = "e2e-encryption")]
20use matrix_sdk_common::deserialized_responses::TimelineEvent;
21#[cfg(feature = "e2e-encryption")]
22use ruma::events::StateEventType;
23use ruma::{
24 api::client::sync::sync_events::{
25 v3::{InviteState, InvitedRoom, KnockState, KnockedRoom},
26 v5 as http,
27 },
28 assign,
29 events::{
30 room::member::{MembershipState, RoomMemberEventContent},
31 AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent,
32 },
33 serde::Raw,
34 JsOption, OwnedRoomId, RoomId, UserId,
35};
36use tokio::sync::broadcast::Sender;
37
38#[cfg(feature = "e2e-encryption")]
39use super::super::e2ee;
40use super::{
41 super::{notification, state_events, timeline, Context},
42 RoomCreationData,
43};
44#[cfg(feature = "e2e-encryption")]
45use crate::StateChanges;
46use crate::{
47 store::BaseStateStore,
48 sync::{InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate},
49 Result, Room, RoomHero, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
50 RoomState,
51};
52
53pub enum RoomUpdateKind {
55 Joined(JoinedRoomUpdate),
56 Left(LeftRoomUpdate),
57 Invited(InvitedRoomUpdate),
58 Knocked(KnockedRoomUpdate),
59}
60
61pub async fn update_any_room(
62 context: &mut Context,
63 user_id: &UserId,
64 room_creation_data: RoomCreationData<'_>,
65 room_response: &http::response::Room,
66 rooms_account_data: &BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
67 #[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'_>,
68 notification: notification::Notification<'_>,
69) -> Result<Option<(RoomInfo, RoomUpdateKind)>> {
70 let RoomCreationData {
71 room_id,
72 room_info_notable_update_sender,
73 requested_required_states,
74 ambiguity_cache,
75 } = room_creation_data;
76
77 let (raw_state_events, state_events) =
83 state_events::sync::collect(context, &room_response.required_state);
84
85 let state_store = notification.state_store;
86
87 let is_new_room = !state_store.room_exists(room_id);
89
90 let invite_state_events = room_response
91 .invite_state
92 .as_ref()
93 .map(|events| state_events::stripped::collect(context, 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_attr(not(feature = "e2e-encryption"), allow(unused))]
110 let new_user_ids = state_events::sync::dispatch_and_get_new_users(
111 context,
112 (&raw_state_events, &state_events),
113 &mut room_info,
114 ambiguity_cache,
115 )
116 .await?;
117
118 if let Some((raw_events, events)) = invite_state_events {
120 state_events::stripped::dispatch_invite_or_knock(
121 context,
122 (&raw_events, &events),
123 &room,
124 &mut room_info,
125 notification::Notification::new(
126 notification.push_rules,
127 notification.notifications,
128 notification.state_store,
129 ),
130 )
131 .await?;
132 }
133
134 properties(context, room_id, room_response, &mut room_info, is_new_room);
135
136 let timeline = timeline::build(
137 context,
138 &room,
139 &mut room_info,
140 timeline::builder::Timeline::from(room_response),
141 notification,
142 #[cfg(feature = "e2e-encryption")]
143 e2ee.clone(),
144 )
145 .await?;
146
147 #[cfg(feature = "e2e-encryption")]
150 cache_latest_events(
151 &room,
152 &mut room_info,
153 &timeline.events,
154 Some(&context.state_changes),
155 Some(state_store),
156 )
157 .await;
158
159 #[cfg(feature = "e2e-encryption")]
160 e2ee::tracked_users::update_or_set_if_room_is_newly_encrypted(
161 context,
162 e2ee.olm_machine,
163 &new_user_ids,
164 room_info.encryption_state(),
165 room.encryption_state(),
166 room_id,
167 state_store,
168 )
169 .await?;
170
171 let notification_count = room_response.unread_notifications.clone().into();
172 room_info.update_notification_count(notification_count);
173
174 let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
175 let room_account_data = rooms_account_data.get(room_id);
176
177 match (room_info.state(), maybe_room_update_kind) {
178 (RoomState::Joined, None) => {
179 let ephemeral = Vec::new();
183
184 Ok(Some((
185 room_info,
186 RoomUpdateKind::Joined(JoinedRoomUpdate::new(
187 timeline,
188 raw_state_events,
189 room_account_data.cloned().unwrap_or_default(),
190 ephemeral,
191 notification_count,
192 ambiguity_changes,
193 )),
194 )))
195 }
196
197 (RoomState::Left, None) | (RoomState::Banned, None) => Ok(Some((
198 room_info,
199 RoomUpdateKind::Left(LeftRoomUpdate::new(
200 timeline,
201 raw_state_events,
202 room_account_data.cloned().unwrap_or_default(),
203 ambiguity_changes,
204 )),
205 ))),
206
207 (RoomState::Invited, Some(update @ RoomUpdateKind::Invited(_)))
208 | (RoomState::Knocked, Some(update @ RoomUpdateKind::Knocked(_))) => {
209 Ok(Some((room_info, update)))
210 }
211
212 _ => Ok(None),
213 }
214}
215
216fn membership(
222 context: &mut Context,
223 state_events: &[AnySyncStateEvent],
224 invite_state_events: &Option<(Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>)>,
225 store: &BaseStateStore,
226 user_id: &UserId,
227 room_id: &RoomId,
228 room_info_notable_update_sender: Sender<RoomInfoNotableUpdate>,
229) -> (Room, RoomInfo, Option<RoomUpdateKind>) {
230 if let Some(state_events) = invite_state_events {
237 let membership_event = state_events.1.iter().find_map(|event| {
240 if let AnyStrippedStateEvent::RoomMember(membership_event) = event {
241 if membership_event.state_key == user_id {
242 return Some(membership_event.content.clone());
243 }
244 }
245 None
246 });
247
248 match membership_event {
249 Some(RoomMemberEventContent { membership: MembershipState::Knock, .. }) => {
251 let room = store.get_or_create_room(
252 room_id,
253 RoomState::Knocked,
254 room_info_notable_update_sender,
255 );
256 let mut room_info = room.clone_info();
257 room_info.mark_as_knocked();
259
260 let raw_events = state_events.0.clone();
261 let knock_state = assign!(KnockState::default(), { events: raw_events });
262 let knocked_room = assign!(KnockedRoom::default(), { knock_state: knock_state });
263
264 (room, room_info, Some(RoomUpdateKind::Knocked(knocked_room)))
265 }
266
267 _ => {
269 let room = store.get_or_create_room(
270 room_id,
271 RoomState::Invited,
272 room_info_notable_update_sender,
273 );
274 let mut room_info = room.clone_info();
275 room_info.mark_as_invited();
277
278 let raw_events = state_events.0.clone();
279 let invited_room = InvitedRoom::from(InviteState::from(raw_events));
280
281 (room, room_info, Some(RoomUpdateKind::Invited(invited_room)))
282 }
283 }
284 }
285 else {
288 let room =
289 store.get_or_create_room(room_id, RoomState::Joined, room_info_notable_update_sender);
290 let mut room_info = room.clone_info();
291
292 room_info.mark_as_joined();
297
298 own_membership(context, user_id, state_events, &mut room_info);
304
305 (room, room_info, None)
306 }
307}
308
309fn own_membership(
312 context: &mut Context,
313 user_id: &UserId,
314 state_events: &[AnySyncStateEvent],
315 room_info: &mut RoomInfo,
316) {
317 for event in state_events.iter().rev() {
321 if let AnySyncStateEvent::RoomMember(member) = &event {
322 if member.state_key() == user_id.as_str() {
325 let new_state: RoomState = member.membership().into();
326
327 if new_state != room_info.state() {
328 room_info.set_state(new_state);
329 context
331 .room_info_notable_updates
332 .entry(room_info.room_id.to_owned())
333 .or_default()
334 .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
335 }
336
337 break;
338 }
339 }
340 }
341}
342
343fn properties(
344 context: &mut Context,
345 room_id: &RoomId,
346 room_response: &http::response::Room,
347 room_info: &mut RoomInfo,
348 is_new_room: bool,
349) {
350 match &room_response.avatar {
356 JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
358 JsOption::Null => room_info.update_avatar(None),
360 JsOption::Undefined => {}
362 }
363
364 if let Some(count) = room_response.joined_count {
367 room_info.update_joined_member_count(count.into());
368 }
369 if let Some(count) = room_response.invited_count {
370 room_info.update_invited_member_count(count.into());
371 }
372
373 if let Some(heroes) = &room_response.heroes {
374 room_info.update_heroes(
375 heroes
376 .iter()
377 .map(|hero| RoomHero {
378 user_id: hero.user_id.clone(),
379 display_name: hero.name.clone(),
380 avatar_url: hero.avatar.clone(),
381 })
382 .collect(),
383 );
384 }
385
386 room_info.set_prev_batch(room_response.prev_batch.as_deref());
387
388 if room_response.limited {
389 room_info.mark_members_missing();
390 }
391
392 if let Some(recency_stamp) = &room_response.bump_stamp {
393 let recency_stamp: u64 = (*recency_stamp).into();
394
395 if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
396 room_info.update_recency_stamp(recency_stamp);
397
398 if !is_new_room {
402 context
403 .room_info_notable_updates
404 .entry(room_id.to_owned())
405 .or_default()
406 .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
407 }
408 }
409 }
410}
411
412#[cfg(feature = "e2e-encryption")]
420pub(crate) async fn cache_latest_events(
421 room: &Room,
422 room_info: &mut RoomInfo,
423 events: &[TimelineEvent],
424 changes: Option<&StateChanges>,
425 store: Option<&BaseStateStore>,
426) {
427 use tracing::warn;
428
429 use crate::{
430 deserialized_responses::DisplayName,
431 latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent},
432 store::ambiguity_map::is_display_name_ambiguous,
433 };
434
435 let mut encrypted_events =
436 Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
437
438 let power_levels_from_changes = || {
440 let state_changes = changes?.state.get(room_info.room_id())?;
441 let room_power_levels_state =
442 state_changes.get(&StateEventType::RoomPowerLevels)?.values().next()?;
443 match room_power_levels_state.deserialize().ok()? {
444 AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.power_levels()),
445 _ => None,
446 }
447 };
448
449 let power_levels = match power_levels_from_changes() {
451 Some(power_levels) => Some(power_levels),
452 None => room.power_levels().await.ok(),
453 };
454
455 let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
456
457 for event in events.iter().rev() {
458 if let Ok(timeline_event) = event.raw().deserialize() {
459 match is_suitable_for_latest_event(&timeline_event, power_levels_info) {
460 PossibleLatestEvent::YesRoomMessage(_)
461 | PossibleLatestEvent::YesPoll(_)
462 | PossibleLatestEvent::YesCallInvite(_)
463 | PossibleLatestEvent::YesCallNotify(_)
464 | PossibleLatestEvent::YesSticker(_)
465 | PossibleLatestEvent::YesKnockedStateEvent(_) => {
466 let mut sender_profile = None;
474 let mut sender_name_is_ambiguous = None;
475
476 if let Some(changes) = changes {
479 sender_profile = changes
480 .profiles
481 .get(room.room_id())
482 .and_then(|profiles_by_user| {
483 profiles_by_user.get(timeline_event.sender())
484 })
485 .cloned();
486
487 if let Some(sender_profile) = sender_profile.as_ref() {
488 sender_name_is_ambiguous = sender_profile
489 .as_original()
490 .and_then(|profile| profile.content.displayname.as_ref())
491 .and_then(|display_name| {
492 let display_name = DisplayName::new(display_name);
493
494 changes.ambiguity_maps.get(room.room_id()).and_then(
495 |map_for_room| {
496 map_for_room.get(&display_name).map(|users| {
497 is_display_name_ambiguous(&display_name, users)
498 })
499 },
500 )
501 });
502 }
503 }
504
505 if sender_profile.is_none() {
507 if let Some(store) = store {
508 sender_profile = store
509 .get_profile(room.room_id(), timeline_event.sender())
510 .await
511 .ok()
512 .flatten();
513
514 }
517 }
518
519 let latest_event = Box::new(LatestEvent::new_with_sender_details(
520 event.clone(),
521 sender_profile,
522 sender_name_is_ambiguous,
523 ));
524
525 room_info.latest_event = Some(latest_event);
527 room.latest_encrypted_events.write().unwrap().clear();
530 break;
533 }
534 PossibleLatestEvent::NoEncrypted => {
535 if encrypted_events.len() < encrypted_events.capacity() {
541 encrypted_events.push(event.raw().clone());
542 }
543 }
544 _ => {
545 }
547 }
548 } else {
549 warn!(
550 "Failed to deserialize event as AnySyncTimelineEvent. ID={}",
551 event.event_id().expect("Event has no ID!")
552 );
553 }
554 }
555
556 room.latest_encrypted_events.write().unwrap().extend(encrypted_events.into_iter().rev());
559}