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(), 2);
1100 assert_let!(VectorDiff::Clear = &diffs[0]);
1101 assert_let!(VectorDiff::Append { values } = &diffs[1]);
1102 assert!(values.is_empty());
1103
1104 assert_let_timeout!(
1106 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) = generic_stream.recv()
1107 );
1108 assert_eq!(received_room_id, room_id);
1109 assert!(generic_stream.is_empty());
1110
1111 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_none());
1113
1114 let items = room_event_cache.events().await.unwrap();
1116 assert!(items.is_empty());
1117
1118 let linked_chunk = from_all_chunks::<3, _, _>(
1120 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
1121 )
1122 .unwrap()
1123 .unwrap();
1124
1125 assert_eq!(linked_chunk.num_items(), 0);
1129 }
1130
1131 #[async_test]
1132 async fn test_load_from_storage() {
1133 let room_id = room_id!("!galette:saucisse.bzh");
1134 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1135
1136 let event_cache_store = Arc::new(MemoryStore::new());
1137
1138 let event_id1 = event_id!("$1");
1139 let event_id2 = event_id!("$2");
1140
1141 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(event_id1).into_event();
1142 let ev2 = f.text_msg("how's it going").sender(*BOB).event_id(event_id2).into_event();
1143
1144 event_cache_store
1146 .handle_linked_chunk_updates(
1147 LinkedChunkId::Room(room_id),
1148 vec![
1149 Update::NewItemsChunk {
1151 previous: None,
1152 new: ChunkIdentifier::new(0),
1153 next: None,
1154 },
1155 Update::NewGapChunk {
1157 previous: Some(ChunkIdentifier::new(0)),
1158 new: ChunkIdentifier::new(42),
1160 next: None,
1161 gap: Gap { token: "cheddar".to_owned() },
1162 },
1163 Update::NewItemsChunk {
1165 previous: Some(ChunkIdentifier::new(42)),
1166 new: ChunkIdentifier::new(1),
1167 next: None,
1168 },
1169 Update::PushItems {
1170 at: Position::new(ChunkIdentifier::new(1), 0),
1171 items: vec![ev1.clone()],
1172 },
1173 Update::NewItemsChunk {
1175 previous: Some(ChunkIdentifier::new(1)),
1176 new: ChunkIdentifier::new(2),
1177 next: None,
1178 },
1179 Update::PushItems {
1180 at: Position::new(ChunkIdentifier::new(2), 0),
1181 items: vec![ev2.clone()],
1182 },
1183 ],
1184 )
1185 .await
1186 .unwrap();
1187
1188 let client = MockClientBuilder::new(None)
1189 .on_builder(|builder| {
1190 builder.store_config(
1191 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1192 .event_cache_store(event_cache_store.clone()),
1193 )
1194 })
1195 .build()
1196 .await;
1197
1198 let event_cache = client.event_cache();
1199
1200 event_cache.subscribe().unwrap();
1202
1203 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1205
1206 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1207 let room = client.get_room(room_id).unwrap();
1208
1209 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1210
1211 assert_matches!(
1214 generic_stream.recv().await,
1215 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1216 assert_eq!(room_id, expected_room_id);
1217 }
1218 );
1219 assert!(generic_stream.is_empty());
1220
1221 let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1222
1223 assert_eq!(items.len(), 1);
1226 assert_eq!(items[0].event_id().unwrap(), event_id2);
1227 assert!(stream.is_empty());
1228
1229 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1231 assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1232
1233 room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1235
1236 assert_let_timeout!(
1237 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1238 stream.recv()
1239 );
1240 assert_eq!(diffs.len(), 1);
1241 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1242 assert_eq!(event.event_id().unwrap(), event_id1);
1243 });
1244
1245 assert!(stream.is_empty());
1246
1247 assert_matches!(
1249 generic_stream.recv().await,
1250 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1251 assert_eq!(expected_room_id, room_id);
1252 }
1253 );
1254 assert!(generic_stream.is_empty());
1255
1256 let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev2] };
1258
1259 room_event_cache
1260 .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
1261 .await
1262 .unwrap();
1263
1264 assert!(generic_stream.recv().now_or_never().is_none());
1267
1268 let items = room_event_cache.events().await.unwrap();
1273 assert_eq!(items.len(), 2);
1274 assert_eq!(items[0].event_id().unwrap(), event_id1);
1275 assert_eq!(items[1].event_id().unwrap(), event_id2);
1276 }
1277
1278 #[async_test]
1279 async fn test_load_from_storage_resilient_to_failure() {
1280 let room_id = room_id!("!fondue:patate.ch");
1281 let event_cache_store = Arc::new(MemoryStore::new());
1282
1283 let event = EventFactory::new()
1284 .room(room_id)
1285 .sender(user_id!("@ben:saucisse.bzh"))
1286 .text_msg("foo")
1287 .event_id(event_id!("$42"))
1288 .into_event();
1289
1290 event_cache_store
1292 .handle_linked_chunk_updates(
1293 LinkedChunkId::Room(room_id),
1294 vec![
1295 Update::NewItemsChunk {
1296 previous: None,
1297 new: ChunkIdentifier::new(0),
1298 next: None,
1299 },
1300 Update::PushItems {
1301 at: Position::new(ChunkIdentifier::new(0), 0),
1302 items: vec![event],
1303 },
1304 Update::NewItemsChunk {
1305 previous: Some(ChunkIdentifier::new(0)),
1306 new: ChunkIdentifier::new(1),
1307 next: Some(ChunkIdentifier::new(0)),
1308 },
1309 ],
1310 )
1311 .await
1312 .unwrap();
1313
1314 let client = MockClientBuilder::new(None)
1315 .on_builder(|builder| {
1316 builder.store_config(
1317 StoreConfig::new(CrossProcessLockConfig::multi_process("holder"))
1318 .event_cache_store(event_cache_store.clone()),
1319 )
1320 })
1321 .build()
1322 .await;
1323
1324 let event_cache = client.event_cache();
1325
1326 event_cache.subscribe().unwrap();
1328
1329 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1330 let room = client.get_room(room_id).unwrap();
1331
1332 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1333
1334 let items = room_event_cache.events().await.unwrap();
1335
1336 assert!(items.is_empty());
1339
1340 let raw_chunks =
1343 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap();
1344 assert!(raw_chunks.is_empty());
1345 }
1346
1347 #[async_test]
1348 async fn test_no_useless_gaps() {
1349 let room_id = room_id!("!galette:saucisse.bzh");
1350
1351 let client = MockClientBuilder::new(None).build().await;
1352
1353 let event_cache = client.event_cache();
1354 event_cache.subscribe().unwrap();
1355
1356 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1357 let room = client.get_room(room_id).unwrap();
1358 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1359 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1360
1361 let f = EventFactory::new().room(room_id).sender(*ALICE);
1362
1363 room_event_cache
1366 .handle_joined_room_update(JoinedRoomUpdate {
1367 timeline: Timeline {
1368 limited: true,
1369 prev_batch: Some("raclette".to_owned()),
1370 events: vec![f.text_msg("hey yo").into_event()],
1371 },
1372 ..Default::default()
1373 })
1374 .await
1375 .unwrap();
1376
1377 assert_matches!(
1379 generic_stream.recv().await,
1380 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1381 assert_eq!(expected_room_id, room_id);
1382 }
1383 );
1384 assert!(generic_stream.is_empty());
1385
1386 {
1387 let state = room_event_cache.inner.state.read().await.unwrap();
1388
1389 let mut num_gaps = 0;
1390 let mut num_events = 0;
1391
1392 for c in state.room_linked_chunk().chunks() {
1393 match c.content() {
1394 ChunkContent::Items(items) => num_events += items.len(),
1395 ChunkContent::Gap(_) => num_gaps += 1,
1396 }
1397 }
1398
1399 assert_eq!(num_gaps, 0);
1402 assert_eq!(num_events, 1);
1403 }
1404
1405 assert_matches!(
1407 room_event_cache.pagination().load_more_events_backwards().await.unwrap(),
1408 LoadMoreEventsBackwardsOutcome::Gap { .. }
1409 );
1410
1411 {
1412 let state = room_event_cache.inner.state.read().await.unwrap();
1413
1414 let mut num_gaps = 0;
1415 let mut num_events = 0;
1416
1417 for c in state.room_linked_chunk().chunks() {
1418 match c.content() {
1419 ChunkContent::Items(items) => num_events += items.len(),
1420 ChunkContent::Gap(_) => num_gaps += 1,
1421 }
1422 }
1423
1424 assert_eq!(num_gaps, 1);
1426 assert_eq!(num_events, 1);
1427 }
1428
1429 room_event_cache
1432 .handle_joined_room_update(JoinedRoomUpdate {
1433 timeline: Timeline {
1434 limited: false,
1435 prev_batch: Some("fondue".to_owned()),
1436 events: vec![f.text_msg("sup").into_event()],
1437 },
1438 ..Default::default()
1439 })
1440 .await
1441 .unwrap();
1442
1443 assert_matches!(
1445 generic_stream.recv().await,
1446 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1447 assert_eq!(expected_room_id, room_id);
1448 }
1449 );
1450 assert!(generic_stream.is_empty());
1451
1452 {
1453 let state = room_event_cache.inner.state.read().await.unwrap();
1454
1455 let mut num_gaps = 0;
1456 let mut num_events = 0;
1457
1458 for c in state.room_linked_chunk().chunks() {
1459 match c.content() {
1460 ChunkContent::Items(items) => num_events += items.len(),
1461 ChunkContent::Gap(gap) => {
1462 assert_eq!(gap.token, "raclette");
1463 num_gaps += 1;
1464 }
1465 }
1466 }
1467
1468 assert_eq!(num_gaps, 1);
1470 assert_eq!(num_events, 2);
1471 }
1472 }
1473
1474 #[async_test]
1475 async fn test_shrink_to_last_chunk() {
1476 let room_id = room_id!("!galette:saucisse.bzh");
1477
1478 let client = MockClientBuilder::new(None).build().await;
1479
1480 let f = EventFactory::new().room(room_id);
1481
1482 let evid1 = event_id!("$1");
1483 let evid2 = event_id!("$2");
1484
1485 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(evid1).into_event();
1486 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1487
1488 {
1490 client
1491 .event_cache_store()
1492 .lock()
1493 .await
1494 .expect("Could not acquire the event cache lock")
1495 .as_clean()
1496 .expect("Could not acquire a clean event cache lock")
1497 .handle_linked_chunk_updates(
1498 LinkedChunkId::Room(room_id),
1499 vec![
1500 Update::NewItemsChunk {
1501 previous: None,
1502 new: ChunkIdentifier::new(0),
1503 next: None,
1504 },
1505 Update::PushItems {
1506 at: Position::new(ChunkIdentifier::new(0), 0),
1507 items: vec![ev1],
1508 },
1509 Update::NewItemsChunk {
1510 previous: Some(ChunkIdentifier::new(0)),
1511 new: ChunkIdentifier::new(1),
1512 next: None,
1513 },
1514 Update::PushItems {
1515 at: Position::new(ChunkIdentifier::new(1), 0),
1516 items: vec![ev2],
1517 },
1518 ],
1519 )
1520 .await
1521 .unwrap();
1522 }
1523
1524 let event_cache = client.event_cache();
1525 event_cache.subscribe().unwrap();
1526
1527 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1528 let room = client.get_room(room_id).unwrap();
1529 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1530
1531 let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
1533 assert_eq!(events.len(), 1);
1534 assert_eq!(events[0].event_id(), Some(evid2));
1535 assert!(stream.is_empty());
1536
1537 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1538
1539 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1541 assert_eq!(outcome.events.len(), 1);
1542 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1543 assert!(outcome.reached_start);
1544
1545 assert_let_timeout!(
1547 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1548 stream.recv()
1549 );
1550 assert_eq!(diffs.len(), 1);
1551 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1552 assert_eq!(value.event_id(), Some(evid1));
1553 });
1554
1555 assert!(stream.is_empty());
1556
1557 assert_let_timeout!(
1559 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1560 );
1561 assert_eq!(expected_room_id, room_id);
1562 assert!(generic_stream.is_empty());
1563
1564 room_event_cache
1566 .inner
1567 .state
1568 .reload_no_preprocessing()
1569 .await
1570 .expect("shrinking should succeed");
1571
1572 assert_let_timeout!(
1574 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1575 stream.recv()
1576 );
1577 assert_eq!(diffs.len(), 2);
1578 assert_matches!(&diffs[0], VectorDiff::Clear);
1579 assert_matches!(&diffs[1], VectorDiff::Append { values} => {
1580 assert_eq!(values.len(), 1);
1581 assert_eq!(values[0].event_id(), Some(evid2));
1582 });
1583
1584 assert!(stream.is_empty());
1585
1586 assert_let_timeout!(Ok(RoomEventCacheGenericUpdate { .. }) = generic_stream.recv());
1588 assert!(generic_stream.is_empty());
1589
1590 let events = room_event_cache.events().await.unwrap();
1592 assert_eq!(events.len(), 1);
1593 assert_eq!(events[0].event_id(), Some(evid2));
1594
1595 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1598 assert_eq!(outcome.events.len(), 1);
1599 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1600 assert!(outcome.reached_start);
1601 }
1602
1603 #[async_test]
1604 async fn test_room_ordering() {
1605 let room_id = room_id!("!galette:saucisse.bzh");
1606
1607 let client = MockClientBuilder::new(None).build().await;
1608
1609 let f = EventFactory::new().room(room_id).sender(*ALICE);
1610
1611 let evid1 = event_id!("$1");
1612 let evid2 = event_id!("$2");
1613 let evid3 = event_id!("$3");
1614
1615 let ev1 = f.text_msg("hello world").event_id(evid1).into_event();
1616 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1617 let ev3 = f.text_msg("yo").event_id(evid3).into_event();
1618
1619 {
1621 client
1622 .event_cache_store()
1623 .lock()
1624 .await
1625 .expect("Could not acquire the event cache lock")
1626 .as_clean()
1627 .expect("Could not acquire a clean event cache lock")
1628 .handle_linked_chunk_updates(
1629 LinkedChunkId::Room(room_id),
1630 vec![
1631 Update::NewItemsChunk {
1632 previous: None,
1633 new: ChunkIdentifier::new(0),
1634 next: None,
1635 },
1636 Update::PushItems {
1637 at: Position::new(ChunkIdentifier::new(0), 0),
1638 items: vec![ev1, ev2],
1639 },
1640 Update::NewItemsChunk {
1641 previous: Some(ChunkIdentifier::new(0)),
1642 new: ChunkIdentifier::new(1),
1643 next: None,
1644 },
1645 Update::PushItems {
1646 at: Position::new(ChunkIdentifier::new(1), 0),
1647 items: vec![ev3.clone()],
1648 },
1649 ],
1650 )
1651 .await
1652 .unwrap();
1653 }
1654
1655 let event_cache = client.event_cache();
1656 event_cache.subscribe().unwrap();
1657
1658 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1659 let room = client.get_room(room_id).unwrap();
1660 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1661
1662 {
1665 let state = room_event_cache.inner.state.read().await.unwrap();
1666 let room_linked_chunk = state.room_linked_chunk();
1667
1668 assert_eq!(
1670 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1671 Some(0)
1672 );
1673
1674 assert_eq!(
1676 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1677 Some(1)
1678 );
1679
1680 let mut events = room_linked_chunk.events();
1682 let (pos, ev) = events.next().unwrap();
1683 assert_eq!(pos, Position::new(ChunkIdentifier::new(1), 0));
1684 assert_eq!(ev.event_id(), Some(evid3));
1685 assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1686
1687 assert!(events.next().is_none());
1689 }
1690
1691 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1693 assert!(outcome.reached_start);
1694
1695 {
1698 let state = room_event_cache.inner.state.read().await.unwrap();
1699 let room_linked_chunk = state.room_linked_chunk();
1700
1701 for (i, (pos, _)) in room_linked_chunk.events().enumerate() {
1702 assert_eq!(room_linked_chunk.event_order(pos), Some(i));
1703 }
1704 }
1705
1706 let evid4 = event_id!("$4");
1711 room_event_cache
1712 .handle_joined_room_update(JoinedRoomUpdate {
1713 timeline: Timeline {
1714 limited: true,
1715 prev_batch: Some("fondue".to_owned()),
1716 events: vec![ev3, f.text_msg("sup").event_id(evid4).into_event()],
1717 },
1718 ..Default::default()
1719 })
1720 .await
1721 .unwrap();
1722
1723 {
1724 let state = room_event_cache.inner.state.read().await.unwrap();
1725 let room_linked_chunk = state.room_linked_chunk();
1726
1727 let mut events = room_linked_chunk.events();
1729
1730 let (pos, ev) = events.next().unwrap();
1731 assert_eq!(ev.event_id(), Some(evid3));
1732 assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1733
1734 let (pos, ev) = events.next().unwrap();
1735 assert_eq!(ev.event_id(), Some(evid4));
1736 assert_eq!(room_linked_chunk.event_order(pos), Some(3));
1737
1738 assert!(events.next().is_none());
1740
1741 assert_eq!(
1743 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1744 Some(0)
1745 );
1746 assert_eq!(
1747 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1748 Some(1)
1749 );
1750
1751 assert_eq!(
1754 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(1), 0)),
1755 None
1756 );
1757 }
1758 }
1759
1760 #[async_test]
1761 async fn test_auto_shrink_after_all_subscribers_are_gone() {
1762 let room_id = room_id!("!galette:saucisse.bzh");
1763
1764 let client = MockClientBuilder::new(None).build().await;
1765
1766 let f = EventFactory::new().room(room_id);
1767
1768 let evid1 = event_id!("$1");
1769 let evid2 = event_id!("$2");
1770
1771 let ev1 = f.text_msg("hello world").sender(*ALICE).event_id(evid1).into_event();
1772 let ev2 = f.text_msg("howdy").sender(*BOB).event_id(evid2).into_event();
1773
1774 {
1776 client
1777 .event_cache_store()
1778 .lock()
1779 .await
1780 .expect("Could not acquire the event cache lock")
1781 .as_clean()
1782 .expect("Could not acquire a clean event cache lock")
1783 .handle_linked_chunk_updates(
1784 LinkedChunkId::Room(room_id),
1785 vec![
1786 Update::NewItemsChunk {
1787 previous: None,
1788 new: ChunkIdentifier::new(0),
1789 next: None,
1790 },
1791 Update::PushItems {
1792 at: Position::new(ChunkIdentifier::new(0), 0),
1793 items: vec![ev1],
1794 },
1795 Update::NewItemsChunk {
1796 previous: Some(ChunkIdentifier::new(0)),
1797 new: ChunkIdentifier::new(1),
1798 next: None,
1799 },
1800 Update::PushItems {
1801 at: Position::new(ChunkIdentifier::new(1), 0),
1802 items: vec![ev2],
1803 },
1804 ],
1805 )
1806 .await
1807 .unwrap();
1808 }
1809
1810 let event_cache = client.event_cache();
1811 event_cache.subscribe().unwrap();
1812
1813 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1814 let room = client.get_room(room_id).unwrap();
1815 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1816
1817 let (events1, mut stream1) = room_event_cache.subscribe().await.unwrap();
1819 assert_eq!(events1.len(), 1);
1820 assert_eq!(events1[0].event_id(), Some(evid2));
1821 assert!(stream1.is_empty());
1822
1823 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1824
1825 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1827 assert_eq!(outcome.events.len(), 1);
1828 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1829 assert!(outcome.reached_start);
1830
1831 assert_let_timeout!(
1834 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1835 stream1.recv()
1836 );
1837 assert_eq!(diffs.len(), 1);
1838 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1839 assert_eq!(value.event_id(), Some(evid1));
1840 });
1841
1842 assert!(stream1.is_empty());
1843
1844 assert_let_timeout!(
1845 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1846 );
1847 assert_eq!(expected_room_id, room_id);
1848 assert!(generic_stream.is_empty());
1849
1850 let (events2, stream2) = room_event_cache.subscribe().await.unwrap();
1854 assert_eq!(events2.len(), 2);
1855 assert_eq!(events2[0].event_id(), Some(evid1));
1856 assert_eq!(events2[1].event_id(), Some(evid2));
1857 assert!(stream2.is_empty());
1858
1859 let subscriber = {
1861 let state = room_event_cache.inner.state.read().await.unwrap();
1862 state.update_sender.new_room_receiver()
1863 };
1864
1865 drop(stream1);
1867 yield_now().await;
1868
1869 assert!(stream2.is_empty());
1871
1872 drop(stream2);
1874 yield_now().await;
1875
1876 {
1879 let state = room_event_cache.inner.state.read().await.unwrap();
1881 assert_eq!(state.subscribers_handle().count(), 0);
1882
1883 assert!(subscriber.is_empty());
1885 assert!(generic_stream.is_empty());
1886 }
1887
1888 let events3 = room_event_cache.events().await.unwrap();
1890 assert_eq!(events3.len(), 1);
1891 assert_eq!(events3[0].event_id(), Some(evid2));
1892 }
1893
1894 #[async_test]
1895 async fn test_rfind_map_event_in_memory_by() {
1896 let user_id = user_id!("@mnt_io:matrix.org");
1897 let room_id = room_id!("!raclette:patate.ch");
1898 let client = MockClientBuilder::new(None).build().await;
1899
1900 let event_factory = EventFactory::new().room(room_id);
1901
1902 let event_id_0 = event_id!("$ev0");
1903 let event_id_1 = event_id!("$ev1");
1904 let event_id_2 = event_id!("$ev2");
1905 let event_id_3 = event_id!("$ev3");
1906
1907 let event_0 =
1908 event_factory.text_msg("hello").sender(*BOB).event_id(event_id_0).into_event();
1909 let event_1 =
1910 event_factory.text_msg("world").sender(*ALICE).event_id(event_id_1).into_event();
1911 let event_2 = event_factory.text_msg("!").sender(*ALICE).event_id(event_id_2).into_event();
1912 let event_3 =
1913 event_factory.text_msg("eh!").sender(user_id).event_id(event_id_3).into_event();
1914
1915 {
1918 client
1919 .event_cache_store()
1920 .lock()
1921 .await
1922 .expect("Could not acquire the event cache lock")
1923 .as_clean()
1924 .expect("Could not acquire a clean event cache lock")
1925 .handle_linked_chunk_updates(
1926 LinkedChunkId::Room(room_id),
1927 vec![
1928 Update::NewItemsChunk {
1929 previous: None,
1930 new: ChunkIdentifier::new(0),
1931 next: None,
1932 },
1933 Update::PushItems {
1934 at: Position::new(ChunkIdentifier::new(0), 0),
1935 items: vec![event_3],
1936 },
1937 Update::NewItemsChunk {
1938 previous: Some(ChunkIdentifier::new(0)),
1939 new: ChunkIdentifier::new(1),
1940 next: None,
1941 },
1942 Update::PushItems {
1943 at: Position::new(ChunkIdentifier::new(1), 0),
1944 items: vec![event_0, event_1, event_2],
1945 },
1946 ],
1947 )
1948 .await
1949 .unwrap();
1950 }
1951
1952 let event_cache = client.event_cache();
1953 event_cache.subscribe().unwrap();
1954
1955 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1956 let room = client.get_room(room_id).unwrap();
1957 let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
1958
1959 assert_matches!(
1961 room_event_cache
1962 .rfind_map_event_in_memory_by(|event| {
1963 (event.sender().as_deref() == Some(*BOB)).then(|| event.event_id().map(ToOwned::to_owned))
1964 })
1965 .await,
1966 Ok(Some(event_id)) => {
1967 assert_eq!(event_id.as_deref(), Some(event_id_0));
1968 }
1969 );
1970
1971 assert_matches!(
1974 room_event_cache
1975 .rfind_map_event_in_memory_by(|event| {
1976 (event.sender().as_deref() == Some(*ALICE)).then(|| event.event_id().map(ToOwned::to_owned))
1977 })
1978 .await,
1979 Ok(Some(event_id)) => {
1980 assert_eq!(event_id.as_deref(), Some(event_id_2));
1981 }
1982 );
1983
1984 assert!(
1986 room_event_cache
1987 .rfind_map_event_in_memory_by(|event| {
1988 (event.sender().as_deref() == Some(user_id))
1989 .then(|| event.event_id().map(ToOwned::to_owned))
1990 })
1991 .await
1992 .unwrap()
1993 .is_none()
1994 );
1995
1996 assert!(
1998 room_event_cache.rfind_map_event_in_memory_by(|_| None::<()>).await.unwrap().is_none()
1999 );
2000 }
2001
2002 #[async_test]
2003 async fn test_reload_when_dirty() {
2004 let user_id = user_id!("@mnt_io:matrix.org");
2005 let room_id = room_id!("!raclette:patate.ch");
2006
2007 let event_cache_store = MemoryStore::new();
2009
2010 let client_p0 = MockClientBuilder::new(None)
2012 .on_builder(|builder| {
2013 builder.store_config(
2014 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2015 .event_cache_store(event_cache_store.clone()),
2016 )
2017 })
2018 .build()
2019 .await;
2020
2021 let client_p1 = MockClientBuilder::new(None)
2023 .on_builder(|builder| {
2024 builder.store_config(
2025 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2026 .event_cache_store(event_cache_store),
2027 )
2028 })
2029 .build()
2030 .await;
2031
2032 let event_factory = EventFactory::new().room(room_id).sender(user_id);
2033
2034 let ev_id_0 = event_id!("$ev_0");
2035 let ev_id_1 = event_id!("$ev_1");
2036
2037 let ev_0 = event_factory.text_msg("comté").event_id(ev_id_0).into_event();
2038 let ev_1 = event_factory.text_msg("morbier").event_id(ev_id_1).into_event();
2039
2040 client_p0
2042 .event_cache_store()
2043 .lock()
2044 .await
2045 .expect("[p0] Could not acquire the event cache lock")
2046 .as_clean()
2047 .expect("[p0] Could not acquire a clean event cache lock")
2048 .handle_linked_chunk_updates(
2049 LinkedChunkId::Room(room_id),
2050 vec![
2051 Update::NewItemsChunk {
2052 previous: None,
2053 new: ChunkIdentifier::new(0),
2054 next: None,
2055 },
2056 Update::PushItems {
2057 at: Position::new(ChunkIdentifier::new(0), 0),
2058 items: vec![ev_0],
2059 },
2060 Update::NewItemsChunk {
2061 previous: Some(ChunkIdentifier::new(0)),
2062 new: ChunkIdentifier::new(1),
2063 next: None,
2064 },
2065 Update::PushItems {
2066 at: Position::new(ChunkIdentifier::new(1), 0),
2067 items: vec![ev_1],
2068 },
2069 ],
2070 )
2071 .await
2072 .unwrap();
2073
2074 let (room_event_cache_p0, room_event_cache_p1) = {
2076 let event_cache_p0 = client_p0.event_cache();
2077 event_cache_p0.subscribe().unwrap();
2078
2079 let event_cache_p1 = client_p1.event_cache();
2080 event_cache_p1.subscribe().unwrap();
2081
2082 client_p0.base_client().get_or_create_room(room_id, RoomState::Joined);
2083 client_p1.base_client().get_or_create_room(room_id, RoomState::Joined);
2084
2085 let (room_event_cache_p0, _drop_handles) =
2086 client_p0.get_room(room_id).unwrap().event_cache().await.unwrap();
2087 let (room_event_cache_p1, _drop_handles) =
2088 client_p1.get_room(room_id).unwrap().event_cache().await.unwrap();
2089
2090 (room_event_cache_p0, room_event_cache_p1)
2091 };
2092
2093 let mut updates_stream_p0 = {
2098 let room_event_cache = &room_event_cache_p0;
2099
2100 let (initial_updates, mut updates_stream) =
2101 room_event_cache_p0.subscribe().await.unwrap();
2102
2103 assert_eq!(initial_updates.len(), 1);
2105 assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2106 assert!(updates_stream.is_empty());
2107
2108 assert!(event_loaded(room_event_cache, ev_id_1).await);
2110
2111 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2113
2114 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2116
2117 assert_matches!(
2119 updates_stream.recv().await.unwrap(),
2120 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2121 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2122 assert_matches!(
2123 &diffs[0],
2124 VectorDiff::Insert { index: 0, value: event } => {
2125 assert_eq!(event.event_id(), Some(ev_id_0));
2126 }
2127 );
2128 }
2129 );
2130
2131 assert!(event_loaded(room_event_cache, ev_id_0).await);
2133
2134 updates_stream
2135 };
2136
2137 let mut updates_stream_p1 = {
2139 let room_event_cache = &room_event_cache_p1;
2140 let (initial_updates, mut updates_stream) =
2141 room_event_cache_p1.subscribe().await.unwrap();
2142
2143 assert_eq!(initial_updates.len(), 1);
2145 assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2146 assert!(updates_stream.is_empty());
2147
2148 assert!(event_loaded(room_event_cache, ev_id_1).await);
2150
2151 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2153
2154 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2156
2157 assert_matches!(
2159 updates_stream.recv().await.unwrap(),
2160 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2161 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2162 assert_matches!(
2163 &diffs[0],
2164 VectorDiff::Insert { index: 0, value: event } => {
2165 assert_eq!(event.event_id(), Some(ev_id_0));
2166 }
2167 );
2168 }
2169 );
2170
2171 assert!(event_loaded(room_event_cache, ev_id_0).await);
2173
2174 updates_stream
2175 };
2176
2177 for _ in 0..3 {
2179 {
2183 let room_event_cache = &room_event_cache_p0;
2184 let updates_stream = &mut updates_stream_p0;
2185
2186 assert!(event_loaded(room_event_cache, ev_id_1).await);
2188
2189 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2192
2193 assert_matches!(
2195 updates_stream.recv().await.unwrap(),
2196 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2197 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2198 assert_matches!(&diffs[0], VectorDiff::Clear);
2199 assert_matches!(
2200 &diffs[1],
2201 VectorDiff::Append { values: events } => {
2202 assert_eq!(events.len(), 1);
2203 assert_eq!(events[0].event_id(), Some(ev_id_1));
2204 }
2205 );
2206 }
2207 );
2208
2209 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2211
2212 assert!(event_loaded(room_event_cache, ev_id_0).await);
2214
2215 assert_matches!(
2217 updates_stream.recv().await.unwrap(),
2218 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2219 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2220 assert_matches!(
2221 &diffs[0],
2222 VectorDiff::Insert { index: 0, value: event } => {
2223 assert_eq!(event.event_id(), Some(ev_id_0));
2224 }
2225 );
2226 }
2227 );
2228 }
2229
2230 {
2234 let room_event_cache = &room_event_cache_p1;
2235 let updates_stream = &mut updates_stream_p1;
2236
2237 assert!(event_loaded(room_event_cache, ev_id_1).await);
2239
2240 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2243
2244 assert_matches!(
2246 updates_stream.recv().await.unwrap(),
2247 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2248 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2249 assert_matches!(&diffs[0], VectorDiff::Clear);
2250 assert_matches!(
2251 &diffs[1],
2252 VectorDiff::Append { values: events } => {
2253 assert_eq!(events.len(), 1);
2254 assert_eq!(events[0].event_id(), Some(ev_id_1));
2255 }
2256 );
2257 }
2258 );
2259
2260 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2262
2263 assert!(event_loaded(room_event_cache, ev_id_0).await);
2265
2266 assert_matches!(
2268 updates_stream.recv().await.unwrap(),
2269 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2270 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2271 assert_matches!(
2272 &diffs[0],
2273 VectorDiff::Insert { index: 0, value: event } => {
2274 assert_eq!(event.event_id(), Some(ev_id_0));
2275 }
2276 );
2277 }
2278 );
2279 }
2280 }
2281
2282 for _ in 0..3 {
2285 {
2286 let room_event_cache = &room_event_cache_p0;
2287 let updates_stream = &mut updates_stream_p0;
2288
2289 let guard = room_event_cache.inner.state.read().await.unwrap();
2290
2291 assert!(guard.is_dirty().not());
2297
2298 assert_matches!(
2300 updates_stream.recv().await.unwrap(),
2301 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2302 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2303 assert_matches!(&diffs[0], VectorDiff::Clear);
2304 assert_matches!(
2305 &diffs[1],
2306 VectorDiff::Append { values: events } => {
2307 assert_eq!(events.len(), 1);
2308 assert_eq!(events[0].event_id(), Some(ev_id_1));
2309 }
2310 );
2311 }
2312 );
2313
2314 assert!(event_loaded(room_event_cache, ev_id_1).await);
2315 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2316
2317 drop(guard);
2323
2324 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2325 assert!(event_loaded(room_event_cache, ev_id_0).await);
2326
2327 assert_matches!(
2329 updates_stream.recv().await.unwrap(),
2330 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2331 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2332 assert_matches!(
2333 &diffs[0],
2334 VectorDiff::Insert { index: 0, value: event } => {
2335 assert_eq!(event.event_id(), Some(ev_id_0));
2336 }
2337 );
2338 }
2339 );
2340 }
2341
2342 {
2343 let room_event_cache = &room_event_cache_p1;
2344 let updates_stream = &mut updates_stream_p1;
2345
2346 let guard = room_event_cache.inner.state.read().await.unwrap();
2347
2348 assert!(guard.is_dirty().not());
2353
2354 assert_matches!(
2356 updates_stream.recv().await.unwrap(),
2357 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2358 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2359 assert_matches!(&diffs[0], VectorDiff::Clear);
2360 assert_matches!(
2361 &diffs[1],
2362 VectorDiff::Append { values: events } => {
2363 assert_eq!(events.len(), 1);
2364 assert_eq!(events[0].event_id(), Some(ev_id_1));
2365 }
2366 );
2367 }
2368 );
2369
2370 assert!(event_loaded(room_event_cache, ev_id_1).await);
2371 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2372
2373 drop(guard);
2379
2380 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2381 assert!(event_loaded(room_event_cache, ev_id_0).await);
2382
2383 assert_matches!(
2385 updates_stream.recv().await.unwrap(),
2386 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2387 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2388 assert_matches!(
2389 &diffs[0],
2390 VectorDiff::Insert { index: 0, value: event } => {
2391 assert_eq!(event.event_id(), Some(ev_id_0));
2392 }
2393 );
2394 }
2395 );
2396 }
2397 }
2398
2399 for _ in 0..3 {
2401 {
2402 let room_event_cache = &room_event_cache_p0;
2403 let updates_stream = &mut updates_stream_p0;
2404
2405 let guard = room_event_cache.inner.state.write().await.unwrap();
2406
2407 assert!(guard.is_dirty().not());
2409
2410 assert_matches!(
2412 updates_stream.recv().await.unwrap(),
2413 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2414 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2415 assert_matches!(&diffs[0], VectorDiff::Clear);
2416 assert_matches!(
2417 &diffs[1],
2418 VectorDiff::Append { values: events } => {
2419 assert_eq!(events.len(), 1);
2420 assert_eq!(events[0].event_id(), Some(ev_id_1));
2421 }
2422 );
2423 }
2424 );
2425
2426 drop(guard);
2429
2430 assert!(event_loaded(room_event_cache, ev_id_1).await);
2431 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2432
2433 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2434 assert!(event_loaded(room_event_cache, ev_id_0).await);
2435
2436 assert_matches!(
2438 updates_stream.recv().await.unwrap(),
2439 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2440 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2441 assert_matches!(
2442 &diffs[0],
2443 VectorDiff::Insert { index: 0, value: event } => {
2444 assert_eq!(event.event_id(), Some(ev_id_0));
2445 }
2446 );
2447 }
2448 );
2449 }
2450
2451 {
2452 let room_event_cache = &room_event_cache_p1;
2453 let updates_stream = &mut updates_stream_p1;
2454
2455 let guard = room_event_cache.inner.state.write().await.unwrap();
2456
2457 assert!(guard.is_dirty().not());
2459
2460 assert_matches!(
2462 updates_stream.recv().await.unwrap(),
2463 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2464 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2465 assert_matches!(&diffs[0], VectorDiff::Clear);
2466 assert_matches!(
2467 &diffs[1],
2468 VectorDiff::Append { values: events } => {
2469 assert_eq!(events.len(), 1);
2470 assert_eq!(events[0].event_id(), Some(ev_id_1));
2471 }
2472 );
2473 }
2474 );
2475
2476 drop(guard);
2479
2480 assert!(event_loaded(room_event_cache, ev_id_1).await);
2481 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2482
2483 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2484 assert!(event_loaded(room_event_cache, ev_id_0).await);
2485
2486 assert_matches!(
2488 updates_stream.recv().await.unwrap(),
2489 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2490 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2491 assert_matches!(
2492 &diffs[0],
2493 VectorDiff::Insert { index: 0, value: event } => {
2494 assert_eq!(event.event_id(), Some(ev_id_0));
2495 }
2496 );
2497 }
2498 );
2499 }
2500 }
2501 }
2502
2503 #[async_test]
2504 async fn test_load_when_dirty() {
2505 let room_id_0 = room_id!("!raclette:patate.ch");
2506 let room_id_1 = room_id!("!morbiflette:patate.ch");
2507
2508 let event_cache_store = MemoryStore::new();
2510
2511 let client_p0 = MockClientBuilder::new(None)
2513 .on_builder(|builder| {
2514 builder.store_config(
2515 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2516 .event_cache_store(event_cache_store.clone()),
2517 )
2518 })
2519 .build()
2520 .await;
2521
2522 let client_p1 = MockClientBuilder::new(None)
2524 .on_builder(|builder| {
2525 builder.store_config(
2526 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2527 .event_cache_store(event_cache_store),
2528 )
2529 })
2530 .build()
2531 .await;
2532
2533 let (room_event_cache_0_p0, room_event_cache_0_p1) = {
2535 let event_cache_p0 = client_p0.event_cache();
2536 event_cache_p0.subscribe().unwrap();
2537
2538 let event_cache_p1 = client_p1.event_cache();
2539 event_cache_p1.subscribe().unwrap();
2540
2541 client_p0.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2542 client_p0.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2543
2544 client_p1.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2545 client_p1.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2546
2547 let (room_event_cache_0_p0, _drop_handles) =
2548 client_p0.get_room(room_id_0).unwrap().event_cache().await.unwrap();
2549 let (room_event_cache_0_p1, _drop_handles) =
2550 client_p1.get_room(room_id_0).unwrap().event_cache().await.unwrap();
2551
2552 (room_event_cache_0_p0, room_event_cache_0_p1)
2553 };
2554
2555 {
2557 drop(room_event_cache_0_p0.inner.state.read().await.unwrap());
2558 drop(room_event_cache_0_p1.inner.state.read().await.unwrap());
2559 }
2560
2561 let (room_event_cache_1_p0, _) =
2565 client_p0.get_room(room_id_1).unwrap().event_cache().await.unwrap();
2566
2567 {
2569 let guard = room_event_cache_1_p0.inner.state.read().await.unwrap();
2570 assert!(guard.is_dirty().not());
2571 }
2572
2573 }
2576
2577 #[async_test]
2578 async fn test_uniq_read_marker() {
2579 let client = MockClientBuilder::new(None).build().await;
2580 let room_id = room_id!("!galette:saucisse.bzh");
2581 client.base_client().get_or_create_room(room_id, RoomState::Joined);
2582
2583 let event_cache = client.event_cache();
2584
2585 event_cache.subscribe().unwrap();
2586
2587 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2588 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
2589 let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
2590
2591 assert!(events.is_empty());
2592
2593 let read_marker_event = Raw::from_json_string(
2595 json!({
2596 "content": {
2597 "event_id": "$crepe:saucisse.bzh"
2598 },
2599 "room_id": "!galette:saucisse.bzh",
2600 "type": "m.fully_read"
2601 })
2602 .to_string(),
2603 )
2604 .unwrap();
2605 let account_data = vec![read_marker_event; 100];
2606
2607 room_event_cache
2608 .handle_joined_room_update(JoinedRoomUpdate { account_data, ..Default::default() })
2609 .await
2610 .unwrap();
2611
2612 assert_matches!(
2614 stream.recv().await.unwrap(),
2615 RoomEventCacheUpdate::MoveReadMarkerTo { .. }
2616 );
2617
2618 assert!(stream.recv().now_or_never().is_none());
2619
2620 assert!(generic_stream.recv().now_or_never().is_none());
2622 }
2623
2624 async fn event_loaded(room_event_cache: &RoomEventCache, event_id: &EventId) -> bool {
2625 room_event_cache
2626 .rfind_map_event_in_memory_by(|event| {
2627 (event.event_id() == Some(event_id)).then_some(())
2628 })
2629 .await
2630 .unwrap()
2631 .is_some()
2632 }
2633}