Skip to main content

matrix_sdk/event_cache/caches/
mod.rs

1// Copyright 2026 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{collections::HashMap, ops::Deref, sync::Arc};
16
17use eyeball::SharedObservable;
18use eyeball_im::VectorDiff;
19use matrix_sdk_base::{
20    ThreadingSupport,
21    event_cache::Event,
22    linked_chunk::Position,
23    sync::{JoinedRoomUpdate, LeftRoomUpdate},
24};
25use ruma::{OwnedEventId, RoomId, room_version_rules::RoomVersionRules};
26use tokio::sync::{
27    OnceCell, OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock, broadcast::Sender, mpsc,
28};
29
30use self::subscriber::AutoShrinkMessage;
31use super::{
32    EventCacheError, EventsOrigin, Result, automatic_pagination::AutomaticPagination, states,
33};
34use crate::{client::WeakClient, room::WeakRoom};
35
36mod aggregator;
37pub mod event_focused;
38pub mod event_linked_chunk;
39pub mod pagination;
40pub mod pinned_events;
41mod read_receipts;
42pub mod room;
43pub mod subscriber;
44pub mod thread;
45
46/// A type to hold all the caches for a given room.
47#[derive(Debug)]
48pub(super) struct Caches {
49    /// The one and only [`RoomEventCache`].
50    ///
51    /// [`RoomEventCache`]: room::RoomEventCache
52    pub room: room::RoomEventCache,
53
54    /// All the lazily-loaded [`ThreadEventCache`].
55    ///
56    /// [`ThreadEventCache`]: thread::ThreadEventCache
57    // An `Arc` is used to get an owned lock.
58    pub threads: Arc<RwLock<HashMap<OwnedEventId, thread::ThreadEventCache>>>,
59
60    /// The one and only [`PinnedEventsCache`].
61    ///
62    /// [`PinnedEventsCache`]: pinned_events::PinnedEventsCache
63    pub pinned_events: OnceCell<pinned_events::PinnedEventsCache>,
64
65    /// All the lazily-loaded [`EventFocusedCache`].
66    ///
67    /// [`EventFocusedCache`]: event_focused::EventFocusedCache
68    // An `Arc` is used to get an owned lock.
69    pub event_focused:
70        Arc<RwLock<HashMap<event_focused::EventFocusedCacheKey, event_focused::EventFocusedCache>>>,
71
72    /// Internals data, used to lazily create caches.
73    internals: CachesInternals,
74}
75
76#[derive(Debug)]
77struct CachesInternals {
78    state: states::StateLock,
79    auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
80    linked_chunk_update_sender: Sender<room::RoomEventCacheLinkedChunkUpdate>,
81    room_version_rules: RoomVersionRules,
82}
83
84impl Caches {
85    /// Create a new [`Caches`].
86    pub async fn new(
87        weak_client: &WeakClient,
88        room_id: &RoomId,
89        generic_update_sender: Sender<room::RoomEventCacheGenericUpdate>,
90        linked_chunk_update_sender: Sender<room::RoomEventCacheLinkedChunkUpdate>,
91        auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
92        state: &states::StateLock,
93        automatic_pagination: Option<AutomaticPagination>,
94    ) -> Result<Self> {
95        let Some(client) = weak_client.get() else {
96            return Err(EventCacheError::ClientDropped);
97        };
98
99        let weak_room = WeakRoom::new(weak_client.clone(), room_id.to_owned());
100
101        let room = client
102            .get_room(room_id)
103            .ok_or_else(|| EventCacheError::RoomNotFound { room_id: room_id.to_owned() })?;
104        let room_version_rules = room.clone_info().room_version_rules_or_default();
105
106        let pagination_status = SharedObservable::new(pagination::SharedPaginationStatus::Idle {
107            hit_timeline_start: false,
108        });
109
110        let enabled_thread_support =
111            matches!(client.base_client().threading_support, ThreadingSupport::Enabled { .. });
112
113        let update_sender = room::RoomEventCacheUpdateSender::new(generic_update_sender.clone());
114
115        let own_user_id =
116            client.user_id().expect("the user must be logged in, at this point").to_owned();
117
118        let room_state = state
119            .try_insert_once_with(
120                states::selectors::RoomStateSelector::new(room_id.to_owned()),
121                |store_guard| {
122                    room::RoomEventCacheState::new(
123                        own_user_id.clone(),
124                        room_id.to_owned(),
125                        weak_room.clone(),
126                        room_version_rules.clone(),
127                        enabled_thread_support,
128                        update_sender.clone(),
129                        linked_chunk_update_sender.clone(),
130                        store_guard,
131                        pagination_status.clone(),
132                        automatic_pagination,
133                    )
134                },
135            )
136            .await?;
137
138        let timeline_is_not_empty =
139            room_state.read().await?.room_linked_chunk().revents().next().is_some();
140
141        let room_event_cache = room::RoomEventCache::new(
142            room_id.to_owned(),
143            weak_room,
144            own_user_id,
145            room_state,
146            pagination_status,
147            auto_shrink_sender.clone(),
148            update_sender,
149        );
150
151        // If at least one event has been loaded, it means there is a timeline. Let's
152        // emit a generic update.
153        if timeline_is_not_empty {
154            let _ = generic_update_sender
155                .send(room::RoomEventCacheGenericUpdate { room_id: room_id.to_owned() });
156        }
157
158        Ok(Self {
159            room: room_event_cache,
160            threads: Arc::new(RwLock::new(HashMap::new())),
161            pinned_events: OnceCell::new(),
162            event_focused: Arc::new(RwLock::new(HashMap::new())),
163            internals: CachesInternals {
164                state: state.clone(),
165                auto_shrink_sender,
166                linked_chunk_update_sender,
167                room_version_rules,
168            },
169        })
170    }
171
172    /// Get the [`RoomEventCache`].
173    ///
174    /// [`RoomEventCache`]: room::RoomEventCache
175    pub fn room(&self) -> &room::RoomEventCache {
176        &self.room
177    }
178
179    /// Get or create a [`ThreadEventCache`].
180    ///
181    /// Note: it is impossible to know if `thread_id` represents a valid thread
182    /// identifier. It means it's possible to create a [`ThreadEventCache`] for
183    /// an event that is not a thread root.
184    ///
185    /// [`ThreadEventCache`]: thread::ThreadEventCache
186    pub async fn thread(
187        &self,
188        thread_id: OwnedEventId,
189    ) -> Result<
190        OwnedRwLockReadGuard<
191            HashMap<OwnedEventId, thread::ThreadEventCache>,
192            thread::ThreadEventCache,
193        >,
194    > {
195        Ok(
196            match OwnedRwLockWriteGuard::try_downgrade_map(
197                self.threads.clone().write_owned().await,
198                |threads| threads.get(&thread_id),
199            ) {
200                // Thread exists.
201                Ok(locked_cache) => locked_cache,
202                // Thread does not exist, let's create it.
203                Err(mut threads) => {
204                    let room = &self.room;
205                    let cache = thread::ThreadEventCache::new(
206                        room.room_id().to_owned(),
207                        thread_id.clone(),
208                        room.own_user_id().to_owned(),
209                        self.internals.room_version_rules.clone(),
210                        room.weak_room().to_owned(),
211                        &self.internals.state,
212                        self.internals.auto_shrink_sender.clone(),
213                        room.update_sender().generic_update_sender().clone(),
214                        self.internals.linked_chunk_update_sender.clone(),
215                    )
216                    .await?;
217
218                    threads.insert(thread_id.clone(), cache);
219
220                    OwnedRwLockWriteGuard::downgrade_map(threads, |threads| {
221                        threads.get(&thread_id).unwrap()
222                    })
223                }
224            },
225        )
226    }
227
228    /// Get or create a [`PinnedEventsCache`].
229    ///
230    /// [`PinnedEventsCache`]: pinned_events::PinnedEventsCache
231    pub async fn pinned_events(&self) -> Result<&pinned_events::PinnedEventsCache> {
232        self.pinned_events
233            .get_or_try_init(|| {
234                pinned_events::PinnedEventsCache::new(
235                    self.room.weak_room(),
236                    self.room.own_user_id().clone(),
237                    self.internals.room_version_rules.clone(),
238                    self.internals.linked_chunk_update_sender.clone(),
239                    &self.internals.state,
240                )
241            })
242            .await
243    }
244
245    /// Get or create a [`EventFocusedCache`].
246    ///
247    /// [`EventFocusedCache`]: event_focused::EventFocusedCache
248    pub async fn event_focused(
249        &self,
250        event_id: OwnedEventId,
251        thread_mode: event_focused::EventFocusThreadMode,
252        number_of_initial_events: u16,
253    ) -> Result<
254        OwnedRwLockReadGuard<
255            HashMap<event_focused::EventFocusedCacheKey, event_focused::EventFocusedCache>,
256            event_focused::EventFocusedCache,
257        >,
258    > {
259        let key = event_focused::EventFocusedCacheKey { focused_event_id: event_id, thread_mode };
260
261        Ok(
262            match OwnedRwLockWriteGuard::try_downgrade_map(
263                self.event_focused.clone().write_owned().await,
264                |event_focused_caches| event_focused_caches.get(&key),
265            ) {
266                // Event-focused cache exists.
267                Ok(locked_cache) => locked_cache,
268                // Event-focused cache does not exist, let's create it.
269                Err(mut event_focused_caches) => {
270                    let cache = event_focused::EventFocusedCache::new(
271                        self.room.weak_room().clone(),
272                        key.clone(),
273                        &self.internals.state,
274                        self.internals.linked_chunk_update_sender.clone(),
275                    )
276                    .await?;
277                    cache.start_from(number_of_initial_events, thread_mode).await?;
278
279                    event_focused_caches.insert(key.clone(), cache);
280
281                    OwnedRwLockWriteGuard::downgrade_map(
282                        event_focused_caches,
283                        |event_focused_caches| event_focused_caches.get(&key).unwrap(),
284                    )
285                }
286            },
287        )
288    }
289
290    /// Update all the event caches with a [`JoinedRoomUpdate`].
291    pub(super) async fn handle_joined_room_update(&self, updates: JoinedRoomUpdate) -> Result<()> {
292        let Self { room, threads, pinned_events, event_focused, internals } = &self;
293
294        // Room.
295        {
296            let mut updates = updates.clone();
297            updates.timeline = aggregator::aggregate_timeline_for_room(updates.timeline);
298
299            room.handle_joined_room_update(updates).await?;
300        }
301
302        // Threads.
303        {
304            let mut updates = updates.clone();
305            updates.account_data.clear();
306            updates.ambiguity_changes.clear();
307
308            let timeline_for_threads = aggregator::aggregate_timeline_for_threads(
309                &updates.timeline,
310                threads.read().await.deref(),
311                room.state().read().await?,
312                &internals.room_version_rules.redaction,
313            )
314            .await?;
315
316            for (thread_id, timeline) in timeline_for_threads {
317                let mut updates = updates.clone();
318                updates.timeline = timeline;
319
320                let thread = self.thread(thread_id).await?;
321                thread.handle_joined_room_update(updates).await?;
322
323                let new_thread_summary =
324                    thread.state().read().await?.compute_thread_summary().await?;
325
326                room.update_thread_summary(thread.thread_id(), new_thread_summary).await?;
327            }
328        }
329
330        // Pinned-events.
331        if let Some(pinned_events) = pinned_events.get() {
332            let mut updates = updates.clone();
333            updates.timeline = aggregator::aggregate_timeline_for_pinned_events(
334                &updates.timeline,
335                &pinned_events.state().read().await?.current_event_ids(),
336                &internals.room_version_rules.redaction,
337            );
338
339            pinned_events.handle_joined_room_update(updates).await?;
340        }
341
342        // Event-focused.
343        {
344            // An event-focused cache isn't listening to live update. Consequently, it is
345            // not interested by this kind of update.
346            let _ = event_focused;
347        }
348
349        Ok(())
350    }
351
352    /// Update all the event caches with a [`LeftRoomUpdate`].
353    pub(super) async fn handle_left_room_update(&self, updates: LeftRoomUpdate) -> Result<()> {
354        let Self { room, threads, pinned_events, event_focused, internals } = &self;
355
356        // Room.
357        {
358            let mut updates = updates.clone();
359            updates.timeline = aggregator::aggregate_timeline_for_room(updates.timeline);
360
361            room.handle_left_room_update(updates).await?;
362        }
363
364        // Threads.
365        {
366            let mut updates = updates.clone();
367            updates.account_data.clear();
368            updates.ambiguity_changes.clear();
369
370            let timeline_for_threads = aggregator::aggregate_timeline_for_threads(
371                &updates.timeline,
372                threads.read().await.deref(),
373                room.state().read().await?,
374                &internals.room_version_rules.redaction,
375            )
376            .await?;
377
378            for (thread_id, timeline) in timeline_for_threads {
379                let mut updates = updates.clone();
380                updates.timeline = timeline;
381
382                let thread = self.thread(thread_id).await?;
383                thread.handle_left_room_update(updates).await?;
384
385                let new_thread_summary =
386                    thread.state().read().await?.compute_thread_summary().await?;
387
388                room.update_thread_summary(thread.thread_id(), new_thread_summary).await?;
389            }
390        }
391
392        // Pinned-events.
393        if let Some(pinned_events) = pinned_events.get() {
394            let mut updates = updates.clone();
395            updates.timeline = aggregator::aggregate_timeline_for_pinned_events(
396                &updates.timeline,
397                &pinned_events.state().read().await?.current_event_ids(),
398                &internals.room_version_rules.redaction,
399            );
400
401            pinned_events.handle_left_room_update(updates).await?;
402        }
403
404        // Event-focused.
405        {
406            // An event-focused cache isn't listening to live update. Consequently, it is
407            // not interested by this kind of update.
408            let _ = event_focused;
409        }
410
411        Ok(())
412    }
413
414    /// Get all in-memory events from all the event caches managed by this
415    /// [`Caches`].
416    ///
417    /// Events can be duplicated if present in different event caches.
418    #[cfg(feature = "e2e-encryption")]
419    pub async fn all_in_memory_events(&self) -> Result<impl Iterator<Item = Event>> {
420        // We have to fetch events from all the caches.
421        //
422        // The room cache contains all the room events + the thread events + the
423        // pinned-events.
424        let mut events = self.room.events().await?;
425
426        // The last cache is the events from the event-focused cache.
427        {
428            let event_focused = self.event_focused.read().await;
429
430            for event_focused in event_focused.values() {
431                events.extend(event_focused.events().await?);
432            }
433        }
434
435        Ok(events.into_iter())
436    }
437
438    /// Get all encrypted events from all the event caches managed by this
439    /// [`Caches`].
440    ///
441    /// The `event_type` represents the type of the event to filter by.
442    /// The `session_id` represents the unique ID of the room key that was used
443    /// to encrypt the event
444    ///
445    /// Events can be duplicated if present in different event caches.
446    #[cfg(feature = "e2e-encryption")]
447    pub async fn all_events_of_type(
448        &self,
449        event_type: Option<&str>,
450        session_id: Option<&str>,
451    ) -> Result<impl Iterator<Item = Event>> {
452        // All caches store their events in the store except one. Let's start by looking
453        // inside the store.
454        let mut events = {
455            let state = self.internals.state.read().await?;
456
457            state.store.get_room_events(self.room.room_id(), event_type, session_id).await?
458        };
459
460        // The only cache to not store its events is the event-focused cache. Its events
461        // only live in memory.
462        {
463            let event_focused = self.event_focused.read().await;
464
465            for event_focused in event_focused.values() {
466                events.extend(
467                    event_focused
468                        .events()
469                        .await?
470                        .into_iter()
471                        .filter(|event| event_type == event.kind.event_type().as_deref())
472                        .filter(|event| session_id == event.kind.session_id()),
473                );
474            }
475        }
476
477        Ok(events.into_iter())
478    }
479}
480
481/// A diff update for an event cache timeline represented as a vector.
482#[derive(Clone, Debug)]
483pub struct TimelineVectorDiffs {
484    /// New vector diff for the thread timeline.
485    pub diffs: Vec<VectorDiff<Event>>,
486    /// The origin that triggered this update.
487    pub origin: EventsOrigin,
488}
489
490/// An enum representing where an event has been found.
491#[derive(Debug)]
492pub(super) enum EventLocation {
493    /// Event lives in memory (and likely in the store!).
494    Memory(Position),
495
496    /// Event lives in the store only, it has not been loaded in memory yet.
497    Store,
498}