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::{JoinedRoomUpdate, LeftRoomUpdate, Timeline},
26};
27use ruma::{
28 EventId, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId,
29 events::{AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent, 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 subscriber::{AutoShrinkMessage, Subscriber},
52};
53use crate::room::WeakRoom;
54
55#[derive(Clone)]
59pub struct RoomEventCache {
60 inner: Arc<RoomEventCacheInner>,
61}
62
63impl fmt::Debug for RoomEventCache {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.debug_struct("RoomEventCache").finish_non_exhaustive()
66 }
67}
68
69impl RoomEventCache {
70 pub(super) fn new(
72 room_id: OwnedRoomId,
73 weak_room: WeakRoom,
74 own_user_id: OwnedUserId,
75 state: CacheStateLock<RoomStateSelector>,
76 shared_pagination_status: SharedObservable<SharedPaginationStatus>,
77 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
78 update_sender: RoomEventCacheUpdateSender,
79 ) -> Self {
80 Self {
81 inner: Arc::new(RoomEventCacheInner {
82 room_id,
83 weak_room,
84 own_user_id,
85 state,
86 update_sender,
87 pagination_batch_token_notifier: Notify::new(),
88 auto_shrink_sender,
89 shared_pagination_status,
90 }),
91 }
92 }
93
94 pub fn room_id(&self) -> &RoomId {
96 &self.inner.room_id
97 }
98
99 pub(super) fn own_user_id(&self) -> &OwnedUserId {
101 &self.inner.own_user_id
102 }
103
104 pub(super) fn weak_room(&self) -> &WeakRoom {
106 &self.inner.weak_room
107 }
108
109 pub async fn events(&self) -> Result<Vec<Event>> {
114 let state = self.inner.state.read().await?;
115
116 Ok(state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect())
117 }
118
119 pub async fn subscribe(&self) -> Result<(Vec<Event>, Subscriber<RoomEventCacheUpdate>)> {
126 let state = self.inner.state.read().await?;
127 let events =
128 state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect();
129
130 let subscribers_handle = state.subscribers_handle();
131
132 let subscriber = Subscriber::new(
133 self.inner.update_sender.new_room_receiver(),
134 AutoShrinkMessage::Room { room_id: self.inner.room_id.clone() },
135 self.inner.auto_shrink_sender.clone(),
136 subscribers_handle,
137 );
138
139 trace!("added a room event cache subscriber; new count: {}", subscribers_handle.count());
140
141 Ok((events, subscriber))
142 }
143
144 pub fn pagination(&self) -> RoomPagination {
147 RoomPagination::new(self.inner.clone())
148 }
149
150 pub async fn rfind_map_event_in_memory_by<O, P>(&self, predicate: P) -> Result<Option<O>>
158 where
159 P: FnMut(&Event) -> Option<O>,
160 {
161 Ok(self.inner.state.read().await?.rfind_map_event_in_memory_by(predicate))
162 }
163
164 pub async fn find_event(&self, event_id: &EventId) -> Result<Option<Event>> {
169 Ok(self
170 .inner
171 .state
172 .read()
173 .await?
174 .find_event(event_id)
175 .await
176 .ok()
177 .flatten()
178 .map(|(_loc, event)| event))
179 }
180
181 pub async fn find_event_with_relations(
193 &self,
194 event_id: &EventId,
195 filter: Option<Vec<RelationType>>,
196 ) -> Result<Option<(Event, Vec<Event>)>> {
197 Ok(self
199 .inner
200 .state
201 .read()
202 .await?
203 .find_event_with_relations(event_id, filter)
204 .await
205 .ok()
206 .flatten())
207 }
208
209 pub async fn find_event_relations(
221 &self,
222 event_id: &EventId,
223 filter: Option<Vec<RelationType>>,
224 ) -> Result<Vec<Event>> {
225 self.inner.state.read().await?.find_event_relations(event_id, filter.clone()).await
227 }
228
229 pub(in super::super) fn state(&self) -> &CacheStateLock<RoomStateSelector> {
231 &self.inner.state
232 }
233
234 #[instrument(skip_all, fields(room_id = %self.room_id()))]
236 pub(super) async fn handle_joined_room_update(&self, updates: JoinedRoomUpdate) -> Result<()> {
237 self.inner
238 .handle_timeline(
239 updates.timeline,
240 updates.ephemeral.clone(),
241 updates.ambiguity_changes,
242 updates.avatar_changes,
243 )
244 .await?;
245 self.inner.handle_account_data(updates.account_data);
246
247 Ok(())
248 }
249
250 #[instrument(skip_all, fields(room_id = %self.room_id()))]
252 pub(super) async fn handle_left_room_update(&self, updates: LeftRoomUpdate) -> Result<()> {
253 self.inner
254 .handle_timeline(updates.timeline, Vec::new(), updates.ambiguity_changes, None)
255 .await?;
256
257 Ok(())
258 }
259
260 pub(in super::super) async fn update_thread_summary(
261 &self,
262 thread_id: &EventId,
263 new_thread_summary: Option<ThreadSummary>,
264 ) -> Result<()> {
265 let timeline_event_diffs = self
266 .inner
267 .state
268 .write()
269 .await?
270 .update_thread_summary(thread_id, new_thread_summary)
271 .await?;
272
273 if !timeline_event_diffs.is_empty() {
274 self.inner.update_sender.send(
275 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
276 diffs: timeline_event_diffs,
277 origin: EventsOrigin::Sync,
278 }),
279 Some(RoomEventCacheGenericUpdate { room_id: self.inner.room_id.clone() }),
280 );
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(
361 RoomEventCacheUpdate::MoveReadMarkerTo { event_id: ev.content.event_id },
362 None,
363 );
364 }
365
366 Ok(_) => {
367 }
370
371 Err(e) => {
372 let event_type = raw_event.get_field::<String>("type").ok().flatten();
373 warn!(event_type, "Failed to deserialize account data: {e}");
374 }
375 }
376 }
377 }
378
379 async fn handle_timeline(
382 &self,
383 timeline: Timeline,
384 ephemeral_events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
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 ephemeral_events,
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() {
408 return self
409 .handle_timeline_inner(
410 state,
411 Timeline { limited: false, prev_batch: None, events: vec![event] },
412 Vec::new(),
413 BTreeMap::new(),
414 None,
415 )
416 .await;
417 }
418
419 Ok(())
420 }
421
422 async fn handle_timeline_inner(
423 &self,
424 mut state: StateLockWriteGuard<'_, RoomEventCacheState>,
425 timeline: Timeline,
426 ephemeral_events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
427 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
428 avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
429 ) -> Result<()> {
430 if timeline.events.is_empty()
431 && timeline.prev_batch.is_none()
432 && ephemeral_events.is_empty()
433 && ambiguity_changes.is_empty()
434 && avatar_changes.as_ref().is_none_or(|avatars| avatars.is_empty())
435 {
436 return Ok(());
437 }
438
439 trace!("adding new events");
440
441 let (stored_prev_batch_token, timeline_event_diffs) =
442 state.handle_sync(timeline, &ephemeral_events).await?;
443
444 drop(state);
445
446 if stored_prev_batch_token {
449 self.pagination_batch_token_notifier.notify_one();
450 }
451
452 if !timeline_event_diffs.is_empty() {
455 self.update_sender.send(
456 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
457 diffs: timeline_event_diffs,
458 origin: EventsOrigin::Sync,
459 }),
460 Some(RoomEventCacheGenericUpdate { room_id: self.room_id.clone() }),
461 );
462 }
463
464 if !ephemeral_events.is_empty() {
465 self.update_sender
466 .send(RoomEventCacheUpdate::AddEphemeralEvents { events: ephemeral_events }, None);
467 }
468
469 if !ambiguity_changes.is_empty() || avatar_changes.as_ref().is_some_and(|c| !c.is_empty()) {
470 self.update_sender.send(
471 RoomEventCacheUpdate::UpdateMembers { ambiguity_changes, avatar_changes },
472 None,
473 );
474 }
475
476 Ok(())
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use matrix_sdk_base::{RoomState, event_cache::Event};
483 use matrix_sdk_test::{async_test, event_factory::EventFactory};
484 use ruma::{
485 RoomId, event_id,
486 events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
487 room_id, user_id,
488 };
489
490 use crate::test_utils::logged_in_client;
491
492 #[async_test]
493 async fn test_find_event_by_id_with_edit_relation() {
494 let original_id = event_id!("$original");
495 let related_id = event_id!("$related");
496 let room_id = room_id!("!galette:saucisse.bzh");
497 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
498
499 assert_relations(
500 room_id,
501 f.text_msg("Original event").event_id(original_id).into(),
502 f.text_msg("* An edited event")
503 .edit(
504 original_id,
505 RoomMessageEventContentWithoutRelation::text_plain("And edited event"),
506 )
507 .event_id(related_id)
508 .into(),
509 f,
510 )
511 .await;
512 }
513
514 #[async_test]
515 async fn test_find_event_by_id_with_thread_reply_relation() {
516 let original_id = event_id!("$original");
517 let related_id = event_id!("$related");
518 let room_id = room_id!("!galette:saucisse.bzh");
519 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
520
521 assert_relations(
522 room_id,
523 f.text_msg("Original event").event_id(original_id).into(),
524 f.text_msg("A reply").in_thread(original_id, related_id).event_id(related_id).into(),
525 f,
526 )
527 .await;
528 }
529
530 #[async_test]
531 async fn test_find_event_by_id_with_reaction_relation() {
532 let original_id = event_id!("$original");
533 let related_id = event_id!("$related");
534 let room_id = room_id!("!galette:saucisse.bzh");
535 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
536
537 assert_relations(
538 room_id,
539 f.text_msg("Original event").event_id(original_id).into(),
540 f.reaction(original_id, ":D").event_id(related_id).into(),
541 f,
542 )
543 .await;
544 }
545
546 #[async_test]
547 async fn test_find_event_by_id_with_poll_response_relation() {
548 let original_id = event_id!("$original");
549 let related_id = event_id!("$related");
550 let room_id = room_id!("!galette:saucisse.bzh");
551 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
552
553 assert_relations(
554 room_id,
555 f.poll_start("Poll start event", "A poll question", vec!["An answer"])
556 .event_id(original_id)
557 .into(),
558 f.poll_response(vec!["1"], original_id).event_id(related_id).into(),
559 f,
560 )
561 .await;
562 }
563
564 #[async_test]
565 async fn test_find_event_by_id_with_poll_end_relation() {
566 let original_id = event_id!("$original");
567 let related_id = event_id!("$related");
568 let room_id = room_id!("!galette:saucisse.bzh");
569 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
570
571 assert_relations(
572 room_id,
573 f.poll_start("Poll start event", "A poll question", vec!["An answer"])
574 .event_id(original_id)
575 .into(),
576 f.poll_end("Poll ended", original_id).event_id(related_id).into(),
577 f,
578 )
579 .await;
580 }
581
582 #[async_test]
583 async fn test_find_event_by_id_with_filtered_relationships() {
584 let original_id = event_id!("$original");
585 let related_id = event_id!("$related");
586 let associated_related_id = event_id!("$recursive_related");
587 let room_id = room_id!("!galette:saucisse.bzh");
588 let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
589
590 let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
591 let related_event = event_factory
592 .text_msg("* Edited event")
593 .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
594 .event_id(related_id)
595 .into();
596 let associated_related_event =
597 event_factory.reaction(related_id, "🤡").event_id(associated_related_id).into();
598
599 let client = logged_in_client(None).await;
600
601 let event_cache = client.event_cache();
602 event_cache.subscribe().unwrap();
603
604 client.base_client().get_or_create_room(room_id, RoomState::Joined);
605 let room = client.get_room(room_id).unwrap();
606
607 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
608
609 {
610 let mut state = room_event_cache.inner.state.write().await.unwrap();
611
612 state.save_events([original_event]).await.unwrap();
614
615 state.save_events([related_event]).await.unwrap();
617
618 state.save_events([associated_related_event]).await.unwrap();
620 }
621
622 let filter = Some(vec![RelationType::Replacement]);
623 let (event, related_events) = room_event_cache
624 .find_event_with_relations(original_id, filter)
625 .await
626 .expect("Failed to find the event with relations")
627 .expect("Event has no relation");
628 let cached_event_id = event.event_id().unwrap();
630 assert_eq!(cached_event_id, original_id);
631
632 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 let event_cache = client.event_cache();
673 event_cache.subscribe().unwrap();
674
675 client.base_client().get_or_create_room(room_id, RoomState::Joined);
676 let room = client.get_room(room_id).unwrap();
677
678 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
679
680 {
681 let mut state = room_event_cache.inner.state.write().await.unwrap();
682
683 state.save_events([original_event]).await.unwrap();
685
686 state.save_events([related_event]).await.unwrap();
688
689 state.save_events([associated_related_event]).await.unwrap();
691 }
692
693 let (event, related_events) = room_event_cache
694 .find_event_with_relations(original_id, None)
695 .await
696 .expect("Failed to find the event with relations")
697 .expect("Event has no relation");
698 let cached_event_id = event.event_id().unwrap();
700 assert_eq!(cached_event_id, original_id);
701
702 assert_eq!(related_events.len(), 2);
704
705 let related_event_id = related_events[0].event_id().unwrap();
706 assert_eq!(related_event_id, related_id);
707 let related_event_id = related_events[1].event_id().unwrap();
708 assert_eq!(related_event_id, associated_related_id);
709 }
710
711 async fn assert_relations(
712 room_id: &RoomId,
713 original_event: Event,
714 related_event: Event,
715 event_factory: EventFactory,
716 ) {
717 let client = logged_in_client(None).await;
718
719 let event_cache = client.event_cache();
720 event_cache.subscribe().unwrap();
721
722 client.base_client().get_or_create_room(room_id, RoomState::Joined);
723 let room = client.get_room(room_id).unwrap();
724
725 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
726
727 let original_event_id = original_event.event_id().unwrap().to_owned();
728 let related_id = related_event.event_id().unwrap().to_owned();
729
730 {
731 let mut state = room_event_cache.inner.state.write().await.unwrap();
732
733 state.save_events([original_event]).await.unwrap();
735
736 let unrelated_id = event_id!("$2");
738 state
739 .save_events([event_factory
740 .text_msg("An unrelated event")
741 .event_id(unrelated_id)
742 .into()])
743 .await
744 .unwrap();
745
746 state.save_events([related_event]).await.unwrap();
748 }
749
750 let (event, related_events) = room_event_cache
751 .find_event_with_relations(&original_event_id, None)
752 .await
753 .expect("Failed to find the event with relations")
754 .expect("Event has no relation");
755 let cached_event_id = event.event_id().unwrap();
757 assert_eq!(cached_event_id, original_event_id);
758
759 let related_event_id = related_events[0].event_id().unwrap();
761 assert_eq!(related_event_id, related_id);
762 }
763}
764
765#[cfg(all(test, not(target_family = "wasm")))] mod timed_tests {
767 use std::{ops::Not, sync::Arc};
768
769 use assert_matches::assert_matches;
770 use assert_matches2::assert_let;
771 use eyeball_im::VectorDiff;
772 use futures_util::FutureExt;
773 use matrix_sdk_base::{
774 RoomState,
775 event_cache::{
776 Gap,
777 store::{EventCacheStore as _, MemoryStore},
778 },
779 linked_chunk::{
780 ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update,
781 lazy_loader::from_all_chunks,
782 },
783 store::StoreConfig,
784 sync::{JoinedRoomUpdate, Timeline},
785 };
786 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
787 use matrix_sdk_test::{ALICE, BOB, async_test, event_factory::EventFactory};
788 use ruma::{
789 EventId, event_id,
790 events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
791 room_id,
792 serde::Raw,
793 user_id,
794 };
795 use serde_json::json;
796 use tokio::task::yield_now;
797
798 use super::{
799 super::{super::TimelineVectorDiffs, pagination::LoadMoreEventsBackwardsOutcome},
800 RoomEventCache, RoomEventCacheGenericUpdate, RoomEventCacheUpdate,
801 };
802 use crate::{assert_let_timeout, test_utils::client::MockClientBuilder};
803
804 #[async_test]
805 async fn test_write_to_storage() {
806 let room_id = room_id!("!galette:saucisse.bzh");
807 let event_id_0 = event_id!("$ev0");
808 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
809
810 let event_cache_store = Arc::new(MemoryStore::new());
811
812 let client = MockClientBuilder::new(None)
813 .on_builder(|builder| {
814 builder.store_config(
815 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
816 .event_cache_store(event_cache_store.clone()),
817 )
818 })
819 .build()
820 .await;
821
822 let event_cache = client.event_cache();
823
824 event_cache.subscribe().unwrap();
826
827 client.base_client().get_or_create_room(room_id, RoomState::Joined);
828 let room = client.get_room(room_id).unwrap();
829
830 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
831 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
832
833 let timeline = Timeline {
835 limited: true,
836 prev_batch: Some("raclette".to_owned()),
837 events: vec![f.text_msg("hey yo").event_id(event_id_0).into_event()],
838 };
839
840 room_event_cache
841 .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
842 .await
843 .unwrap();
844
845 assert_matches!(
847 generic_stream.recv().await,
848 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
849 assert_eq!(expected_room_id, room_id);
850 }
851 );
852 assert!(generic_stream.is_empty());
853
854 let linked_chunk = from_all_chunks::<3, _, _>(
856 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
857 )
858 .unwrap()
859 .unwrap();
860
861 assert_eq!(linked_chunk.chunks().count(), 2);
862
863 let mut chunks = linked_chunk.chunks();
864
865 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Gap(gap) => {
867 assert_eq!(gap.token, "raclette");
868 });
869
870 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
872 assert_eq!(events.len(), 1);
873 assert_eq!(events[0].event_id(), Some(event_id_0));
874 });
875
876 assert!(chunks.next().is_none());
878 }
879
880 #[async_test]
881 async fn test_write_to_storage_strips_bundled_relations() {
882 let room_id = room_id!("!galette:saucisse.bzh");
883 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
884
885 let event_cache_store = Arc::new(MemoryStore::new());
886
887 let client = MockClientBuilder::new(None)
888 .on_builder(|builder| {
889 builder.store_config(
890 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
891 .event_cache_store(event_cache_store.clone()),
892 )
893 })
894 .build()
895 .await;
896
897 let event_cache = client.event_cache();
898
899 event_cache.subscribe().unwrap();
901
902 client.base_client().get_or_create_room(room_id, RoomState::Joined);
903 let room = client.get_room(room_id).unwrap();
904
905 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
906 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
907
908 let ev = f
910 .text_msg("hey yo")
911 .sender(*ALICE)
912 .with_bundled_edit(f.text_msg("Hello, Kind Sir").sender(*ALICE))
913 .into_event();
914
915 let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev] };
916
917 room_event_cache
918 .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
919 .await
920 .unwrap();
921
922 assert_matches!(
924 generic_stream.recv().await,
925 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
926 assert_eq!(expected_room_id, room_id);
927 }
928 );
929 assert!(generic_stream.is_empty());
930
931 {
933 let events = room_event_cache.events().await.unwrap();
934
935 assert_eq!(events.len(), 1);
936
937 let ev = events[0].raw().deserialize().unwrap();
938 assert_let!(
939 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev
940 );
941
942 let original = msg.as_original().unwrap();
943 assert_eq!(original.content.body(), "hey yo");
944 assert!(original.unsigned.relations.replace.is_some());
945 }
946
947 let linked_chunk = from_all_chunks::<3, _, _>(
949 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
950 )
951 .unwrap()
952 .unwrap();
953
954 assert_eq!(linked_chunk.chunks().count(), 1);
955
956 let mut chunks = linked_chunk.chunks();
957 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
958 assert_eq!(events.len(), 1);
959
960 let ev = events[0].raw().deserialize().unwrap();
961 assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev);
962
963 let original = msg.as_original().unwrap();
964 assert_eq!(original.content.body(), "hey yo");
965 assert!(original.unsigned.relations.replace.is_none());
966 });
967
968 assert!(chunks.next().is_none());
970 }
971
972 #[async_test]
973 async fn test_clear() {
974 let room_id = room_id!("!galette:saucisse.bzh");
975 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
976
977 let event_cache_store = Arc::new(MemoryStore::new());
978
979 let event_id1 = event_id!("$1");
980 let event_id2 = event_id!("$2");
981
982 let ev1 = f.text_msg("hello world").event_id(event_id1).into_event();
983 let ev2 = f.text_msg("how's it going").event_id(event_id2).into_event();
984
985 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();
1040
1041 event_cache.subscribe().unwrap();
1043
1044 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1045 let room = client.get_room(room_id).unwrap();
1046
1047 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1048
1049 let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1050 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1051
1052 {
1054 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1055 assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1056 }
1057
1058 {
1060 assert_eq!(items.len(), 1);
1062 assert_eq!(items[0].event_id().unwrap(), event_id2);
1063
1064 assert!(stream.is_empty());
1065 }
1066
1067 {
1069 room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1070
1071 assert_let_timeout!(
1072 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1073 stream.recv()
1074 );
1075 assert_eq!(diffs.len(), 1);
1076 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1077 assert_eq!(event.event_id().unwrap(), event_id1);
1079 });
1080
1081 assert!(stream.is_empty());
1082
1083 assert_let_timeout!(
1084 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) =
1085 generic_stream.recv()
1086 );
1087 assert_eq!(room_id, expected_room_id);
1088 assert!(generic_stream.is_empty());
1089 }
1090
1091 event_cache.clear_all_rooms().await.unwrap();
1093
1094 assert_let_timeout!(
1096 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1097 stream.recv()
1098 );
1099 assert_eq!(diffs.len(), 1);
1100 assert_let!(VectorDiff::Clear = &diffs[0]);
1101
1102 assert_let_timeout!(
1104 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) = generic_stream.recv()
1105 );
1106 assert_eq!(received_room_id, room_id);
1107 assert!(generic_stream.is_empty());
1108
1109 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_none());
1111
1112 let items = room_event_cache.events().await.unwrap();
1114 assert!(items.is_empty());
1115
1116 assert!(
1118 event_cache_store
1119 .load_all_chunks(LinkedChunkId::Room(room_id))
1120 .await
1121 .unwrap()
1122 .is_empty()
1123 );
1124 }
1125
1126 #[async_test]
1127 async fn test_load_from_storage() {
1128 let room_id = room_id!("!galette:saucisse.bzh");
1129 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1130
1131 let event_cache_store = Arc::new(MemoryStore::new());
1132
1133 let event_id1 = event_id!("$1");
1134 let event_id2 = event_id!("$2");
1135
1136 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_event();
1137 let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_event();
1138
1139 event_cache_store
1141 .handle_linked_chunk_updates(
1142 LinkedChunkId::Room(room_id),
1143 vec![
1144 Update::NewItemsChunk {
1146 previous: None,
1147 new: ChunkIdentifier::new(0),
1148 next: None,
1149 },
1150 Update::NewGapChunk {
1152 previous: Some(ChunkIdentifier::new(0)),
1153 new: ChunkIdentifier::new(42),
1155 next: None,
1156 gap: Gap { token: "cheddar".to_owned() },
1157 },
1158 Update::NewItemsChunk {
1160 previous: Some(ChunkIdentifier::new(42)),
1161 new: ChunkIdentifier::new(1),
1162 next: None,
1163 },
1164 Update::PushItems {
1165 at: Position::new(ChunkIdentifier::new(1), 0),
1166 items: vec![ev1.clone()],
1167 },
1168 Update::NewItemsChunk {
1170 previous: Some(ChunkIdentifier::new(1)),
1171 new: ChunkIdentifier::new(2),
1172 next: None,
1173 },
1174 Update::PushItems {
1175 at: Position::new(ChunkIdentifier::new(2), 0),
1176 items: vec![ev2.clone()],
1177 },
1178 ],
1179 )
1180 .await
1181 .unwrap();
1182
1183 let client = MockClientBuilder::new(None)
1184 .on_builder(|builder| {
1185 builder.store_config(
1186 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1187 .event_cache_store(event_cache_store.clone()),
1188 )
1189 })
1190 .build()
1191 .await;
1192
1193 let event_cache = client.event_cache();
1194
1195 event_cache.subscribe().unwrap();
1197
1198 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1200
1201 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1202 let room = client.get_room(room_id).unwrap();
1203
1204 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1205
1206 assert_matches!(
1209 generic_stream.recv().await,
1210 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1211 assert_eq!(room_id, expected_room_id);
1212 }
1213 );
1214 assert!(generic_stream.is_empty());
1215
1216 let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1217
1218 assert_eq!(items.len(), 1);
1221 assert_eq!(items[0].event_id().unwrap(), event_id2);
1222 assert!(stream.is_empty());
1223
1224 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1226 assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1227
1228 room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1230
1231 assert_let_timeout!(
1232 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1233 stream.recv()
1234 );
1235 assert_eq!(diffs.len(), 1);
1236 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1237 assert_eq!(event.event_id().unwrap(), event_id1);
1238 });
1239
1240 assert!(stream.is_empty());
1241
1242 assert_matches!(
1244 generic_stream.recv().await,
1245 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1246 assert_eq!(expected_room_id, room_id);
1247 }
1248 );
1249 assert!(generic_stream.is_empty());
1250
1251 let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev2] };
1253
1254 room_event_cache
1255 .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
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 let event_cache_store = Arc::new(MemoryStore::new());
1277
1278 let event = EventFactory::new()
1279 .room(room_id)
1280 .sender(user_id!("@ben:saucisse.bzh"))
1281 .text_msg("foo")
1282 .event_id(event_id!("$42"))
1283 .into_event();
1284
1285 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 let event_cache = client.event_cache();
1320
1321 event_cache.subscribe().unwrap();
1323
1324 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1325 let room = client.get_room(room_id).unwrap();
1326
1327 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1328
1329 let items = room_event_cache.events().await.unwrap();
1330
1331 assert!(items.is_empty());
1334
1335 let raw_chunks =
1338 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap();
1339 assert!(raw_chunks.is_empty());
1340 }
1341
1342 #[async_test]
1343 async fn test_no_useless_gaps() {
1344 let room_id = room_id!("!galette:saucisse.bzh");
1345
1346 let client = MockClientBuilder::new(None).build().await;
1347
1348 let event_cache = client.event_cache();
1349 event_cache.subscribe().unwrap();
1350
1351 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1352 let room = client.get_room(room_id).unwrap();
1353 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1354 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1355
1356 let f = EventFactory::new().room(room_id).sender(*ALICE);
1357
1358 room_event_cache
1361 .handle_joined_room_update(JoinedRoomUpdate {
1362 timeline: Timeline {
1363 limited: true,
1364 prev_batch: Some("raclette".to_owned()),
1365 events: vec![f.text_msg("hey yo").into_event()],
1366 },
1367 ..Default::default()
1368 })
1369 .await
1370 .unwrap();
1371
1372 assert_matches!(
1374 generic_stream.recv().await,
1375 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1376 assert_eq!(expected_room_id, room_id);
1377 }
1378 );
1379 assert!(generic_stream.is_empty());
1380
1381 {
1382 let state = room_event_cache.inner.state.read().await.unwrap();
1383
1384 let mut num_gaps = 0;
1385 let mut num_events = 0;
1386
1387 for c in state.room_linked_chunk().chunks() {
1388 match c.content() {
1389 ChunkContent::Items(items) => num_events += items.len(),
1390 ChunkContent::Gap(_) => num_gaps += 1,
1391 }
1392 }
1393
1394 assert_eq!(num_gaps, 0);
1397 assert_eq!(num_events, 1);
1398 }
1399
1400 assert_matches!(
1402 room_event_cache.pagination().load_more_events_backwards().await.unwrap(),
1403 LoadMoreEventsBackwardsOutcome::Gap { .. }
1404 );
1405
1406 {
1407 let state = room_event_cache.inner.state.read().await.unwrap();
1408
1409 let mut num_gaps = 0;
1410 let mut num_events = 0;
1411
1412 for c in state.room_linked_chunk().chunks() {
1413 match c.content() {
1414 ChunkContent::Items(items) => num_events += items.len(),
1415 ChunkContent::Gap(_) => num_gaps += 1,
1416 }
1417 }
1418
1419 assert_eq!(num_gaps, 1);
1421 assert_eq!(num_events, 1);
1422 }
1423
1424 room_event_cache
1427 .handle_joined_room_update(JoinedRoomUpdate {
1428 timeline: Timeline {
1429 limited: false,
1430 prev_batch: Some("fondue".to_owned()),
1431 events: vec![f.text_msg("sup").into_event()],
1432 },
1433 ..Default::default()
1434 })
1435 .await
1436 .unwrap();
1437
1438 assert_matches!(
1440 generic_stream.recv().await,
1441 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1442 assert_eq!(expected_room_id, room_id);
1443 }
1444 );
1445 assert!(generic_stream.is_empty());
1446
1447 {
1448 let state = room_event_cache.inner.state.read().await.unwrap();
1449
1450 let mut num_gaps = 0;
1451 let mut num_events = 0;
1452
1453 for c in state.room_linked_chunk().chunks() {
1454 match c.content() {
1455 ChunkContent::Items(items) => num_events += items.len(),
1456 ChunkContent::Gap(gap) => {
1457 assert_eq!(gap.token, "raclette");
1458 num_gaps += 1;
1459 }
1460 }
1461 }
1462
1463 assert_eq!(num_gaps, 1);
1465 assert_eq!(num_events, 2);
1466 }
1467 }
1468
1469 #[async_test]
1470 async fn test_shrink_to_last_chunk() {
1471 let room_id = room_id!("!galette:saucisse.bzh");
1472
1473 let client = MockClientBuilder::new(None).build().await;
1474
1475 let f = EventFactory::new().room(room_id);
1476
1477 let evid1 = event_id!("$1");
1478 let evid2 = event_id!("$2");
1479
1480 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(evid1).into_event();
1481 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1482
1483 {
1485 client
1486 .event_cache_store()
1487 .lock()
1488 .await
1489 .expect("Could not acquire the event cache lock")
1490 .as_clean()
1491 .expect("Could not acquire a clean event cache lock")
1492 .handle_linked_chunk_updates(
1493 LinkedChunkId::Room(room_id),
1494 vec![
1495 Update::NewItemsChunk {
1496 previous: None,
1497 new: ChunkIdentifier::new(0),
1498 next: None,
1499 },
1500 Update::PushItems {
1501 at: Position::new(ChunkIdentifier::new(0), 0),
1502 items: vec![ev1],
1503 },
1504 Update::NewItemsChunk {
1505 previous: Some(ChunkIdentifier::new(0)),
1506 new: ChunkIdentifier::new(1),
1507 next: None,
1508 },
1509 Update::PushItems {
1510 at: Position::new(ChunkIdentifier::new(1), 0),
1511 items: vec![ev2],
1512 },
1513 ],
1514 )
1515 .await
1516 .unwrap();
1517 }
1518
1519 let event_cache = client.event_cache();
1520 event_cache.subscribe().unwrap();
1521
1522 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1523 let room = client.get_room(room_id).unwrap();
1524 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1525
1526 let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
1528 assert_eq!(events.len(), 1);
1529 assert_eq!(events[0].event_id(), Some(evid2));
1530 assert!(stream.is_empty());
1531
1532 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1533
1534 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1536 assert_eq!(outcome.events.len(), 1);
1537 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1538 assert!(outcome.reached_start);
1539
1540 assert_let_timeout!(
1542 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1543 stream.recv()
1544 );
1545 assert_eq!(diffs.len(), 1);
1546 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1547 assert_eq!(value.event_id(), Some(evid1));
1548 });
1549
1550 assert!(stream.is_empty());
1551
1552 assert_let_timeout!(
1554 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1555 );
1556 assert_eq!(expected_room_id, room_id);
1557 assert!(generic_stream.is_empty());
1558
1559 room_event_cache
1561 .inner
1562 .state
1563 .reload_no_preprocessing()
1564 .await
1565 .expect("shrinking should succeed");
1566
1567 assert_let_timeout!(
1569 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1570 stream.recv()
1571 );
1572 assert_eq!(diffs.len(), 2);
1573 assert_matches!(&diffs[0], VectorDiff::Clear);
1574 assert_matches!(&diffs[1], VectorDiff::Append { values} => {
1575 assert_eq!(values.len(), 1);
1576 assert_eq!(values[0].event_id(), Some(evid2));
1577 });
1578
1579 assert!(stream.is_empty());
1580
1581 assert_let_timeout!(Ok(RoomEventCacheGenericUpdate { .. }) = generic_stream.recv());
1583 assert!(generic_stream.is_empty());
1584
1585 let events = room_event_cache.events().await.unwrap();
1587 assert_eq!(events.len(), 1);
1588 assert_eq!(events[0].event_id(), Some(evid2));
1589
1590 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1593 assert_eq!(outcome.events.len(), 1);
1594 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1595 assert!(outcome.reached_start);
1596 }
1597
1598 #[async_test]
1599 async fn test_room_ordering() {
1600 let room_id = room_id!("!galette:saucisse.bzh");
1601
1602 let client = MockClientBuilder::new(None).build().await;
1603
1604 let f = EventFactory::new().room(room_id).sender(*ALICE);
1605
1606 let evid1 = event_id!("$1");
1607 let evid2 = event_id!("$2");
1608 let evid3 = event_id!("$3");
1609
1610 let ev1 = f.text_msg("hello world").event_id(evid1).into_event();
1611 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1612 let ev3 = f.text_msg("yo").event_id(evid3).into_event();
1613
1614 {
1616 client
1617 .event_cache_store()
1618 .lock()
1619 .await
1620 .expect("Could not acquire the event cache lock")
1621 .as_clean()
1622 .expect("Could not acquire a clean event cache lock")
1623 .handle_linked_chunk_updates(
1624 LinkedChunkId::Room(room_id),
1625 vec![
1626 Update::NewItemsChunk {
1627 previous: None,
1628 new: ChunkIdentifier::new(0),
1629 next: None,
1630 },
1631 Update::PushItems {
1632 at: Position::new(ChunkIdentifier::new(0), 0),
1633 items: vec![ev1, ev2],
1634 },
1635 Update::NewItemsChunk {
1636 previous: Some(ChunkIdentifier::new(0)),
1637 new: ChunkIdentifier::new(1),
1638 next: None,
1639 },
1640 Update::PushItems {
1641 at: Position::new(ChunkIdentifier::new(1), 0),
1642 items: vec![ev3.clone()],
1643 },
1644 ],
1645 )
1646 .await
1647 .unwrap();
1648 }
1649
1650 let event_cache = client.event_cache();
1651 event_cache.subscribe().unwrap();
1652
1653 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1654 let room = client.get_room(room_id).unwrap();
1655 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1656
1657 {
1660 let state = room_event_cache.inner.state.read().await.unwrap();
1661 let room_linked_chunk = state.room_linked_chunk();
1662
1663 assert_eq!(
1665 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1666 Some(0)
1667 );
1668
1669 assert_eq!(
1671 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1672 Some(1)
1673 );
1674
1675 let mut events = room_linked_chunk.events();
1677 let (pos, ev) = events.next().unwrap();
1678 assert_eq!(pos, Position::new(ChunkIdentifier::new(1), 0));
1679 assert_eq!(ev.event_id(), Some(evid3));
1680 assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1681
1682 assert!(events.next().is_none());
1684 }
1685
1686 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1688 assert!(outcome.reached_start);
1689
1690 {
1693 let state = room_event_cache.inner.state.read().await.unwrap();
1694 let room_linked_chunk = state.room_linked_chunk();
1695
1696 for (i, (pos, _)) in room_linked_chunk.events().enumerate() {
1697 assert_eq!(room_linked_chunk.event_order(pos), Some(i));
1698 }
1699 }
1700
1701 let evid4 = event_id!("$4");
1706 room_event_cache
1707 .handle_joined_room_update(JoinedRoomUpdate {
1708 timeline: Timeline {
1709 limited: true,
1710 prev_batch: Some("fondue".to_owned()),
1711 events: vec![ev3, f.text_msg("sup").event_id(evid4).into_event()],
1712 },
1713 ..Default::default()
1714 })
1715 .await
1716 .unwrap();
1717
1718 {
1719 let state = room_event_cache.inner.state.read().await.unwrap();
1720 let room_linked_chunk = state.room_linked_chunk();
1721
1722 let mut events = room_linked_chunk.events();
1724
1725 let (pos, ev) = events.next().unwrap();
1726 assert_eq!(ev.event_id(), Some(evid3));
1727 assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1728
1729 let (pos, ev) = events.next().unwrap();
1730 assert_eq!(ev.event_id(), Some(evid4));
1731 assert_eq!(room_linked_chunk.event_order(pos), Some(3));
1732
1733 assert!(events.next().is_none());
1735
1736 assert_eq!(
1738 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1739 Some(0)
1740 );
1741 assert_eq!(
1742 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1743 Some(1)
1744 );
1745
1746 assert_eq!(
1749 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(1), 0)),
1750 None
1751 );
1752 }
1753 }
1754
1755 #[async_test]
1756 async fn test_auto_shrink_after_all_subscribers_are_gone() {
1757 let room_id = room_id!("!galette:saucisse.bzh");
1758
1759 let client = MockClientBuilder::new(None).build().await;
1760
1761 let f = EventFactory::new().room(room_id);
1762
1763 let evid1 = event_id!("$1");
1764 let evid2 = event_id!("$2");
1765
1766 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(evid1).into_event();
1767 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1768
1769 {
1771 client
1772 .event_cache_store()
1773 .lock()
1774 .await
1775 .expect("Could not acquire the event cache lock")
1776 .as_clean()
1777 .expect("Could not acquire a clean event cache lock")
1778 .handle_linked_chunk_updates(
1779 LinkedChunkId::Room(room_id),
1780 vec![
1781 Update::NewItemsChunk {
1782 previous: None,
1783 new: ChunkIdentifier::new(0),
1784 next: None,
1785 },
1786 Update::PushItems {
1787 at: Position::new(ChunkIdentifier::new(0), 0),
1788 items: vec![ev1],
1789 },
1790 Update::NewItemsChunk {
1791 previous: Some(ChunkIdentifier::new(0)),
1792 new: ChunkIdentifier::new(1),
1793 next: None,
1794 },
1795 Update::PushItems {
1796 at: Position::new(ChunkIdentifier::new(1), 0),
1797 items: vec![ev2],
1798 },
1799 ],
1800 )
1801 .await
1802 .unwrap();
1803 }
1804
1805 let event_cache = client.event_cache();
1806 event_cache.subscribe().unwrap();
1807
1808 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1809 let room = client.get_room(room_id).unwrap();
1810 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1811
1812 let (events1, mut stream1) = room_event_cache.subscribe().await.unwrap();
1814 assert_eq!(events1.len(), 1);
1815 assert_eq!(events1[0].event_id(), Some(evid2));
1816 assert!(stream1.is_empty());
1817
1818 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1819
1820 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1822 assert_eq!(outcome.events.len(), 1);
1823 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1824 assert!(outcome.reached_start);
1825
1826 assert_let_timeout!(
1829 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1830 stream1.recv()
1831 );
1832 assert_eq!(diffs.len(), 1);
1833 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1834 assert_eq!(value.event_id(), Some(evid1));
1835 });
1836
1837 assert!(stream1.is_empty());
1838
1839 assert_let_timeout!(
1840 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1841 );
1842 assert_eq!(expected_room_id, room_id);
1843 assert!(generic_stream.is_empty());
1844
1845 let (events2, stream2) = room_event_cache.subscribe().await.unwrap();
1849 assert_eq!(events2.len(), 2);
1850 assert_eq!(events2[0].event_id(), Some(evid1));
1851 assert_eq!(events2[1].event_id(), Some(evid2));
1852 assert!(stream2.is_empty());
1853
1854 let subscriber = {
1856 let state = room_event_cache.inner.state.read().await.unwrap();
1857 state.update_sender.new_room_receiver()
1858 };
1859
1860 drop(stream1);
1862 yield_now().await;
1863
1864 assert!(stream2.is_empty());
1866
1867 drop(stream2);
1869 yield_now().await;
1870
1871 {
1874 let state = room_event_cache.inner.state.read().await.unwrap();
1876 assert_eq!(state.subscribers_handle().count(), 0);
1877
1878 assert!(subscriber.is_empty());
1880 assert!(generic_stream.is_empty());
1881 }
1882
1883 let events3 = room_event_cache.events().await.unwrap();
1885 assert_eq!(events3.len(), 1);
1886 assert_eq!(events3[0].event_id(), Some(evid2));
1887 }
1888
1889 #[async_test]
1890 async fn test_rfind_map_event_in_memory_by() {
1891 let user_id = user_id!("@mnt_io:matrix.org");
1892 let room_id = room_id!("!raclette:patate.ch");
1893 let client = MockClientBuilder::new(None).build().await;
1894
1895 let event_factory = EventFactory::new().room(room_id);
1896
1897 let event_id_0 = event_id!("$ev0");
1898 let event_id_1 = event_id!("$ev1");
1899 let event_id_2 = event_id!("$ev2");
1900 let event_id_3 = event_id!("$ev3");
1901
1902 let event_0 =
1903 event_factory.text_msg("hello").sender(*BOB).event_id(event_id_0).into_event();
1904 let event_1 =
1905 event_factory.text_msg("world").sender(*ALICE).event_id(event_id_1).into_event();
1906 let event_2 = event_factory.text_msg("!").sender(*ALICE).event_id(event_id_2).into_event();
1907 let event_3 =
1908 event_factory.text_msg("eh!").sender(user_id).event_id(event_id_3).into_event();
1909
1910 {
1913 client
1914 .event_cache_store()
1915 .lock()
1916 .await
1917 .expect("Could not acquire the event cache lock")
1918 .as_clean()
1919 .expect("Could not acquire a clean event cache lock")
1920 .handle_linked_chunk_updates(
1921 LinkedChunkId::Room(room_id),
1922 vec![
1923 Update::NewItemsChunk {
1924 previous: None,
1925 new: ChunkIdentifier::new(0),
1926 next: None,
1927 },
1928 Update::PushItems {
1929 at: Position::new(ChunkIdentifier::new(0), 0),
1930 items: vec![event_3],
1931 },
1932 Update::NewItemsChunk {
1933 previous: Some(ChunkIdentifier::new(0)),
1934 new: ChunkIdentifier::new(1),
1935 next: None,
1936 },
1937 Update::PushItems {
1938 at: Position::new(ChunkIdentifier::new(1), 0),
1939 items: vec![event_0, event_1, event_2],
1940 },
1941 ],
1942 )
1943 .await
1944 .unwrap();
1945 }
1946
1947 let event_cache = client.event_cache();
1948 event_cache.subscribe().unwrap();
1949
1950 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1951 let room = client.get_room(room_id).unwrap();
1952 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1953
1954 assert_matches!(
1956 room_event_cache
1957 .rfind_map_event_in_memory_by(|event| {
1958 (event.sender().as_deref() == Some(*BOB)).then(|| event.event_id().map(ToOwned::to_owned))
1959 })
1960 .await,
1961 Ok(Some(event_id)) => {
1962 assert_eq!(event_id.as_deref(), Some(event_id_0));
1963 }
1964 );
1965
1966 assert_matches!(
1969 room_event_cache
1970 .rfind_map_event_in_memory_by(|event| {
1971 (event.sender().as_deref() == Some(*ALICE)).then(|| event.event_id().map(ToOwned::to_owned))
1972 })
1973 .await,
1974 Ok(Some(event_id)) => {
1975 assert_eq!(event_id.as_deref(), Some(event_id_2));
1976 }
1977 );
1978
1979 assert!(
1981 room_event_cache
1982 .rfind_map_event_in_memory_by(|event| {
1983 (event.sender().as_deref() == Some(user_id))
1984 .then(|| event.event_id().map(ToOwned::to_owned))
1985 })
1986 .await
1987 .unwrap()
1988 .is_none()
1989 );
1990
1991 assert!(
1993 room_event_cache.rfind_map_event_in_memory_by(|_| None::<()>).await.unwrap().is_none()
1994 );
1995 }
1996
1997 #[async_test]
1998 async fn test_reload_when_dirty() {
1999 let user_id = user_id!("@mnt_io:matrix.org");
2000 let room_id = room_id!("!raclette:patate.ch");
2001
2002 let event_cache_store = MemoryStore::new();
2004
2005 let client_p0 = MockClientBuilder::new(None)
2007 .on_builder(|builder| {
2008 builder.store_config(
2009 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2010 .event_cache_store(event_cache_store.clone()),
2011 )
2012 })
2013 .build()
2014 .await;
2015
2016 let client_p1 = MockClientBuilder::new(None)
2018 .on_builder(|builder| {
2019 builder.store_config(
2020 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2021 .event_cache_store(event_cache_store),
2022 )
2023 })
2024 .build()
2025 .await;
2026
2027 let event_factory = EventFactory::new().room(room_id).sender(user_id);
2028
2029 let ev_id_0 = event_id!("$ev_0");
2030 let ev_id_1 = event_id!("$ev_1");
2031
2032 let ev_0 = event_factory.text_msg("comté").event_id(ev_id_0).into_event();
2033 let ev_1 = event_factory.text_msg("morbier").event_id(ev_id_1).into_event();
2034
2035 client_p0
2037 .event_cache_store()
2038 .lock()
2039 .await
2040 .expect("[p0] Could not acquire the event cache lock")
2041 .as_clean()
2042 .expect("[p0] Could not acquire a clean event cache lock")
2043 .handle_linked_chunk_updates(
2044 LinkedChunkId::Room(room_id),
2045 vec![
2046 Update::NewItemsChunk {
2047 previous: None,
2048 new: ChunkIdentifier::new(0),
2049 next: None,
2050 },
2051 Update::PushItems {
2052 at: Position::new(ChunkIdentifier::new(0), 0),
2053 items: vec![ev_0],
2054 },
2055 Update::NewItemsChunk {
2056 previous: Some(ChunkIdentifier::new(0)),
2057 new: ChunkIdentifier::new(1),
2058 next: None,
2059 },
2060 Update::PushItems {
2061 at: Position::new(ChunkIdentifier::new(1), 0),
2062 items: vec![ev_1],
2063 },
2064 ],
2065 )
2066 .await
2067 .unwrap();
2068
2069 let (room_event_cache_p0, room_event_cache_p1) = {
2071 let event_cache_p0 = client_p0.event_cache();
2072 event_cache_p0.subscribe().unwrap();
2073
2074 let event_cache_p1 = client_p1.event_cache();
2075 event_cache_p1.subscribe().unwrap();
2076
2077 client_p0.base_client().get_or_create_room(room_id, RoomState::Joined);
2078 client_p1.base_client().get_or_create_room(room_id, RoomState::Joined);
2079
2080 let (room_event_cache_p0, _drop_handles) =
2081 client_p0.get_room(room_id).unwrap().event_cache().await.unwrap();
2082 let (room_event_cache_p1, _drop_handles) =
2083 client_p1.get_room(room_id).unwrap().event_cache().await.unwrap();
2084
2085 (room_event_cache_p0, room_event_cache_p1)
2086 };
2087
2088 let mut updates_stream_p0 = {
2093 let room_event_cache = &room_event_cache_p0;
2094
2095 let (initial_updates, mut updates_stream) =
2096 room_event_cache_p0.subscribe().await.unwrap();
2097
2098 assert_eq!(initial_updates.len(), 1);
2100 assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2101 assert!(updates_stream.is_empty());
2102
2103 assert!(event_loaded(room_event_cache, ev_id_1).await);
2105
2106 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2108
2109 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2111
2112 assert_matches!(
2114 updates_stream.recv().await.unwrap(),
2115 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2116 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2117 assert_matches!(
2118 &diffs[0],
2119 VectorDiff::Insert { index: 0, value: event } => {
2120 assert_eq!(event.event_id(), Some(ev_id_0));
2121 }
2122 );
2123 }
2124 );
2125
2126 assert!(event_loaded(room_event_cache, ev_id_0).await);
2128
2129 updates_stream
2130 };
2131
2132 let mut updates_stream_p1 = {
2134 let room_event_cache = &room_event_cache_p1;
2135 let (initial_updates, mut updates_stream) =
2136 room_event_cache_p1.subscribe().await.unwrap();
2137
2138 assert_eq!(initial_updates.len(), 1);
2140 assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2141 assert!(updates_stream.is_empty());
2142
2143 assert!(event_loaded(room_event_cache, ev_id_1).await);
2145
2146 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2148
2149 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2151
2152 assert_matches!(
2154 updates_stream.recv().await.unwrap(),
2155 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2156 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2157 assert_matches!(
2158 &diffs[0],
2159 VectorDiff::Insert { index: 0, value: event } => {
2160 assert_eq!(event.event_id(), Some(ev_id_0));
2161 }
2162 );
2163 }
2164 );
2165
2166 assert!(event_loaded(room_event_cache, ev_id_0).await);
2168
2169 updates_stream
2170 };
2171
2172 for _ in 0..3 {
2174 {
2178 let room_event_cache = &room_event_cache_p0;
2179 let updates_stream = &mut updates_stream_p0;
2180
2181 assert!(event_loaded(room_event_cache, ev_id_1).await);
2183
2184 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2187
2188 assert_matches!(
2190 updates_stream.recv().await.unwrap(),
2191 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2192 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2193 assert_matches!(&diffs[0], VectorDiff::Clear);
2194 assert_matches!(
2195 &diffs[1],
2196 VectorDiff::Append { values: events } => {
2197 assert_eq!(events.len(), 1);
2198 assert_eq!(events[0].event_id(), Some(ev_id_1));
2199 }
2200 );
2201 }
2202 );
2203
2204 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2206
2207 assert!(event_loaded(room_event_cache, ev_id_0).await);
2209
2210 assert_matches!(
2212 updates_stream.recv().await.unwrap(),
2213 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2214 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2215 assert_matches!(
2216 &diffs[0],
2217 VectorDiff::Insert { index: 0, value: event } => {
2218 assert_eq!(event.event_id(), Some(ev_id_0));
2219 }
2220 );
2221 }
2222 );
2223 }
2224
2225 {
2229 let room_event_cache = &room_event_cache_p1;
2230 let updates_stream = &mut updates_stream_p1;
2231
2232 assert!(event_loaded(room_event_cache, ev_id_1).await);
2234
2235 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2238
2239 assert_matches!(
2241 updates_stream.recv().await.unwrap(),
2242 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2243 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2244 assert_matches!(&diffs[0], VectorDiff::Clear);
2245 assert_matches!(
2246 &diffs[1],
2247 VectorDiff::Append { values: events } => {
2248 assert_eq!(events.len(), 1);
2249 assert_eq!(events[0].event_id(), Some(ev_id_1));
2250 }
2251 );
2252 }
2253 );
2254
2255 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2257
2258 assert!(event_loaded(room_event_cache, ev_id_0).await);
2260
2261 assert_matches!(
2263 updates_stream.recv().await.unwrap(),
2264 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2265 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2266 assert_matches!(
2267 &diffs[0],
2268 VectorDiff::Insert { index: 0, value: event } => {
2269 assert_eq!(event.event_id(), Some(ev_id_0));
2270 }
2271 );
2272 }
2273 );
2274 }
2275 }
2276
2277 for _ in 0..3 {
2280 {
2281 let room_event_cache = &room_event_cache_p0;
2282 let updates_stream = &mut updates_stream_p0;
2283
2284 let guard = room_event_cache.inner.state.read().await.unwrap();
2285
2286 assert!(guard.is_dirty().not());
2292
2293 assert_matches!(
2295 updates_stream.recv().await.unwrap(),
2296 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2297 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2298 assert_matches!(&diffs[0], VectorDiff::Clear);
2299 assert_matches!(
2300 &diffs[1],
2301 VectorDiff::Append { values: events } => {
2302 assert_eq!(events.len(), 1);
2303 assert_eq!(events[0].event_id(), Some(ev_id_1));
2304 }
2305 );
2306 }
2307 );
2308
2309 assert!(event_loaded(room_event_cache, ev_id_1).await);
2310 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2311
2312 drop(guard);
2318
2319 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2320 assert!(event_loaded(room_event_cache, ev_id_0).await);
2321
2322 assert_matches!(
2324 updates_stream.recv().await.unwrap(),
2325 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2326 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2327 assert_matches!(
2328 &diffs[0],
2329 VectorDiff::Insert { index: 0, value: event } => {
2330 assert_eq!(event.event_id(), Some(ev_id_0));
2331 }
2332 );
2333 }
2334 );
2335 }
2336
2337 {
2338 let room_event_cache = &room_event_cache_p1;
2339 let updates_stream = &mut updates_stream_p1;
2340
2341 let guard = room_event_cache.inner.state.read().await.unwrap();
2342
2343 assert!(guard.is_dirty().not());
2348
2349 assert_matches!(
2351 updates_stream.recv().await.unwrap(),
2352 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2353 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2354 assert_matches!(&diffs[0], VectorDiff::Clear);
2355 assert_matches!(
2356 &diffs[1],
2357 VectorDiff::Append { values: events } => {
2358 assert_eq!(events.len(), 1);
2359 assert_eq!(events[0].event_id(), Some(ev_id_1));
2360 }
2361 );
2362 }
2363 );
2364
2365 assert!(event_loaded(room_event_cache, ev_id_1).await);
2366 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2367
2368 drop(guard);
2374
2375 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2376 assert!(event_loaded(room_event_cache, ev_id_0).await);
2377
2378 assert_matches!(
2380 updates_stream.recv().await.unwrap(),
2381 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2382 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2383 assert_matches!(
2384 &diffs[0],
2385 VectorDiff::Insert { index: 0, value: event } => {
2386 assert_eq!(event.event_id(), Some(ev_id_0));
2387 }
2388 );
2389 }
2390 );
2391 }
2392 }
2393
2394 for _ in 0..3 {
2396 {
2397 let room_event_cache = &room_event_cache_p0;
2398 let updates_stream = &mut updates_stream_p0;
2399
2400 let guard = room_event_cache.inner.state.write().await.unwrap();
2401
2402 assert!(guard.is_dirty().not());
2404
2405 assert_matches!(
2407 updates_stream.recv().await.unwrap(),
2408 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2409 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2410 assert_matches!(&diffs[0], VectorDiff::Clear);
2411 assert_matches!(
2412 &diffs[1],
2413 VectorDiff::Append { values: events } => {
2414 assert_eq!(events.len(), 1);
2415 assert_eq!(events[0].event_id(), Some(ev_id_1));
2416 }
2417 );
2418 }
2419 );
2420
2421 drop(guard);
2424
2425 assert!(event_loaded(room_event_cache, ev_id_1).await);
2426 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2427
2428 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2429 assert!(event_loaded(room_event_cache, ev_id_0).await);
2430
2431 assert_matches!(
2433 updates_stream.recv().await.unwrap(),
2434 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2435 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2436 assert_matches!(
2437 &diffs[0],
2438 VectorDiff::Insert { index: 0, value: event } => {
2439 assert_eq!(event.event_id(), Some(ev_id_0));
2440 }
2441 );
2442 }
2443 );
2444 }
2445
2446 {
2447 let room_event_cache = &room_event_cache_p1;
2448 let updates_stream = &mut updates_stream_p1;
2449
2450 let guard = room_event_cache.inner.state.write().await.unwrap();
2451
2452 assert!(guard.is_dirty().not());
2454
2455 assert_matches!(
2457 updates_stream.recv().await.unwrap(),
2458 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2459 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2460 assert_matches!(&diffs[0], VectorDiff::Clear);
2461 assert_matches!(
2462 &diffs[1],
2463 VectorDiff::Append { values: events } => {
2464 assert_eq!(events.len(), 1);
2465 assert_eq!(events[0].event_id(), Some(ev_id_1));
2466 }
2467 );
2468 }
2469 );
2470
2471 drop(guard);
2474
2475 assert!(event_loaded(room_event_cache, ev_id_1).await);
2476 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2477
2478 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2479 assert!(event_loaded(room_event_cache, ev_id_0).await);
2480
2481 assert_matches!(
2483 updates_stream.recv().await.unwrap(),
2484 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2485 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2486 assert_matches!(
2487 &diffs[0],
2488 VectorDiff::Insert { index: 0, value: event } => {
2489 assert_eq!(event.event_id(), Some(ev_id_0));
2490 }
2491 );
2492 }
2493 );
2494 }
2495 }
2496 }
2497
2498 #[async_test]
2499 async fn test_load_when_dirty() {
2500 let room_id_0 = room_id!("!raclette:patate.ch");
2501 let room_id_1 = room_id!("!morbiflette:patate.ch");
2502
2503 let event_cache_store = MemoryStore::new();
2505
2506 let client_p0 = MockClientBuilder::new(None)
2508 .on_builder(|builder| {
2509 builder.store_config(
2510 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2511 .event_cache_store(event_cache_store.clone()),
2512 )
2513 })
2514 .build()
2515 .await;
2516
2517 let client_p1 = MockClientBuilder::new(None)
2519 .on_builder(|builder| {
2520 builder.store_config(
2521 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2522 .event_cache_store(event_cache_store),
2523 )
2524 })
2525 .build()
2526 .await;
2527
2528 let (room_event_cache_0_p0, room_event_cache_0_p1) = {
2530 let event_cache_p0 = client_p0.event_cache();
2531 event_cache_p0.subscribe().unwrap();
2532
2533 let event_cache_p1 = client_p1.event_cache();
2534 event_cache_p1.subscribe().unwrap();
2535
2536 client_p0.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2537 client_p0.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2538
2539 client_p1.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2540 client_p1.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2541
2542 let (room_event_cache_0_p0, _drop_handles) =
2543 client_p0.get_room(room_id_0).unwrap().event_cache().await.unwrap();
2544 let (room_event_cache_0_p1, _drop_handles) =
2545 client_p1.get_room(room_id_0).unwrap().event_cache().await.unwrap();
2546
2547 (room_event_cache_0_p0, room_event_cache_0_p1)
2548 };
2549
2550 {
2552 drop(room_event_cache_0_p0.inner.state.read().await.unwrap());
2553 drop(room_event_cache_0_p1.inner.state.read().await.unwrap());
2554 }
2555
2556 let (room_event_cache_1_p0, _) =
2560 client_p0.get_room(room_id_1).unwrap().event_cache().await.unwrap();
2561
2562 {
2564 let guard = room_event_cache_1_p0.inner.state.read().await.unwrap();
2565 assert!(guard.is_dirty().not());
2566 }
2567
2568 }
2571
2572 #[async_test]
2573 async fn test_uniq_read_marker() {
2574 let client = MockClientBuilder::new(None).build().await;
2575 let room_id = room_id!("!galette:saucisse.bzh");
2576 client.base_client().get_or_create_room(room_id, RoomState::Joined);
2577
2578 let event_cache = client.event_cache();
2579
2580 event_cache.subscribe().unwrap();
2581
2582 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2583 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
2584 let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
2585
2586 assert!(events.is_empty());
2587
2588 let read_marker_event = Raw::from_json_string(
2590 json!({
2591 "content": {
2592 "event_id": "$crepe:saucisse.bzh"
2593 },
2594 "room_id": "!galette:saucisse.bzh",
2595 "type": "m.fully_read"
2596 })
2597 .to_string(),
2598 )
2599 .unwrap();
2600 let account_data = vec![read_marker_event; 100];
2601
2602 room_event_cache
2603 .handle_joined_room_update(JoinedRoomUpdate { account_data, ..Default::default() })
2604 .await
2605 .unwrap();
2606
2607 assert_matches!(
2609 stream.recv().await.unwrap(),
2610 RoomEventCacheUpdate::MoveReadMarkerTo { .. }
2611 );
2612
2613 assert!(stream.recv().now_or_never().is_none());
2614
2615 assert!(generic_stream.recv().now_or_never().is_none());
2617 }
2618
2619 async fn event_loaded(room_event_cache: &RoomEventCache, event_id: &EventId) -> bool {
2620 room_event_cache
2621 .rfind_map_event_in_memory_by(|event| {
2622 (event.event_id() == Some(event_id)).then_some(())
2623 })
2624 .await
2625 .unwrap()
2626 .is_some()
2627 }
2628}