matrix_sdk/event_cache/caches/room/
pagination.rs1use std::{
21 fmt,
22 pin::Pin,
23 sync::Arc,
24 task::{Context, Poll},
25};
26
27use eyeball::{SharedObservable, Subscriber};
28use eyeball_im::VectorDiff;
29use futures_core::{Stream, ready};
30use matrix_sdk_base::{
31 event_cache::{Event, Gap},
32 linked_chunk::{ChunkContent, LinkedChunkId, Update},
33};
34use pin_project_lite::pin_project;
35use ruma::api::Direction;
36use tracing::{error, trace};
37
38pub use super::super::pagination::PaginationStatus;
39use super::{
40 super::{
41 super::{
42 EventCacheError, EventsOrigin, Result, RoomEventCacheGenericUpdate,
43 deduplicator::{DeduplicationOutcome, filter_duplicate_events},
44 },
45 TimelineVectorDiffs,
46 pagination::{
47 BackPaginationOutcome, LoadMoreEventsBackwardsOutcome, PaginatedCache, Pagination,
48 },
49 },
50 RoomEventCacheInner, RoomEventCacheUpdate,
51};
52use crate::{event_cache::caches::pagination::SharedPaginationStatus, room::MessagesOptions};
53
54pin_project! {
55 pub struct PaginationStatusSubscriber {
60 #[pin]
61 subscriber: Subscriber<SharedPaginationStatus>,
62 }
63}
64
65#[cfg(not(tarpaulin_include))]
66impl fmt::Debug for PaginationStatusSubscriber {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 f.debug_struct("PaginationStatusSubscriber").finish_non_exhaustive()
69 }
70}
71
72impl PaginationStatusSubscriber {
73 fn map(from: SharedPaginationStatus) -> PaginationStatus {
74 match from {
75 SharedPaginationStatus::Idle { hit_timeline_start } => {
76 PaginationStatus::Idle { hit_timeline_start }
77 }
78 SharedPaginationStatus::Paginating { .. } => PaginationStatus::Paginating,
79 }
80 }
81
82 pub fn get(&self) -> PaginationStatus {
83 Self::map(self.subscriber.get())
84 }
85
86 pub async fn next(&mut self) -> Option<PaginationStatus> {
87 self.subscriber.next().await.map(Self::map)
88 }
89
90 pub fn next_now(&mut self) -> PaginationStatus {
91 Self::map(self.subscriber.next_now())
92 }
93}
94
95impl Stream for PaginationStatusSubscriber {
96 type Item = PaginationStatus;
97
98 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
99 Poll::Ready(ready!(self.project().subscriber.as_mut().poll_next(cx)).map(Self::map))
100 }
101}
102
103#[allow(missing_debug_implementations)]
110#[derive(Clone)]
111pub struct RoomPagination(Pagination<Arc<RoomEventCacheInner>>);
112
113impl RoomPagination {
114 pub(super) fn new(cache: Arc<RoomEventCacheInner>) -> Self {
116 Self(Pagination::new(cache))
117 }
118
119 pub async fn run_backwards_until(
130 &self,
131 num_requested_events: u16,
132 ) -> Result<BackPaginationOutcome> {
133 self.0.run_backwards_until(num_requested_events).await
134 }
135
136 pub async fn run_backwards_once(&self, batch_size: u16) -> Result<BackPaginationOutcome> {
141 self.0.run_backwards_once(batch_size).await
142 }
143
144 pub fn status(&self) -> PaginationStatusSubscriber {
146 PaginationStatusSubscriber { subscriber: self.0.cache.status().subscribe() }
147 }
148
149 #[cfg(test)]
150 pub(super) async fn load_more_events_backwards(
151 &self,
152 ) -> Result<LoadMoreEventsBackwardsOutcome> {
153 self.0.cache.load_more_events_backwards().await
154 }
155}
156
157impl PaginatedCache for Arc<RoomEventCacheInner> {
158 fn status(&self) -> &SharedObservable<SharedPaginationStatus> {
159 &self.shared_pagination_status
160 }
161
162 async fn load_more_events_backwards(&self) -> Result<LoadMoreEventsBackwardsOutcome> {
163 let mut state = self.state.write().await?;
164
165 if let Some(prev_token) = state.room_linked_chunk().rgap().map(|gap| gap.token) {
168 return Ok(LoadMoreEventsBackwardsOutcome::Gap {
169 prev_token: Some(prev_token),
170 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
171 });
172 }
173
174 let prev_first_chunk = state.room_linked_chunk().first_chunk();
175
176 let linked_chunk_id = LinkedChunkId::Room(&state.state.room_id);
178 let new_first_chunk = match state
179 .store
180 .load_previous_chunk(linked_chunk_id, prev_first_chunk.identifier())
181 .await
182 {
183 Ok(Some(new_first_chunk)) => {
184 new_first_chunk
186 }
187
188 Ok(None) => {
189 if state.room_linked_chunk().events().next().is_some() {
194 trace!("chunk is fully loaded and non-empty: reached_start=true");
197 return Ok(LoadMoreEventsBackwardsOutcome::StartOfTimeline);
198 }
199
200 return Ok(LoadMoreEventsBackwardsOutcome::Gap {
202 prev_token: None,
203 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
204 });
205 }
206
207 Err(err) => {
208 error!("error when loading the previous chunk of a linked chunk: {err}");
209
210 state
212 .store
213 .handle_linked_chunk_updates(linked_chunk_id, vec![Update::Clear])
214 .await?;
215
216 return Err(err.into());
218 }
219 };
220
221 let chunk_content = new_first_chunk.content.clone();
222
223 let reached_start = new_first_chunk.previous.is_none();
229
230 if let Err(err) = state.room_linked_chunk_mut().insert_new_chunk_as_first(new_first_chunk) {
231 error!("error when inserting the previous chunk into its linked chunk: {err}");
232
233 state
235 .store
236 .handle_linked_chunk_updates(
237 LinkedChunkId::Room(&state.state.room_id),
238 vec![Update::Clear],
239 )
240 .await?;
241
242 return Err(err.into());
244 }
245
246 let _ = state.room_linked_chunk_mut().store_updates().take();
249
250 let timeline_event_diffs = state.room_linked_chunk_mut().updates_as_vector_diffs();
252
253 Ok(match chunk_content {
254 ChunkContent::Gap(gap) => {
255 trace!("reloaded chunk from disk (gap)");
256
257 LoadMoreEventsBackwardsOutcome::Gap {
258 prev_token: Some(gap.token),
259 waited_for_initial_prev_token: state.waited_for_initial_prev_token(),
260 }
261 }
262
263 ChunkContent::Items(events) => {
264 trace!(?reached_start, "reloaded chunk from disk ({} items)", events.len());
265
266 LoadMoreEventsBackwardsOutcome::Events {
267 events,
268 timeline_event_diffs,
269 reached_start,
270 }
271 }
272 })
273 }
274
275 async fn mark_has_waited_for_initial_prev_token(&self) -> Result<()> {
276 *self.state.write().await?.waited_for_initial_prev_token_mut() = true;
277
278 Ok(())
279 }
280
281 async fn wait_for_prev_token(&self) {
282 self.pagination_batch_token_notifier.notified().await
283 }
284
285 async fn paginate_backwards_with_network(
286 &self,
287 batch_size: u16,
288 prev_token: &Option<String>,
289 ) -> Result<Option<(Vec<Event>, Option<String>)>> {
290 let Some(room) = self.weak_room.get() else {
291 return Ok(None);
293 };
294
295 let mut options = MessagesOptions::new(Direction::Backward).from(prev_token.as_deref());
296 options.limit = batch_size.into();
297
298 let response = room
299 .messages(options)
300 .await
301 .map_err(|err| EventCacheError::PaginationError(Arc::new(err)))?;
302
303 Ok(Some((response.chunk, response.end)))
304 }
305
306 async fn conclude_backwards_pagination_from_disk(
307 &self,
308 events: Vec<Event>,
309 timeline_event_diffs: Vec<VectorDiff<Event>>,
310 reached_start: bool,
311 ) -> BackPaginationOutcome {
312 if !timeline_event_diffs.is_empty() {
313 self.update_sender.send(
314 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
315 diffs: timeline_event_diffs,
316 origin: EventsOrigin::Cache,
317 }),
318 Some(RoomEventCacheGenericUpdate { room_id: self.room_id.clone() }),
319 );
320 }
321
322 BackPaginationOutcome {
323 reached_start,
324 events: events.into_iter().rev().collect(),
327 }
328 }
329
330 async fn conclude_backwards_pagination_from_network(
331 &self,
332 events: Vec<Event>,
333 prev_token: Option<String>,
334 mut new_token: Option<String>,
335 ) -> Result<Option<BackPaginationOutcome>> {
336 let mut state = self.state.write().await?;
337
338 let prev_gap_id = if let Some(token) = prev_token {
341 let gap_chunk_id = state.room_linked_chunk().chunk_identifier(|chunk| {
343 matches!(chunk.content(), ChunkContent::Gap(Gap { token: prev_token }) if *prev_token == token)
344 });
345
346 if gap_chunk_id.is_none() {
347 return Ok(None);
352 }
353
354 gap_chunk_id
355 } else {
356 None
357 };
358
359 let DeduplicationOutcome {
360 all_events: mut events,
361 in_memory_duplicated_event_ids,
362 in_store_duplicated_event_ids,
363 non_empty_all_duplicates: all_duplicates,
364 } = filter_duplicate_events(
365 &state.state.own_user_id,
366 &state.store,
367 LinkedChunkId::Room(&state.state.room_id),
368 state.room_linked_chunk(),
369 events,
370 )
371 .await?;
372
373 if !all_duplicates {
387 state
389 .remove_events(in_memory_duplicated_event_ids, in_store_duplicated_event_ids)
390 .await?;
391 } else {
392 events.clear();
394 new_token = None;
397 }
398
399 let topo_ordered_events = events.iter().rev().cloned().collect::<Vec<_>>();
402
403 let new_gap = new_token.as_ref().map(|prev_token| Gap { token: prev_token.clone() });
404 let reached_start = state.room_linked_chunk_mut().push_backwards_pagination_events(
405 prev_gap_id,
406 new_gap,
407 &topo_ordered_events,
408 );
409
410 let receipt_event = None;
418
419 state.post_process_new_events(topo_ordered_events, receipt_event).await?;
421
422 let timeline_event_diffs = state.room_linked_chunk_mut().updates_as_vector_diffs();
423
424 if !timeline_event_diffs.is_empty() {
425 self.update_sender.send(
426 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
427 diffs: timeline_event_diffs,
428 origin: EventsOrigin::Pagination,
429 }),
430 Some(RoomEventCacheGenericUpdate { room_id: self.room_id.clone() }),
431 );
432 }
433
434 Ok(Some(BackPaginationOutcome { events, reached_start }))
435 }
436}
437
438impl fmt::Debug for RoomPagination {
439 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
440 formatter.debug_tuple("RoomPagination").finish_non_exhaustive()
441 }
442}