diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 431415b48..e307c4b86 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -390,7 +390,7 @@ impl Common { } } - async fn ensure_members(&self) -> Result<()> { + pub(crate) async fn ensure_members(&self) -> Result<()> { if !self.are_events_visible() { return Ok(()); } diff --git a/crates/matrix-sdk/src/room/timeline/event_item.rs b/crates/matrix-sdk/src/room/timeline/event_item.rs index 5adcb99fe..54159be76 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item.rs +++ b/crates/matrix-sdk/src/room/timeline/event_item.rs @@ -209,6 +209,18 @@ impl EventTimelineItem { } } } + + /// Clone the current event item, and update its `sender_profile`. + pub(super) fn with_sender_profile(&self, sender_profile: TimelineDetails) -> Self { + match self { + EventTimelineItem::Local(item) => { + Self::Local(LocalEventTimelineItem { sender_profile, ..item.clone() }) + } + EventTimelineItem::Remote(item) => { + Self::Remote(RemoteEventTimelineItem { sender_profile, ..item.clone() }) + } + } + } } /// This type represents the "send state" of a local event timeline item. @@ -342,7 +354,7 @@ impl fmt::Debug for RemoteEventTimelineItem { } /// The display name and avatar URL of a room member. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Profile { /// The display name, if set. pub display_name: Option, @@ -382,6 +394,17 @@ impl TimelineDetails { None => Self::Unavailable, } } + + pub(crate) fn is_unavailable(&self) -> bool { + matches!(self, Self::Unavailable) + } + + pub(crate) fn contains(&self, value: &U) -> bool + where + T: PartialEq, + { + matches!(self, Self::Ready(v) if v == value) + } } /// The content of an [`EventTimelineItem`]. diff --git a/crates/matrix-sdk/src/room/timeline/inner.rs b/crates/matrix-sdk/src/room/timeline/inner.rs index aeb14493f..cb7aa0985 100644 --- a/crates/matrix-sdk/src/room/timeline/inner.rs +++ b/crates/matrix-sdk/src/room/timeline/inner.rs @@ -37,7 +37,7 @@ use super::{ use crate::{ events::SyncTimelineEventWithoutContent, room::{self, timeline::event_item::RemoteEventTimelineItem}, - Result, + Error, Result, }; #[derive(Debug)] @@ -372,6 +372,66 @@ impl TimelineInner

{ } } + pub(super) fn set_sender_profiles_pending(&self) { + self.set_non_ready_sender_profiles(TimelineDetails::Pending); + } + + pub(super) fn set_sender_profiles_error(&self, error: Arc) { + self.set_non_ready_sender_profiles(TimelineDetails::Error(error)); + } + + fn set_non_ready_sender_profiles(&self, state: TimelineDetails) { + let mut timeline_items = self.items.lock_mut(); + for idx in 0..timeline_items.len() { + let Some(event_item) = timeline_items[idx].as_event() else { continue }; + if !matches!(event_item.sender_profile(), TimelineDetails::Ready(_)) { + timeline_items.set_cloned( + idx, + Arc::new(TimelineItem::Event(event_item.with_sender_profile(state.clone()))), + ); + } + } + } + + pub(super) async fn update_sender_profiles(&self) { + // Can't lock the timeline items across .await points without making the + // resulting future `!Send`. As a (brittle) hack around that, lock the + // timeline items in each loop iteration but keep a lock of the metadata + // so no event handler runs in parallel and assert the number of items + // doesn't change between iterations. + let _guard = self.metadata.lock().await; + let num_items = self.items().len(); + + for idx in 0..num_items { + let sender = match self.items()[idx].as_event() { + Some(event_item) => event_item.sender().to_owned(), + None => continue, + }; + let maybe_profile = self.profile_provider.profile(&sender).await; + + let mut timeline_items = self.items.lock_mut(); + assert_eq!(timeline_items.len(), num_items); + + let event_item = timeline_items[idx].as_event().unwrap(); + match maybe_profile { + Some(profile) => { + if !event_item.sender_profile().contains(&profile) { + let updated_item = + event_item.with_sender_profile(TimelineDetails::Ready(profile)); + timeline_items.set_cloned(idx, Arc::new(TimelineItem::Event(updated_item))); + } + } + None => { + if !event_item.sender_profile().is_unavailable() { + let updated_item = + event_item.with_sender_profile(TimelineDetails::Unavailable); + timeline_items.set_cloned(idx, Arc::new(TimelineItem::Event(updated_item))); + } + } + } + } + } + fn update_event_item(&self, index: usize, event_item: EventTimelineItem) { self.items.lock_mut().set_cloned(index, Arc::new(TimelineItem::Event(event_item))) } diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index 458c54d5a..9921a027b 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -421,6 +421,26 @@ impl Timeline { Ok(()) } + + /// Fetch all member events for the room this timeline is displaying. + /// + /// If the full member list is not known, sender profiles are currently + /// likely not going to be available. This will be fixed in the future. + /// + /// If fetching the members fails, any affected timeline items will have + /// the `sender_profile` set to [`TimelineDetails::Error`]. + #[instrument(skip_all)] + pub async fn fetch_members(&self) { + self.inner.set_sender_profiles_pending(); + match self.room().ensure_members().await { + Ok(_) => { + self.inner.update_sender_profiles().await; + } + Err(e) => { + self.inner.set_sender_profiles_error(Arc::new(e)); + } + } + } } /// A single entry in timeline.