matrix_sdk/event_cache/caches/thread/
pagination.rs1use std::{fmt, sync::Arc};
16
17use eyeball::SharedObservable;
18use matrix_sdk_base::{
19 event_cache::{Event, Gap},
20 linked_chunk::{ChunkContent, LinkedChunkId, Update},
21};
22use ruma::api::Direction;
23use tracing::{error, trace};
24
25use super::{
26 super::{
27 super::{
28 EventCacheError, EventsOrigin, Result, TimelineVectorDiffs,
29 deduplicator::{DeduplicationOutcome, filter_duplicate_events},
30 },
31 pagination::{
32 BackPaginationOutcome, LoadMoreEventsBackwardsOutcome, PaginatedCache, Pagination,
33 SharedPaginationStatus,
34 },
35 room::RoomEventCacheGenericUpdate,
36 },
37 ThreadEventCacheInner,
38 updates::ThreadEventCacheUpdate,
39};
40use crate::room::{IncludeRelations, RelationsOptions};
41
42#[derive(Clone)]
46struct ThreadEventCacheWrapper {
47 cache: Arc<ThreadEventCacheInner>,
48
49 dummy_pagination_status: SharedObservable<SharedPaginationStatus>,
52}
53
54#[allow(missing_debug_implementations)]
56pub struct ThreadPagination(Pagination<ThreadEventCacheWrapper>);
57
58impl ThreadPagination {
59 pub(super) fn new(cache: Arc<ThreadEventCacheInner>) -> Self {
61 Self(Pagination::new(ThreadEventCacheWrapper {
62 cache,
63 dummy_pagination_status: SharedObservable::new(SharedPaginationStatus::Idle {
64 hit_timeline_start: false,
65 }),
66 }))
67 }
68
69 pub async fn run_backwards_until(
81 &self,
82 num_requested_events: u16,
83 ) -> Result<BackPaginationOutcome> {
84 self.0.run_backwards_until(num_requested_events).await
85 }
86
87 pub async fn run_backwards_once(&self, batch_size: u16) -> Result<BackPaginationOutcome> {
92 self.0.run_backwards_once(batch_size).await
93 }
94}
95
96impl PaginatedCache for ThreadEventCacheWrapper {
97 fn status(&self) -> &SharedObservable<SharedPaginationStatus> {
98 &self.dummy_pagination_status
99 }
100
101 async fn load_more_events_backwards(&self) -> Result<LoadMoreEventsBackwardsOutcome> {
102 let mut state = self.cache.state.write().await?;
103
104 if let Some(prev_token) = state.thread_linked_chunk().rgap().map(|gap| gap.token) {
107 trace!(%prev_token, "thread chunk has at least a gap");
108
109 return Ok(LoadMoreEventsBackwardsOutcome::Gap {
110 prev_token: Some(prev_token),
111 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
112 });
113 }
114
115 let prev_first_chunk = state.thread_linked_chunk().first_chunk();
116
117 let linked_chunk_id = LinkedChunkId::Thread(&state.room_id, &state.thread_id);
121 let new_first_chunk = match state
122 .store
123 .load_previous_chunk(linked_chunk_id, prev_first_chunk.identifier())
124 .await
125 {
126 Ok(Some(new_first_chunk)) => {
127 new_first_chunk
129 }
130
131 Ok(None) => {
132 if let Some((_pos, first_event)) = state.thread_linked_chunk().events().next()
137 && self.cache.thread_id
138 == first_event.event_id().expect("Stored events all have an ID")
139 {
140 trace!("thread chunk is fully loaded and non-empty: reached_start=true");
141
142 return Ok(LoadMoreEventsBackwardsOutcome::StartOfTimeline);
143 }
144
145 return Ok(LoadMoreEventsBackwardsOutcome::Gap {
147 prev_token: None,
148 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
149 });
150 }
151
152 Err(err) => {
153 error!("error when loading the previous chunk of a linked chunk: {err}");
154
155 state
157 .store
158 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
159 .await?;
160
161 return Err(err.into());
163 }
164 };
165
166 let chunk_content = new_first_chunk.content.clone();
167
168 let reached_start = new_first_chunk.previous.is_none();
174
175 if let Err(err) = state.thread_linked_chunk_mut().insert_new_chunk_as_first(new_first_chunk)
176 {
177 error!("error when inserting the previous chunk into its linked chunk: {err}");
178
179 state
181 .store
182 .handle_linked_chunk_updates(
183 LinkedChunkId::Thread(&state.room_id, &state.thread_id),
184 vec![Update::Clear],
185 )
186 .await?;
187
188 return Err(err.into());
190 }
191
192 let _ = state.thread_linked_chunk_mut().store_updates().take();
195
196 let timeline_event_diffs = state.thread_linked_chunk_mut().updates_as_vector_diffs();
198
199 Ok(match chunk_content {
200 ChunkContent::Gap(gap) => {
201 trace!("reloaded chunk from disk (gap)");
202
203 LoadMoreEventsBackwardsOutcome::Gap {
204 prev_token: Some(gap.token),
205 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
206 }
207 }
208
209 ChunkContent::Items(events) => {
210 trace!(?reached_start, "reloaded chunk from disk ({} items)", events.len());
211
212 if !timeline_event_diffs.is_empty() {
216 state.update_sender.send(
217 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
218 diffs: timeline_event_diffs,
219 origin: EventsOrigin::Cache,
220 }),
221 Some(RoomEventCacheGenericUpdate { room_id: self.cache.room_id.clone() }),
222 );
223 }
224
225 LoadMoreEventsBackwardsOutcome::Events { events, reached_start }
226 }
227 })
228 }
229
230 async fn mark_has_waited_for_initial_prev_token(&self) -> Result<()> {
231 *self.cache.state.write().await?.waited_for_initial_prev_token_mut() = true;
232
233 Ok(())
234 }
235
236 async fn wait_for_prev_token(&self) {
237 self.cache.pagination_batch_token_notifier.notified().await
238 }
239
240 async fn paginate_backwards_with_network(
241 &self,
242 batch_size: u16,
243 prev_token: &Option<String>,
244 ) -> Result<Option<(Vec<Event>, Option<String>)>> {
245 let Some(room) = self.cache.weak_room.get() else {
246 return Ok(None);
248 };
249
250 let options = RelationsOptions {
251 from: prev_token.clone(),
252 dir: Direction::Backward,
253 limit: Some(batch_size.into()),
254 include_relations: IncludeRelations::AllRelations,
255 recurse: true,
256 };
257
258 let response = room
259 .relations(self.cache.thread_id.clone(), options)
260 .await
261 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
262
263 Ok(Some((response.chunk, response.next_batch_token)))
264 }
265
266 async fn conclude_backwards_pagination_from_network(
267 &self,
268 mut events: Vec<Event>,
269 prev_token: Option<String>,
270 mut new_token: Option<String>,
271 ) -> Result<Option<BackPaginationOutcome>> {
272 let Some(room) = self.cache.weak_room.get() else {
273 return Ok(None);
275 };
276
277 if new_token.is_none() {
288 events.push(
289 room.load_or_fetch_event(&self.cache.thread_id, None)
290 .await
291 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?,
292 );
293 }
294
295 let mut state = self.cache.state.write().await?;
296
297 let prev_gap_id = if let Some(token) = prev_token {
300 let gap_chunk_id = state.thread_linked_chunk().chunk_identifier(|chunk| {
302 matches!(chunk.content(), ChunkContent::Gap(Gap { token: prev_token }) if *prev_token == token)
303 });
304
305 if gap_chunk_id.is_none() {
306 return Ok(None);
313 }
314
315 gap_chunk_id
316 } else {
317 None
318 };
319
320 let DeduplicationOutcome {
321 all_events: mut events,
322 in_memory_duplicated_event_ids,
323 in_store_duplicated_event_ids,
324 non_empty_all_duplicates: all_duplicates,
325 } = filter_duplicate_events(
326 &state.own_user_id,
327 &state.store,
328 LinkedChunkId::Thread(&state.room_id, &state.thread_id),
329 state.thread_linked_chunk(),
330 events,
331 )
332 .await?;
333
334 if !all_duplicates {
349 state
351 .remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids)
352 .await?;
353 } else {
354 events.clear();
356 new_token = None;
359 }
360
361 let topo_ordered_events = events.iter().rev().cloned().collect::<Vec<_>>();
364
365 let new_gap = new_token.map(|prev_token| Gap { token: prev_token });
366 let reached_start = state.thread_linked_chunk_mut().push_backwards_pagination_events(
367 prev_gap_id,
368 new_gap,
369 &topo_ordered_events,
370 );
371
372 state.state.propagate_changes(&state.store).await?;
374
375 let receipt_event = None;
383
384 state.post_process_upserted_events(topo_ordered_events.iter(), receipt_event).await?;
386
387 let timeline_event_diffs = state.thread_linked_chunk_mut().updates_as_vector_diffs();
389
390 if !timeline_event_diffs.is_empty() {
391 state.update_sender.send(
392 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
393 diffs: timeline_event_diffs,
394 origin: EventsOrigin::Pagination,
395 }),
396 Some(RoomEventCacheGenericUpdate { room_id: state.room_id.clone() }),
397 );
398 }
399
400 Ok(Some(BackPaginationOutcome { reached_start, events }))
401 }
402}
403
404impl fmt::Debug for ThreadPagination {
405 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
406 formatter.debug_tuple("ThreadPagination").finish_non_exhaustive()
407 }
408}