1use std::sync::Arc;
33
34use eyeball_im::VectorDiff;
35use matrix_sdk_base::{
36 deserialized_responses::TimelineEvent,
37 event_cache::{Event, Gap},
38 linked_chunk::OwnedLinkedChunkId,
39};
40use matrix_sdk_common::{linked_chunk::ChunkIdentifier, serde_helpers::extract_thread_root};
41use ruma::{OwnedEventId, OwnedRoomId, RoomId, UInt, api::Direction};
42use tokio::sync::broadcast::{Receiver, Sender};
43use tracing::{instrument, trace};
44
45#[cfg(feature = "e2e-encryption")]
46use super::super::redecryptor::{MaybeResolvedEvent, TryResolveEvents};
47use super::{
48 super::{
49 EventCacheError, EventsOrigin, Result, RoomEventCacheLinkedChunkUpdate,
50 states::{
51 CacheStateLock, ReloadPreprocessing, StateLock, selectors::EventFocusedStateSelector,
52 },
53 },
54 TimelineVectorDiffs,
55 event_linked_chunk::EventLinkedChunk,
56};
57use crate::{
58 Room,
59 paginators::{PaginationResult, Paginator, StartFromResult, thread::PaginableThread},
60 room::{IncludeRelations, MessagesOptions, RelationsOptions, WeakRoom},
61};
62
63#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
66pub enum EventFocusThreadMode {
67 ForceThread,
75
76 Automatic,
81}
82
83#[derive(Debug, Clone)]
85pub(crate) enum EventFocusedPaginationMode {
86 Room { hide_thread_events: bool },
89
90 Thread {
92 thread_root: OwnedEventId,
94 },
95}
96
97pub struct EventFocusedCacheState {
98 room: WeakRoom,
100
101 focused_event_id: OwnedEventId,
103
104 pagination_mode: EventFocusedPaginationMode,
106
107 chunk: EventLinkedChunk,
109
110 initial_num_context_events: u16,
114
115 thread_mode: EventFocusThreadMode,
117
118 pub update_sender: EventFocusedCacheUpdateSender,
120
121 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
123}
124
125impl EventFocusedCacheState {
126 #[instrument(skip(self), fields(room_id = %self.room.room_id(), event_id = %self.focused_event_id))]
138 async fn start_from(
139 &mut self,
140 num_context_events: u16,
141 thread_mode: EventFocusThreadMode,
142 ) -> Result<StartFromResult> {
143 self.initial_num_context_events = num_context_events;
144 self.thread_mode = thread_mode;
145
146 let result = self.reload_impl().await?;
147
148 let _ = self.chunk.updates_as_vector_diffs();
156
157 Ok(result)
158 }
159
160 #[must_use = "Propagate `VectorDiff` updates via `TimelineVectorDiffs`"]
166 pub async fn reload(
167 &mut self,
168 _preprocessing: ReloadPreprocessing,
169 ) -> Result<Vec<VectorDiff<Event>>> {
170 let _ = self.reload_impl().await?;
171
172 Ok(self.chunk.updates_as_vector_diffs())
173 }
174
175 async fn reload_impl(&mut self) -> Result<StartFromResult> {
178 let room = self.room.get().ok_or(EventCacheError::ClientDropped)?;
179 let num_context_events = self.initial_num_context_events;
180 let thread_mode = self.thread_mode;
181
182 trace!(num_context_events, "fetching event with context via /context");
183
184 let paginator = Paginator::new(room);
185
186 let result =
187 paginator.start_from(&self.focused_event_id, UInt::from(num_context_events)).await?;
188
189 let thread_root = match thread_mode {
191 EventFocusThreadMode::ForceThread => {
192 let focused_event = result
194 .events
195 .iter()
196 .find(|event| event.event_id() == Some(&self.focused_event_id));
197
198 let mut thread_root =
200 focused_event.and_then(|event| extract_thread_root(event.raw()));
201
202 if thread_root.is_none() {
205 thread_root = Some(self.focused_event_id.clone());
206 }
207
208 trace!("force thread mode enabled, treating focused event as thread root");
209 thread_root
210 }
211
212 EventFocusThreadMode::Automatic => {
213 trace!(
214 "automatic thread mode enabled, checking if focused event is part of a thread"
215 );
216 result
217 .events
218 .iter()
219 .find(|event| event.event_id() == Some(&self.focused_event_id))
220 .and_then(|event| extract_thread_root(event.raw()))
221 }
222 };
223
224 let tokens = paginator.tokens();
226
227 if let Some(root_id) = thread_root {
228 trace!(thread_root = %root_id, "focused event is part of a thread, setting up thread pagination");
229
230 let includes_root =
234 result.events.iter().any(|event| event.event_id() == Some(&root_id));
235
236 self.pagination_mode =
237 EventFocusedPaginationMode::Thread { thread_root: root_id.clone() };
238
239 let thread_events = result
241 .events
242 .iter()
243 .filter(|event| {
244 extract_thread_root(event.raw()).as_ref() == Some(&root_id)
245 || event.event_id() == Some(&root_id)
246 })
247 .cloned()
248 .collect();
249
250 let backward_token = if includes_root {
252 None
254 } else {
255 tokens.previous.into_token()
256 };
257
258 let forward_token = tokens.next.into_token();
260
261 self.add_initial_events_with_gaps(thread_events, backward_token, forward_token);
262 } else {
263 trace!("focused event is not part of a thread, setting up room pagination");
264
265 let backward_token = tokens.previous.into_token();
266 let forward_token = tokens.next.into_token();
267
268 let hide_thread_events =
269 matches!(thread_mode, EventFocusThreadMode::Automatic) && thread_root.is_none();
270
271 self.pagination_mode = EventFocusedPaginationMode::Room { hide_thread_events };
272
273 let events = if hide_thread_events {
274 result
275 .events
276 .iter()
277 .filter(|event| extract_thread_root(event.raw()).is_none())
278 .cloned()
279 .collect()
280 } else {
281 result.events.clone()
282 };
283
284 self.add_initial_events_with_gaps(events, backward_token, forward_token);
285 }
286
287 self.propagate_changes();
288
289 Ok(result)
290 }
291
292 fn add_initial_events_with_gaps(
294 &mut self,
295 events: Vec<TimelineEvent>,
296 prev_gap_token: Option<String>,
297 next_gap_token: Option<String>,
298 ) {
299 self.chunk.reset();
301
302 self.chunk
305 .push_live_events(prev_gap_token.map(|prev_token| Gap { token: prev_token }), &events);
306
307 if let Some(next_token) = next_gap_token {
309 trace!("inserting forward pagination gap at back");
310 self.chunk.push_gap(Gap { token: next_token });
311 }
312 }
313
314 fn propagate_changes(&mut self) {
316 let updates = self.chunk.store_updates().take();
317 if !updates.is_empty() {
318 let _ = self.linked_chunk_update_sender.send(RoomEventCacheLinkedChunkUpdate {
319 updates,
320 linked_chunk_id: OwnedLinkedChunkId::EventFocused(
321 self.room.room_id().to_owned(),
322 self.focused_event_id.clone(),
323 ),
324 });
325 }
326 }
327
328 fn notify_subscribers(&mut self, origin: EventsOrigin) {
330 let diffs = self.chunk.updates_as_vector_diffs();
331 if !diffs.is_empty() {
332 let _ = self.update_sender.send(TimelineVectorDiffs { diffs, origin });
333 }
334 }
335
336 fn first_chunk_as_gap(&self) -> Option<(ChunkIdentifier, Gap)> {
338 self.chunk.first_chunk_as_gap()
339 }
340
341 fn last_chunk_as_gap(&self) -> Option<(ChunkIdentifier, Gap)> {
343 self.chunk.last_chunk_as_gap()
344 }
345
346 #[instrument(skip(self), fields(room_id = %self.room.room_id()))]
352 async fn paginate_backwards(&mut self, num_events: u16) -> Result<PaginationResult> {
353 let room = self.room.get().ok_or(EventCacheError::ClientDropped)?;
354
355 let Some((gap_id, gap)) = self.first_chunk_as_gap() else {
357 trace!("no front gap found, already at timeline start");
360 return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });
361 };
362
363 let token = gap.token;
364 trace!(?token, "paginating backwards with token from front gap");
365
366 let (mut events, new_token) = match &self.pagination_mode {
368 EventFocusedPaginationMode::Room { .. } => {
369 Self::fetch_room_backwards(&room, num_events, &token).await?
370 }
371 EventFocusedPaginationMode::Thread { thread_root } => {
372 Self::fetch_thread_backwards(&room, num_events, &token, thread_root.clone()).await?
373 }
374 };
375
376 events.reverse();
379
380 let hit_end = new_token.is_none();
381 let new_gap = new_token.map(|t| Gap { token: t });
382
383 let hide_thread_events = match &self.pagination_mode {
384 EventFocusedPaginationMode::Room { hide_thread_events } => *hide_thread_events,
385 EventFocusedPaginationMode::Thread { .. } => false,
386 };
387
388 let events = if hide_thread_events {
389 events.into_iter().filter(|event| extract_thread_root(event.raw()).is_none()).collect()
390 } else {
391 events
392 };
393
394 self.chunk.push_backwards_pagination_events(Some(gap_id), new_gap, &events);
396
397 self.propagate_changes();
398 self.notify_subscribers(EventsOrigin::Pagination);
399
400 Ok(PaginationResult { events, hit_end_of_timeline: hit_end })
401 }
402
403 async fn fetch_room_backwards(
409 room: &Room,
410 num_events: u16,
411 token: &str,
412 ) -> Result<(Vec<Event>, Option<String>)> {
413 let mut options = MessagesOptions::backward().from(token);
414 options.limit = UInt::from(num_events);
415
416 let messages = room
417 .messages(options)
418 .await
419 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
420
421 Ok((messages.chunk, messages.end))
422 }
423
424 async fn fetch_thread_backwards(
429 room: &Room,
430 num_events: u16,
431 token: &str,
432 thread_root: OwnedEventId,
433 ) -> Result<(Vec<Event>, Option<String>)> {
434 let options = RelationsOptions {
435 from: Some(token.to_owned()),
436 dir: Direction::Backward,
437 limit: Some(UInt::from(num_events)),
438 include_relations: IncludeRelations::AllRelations,
439 recurse: true,
440 };
441
442 let mut result = room
443 .relations(thread_root.clone(), options)
444 .await
445 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
446
447 if result.next_batch_token.is_none() {
449 let root_event = room
450 .load_event(&thread_root)
451 .await
452 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
453 result.chunk.push(root_event);
454 }
455
456 Ok((result.chunk, result.next_batch_token))
457 }
458
459 #[instrument(skip(self), fields(room_id = %self.room.room_id()))]
465 async fn paginate_forwards(&mut self, num_events: u16) -> Result<PaginationResult> {
466 let room = self.room.get().ok_or(EventCacheError::ClientDropped)?;
467
468 let Some((gap_id, gap)) = self.last_chunk_as_gap() else {
470 trace!("no back gap found, already at timeline end");
472 return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });
473 };
474
475 let token = gap.token;
476 trace!(?token, "paginating forwards with token from back gap");
477
478 let (events, new_token) = match &self.pagination_mode {
480 EventFocusedPaginationMode::Room { .. } => {
481 Self::fetch_room_forwards(&room, num_events, &token).await?
482 }
483 EventFocusedPaginationMode::Thread { thread_root } => {
484 Self::fetch_thread_forwards(&room, num_events, &token, thread_root.clone()).await?
485 }
486 };
487
488 let hit_end = new_token.is_none();
489 let new_gap = new_token.map(|t| Gap { token: t });
490
491 let hide_thread_events = match &self.pagination_mode {
492 EventFocusedPaginationMode::Room { hide_thread_events } => *hide_thread_events,
493 EventFocusedPaginationMode::Thread { .. } => false,
494 };
495
496 let events = if hide_thread_events {
497 events.into_iter().filter(|event| extract_thread_root(event.raw()).is_none()).collect()
498 } else {
499 events
500 };
501
502 self.chunk.push_forwards_pagination_events(Some(gap_id), new_gap, &events);
504
505 self.propagate_changes();
506 self.notify_subscribers(EventsOrigin::Pagination);
507
508 Ok(PaginationResult { events, hit_end_of_timeline: hit_end })
509 }
510
511 async fn fetch_room_forwards(
513 room: &Room,
514 num_events: u16,
515 token: &str,
516 ) -> Result<(Vec<Event>, Option<String>)> {
517 let mut options = MessagesOptions::new(Direction::Forward);
518 options = options.from(Some(token));
519 options.limit = UInt::from(num_events);
520
521 let messages = room
522 .messages(options)
523 .await
524 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
525
526 Ok((messages.chunk, messages.end))
527 }
528
529 async fn fetch_thread_forwards(
531 room: &Room,
532 num_events: u16,
533 token: &str,
534 thread_root: OwnedEventId,
535 ) -> Result<(Vec<Event>, Option<String>)> {
536 let options = RelationsOptions {
537 from: Some(token.to_owned()),
538 dir: Direction::Forward,
539 limit: Some(UInt::from(num_events)),
540 include_relations: IncludeRelations::AllRelations,
541 recurse: true,
542 };
543
544 let result = room
545 .relations(thread_root, options)
546 .await
547 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
548
549 Ok((result.chunk, result.next_batch_token))
550 }
551}
552
553#[derive(Clone)]
569pub struct EventFocusedCache {
570 room_id: OwnedRoomId,
571 inner: Arc<CacheStateLock<EventFocusedStateSelector>>,
572}
573
574impl EventFocusedCache {
575 pub(super) async fn new(
577 room: WeakRoom,
578 key: EventFocusedCacheKey,
579 state: &StateLock,
580 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
581 ) -> Result<Self> {
582 let room_id = room.room_id().to_owned();
583
584 let cache_state = state
585 .try_insert_once_with(
586 EventFocusedStateSelector::new(room_id.clone(), key.clone()),
587 |_store_guard| async {
588 Ok(EventFocusedCacheState {
589 room,
590 focused_event_id: key.focused_event_id,
591 pagination_mode: EventFocusedPaginationMode::Room {
592 hide_thread_events: false,
593 },
594 chunk: EventLinkedChunk::new(),
595 initial_num_context_events: 0, thread_mode: EventFocusThreadMode::Automatic, update_sender: Sender::new(32),
598 linked_chunk_update_sender,
599 })
600 },
601 )
602 .await?;
603
604 Ok(Self { room_id, inner: Arc::new(cache_state) })
605 }
606
607 pub fn room_id(&self) -> &RoomId {
609 &self.room_id
610 }
611
612 pub async fn events(&self) -> Result<Vec<Event>> {
617 let state = self.inner.read().await?;
618
619 Ok(state.chunk.events().map(|(_position, item)| item.clone()).collect())
620 }
621
622 pub async fn subscribe(&self) -> Result<(Vec<Event>, Receiver<TimelineVectorDiffs>)> {
624 let state = self.inner.read().await?;
625 let events = state.chunk.events().map(|(_position, item)| item.clone()).collect();
626 let recv = state.update_sender.subscribe();
627 Ok((events, recv))
628 }
629
630 pub async fn hit_timeline_start(&self) -> Result<bool> {
633 Ok(self.inner.read().await?.first_chunk_as_gap().is_none())
634 }
635
636 pub async fn hit_timeline_end(&self) -> Result<bool> {
639 Ok(self.inner.read().await?.last_chunk_as_gap().is_none())
640 }
641
642 pub(super) async fn start_from(
645 &self,
646 num_context_events: u16,
647 thread_mode: EventFocusThreadMode,
648 ) -> Result<StartFromResult> {
649 self.inner.write().await?.start_from(num_context_events, thread_mode).await
650 }
651
652 pub async fn paginate_backwards(&self, num_events: u16) -> Result<PaginationResult> {
655 self.inner.write().await?.paginate_backwards(num_events).await
656 }
657
658 pub async fn paginate_forwards(&self, num_events: u16) -> Result<PaginationResult> {
661 self.inner.write().await?.paginate_forwards(num_events).await
662 }
663
664 pub async fn thread_root(&self) -> Result<Option<OwnedEventId>> {
666 Ok(match &self.inner.read().await?.pagination_mode {
667 EventFocusedPaginationMode::Thread { thread_root } => Some(thread_root.clone()),
668 _ => None,
669 })
670 }
671
672 #[cfg(feature = "e2e-encryption")]
676 pub(in super::super) async fn replace_in_memory_utds(
677 &self,
678 resolved_events: &[MaybeResolvedEvent],
679 ) -> Result<()> {
680 let mut state = self.inner.write().await?;
681
682 let new_resolved_events = resolved_events.try_resolve_events(&state.chunk);
688
689 if state.chunk.replace_utds(&new_resolved_events) {
690 state.propagate_changes();
691 state.notify_subscribers(EventsOrigin::Cache);
692 }
693
694 Ok(())
695 }
696}
697
698#[cfg(not(tarpaulin_include))]
699impl std::fmt::Debug for EventFocusedCache {
700 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
701 f.debug_struct("EventFocusedCache").finish_non_exhaustive()
702 }
703}
704
705#[derive(Clone, Debug, Hash, PartialEq, Eq)]
707pub struct EventFocusedCacheKey {
708 pub focused_event_id: OwnedEventId,
710 pub thread_mode: EventFocusThreadMode,
712}
713
714pub type EventFocusedCacheUpdateSender = Sender<TimelineVectorDiffs>;