1use std::iter::empty;
16
17use eyeball::{AsyncLock, ObservableWriteGuard, SharedObservable};
18use eyeball_im::VectorDiff;
19use matrix_sdk_base::{
20 apply_redaction, check_validity_of_replacement_events,
21 deserialized_responses::ThreadSummary,
22 event_cache::{Event, Gap, store::EventCacheStoreLockGuard, thread::ThreadInfo},
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, Result,
47 deduplicator::{DeduplicationOutcome, filter_duplicate_events},
48 persistence::{
49 find_event, find_event_with_relations, load_linked_chunk_metadata,
50 send_updates_to_store,
51 },
52 states::{ReloadPreprocessing, StateLockReadGuard, StateLockWriteGuard},
53 },
54 EventLocation,
55 event_linked_chunk::{EventLinkedChunk, sort_positions_descending},
56 read_receipts::{
57 MaybeReceiptEventContent, ThreadReadReceiptEventFilter, compute_unread_counts,
58 },
59 room::RoomEventCacheLinkedChunkUpdate,
60 subscriber::SubscribersHandle,
61 },
62 ThreadEventCacheUpdateSender,
63};
64use crate::room::WeakRoom;
65
66pub struct ThreadEventCacheState {
67 pub room_id: OwnedRoomId,
69
70 pub thread_id: OwnedEventId,
73
74 weak_room: WeakRoom,
76
77 pub own_user_id: OwnedUserId,
79
80 room_version_rules: RoomVersionRules,
82
83 thread_linked_chunk: EventLinkedChunk,
85
86 pub thread_info: SharedObservable<ThreadInfo, AsyncLock>,
88
89 pub update_sender: ThreadEventCacheUpdateSender,
94
95 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
100
101 waited_for_initial_prev_token: bool,
106
107 subscribers_handle: SubscribersHandle,
109}
110
111impl ThreadEventCacheState {
112 #[allow(clippy::too_many_arguments)]
123 pub async fn new(
124 room_id: OwnedRoomId,
125 thread_id: OwnedEventId,
126 weak_room: WeakRoom,
127 own_user_id: OwnedUserId,
128 room_version_rules: RoomVersionRules,
129 store_guard: EventCacheStoreLockGuard,
130 update_sender: ThreadEventCacheUpdateSender,
131 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
132 ) -> Result<Self> {
133 let linked_chunk_id = LinkedChunkId::Thread(&room_id, &thread_id);
134
135 let thread_info = store_guard.load_thread_info(&room_id, &thread_id).await?;
140
141 let full_linked_chunk_metadata =
146 match load_linked_chunk_metadata(&store_guard, linked_chunk_id).await {
147 Ok(metas) => metas,
148 Err(err) => {
149 error!("error when loading a linked chunk's metadata from the store: {err}");
150
151 store_guard
153 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
154 .await?;
155
156 None
158 }
159 };
160
161 let linked_chunk = match store_guard
162 .load_last_chunk(linked_chunk_id)
163 .await
164 .map_err(EventCacheError::from)
165 .and_then(|(last_chunk, chunk_identifier_generator)| {
166 lazy_loader::from_last_chunk(last_chunk, chunk_identifier_generator)
167 .map_err(EventCacheError::from)
168 }) {
169 Ok(linked_chunk) => linked_chunk,
170 Err(err) => {
171 error!("error when loading a linked chunk's latest chunk from the store: {err}");
172
173 store_guard
175 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
176 .await?;
177
178 None
179 }
180 };
181
182 Ok(ThreadEventCacheState {
183 room_id,
184 thread_id,
185 weak_room,
186 own_user_id,
187 room_version_rules,
188 thread_linked_chunk: EventLinkedChunk::with_initial_linked_chunk(
189 linked_chunk,
190 full_linked_chunk_metadata,
191 ),
192 thread_info: SharedObservable::new_async(thread_info),
193 update_sender,
194 linked_chunk_update_sender,
195 waited_for_initial_prev_token: false,
196 subscribers_handle: SubscribersHandle::default(),
197 })
198 }
199
200 #[instrument(skip(self, store))]
209 async fn shrink_to_last_reloaded_chunk(
210 &mut self,
211 store: &EventCacheStoreLockGuard,
212 ) -> Result<()> {
213 let linked_chunk_id = LinkedChunkId::Thread(&self.room_id, &self.thread_id);
215
216 let full_linked_chunk_metadata =
217 match load_linked_chunk_metadata(store, linked_chunk_id).await {
218 Ok(metas) => metas,
219 Err(err) => {
220 error!("error when reloading a linked chunk's metadata from the store: {err}");
221
222 store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await?;
224
225 None
227 }
228 };
229
230 let (last_chunk, chunk_identifier_generator) =
231 match store.load_last_chunk(linked_chunk_id).await {
232 Ok(pair) => pair,
233
234 Err(err) => {
235 error!("error when reloading a linked chunk from memory: {err}");
237
238 store.handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear]).await?;
240
241 (None, ChunkIdentifierGenerator::new_from_scratch())
243 }
244 };
245
246 debug!("unloading the linked chunk, and resetting it to its last chunk");
247
248 if let Err(err) = self.thread_linked_chunk.shrink_to_last_reloaded_chunk(
251 last_chunk,
252 chunk_identifier_generator,
253 full_linked_chunk_metadata,
254 ) {
255 error!("error when replacing the linked chunk: {err}");
256
257 self.thread_linked_chunk.reset();
258 self.propagate_changes(store).await?;
259
260 self.waited_for_initial_prev_token = false;
264
265 return Ok(());
266 }
267
268 Ok(())
269 }
270
271 pub async fn propagate_changes(&mut self, store: &EventCacheStoreLockGuard) -> Result<()> {
272 let updates = self.thread_linked_chunk.store_updates().take();
273
274 self.send_updates_to_store(updates, store).await
275 }
276
277 async fn send_updates_to_store(
278 &mut self,
279 updates: Vec<Update<Event, Gap>>,
280 store: &EventCacheStoreLockGuard,
281 ) -> Result<()> {
282 let linked_chunk_id =
283 OwnedLinkedChunkId::Thread(self.room_id.clone(), self.thread_id.clone());
284
285 send_updates_to_store(store, linked_chunk_id, &self.linked_chunk_update_sender, updates)
286 .await
287 }
288}
289
290impl<'a> StateLockReadGuard<'a, ThreadEventCacheState> {
291 pub fn thread_linked_chunk(&self) -> &EventLinkedChunk {
293 &self.state.thread_linked_chunk
294 }
295
296 pub fn subscribers_handle(&self) -> &SubscribersHandle {
298 &self.state.subscribers_handle
299 }
300
301 pub async fn compute_thread_summary(&self) -> Result<Option<ThreadSummary>> {
303 let latest_event_id = {
305 let mut latest_event_id = self
311 .thread_linked_chunk()
312 .revents()
313 .find(|(_position, event)| {
314 crate::latest_events::filter_timeline_event(
315 event,
316 None,
317 &self.state.own_user_id,
318 None,
319 )
320 .is_break()
321 })
322 .and_then(|(_position, event)| event.event_id().map(ToOwned::to_owned));
323
324 if let Some(event_id) = &latest_event_id
329 && let Some((original_event, edits)) = self
330 .find_event_with_relations(event_id, Some(vec![RelationType::Replacement]))
331 .await?
332 {
333 let latest_valid_edit = edits.into_iter().rfind(|edit| {
334 let original_json = original_event.raw();
335 let original_encryption_info = original_event.encryption_info();
336 let replacement_json = edit.raw();
337 let replacement_encryption_info = edit.encryption_info();
338
339 check_validity_of_replacement_events(
340 original_json,
341 original_encryption_info.map(|v| &**v),
342 replacement_json,
343 replacement_encryption_info.map(|v| &**v),
344 )
345 .is_ok()
346 });
347
348 if let Some(latest_valid_edit) = latest_valid_edit {
349 latest_event_id = latest_valid_edit.event_id().map(ToOwned::to_owned);
350 }
351 }
352
353 latest_event_id
354 };
355
356 let num_replies = {
365 let thread_replies = self
366 .store
367 .find_event_relations(&self.room_id, &self.thread_id, Some(&[RelationType::Thread]))
368 .await?;
369 thread_replies.len().try_into().unwrap_or(u32::MAX)
370 };
371
372 let summary = if num_replies > 0 {
373 Some(ThreadSummary { num_replies, latest_reply: latest_event_id })
374 } else {
375 None
376 };
377
378 Ok(summary)
379 }
380
381 pub(in super::super) async fn find_event(
383 &self,
384 event_id: &EventId,
385 ) -> Result<Option<(EventLocation, Event)>> {
386 find_event(event_id, &self.room_id, &self.thread_linked_chunk, &self.store).await
387 }
388
389 pub async fn find_event_with_relations(
391 &self,
392 event_id: &EventId,
393 filters: Option<Vec<RelationType>>,
394 ) -> Result<Option<(Event, Vec<Event>)>> {
395 find_event_with_relations(
396 event_id,
397 &self.room_id,
398 filters,
399 &self.thread_linked_chunk,
400 &self.store,
401 )
402 .await
403 }
404}
405
406impl<'a> StateLockWriteGuard<'a, ThreadEventCacheState> {
407 pub fn thread_linked_chunk(&self) -> &EventLinkedChunk {
409 &self.state.thread_linked_chunk
410 }
411
412 pub fn thread_linked_chunk_mut(&mut self) -> &mut EventLinkedChunk {
414 &mut self.state.thread_linked_chunk
415 }
416
417 pub fn waited_for_initial_prev_token(&self) -> bool {
419 self.state.waited_for_initial_prev_token
420 }
421
422 pub fn waited_for_initial_prev_token_mut(&mut self) -> &mut bool {
424 &mut self.state.waited_for_initial_prev_token
425 }
426
427 #[must_use = "Propagate `VectorDiff` updates via `TimelineVectorDiffs`"]
433 pub async fn reload(
434 &mut self,
435 preprocessing: ReloadPreprocessing,
436 ) -> Result<Vec<VectorDiff<Event>>> {
437 match preprocessing {
438 ReloadPreprocessing::ForgetAll => {
439 self.thread_linked_chunk_mut().reset();
442 self.state.propagate_changes(&self.store).await?;
443
444 *self.waited_for_initial_prev_token_mut() = false;
448 }
449
450 ReloadPreprocessing::None => {}
451 }
452
453 self.state.shrink_to_last_reloaded_chunk(&self.store).await?;
454
455 Ok(self.thread_linked_chunk_mut().updates_as_vector_diffs())
456 }
457
458 #[must_use = "Propagate `VectorDiff` updates via `TimelineVectorDiffs`"]
459 pub async fn handle_sync(
460 &mut self,
461 timeline: Timeline,
462 read_receipts: &MaybeReceiptEventContent,
463 ) -> Result<(bool, Vec<VectorDiff<Event>>)> {
464 let prev_batch_token = &timeline.prev_batch;
465
466 let DeduplicationOutcome {
467 all_events: events,
468 in_memory_duplicated_event_ids,
469 in_store_duplicated_event_ids,
470 non_empty_all_duplicates: all_duplicates,
471 } = filter_duplicate_events(
472 &self.state.own_user_id,
473 &self.store,
474 LinkedChunkId::Thread(&self.state.room_id, &self.state.thread_id),
475 &self.state.thread_linked_chunk,
476 timeline.events,
477 )
478 .await?;
479
480 if all_duplicates {
481 self.post_process_upserted_events(empty(), read_receipts.as_ref()).await?;
489
490 return Ok((false, Vec::new()));
491 }
492
493 let has_new_gap = prev_batch_token.is_some();
494
495 if !self.state.waited_for_initial_prev_token && has_new_gap {
498 self.state.waited_for_initial_prev_token = true;
499 }
500
501 self.remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids).await?;
506
507 self.state.thread_linked_chunk.push_live_events(
508 prev_batch_token.as_ref().map(|prev_token| Gap { token: prev_token.clone() }),
509 &events,
510 );
511
512 self.state.propagate_changes(&self.store).await?;
514
515 self.post_process_upserted_events(events.iter(), read_receipts.as_ref()).await?;
517
518 if timeline.limited && has_new_gap {
519 self.state.shrink_to_last_reloaded_chunk(&self.store).await?;
525 }
526
527 let timeline_event_diffs = self.state.thread_linked_chunk.updates_as_vector_diffs();
528
529 Ok((has_new_gap, timeline_event_diffs))
530 }
531
532 pub(super) async fn post_process_upserted_events<'i, I>(
534 &mut self,
535 events: I,
536 receipt_event: Option<&ReceiptEventContent>,
537 ) -> Result<()>
538 where
539 I: Iterator<Item = &'i Event>,
540 {
541 for event in events {
542 self.maybe_apply_new_redaction(event).await?;
544
545 if let Some(bundled_thread) = event.bundled_latest_thread_event() {
547 self.save_events([bundled_thread]).await?;
548 }
549 }
550
551 self.update_read_receipts(receipt_event).await?;
552
553 Ok(())
554 }
555
556 pub async fn update_read_receipts(
559 &mut self,
560 receipt_event: Option<&ReceiptEventContent>,
561 ) -> Result<()> {
562 let Some(room) = self.state.weak_room.get() else {
563 debug!("can't update read receipts: client's closing");
564 return Ok(());
565 };
566
567 let prev_read_receipts = self.state.thread_info.read().await.read_receipts.clone();
568 let mut read_receipts = prev_read_receipts.clone();
569
570 let client = room.client();
571 let event_filter = ThreadReadReceiptEventFilter::new(&self.state, client.state_store());
572
573 compute_unread_counts(
574 &self.state.own_user_id,
575 receipt_event,
576 &self.state.thread_linked_chunk,
577 &event_filter,
578 &mut read_receipts,
579 None,
580 )
581 .await;
582
583 if prev_read_receipts != read_receipts {
584 let mut thread_info = self.state.thread_info.write().await;
587
588 ObservableWriteGuard::update(&mut thread_info, |thread_info| {
589 thread_info.read_receipts = read_receipts;
590 });
591
592 let room_id = &self.state.room_id;
593 let thread_id = &self.state.thread_id;
594
595 if let Err(error) =
596 self.store.update_thread_info(room_id, thread_id, &thread_info).await
597 {
598 error!(?room_id, ?thread_id, ?error, "Failed to update the `ThreadInfo`");
599 }
600 }
601
602 Ok(())
603 }
604
605 #[instrument(skip_all)]
609 async fn maybe_apply_new_redaction(&mut self, event: &Event) -> Result<()> {
610 let Some(event_id) =
611 extract_redaction_target(event.raw(), &self.room_version_rules.redaction)
612 else {
613 return Ok(());
614 };
615
616 let Some((location, mut target_event)) = self.find_event(&event_id).await? else {
618 trace!("redacted event is missing from the linked chunk");
619 return Ok(());
620 };
621
622 let target_event_raw = target_event.raw();
623
624 if let Ok(deserialized) = target_event_raw.deserialize()
626 && deserialized.is_redacted()
627 {
628 return Ok(());
629 }
630
631 if let Some(redacted_event) = apply_redaction(
632 target_event_raw,
633 event.raw().cast_ref_unchecked::<SyncRoomRedactionEvent>(),
634 &self.room_version_rules.redaction,
635 ) {
636 target_event.replace_raw(redacted_event.cast_unchecked());
641
642 self.replace_event_at(location, target_event.clone()).await?;
643 }
644
645 Ok(())
646 }
647
648 pub(super) async fn find_event(
650 &self,
651 event_id: &EventId,
652 ) -> Result<Option<(EventLocation, Event)>> {
653 find_event(event_id, &self.room_id, &self.thread_linked_chunk, &self.store).await
654 }
655
656 pub async fn replace_event_at(
663 &mut self,
664 location: EventLocation,
665 new_event: Event,
666 ) -> Result<()> {
667 match location {
668 EventLocation::Memory(position) => {
669 self.state
670 .thread_linked_chunk
671 .replace_event_at(position, new_event)
672 .expect("should have been a valid position of an item");
673 self.state.propagate_changes(&self.store).await?;
676 }
677 EventLocation::Store => {
678 self.save_events([new_event]).await?;
679 }
680 }
681
682 Ok(())
683 }
684
685 pub async fn save_events(&mut self, events: impl IntoIterator<Item = Event>) -> Result<()> {
687 let store = self.store.clone();
688 let room_id = self.state.room_id.clone();
689 let events = events.into_iter().collect::<Vec<_>>();
690
691 spawn(async move {
693 for event in events {
694 store.save_event(&room_id, event).await?;
695 }
696
697 Result::Ok(())
698 })
699 .await
700 .expect("joining failed")?;
701
702 Ok(())
703 }
704
705 #[instrument(skip_all)]
710 pub async fn remove_events(
711 &mut self,
712 in_memory_events: Vec<(OwnedEventId, Position)>,
713 in_store_events: Vec<(OwnedEventId, Position)>,
714 ) -> Result<()> {
715 if !in_store_events.is_empty() {
717 let mut positions = in_store_events
718 .into_iter()
719 .map(|(_event_id, position)| position)
720 .collect::<Vec<_>>();
721
722 sort_positions_descending(&mut positions);
723
724 let updates =
725 positions.into_iter().map(|pos| Update::RemoveItem { at: pos }).collect::<Vec<_>>();
726
727 self.apply_store_only_updates(updates).await?;
728 }
729
730 if in_memory_events.is_empty() {
732 return Ok(());
734 }
735
736 self.state
738 .thread_linked_chunk
739 .remove_events_by_position(
740 in_memory_events.into_iter().map(|(_event_id, position)| position).collect(),
741 )
742 .expect("failed to remove an event");
743
744 self.state.propagate_changes(&self.store).await
745 }
746
747 #[must_use = "Propagate `VectorDiff` updates via `ThreadEventCacheUpdate`"]
750 pub async fn auto_shrink_if_no_subscribers(
751 &mut self,
752 ) -> Result<Option<Vec<VectorDiff<Event>>>> {
753 let number_of_subscribers = self.state.subscribers_handle.count();
754
755 trace!(number_of_subscribers, "received request to auto-shrink");
756
757 if number_of_subscribers == 0 {
758 self.state.shrink_to_last_reloaded_chunk(&self.store).await?;
766
767 Ok(Some(self.state.thread_linked_chunk.updates_as_vector_diffs()))
768 } else {
769 Ok(None)
770 }
771 }
772
773 async fn apply_store_only_updates(&mut self, updates: Vec<Update<Event, Gap>>) -> Result<()> {
780 self.state.thread_linked_chunk.order_tracker.map_updates(&updates);
781 self.state.send_updates_to_store(updates, &self.store).await
782 }
783
784 #[cfg(feature = "e2e-encryption")]
788 #[must_use = "Propagate `VectorDiff` updates via `TimelineVectorDiffs`"]
789 pub(in super::super) fn replace_in_memory_utds(
790 &mut self,
791 resolved_events: &[MaybeResolvedEvent],
792 ) -> Result<Option<Vec<VectorDiff<Event>>>> {
793 Ok(if self.thread_linked_chunk_mut().replace_utds(resolved_events) {
794 Some(self.thread_linked_chunk_mut().updates_as_vector_diffs())
795 } else {
796 None
797 })
798 }
799}