matrix_sdk/event_cache/caches/thread/
pagination.rs1use std::{fmt, sync::Arc};
16
17use eyeball::SharedObservable;
18use eyeball_im::VectorDiff;
19use matrix_sdk_base::{
20 event_cache::{Event, Gap},
21 linked_chunk::{ChunkContent, LinkedChunkId, Update},
22};
23use ruma::api::Direction;
24use tracing::{error, trace};
25
26use super::{
27 super::{
28 super::{
29 EventCacheError, EventsOrigin, Result, TimelineVectorDiffs,
30 deduplicator::{DeduplicationOutcome, filter_duplicate_events},
31 },
32 pagination::{
33 BackPaginationOutcome, LoadMoreEventsBackwardsOutcome, PaginatedCache, Pagination,
34 SharedPaginationStatus,
35 },
36 room::RoomEventCacheGenericUpdate,
37 },
38 ThreadEventCacheInner,
39 updates::ThreadEventCacheUpdate,
40};
41use crate::room::{IncludeRelations, RelationsOptions};
42
43#[derive(Clone)]
47struct ThreadEventCacheWrapper {
48 cache: Arc<ThreadEventCacheInner>,
49
50 dummy_pagination_status: SharedObservable<SharedPaginationStatus>,
53}
54
55#[allow(missing_debug_implementations)]
57pub struct ThreadPagination(Pagination<ThreadEventCacheWrapper>);
58
59impl ThreadPagination {
60 pub(super) fn new(cache: Arc<ThreadEventCacheInner>) -> Self {
62 Self(Pagination::new(ThreadEventCacheWrapper {
63 cache,
64 dummy_pagination_status: SharedObservable::new(SharedPaginationStatus::Idle {
65 hit_timeline_start: false,
66 }),
67 }))
68 }
69
70 pub async fn run_backwards_until(
82 &self,
83 num_requested_events: u16,
84 ) -> Result<BackPaginationOutcome> {
85 self.0.run_backwards_until(num_requested_events).await
86 }
87
88 pub async fn run_backwards_once(&self, batch_size: u16) -> Result<BackPaginationOutcome> {
93 self.0.run_backwards_once(batch_size).await
94 }
95}
96
97impl PaginatedCache for ThreadEventCacheWrapper {
98 fn status(&self) -> &SharedObservable<SharedPaginationStatus> {
99 &self.dummy_pagination_status
100 }
101
102 async fn load_more_events_backwards(&self) -> Result<LoadMoreEventsBackwardsOutcome> {
103 let mut state = self.cache.state.write().await?;
104
105 if let Some(prev_token) = state.thread_linked_chunk().rgap().map(|gap| gap.token) {
108 trace!(%prev_token, "thread chunk has at least a gap");
109
110 return Ok(LoadMoreEventsBackwardsOutcome::Gap {
111 prev_token: Some(prev_token),
112 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
113 });
114 }
115
116 let prev_first_chunk = state.thread_linked_chunk().first_chunk();
117
118 let linked_chunk_id = LinkedChunkId::Thread(&state.room_id, &state.thread_id);
122 let new_first_chunk = match state
123 .store
124 .load_previous_chunk(linked_chunk_id, prev_first_chunk.identifier())
125 .await
126 {
127 Ok(Some(new_first_chunk)) => {
128 new_first_chunk
130 }
131
132 Ok(None) => {
133 if let Some((_pos, first_event)) = state.thread_linked_chunk().events().next()
138 && self.cache.thread_id
139 == first_event.event_id().expect("Stored events all have an ID")
140 {
141 trace!("thread chunk is fully loaded and non-empty: reached_start=true");
142
143 return Ok(LoadMoreEventsBackwardsOutcome::StartOfTimeline);
144 }
145
146 return Ok(LoadMoreEventsBackwardsOutcome::Gap {
148 prev_token: None,
149 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
150 });
151 }
152
153 Err(err) => {
154 error!("error when loading the previous chunk of a linked chunk: {err}");
155
156 state
158 .store
159 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
160 .await?;
161
162 return Err(err.into());
164 }
165 };
166
167 let chunk_content = new_first_chunk.content.clone();
168
169 let reached_start = new_first_chunk.previous.is_none();
175
176 if let Err(err) = state.thread_linked_chunk_mut().insert_new_chunk_as_first(new_first_chunk)
177 {
178 error!("error when inserting the previous chunk into its linked chunk: {err}");
179
180 state
182 .store
183 .handle_linked_chunk_updates(
184 LinkedChunkId::Thread(&state.room_id, &state.thread_id),
185 vec![Update::Clear],
186 )
187 .await?;
188
189 return Err(err.into());
191 }
192
193 let _ = state.thread_linked_chunk_mut().store_updates().take();
196
197 let timeline_event_diffs = state.thread_linked_chunk_mut().updates_as_vector_diffs();
199
200 Ok(match chunk_content {
201 ChunkContent::Gap(gap) => {
202 trace!("reloaded chunk from disk (gap)");
203
204 LoadMoreEventsBackwardsOutcome::Gap {
205 prev_token: Some(gap.token),
206 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
207 }
208 }
209
210 ChunkContent::Items(events) => {
211 trace!(?reached_start, "reloaded chunk from disk ({} items)", events.len());
212
213 LoadMoreEventsBackwardsOutcome::Events {
214 events,
215 timeline_event_diffs,
216 reached_start,
217 }
218 }
219 })
220 }
221
222 async fn mark_has_waited_for_initial_prev_token(&self) -> Result<()> {
223 *self.cache.state.write().await?.waited_for_initial_prev_token_mut() = true;
224
225 Ok(())
226 }
227
228 async fn wait_for_prev_token(&self) {
229 self.cache.pagination_batch_token_notifier.notified().await
230 }
231
232 async fn paginate_backwards_with_network(
233 &self,
234 batch_size: u16,
235 prev_token: &Option<String>,
236 ) -> Result<Option<(Vec<Event>, Option<String>)>> {
237 let Some(room) = self.cache.weak_room.get() else {
238 return Ok(None);
240 };
241
242 let options = RelationsOptions {
243 from: prev_token.clone(),
244 dir: Direction::Backward,
245 limit: Some(batch_size.into()),
246 include_relations: IncludeRelations::AllRelations,
247 recurse: true,
248 };
249
250 let response = room
251 .relations(self.cache.thread_id.clone(), options)
252 .await
253 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
254
255 Ok(Some((response.chunk, response.next_batch_token)))
256 }
257
258 async fn conclude_backwards_pagination_from_disk(
259 &self,
260 events: Vec<Event>,
261 timeline_event_diffs: Vec<VectorDiff<Event>>,
262 reached_start: bool,
263 ) -> BackPaginationOutcome {
264 if !timeline_event_diffs.is_empty() {
265 self.cache.update_sender.send(
266 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
267 diffs: timeline_event_diffs,
268 origin: EventsOrigin::Cache,
269 }),
270 Some(RoomEventCacheGenericUpdate { room_id: self.cache.room_id.clone() }),
271 );
272 }
273
274 BackPaginationOutcome {
275 reached_start,
276 events: events.into_iter().rev().collect(),
279 }
280 }
281
282 async fn conclude_backwards_pagination_from_network(
283 &self,
284 mut events: Vec<Event>,
285 prev_token: Option<String>,
286 mut new_token: Option<String>,
287 ) -> Result<Option<BackPaginationOutcome>> {
288 let Some(room) = self.cache.weak_room.get() else {
289 return Ok(None);
291 };
292
293 if new_token.is_none() {
304 events.push(
305 room.load_or_fetch_event(&self.cache.thread_id, None)
306 .await
307 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?,
308 );
309 }
310
311 let mut state = self.cache.state.write().await?;
312
313 let prev_gap_id = if let Some(token) = prev_token {
316 let gap_chunk_id = state.thread_linked_chunk().chunk_identifier(|chunk| {
318 matches!(chunk.content(), ChunkContent::Gap(Gap { token: prev_token }) if *prev_token == token)
319 });
320
321 if gap_chunk_id.is_none() {
322 return Ok(None);
329 }
330
331 gap_chunk_id
332 } else {
333 None
334 };
335
336 let DeduplicationOutcome {
337 all_events: mut events,
338 in_memory_duplicated_event_ids,
339 in_store_duplicated_event_ids,
340 non_empty_all_duplicates: all_duplicates,
341 } = filter_duplicate_events(
342 &state.own_user_id,
343 &state.store,
344 LinkedChunkId::Thread(&state.room_id, &state.thread_id),
345 state.thread_linked_chunk(),
346 events,
347 )
348 .await?;
349
350 if !all_duplicates {
365 state
367 .remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids)
368 .await?;
369 } else {
370 events.clear();
372 new_token = None;
375 }
376
377 let topo_ordered_events = events.iter().rev().cloned().collect::<Vec<_>>();
380
381 let new_gap = new_token.map(|prev_token| Gap { token: prev_token });
382 let reached_start = state.thread_linked_chunk_mut().push_backwards_pagination_events(
383 prev_gap_id,
384 new_gap,
385 &topo_ordered_events,
386 );
387
388 state.state.propagate_changes(&state.store).await?;
390
391 let receipt_event = None;
399
400 state.post_process_upserted_events(topo_ordered_events.iter(), receipt_event).await?;
402
403 let timeline_event_diffs = state.thread_linked_chunk_mut().updates_as_vector_diffs();
405
406 if !timeline_event_diffs.is_empty() {
407 state.update_sender.send(
408 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
409 diffs: timeline_event_diffs,
410 origin: EventsOrigin::Pagination,
411 }),
412 Some(RoomEventCacheGenericUpdate { room_id: state.room_id.clone() }),
413 );
414 }
415
416 Ok(Some(BackPaginationOutcome { reached_start, events }))
417 }
418}
419
420impl fmt::Debug for ThreadPagination {
421 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
422 formatter.debug_tuple("ThreadPagination").finish_non_exhaustive()
423 }
424}