1mod updates;
16
17use std::{cmp::Ordering, collections::BTreeSet, fmt, sync::Arc};
18
19use eyeball_im::VectorDiff;
20use futures_util::{StreamExt as _, stream};
21use matrix_sdk_base::{
22 apply_redaction,
23 event_cache::{Event, Gap},
24 linked_chunk::{LinkedChunkId, OwnedLinkedChunkId, Position, Update},
25 serde_helpers::{extract_redaction_target, extract_relation},
26 sync::Timeline,
27 task_monitor::BackgroundTaskHandle,
28};
29use matrix_sdk_common::executor::spawn;
30use ruma::{
31 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
32 events::{relation::RelationType, room::redaction::SyncRoomRedactionEvent},
33 room_version_rules::RoomVersionRules,
34};
35use tokio::sync::broadcast::{Receiver, Sender};
36use tracing::{debug, instrument, trace, warn};
37
38pub(super) use self::updates::PinnedEventsCacheUpdateSender;
39#[cfg(feature = "e2e-encryption")]
40use super::super::redecryptor::MaybeResolvedEvent;
41use super::{
42 super::{
43 EventCacheError, EventsOrigin, Result,
44 deduplicator::{DeduplicationOutcome, filter_duplicate_events},
45 persistence::{find_event, send_updates_to_store},
46 states::{
47 CacheStateLock, ReloadPreprocessing, StateLock, StateLockWriteGuard,
48 selectors::PinnedEventsStateSelector,
49 },
50 },
51 EventLocation, TimelineVectorDiffs,
52 event_linked_chunk::{EventLinkedChunk, sort_positions_descending},
53 room::RoomEventCacheLinkedChunkUpdate,
54};
55use crate::{Room, config::RequestConfig, room::WeakRoom};
56
57pub struct PinnedEventsCacheState {
58 room_id: OwnedRoomId,
60
61 own_user_id: OwnedUserId,
63
64 room_version_rules: RoomVersionRules,
66
67 chunk: EventLinkedChunk,
74
75 pub update_sender: PinnedEventsCacheUpdateSender,
77
78 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
83}
84
85#[cfg(not(tarpaulin_include))]
86impl fmt::Debug for PinnedEventsCacheState {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.debug_struct("PinnedEventsCacheState")
89 .field("room_id", &self.room_id)
90 .field("chunk", &self.chunk)
91 .finish_non_exhaustive()
92 }
93}
94
95impl<'a> StateLockWriteGuard<'a, PinnedEventsCacheState> {
96 #[must_use = "Propagate `VectorDiff` updates via `TimelineVectorDiffs`"]
102 pub async fn reload(
103 &mut self,
104 preprocessing: ReloadPreprocessing,
105 ) -> Result<Vec<VectorDiff<Event>>> {
106 match preprocessing {
107 ReloadPreprocessing::ForgetAll => {
108 self.state.chunk.reset();
111 self.propagate_changes().await?;
112 }
113
114 ReloadPreprocessing::None => {}
115 }
116
117 self.reload_from_storage().await?;
120
121 Ok(self.state.chunk.updates_as_vector_diffs())
122 }
123
124 async fn handle_sync(&mut self, timeline: Timeline) -> Result<()> {
125 let DeduplicationOutcome {
126 all_events: events,
127 in_memory_duplicated_event_ids,
128 in_store_duplicated_event_ids,
129 non_empty_all_duplicates: all_duplicates,
130 } = filter_duplicate_events(
131 &self.state.own_user_id,
132 &self.store,
133 LinkedChunkId::PinnedEvents(&self.state.room_id),
134 &self.state.chunk,
135 timeline.events,
136 )
137 .await?;
138
139 if all_duplicates {
140 return Ok(());
143 }
144
145 self.remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids).await?;
151
152 self.state.chunk.push_live_events(None, &events);
154
155 self.propagate_changes().await?;
156 self.notify_subscribers(EventsOrigin::Sync);
157
158 for event in &events {
160 self.maybe_apply_new_redaction(event).await?;
162 }
163
164 Ok(())
165 }
166
167 #[instrument(skip_all)]
172 pub async fn remove_events(
173 &mut self,
174 in_memory_events: Vec<(OwnedEventId, Position)>,
175 in_store_events: Vec<(OwnedEventId, Position)>,
176 ) -> Result<()> {
177 if !in_store_events.is_empty() {
179 let mut positions = in_store_events
180 .into_iter()
181 .map(|(_event_id, position)| position)
182 .collect::<Vec<_>>();
183
184 sort_positions_descending(&mut positions);
185
186 let updates =
187 positions.into_iter().map(|pos| Update::RemoveItem { at: pos }).collect::<Vec<_>>();
188
189 self.apply_store_only_updates(updates).await?;
190 }
191
192 if in_memory_events.is_empty() {
194 return Ok(());
196 }
197
198 self.state
200 .chunk
201 .remove_events_by_position(
202 in_memory_events.into_iter().map(|(_event_id, position)| position).collect(),
203 )
204 .expect("failed to remove an event");
205
206 self.propagate_changes().await
207 }
208
209 async fn apply_store_only_updates(&mut self, updates: Vec<Update<Event, Gap>>) -> Result<()> {
215 self.send_updates_to_store(updates).await
216 }
217
218 #[instrument(skip_all)]
221 async fn maybe_apply_new_redaction(&mut self, event: &Event) -> Result<()> {
222 let Some(event_id) =
223 extract_redaction_target(event.raw(), &self.room_version_rules.redaction)
224 else {
225 return Ok(());
226 };
227
228 let Some((location, mut target_event)) = self.find_event(&event_id).await? else {
230 trace!("redacted event is missing from the linked chunk");
231 return Ok(());
232 };
233
234 let target_event_raw = target_event.raw();
235
236 if let Ok(deserialized) = target_event_raw.deserialize()
238 && deserialized.is_redacted()
239 {
240 return Ok(());
241 }
242
243 if let Some(redacted_event) = apply_redaction(
244 target_event_raw,
245 event.raw().cast_ref_unchecked::<SyncRoomRedactionEvent>(),
246 &self.room_version_rules.redaction,
247 ) {
248 target_event.replace_raw(redacted_event.cast_unchecked());
256
257 self.replace_event_at(location, target_event.clone()).await?;
258 }
259
260 Ok(())
261 }
262
263 pub(super) async fn find_event(
265 &self,
266 event_id: &EventId,
267 ) -> Result<Option<(EventLocation, Event)>> {
268 find_event(event_id, &self.room_id, &self.chunk, &self.store).await
269 }
270
271 pub async fn replace_event_at(
278 &mut self,
279 location: EventLocation,
280 new_event: Event,
281 ) -> Result<()> {
282 match location {
283 EventLocation::Memory(position) => {
284 self.state
285 .chunk
286 .replace_event_at(position, new_event)
287 .expect("should have been a valid position of an item");
288 self.propagate_changes().await?;
291 }
292 EventLocation::Store => {
293 self.save_events([new_event]).await?;
294 }
295 }
296
297 Ok(())
298 }
299
300 pub async fn save_events(&mut self, events: impl IntoIterator<Item = Event>) -> Result<()> {
302 let store = self.store.clone();
303 let room_id = self.state.room_id.clone();
304 let events = events.into_iter().collect::<Vec<_>>();
305
306 spawn(async move {
308 for event in events {
309 store.save_event(&room_id, event).await?;
310 }
311
312 Result::Ok(())
313 })
314 .await
315 .expect("joining failed")?;
316
317 Ok(())
318 }
319
320 async fn reload_from_storage(&mut self) -> Result<()> {
323 let room_id = self.state.room_id.clone();
324 let linked_chunk_id = LinkedChunkId::PinnedEvents(&room_id);
325
326 let (last_chunk, chunk_id_gen) = self.store.load_last_chunk(linked_chunk_id).await?;
327
328 let Some(last_chunk) = last_chunk else {
329 if self.state.chunk.events().next().is_some() {
332 self.state.chunk.reset();
333 self.notify_subscribers(EventsOrigin::Sync);
334 }
335
336 return Ok(());
337 };
338
339 {
340 let mut current_chunk_identifier = last_chunk.identifier;
341 self.state.chunk.shrink_to_last_reloaded_chunk(
342 Some(last_chunk),
343 chunk_id_gen,
344 None,
346 )?;
347
348 while let Some(previous_chunk) =
350 self.store.load_previous_chunk(linked_chunk_id, current_chunk_identifier).await?
351 {
352 current_chunk_identifier = previous_chunk.identifier;
353 self.state.chunk.insert_new_chunk_as_first(previous_chunk)?;
354 }
355 }
356
357 self.state.chunk.store_updates().take();
359
360 self.notify_subscribers(EventsOrigin::Cache);
362
363 Ok(())
364 }
365
366 async fn replace_all_events(&mut self, new_events: Vec<Event>) -> Result<()> {
367 trace!("resetting all pinned events in linked chunk");
368
369 let previous_pinned_event_ids = self.state.current_event_ids();
370
371 if new_events
372 .iter()
373 .filter_map(|e| e.event_id())
374 .map(ToOwned::to_owned)
375 .collect::<BTreeSet<_>>()
376 == previous_pinned_event_ids.into_iter().collect()
377 {
378 return Ok(());
380 }
381
382 if self.state.chunk.events().next().is_some() {
383 self.state.chunk.reset();
384 }
385
386 self.state.chunk.push_live_events(None, &new_events);
387 self.propagate_changes().await?;
388 self.notify_subscribers(EventsOrigin::Sync);
389
390 Ok(())
391 }
392
393 pub async fn propagate_changes(&mut self) -> Result<()> {
396 let updates = self.state.chunk.store_updates().take();
397
398 self.send_updates_to_store(updates).await
399 }
400
401 async fn send_updates_to_store(&mut self, updates: Vec<Update<Event, Gap>>) -> Result<()> {
402 let linked_chunk_id = OwnedLinkedChunkId::PinnedEvents(self.room_id.clone());
403
404 send_updates_to_store(
405 &self.store,
406 linked_chunk_id,
407 &self.state.linked_chunk_update_sender,
408 updates,
409 )
410 .await
411 }
412
413 fn notify_subscribers(&mut self, origin: EventsOrigin) {
415 let diffs = self.state.chunk.updates_as_vector_diffs();
416
417 if !diffs.is_empty() {
418 self.update_sender.send(TimelineVectorDiffs { diffs, origin });
419 }
420 }
421}
422
423impl PinnedEventsCacheState {
424 pub(super) fn current_event_ids(&self) -> Vec<OwnedEventId> {
426 self.chunk
427 .events()
428 .filter_map(|(_position, event)| event.event_id().map(ToOwned::to_owned))
429 .collect()
430 }
431
432 fn has_exactly_pinned_events(&self, pinned_event_ids: &[OwnedEventId]) -> bool {
441 let pinned_event_ids: BTreeSet<&EventId> =
442 pinned_event_ids.iter().map(|event_id| &**event_id).collect();
443 let event_ids: BTreeSet<&EventId> =
444 self.chunk.events().filter_map(|(_position, event)| event.event_id()).collect();
445
446 if !pinned_event_ids.is_subset(&event_ids) {
447 return false;
448 }
449
450 self.chunk.events().all(|(_position, event)| {
454 event.event_id().is_some_and(|event_id| pinned_event_ids.contains(event_id))
455 || extract_relation(event.raw()).is_some_and(|(relation_type, related_event_id)| {
456 relation_type != RelationType::Thread && event_ids.contains(&*related_event_id)
457 })
458 || extract_redaction_target(event.raw(), &self.room_version_rules.redaction)
459 .is_some_and(|redacted_event_id| event_ids.contains(&*redacted_event_id))
460 })
461 }
462}
463
464#[derive(Clone)]
468pub struct PinnedEventsCache {
469 inner: Arc<PinnedEventsCacheInner>,
470
471 _task: Arc<BackgroundTaskHandle>,
474}
475
476struct PinnedEventsCacheInner {
478 room_id: OwnedRoomId,
480
481 state: CacheStateLock<PinnedEventsStateSelector>,
485}
486
487impl PinnedEventsCache {
488 pub(in super::super) async fn new(
490 weak_room: &WeakRoom,
491 own_user_id: OwnedUserId,
492 room_version_rules: RoomVersionRules,
493 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
494 state: &StateLock,
495 ) -> Result<Self> {
496 let room = weak_room.get().ok_or(EventCacheError::ClientDropped)?;
497 let room_id = room.room_id().to_owned();
498
499 let cache_state = state
500 .try_insert_once_with(
501 PinnedEventsStateSelector::new(room_id.clone()),
502 |_store_guard| async {
503 Ok(PinnedEventsCacheState {
504 room_id: room_id.clone(),
505 own_user_id,
506 room_version_rules,
507 chunk: EventLinkedChunk::new(),
508 update_sender: PinnedEventsCacheUpdateSender::new(),
509 linked_chunk_update_sender,
510 })
511 },
512 )
513 .await?;
514
515 let inner = Arc::new(PinnedEventsCacheInner { room_id, state: cache_state });
516
517 let task = room
518 .client()
519 .task_monitor()
520 .spawn_infinite_task(
521 "pinned_event_listener_task",
522 Self::pinned_event_listener_task(room, inner.clone()),
523 )
524 .abort_on_drop();
525
526 Ok(Self { inner, _task: Arc::new(task) })
527 }
528
529 pub fn room_id(&self) -> &RoomId {
531 &self.inner.room_id
532 }
533
534 pub(super) fn state(&self) -> &CacheStateLock<PinnedEventsStateSelector> {
536 &self.inner.state
537 }
538
539 pub async fn subscribe(&self) -> Result<(Vec<Event>, Receiver<TimelineVectorDiffs>)> {
541 let guard = self.inner.state.read().await?;
542 let events = guard.state.chunk.events().map(|(_position, item)| item.clone()).collect();
543
544 let recv = guard.state.update_sender.new_pinned_events_receiver();
545
546 Ok((events, recv))
547 }
548
549 #[cfg(feature = "e2e-encryption")]
553 pub(in super::super) async fn replace_in_memory_utds(
554 &self,
555 resolved_events: &[MaybeResolvedEvent],
556 ) -> Result<()> {
557 let mut state = self.inner.state.write().await?;
558
559 let _ = state.state.chunk.store_updates().take();
562
563 if state.state.chunk.replace_utds(resolved_events) {
564 state.propagate_changes().await?;
565 state.notify_subscribers(EventsOrigin::Cache);
566 }
567
568 Ok(())
569 }
570
571 #[instrument(skip_all, fields(room_id = %self.inner.room_id))]
573 pub(super) async fn handle_joined_room_update(&self, timeline: Timeline) -> Result<()> {
574 self.handle_timeline(timeline).await
575 }
576
577 #[instrument(skip_all, fields(room_id = %self.inner.room_id))]
579 pub(super) async fn handle_left_room_update(&self, timeline: Timeline) -> Result<()> {
580 self.handle_timeline(timeline).await
581 }
582
583 async fn handle_timeline(&self, timeline: Timeline) -> Result<()> {
586 if timeline.events.is_empty() {
587 return Ok(());
588 }
589
590 trace!("adding new {} events", timeline.events.len());
591
592 self.inner.state.write().await?.handle_sync(timeline).await
593 }
594
595 #[instrument(fields(%room_id = room.room_id()), skip(room, inner))]
596 async fn pinned_event_listener_task(room: Room, inner: Arc<PinnedEventsCacheInner>) {
597 debug!("pinned events listener task started");
598
599 let max_pinned_events_to_load = room
600 .client()
601 .event_cache()
602 .expect("Pinned-events are running inside the Event Cache")
603 .config()
604 .max_pinned_events_to_load;
605
606 let reload_from_network = async |room: Room| {
607 let events = match Self::reload_pinned_events(room).await {
608 Ok(Some(events)) => events,
609 Ok(None) => Vec::new(),
610 Err(err) => {
611 warn!("error when loading pinned events: {err}");
612 return;
613 }
614 };
615
616 match inner.state.write().await {
619 Ok(mut guard) => {
620 guard.replace_all_events(events).await.unwrap_or_else(|err| {
621 warn!("error when replacing pinned events: {err}");
622 });
623 }
624
625 Err(err) => {
626 warn!("error when acquiring write lock to replace pinned events: {err}");
627 }
628 }
629 };
630
631 match inner.state.write().await {
633 Ok(mut guard) => {
634 guard.reload_from_storage().await.unwrap_or_else(|err| {
636 warn!("error when reloading pinned events from storage, at start: {err}");
637 });
638
639 let actual_pinned_events = pinned_event_ids_to_load(
642 max_pinned_events_to_load,
643 room.pinned_event_ids().unwrap_or_default(),
644 );
645
646 if !guard.state.has_exactly_pinned_events(&actual_pinned_events) {
647 drop(guard);
649 reload_from_network(room.clone()).await;
650 }
651 }
652
653 Err(err) => {
654 warn!("error when acquiring write lock to initialize pinned events: {err}");
655 }
656 }
657
658 let weak_room = WeakRoom::new(room.client().downgrade(), room.room_id().to_owned());
659
660 let mut stream = room.pinned_event_ids_stream();
661
662 drop(room);
663
664 while let Some(new_list) = stream.next().await {
666 trace!("handling update");
667
668 let Some(room) = weak_room.get() else {
669 debug!("room has been dropped, ending pinned events listener task");
670 break;
671 };
672
673 let new_list = pinned_event_ids_to_load(max_pinned_events_to_load, new_list);
674
675 let guard = match inner.state.read().await {
676 Ok(guard) => guard,
677 Err(err) => {
678 warn!("error when acquiring read lock to handle pinned events update: {err}");
679 break;
680 }
681 };
682
683 if guard.state.has_exactly_pinned_events(&new_list) {
685 continue;
687 }
688
689 drop(guard);
690
691 reload_from_network(room).await;
693 }
694
695 debug!("pinned events listener task ended");
696 }
697
698 async fn reload_pinned_events(room: Room) -> Result<Option<Vec<Event>>> {
707 let (max_concurrent_requests, max_pinned_events_to_load) = {
708 let client = room.client();
709 let config = client
710 .event_cache()
711 .expect("Pinned-events are running inside the Event Cache")
712 .config();
713
714 (config.max_pinned_events_concurrent_requests, config.max_pinned_events_to_load)
715 };
716
717 let pinned_event_ids = pinned_event_ids_to_load(
718 max_pinned_events_to_load,
719 room.pinned_event_ids().unwrap_or_default(),
720 );
721
722 if pinned_event_ids.is_empty() {
723 return Ok(Some(Vec::new()));
724 }
725
726 let mut num_successful_loads = 0;
727
728 let mut loaded_events: Vec<Event> =
729 stream::iter(pinned_event_ids.clone().into_iter().map(|event_id| {
730 let room = room.clone();
731 let filter = vec![RelationType::Annotation, RelationType::Replacement];
732 let request_config = RequestConfig::default().retry_limit(3);
733
734 async move {
735 let (target, mut relations) = room
736 .load_or_fetch_event_with_relations(
737 &event_id,
738 Some(filter),
739 Some(request_config),
740 )
741 .await?;
742
743 relations.insert(0, target);
744 Ok::<_, crate::Error>(relations)
745 }
746 }))
747 .buffer_unordered(max_concurrent_requests)
748 .inspect(|result| {
750 if result.is_ok() {
751 num_successful_loads += 1;
752 }
753 })
754 .flat_map(stream::iter)
756 .flat_map(stream::iter)
758 .collect()
759 .await;
760
761 if num_successful_loads != pinned_event_ids.len() {
762 warn!(
763 "only successfully loaded {} out of {} pinned events",
764 num_successful_loads,
765 pinned_event_ids.len()
766 );
767 }
768
769 if loaded_events.is_empty() {
770 return Err(EventCacheError::UnableToLoadPinnedEvents);
774 }
775
776 loaded_events.sort_by(compare_pinned_items);
781
782 Ok(Some(loaded_events))
783 }
784}
785
786impl fmt::Debug for PinnedEventsCache {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 f.debug_struct("PinnedEventsCache").finish_non_exhaustive()
789 }
790}
791
792#[inline]
798fn pinned_event_ids_to_load(
799 max_events_to_load: usize,
800 pinned_event_ids: Vec<OwnedEventId>,
801) -> Vec<OwnedEventId> {
802 pinned_event_ids.into_iter().rev().take(max_events_to_load).rev().collect()
803}
804
805fn compare_pinned_items(a: &Event, b: &Event) -> Ordering {
806 let a_time: Option<MilliSecondsSinceUnixEpoch> = a.timestamp_raw();
807 let b_time: Option<MilliSecondsSinceUnixEpoch> = b.timestamp_raw();
808
809 compare_by_optional_timestamp(a_time, b_time)
810}
811
812fn compare_by_optional_timestamp(
813 a: Option<MilliSecondsSinceUnixEpoch>,
814 b: Option<MilliSecondsSinceUnixEpoch>,
815) -> Ordering {
816 match (a, b) {
817 (None, None) => Ordering::Equal,
818 (None, Some(_)) => Ordering::Greater,
819 (Some(_), None) => Ordering::Less,
820 (Some(a), Some(b)) => a.cmp(&b),
821 }
822}
823
824#[cfg(not(target_family = "wasm"))]
825#[cfg(test)]
826mod tests {
827 use proptest::prelude::*;
828 use ruma::UInt;
829
830 use super::*;
831
832 fn any_timestamp() -> impl Strategy<Value = Option<MilliSecondsSinceUnixEpoch>> {
833 prop::option::of(
834 any::<u32>().prop_map(|value| MilliSecondsSinceUnixEpoch(UInt::from(value))),
835 )
836 }
837
838 #[test]
839 fn sort_pinned_events_never_panics_only_nones() {
840 let mut vec = vec![None; 100_000];
841 vec.sort_by(|a, b| compare_by_optional_timestamp(*a, *b))
842 }
843
844 proptest! {
845 #[test]
846 fn sort_pinned_events_never_panics(mut v in prop::collection::vec(any_timestamp(), 0..1000)) {
847 v.sort_by(
848 |a, b| compare_by_optional_timestamp(*a, *b))
849 }
850
851 #[test]
852 fn compare_pinned_events_reflexive(a in any_timestamp()) {
853 prop_assert_eq!(compare_by_optional_timestamp(a, a), Ordering::Equal);
854 }
855
856 #[test]
857 fn compare_pinned_events_antisymmetric(a in any_timestamp(), b in any_timestamp()) {
858 let ab = compare_by_optional_timestamp(a, b);
859 let ba = compare_by_optional_timestamp(b, a);
860
861 prop_assert_eq!(ab, ba.reverse());
862 }
863
864 #[test]
865 fn compare_pinned_events_transitive(
866 a in any_timestamp(),
867 b in any_timestamp(),
868 c in any_timestamp()
869 ) {
870 let ab = compare_by_optional_timestamp(a, b);
871 let bc = compare_by_optional_timestamp(b, c);
872 let ac = compare_by_optional_timestamp(a, c);
873
874 if ab == Ordering::Less && bc == Ordering::Less {
875 prop_assert_eq!(ac, Ordering::Less);
876 }
877
878 if ab == Ordering::Equal && bc == Ordering::Equal {
879 prop_assert_eq!(ac, Ordering::Equal);
880 }
881
882 if ab == Ordering::Greater && bc == Ordering::Greater {
883 prop_assert_eq!(ac, Ordering::Greater);
884 }
885 }
886 }
887}