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 event_cache::caches::room::LinkedChunkUpdateSender,
60 paginators::{PaginationResult, Paginator, StartFromResult, thread::PaginableThread},
61 room::{IncludeRelations, MessagesOptions, RelationsOptions, WeakRoom},
62};
63
64#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
67pub enum EventFocusThreadMode {
68 ForceThread,
76
77 Automatic,
82}
83
84#[derive(Debug, Clone)]
86pub(crate) enum EventFocusedPaginationMode {
87 Room { hide_thread_events: bool },
90
91 Thread {
93 thread_root: OwnedEventId,
95 },
96}
97
98pub struct EventFocusedCacheState {
99 room: WeakRoom,
101
102 focused_event_id: OwnedEventId,
104
105 pagination_mode: EventFocusedPaginationMode,
107
108 chunk: EventLinkedChunk,
110
111 initial_num_context_events: u16,
115
116 thread_mode: EventFocusThreadMode,
118
119 pub update_sender: EventFocusedCacheUpdateSender,
121
122 linked_chunk_update_sender: LinkedChunkUpdateSender,
124}
125
126impl EventFocusedCacheState {
127 #[instrument(skip(self), fields(room_id = %self.room.room_id(), event_id = %self.focused_event_id))]
139 async fn start_from(
140 &mut self,
141 num_context_events: u16,
142 thread_mode: EventFocusThreadMode,
143 ) -> Result<StartFromResult> {
144 self.initial_num_context_events = num_context_events;
145 self.thread_mode = thread_mode;
146
147 let result = self.reload_impl().await?;
148
149 let _ = self.chunk.updates_as_vector_diffs();
157
158 Ok(result)
159 }
160
161 #[must_use = "Propagate `VectorDiff` updates via `TimelineVectorDiffs`"]
167 pub async fn reload(
168 &mut self,
169 _preprocessing: ReloadPreprocessing,
170 ) -> Result<Vec<VectorDiff<Event>>> {
171 let _ = self.reload_impl().await?;
172
173 Ok(self.chunk.updates_as_vector_diffs())
174 }
175
176 async fn reload_impl(&mut self) -> Result<StartFromResult> {
179 let room = self.room.get().ok_or(EventCacheError::ClientDropped)?;
180 let num_context_events = self.initial_num_context_events;
181 let thread_mode = self.thread_mode;
182
183 trace!(num_context_events, "fetching event with context via /context");
184
185 let paginator = Paginator::new(room);
186
187 let result =
188 paginator.start_from(&self.focused_event_id, UInt::from(num_context_events)).await?;
189
190 let thread_root = match thread_mode {
192 EventFocusThreadMode::ForceThread => {
193 let focused_event = result
195 .events
196 .iter()
197 .find(|event| event.event_id() == Some(&self.focused_event_id));
198
199 let mut thread_root =
201 focused_event.and_then(|event| extract_thread_root(event.raw()));
202
203 if thread_root.is_none() {
206 thread_root = Some(self.focused_event_id.clone());
207 }
208
209 trace!("force thread mode enabled, treating focused event as thread root");
210 thread_root
211 }
212
213 EventFocusThreadMode::Automatic => {
214 trace!(
215 "automatic thread mode enabled, checking if focused event is part of a thread"
216 );
217 result
218 .events
219 .iter()
220 .find(|event| event.event_id() == Some(&self.focused_event_id))
221 .and_then(|event| extract_thread_root(event.raw()))
222 }
223 };
224
225 let tokens = paginator.tokens();
227
228 if let Some(root_id) = thread_root {
229 trace!(thread_root = %root_id, "focused event is part of a thread, setting up thread pagination");
230
231 let includes_root =
235 result.events.iter().any(|event| event.event_id() == Some(&root_id));
236
237 self.pagination_mode =
238 EventFocusedPaginationMode::Thread { thread_root: root_id.clone() };
239
240 let thread_events = result
242 .events
243 .iter()
244 .filter(|event| {
245 extract_thread_root(event.raw()).as_ref() == Some(&root_id)
246 || event.event_id() == Some(&root_id)
247 })
248 .cloned()
249 .collect();
250
251 let backward_token = if includes_root {
253 None
255 } else {
256 tokens.previous.into_token()
257 };
258
259 let forward_token = tokens.next.into_token();
261
262 self.add_initial_events_with_gaps(thread_events, backward_token, forward_token);
263 } else {
264 trace!("focused event is not part of a thread, setting up room pagination");
265
266 let backward_token = tokens.previous.into_token();
267 let forward_token = tokens.next.into_token();
268
269 let hide_thread_events =
270 matches!(thread_mode, EventFocusThreadMode::Automatic) && thread_root.is_none();
271
272 self.pagination_mode = EventFocusedPaginationMode::Room { hide_thread_events };
273
274 let events = if hide_thread_events {
275 result
276 .events
277 .iter()
278 .filter(|event| extract_thread_root(event.raw()).is_none())
279 .cloned()
280 .collect()
281 } else {
282 result.events.clone()
283 };
284
285 self.add_initial_events_with_gaps(events, backward_token, forward_token);
286 }
287
288 self.propagate_changes();
289
290 Ok(result)
291 }
292
293 fn add_initial_events_with_gaps(
295 &mut self,
296 events: Vec<TimelineEvent>,
297 prev_gap_token: Option<String>,
298 next_gap_token: Option<String>,
299 ) {
300 self.chunk.reset();
302
303 self.chunk
306 .push_live_events(prev_gap_token.map(|prev_token| Gap { token: prev_token }), &events);
307
308 if let Some(next_token) = next_gap_token {
310 trace!("inserting forward pagination gap at back");
311 self.chunk.push_gap(Gap { token: next_token });
312 }
313 }
314
315 fn propagate_changes(&mut self) {
317 let updates = self.chunk.store_updates().take();
318 if !updates.is_empty() {
319 self.linked_chunk_update_sender.send(RoomEventCacheLinkedChunkUpdate {
320 updates,
321 linked_chunk_id: OwnedLinkedChunkId::EventFocused(
322 self.room.room_id().to_owned(),
323 self.focused_event_id.clone(),
324 ),
325 });
326 }
327 }
328
329 fn notify_subscribers(&mut self, origin: EventsOrigin) {
331 let diffs = self.chunk.updates_as_vector_diffs();
332 if !diffs.is_empty() {
333 let _ = self.update_sender.send(TimelineVectorDiffs { diffs, origin });
334 }
335 }
336
337 fn first_chunk_as_gap(&self) -> Option<(ChunkIdentifier, Gap)> {
339 self.chunk.first_chunk_as_gap()
340 }
341
342 fn last_chunk_as_gap(&self) -> Option<(ChunkIdentifier, Gap)> {
344 self.chunk.last_chunk_as_gap()
345 }
346
347 #[instrument(skip(self), fields(room_id = %self.room.room_id()))]
353 async fn paginate_backwards(&mut self, num_events: u16) -> Result<PaginationResult> {
354 let room = self.room.get().ok_or(EventCacheError::ClientDropped)?;
355
356 let Some((gap_id, gap)) = self.first_chunk_as_gap() else {
358 trace!("no front gap found, already at timeline start");
361 return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });
362 };
363
364 let token = gap.token;
365 trace!(?token, "paginating backwards with token from front gap");
366
367 let (mut events, new_token) = match &self.pagination_mode {
369 EventFocusedPaginationMode::Room { .. } => {
370 Self::fetch_room_backwards(&room, num_events, &token).await?
371 }
372 EventFocusedPaginationMode::Thread { thread_root } => {
373 Self::fetch_thread_backwards(&room, num_events, &token, thread_root.clone()).await?
374 }
375 };
376
377 events.reverse();
380
381 let hit_end = new_token.is_none();
382 let new_gap = new_token.map(|t| Gap { token: t });
383
384 let hide_thread_events = match &self.pagination_mode {
385 EventFocusedPaginationMode::Room { hide_thread_events } => *hide_thread_events,
386 EventFocusedPaginationMode::Thread { .. } => false,
387 };
388
389 let events = if hide_thread_events {
390 events.into_iter().filter(|event| extract_thread_root(event.raw()).is_none()).collect()
391 } else {
392 events
393 };
394
395 self.chunk.push_backwards_pagination_events(Some(gap_id), new_gap, &events);
397
398 self.propagate_changes();
399 self.notify_subscribers(EventsOrigin::Pagination);
400
401 Ok(PaginationResult { events, hit_end_of_timeline: hit_end })
402 }
403
404 async fn fetch_room_backwards(
410 room: &Room,
411 num_events: u16,
412 token: &str,
413 ) -> Result<(Vec<Event>, Option<String>)> {
414 let mut options = MessagesOptions::backward().from(token);
415 options.limit = UInt::from(num_events);
416
417 let messages = room
418 .messages(options)
419 .await
420 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
421
422 Ok((messages.chunk, messages.end))
423 }
424
425 async fn fetch_thread_backwards(
430 room: &Room,
431 num_events: u16,
432 token: &str,
433 thread_root: OwnedEventId,
434 ) -> Result<(Vec<Event>, Option<String>)> {
435 let options = RelationsOptions {
436 from: Some(token.to_owned()),
437 dir: Direction::Backward,
438 limit: Some(UInt::from(num_events)),
439 include_relations: IncludeRelations::AllRelations,
440 recurse: true,
441 };
442
443 let mut result = room
444 .relations(thread_root.clone(), options)
445 .await
446 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
447
448 if result.next_batch_token.is_none() {
450 let root_event = room
451 .load_event(&thread_root)
452 .await
453 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
454 result.chunk.push(root_event);
455 }
456
457 Ok((result.chunk, result.next_batch_token))
458 }
459
460 #[instrument(skip(self), fields(room_id = %self.room.room_id()))]
466 async fn paginate_forwards(&mut self, num_events: u16) -> Result<PaginationResult> {
467 let room = self.room.get().ok_or(EventCacheError::ClientDropped)?;
468
469 let Some((gap_id, gap)) = self.last_chunk_as_gap() else {
471 trace!("no back gap found, already at timeline end");
473 return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });
474 };
475
476 let token = gap.token;
477 trace!(?token, "paginating forwards with token from back gap");
478
479 let (events, new_token) = match &self.pagination_mode {
481 EventFocusedPaginationMode::Room { .. } => {
482 Self::fetch_room_forwards(&room, num_events, &token).await?
483 }
484 EventFocusedPaginationMode::Thread { thread_root } => {
485 Self::fetch_thread_forwards(&room, num_events, &token, thread_root.clone()).await?
486 }
487 };
488
489 let hit_end = new_token.is_none();
490 let new_gap = new_token.map(|t| Gap { token: t });
491
492 let hide_thread_events = match &self.pagination_mode {
493 EventFocusedPaginationMode::Room { hide_thread_events } => *hide_thread_events,
494 EventFocusedPaginationMode::Thread { .. } => false,
495 };
496
497 let events = if hide_thread_events {
498 events.into_iter().filter(|event| extract_thread_root(event.raw()).is_none()).collect()
499 } else {
500 events
501 };
502
503 self.chunk.push_forwards_pagination_events(Some(gap_id), new_gap, &events);
505
506 self.propagate_changes();
507 self.notify_subscribers(EventsOrigin::Pagination);
508
509 Ok(PaginationResult { events, hit_end_of_timeline: hit_end })
510 }
511
512 async fn fetch_room_forwards(
514 room: &Room,
515 num_events: u16,
516 token: &str,
517 ) -> Result<(Vec<Event>, Option<String>)> {
518 let mut options = MessagesOptions::new(Direction::Forward);
519 options = options.from(Some(token));
520 options.limit = UInt::from(num_events);
521
522 let messages = room
523 .messages(options)
524 .await
525 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
526
527 Ok((messages.chunk, messages.end))
528 }
529
530 async fn fetch_thread_forwards(
532 room: &Room,
533 num_events: u16,
534 token: &str,
535 thread_root: OwnedEventId,
536 ) -> Result<(Vec<Event>, Option<String>)> {
537 let options = RelationsOptions {
538 from: Some(token.to_owned()),
539 dir: Direction::Forward,
540 limit: Some(UInt::from(num_events)),
541 include_relations: IncludeRelations::AllRelations,
542 recurse: true,
543 };
544
545 let result = room
546 .relations(thread_root, options)
547 .await
548 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
549
550 Ok((result.chunk, result.next_batch_token))
551 }
552}
553
554#[derive(Clone)]
570pub struct EventFocusedCache {
571 room_id: OwnedRoomId,
572 inner: Arc<CacheStateLock<EventFocusedStateSelector>>,
573}
574
575impl EventFocusedCache {
576 pub(super) async fn new(
578 room: WeakRoom,
579 key: EventFocusedCacheKey,
580 state: &StateLock,
581 linked_chunk_update_sender: LinkedChunkUpdateSender,
582 ) -> Result<Self> {
583 let room_id = room.room_id().to_owned();
584
585 let cache_state = state
586 .try_insert_once_with(
587 EventFocusedStateSelector::new(room_id.clone(), key.clone()),
588 |_store_guard| async {
589 Ok(EventFocusedCacheState {
590 room,
591 focused_event_id: key.focused_event_id,
592 pagination_mode: EventFocusedPaginationMode::Room {
593 hide_thread_events: false,
594 },
595 chunk: EventLinkedChunk::new(),
596 initial_num_context_events: 0, thread_mode: EventFocusThreadMode::Automatic, update_sender: Sender::new(32),
599 linked_chunk_update_sender,
600 })
601 },
602 )
603 .await?;
604
605 Ok(Self { room_id, inner: Arc::new(cache_state) })
606 }
607
608 pub fn room_id(&self) -> &RoomId {
610 &self.room_id
611 }
612
613 pub async fn events(&self) -> Result<Vec<Event>> {
618 let state = self.inner.read().await?;
619
620 Ok(state.chunk.events().map(|(_position, item)| item.clone()).collect())
621 }
622
623 pub async fn subscribe(&self) -> Result<(Vec<Event>, Receiver<TimelineVectorDiffs>)> {
625 let state = self.inner.read().await?;
626 let events = state.chunk.events().map(|(_position, item)| item.clone()).collect();
627 let recv = state.update_sender.subscribe();
628 Ok((events, recv))
629 }
630
631 pub async fn hit_timeline_start(&self) -> Result<bool> {
634 Ok(self.inner.read().await?.first_chunk_as_gap().is_none())
635 }
636
637 pub async fn hit_timeline_end(&self) -> Result<bool> {
640 Ok(self.inner.read().await?.last_chunk_as_gap().is_none())
641 }
642
643 pub(super) async fn start_from(
646 &self,
647 num_context_events: u16,
648 thread_mode: EventFocusThreadMode,
649 ) -> Result<StartFromResult> {
650 self.inner.write().await?.start_from(num_context_events, thread_mode).await
651 }
652
653 pub async fn paginate_backwards(&self, num_events: u16) -> Result<PaginationResult> {
656 self.inner.write().await?.paginate_backwards(num_events).await
657 }
658
659 pub async fn paginate_forwards(&self, num_events: u16) -> Result<PaginationResult> {
662 self.inner.write().await?.paginate_forwards(num_events).await
663 }
664
665 pub async fn thread_root(&self) -> Result<Option<OwnedEventId>> {
667 Ok(match &self.inner.read().await?.pagination_mode {
668 EventFocusedPaginationMode::Thread { thread_root } => Some(thread_root.clone()),
669 _ => None,
670 })
671 }
672
673 #[cfg(feature = "e2e-encryption")]
677 pub(in super::super) async fn replace_in_memory_utds(
678 &self,
679 resolved_events: &[MaybeResolvedEvent],
680 ) -> Result<()> {
681 let mut state = self.inner.write().await?;
682
683 let new_resolved_events = resolved_events.try_resolve_events(&state.chunk);
689
690 if state.chunk.replace_utds(&new_resolved_events) {
691 state.propagate_changes();
692 state.notify_subscribers(EventsOrigin::Cache);
693 }
694
695 Ok(())
696 }
697}
698
699#[cfg(not(tarpaulin_include))]
700impl std::fmt::Debug for EventFocusedCache {
701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
702 f.debug_struct("EventFocusedCache").finish_non_exhaustive()
703 }
704}
705
706#[derive(Clone, Debug, Hash, PartialEq, Eq)]
708pub struct EventFocusedCacheKey {
709 pub focused_event_id: OwnedEventId,
711 pub thread_mode: EventFocusThreadMode,
713}
714
715pub type EventFocusedCacheUpdateSender = Sender<TimelineVectorDiffs>;