use std::future::Future;
use eyeball::Subscriber;
use indexmap::IndexMap;
use matrix_sdk::{
Result, Room, SendOutsideWasm,
deserialized_responses::TimelineEvent,
paginators::{PaginableRoom, thread::PaginableThread},
};
use matrix_sdk_base::{RoomInfo, crypto::types::events::CryptoContextInfo};
use ruma::{
EventId, OwnedEventId, OwnedTransactionId, OwnedUserId, UserId,
events::{
AnyMessageLikeEventContent,
fully_read::FullyReadEventContent,
receipt::{Receipt, ReceiptThread, ReceiptType},
},
room_version_rules::RoomVersionRules,
};
use tracing::error;
use super::{Profile, RedactError, TimelineBuilder};
use crate::timeline::{
self, Timeline, TimelineReadReceiptTracking, latest_event::LatestEventValue,
thread_list_service::ThreadListService,
};
pub trait RoomExt {
fn timeline(&self)
-> impl Future<Output = Result<Timeline, timeline::Error>> + SendOutsideWasm;
fn timeline_builder(&self) -> TimelineBuilder;
fn latest_event(&self) -> impl Future<Output = LatestEventValue>;
fn thread_list_service(&self) -> ThreadListService;
}
impl RoomExt for Room {
async fn timeline(&self) -> Result<Timeline, timeline::Error> {
self.timeline_builder().build().await
}
fn timeline_builder(&self) -> TimelineBuilder {
TimelineBuilder::new(self)
.track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
}
async fn latest_event(&self) -> LatestEventValue {
LatestEventValue::from_base_latest_event_value(
(**self).latest_event(),
self,
&self.client(),
)
.await
}
fn thread_list_service(&self) -> ThreadListService {
ThreadListService::new(self.clone())
}
}
pub(super) trait RoomDataProvider:
Clone + PaginableRoom + PaginableThread + 'static
{
fn own_user_id(&self) -> &UserId;
fn room_version_rules(&self) -> RoomVersionRules;
fn crypto_context_info(&self)
-> impl Future<Output = CryptoContextInfo> + SendOutsideWasm + '_;
fn profile_from_user_id<'a>(
&'a self,
user_id: &'a UserId,
) -> impl Future<Output = Option<Profile>> + SendOutsideWasm + 'a;
fn load_user_receipt<'a>(
&'a self,
receipt_type: ReceiptType,
thread: ReceiptThread,
user_id: &'a UserId,
) -> impl Future<Output = Option<(OwnedEventId, Receipt)>> + SendOutsideWasm + 'a;
fn load_event_receipts<'a>(
&'a self,
event_id: &'a EventId,
receipt_thread: ReceiptThread,
) -> impl Future<Output = IndexMap<OwnedUserId, Receipt>> + SendOutsideWasm + 'a;
fn load_fully_read_marker(&self) -> impl Future<Output = Option<OwnedEventId>> + '_;
fn send(
&self,
content: AnyMessageLikeEventContent,
) -> impl Future<Output = Result<(), super::Error>> + SendOutsideWasm + '_;
fn redact<'a>(
&'a self,
event_id: &'a EventId,
reason: Option<&'a str>,
transaction_id: Option<OwnedTransactionId>,
) -> impl Future<Output = Result<(), super::Error>> + SendOutsideWasm + 'a;
fn room_info(&self) -> Subscriber<RoomInfo>;
fn load_event<'a>(
&'a self,
event_id: &'a EventId,
) -> impl Future<Output = Result<TimelineEvent>> + SendOutsideWasm + 'a;
}
impl RoomDataProvider for Room {
fn own_user_id(&self) -> &UserId {
(**self).own_user_id()
}
fn room_version_rules(&self) -> RoomVersionRules {
(**self).clone_info().room_version_rules_or_default()
}
async fn crypto_context_info(&self) -> CryptoContextInfo {
self.crypto_context_info().await
}
async fn profile_from_user_id<'a>(&'a self, user_id: &'a UserId) -> Option<Profile> {
Profile::load(self, user_id).await
}
async fn load_user_receipt<'a>(
&'a self,
receipt_type: ReceiptType,
thread: ReceiptThread,
user_id: &'a UserId,
) -> Option<(OwnedEventId, Receipt)> {
match self.load_user_receipt(receipt_type.clone(), thread.clone(), user_id).await {
Ok(receipt) => receipt,
Err(e) => {
error!(
?receipt_type,
?thread,
?user_id,
"Failed to get read receipt for user: {e}"
);
None
}
}
}
async fn load_event_receipts<'a>(
&'a self,
event_id: &'a EventId,
receipt_thread: ReceiptThread,
) -> IndexMap<OwnedUserId, Receipt> {
match self.load_event_receipts(ReceiptType::Read, receipt_thread.clone(), event_id).await {
Ok(receipts) => receipts.into_iter().collect(),
Err(e) => {
error!(?event_id, ?receipt_thread, "Failed to get read receipts for event: {e}");
IndexMap::new()
}
}
}
async fn load_fully_read_marker(&self) -> Option<OwnedEventId> {
match self.account_data_static::<FullyReadEventContent>().await {
Ok(Some(fully_read)) => match fully_read.deserialize() {
Ok(fully_read) => Some(fully_read.content.event_id),
Err(e) => {
error!("Failed to deserialize fully-read account data: {e}");
None
}
},
Err(e) => {
error!("Failed to get fully-read account data from the store: {e}");
None
}
_ => None,
}
}
async fn send(&self, content: AnyMessageLikeEventContent) -> Result<(), super::Error> {
let _ = self.send_queue().send(content).await?;
Ok(())
}
async fn redact<'a>(
&'a self,
event_id: &'a EventId,
reason: Option<&'a str>,
transaction_id: Option<OwnedTransactionId>,
) -> Result<(), super::Error> {
let _ = self
.redact(event_id, reason, transaction_id)
.await
.map_err(RedactError::HttpError)
.map_err(super::Error::RedactError)?;
Ok(())
}
fn room_info(&self) -> Subscriber<RoomInfo> {
self.subscribe_info()
}
async fn load_event<'a>(&'a self, event_id: &'a EventId) -> Result<TimelineEvent> {
self.load_or_fetch_event(event_id, None).await
}
}