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};
40use crate::room::{IncludeRelations, RelationsOptions};
41
42#[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(
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);
120 let new_first_chunk = match state
121 .store
122 .load_previous_chunk(linked_chunk_id, prev_first_chunk.identifier())
123 .await
124 {
125 Ok(Some(new_first_chunk)) => {
126 new_first_chunk
128 }
129
130 Ok(None) => {
131 if let Some((_pos, first_event)) = state.thread_linked_chunk().events().next()
136 && self.cache.thread_id
137 == first_event.event_id().expect("Stored events all have an ID")
138 {
139 trace!("thread chunk is fully loaded and non-empty: reached_start=true");
140
141 return Ok(LoadMoreEventsBackwardsOutcome::StartOfTimeline);
142 }
143
144 return Ok(LoadMoreEventsBackwardsOutcome::Gap {
146 prev_token: None,
147 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
148 });
149 }
150
151 Err(err) => {
152 error!("error when loading the previous chunk of a linked chunk: {err}");
153
154 state
156 .store
157 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
158 .await?;
159
160 return Err(err.into());
162 }
163 };
164
165 let chunk_content = new_first_chunk.content.clone();
166
167 let reached_start = new_first_chunk.previous.is_none();
173
174 if let Err(err) = state.thread_linked_chunk_mut().insert_new_chunk_as_first(new_first_chunk)
175 {
176 error!("error when inserting the previous chunk into its linked chunk: {err}");
177
178 state
180 .store
181 .handle_linked_chunk_updates(
182 LinkedChunkId::Thread(&state.room_id, &state.thread_id),
183 vec![Update::Clear],
184 )
185 .await?;
186
187 return Err(err.into());
189 }
190
191 let _ = state.thread_linked_chunk_mut().store_updates().take();
194
195 let timeline_event_diffs = state.thread_linked_chunk_mut().updates_as_vector_diffs();
197
198 Ok(match chunk_content {
199 ChunkContent::Gap(gap) => {
200 trace!("reloaded chunk from disk (gap)");
201
202 LoadMoreEventsBackwardsOutcome::Gap {
203 prev_token: Some(gap.token),
204 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
205 }
206 }
207
208 ChunkContent::Items(events) => {
209 trace!(?reached_start, "reloaded chunk from disk ({} items)", events.len());
210
211 LoadMoreEventsBackwardsOutcome::Events {
212 events,
213 timeline_event_diffs,
214 reached_start,
215 }
216 }
217 })
218 }
219
220 async fn mark_has_waited_for_initial_prev_token(&self) -> Result<()> {
221 *self.cache.state.write().await?.waited_for_initial_prev_token_mut() = true;
222
223 Ok(())
224 }
225
226 async fn wait_for_prev_token(&self) {
227 self.cache.pagination_batch_token_notifier.notified().await
228 }
229
230 async fn paginate_backwards_with_network(
231 &self,
232 batch_size: u16,
233 prev_token: &Option<String>,
234 ) -> Result<Option<(Vec<Event>, Option<String>)>> {
235 let Some(room) = self.cache.weak_room.get() else {
236 return Ok(None);
238 };
239
240 let options = RelationsOptions {
241 from: prev_token.clone(),
242 dir: Direction::Backward,
243 limit: Some(batch_size.into()),
244 include_relations: IncludeRelations::AllRelations,
245 recurse: true,
246 };
247
248 let response = room
249 .relations(self.cache.thread_id.clone(), options)
250 .await
251 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
252
253 Ok(Some((response.chunk, response.next_batch_token)))
254 }
255
256 async fn conclude_backwards_pagination_from_disk(
257 &self,
258 events: Vec<Event>,
259 timeline_event_diffs: Vec<VectorDiff<Event>>,
260 reached_start: bool,
261 ) -> BackPaginationOutcome {
262 if !timeline_event_diffs.is_empty() {
263 self.cache.update_sender.send(
264 TimelineVectorDiffs { diffs: timeline_event_diffs, origin: EventsOrigin::Cache },
265 Some(RoomEventCacheGenericUpdate { room_id: self.cache.room_id.clone() }),
266 );
267 }
268
269 BackPaginationOutcome {
270 reached_start,
271 events: events.into_iter().rev().collect(),
274 }
275 }
276
277 async fn conclude_backwards_pagination_from_network(
278 &self,
279 mut events: Vec<Event>,
280 prev_token: Option<String>,
281 mut new_token: Option<String>,
282 ) -> Result<Option<BackPaginationOutcome>> {
283 let Some(room) = self.cache.weak_room.get() else {
284 return Ok(None);
286 };
287
288 if new_token.is_none() {
298 events.push(
299 room.load_or_fetch_event(&self.cache.thread_id, None)
300 .await
301 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?,
302 );
303 }
304
305 let mut state = self.cache.state.write().await?;
306
307 let prev_gap_id = if let Some(token) = prev_token {
310 let gap_chunk_id = state.thread_linked_chunk().chunk_identifier(|chunk| {
312 matches!(chunk.content(), ChunkContent::Gap(Gap { token: prev_token }) if *prev_token == token)
313 });
314
315 if gap_chunk_id.is_none() {
316 return Ok(None);
321 }
322
323 gap_chunk_id
324 } else {
325 None
326 };
327
328 let DeduplicationOutcome {
329 all_events: mut events,
330 in_memory_duplicated_event_ids,
331 in_store_duplicated_event_ids,
332 non_empty_all_duplicates: all_duplicates,
333 } = filter_duplicate_events(
334 &state.own_user_id,
335 &state.store,
336 LinkedChunkId::Thread(&state.room_id, &state.thread_id),
337 state.thread_linked_chunk(),
338 events,
339 )
340 .await?;
341
342 if !all_duplicates {
356 state
358 .remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids)
359 .await?;
360 } else {
361 events.clear();
363 new_token = None;
366 }
367
368 let topo_ordered_events = events.iter().rev().cloned().collect::<Vec<_>>();
371
372 let new_gap = new_token.map(|prev_token| Gap { token: prev_token });
373 let reached_start = state.thread_linked_chunk_mut().push_backwards_pagination_events(
374 prev_gap_id,
375 new_gap,
376 &topo_ordered_events,
377 );
378
379 state.state.propagate_changes(&state.store).await?;
380
381 let timeline_event_diffs = state.thread_linked_chunk_mut().updates_as_vector_diffs();
383
384 if !timeline_event_diffs.is_empty() {
385 state.update_sender.send(
386 TimelineVectorDiffs {
387 diffs: timeline_event_diffs,
388 origin: EventsOrigin::Pagination,
389 },
390 Some(RoomEventCacheGenericUpdate { room_id: state.room_id.clone() }),
391 );
392 }
393
394 Ok(Some(BackPaginationOutcome { reached_start, events }))
395 }
396}
397
398impl fmt::Debug for ThreadPagination {
399 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
400 formatter.debug_tuple("ThreadPagination").finish_non_exhaustive()
401 }
402}