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;
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};
54use crate::{
55 Room,
56 config::RequestConfig,
57 event_cache::{caches::room::LinkedChunkUpdateSender, deduplicator::is_valid_event},
58 room::WeakRoom,
59};
60
61pub struct PinnedEventsCacheState {
62 room_id: OwnedRoomId,
64
65 own_user_id: OwnedUserId,
67
68 room_version_rules: RoomVersionRules,
70
71 chunk: EventLinkedChunk,
78
79 pub update_sender: PinnedEventsCacheUpdateSender,
81
82 linked_chunk_update_sender: LinkedChunkUpdateSender,
87}
88
89#[cfg(not(tarpaulin_include))]
90impl fmt::Debug for PinnedEventsCacheState {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 f.debug_struct("PinnedEventsCacheState")
93 .field("room_id", &self.room_id)
94 .field("chunk", &self.chunk)
95 .finish_non_exhaustive()
96 }
97}
98
99impl<'a> StateLockWriteGuard<'a, PinnedEventsCacheState> {
100 #[must_use = "Propagate `VectorDiff` updates via `TimelineVectorDiffs`"]
106 pub async fn reload(
107 &mut self,
108 preprocessing: ReloadPreprocessing,
109 ) -> Result<Vec<VectorDiff<Event>>> {
110 match preprocessing {
111 ReloadPreprocessing::ForgetAll => {
112 self.state.chunk.reset();
115 self.propagate_changes().await?;
116 }
117
118 ReloadPreprocessing::None => {}
119 }
120
121 self.reload_from_storage().await?;
124
125 Ok(self.state.chunk.updates_as_vector_diffs())
126 }
127
128 async fn handle_sync(&mut self, timeline: Timeline) -> Result<()> {
129 let DeduplicationOutcome {
130 all_events: events,
131 in_memory_duplicated_event_ids,
132 in_store_duplicated_event_ids,
133 non_empty_all_duplicates: all_duplicates,
134 } = filter_duplicate_events(
135 &self.state.own_user_id,
136 &self.store,
137 LinkedChunkId::PinnedEvents(&self.state.room_id),
138 &self.state.chunk,
139 timeline.events,
140 )
141 .await?;
142
143 if all_duplicates {
144 return Ok(());
147 }
148
149 self.remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids).await?;
155
156 self.state.chunk.push_live_events(None, &events);
158
159 self.propagate_changes().await?;
160 self.notify_subscribers(EventsOrigin::Sync);
161
162 for event in &events {
164 self.maybe_apply_new_redaction(event).await?;
166 }
167
168 Ok(())
169 }
170
171 #[instrument(skip_all)]
176 pub async fn remove_events(
177 &mut self,
178 in_memory_events: Vec<(OwnedEventId, Position)>,
179 in_store_events: Vec<(OwnedEventId, Position)>,
180 ) -> Result<()> {
181 if !in_store_events.is_empty() {
183 let mut positions = in_store_events
184 .into_iter()
185 .map(|(_event_id, position)| position)
186 .collect::<Vec<_>>();
187
188 sort_positions_descending(&mut positions);
189
190 let updates =
191 positions.into_iter().map(|pos| Update::RemoveItem { at: pos }).collect::<Vec<_>>();
192
193 self.apply_store_only_updates(updates).await?;
194 }
195
196 if in_memory_events.is_empty() {
198 return Ok(());
200 }
201
202 self.state
204 .chunk
205 .remove_events_by_position(
206 in_memory_events.into_iter().map(|(_event_id, position)| position).collect(),
207 )
208 .expect("failed to remove an event");
209
210 self.propagate_changes().await
211 }
212
213 async fn apply_store_only_updates(&mut self, updates: Vec<Update<Event, Gap>>) -> Result<()> {
219 self.send_updates_to_store(updates).await
220 }
221
222 #[instrument(skip_all)]
225 async fn maybe_apply_new_redaction(&mut self, event: &Event) -> Result<()> {
226 let Some(event_id) =
227 extract_redaction_target(event.raw(), &self.room_version_rules.redaction)
228 else {
229 return Ok(());
230 };
231
232 let Some((location, mut target_event)) = self.find_event(&event_id).await? else {
234 trace!("redacted event is missing from the linked chunk");
235 return Ok(());
236 };
237
238 let target_event_raw = target_event.raw();
239
240 if let Ok(deserialized) = target_event_raw.deserialize()
242 && deserialized.is_redacted()
243 {
244 return Ok(());
245 }
246
247 if let Some(redacted_event) = apply_redaction(
248 target_event_raw,
249 event.raw().cast_ref_unchecked::<SyncRoomRedactionEvent>(),
250 &self.room_version_rules.redaction,
251 ) {
252 target_event.replace_raw(redacted_event.cast_unchecked());
260
261 self.replace_event_at(location, target_event.clone()).await?;
262 }
263
264 Ok(())
265 }
266
267 pub(super) async fn find_event(
269 &self,
270 event_id: &EventId,
271 ) -> Result<Option<(EventLocation, Event)>> {
272 find_event(event_id, &self.room_id, &self.chunk, &self.store).await
273 }
274
275 pub async fn replace_event_at(
282 &mut self,
283 location: EventLocation,
284 new_event: Event,
285 ) -> Result<()> {
286 match location {
287 EventLocation::Memory(position) => {
288 self.state
289 .chunk
290 .replace_event_at(position, new_event)
291 .expect("should have been a valid position of an item");
292 self.propagate_changes().await?;
295 }
296 EventLocation::Store => {
297 self.save_events([new_event]).await?;
298 }
299 }
300
301 Ok(())
302 }
303
304 pub async fn save_events(&mut self, events: impl IntoIterator<Item = Event>) -> Result<()> {
306 let store = self.store.clone();
307 let room_id = self.state.room_id.clone();
308 let events = events.into_iter().collect::<Vec<_>>();
309
310 spawn(async move {
312 for event in events {
313 store.save_event(&room_id, event).await?;
314 }
315
316 Result::Ok(())
317 })
318 .await
319 .expect("joining failed")?;
320
321 Ok(())
322 }
323
324 async fn reload_from_storage(&mut self) -> Result<()> {
327 let room_id = self.state.room_id.clone();
328 let linked_chunk_id = LinkedChunkId::PinnedEvents(&room_id);
329
330 let (last_chunk, chunk_id_gen) = self.store.load_last_chunk(linked_chunk_id).await?;
331
332 let Some(last_chunk) = last_chunk else {
333 if self.state.chunk.events().next().is_some() {
336 self.state.chunk.reset();
337 self.notify_subscribers(EventsOrigin::Sync);
338 }
339
340 return Ok(());
341 };
342
343 {
344 let mut current_chunk_identifier = last_chunk.identifier;
345 self.state.chunk.shrink_to_last_reloaded_chunk(
346 Some(last_chunk),
347 chunk_id_gen,
348 None,
350 )?;
351
352 while let Some(previous_chunk) =
354 self.store.load_previous_chunk(linked_chunk_id, current_chunk_identifier).await?
355 {
356 current_chunk_identifier = previous_chunk.identifier;
357 self.state.chunk.insert_new_chunk_as_first(previous_chunk)?;
358 }
359 }
360
361 self.state.chunk.store_updates().take();
363
364 self.notify_subscribers(EventsOrigin::Cache);
366
367 Ok(())
368 }
369
370 async fn replace_all_events(&mut self, mut new_events: Vec<Event>) -> Result<()> {
371 trace!("resetting all pinned events in linked chunk");
372
373 new_events.retain(is_valid_event);
378
379 let previous_pinned_event_ids = self.state.current_event_ids();
380
381 if new_events
382 .iter()
383 .filter_map(|e| e.event_id())
384 .map(ToOwned::to_owned)
385 .collect::<BTreeSet<_>>()
386 == previous_pinned_event_ids.into_iter().collect()
387 {
388 return Ok(());
390 }
391
392 if self.state.chunk.events().next().is_some() {
393 self.state.chunk.reset();
394 }
395
396 self.state.chunk.push_live_events(None, &new_events);
397 self.propagate_changes().await?;
398 self.notify_subscribers(EventsOrigin::Sync);
399
400 Ok(())
401 }
402
403 pub async fn propagate_changes(&mut self) -> Result<()> {
406 let updates = self.state.chunk.store_updates().take();
407
408 self.send_updates_to_store(updates).await
409 }
410
411 async fn send_updates_to_store(&mut self, updates: Vec<Update<Event, Gap>>) -> Result<()> {
412 let linked_chunk_id = OwnedLinkedChunkId::PinnedEvents(self.room_id.clone());
413
414 send_updates_to_store(
415 &self.store,
416 linked_chunk_id,
417 &self.state.linked_chunk_update_sender,
418 updates,
419 )
420 .await
421 }
422
423 fn notify_subscribers(&mut self, origin: EventsOrigin) {
425 let diffs = self.state.chunk.updates_as_vector_diffs();
426
427 if !diffs.is_empty() {
428 self.update_sender.send(TimelineVectorDiffs { diffs, origin });
429 }
430 }
431}
432
433impl PinnedEventsCacheState {
434 pub(super) fn current_event_ids(&self) -> Vec<OwnedEventId> {
436 self.chunk
437 .events()
438 .filter_map(|(_position, event)| event.event_id().map(ToOwned::to_owned))
439 .collect()
440 }
441
442 fn has_exactly_pinned_events(&self, pinned_event_ids: &[OwnedEventId]) -> bool {
451 let pinned_event_ids: BTreeSet<&EventId> =
452 pinned_event_ids.iter().map(|event_id| &**event_id).collect();
453 let event_ids: BTreeSet<&EventId> =
454 self.chunk.events().filter_map(|(_position, event)| event.event_id()).collect();
455
456 if !pinned_event_ids.is_subset(&event_ids) {
457 return false;
458 }
459
460 self.chunk.events().all(|(_position, event)| {
464 event.event_id().is_some_and(|event_id| pinned_event_ids.contains(event_id))
465 || extract_relation(event.raw()).is_some_and(|(relation_type, related_event_id)| {
466 relation_type != RelationType::Thread && event_ids.contains(&*related_event_id)
467 })
468 || extract_redaction_target(event.raw(), &self.room_version_rules.redaction)
469 .is_some_and(|redacted_event_id| event_ids.contains(&*redacted_event_id))
470 })
471 }
472}
473
474#[derive(Clone)]
478pub struct PinnedEventsCache {
479 inner: Arc<PinnedEventsCacheInner>,
480
481 _task: Arc<BackgroundTaskHandle>,
484}
485
486struct PinnedEventsCacheInner {
488 room_id: OwnedRoomId,
490
491 state: CacheStateLock<PinnedEventsStateSelector>,
495}
496
497impl PinnedEventsCache {
498 pub(in super::super) async fn new(
500 weak_room: &WeakRoom,
501 own_user_id: OwnedUserId,
502 room_version_rules: RoomVersionRules,
503 linked_chunk_update_sender: LinkedChunkUpdateSender,
504 state: &StateLock,
505 ) -> Result<Self> {
506 let room = weak_room.get().ok_or(EventCacheError::ClientDropped)?;
507 let room_id = room.room_id().to_owned();
508
509 let cache_state = state
510 .try_insert_once_with(
511 PinnedEventsStateSelector::new(room_id.clone()),
512 |_store_guard| async {
513 Ok(PinnedEventsCacheState {
514 room_id: room_id.clone(),
515 own_user_id,
516 room_version_rules,
517 chunk: EventLinkedChunk::new(),
518 update_sender: PinnedEventsCacheUpdateSender::new(),
519 linked_chunk_update_sender,
520 })
521 },
522 )
523 .await?;
524
525 let inner = Arc::new(PinnedEventsCacheInner { room_id, state: cache_state });
526
527 let task = room
528 .client()
529 .task_monitor()
530 .spawn_infinite_task(
531 "pinned_event_listener_task",
532 Self::pinned_event_listener_task(room, inner.clone()),
533 )
534 .abort_on_drop();
535
536 Ok(Self { inner, _task: Arc::new(task) })
537 }
538
539 pub fn room_id(&self) -> &RoomId {
541 &self.inner.room_id
542 }
543
544 pub(super) fn state(&self) -> &CacheStateLock<PinnedEventsStateSelector> {
546 &self.inner.state
547 }
548
549 pub async fn subscribe(&self) -> Result<(Vec<Event>, Receiver<TimelineVectorDiffs>)> {
551 let guard = self.inner.state.read().await?;
552 let events = guard.state.chunk.events().map(|(_position, item)| item.clone()).collect();
553
554 let recv = guard.state.update_sender.new_pinned_events_receiver();
555
556 Ok((events, recv))
557 }
558
559 #[cfg(feature = "e2e-encryption")]
563 pub(in super::super) async fn replace_in_memory_utds(
564 &self,
565 resolved_events: &[MaybeResolvedEvent],
566 ) -> Result<()> {
567 let mut state = self.inner.state.write().await?;
568
569 let _ = state.state.chunk.store_updates().take();
572
573 if state.state.chunk.replace_utds(resolved_events) {
574 state.propagate_changes().await?;
575 state.notify_subscribers(EventsOrigin::Cache);
576 }
577
578 Ok(())
579 }
580
581 #[instrument(skip_all, fields(room_id = %self.inner.room_id))]
583 pub(super) async fn handle_joined_room_update(&self, timeline: Timeline) -> Result<()> {
584 self.handle_timeline(timeline).await
585 }
586
587 #[instrument(skip_all, fields(room_id = %self.inner.room_id))]
589 pub(super) async fn handle_left_room_update(&self, timeline: Timeline) -> Result<()> {
590 self.handle_timeline(timeline).await
591 }
592
593 async fn handle_timeline(&self, timeline: Timeline) -> Result<()> {
596 if timeline.events.is_empty() {
597 return Ok(());
598 }
599
600 trace!("adding new {} events", timeline.events.len());
601
602 self.inner.state.write().await?.handle_sync(timeline).await
603 }
604
605 #[instrument(fields(%room_id = room.room_id()), skip(room, inner))]
606 async fn pinned_event_listener_task(room: Room, inner: Arc<PinnedEventsCacheInner>) {
607 debug!("pinned events listener task started");
608
609 let max_pinned_events_to_load = room
610 .client()
611 .event_cache()
612 .expect("Pinned-events are running inside the Event Cache")
613 .config()
614 .max_pinned_events_to_load;
615
616 let reload_from_network = async |room: Room| {
617 let events = match Self::reload_pinned_events(room).await {
618 Ok(Some(events)) => events,
619 Ok(None) => Vec::new(),
620 Err(err) => {
621 warn!("error when loading pinned events: {err}");
622 return;
623 }
624 };
625
626 match inner.state.write().await {
629 Ok(mut guard) => {
630 guard.replace_all_events(events).await.unwrap_or_else(|err| {
631 warn!("error when replacing pinned events: {err}");
632 });
633 }
634
635 Err(err) => {
636 warn!("error when acquiring write lock to replace pinned events: {err}");
637 }
638 }
639 };
640
641 match inner.state.write().await {
643 Ok(mut guard) => {
644 guard.reload_from_storage().await.unwrap_or_else(|err| {
646 warn!("error when reloading pinned events from storage, at start: {err}");
647 });
648
649 let actual_pinned_events = pinned_event_ids_to_load(
652 max_pinned_events_to_load,
653 room.pinned_event_ids().unwrap_or_default(),
654 );
655
656 if !guard.state.has_exactly_pinned_events(&actual_pinned_events) {
657 drop(guard);
659 reload_from_network(room.clone()).await;
660 }
661 }
662
663 Err(err) => {
664 warn!("error when acquiring write lock to initialize pinned events: {err}");
665 }
666 }
667
668 let weak_room = WeakRoom::new(room.client().downgrade(), room.room_id().to_owned());
669
670 let mut stream = room.pinned_event_ids_stream();
671
672 drop(room);
673
674 while let Some(new_list) = stream.next().await {
676 trace!("handling update");
677
678 let Some(room) = weak_room.get() else {
679 debug!("room has been dropped, ending pinned events listener task");
680 break;
681 };
682
683 let new_list = pinned_event_ids_to_load(max_pinned_events_to_load, new_list);
684
685 let guard = match inner.state.read().await {
686 Ok(guard) => guard,
687 Err(err) => {
688 warn!("error when acquiring read lock to handle pinned events update: {err}");
689 break;
690 }
691 };
692
693 if guard.state.has_exactly_pinned_events(&new_list) {
695 continue;
697 }
698
699 drop(guard);
700
701 reload_from_network(room).await;
703 }
704
705 debug!("pinned events listener task ended");
706 }
707
708 async fn reload_pinned_events(room: Room) -> Result<Option<Vec<Event>>> {
717 let (max_concurrent_requests, max_pinned_events_to_load) = {
718 let client = room.client();
719 let config = client
720 .event_cache()
721 .expect("Pinned-events are running inside the Event Cache")
722 .config();
723
724 (config.max_pinned_events_concurrent_requests, config.max_pinned_events_to_load)
725 };
726
727 let pinned_event_ids = pinned_event_ids_to_load(
728 max_pinned_events_to_load,
729 room.pinned_event_ids().unwrap_or_default(),
730 );
731
732 if pinned_event_ids.is_empty() {
733 return Ok(Some(Vec::new()));
734 }
735
736 let mut num_successful_loads = 0;
737
738 let mut loaded_events: Vec<Event> =
739 stream::iter(pinned_event_ids.clone().into_iter().map(|event_id| {
740 let room = room.clone();
741 let filter = vec![RelationType::Annotation, RelationType::Replacement];
742 let request_config = RequestConfig::default().retry_limit(3);
743
744 async move {
745 let (target, mut relations) = room
746 .load_or_fetch_event_with_relations(
747 &event_id,
748 Some(filter),
749 Some(request_config),
750 )
751 .await?;
752
753 relations.insert(0, target);
754 Ok::<_, crate::Error>(relations)
755 }
756 }))
757 .buffer_unordered(max_concurrent_requests)
758 .inspect(|result| {
760 if result.is_ok() {
761 num_successful_loads += 1;
762 }
763 })
764 .flat_map(stream::iter)
766 .flat_map(stream::iter)
768 .collect()
769 .await;
770
771 if num_successful_loads != pinned_event_ids.len() {
772 warn!(
773 "only successfully loaded {} out of {} pinned events",
774 num_successful_loads,
775 pinned_event_ids.len()
776 );
777 }
778
779 if loaded_events.is_empty() {
780 return Err(EventCacheError::UnableToLoadPinnedEvents);
784 }
785
786 loaded_events.sort_by(compare_pinned_items);
791
792 Ok(Some(loaded_events))
793 }
794}
795
796impl fmt::Debug for PinnedEventsCache {
797 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
798 f.debug_struct("PinnedEventsCache").finish_non_exhaustive()
799 }
800}
801
802#[inline]
808fn pinned_event_ids_to_load(
809 max_events_to_load: usize,
810 pinned_event_ids: Vec<OwnedEventId>,
811) -> Vec<OwnedEventId> {
812 pinned_event_ids.into_iter().rev().take(max_events_to_load).rev().collect()
813}
814
815fn compare_pinned_items(a: &Event, b: &Event) -> Ordering {
816 let a_time: Option<MilliSecondsSinceUnixEpoch> = a.timestamp_raw();
817 let b_time: Option<MilliSecondsSinceUnixEpoch> = b.timestamp_raw();
818
819 compare_by_optional_timestamp(a_time, b_time)
820}
821
822fn compare_by_optional_timestamp(
823 a: Option<MilliSecondsSinceUnixEpoch>,
824 b: Option<MilliSecondsSinceUnixEpoch>,
825) -> Ordering {
826 match (a, b) {
827 (None, None) => Ordering::Equal,
828 (None, Some(_)) => Ordering::Greater,
829 (Some(_), None) => Ordering::Less,
830 (Some(a), Some(b)) => a.cmp(&b),
831 }
832}
833
834#[cfg(not(target_family = "wasm"))]
835#[cfg(test)]
836mod tests {
837 use proptest::prelude::*;
838 use ruma::UInt;
839
840 use super::*;
841
842 fn any_timestamp() -> impl Strategy<Value = Option<MilliSecondsSinceUnixEpoch>> {
843 prop::option::of(
844 any::<u32>().prop_map(|value| MilliSecondsSinceUnixEpoch(UInt::from(value))),
845 )
846 }
847
848 #[test]
849 fn sort_pinned_events_never_panics_only_nones() {
850 let mut vec = vec![None; 100_000];
851 vec.sort_by(|a, b| compare_by_optional_timestamp(*a, *b))
852 }
853
854 proptest! {
855 #[test]
856 fn sort_pinned_events_never_panics(mut v in prop::collection::vec(any_timestamp(), 0..1000)) {
857 v.sort_by(
858 |a, b| compare_by_optional_timestamp(*a, *b))
859 }
860
861 #[test]
862 fn compare_pinned_events_reflexive(a in any_timestamp()) {
863 prop_assert_eq!(compare_by_optional_timestamp(a, a), Ordering::Equal);
864 }
865
866 #[test]
867 fn compare_pinned_events_antisymmetric(a in any_timestamp(), b in any_timestamp()) {
868 let ab = compare_by_optional_timestamp(a, b);
869 let ba = compare_by_optional_timestamp(b, a);
870
871 prop_assert_eq!(ab, ba.reverse());
872 }
873
874 #[test]
875 fn compare_pinned_events_transitive(
876 a in any_timestamp(),
877 b in any_timestamp(),
878 c in any_timestamp()
879 ) {
880 let ab = compare_by_optional_timestamp(a, b);
881 let bc = compare_by_optional_timestamp(b, c);
882 let ac = compare_by_optional_timestamp(a, c);
883
884 if ab == Ordering::Less && bc == Ordering::Less {
885 prop_assert_eq!(ac, Ordering::Less);
886 }
887
888 if ab == Ordering::Equal && bc == Ordering::Equal {
889 prop_assert_eq!(ac, Ordering::Equal);
890 }
891
892 if ab == Ordering::Greater && bc == Ordering::Greater {
893 prop_assert_eq!(ac, Ordering::Greater);
894 }
895 }
896 }
897}