matrix_sdk_ui/timeline/
threaded_events_loader.rs

1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{fmt::Formatter, sync::Mutex};
16
17use matrix_sdk::{
18    event_cache::{
19        paginator::{PaginationResult, PaginatorError},
20        PaginationToken,
21    },
22    room::{IncludeRelations, RelationsOptions},
23};
24use ruma::{api::Direction, OwnedEventId, UInt};
25
26use super::traits::RoomDataProvider;
27
28pub struct ThreadedEventsLoader<P: RoomDataProvider> {
29    room: P,
30    root_event_id: OwnedEventId,
31    token: Mutex<PaginationToken>,
32}
33
34impl<P: RoomDataProvider> ThreadedEventsLoader<P> {
35    /// Create a new [`Paginator`], given a room implementation.
36    pub fn new(room: P, root_event_id: OwnedEventId) -> Self {
37        Self { room, root_event_id, token: Mutex::new(None.into()) }
38    }
39
40    pub async fn paginate_backwards(
41        &self,
42        num_events: UInt,
43    ) -> Result<PaginationResult, PaginatorError> {
44        let token = {
45            let token = self.token.lock().unwrap();
46
47            match &*token {
48                PaginationToken::None => None,
49                PaginationToken::HasMore(token) => Some(token.clone()),
50                PaginationToken::HitEnd => {
51                    return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });
52                }
53            }
54        };
55
56        let options = RelationsOptions {
57            from: token,
58            dir: Direction::Backward,
59            limit: Some(num_events),
60            include_relations: IncludeRelations::AllRelations,
61            recurse: true,
62        };
63
64        let mut result = self
65            .room
66            .relations(self.root_event_id.to_owned(), options)
67            .await
68            .map_err(|error| PaginatorError::SdkError(Box::new(error)))?;
69
70        let hit_end_of_timeline = result.next_batch_token.is_none();
71
72        // Update the stored tokens
73        {
74            let mut token = self.token.lock().unwrap();
75
76            *token = match result.next_batch_token {
77                Some(val) => PaginationToken::HasMore(val),
78                None => PaginationToken::HitEnd,
79            };
80        }
81
82        // Finally insert the thread root if at the end of the timeline going backwards
83        if hit_end_of_timeline {
84            let root_event =
85                self.room.load_event_with_relations(&self.root_event_id, None, None).await?.0;
86
87            result.chunk.push(root_event);
88        }
89
90        Ok(PaginationResult { events: result.chunk, hit_end_of_timeline })
91    }
92}
93
94#[cfg(not(tarpaulin_include))]
95impl<P: RoomDataProvider> std::fmt::Debug for ThreadedEventsLoader<P> {
96    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
97        f.debug_struct("ThreadedEventsLoader").finish()
98    }
99}