use std::{fmt::Formatter, future::Future, sync::Mutex};
use matrix_sdk_base::{SendOutsideWasm, SyncOutsideWasm, deserialized_responses::TimelineEvent};
use ruma::{EventId, OwnedEventId, UInt, api::Direction};
use crate::{
Error, Room,
paginators::{PaginationResult, PaginationToken, PaginationTokens, PaginatorError},
room::{IncludeRelations, Relations, RelationsOptions},
};
pub trait PaginableThread: SendOutsideWasm + SyncOutsideWasm {
fn relations(
&self,
thread_root: OwnedEventId,
opts: RelationsOptions,
) -> impl Future<Output = Result<Relations, Error>> + SendOutsideWasm;
fn load_event(
&self,
event_id: &OwnedEventId,
) -> impl Future<Output = Result<TimelineEvent, Error>> + SendOutsideWasm;
}
impl PaginableThread for Room {
async fn relations(
&self,
thread_root: OwnedEventId,
opts: RelationsOptions,
) -> Result<Relations, Error> {
self.relations(thread_root, opts).await
}
async fn load_event(&self, event_id: &OwnedEventId) -> Result<TimelineEvent, Error> {
self.event(event_id, None).await
}
}
pub struct ThreadedEventsLoader<P: PaginableThread> {
room: P,
root_event_id: OwnedEventId,
tokens: Mutex<PaginationTokens>,
}
impl<P: PaginableThread> ThreadedEventsLoader<P> {
pub fn new(room: P, root_event_id: OwnedEventId, tokens: PaginationTokens) -> Self {
Self { room, root_event_id, tokens: Mutex::new(tokens) }
}
pub async fn paginate_backwards(
&self,
num_events: UInt,
) -> Result<PaginationResult, PaginatorError> {
let token = {
let token = &self.tokens.lock().unwrap().previous;
match token {
PaginationToken::None => None,
PaginationToken::HasMore(token) => Some(token.clone()),
PaginationToken::HitEnd => {
return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });
}
}
};
let options = RelationsOptions {
from: token,
dir: Direction::Backward,
limit: Some(num_events),
include_relations: IncludeRelations::AllRelations,
recurse: true,
};
let mut result = self
.room
.relations(self.root_event_id.to_owned(), options)
.await
.map_err(|error| PaginatorError::SdkError(Box::new(error)))?;
let hit_end_of_timeline = result.next_batch_token.is_none();
{
let mut tokens = self.tokens.lock().unwrap();
tokens.previous = match result.next_batch_token {
Some(val) => PaginationToken::HasMore(val),
None => PaginationToken::HitEnd,
};
}
if hit_end_of_timeline {
let root_event = self
.room
.load_event(&self.root_event_id)
.await
.map_err(|err| PaginatorError::SdkError(Box::new(err)))?;
result.chunk.push(root_event);
}
Ok(PaginationResult { events: result.chunk, hit_end_of_timeline })
}
pub async fn paginate_forwards(
&self,
num_events: UInt,
) -> Result<PaginationResult, PaginatorError> {
let token = {
let token = &self.tokens.lock().unwrap().next;
match token {
PaginationToken::None => None,
PaginationToken::HasMore(token) => Some(token.clone()),
PaginationToken::HitEnd => {
return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });
}
}
};
let options = RelationsOptions {
from: token,
dir: Direction::Forward,
limit: Some(num_events),
include_relations: IncludeRelations::AllRelations,
recurse: true,
};
let result = self
.room
.relations(self.root_event_id.to_owned(), options)
.await
.map_err(|error| PaginatorError::SdkError(Box::new(error)))?;
let hit_end_of_timeline = result.next_batch_token.is_none();
{
let mut tokens = self.tokens.lock().unwrap();
tokens.next = match result.next_batch_token {
Some(val) => PaginationToken::HasMore(val),
None => PaginationToken::HitEnd,
};
}
Ok(PaginationResult { events: result.chunk, hit_end_of_timeline })
}
pub fn thread_root_event_id(&self) -> &EventId {
&self.root_event_id
}
}
#[cfg(not(tarpaulin_include))]
impl<P: PaginableThread> std::fmt::Debug for ThreadedEventsLoader<P> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ThreadedEventsLoader").finish()
}
}