diff --git a/bindings/matrix-sdk-ffi/src/sliding_sync.rs b/bindings/matrix-sdk-ffi/src/sliding_sync.rs index 1fdce3db7..e41f95c4a 100644 --- a/bindings/matrix-sdk-ffi/src/sliding_sync.rs +++ b/bindings/matrix-sdk-ffi/src/sliding_sync.rs @@ -129,7 +129,7 @@ impl SlidingSyncRoom { #[allow(clippy::significant_drop_in_scrutinee)] pub fn latest_room_message(&self) -> Option> { - let item = self.inner.latest_event()?; + let item = RUNTIME.block_on(self.inner.latest_event())?; Some(Arc::new(EventTimelineItem(item))) } } diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index d7ea37869..ea5b42a4c 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -270,7 +270,7 @@ impl Common { /// independent events. #[cfg(feature = "experimental-timeline")] pub async fn timeline(&self) -> Timeline { - Timeline::new(self).await.with_fully_read_tracking().await + Timeline::new(self).with_fully_read_tracking().await } /// Fetch the event with the given `EventId` in this room. diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index ed6350fef..bfceb083a 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -39,7 +39,7 @@ use ruma::{ use tracing::{debug, error, field::debug, info, instrument, trace, warn}; use super::{ - event_item::{BundledReactions, Sticker, TimelineDetails}, + event_item::{BundledReactions, Profile, Sticker, TimelineDetails}, find_event_by_id, find_event_by_txn_id, find_read_marker, EventTimelineItem, Message, TimelineInnerMetadata, TimelineItem, TimelineItemContent, TimelineKey, VirtualTimelineItem, }; @@ -84,6 +84,7 @@ impl Flow { pub(super) struct TimelineEventMetadata { pub(super) sender: OwnedUserId, + pub(super) sender_profile: Profile, pub(super) is_own_event: bool, pub(super) relations: BundledRelations, pub(super) encryption_info: Option, @@ -457,6 +458,7 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { key: self.flow.to_key(), event_id: None, sender: self.meta.sender.to_owned(), + sender_profile: self.meta.sender_profile.clone(), content, reactions, timestamp: self.flow.timestamp(), diff --git a/crates/matrix-sdk/src/room/timeline/event_item.rs b/crates/matrix-sdk/src/room/timeline/event_item.rs index 4b30deb57..626188f7a 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item.rs +++ b/crates/matrix-sdk/src/room/timeline/event_item.rs @@ -27,8 +27,8 @@ use ruma::{ AnySyncTimelineEvent, MessageLikeEventType, StateEventType, }, serde::Raw, - uint, EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedTransactionId, - OwnedUserId, TransactionId, UInt, UserId, + uint, EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedMxcUri, + OwnedTransactionId, OwnedUserId, TransactionId, UInt, UserId, }; /// An item in the timeline that represents at least one event. @@ -44,6 +44,7 @@ pub struct EventTimelineItem { // response. pub(super) event_id: Option, pub(super) sender: OwnedUserId, + pub(super) sender_profile: Profile, pub(super) content: TimelineItemContent, pub(super) reactions: BundledReactions, pub(super) timestamp: MilliSecondsSinceUnixEpoch, @@ -96,6 +97,11 @@ impl EventTimelineItem { &self.sender } + /// Get the profile of the sender. + pub fn sender_profile(&self) -> &Profile { + &self.sender_profile + } + /// Get the content of this item. pub fn content(&self) -> &TimelineItemContent { &self.content @@ -195,6 +201,15 @@ impl PartialEq for TimelineKey { } } +/// The display name and avatar URL of a room member. +#[derive(Clone, Debug)] +pub struct Profile { + /// The display name, if set. + pub display_name: Option, + /// The avatar URL, if set. + pub avatar_url: Option, +} + /// Some details of an [`EventTimelineItem`] that may require server requests /// other than just the regular /// [`sync_events`][ruma::api::client::sync::sync_events]. diff --git a/crates/matrix-sdk/src/room/timeline/inner.rs b/crates/matrix-sdk/src/room/timeline/inner.rs index 3fed9a421..f4ec05e67 100644 --- a/crates/matrix-sdk/src/room/timeline/inner.rs +++ b/crates/matrix-sdk/src/room/timeline/inner.rs @@ -3,6 +3,7 @@ use std::{ sync::Arc, }; +use async_trait::async_trait; use futures_signals::signal_vec::{MutableVec, MutableVecLockRef, SignalVec}; use matrix_sdk_base::{ crypto::OlmMachine, @@ -27,7 +28,7 @@ use super::{ update_read_marker, Flow, HandleEventResult, TimelineEventHandler, TimelineEventKind, TimelineEventMetadata, TimelineItemPosition, }, - find_event_by_txn_id, TimelineItem, TimelineKey, + find_event_by_txn_id, Profile, TimelineItem, TimelineKey, }; use crate::{events::SyncTimelineEventWithoutContent, room}; @@ -60,7 +61,7 @@ impl TimelineInner

{ self.items.signal_vec_cloned() } - pub(super) fn add_initial_events(&mut self, events: Vec) { + pub(super) async fn add_initial_events(&mut self, events: Vec) { if events.is_empty() { return; } @@ -77,7 +78,8 @@ impl TimelineInner

{ &self.items, timeline_meta, &self.profile_provider, - ); + ) + .await; } } @@ -94,7 +96,8 @@ impl TimelineInner

{ &self.items, &mut timeline_meta, &self.profile_provider, - ); + ) + .await; } pub(super) async fn handle_local_event( @@ -102,8 +105,11 @@ impl TimelineInner

{ txn_id: OwnedTransactionId, content: AnyMessageLikeEventContent, ) { + let sender = self.profile_provider.own_user_id().to_owned(); + let sender_profile = self.profile_provider.profile(&sender).await; let event_meta = TimelineEventMetadata { - sender: self.profile_provider.own_user_id().to_owned(), + sender, + sender_profile, is_own_event: true, relations: Default::default(), // FIXME: Should we supply something here for encrypted rooms? @@ -135,6 +141,7 @@ impl TimelineInner

{ &mut metadata_lock, &self.profile_provider, ) + .await } pub(super) fn add_event_id(&self, txn_id: &TransactionId, event_id: OwnedEventId) { @@ -279,7 +286,8 @@ impl TimelineInner

{ &self.items, &mut metadata_lock, &self.profile_provider, - ); + ) + .await; } } } @@ -290,20 +298,37 @@ impl TimelineInner { } } +#[async_trait] pub(super) trait ProfileProvider { fn own_user_id(&self) -> &UserId; + async fn profile(&self, user_id: &UserId) -> Profile; } +#[async_trait] impl ProfileProvider for room::Common { fn own_user_id(&self) -> &UserId { (**self).own_user_id() } + + async fn profile(&self, user_id: &UserId) -> Profile { + match self.get_member_no_sync(user_id).await { + Ok(Some(member)) => Profile { + display_name: member.display_name().map(ToOwned::to_owned), + avatar_url: member.avatar_url().map(ToOwned::to_owned), + }, + Ok(None) => Profile { display_name: None, avatar_url: None }, + Err(e) => { + error!(%user_id, "Failed to getch room member information: {e}"); + Profile { display_name: None, avatar_url: None } + } + } + } } /// Handle a remote event. /// /// Returns the number of timeline updates that were made. -fn handle_remote_event( +async fn handle_remote_event( raw: Raw, encryption_info: Option, position: TimelineItemPosition, @@ -340,7 +365,9 @@ fn handle_remote_event( }; let is_own_event = sender == profile_provider.own_user_id(); - let event_meta = TimelineEventMetadata { sender, is_own_event, relations, encryption_info }; + let sender_profile = profile_provider.profile(&sender).await; + let event_meta = + TimelineEventMetadata { sender, sender_profile, is_own_event, relations, encryption_info }; let flow = Flow::Remote { event_id, origin_server_ts, raw_event: raw, txn_id, position }; let mut timeline_items = timeline_items.lock_mut(); diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index dd7b4edec..c59aec59e 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -50,8 +50,8 @@ mod virtual_item; pub use self::{ event_item::{ - EncryptedMessage, EventTimelineItem, Message, ReactionDetails, Sticker, TimelineDetails, - TimelineItemContent, TimelineKey, + EncryptedMessage, EventTimelineItem, Message, Profile, ReactionDetails, Sticker, + TimelineDetails, TimelineItemContent, TimelineKey, }, pagination::{PaginationOptions, PaginationOutcome}, virtual_item::VirtualTimelineItem, @@ -83,19 +83,22 @@ impl Drop for Timeline { } impl Timeline { - pub(super) async fn new(room: &room::Common) -> Self { - Self::with_events(room, None, Vec::new()) + pub(super) fn new(room: &room::Common) -> Self { + Self::from_inner(Arc::new(TimelineInner::new(room.to_owned())), None) } - pub(crate) fn with_events( + pub(crate) async fn with_events( room: &room::Common, prev_token: Option, events: Vec, ) -> Self { let mut inner = TimelineInner::new(room.to_owned()); - inner.add_initial_events(events); + inner.add_initial_events(events).await; + Self::from_inner(Arc::new(inner), prev_token) + } - let inner = Arc::new(inner); + fn from_inner(inner: Arc, prev_token: Option) -> Timeline { + let room = inner.room(); let timeline_event_handle = room.add_event_handler({ let inner = inner.clone(); diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index 3a3af2f6f..6b4afb6f6 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -20,6 +20,7 @@ use std::sync::{ }; use assert_matches::assert_matches; +use async_trait::async_trait; use futures_core::Stream; use futures_signals::signal_vec::{SignalVecExt, VecDiff}; use futures_util::StreamExt; @@ -47,8 +48,8 @@ use ruma::{ use serde_json::{json, Value as JsonValue}; use super::{ - inner::ProfileProvider, EncryptedMessage, TimelineInner, TimelineItem, TimelineItemContent, - TimelineKey, VirtualTimelineItem, + inner::ProfileProvider, EncryptedMessage, Profile, TimelineInner, TimelineItem, + TimelineItemContent, TimelineKey, VirtualTimelineItem, }; static ALICE: Lazy<&UserId> = Lazy::new(|| user_id!("@alice:server.name")); @@ -528,7 +529,8 @@ async fn initial_events() { let timeline = TestTimeline::with_initial_events([ (*ALICE, RoomMessageEventContent::text_plain("A").into()), (*BOB, RoomMessageEventContent::text_plain("B").into()), - ]); + ]) + .await; let mut stream = timeline.stream(); let items = assert_matches!(stream.next().await, Some(VecDiff::Replace { values }) => values); @@ -544,23 +546,25 @@ struct TestTimeline { impl TestTimeline { fn new() -> Self { - Self::with_initial_events([]) + Self { inner: TimelineInner::new(TestProfileProvider) } } - fn with_initial_events<'a>( + async fn with_initial_events<'a>( events: impl IntoIterator, ) -> Self { let mut inner = TimelineInner::new(TestProfileProvider); - inner.add_initial_events( - events - .into_iter() - .map(|(sender, content)| { - let event = - serde_json::from_value(make_message_event(sender, content)).unwrap(); - SyncTimelineEvent { event, encryption_info: None } - }) - .collect(), - ); + inner + .add_initial_events( + events + .into_iter() + .map(|(sender, content)| { + let event = + serde_json::from_value(make_message_event(sender, content)).unwrap(); + SyncTimelineEvent { event, encryption_info: None } + }) + .collect(), + ) + .await; Self { inner } } @@ -605,10 +609,15 @@ impl TestTimeline { struct TestProfileProvider; +#[async_trait] impl ProfileProvider for TestProfileProvider { fn own_user_id(&self) -> &UserId { &ALICE } + + async fn profile(&self, _user_id: &UserId) -> Profile { + Profile { display_name: None, avatar_url: None } + } } fn make_message_event(sender: &UserId, content: C) -> JsonValue { diff --git a/crates/matrix-sdk/src/sliding_sync.rs b/crates/matrix-sdk/src/sliding_sync.rs index 458796d34..d624c479a 100644 --- a/crates/matrix-sdk/src/sliding_sync.rs +++ b/crates/matrix-sdk/src/sliding_sync.rs @@ -235,16 +235,16 @@ impl SlidingSyncRoom { /// `Timeline` of this room #[cfg(feature = "experimental-timeline")] pub async fn timeline(&self) -> Option { - Some(self.timeline_no_fully_read_tracking()?.with_fully_read_tracking().await) + Some(self.timeline_no_fully_read_tracking().await?.with_fully_read_tracking().await) } - fn timeline_no_fully_read_tracking(&self) -> Option { + async fn timeline_no_fully_read_tracking(&self) -> Option { if let Some(room) = self.client.get_room(&self.room_id) { let current_timeline = self.timeline.lock_ref().to_vec(); let prev_batch = self.prev_batch.lock_ref().clone(); - Some(Timeline::with_events(&room, prev_batch, current_timeline)) + Some(Timeline::with_events(&room, prev_batch, current_timeline).await) } else if let Some(invited_room) = self.client.get_invited_room(&self.room_id) { - Some(Timeline::with_events(&invited_room, None, vec![])) + Some(Timeline::with_events(&invited_room, None, vec![]).await) } else { error!( room_id = ?self.room_id, @@ -259,8 +259,8 @@ impl SlidingSyncRoom { /// Use `Timeline::latest_event` instead if you already have a timeline for /// this `SlidingSyncRoom`. #[cfg(feature = "experimental-timeline")] - pub fn latest_event(&self) -> Option { - self.timeline_no_fully_read_tracking()?.latest_event() + pub async fn latest_event(&self) -> Option { + self.timeline_no_fully_read_tracking().await?.latest_event() } /// This rooms name as calculated by the server, if any