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)]
48struct ThreadEventCacheWrapper {
49 cache: Arc<ThreadEventCacheInner>,
50
51 dummy_pagination_status: SharedObservable<SharedPaginationStatus>,
54}
55
56#[allow(missing_debug_implementations)]
58pub struct ThreadPagination(Pagination<ThreadEventCacheWrapper>);
59
60impl ThreadPagination {
61 pub(super) fn new(cache: Arc<ThreadEventCacheInner>) -> Self {
63 Self(Pagination::new(ThreadEventCacheWrapper {
64 cache,
65 dummy_pagination_status: SharedObservable::new(SharedPaginationStatus::Idle {
66 hit_timeline_start: false,
67 }),
68 }))
69 }
70
71 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);
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 LoadMoreEventsBackwardsOutcome::Events {
213 events,
214 timeline_event_diffs,
215 reached_start,
216 }
217 }
218 })
219 }
220
221 async fn mark_has_waited_for_initial_prev_token(&self) -> Result<()> {
222 *self.cache.state.write().await?.waited_for_initial_prev_token_mut() = true;
223
224 Ok(())
225 }
226
227 async fn wait_for_prev_token(&self) {
228 self.cache.pagination_batch_token_notifier.notified().await
229 }
230
231 async fn paginate_backwards_with_network(
232 &self,
233 batch_size: u16,
234 prev_token: &Option<String>,
235 ) -> Result<Option<(Vec<Event>, Option<String>)>> {
236 let Some(room) = self.cache.weak_room.get() else {
237 return Ok(None);
239 };
240
241 let options = RelationsOptions {
242 from: prev_token.clone(),
243 dir: Direction::Backward,
244 limit: Some(batch_size.into()),
245 include_relations: IncludeRelations::AllRelations,
246 recurse: true,
247 };
248
249 let response = room
250 .relations(self.cache.thread_id.clone(), options)
251 .await
252 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
253
254 Ok(Some((response.chunk, response.next_batch_token)))
255 }
256
257 async fn conclude_backwards_pagination_from_disk(
258 &self,
259 events: Vec<Event>,
260 timeline_event_diffs: Vec<VectorDiff<Event>>,
261 reached_start: bool,
262 ) -> BackPaginationOutcome {
263 if !timeline_event_diffs.is_empty() {
264 self.cache.update_sender.send(
265 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
266 diffs: timeline_event_diffs,
267 origin: EventsOrigin::Cache,
268 }),
269 Some(RoomEventCacheGenericUpdate { room_id: self.cache.room_id.clone() }),
270 );
271 }
272
273 BackPaginationOutcome {
274 reached_start,
275 events: events.into_iter().rev().collect(),
278 }
279 }
280
281 async fn conclude_backwards_pagination_from_network(
282 &self,
283 mut events: Vec<Event>,
284 prev_token: Option<String>,
285 mut new_token: Option<String>,
286 ) -> Result<Option<BackPaginationOutcome>> {
287 let Some(room) = self.cache.weak_room.get() else {
288 return Ok(None);
290 };
291
292 if new_token.is_none() {
302 events.push(
303 room.load_or_fetch_event(&self.cache.thread_id, None)
304 .await
305 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?,
306 );
307 }
308
309 let mut state = self.cache.state.write().await?;
310
311 let prev_gap_id = if let Some(token) = prev_token {
314 let gap_chunk_id = state.thread_linked_chunk().chunk_identifier(|chunk| {
316 matches!(chunk.content(), ChunkContent::Gap(Gap { token: prev_token }) if *prev_token == token)
317 });
318
319 if gap_chunk_id.is_none() {
320 return Ok(None);
325 }
326
327 gap_chunk_id
328 } else {
329 None
330 };
331
332 let DeduplicationOutcome {
333 all_events: mut events,
334 in_memory_duplicated_event_ids,
335 in_store_duplicated_event_ids,
336 non_empty_all_duplicates: all_duplicates,
337 } = filter_duplicate_events(
338 &state.own_user_id,
339 &state.store,
340 LinkedChunkId::Thread(&state.room_id, &state.thread_id),
341 state.thread_linked_chunk(),
342 events,
343 )
344 .await?;
345
346 if !all_duplicates {
360 state
362 .remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids)
363 .await?;
364 } else {
365 events.clear();
367 new_token = None;
370 }
371
372 let topo_ordered_events = events.iter().rev().cloned().collect::<Vec<_>>();
375
376 let new_gap = new_token.map(|prev_token| Gap { token: prev_token });
377 let reached_start = state.thread_linked_chunk_mut().push_backwards_pagination_events(
378 prev_gap_id,
379 new_gap,
380 &topo_ordered_events,
381 );
382
383 state.state.propagate_changes(&state.store).await?;
385
386 let receipt_event = None;
394
395 state.post_process_upserted_events(topo_ordered_events.iter(), receipt_event).await?;
397
398 let timeline_event_diffs = state.thread_linked_chunk_mut().updates_as_vector_diffs();
400
401 if !timeline_event_diffs.is_empty() {
402 state.update_sender.send(
403 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
404 diffs: timeline_event_diffs,
405 origin: EventsOrigin::Pagination,
406 }),
407 Some(RoomEventCacheGenericUpdate { room_id: state.room_id.clone() }),
408 );
409 }
410
411 Ok(Some(BackPaginationOutcome { reached_start, events }))
412 }
413}
414
415impl fmt::Debug for ThreadPagination {
416 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
417 formatter.debug_tuple("ThreadPagination").finish_non_exhaustive()
418 }
419}