1use std::iter::empty;
16
17use eyeball::SharedObservable;
18use eyeball_im::VectorDiff;
19use matrix_sdk_base::{
20 RoomInfoNotableUpdateReasons, apply_redaction,
21 deserialized_responses::{ThreadSummary, ThreadSummaryStatus},
22 event_cache::{Event, Gap, store::EventCacheStoreLockGuard},
23 linked_chunk::{
24 ChunkIdentifierGenerator, LinkedChunkId, OwnedLinkedChunkId, Position, Update, lazy_loader,
25 },
26 serde_helpers::extract_redaction_target,
27 sync::Timeline,
28};
29use matrix_sdk_common::executor::spawn;
30use ruma::{
31 EventId, OwnedEventId, OwnedRoomId, OwnedUserId,
32 events::{
33 receipt::ReceiptEventContent, relation::RelationType,
34 room::redaction::SyncRoomRedactionEvent,
35 },
36 room_version_rules::RoomVersionRules,
37};
38use tokio::sync::broadcast::Sender;
39use tracing::{debug, error, instrument, trace};
40
41#[cfg(feature = "e2e-encryption")]
42use super::super::super::redecryptor::MaybeResolvedEvent;
43use super::{
44 super::{
45 super::{
46 EventCacheError,
47 back_pagination_queue::BackPaginationQueue,
48 deduplicator::{DeduplicationOutcome, filter_duplicate_events},
49 persistence::{
50 find_event, find_event_relations, find_event_with_relations,
51 load_linked_chunk_metadata, send_updates_to_store,
52 },
53 states::{ReloadPreprocessing, StateLockReadGuard, StateLockWriteGuard},
54 },
55 EventLocation,
56 event_linked_chunk::EventLinkedChunk,
57 pagination::SharedPaginationStatus,
58 read_receipts::{
59 MaybeReceiptEventContent, RoomReadReceiptEventFilter, compute_unread_counts,
60 },
61 subscriber::SubscribersHandle,
62 },
63 RoomEventCacheLinkedChunkUpdate, RoomEventCacheUpdateSender, sort_positions_descending,
64};
65use crate::room::WeakRoom;
66
67pub struct RoomEventCacheState {
68 pub enabled_thread_support: bool,
70
71 pub room_id: OwnedRoomId,
73
74 weak_room: WeakRoom,
76
77 pub own_user_id: OwnedUserId,
79
80 room_linked_chunk: EventLinkedChunk,
83
84 pagination_status: SharedObservable<SharedPaginationStatus>,
85
86 pub update_sender: RoomEventCacheUpdateSender,
91
92 pub(super) linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
95
96 room_version_rules: RoomVersionRules,
98
99 waited_for_initial_prev_token: bool,
104
105 subscribers_handle: SubscribersHandle,
107
108 back_pagination_queue: Option<BackPaginationQueue>,
110}
111
112impl RoomEventCacheState {
113 #[allow(clippy::too_many_arguments)]
124 pub async fn new(
125 own_user_id: OwnedUserId,
126 room_id: OwnedRoomId,
127 weak_room: WeakRoom,
128 room_version_rules: RoomVersionRules,
129 enabled_thread_support: bool,
130 update_sender: RoomEventCacheUpdateSender,
131 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
132 store_guard: EventCacheStoreLockGuard,
133 pagination_status: SharedObservable<SharedPaginationStatus>,
134 back_pagination_queue: Option<BackPaginationQueue>,
135 ) -> Result<Self, EventCacheError> {
136 let linked_chunk_id = LinkedChunkId::Room(&room_id);
137
138 let full_linked_chunk_metadata =
143 match load_linked_chunk_metadata(&store_guard, linked_chunk_id).await {
144 Ok(metas) => metas,
145 Err(err) => {
146 error!("error when loading a linked chunk's metadata from the store: {err}");
147
148 store_guard
150 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
151 .await?;
152
153 None
155 }
156 };
157
158 let linked_chunk = match store_guard
159 .load_last_chunk(linked_chunk_id)
160 .await
161 .map_err(EventCacheError::from)
162 .and_then(|(last_chunk, chunk_identifier_generator)| {
163 lazy_loader::from_last_chunk(last_chunk, chunk_identifier_generator)
164 .map_err(EventCacheError::from)
165 }) {
166 Ok(linked_chunk) => linked_chunk,
167 Err(err) => {
168 error!("error when loading a linked chunk's latest chunk from the store: {err}");
169
170 store_guard
172 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
173 .await?;
174
175 None
176 }
177 };
178
179 Ok(RoomEventCacheState {
180 own_user_id,
181 enabled_thread_support,
182 room_id,
183 weak_room,
184 room_linked_chunk: EventLinkedChunk::with_initial_linked_chunk(
185 linked_chunk,
186 full_linked_chunk_metadata,
187 ),
188 pagination_status,
189 update_sender,
190 linked_chunk_update_sender,
191 room_version_rules,
192 waited_for_initial_prev_token: false,
193 subscribers_handle: Default::default(),
194 back_pagination_queue,
195 })
196 }
197
198 pub fn room_linked_chunk(&self) -> &EventLinkedChunk {
200 &self.room_linked_chunk
201 }
202}
203
204impl<'a> StateLockReadGuard<'a, RoomEventCacheState> {
205 pub fn subscribers_handle(&self) -> &SubscribersHandle {
207 &self.state.subscribers_handle
208 }
209
210 pub async fn find_event(
212 &self,
213 event_id: &EventId,
214 ) -> Result<Option<(EventLocation, Event)>, EventCacheError> {
215 find_event(event_id, &self.room_id, &self.room_linked_chunk, &self.store).await
216 }
217
218 pub async fn find_event_with_relations(
220 &self,
221 event_id: &EventId,
222 filters: Option<Vec<RelationType>>,
223 ) -> Result<Option<(Event, Vec<Event>)>, EventCacheError> {
224 find_event_with_relations(
225 event_id,
226 &self.room_id,
227 filters,
228 &self.room_linked_chunk,
229 &self.store,
230 )
231 .await
232 }
233
234 pub async fn find_event_relations(
236 &self,
237 event_id: &EventId,
238 filters: Option<Vec<RelationType>>,
239 ) -> Result<Vec<Event>, EventCacheError> {
240 find_event_relations(event_id, &self.room_id, filters, &self.room_linked_chunk, &self.store)
241 .await
242 }
243
244 pub fn rfind_map_event_in_memory_by<O, P>(&self, mut predicate: P) -> Option<O>
252 where
253 P: FnMut(&Event) -> Option<O>,
254 {
255 self.state.room_linked_chunk.revents().find_map(|(_, event)| predicate(event))
256 }
257
258 #[cfg(test)]
259 pub fn is_dirty(&self) -> bool {
260 EventCacheStoreLockGuard::is_dirty(&self.store)
261 }
262}
263
264impl<'a> StateLockWriteGuard<'a, RoomEventCacheState> {
265 pub fn room_linked_chunk_mut(&mut self) -> &mut EventLinkedChunk {
267 &mut self.state.room_linked_chunk
268 }
269
270 pub fn waited_for_initial_prev_token(&self) -> bool {
272 self.state.waited_for_initial_prev_token
273 }
274
275 pub fn waited_for_initial_prev_token_mut(&mut self) -> &mut bool {
277 &mut self.state.waited_for_initial_prev_token
278 }
279
280 pub async fn find_event(
282 &self,
283 event_id: &EventId,
284 ) -> Result<Option<(EventLocation, Event)>, EventCacheError> {
285 find_event(event_id, &self.room_id, &self.room_linked_chunk, &self.store).await
286 }
287
288 #[must_use = "Propagate `VectorDiff` updates via `RoomEventCacheUpdate`"]
294 pub async fn reload(
295 &mut self,
296 preprocessing: ReloadPreprocessing,
297 ) -> Result<Vec<VectorDiff<Event>>, EventCacheError> {
298 match preprocessing {
299 ReloadPreprocessing::ForgetAll => {
300 self.room_linked_chunk_mut().reset();
302 self.propagate_changes().await?;
303
304 *self.waited_for_initial_prev_token_mut() = false;
308
309 self.state
311 .pagination_status
312 .set(SharedPaginationStatus::Idle { hit_timeline_start: false });
313 }
314
315 ReloadPreprocessing::None => {}
316 }
317
318 self.shrink_to_last_reloaded_chunk().await?;
319
320 Ok(self.room_linked_chunk_mut().updates_as_vector_diffs())
321 }
322
323 #[instrument(skip(self))]
332 async fn shrink_to_last_reloaded_chunk(&mut self) -> Result<(), EventCacheError> {
333 let linked_chunk_id = LinkedChunkId::Room(&self.state.room_id);
335
336 let full_linked_chunk_metadata =
337 match load_linked_chunk_metadata(&self.store, linked_chunk_id).await {
338 Ok(metas) => metas,
339 Err(err) => {
340 error!("error when reloading a linked chunk's metadata from the store: {err}");
341
342 self.store
344 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
345 .await?;
346
347 None
349 }
350 };
351
352 let (last_chunk, chunk_identifier_generator) =
353 match self.store.load_last_chunk(linked_chunk_id).await {
354 Ok(pair) => pair,
355
356 Err(err) => {
357 error!("error when reloading a linked chunk from memory: {err}");
359
360 self.store
362 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
363 .await?;
364
365 (None, ChunkIdentifierGenerator::new_from_scratch())
367 }
368 };
369
370 debug!("unloading the linked chunk, and resetting it to its last chunk");
371
372 if let Err(err) = self.state.room_linked_chunk.shrink_to_last_reloaded_chunk(
375 last_chunk,
376 chunk_identifier_generator,
377 full_linked_chunk_metadata,
378 ) {
379 error!("error when replacing the linked chunk: {err}");
380
381 self.state.room_linked_chunk.reset();
382 self.propagate_changes().await?;
383
384 self.state.waited_for_initial_prev_token = false;
388
389 self.state
391 .pagination_status
392 .set(SharedPaginationStatus::Idle { hit_timeline_start: false });
393
394 return Ok(());
395 }
396
397 self.state
400 .pagination_status
401 .set(SharedPaginationStatus::Idle { hit_timeline_start: false });
402
403 Ok(())
404 }
405
406 #[must_use = "Propagate `VectorDiff` updates via `RoomEventCacheUpdate`"]
409 pub async fn auto_shrink_if_no_subscribers(
410 &mut self,
411 ) -> Result<Option<Vec<VectorDiff<Event>>>, EventCacheError> {
412 let number_of_subscribers = self.state.subscribers_handle.count();
413
414 trace!(number_of_subscribers, "received request to auto-shrink");
415
416 if number_of_subscribers == 0 {
417 self.shrink_to_last_reloaded_chunk().await?;
425
426 Ok(Some(self.state.room_linked_chunk.updates_as_vector_diffs()))
427 } else {
428 Ok(None)
429 }
430 }
431
432 #[instrument(skip_all)]
438 pub async fn remove_events(
439 &mut self,
440 in_memory_events: Vec<(OwnedEventId, Position)>,
441 in_store_events: Vec<(OwnedEventId, Position)>,
442 ) -> Result<(), EventCacheError> {
443 if !in_store_events.is_empty() {
445 let mut positions = in_store_events
446 .into_iter()
447 .map(|(_event_id, position)| position)
448 .collect::<Vec<_>>();
449
450 sort_positions_descending(&mut positions);
451
452 let updates =
453 positions.into_iter().map(|pos| Update::RemoveItem { at: pos }).collect::<Vec<_>>();
454
455 self.apply_store_only_updates(updates).await?;
456 }
457
458 if in_memory_events.is_empty() {
460 return Ok(());
462 }
463
464 self.state
466 .room_linked_chunk
467 .remove_events_by_position(
468 in_memory_events.into_iter().map(|(_event_id, position)| position).collect(),
469 )
470 .expect("failed to remove an event");
471
472 self.propagate_changes().await
473 }
474
475 pub(super) async fn propagate_changes(&mut self) -> Result<(), EventCacheError> {
476 let updates = self.state.room_linked_chunk.store_updates().take();
477
478 self.send_updates_to_store(updates).await
479 }
480
481 async fn apply_store_only_updates(
488 &mut self,
489 updates: Vec<Update<Event, Gap>>,
490 ) -> Result<(), EventCacheError> {
491 self.state.room_linked_chunk.order_tracker.map_updates(&updates);
492 self.send_updates_to_store(updates).await
493 }
494
495 async fn send_updates_to_store(
496 &mut self,
497 updates: Vec<Update<Event, Gap>>,
498 ) -> Result<(), EventCacheError> {
499 let linked_chunk_id = OwnedLinkedChunkId::Room(self.state.room_id.clone());
500
501 send_updates_to_store(
502 &self.store,
503 linked_chunk_id,
504 &self.state.linked_chunk_update_sender,
505 updates,
506 )
507 .await
508 }
509
510 #[must_use = "Propagate `VectorDiff` updates via `RoomEventCacheUpdate`"]
518 pub async fn handle_sync(
519 &mut self,
520 mut timeline: Timeline,
521 read_receipt_event: &MaybeReceiptEventContent,
522 ) -> Result<(bool, Vec<VectorDiff<Event>>), EventCacheError> {
523 let mut prev_batch_token = timeline.prev_batch.take();
524
525 let DeduplicationOutcome {
526 all_events: events,
527 in_memory_duplicated_event_ids,
528 in_store_duplicated_event_ids,
529 non_empty_all_duplicates: all_duplicates,
530 } = filter_duplicate_events(
531 &self.state.own_user_id,
532 &self.store,
533 LinkedChunkId::Room(&self.state.room_id),
534 &self.state.room_linked_chunk,
535 timeline.events,
536 )
537 .await?;
538
539 if !timeline.limited && self.state.room_linked_chunk.events().next().is_some()
552 || all_duplicates
553 {
554 prev_batch_token = None;
555 }
556
557 if all_duplicates {
558 self.post_process_upserted_events(empty(), read_receipt_event.as_ref()).await?;
566
567 return Ok((false, Vec::new()));
568 }
569
570 let has_new_gap = prev_batch_token.is_some();
571
572 if !self.state.waited_for_initial_prev_token && has_new_gap {
575 self.state.waited_for_initial_prev_token = true;
576 }
577
578 self.remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids).await?;
583
584 self.state.room_linked_chunk.push_live_events(
585 prev_batch_token.map(|prev_token| Gap { token: prev_token }),
586 &events,
587 );
588
589 self.propagate_changes().await?;
591
592 self.post_process_upserted_events(events.iter(), read_receipt_event.as_ref()).await?;
594
595 if timeline.limited && has_new_gap {
596 self.shrink_to_last_reloaded_chunk().await?;
602 }
603
604 let timeline_event_diffs = self.room_linked_chunk.updates_as_vector_diffs();
605
606 Ok((has_new_gap, timeline_event_diffs))
607 }
608
609 pub(super) async fn post_process_upserted_events<'i, I>(
615 &mut self,
616 events: I,
617 receipt_event: Option<&ReceiptEventContent>,
618 ) -> Result<(), EventCacheError>
619 where
620 I: Iterator<Item = &'i Event>,
621 {
622 for event in events {
623 self.maybe_apply_new_redaction(event).await?;
624
625 if let Some(bundled_thread) = event.bundled_latest_thread_event() {
627 self.save_events([bundled_thread]).await?;
628 }
629 }
630
631 self.update_read_receipts(receipt_event).await?;
632
633 Ok(())
634 }
635
636 pub async fn update_read_receipts(
639 &mut self,
640 receipt_event: Option<&ReceiptEventContent>,
641 ) -> Result<(), EventCacheError> {
642 let Some(room) = self.state.weak_room.get() else {
643 debug!("can't update read receipts: client's closing");
644 return Ok(());
645 };
646
647 let prev_read_receipts = room.read_receipts().clone();
648 let mut read_receipts = prev_read_receipts.clone();
649
650 let client = room.client();
651 let event_filter = RoomReadReceiptEventFilter::new(&self.state, client.state_store());
652
653 compute_unread_counts(
654 &self.state.own_user_id,
655 receipt_event,
656 &self.state.room_linked_chunk,
657 &event_filter,
658 &mut read_receipts,
659 self.state.back_pagination_queue.as_ref(),
660 )
661 .await;
662
663 if prev_read_receipts != read_receipts {
664 let result = room
668 .update_and_save_room_info(|mut room_info| {
669 room_info.set_read_receipts(read_receipts);
670 (room_info, RoomInfoNotableUpdateReasons::READ_RECEIPT)
671 })
672 .await;
673
674 if let Err(error) = result {
675 error!(room_id = ?room.room_id(), ?error, "Failed to save the changes");
676 }
677 }
678
679 Ok(())
680 }
681
682 #[must_use = "Propagate `VectorDiff` updates via `RoomEventCacheUpdate`"]
684 pub async fn update_thread_summary(
685 &mut self,
686 thread_id: &EventId,
687 new_thread_summary: Option<ThreadSummary>,
688 ) -> Result<Vec<VectorDiff<Event>>, EventCacheError> {
689 let Some((location, mut thread_root_event)) = self.find_event(thread_id).await? else {
690 trace!(%thread_id, "thread root event is missing from the room linked chunk");
691 return Ok(Vec::new());
692 };
693
694 trace!(%thread_id, "updating thread summary: {new_thread_summary:?}");
696 thread_root_event.thread_summary = ThreadSummaryStatus::from_opt(new_thread_summary);
697 self.replace_event_at(location, thread_root_event).await?;
698
699 Ok(self.room_linked_chunk.updates_as_vector_diffs())
700 }
701
702 pub async fn replace_event_at(
709 &mut self,
710 location: EventLocation,
711 event: Event,
712 ) -> Result<(), EventCacheError> {
713 match location {
714 EventLocation::Memory(position) => {
715 self.state
716 .room_linked_chunk
717 .replace_event_at(position, event)
718 .expect("should have been a valid position of an item");
719 self.propagate_changes().await?;
722 }
723 EventLocation::Store => {
724 self.save_events([event]).await?;
725 }
726 }
727
728 Ok(())
729 }
730
731 #[instrument(skip_all)]
735 async fn maybe_apply_new_redaction(&mut self, event: &Event) -> Result<(), EventCacheError> {
736 let Some(target_event_id) =
737 extract_redaction_target(event.raw(), &self.room_version_rules.redaction)
738 else {
739 trace!("missing target event id from the redaction event");
740 return Ok(());
741 };
742
743 let Some((location, mut target_event)) = self.find_event(&target_event_id).await? else {
745 trace!("redacted event is missing from the linked chunk");
746 return Ok(());
747 };
748
749 let target_event_raw = target_event.raw();
750
751 if let Ok(deserialized) = target_event_raw.deserialize()
753 && deserialized.is_redacted()
754 {
755 return Ok(());
756 }
757
758 if let Some(redacted_event) = apply_redaction(
759 target_event_raw,
760 event.raw().cast_ref_unchecked::<SyncRoomRedactionEvent>(),
761 &self.room_version_rules.redaction,
762 ) {
763 target_event.replace_raw(redacted_event.cast_unchecked());
768
769 self.replace_event_at(location, target_event.clone()).await?;
770 }
771
772 Ok(())
773 }
774
775 #[cfg(feature = "e2e-encryption")]
779 #[must_use = "Propagate `VectorDiff` updates via `TimelineVectorDiffs`"]
780 pub(in super::super::super) async fn replace_in_memory_utds(
781 &mut self,
782 resolved_events: &[MaybeResolvedEvent],
783 ) -> Result<Option<Vec<VectorDiff<Event>>>, EventCacheError> {
784 Ok(if self.room_linked_chunk_mut().replace_utds(resolved_events) {
785 let _ = self.room_linked_chunk_mut().store_updates().take();
788
789 self.post_process_upserted_events(
790 resolved_events.iter().filter_map(|resolved_event| resolved_event.as_resolved()),
791 None,
799 )
800 .await?;
801
802 Some(self.room_linked_chunk_mut().updates_as_vector_diffs())
803 } else {
804 None
805 })
806 }
807
808 pub async fn save_events(
810 &mut self,
811 events: impl IntoIterator<Item = Event>,
812 ) -> Result<(), EventCacheError> {
813 let store = self.store.clone();
814 let room_id = self.state.room_id.clone();
815 let events = events.into_iter().collect::<Vec<_>>();
816
817 spawn(async move {
819 for event in events {
820 store.save_event(&room_id, event).await?;
821 }
822 super::Result::Ok(())
823 })
824 .await
825 .expect("joining failed")?;
826
827 Ok(())
828 }
829
830 #[cfg(test)]
831 pub fn is_dirty(&self) -> bool {
832 EventCacheStoreLockGuard::is_dirty(&self.store)
833 }
834}
835
836#[cfg(test)]
837mod tests {
838 use matrix_sdk_base::RoomState;
839 use matrix_sdk_test::{async_test, event_factory::EventFactory};
840 use ruma::{event_id, room_id, user_id};
841
842 use crate::test_utils::logged_in_client;
843
844 #[async_test]
845 async fn test_save_event() {
846 let client = logged_in_client(None).await;
847 let room_id = room_id!("!galette:saucisse.bzh");
848
849 let event_cache = client.event_cache();
850 event_cache.subscribe().unwrap();
851
852 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
853 let event_id = event_id!("$1");
854
855 client.base_client().get_or_create_room(room_id, RoomState::Joined);
856 let room = client.get_room(room_id).unwrap();
857
858 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
859 room_event_cache
860 .inner
861 .state
862 .write()
863 .await
864 .unwrap()
865 .save_events([f.text_msg("hey there").event_id(event_id).into()])
866 .await
867 .unwrap();
868
869 assert!(room_event_cache.find_event(event_id).await.unwrap().is_some());
871 }
872}