1pub mod pagination;
16mod state;
17mod updates;
18
19use std::{collections::BTreeMap, fmt, sync::Arc};
20
21use eyeball::SharedObservable;
22use matrix_sdk_base::{
23 deserialized_responses::{AmbiguityChange, ThreadSummary},
24 event_cache::Event,
25 sync::Timeline,
26};
27use ruma::{
28 EventId, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId,
29 events::{AnyRoomAccountDataEvent, relation::RelationType},
30 serde::Raw,
31};
32use tokio::sync::{Notify, mpsc};
33use tracing::{instrument, trace, warn};
34
35use self::pagination::RoomPagination;
36pub use self::{
37 state::RoomEventCacheState,
38 updates::{
39 RoomEventCacheGenericUpdate, RoomEventCacheLinkedChunkUpdate, RoomEventCacheUpdate,
40 RoomEventCacheUpdateSender,
41 },
42};
43use super::{
44 super::{
45 EventsOrigin, Result,
46 states::{CacheStateLock, StateLockWriteGuard, selectors::RoomStateSelector},
47 },
48 TimelineVectorDiffs,
49 event_linked_chunk::sort_positions_descending,
50 pagination::SharedPaginationStatus,
51 read_receipts::MaybeReceiptEventContent,
52 subscriber::{AutoShrinkMessage, Subscriber},
53};
54use crate::room::WeakRoom;
55
56#[derive(Clone)]
60pub struct RoomEventCache {
61 inner: Arc<RoomEventCacheInner>,
62}
63
64impl fmt::Debug for RoomEventCache {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.debug_struct("RoomEventCache").finish_non_exhaustive()
67 }
68}
69
70impl RoomEventCache {
71 pub(super) fn new(
73 room_id: OwnedRoomId,
74 weak_room: WeakRoom,
75 own_user_id: OwnedUserId,
76 state: CacheStateLock<RoomStateSelector>,
77 shared_pagination_status: SharedObservable<SharedPaginationStatus>,
78 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
79 update_sender: RoomEventCacheUpdateSender,
80 ) -> Self {
81 Self {
82 inner: Arc::new(RoomEventCacheInner {
83 room_id,
84 weak_room,
85 own_user_id,
86 state,
87 update_sender,
88 pagination_batch_token_notifier: Notify::new(),
89 auto_shrink_sender,
90 shared_pagination_status,
91 }),
92 }
93 }
94
95 pub fn room_id(&self) -> &RoomId {
97 &self.inner.room_id
98 }
99
100 pub(super) fn own_user_id(&self) -> &OwnedUserId {
102 &self.inner.own_user_id
103 }
104
105 pub(super) fn weak_room(&self) -> &WeakRoom {
107 &self.inner.weak_room
108 }
109
110 pub async fn events(&self) -> Result<Vec<Event>> {
115 let state = self.inner.state.read().await?;
116
117 Ok(state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect())
118 }
119
120 pub async fn subscribe(&self) -> Result<(Vec<Event>, Subscriber<RoomEventCacheUpdate>)> {
127 let state = self.inner.state.read().await?;
128 let events =
129 state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect();
130
131 let subscribers_handle = state.subscribers_handle();
132
133 let subscriber = Subscriber::new(
134 self.inner.update_sender.new_room_receiver(),
135 AutoShrinkMessage::Room { room_id: self.inner.room_id.clone() },
136 self.inner.auto_shrink_sender.clone(),
137 subscribers_handle,
138 );
139
140 trace!("added a room event cache subscriber; new count: {}", subscribers_handle.count());
141
142 Ok((events, subscriber))
143 }
144
145 pub fn pagination(&self) -> RoomPagination {
148 RoomPagination::new(self.inner.clone())
149 }
150
151 pub async fn rfind_map_event_in_memory_by<O, P>(&self, predicate: P) -> Result<Option<O>>
159 where
160 P: FnMut(&Event) -> Option<O>,
161 {
162 Ok(self.inner.state.read().await?.rfind_map_event_in_memory_by(predicate))
163 }
164
165 pub async fn find_event(&self, event_id: &EventId) -> Result<Option<Event>> {
170 Ok(self
171 .inner
172 .state
173 .read()
174 .await?
175 .find_event(event_id)
176 .await
177 .ok()
178 .flatten()
179 .map(|(_loc, event)| event))
180 }
181
182 pub async fn find_event_with_relations(
194 &self,
195 event_id: &EventId,
196 filter: Option<Vec<RelationType>>,
197 ) -> Result<Option<(Event, Vec<Event>)>> {
198 Ok(self
200 .inner
201 .state
202 .read()
203 .await?
204 .find_event_with_relations(event_id, filter)
205 .await
206 .ok()
207 .flatten())
208 }
209
210 pub async fn find_event_relations(
222 &self,
223 event_id: &EventId,
224 filter: Option<Vec<RelationType>>,
225 ) -> Result<Vec<Event>> {
226 self.inner.state.read().await?.find_event_relations(event_id, filter.clone()).await
228 }
229
230 pub(in super::super) fn state(&self) -> &CacheStateLock<RoomStateSelector> {
232 &self.inner.state
233 }
234
235 #[instrument(skip_all, fields(room_id = %self.room_id()))]
237 pub(super) async fn handle_joined_room_update(
238 &self,
239 timeline: Timeline,
240 read_receipts: MaybeReceiptEventContent,
241 account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
242 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
243 avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
244 ) -> Result<()> {
245 self.inner
246 .handle_timeline(timeline, read_receipts, ambiguity_changes, avatar_changes)
247 .await?;
248 self.inner.handle_account_data(account_data);
249
250 Ok(())
251 }
252
253 #[instrument(skip_all, fields(room_id = %self.room_id()))]
255 pub(super) async fn handle_left_room_update(
256 &self,
257 timeline: Timeline,
258 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
259 ) -> Result<()> {
260 self.inner
261 .handle_timeline(timeline, MaybeReceiptEventContent::none(), ambiguity_changes, None)
262 .await?;
263
264 Ok(())
265 }
266
267 pub(in super::super) fn update_thread_summary(
268 &self,
269 thread_id: &EventId,
270 new_thread_summary: ThreadSummary,
271 ) -> Result<()> {
272 self.inner.update_sender.send(
276 RoomEventCacheUpdate::UpdateThreadSummary {
277 thread_root: thread_id.to_owned(),
278 thread_summary: new_thread_summary,
279 },
280 Some(RoomEventCacheGenericUpdate { room_id: self.inner.room_id.clone() }),
281 );
282
283 Ok(())
284 }
285
286 pub(in super::super) fn update_sender(&self) -> &RoomEventCacheUpdateSender {
288 &self.inner.update_sender
289 }
290
291 pub(crate) async fn insert_sent_event_from_send_queue(&self, event: Event) -> Result<()> {
293 self.inner.insert_sent_event_from_send_queue(event).await
294 }
295
296 pub async fn debug_string(&self) -> Vec<String> {
299 match self.inner.state.read().await {
300 Ok(read_guard) => read_guard.room_linked_chunk().debug_string(),
301 Err(err) => {
302 warn!(?err, "Failed to obtain the read guard for the `RoomEventCache`");
303
304 vec![]
305 }
306 }
307 }
308}
309
310pub(super) struct RoomEventCacheInner {
312 room_id: OwnedRoomId,
314
315 weak_room: WeakRoom,
316
317 own_user_id: OwnedUserId,
319
320 state: CacheStateLock<RoomStateSelector>,
322
323 pagination_batch_token_notifier: Notify,
325
326 shared_pagination_status: SharedObservable<SharedPaginationStatus>,
327
328 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
333
334 update_sender: RoomEventCacheUpdateSender,
336}
337
338impl RoomEventCacheInner {
339 fn handle_account_data(&self, account_data: Vec<Raw<AnyRoomAccountDataEvent>>) {
340 if account_data.is_empty() {
341 return;
342 }
343
344 let mut handled_read_marker = false;
345
346 trace!("Handling account data");
347
348 for raw_event in account_data {
349 match raw_event.deserialize() {
350 Ok(AnyRoomAccountDataEvent::FullyRead(ev)) => {
351 if handled_read_marker {
354 continue;
355 }
356
357 handled_read_marker = true;
358
359 self.update_sender.send(
362 RoomEventCacheUpdate::MoveReadMarkerTo { event_id: ev.content.event_id },
363 None,
364 );
365 }
366
367 Ok(_) => {
368 }
371
372 Err(e) => {
373 let event_type = raw_event.get_field::<String>("type").ok().flatten();
374 warn!(event_type, "Failed to deserialize account data: {e}");
375 }
376 }
377 }
378 }
379
380 async fn handle_timeline(
382 &self,
383 timeline: Timeline,
384 read_receipts: MaybeReceiptEventContent,
385 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
386 avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
387 ) -> Result<()> {
388 self.handle_timeline_inner(
389 self.state.write().await?,
390 timeline,
391 read_receipts,
392 ambiguity_changes,
393 avatar_changes,
394 )
395 .await
396 }
397
398 async fn insert_sent_event_from_send_queue(&self, event: Event) -> Result<()> {
402 let state = self.state.write().await?;
403
404 if state.room_linked_chunk().events().next().is_some() {
409 return self
410 .handle_timeline_inner(
411 state,
412 Timeline { limited: false, prev_batch: None, events: vec![event] },
413 MaybeReceiptEventContent::none(),
414 BTreeMap::new(),
415 None,
416 )
417 .await;
418 }
419
420 Ok(())
421 }
422
423 async fn handle_timeline_inner(
424 &self,
425 mut state: StateLockWriteGuard<'_, RoomEventCacheState>,
426 timeline: Timeline,
427 read_receipts: MaybeReceiptEventContent,
428 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
429 avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
430 ) -> Result<()> {
431 if timeline.events.is_empty()
432 && timeline.prev_batch.is_none()
433 && read_receipts.is_none()
434 && ambiguity_changes.is_empty()
435 && avatar_changes.as_ref().is_none_or(|avatars| avatars.is_empty())
436 {
437 return Ok(());
438 }
439
440 trace!("adding new events");
441
442 let (stored_prev_batch_token, timeline_event_diffs) =
443 state.handle_sync(timeline, &read_receipts).await?;
444
445 drop(state);
446
447 if stored_prev_batch_token {
450 self.pagination_batch_token_notifier.notify_one();
451 }
452
453 if !timeline_event_diffs.is_empty() {
456 self.update_sender.send(
457 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
458 diffs: timeline_event_diffs,
459 origin: EventsOrigin::Sync,
460 }),
461 Some(RoomEventCacheGenericUpdate { room_id: self.room_id.clone() }),
462 );
463 }
464
465 if let Some(read_receipts) = read_receipts.into_inner() {
466 self.update_sender
467 .send(RoomEventCacheUpdate::AddReadReceiptEvent { event: read_receipts }, None);
468 }
469
470 if !ambiguity_changes.is_empty() || avatar_changes.as_ref().is_some_and(|c| !c.is_empty()) {
471 self.update_sender.send(
472 RoomEventCacheUpdate::UpdateMembers { ambiguity_changes, avatar_changes },
473 None,
474 );
475 }
476
477 Ok(())
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use matrix_sdk_base::{RoomState, event_cache::Event};
484 use matrix_sdk_test::{async_test, event_factory::EventFactory};
485 use ruma::{
486 RoomId, event_id,
487 events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
488 room_id, user_id,
489 };
490
491 use crate::test_utils::logged_in_client;
492
493 #[async_test]
494 async fn test_find_event_by_id_with_edit_relation() {
495 let original_id = event_id!("$original");
496 let related_id = event_id!("$related");
497 let room_id = room_id!("!galette:saucisse.bzh");
498 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
499
500 assert_relations(
501 room_id,
502 f.text_msg("Original event").event_id(original_id).into(),
503 f.text_msg("* An edited event")
504 .edit(
505 original_id,
506 RoomMessageEventContentWithoutRelation::text_plain("And edited event"),
507 )
508 .event_id(related_id)
509 .into(),
510 f,
511 )
512 .await;
513 }
514
515 #[async_test]
516 async fn test_find_event_by_id_with_thread_reply_relation() {
517 let original_id = event_id!("$original");
518 let related_id = event_id!("$related");
519 let room_id = room_id!("!galette:saucisse.bzh");
520 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
521
522 assert_relations(
523 room_id,
524 f.text_msg("Original event").event_id(original_id).into(),
525 f.text_msg("A reply").in_thread(original_id, related_id).event_id(related_id).into(),
526 f,
527 )
528 .await;
529 }
530
531 #[async_test]
532 async fn test_find_event_by_id_with_reaction_relation() {
533 let original_id = event_id!("$original");
534 let related_id = event_id!("$related");
535 let room_id = room_id!("!galette:saucisse.bzh");
536 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
537
538 assert_relations(
539 room_id,
540 f.text_msg("Original event").event_id(original_id).into(),
541 f.reaction(original_id, ":D").event_id(related_id).into(),
542 f,
543 )
544 .await;
545 }
546
547 #[async_test]
548 async fn test_find_event_by_id_with_poll_response_relation() {
549 let original_id = event_id!("$original");
550 let related_id = event_id!("$related");
551 let room_id = room_id!("!galette:saucisse.bzh");
552 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
553
554 assert_relations(
555 room_id,
556 f.poll_start("Poll start event", "A poll question", vec!["An answer"])
557 .event_id(original_id)
558 .into(),
559 f.poll_response(vec!["1"], original_id).event_id(related_id).into(),
560 f,
561 )
562 .await;
563 }
564
565 #[async_test]
566 async fn test_find_event_by_id_with_poll_end_relation() {
567 let original_id = event_id!("$original");
568 let related_id = event_id!("$related");
569 let room_id = room_id!("!galette:saucisse.bzh");
570 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
571
572 assert_relations(
573 room_id,
574 f.poll_start("Poll start event", "A poll question", vec!["An answer"])
575 .event_id(original_id)
576 .into(),
577 f.poll_end("Poll ended", original_id).event_id(related_id).into(),
578 f,
579 )
580 .await;
581 }
582
583 #[async_test]
584 async fn test_find_event_by_id_with_filtered_relationships() {
585 let original_id = event_id!("$original");
586 let related_id = event_id!("$related");
587 let associated_related_id = event_id!("$recursive_related");
588 let room_id = room_id!("!galette:saucisse.bzh");
589 let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
590
591 let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
592 let related_event = event_factory
593 .text_msg("* Edited event")
594 .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
595 .event_id(related_id)
596 .into();
597 let associated_related_event =
598 event_factory.reaction(related_id, "🤡").event_id(associated_related_id).into();
599
600 let client = logged_in_client(None).await;
601
602 client.base_client().get_or_create_room(room_id, RoomState::Joined);
603
604 let (room_event_cache, _drop_handles) =
605 client.event_cache().unwrap().room(room_id).await.unwrap();
606
607 {
608 let mut state = room_event_cache.inner.state.write().await.unwrap();
609
610 state.save_events([original_event]).await.unwrap();
612
613 state.save_events([related_event]).await.unwrap();
615
616 state.save_events([associated_related_event]).await.unwrap();
619 }
620
621 let filter = Some(vec![RelationType::Replacement]);
622 let (event, related_events) = room_event_cache
623 .find_event_with_relations(original_id, filter)
624 .await
625 .expect("Failed to find the event with relations")
626 .expect("Event has no relation");
627 let cached_event_id = event.event_id().unwrap();
629 assert_eq!(cached_event_id, original_id);
630
631 assert_eq!(related_events.len(), 1);
634
635 let related_event_id = related_events[0].event_id().unwrap();
636 assert_eq!(related_event_id, related_id);
637
638 let filter = Some(vec![RelationType::Thread]);
640 let (event, related_events) = room_event_cache
641 .find_event_with_relations(original_id, filter)
642 .await
643 .expect("Failed to find the event with relations")
644 .expect("Event has no relation");
645
646 let cached_event_id = event.event_id().unwrap();
648 assert_eq!(cached_event_id, original_id);
649 assert!(related_events.is_empty());
651 }
652
653 #[async_test]
654 async fn test_find_event_by_id_with_recursive_relation() {
655 let original_id = event_id!("$original");
656 let related_id = event_id!("$related");
657 let associated_related_id = event_id!("$recursive_related");
658 let room_id = room_id!("!galette:saucisse.bzh");
659 let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
660
661 let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
662 let related_event = event_factory
663 .text_msg("* Edited event")
664 .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
665 .event_id(related_id)
666 .into();
667 let associated_related_event =
668 event_factory.reaction(related_id, "👍").event_id(associated_related_id).into();
669
670 let client = logged_in_client(None).await;
671
672 client.base_client().get_or_create_room(room_id, RoomState::Joined);
673
674 let (room_event_cache, _drop_handles) =
675 client.event_cache().unwrap().room(room_id).await.unwrap();
676
677 {
678 let mut state = room_event_cache.inner.state.write().await.unwrap();
679
680 state.save_events([original_event]).await.unwrap();
682
683 state.save_events([related_event]).await.unwrap();
685
686 state.save_events([associated_related_event]).await.unwrap();
689 }
690
691 let (event, related_events) = room_event_cache
692 .find_event_with_relations(original_id, None)
693 .await
694 .expect("Failed to find the event with relations")
695 .expect("Event has no relation");
696 let cached_event_id = event.event_id().unwrap();
698 assert_eq!(cached_event_id, original_id);
699
700 assert_eq!(related_events.len(), 2);
702
703 let related_event_id = related_events[0].event_id().unwrap();
704 assert_eq!(related_event_id, related_id);
705 let related_event_id = related_events[1].event_id().unwrap();
706 assert_eq!(related_event_id, associated_related_id);
707 }
708
709 async fn assert_relations(
710 room_id: &RoomId,
711 original_event: Event,
712 related_event: Event,
713 event_factory: EventFactory,
714 ) {
715 let client = logged_in_client(None).await;
716
717 client.base_client().get_or_create_room(room_id, RoomState::Joined);
718
719 let (room_event_cache, _drop_handles) =
720 client.event_cache().unwrap().room(room_id).await.unwrap();
721
722 let original_event_id = original_event.event_id().unwrap().to_owned();
723 let related_id = related_event.event_id().unwrap().to_owned();
724
725 {
726 let mut state = room_event_cache.inner.state.write().await.unwrap();
727
728 state.save_events([original_event]).await.unwrap();
730
731 let unrelated_id = event_id!("$2");
734 state
735 .save_events([event_factory
736 .text_msg("An unrelated event")
737 .event_id(unrelated_id)
738 .into()])
739 .await
740 .unwrap();
741
742 state.save_events([related_event]).await.unwrap();
744 }
745
746 let (event, related_events) = room_event_cache
747 .find_event_with_relations(&original_event_id, None)
748 .await
749 .expect("Failed to find the event with relations")
750 .expect("Event has no relation");
751 let cached_event_id = event.event_id().unwrap();
753 assert_eq!(cached_event_id, original_event_id);
754
755 let related_event_id = related_events[0].event_id().unwrap();
757 assert_eq!(related_event_id, related_id);
758 }
759}
760
761#[cfg(all(test, not(target_family = "wasm")))] mod timed_tests {
763 use std::{ops::Not, sync::Arc};
764
765 use assert_matches::assert_matches;
766 use eyeball_im::VectorDiff;
767 use futures_util::FutureExt;
768 use matrix_sdk_base::{
769 RoomState,
770 event_cache::{
771 Gap,
772 store::{EventCacheStore as _, MemoryStore},
773 },
774 linked_chunk::{
775 ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update,
776 lazy_loader::from_all_chunks,
777 },
778 store::StoreConfig,
779 sync::Timeline,
780 };
781 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
782 use matrix_sdk_test::{ALICE, BOB, User, async_test, event_factory::EventFactory};
783 use ruma::{
784 EventId, event_id,
785 events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
786 room_id,
787 serde::Raw,
788 user_id,
789 };
790 use serde_json::json;
791 use strass::assert_let;
792 use tokio::task::yield_now;
793
794 use super::{
795 super::{super::TimelineVectorDiffs, pagination::LoadMoreEventsBackwardsOutcome},
796 MaybeReceiptEventContent, RoomEventCache, RoomEventCacheGenericUpdate,
797 RoomEventCacheUpdate,
798 };
799 use crate::{assert_let_timeout, test_utils::client::MockClientBuilder};
800
801 #[async_test]
802 async fn test_write_to_storage() {
803 let room_id = room_id!("!galette:saucisse.bzh");
804 let event_id_0 = event_id!("$ev0");
805 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
806
807 let event_cache_store = Arc::new(MemoryStore::new());
808
809 let client = MockClientBuilder::new(None)
810 .on_builder(|builder| {
811 builder.store_config(
812 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
813 .event_cache_store(event_cache_store.clone()),
814 )
815 })
816 .build()
817 .await;
818
819 let event_cache = client.event_cache().unwrap();
820
821 client.base_client().get_or_create_room(room_id, RoomState::Joined);
822
823 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
824 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
825
826 let timeline = Timeline {
828 limited: true,
829 prev_batch: Some("raclette".to_owned()),
830 events: vec![f.text_msg("hey yo").event_id(event_id_0).into_event()],
831 };
832
833 room_event_cache
834 .handle_joined_room_update(
835 timeline,
836 MaybeReceiptEventContent::none(),
837 Default::default(),
838 Default::default(),
839 Default::default(),
840 )
841 .await
842 .unwrap();
843
844 assert_matches!(
846 generic_stream.recv().await,
847 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
848 assert_eq!(expected_room_id, room_id);
849 }
850 );
851 assert!(generic_stream.is_empty());
852
853 let linked_chunk = from_all_chunks::<3, _, _>(
855 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
856 )
857 .unwrap()
858 .unwrap();
859
860 assert_eq!(linked_chunk.chunks().count(), 2);
861
862 let mut chunks = linked_chunk.chunks();
863
864 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Gap(gap) => {
866 assert_eq!(gap.token, "raclette");
867 });
868
869 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
871 assert_eq!(events.len(), 1);
872 assert_eq!(events[0].event_id(), Some(event_id_0));
873 });
874
875 assert!(chunks.next().is_none());
877 }
878
879 #[async_test]
880 async fn test_write_to_storage_strips_bundled_relations() {
881 let room_id = room_id!("!galette:saucisse.bzh");
882 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
883
884 let event_cache_store = Arc::new(MemoryStore::new());
885
886 let client = MockClientBuilder::new(None)
887 .on_builder(|builder| {
888 builder.store_config(
889 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
890 .event_cache_store(event_cache_store.clone()),
891 )
892 })
893 .build()
894 .await;
895
896 let event_cache = client.event_cache().unwrap();
897
898 client.base_client().get_or_create_room(room_id, RoomState::Joined);
899
900 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
901 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
902
903 let ev = f
905 .text_msg("hey yo")
906 .sender(*ALICE)
907 .with_bundled_edit(f.text_msg("Hello, Kind Sir").sender(*ALICE))
908 .into_event();
909
910 let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev] };
911
912 room_event_cache
913 .handle_joined_room_update(
914 timeline,
915 MaybeReceiptEventContent::none(),
916 Default::default(),
917 Default::default(),
918 Default::default(),
919 )
920 .await
921 .unwrap();
922
923 assert_matches!(
925 generic_stream.recv().await,
926 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
927 assert_eq!(expected_room_id, room_id);
928 }
929 );
930 assert!(generic_stream.is_empty());
931
932 {
934 let events = room_event_cache.events().await.unwrap();
935
936 assert_eq!(events.len(), 1);
937
938 let ev = events[0].raw().deserialize().unwrap();
939 assert_let!(
940 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev
941 );
942
943 let original = msg.as_original().unwrap();
944 assert_eq!(original.content.body(), "hey yo");
945 assert!(original.unsigned.relations.replace.is_some());
946 }
947
948 let linked_chunk = from_all_chunks::<3, _, _>(
950 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
951 )
952 .unwrap()
953 .unwrap();
954
955 assert_eq!(linked_chunk.chunks().count(), 1);
956
957 let mut chunks = linked_chunk.chunks();
958 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
959 assert_eq!(events.len(), 1);
960
961 let ev = events[0].raw().deserialize().unwrap();
962 assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev);
963
964 let original = msg.as_original().unwrap();
965 assert_eq!(original.content.body(), "hey yo");
966 assert!(original.unsigned.relations.replace.is_none());
967 });
968
969 assert!(chunks.next().is_none());
971 }
972
973 #[async_test]
974 async fn test_clear() {
975 let room_id = room_id!("!galette:saucisse.bzh");
976 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
977
978 let event_id1 = event_id!("$1");
979 let event_id2 = event_id!("$2");
980
981 let ev1 = f.text_msg("hello world").event_id(event_id1).into_event();
982 let ev2 = f.text_msg("how's it going").event_id(event_id2).into_event();
983
984 let event_cache_store = Arc::new(MemoryStore::new());
986 event_cache_store
987 .handle_linked_chunk_updates(
988 LinkedChunkId::Room(room_id),
989 vec![
990 Update::NewItemsChunk {
992 previous: None,
993 new: ChunkIdentifier::new(0),
994 next: None,
995 },
996 Update::NewGapChunk {
998 previous: Some(ChunkIdentifier::new(0)),
999 new: ChunkIdentifier::new(42),
1001 next: None,
1002 gap: Gap { token: "comté".to_owned() },
1003 },
1004 Update::NewItemsChunk {
1006 previous: Some(ChunkIdentifier::new(42)),
1007 new: ChunkIdentifier::new(1),
1008 next: None,
1009 },
1010 Update::PushItems {
1011 at: Position::new(ChunkIdentifier::new(1), 0),
1012 items: vec![ev1.clone()],
1013 },
1014 Update::NewItemsChunk {
1016 previous: Some(ChunkIdentifier::new(1)),
1017 new: ChunkIdentifier::new(2),
1018 next: None,
1019 },
1020 Update::PushItems {
1021 at: Position::new(ChunkIdentifier::new(2), 0),
1022 items: vec![ev2.clone()],
1023 },
1024 ],
1025 )
1026 .await
1027 .unwrap();
1028
1029 let client = MockClientBuilder::new(None)
1030 .on_builder(|builder| {
1031 builder.store_config(
1032 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1033 .event_cache_store(event_cache_store.clone()),
1034 )
1035 })
1036 .build()
1037 .await;
1038
1039 let event_cache = client.event_cache().unwrap();
1040
1041 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1042
1043 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1044
1045 let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1046 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1047
1048 {
1050 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1051 assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1052 }
1053
1054 {
1056 assert_eq!(items.len(), 1);
1059 assert_eq!(items[0].event_id().unwrap(), event_id2);
1060
1061 assert!(stream.is_empty());
1062 }
1063
1064 {
1066 room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1067
1068 assert_let_timeout!(
1069 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1070 stream.recv()
1071 );
1072 assert_eq!(diffs.len(), 1);
1073 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1074 assert_eq!(event.event_id().unwrap(), event_id1);
1076 });
1077
1078 assert!(stream.is_empty());
1079
1080 assert_let_timeout!(
1081 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) =
1082 generic_stream.recv()
1083 );
1084 assert_eq!(room_id, expected_room_id);
1085 assert!(generic_stream.is_empty());
1086 }
1087
1088 event_cache.clear_all_rooms().await.unwrap();
1090
1091 assert_let_timeout!(
1093 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1094 stream.recv()
1095 );
1096 assert_eq!(diffs.len(), 1);
1097 assert_let!(VectorDiff::Clear = &diffs[0]);
1098
1099 assert_let_timeout!(
1101 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) = generic_stream.recv()
1102 );
1103 assert_eq!(received_room_id, room_id);
1104 assert!(generic_stream.is_empty());
1105
1106 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_none());
1108
1109 let items = room_event_cache.events().await.unwrap();
1111 assert!(items.is_empty());
1112
1113 assert!(
1115 event_cache_store
1116 .load_all_chunks(LinkedChunkId::Room(room_id))
1117 .await
1118 .unwrap()
1119 .is_empty()
1120 );
1121 }
1122
1123 #[async_test]
1124 async fn test_load_from_storage() {
1125 let room_id = room_id!("!galette:saucisse.bzh");
1126 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1127
1128 let event_id1 = event_id!("$1");
1129 let event_id2 = event_id!("$2");
1130
1131 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_event();
1132 let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_event();
1133
1134 let event_cache_store = Arc::new(MemoryStore::new());
1136 event_cache_store
1137 .handle_linked_chunk_updates(
1138 LinkedChunkId::Room(room_id),
1139 vec![
1140 Update::NewItemsChunk {
1142 previous: None,
1143 new: ChunkIdentifier::new(0),
1144 next: None,
1145 },
1146 Update::NewGapChunk {
1148 previous: Some(ChunkIdentifier::new(0)),
1149 new: ChunkIdentifier::new(42),
1151 next: None,
1152 gap: Gap { token: "cheddar".to_owned() },
1153 },
1154 Update::NewItemsChunk {
1156 previous: Some(ChunkIdentifier::new(42)),
1157 new: ChunkIdentifier::new(1),
1158 next: None,
1159 },
1160 Update::PushItems {
1161 at: Position::new(ChunkIdentifier::new(1), 0),
1162 items: vec![ev1.clone()],
1163 },
1164 Update::NewItemsChunk {
1166 previous: Some(ChunkIdentifier::new(1)),
1167 new: ChunkIdentifier::new(2),
1168 next: None,
1169 },
1170 Update::PushItems {
1171 at: Position::new(ChunkIdentifier::new(2), 0),
1172 items: vec![ev2.clone()],
1173 },
1174 ],
1175 )
1176 .await
1177 .unwrap();
1178
1179 let client = MockClientBuilder::new(None)
1180 .on_builder(|builder| {
1181 builder.store_config(
1182 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1183 .event_cache_store(event_cache_store.clone()),
1184 )
1185 })
1186 .build()
1187 .await;
1188
1189 let event_cache = client.event_cache().unwrap();
1190
1191 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1194
1195 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1196
1197 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1198
1199 assert_matches!(
1202 generic_stream.recv().await,
1203 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1204 assert_eq!(room_id, expected_room_id);
1205 }
1206 );
1207 assert!(generic_stream.is_empty());
1208
1209 let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1210
1211 assert_eq!(items.len(), 1);
1214 assert_eq!(items[0].event_id().unwrap(), event_id2);
1215 assert!(stream.is_empty());
1216
1217 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1220 assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1221
1222 room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1224
1225 assert_let_timeout!(
1226 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1227 stream.recv()
1228 );
1229 assert_eq!(diffs.len(), 1);
1230 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1231 assert_eq!(event.event_id().unwrap(), event_id1);
1232 });
1233
1234 assert!(stream.is_empty());
1235
1236 assert_matches!(
1238 generic_stream.recv().await,
1239 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1240 assert_eq!(expected_room_id, room_id);
1241 }
1242 );
1243 assert!(generic_stream.is_empty());
1244
1245 let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev2] };
1247
1248 room_event_cache
1249 .handle_joined_room_update(
1250 timeline,
1251 MaybeReceiptEventContent::none(),
1252 Default::default(),
1253 Default::default(),
1254 Default::default(),
1255 )
1256 .await
1257 .unwrap();
1258
1259 assert!(generic_stream.recv().now_or_never().is_none());
1262
1263 let items = room_event_cache.events().await.unwrap();
1268 assert_eq!(items.len(), 2);
1269 assert_eq!(items[0].event_id().unwrap(), event_id1);
1270 assert_eq!(items[1].event_id().unwrap(), event_id2);
1271 }
1272
1273 #[async_test]
1274 async fn test_load_from_storage_resilient_to_failure() {
1275 let room_id = room_id!("!fondue:patate.ch");
1276
1277 let event = EventFactory::new()
1278 .room(room_id)
1279 .sender(user_id!("@ben:saucisse.bzh"))
1280 .text_msg("foo")
1281 .event_id(event_id!("$42"))
1282 .into_event();
1283
1284 let event_cache_store = Arc::new(MemoryStore::new());
1286 event_cache_store
1287 .handle_linked_chunk_updates(
1288 LinkedChunkId::Room(room_id),
1289 vec![
1290 Update::NewItemsChunk {
1291 previous: None,
1292 new: ChunkIdentifier::new(0),
1293 next: None,
1294 },
1295 Update::PushItems {
1296 at: Position::new(ChunkIdentifier::new(0), 0),
1297 items: vec![event],
1298 },
1299 Update::NewItemsChunk {
1300 previous: Some(ChunkIdentifier::new(0)),
1301 new: ChunkIdentifier::new(1),
1302 next: Some(ChunkIdentifier::new(0)),
1303 },
1304 ],
1305 )
1306 .await
1307 .unwrap();
1308
1309 let client = MockClientBuilder::new(None)
1310 .on_builder(|builder| {
1311 builder.store_config(
1312 StoreConfig::new(CrossProcessLockConfig::multi_process("holder"))
1313 .event_cache_store(event_cache_store.clone()),
1314 )
1315 })
1316 .build()
1317 .await;
1318
1319 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1320 let event_cache = client.event_cache().unwrap();
1321
1322 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1323
1324 let items = room_event_cache.events().await.unwrap();
1325
1326 assert!(items.is_empty());
1329
1330 let raw_chunks =
1333 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap();
1334 assert!(raw_chunks.is_empty());
1335 }
1336
1337 #[async_test]
1338 async fn test_no_useless_gaps() {
1339 let room_id = room_id!("!galette:saucisse.bzh");
1340
1341 let client = MockClientBuilder::new(None).build().await;
1342
1343 let event_cache = client.event_cache().unwrap();
1344
1345 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1346 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1347 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1348
1349 let f = EventFactory::new().room(room_id).sender(*ALICE);
1350
1351 room_event_cache
1354 .handle_joined_room_update(
1355 Timeline {
1356 limited: true,
1357 prev_batch: Some("raclette".to_owned()),
1358 events: vec![f.text_msg("hey yo").into_event()],
1359 },
1360 MaybeReceiptEventContent::none(),
1361 Default::default(),
1362 Default::default(),
1363 Default::default(),
1364 )
1365 .await
1366 .unwrap();
1367
1368 assert_matches!(
1370 generic_stream.recv().await,
1371 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1372 assert_eq!(expected_room_id, room_id);
1373 }
1374 );
1375 assert!(generic_stream.is_empty());
1376
1377 {
1378 let state = room_event_cache.inner.state.read().await.unwrap();
1379
1380 let mut num_gaps = 0;
1381 let mut num_events = 0;
1382
1383 for c in state.room_linked_chunk().chunks() {
1384 match c.content() {
1385 ChunkContent::Items(items) => num_events += items.len(),
1386 ChunkContent::Gap(_) => num_gaps += 1,
1387 }
1388 }
1389
1390 assert_eq!(num_gaps, 0);
1393 assert_eq!(num_events, 1);
1394 }
1395
1396 assert_matches!(
1398 room_event_cache.pagination().load_more_events_backwards().await.unwrap(),
1399 LoadMoreEventsBackwardsOutcome::Gap { .. }
1400 );
1401
1402 {
1403 let state = room_event_cache.inner.state.read().await.unwrap();
1404
1405 let mut num_gaps = 0;
1406 let mut num_events = 0;
1407
1408 for c in state.room_linked_chunk().chunks() {
1409 match c.content() {
1410 ChunkContent::Items(items) => num_events += items.len(),
1411 ChunkContent::Gap(_) => num_gaps += 1,
1412 }
1413 }
1414
1415 assert_eq!(num_gaps, 1);
1417 assert_eq!(num_events, 1);
1418 }
1419
1420 room_event_cache
1423 .handle_joined_room_update(
1424 Timeline {
1425 limited: false,
1426 prev_batch: Some("fondue".to_owned()),
1427 events: vec![f.text_msg("sup").into_event()],
1428 },
1429 MaybeReceiptEventContent::none(),
1430 Default::default(),
1431 Default::default(),
1432 Default::default(),
1433 )
1434 .await
1435 .unwrap();
1436
1437 assert_matches!(
1439 generic_stream.recv().await,
1440 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1441 assert_eq!(expected_room_id, room_id);
1442 }
1443 );
1444 assert!(generic_stream.is_empty());
1445
1446 {
1447 let state = room_event_cache.inner.state.read().await.unwrap();
1448
1449 let mut num_gaps = 0;
1450 let mut num_events = 0;
1451
1452 for c in state.room_linked_chunk().chunks() {
1453 match c.content() {
1454 ChunkContent::Items(items) => num_events += items.len(),
1455 ChunkContent::Gap(gap) => {
1456 assert_eq!(gap.token, "raclette");
1457 num_gaps += 1;
1458 }
1459 }
1460 }
1461
1462 assert_eq!(num_gaps, 1);
1464 assert_eq!(num_events, 2);
1465 }
1466 }
1467
1468 #[async_test]
1469 async fn test_shrink_to_last_chunk() {
1470 let room_id = room_id!("!galette:saucisse.bzh");
1471
1472 let f = EventFactory::new().room(room_id);
1473
1474 let evid1 = event_id!("$1");
1475 let evid2 = event_id!("$2");
1476
1477 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(evid1).into_event();
1478 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1479
1480 let event_cache_store = MemoryStore::new();
1483 event_cache_store
1484 .handle_linked_chunk_updates(
1485 LinkedChunkId::Room(room_id),
1486 vec![
1487 Update::NewItemsChunk {
1488 previous: None,
1489 new: ChunkIdentifier::new(0),
1490 next: None,
1491 },
1492 Update::PushItems {
1493 at: Position::new(ChunkIdentifier::new(0), 0),
1494 items: vec![ev1],
1495 },
1496 Update::NewItemsChunk {
1497 previous: Some(ChunkIdentifier::new(0)),
1498 new: ChunkIdentifier::new(1),
1499 next: None,
1500 },
1501 Update::PushItems {
1502 at: Position::new(ChunkIdentifier::new(1), 0),
1503 items: vec![ev2],
1504 },
1505 ],
1506 )
1507 .await
1508 .unwrap();
1509
1510 let client = MockClientBuilder::new(None)
1511 .on_builder(move |builder| {
1512 builder.store_config(
1513 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1514 holder_name: "foo".to_owned(),
1515 })
1516 .event_cache_store(event_cache_store),
1517 )
1518 })
1519 .build()
1520 .await;
1521
1522 let event_cache = client.event_cache().unwrap();
1523
1524 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1525 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1526
1527 let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
1529 assert_eq!(events.len(), 1);
1530 assert_eq!(events[0].event_id(), Some(evid2));
1531 assert!(stream.is_empty());
1532
1533 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1534
1535 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1537 assert_eq!(outcome.events.len(), 1);
1538 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1539 assert!(outcome.reached_start);
1540
1541 assert_let_timeout!(
1543 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1544 stream.recv()
1545 );
1546 assert_eq!(diffs.len(), 1);
1547 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1548 assert_eq!(value.event_id(), Some(evid1));
1549 });
1550
1551 assert!(stream.is_empty());
1552
1553 assert_let_timeout!(
1555 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1556 );
1557 assert_eq!(expected_room_id, room_id);
1558 assert!(generic_stream.is_empty());
1559
1560 room_event_cache
1562 .inner
1563 .state
1564 .reload_no_preprocessing()
1565 .await
1566 .expect("shrinking should succeed");
1567
1568 assert_let_timeout!(
1570 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1571 stream.recv()
1572 );
1573 assert_eq!(diffs.len(), 2);
1574 assert_matches!(&diffs[0], VectorDiff::Clear);
1575 assert_matches!(&diffs[1], VectorDiff::Append { values} => {
1576 assert_eq!(values.len(), 1);
1577 assert_eq!(values[0].event_id(), Some(evid2));
1578 });
1579
1580 assert!(stream.is_empty());
1581
1582 assert_let_timeout!(Ok(RoomEventCacheGenericUpdate { .. }) = generic_stream.recv());
1584 assert!(generic_stream.is_empty());
1585
1586 let events = room_event_cache.events().await.unwrap();
1588 assert_eq!(events.len(), 1);
1589 assert_eq!(events[0].event_id(), Some(evid2));
1590
1591 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1594 assert_eq!(outcome.events.len(), 1);
1595 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1596 assert!(outcome.reached_start);
1597 }
1598
1599 #[async_test]
1600 async fn test_room_ordering() {
1601 let room_id = room_id!("!galette:saucisse.bzh");
1602
1603 let f = EventFactory::new().room(room_id).sender(*ALICE);
1604
1605 let evid1 = event_id!("$1");
1606 let evid2 = event_id!("$2");
1607 let evid3 = event_id!("$3");
1608
1609 let ev1 = f.text_msg("hello world").event_id(evid1).into_event();
1610 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1611 let ev3 = f.text_msg("yo").event_id(evid3).into_event();
1612
1613 let event_cache_store = MemoryStore::new();
1616 event_cache_store
1617 .handle_linked_chunk_updates(
1618 LinkedChunkId::Room(room_id),
1619 vec![
1620 Update::NewItemsChunk {
1621 previous: None,
1622 new: ChunkIdentifier::new(0),
1623 next: None,
1624 },
1625 Update::PushItems {
1626 at: Position::new(ChunkIdentifier::new(0), 0),
1627 items: vec![ev1, ev2],
1628 },
1629 Update::NewItemsChunk {
1630 previous: Some(ChunkIdentifier::new(0)),
1631 new: ChunkIdentifier::new(1),
1632 next: None,
1633 },
1634 Update::PushItems {
1635 at: Position::new(ChunkIdentifier::new(1), 0),
1636 items: vec![ev3.clone()],
1637 },
1638 ],
1639 )
1640 .await
1641 .unwrap();
1642
1643 let client = MockClientBuilder::new(None)
1644 .on_builder(move |builder| {
1645 builder.store_config(
1646 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1647 holder_name: "foo".to_owned(),
1648 })
1649 .event_cache_store(event_cache_store),
1650 )
1651 })
1652 .build()
1653 .await;
1654
1655 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1656 let event_cache = client.event_cache().unwrap();
1657
1658 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1659
1660 {
1663 let state = room_event_cache.inner.state.read().await.unwrap();
1664 let room_linked_chunk = state.room_linked_chunk();
1665
1666 assert_eq!(
1668 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1669 Some(0)
1670 );
1671
1672 assert_eq!(
1674 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1675 Some(1)
1676 );
1677
1678 let mut events = room_linked_chunk.events();
1680 let (pos, ev) = events.next().unwrap();
1681 assert_eq!(pos, Position::new(ChunkIdentifier::new(1), 0));
1682 assert_eq!(ev.event_id(), Some(evid3));
1683 assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1684
1685 assert!(events.next().is_none());
1687 }
1688
1689 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1691 assert!(outcome.reached_start);
1692
1693 {
1696 let state = room_event_cache.inner.state.read().await.unwrap();
1697 let room_linked_chunk = state.room_linked_chunk();
1698
1699 for (i, (pos, _)) in room_linked_chunk.events().enumerate() {
1700 assert_eq!(room_linked_chunk.event_order(pos), Some(i));
1701 }
1702 }
1703
1704 let evid4 = event_id!("$4");
1709 room_event_cache
1710 .handle_joined_room_update(
1711 Timeline {
1712 limited: true,
1713 prev_batch: Some("fondue".to_owned()),
1714 events: vec![ev3, f.text_msg("sup").event_id(evid4).into_event()],
1715 },
1716 MaybeReceiptEventContent::none(),
1717 Default::default(),
1718 Default::default(),
1719 Default::default(),
1720 )
1721 .await
1722 .unwrap();
1723
1724 {
1725 let state = room_event_cache.inner.state.read().await.unwrap();
1726 let room_linked_chunk = state.room_linked_chunk();
1727
1728 let mut events = room_linked_chunk.events();
1730
1731 let (pos, ev) = events.next().unwrap();
1732 assert_eq!(ev.event_id(), Some(evid3));
1733 assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1734
1735 let (pos, ev) = events.next().unwrap();
1736 assert_eq!(ev.event_id(), Some(evid4));
1737 assert_eq!(room_linked_chunk.event_order(pos), Some(3));
1738
1739 assert!(events.next().is_none());
1741
1742 assert_eq!(
1744 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1745 Some(0)
1746 );
1747 assert_eq!(
1748 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1749 Some(1)
1750 );
1751
1752 assert_eq!(
1755 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(1), 0)),
1756 None
1757 );
1758 }
1759 }
1760
1761 #[async_test]
1762 async fn test_auto_shrink_after_all_subscribers_are_gone() {
1763 let room_id = room_id!("!galette:saucisse.bzh");
1764
1765 let f = EventFactory::new().room(room_id);
1766
1767 let evid1 = event_id!("$1");
1768 let evid2 = event_id!("$2");
1769
1770 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(evid1).into_event();
1771 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1772
1773 let event_cache_store = MemoryStore::new();
1776 event_cache_store
1777 .handle_linked_chunk_updates(
1778 LinkedChunkId::Room(room_id),
1779 vec![
1780 Update::NewItemsChunk {
1781 previous: None,
1782 new: ChunkIdentifier::new(0),
1783 next: None,
1784 },
1785 Update::PushItems {
1786 at: Position::new(ChunkIdentifier::new(0), 0),
1787 items: vec![ev1],
1788 },
1789 Update::NewItemsChunk {
1790 previous: Some(ChunkIdentifier::new(0)),
1791 new: ChunkIdentifier::new(1),
1792 next: None,
1793 },
1794 Update::PushItems {
1795 at: Position::new(ChunkIdentifier::new(1), 0),
1796 items: vec![ev2],
1797 },
1798 ],
1799 )
1800 .await
1801 .unwrap();
1802
1803 let client = MockClientBuilder::new(None)
1804 .on_builder(move |builder| {
1805 builder.store_config(
1806 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1807 holder_name: "foo".to_owned(),
1808 })
1809 .event_cache_store(event_cache_store),
1810 )
1811 })
1812 .build()
1813 .await;
1814
1815 let event_cache = client.event_cache().unwrap();
1816
1817 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1818 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1819
1820 let (events1, mut stream1) = room_event_cache.subscribe().await.unwrap();
1822 assert_eq!(events1.len(), 1);
1823 assert_eq!(events1[0].event_id(), Some(evid2));
1824 assert!(stream1.is_empty());
1825
1826 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1827
1828 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1830 assert_eq!(outcome.events.len(), 1);
1831 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1832 assert!(outcome.reached_start);
1833
1834 assert_let_timeout!(
1837 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1838 stream1.recv()
1839 );
1840 assert_eq!(diffs.len(), 1);
1841 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1842 assert_eq!(value.event_id(), Some(evid1));
1843 });
1844
1845 assert!(stream1.is_empty());
1846
1847 assert_let_timeout!(
1848 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1849 );
1850 assert_eq!(expected_room_id, room_id);
1851 assert!(generic_stream.is_empty());
1852
1853 let (events2, stream2) = room_event_cache.subscribe().await.unwrap();
1857 assert_eq!(events2.len(), 2);
1858 assert_eq!(events2[0].event_id(), Some(evid1));
1859 assert_eq!(events2[1].event_id(), Some(evid2));
1860 assert!(stream2.is_empty());
1861
1862 let subscriber = {
1864 let state = room_event_cache.inner.state.read().await.unwrap();
1865 state.update_sender.new_room_receiver()
1866 };
1867
1868 drop(stream1);
1870 yield_now().await;
1871
1872 assert!(stream2.is_empty());
1874
1875 drop(stream2);
1877 yield_now().await;
1878
1879 {
1882 let state = room_event_cache.inner.state.read().await.unwrap();
1884 assert_eq!(state.subscribers_handle().count(), 0);
1885
1886 assert!(subscriber.is_empty());
1888 assert!(generic_stream.is_empty());
1889 }
1890
1891 let events3 = room_event_cache.events().await.unwrap();
1893 assert_eq!(events3.len(), 1);
1894 assert_eq!(events3[0].event_id(), Some(evid2));
1895 }
1896
1897 #[async_test]
1898 async fn test_rfind_map_event_in_memory_by() {
1899 let user_id = user_id!("@mnt_io:matrix.org");
1900 let room_id = room_id!("!raclette:patate.ch");
1901
1902 let event_factory = EventFactory::new().room(room_id);
1903
1904 let event_id_0 = event_id!("$ev0");
1905 let event_id_1 = event_id!("$ev1");
1906 let event_id_2 = event_id!("$ev2");
1907 let event_id_3 = event_id!("$ev3");
1908
1909 let event_0 =
1910 event_factory.text_msg("hello").sender(*BOB).event_id(event_id_0).into_event();
1911 let event_1 =
1912 event_factory.text_msg("world").sender(*ALICE).event_id(event_id_1).into_event();
1913 let event_2 = event_factory.text_msg("!").sender(*ALICE).event_id(event_id_2).into_event();
1914 let event_3 =
1915 event_factory.text_msg("eh!").sender(user_id).event_id(event_id_3).into_event();
1916
1917 let event_cache_store = MemoryStore::new();
1920 event_cache_store
1921 .handle_linked_chunk_updates(
1922 LinkedChunkId::Room(room_id),
1923 vec![
1924 Update::NewItemsChunk {
1925 previous: None,
1926 new: ChunkIdentifier::new(0),
1927 next: None,
1928 },
1929 Update::PushItems {
1930 at: Position::new(ChunkIdentifier::new(0), 0),
1931 items: vec![event_3],
1932 },
1933 Update::NewItemsChunk {
1934 previous: Some(ChunkIdentifier::new(0)),
1935 new: ChunkIdentifier::new(1),
1936 next: None,
1937 },
1938 Update::PushItems {
1939 at: Position::new(ChunkIdentifier::new(1), 0),
1940 items: vec![event_0, event_1, event_2],
1941 },
1942 ],
1943 )
1944 .await
1945 .unwrap();
1946
1947 let client = MockClientBuilder::new(None)
1948 .on_builder(move |builder| {
1949 builder.store_config(
1950 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1951 holder_name: "foo".to_owned(),
1952 })
1953 .event_cache_store(event_cache_store),
1954 )
1955 })
1956 .build()
1957 .await;
1958
1959 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1960 let event_cache = client.event_cache().unwrap();
1961 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1962
1963 assert_matches!(
1965 room_event_cache
1966 .rfind_map_event_in_memory_by(|event| {
1967 (event.sender().is_some_and(|sender| sender == User::Bob)).then(|| event.event_id().map(ToOwned::to_owned))
1968 })
1969 .await,
1970 Ok(Some(event_id)) => {
1971 assert_eq!(event_id.as_deref(), Some(event_id_0));
1972 }
1973 );
1974
1975 assert_matches!(
1978 room_event_cache
1979 .rfind_map_event_in_memory_by(|event| {
1980 (event.sender().as_deref() == Some(*ALICE)).then(|| event.event_id().map(ToOwned::to_owned))
1981 })
1982 .await,
1983 Ok(Some(event_id)) => {
1984 assert_eq!(event_id.as_deref(), Some(event_id_2));
1985 }
1986 );
1987
1988 assert!(
1990 room_event_cache
1991 .rfind_map_event_in_memory_by(|event| {
1992 (event.sender().as_deref() == Some(user_id))
1993 .then(|| event.event_id().map(ToOwned::to_owned))
1994 })
1995 .await
1996 .unwrap()
1997 .is_none()
1998 );
1999
2000 assert!(
2002 room_event_cache.rfind_map_event_in_memory_by(|_| None::<()>).await.unwrap().is_none()
2003 );
2004 }
2005
2006 #[async_test]
2007 async fn test_reload_when_dirty() {
2008 let user_id = user_id!("@mnt_io:matrix.org");
2009 let room_id = room_id!("!raclette:patate.ch");
2010
2011 let event_factory = EventFactory::new().room(room_id).sender(user_id);
2012
2013 let ev_id_0 = event_id!("$ev_0");
2014 let ev_id_1 = event_id!("$ev_1");
2015
2016 let ev_0 = event_factory.text_msg("comté").event_id(ev_id_0).into_event();
2017 let ev_1 = event_factory.text_msg("morbier").event_id(ev_id_1).into_event();
2018
2019 let event_cache_store = MemoryStore::new();
2021 event_cache_store
2022 .handle_linked_chunk_updates(
2023 LinkedChunkId::Room(room_id),
2024 vec![
2025 Update::NewItemsChunk {
2026 previous: None,
2027 new: ChunkIdentifier::new(0),
2028 next: None,
2029 },
2030 Update::PushItems {
2031 at: Position::new(ChunkIdentifier::new(0), 0),
2032 items: vec![ev_0],
2033 },
2034 Update::NewItemsChunk {
2035 previous: Some(ChunkIdentifier::new(0)),
2036 new: ChunkIdentifier::new(1),
2037 next: None,
2038 },
2039 Update::PushItems {
2040 at: Position::new(ChunkIdentifier::new(1), 0),
2041 items: vec![ev_1],
2042 },
2043 ],
2044 )
2045 .await
2046 .unwrap();
2047
2048 let client_p0 = MockClientBuilder::new(None)
2050 .on_builder(|builder| {
2051 builder.store_config(
2052 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2053 .event_cache_store(event_cache_store.clone()),
2054 )
2055 })
2056 .build()
2057 .await;
2058
2059 let client_p1 = MockClientBuilder::new(None)
2061 .on_builder(|builder| {
2062 builder.store_config(
2063 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2064 .event_cache_store(event_cache_store),
2065 )
2066 })
2067 .build()
2068 .await;
2069
2070 let (room_event_cache_p0, room_event_cache_p1) = {
2072 client_p0.base_client().get_or_create_room(room_id, RoomState::Joined);
2073 client_p1.base_client().get_or_create_room(room_id, RoomState::Joined);
2074
2075 let (room_event_cache_p0, _drop_handles) =
2076 client_p0.event_cache().unwrap().room(room_id).await.unwrap();
2077 let (room_event_cache_p1, _drop_handles) =
2078 client_p1.event_cache().unwrap().room(room_id).await.unwrap();
2079
2080 (room_event_cache_p0, room_event_cache_p1)
2081 };
2082
2083 let mut updates_stream_p0 = {
2088 let room_event_cache = &room_event_cache_p0;
2089
2090 let (initial_updates, mut updates_stream) =
2091 room_event_cache_p0.subscribe().await.unwrap();
2092
2093 assert_eq!(initial_updates.len(), 1);
2095 assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2096 assert!(updates_stream.is_empty());
2097
2098 assert!(event_loaded(room_event_cache, ev_id_1).await);
2100
2101 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2103
2104 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2106
2107 assert_matches!(
2109 updates_stream.recv().await.unwrap(),
2110 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2111 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2112 assert_matches!(
2113 &diffs[0],
2114 VectorDiff::Insert { index: 0, value: event } => {
2115 assert_eq!(event.event_id(), Some(ev_id_0));
2116 }
2117 );
2118 }
2119 );
2120
2121 assert!(event_loaded(room_event_cache, ev_id_0).await);
2123
2124 updates_stream
2125 };
2126
2127 let mut updates_stream_p1 = {
2129 let room_event_cache = &room_event_cache_p1;
2130 let (initial_updates, mut updates_stream) =
2131 room_event_cache_p1.subscribe().await.unwrap();
2132
2133 assert_eq!(initial_updates.len(), 1);
2135 assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2136 assert!(updates_stream.is_empty());
2137
2138 assert!(event_loaded(room_event_cache, ev_id_1).await);
2140
2141 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2143
2144 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2146
2147 assert_matches!(
2149 updates_stream.recv().await.unwrap(),
2150 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2151 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2152 assert_matches!(
2153 &diffs[0],
2154 VectorDiff::Insert { index: 0, value: event } => {
2155 assert_eq!(event.event_id(), Some(ev_id_0));
2156 }
2157 );
2158 }
2159 );
2160
2161 assert!(event_loaded(room_event_cache, ev_id_0).await);
2163
2164 updates_stream
2165 };
2166
2167 for _ in 0..3 {
2169 {
2173 let room_event_cache = &room_event_cache_p0;
2174 let updates_stream = &mut updates_stream_p0;
2175
2176 assert!(event_loaded(room_event_cache, ev_id_1).await);
2178
2179 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2182
2183 assert_matches!(
2185 updates_stream.recv().await.unwrap(),
2186 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2187 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2188 assert_matches!(&diffs[0], VectorDiff::Clear);
2189 assert_matches!(
2190 &diffs[1],
2191 VectorDiff::Append { values: events } => {
2192 assert_eq!(events.len(), 1);
2193 assert_eq!(events[0].event_id(), Some(ev_id_1));
2194 }
2195 );
2196 }
2197 );
2198
2199 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2201
2202 assert!(event_loaded(room_event_cache, ev_id_0).await);
2204
2205 assert_matches!(
2207 updates_stream.recv().await.unwrap(),
2208 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2209 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2210 assert_matches!(
2211 &diffs[0],
2212 VectorDiff::Insert { index: 0, value: event } => {
2213 assert_eq!(event.event_id(), Some(ev_id_0));
2214 }
2215 );
2216 }
2217 );
2218 }
2219
2220 {
2224 let room_event_cache = &room_event_cache_p1;
2225 let updates_stream = &mut updates_stream_p1;
2226
2227 assert!(event_loaded(room_event_cache, ev_id_1).await);
2229
2230 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2233
2234 assert_matches!(
2236 updates_stream.recv().await.unwrap(),
2237 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2238 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2239 assert_matches!(&diffs[0], VectorDiff::Clear);
2240 assert_matches!(
2241 &diffs[1],
2242 VectorDiff::Append { values: events } => {
2243 assert_eq!(events.len(), 1);
2244 assert_eq!(events[0].event_id(), Some(ev_id_1));
2245 }
2246 );
2247 }
2248 );
2249
2250 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2252
2253 assert!(event_loaded(room_event_cache, ev_id_0).await);
2255
2256 assert_matches!(
2258 updates_stream.recv().await.unwrap(),
2259 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2260 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2261 assert_matches!(
2262 &diffs[0],
2263 VectorDiff::Insert { index: 0, value: event } => {
2264 assert_eq!(event.event_id(), Some(ev_id_0));
2265 }
2266 );
2267 }
2268 );
2269 }
2270 }
2271
2272 for _ in 0..3 {
2275 {
2276 let room_event_cache = &room_event_cache_p0;
2277 let updates_stream = &mut updates_stream_p0;
2278
2279 let guard = room_event_cache.inner.state.read().await.unwrap();
2280
2281 assert!(guard.is_dirty().not());
2287
2288 assert_matches!(
2290 updates_stream.recv().await.unwrap(),
2291 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2292 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2293 assert_matches!(&diffs[0], VectorDiff::Clear);
2294 assert_matches!(
2295 &diffs[1],
2296 VectorDiff::Append { values: events } => {
2297 assert_eq!(events.len(), 1);
2298 assert_eq!(events[0].event_id(), Some(ev_id_1));
2299 }
2300 );
2301 }
2302 );
2303
2304 assert!(event_loaded(room_event_cache, ev_id_1).await);
2305 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2306
2307 drop(guard);
2313
2314 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2315 assert!(event_loaded(room_event_cache, ev_id_0).await);
2316
2317 assert_matches!(
2319 updates_stream.recv().await.unwrap(),
2320 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2321 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2322 assert_matches!(
2323 &diffs[0],
2324 VectorDiff::Insert { index: 0, value: event } => {
2325 assert_eq!(event.event_id(), Some(ev_id_0));
2326 }
2327 );
2328 }
2329 );
2330 }
2331
2332 {
2333 let room_event_cache = &room_event_cache_p1;
2334 let updates_stream = &mut updates_stream_p1;
2335
2336 let guard = room_event_cache.inner.state.read().await.unwrap();
2337
2338 assert!(guard.is_dirty().not());
2343
2344 assert_matches!(
2346 updates_stream.recv().await.unwrap(),
2347 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2348 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2349 assert_matches!(&diffs[0], VectorDiff::Clear);
2350 assert_matches!(
2351 &diffs[1],
2352 VectorDiff::Append { values: events } => {
2353 assert_eq!(events.len(), 1);
2354 assert_eq!(events[0].event_id(), Some(ev_id_1));
2355 }
2356 );
2357 }
2358 );
2359
2360 assert!(event_loaded(room_event_cache, ev_id_1).await);
2361 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2362
2363 drop(guard);
2369
2370 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2371 assert!(event_loaded(room_event_cache, ev_id_0).await);
2372
2373 assert_matches!(
2375 updates_stream.recv().await.unwrap(),
2376 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2377 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2378 assert_matches!(
2379 &diffs[0],
2380 VectorDiff::Insert { index: 0, value: event } => {
2381 assert_eq!(event.event_id(), Some(ev_id_0));
2382 }
2383 );
2384 }
2385 );
2386 }
2387 }
2388
2389 for _ in 0..3 {
2391 {
2392 let room_event_cache = &room_event_cache_p0;
2393 let updates_stream = &mut updates_stream_p0;
2394
2395 let guard = room_event_cache.inner.state.write().await.unwrap();
2396
2397 assert!(guard.is_dirty().not());
2399
2400 assert_matches!(
2402 updates_stream.recv().await.unwrap(),
2403 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2404 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2405 assert_matches!(&diffs[0], VectorDiff::Clear);
2406 assert_matches!(
2407 &diffs[1],
2408 VectorDiff::Append { values: events } => {
2409 assert_eq!(events.len(), 1);
2410 assert_eq!(events[0].event_id(), Some(ev_id_1));
2411 }
2412 );
2413 }
2414 );
2415
2416 drop(guard);
2419
2420 assert!(event_loaded(room_event_cache, ev_id_1).await);
2421 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2422
2423 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2424 assert!(event_loaded(room_event_cache, ev_id_0).await);
2425
2426 assert_matches!(
2428 updates_stream.recv().await.unwrap(),
2429 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2430 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2431 assert_matches!(
2432 &diffs[0],
2433 VectorDiff::Insert { index: 0, value: event } => {
2434 assert_eq!(event.event_id(), Some(ev_id_0));
2435 }
2436 );
2437 }
2438 );
2439 }
2440
2441 {
2442 let room_event_cache = &room_event_cache_p1;
2443 let updates_stream = &mut updates_stream_p1;
2444
2445 let guard = room_event_cache.inner.state.write().await.unwrap();
2446
2447 assert!(guard.is_dirty().not());
2449
2450 assert_matches!(
2452 updates_stream.recv().await.unwrap(),
2453 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2454 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2455 assert_matches!(&diffs[0], VectorDiff::Clear);
2456 assert_matches!(
2457 &diffs[1],
2458 VectorDiff::Append { values: events } => {
2459 assert_eq!(events.len(), 1);
2460 assert_eq!(events[0].event_id(), Some(ev_id_1));
2461 }
2462 );
2463 }
2464 );
2465
2466 drop(guard);
2469
2470 assert!(event_loaded(room_event_cache, ev_id_1).await);
2471 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2472
2473 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2474 assert!(event_loaded(room_event_cache, ev_id_0).await);
2475
2476 assert_matches!(
2478 updates_stream.recv().await.unwrap(),
2479 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2480 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2481 assert_matches!(
2482 &diffs[0],
2483 VectorDiff::Insert { index: 0, value: event } => {
2484 assert_eq!(event.event_id(), Some(ev_id_0));
2485 }
2486 );
2487 }
2488 );
2489 }
2490 }
2491 }
2492
2493 #[async_test]
2494 async fn test_load_when_dirty() {
2495 let room_id_0 = room_id!("!raclette:patate.ch");
2496 let room_id_1 = room_id!("!morbiflette:patate.ch");
2497
2498 let event_cache_store = MemoryStore::new();
2500
2501 let client_p0 = MockClientBuilder::new(None)
2503 .on_builder(|builder| {
2504 builder.store_config(
2505 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2506 .event_cache_store(event_cache_store.clone()),
2507 )
2508 })
2509 .build()
2510 .await;
2511
2512 let client_p1 = MockClientBuilder::new(None)
2514 .on_builder(|builder| {
2515 builder.store_config(
2516 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2517 .event_cache_store(event_cache_store),
2518 )
2519 })
2520 .build()
2521 .await;
2522
2523 let (room_event_cache_0_p0, room_event_cache_0_p1) = {
2525 client_p0.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2526 client_p0.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2527
2528 client_p1.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2529 client_p1.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2530
2531 let (room_event_cache_0_p0, _drop_handles) =
2532 client_p0.event_cache().unwrap().room(room_id_0).await.unwrap();
2533 let (room_event_cache_0_p1, _drop_handles) =
2534 client_p1.event_cache().unwrap().room(room_id_0).await.unwrap();
2535
2536 (room_event_cache_0_p0, room_event_cache_0_p1)
2537 };
2538
2539 {
2541 drop(room_event_cache_0_p0.inner.state.read().await.unwrap());
2542 drop(room_event_cache_0_p1.inner.state.read().await.unwrap());
2543 }
2544
2545 let (room_event_cache_1_p0, _) =
2549 client_p0.event_cache().unwrap().room(room_id_1).await.unwrap();
2550
2551 {
2553 let guard = room_event_cache_1_p0.inner.state.read().await.unwrap();
2554 assert!(guard.is_dirty().not());
2555 }
2556
2557 }
2560
2561 #[async_test]
2562 async fn test_uniq_read_marker() {
2563 let client = MockClientBuilder::new(None).build().await;
2564 let room_id = room_id!("!galette:saucisse.bzh");
2565 client.base_client().get_or_create_room(room_id, RoomState::Joined);
2566
2567 let event_cache = client.event_cache().unwrap();
2568
2569 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2570 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
2571 let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
2572
2573 assert!(events.is_empty());
2574
2575 let read_marker_event = Raw::from_json_string(
2577 json!({
2578 "content": {
2579 "event_id": "$crepe:saucisse.bzh"
2580 },
2581 "room_id": "!galette:saucisse.bzh",
2582 "type": "m.fully_read"
2583 })
2584 .to_string(),
2585 )
2586 .unwrap();
2587 let account_data = vec![read_marker_event; 100];
2588
2589 room_event_cache
2590 .handle_joined_room_update(
2591 Default::default(),
2592 MaybeReceiptEventContent::none(),
2593 account_data,
2594 Default::default(),
2595 Default::default(),
2596 )
2597 .await
2598 .unwrap();
2599
2600 assert_matches!(
2602 stream.recv().await.unwrap(),
2603 RoomEventCacheUpdate::MoveReadMarkerTo { .. }
2604 );
2605
2606 assert!(stream.recv().now_or_never().is_none());
2607
2608 assert!(generic_stream.recv().now_or_never().is_none());
2610 }
2611
2612 async fn event_loaded(room_event_cache: &RoomEventCache, event_id: &EventId) -> bool {
2613 room_event_cache
2614 .rfind_map_event_in_memory_by(|event| {
2615 (event.event_id() == Some(event_id)).then_some(())
2616 })
2617 .await
2618 .unwrap()
2619 .is_some()
2620 }
2621}