feat(sdk): Attach profile information to timeline items

This commit is contained in:
Jonas Platte
2023-01-11 10:45:11 +01:00
parent 5d632d37ff
commit 65a9931ce4
8 changed files with 97 additions and 41 deletions

View File

@@ -129,7 +129,7 @@ impl SlidingSyncRoom {
#[allow(clippy::significant_drop_in_scrutinee)]
pub fn latest_room_message(&self) -> Option<Arc<EventTimelineItem>> {
let item = self.inner.latest_event()?;
let item = RUNTIME.block_on(self.inner.latest_event())?;
Some(Arc::new(EventTimelineItem(item)))
}
}

View File

@@ -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.

View File

@@ -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<EncryptionInfo>,
@@ -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(),

View File

@@ -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<OwnedEventId>,
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<TransactionId> 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<String>,
/// The avatar URL, if set.
pub avatar_url: Option<OwnedMxcUri>,
}
/// Some details of an [`EventTimelineItem`] that may require server requests
/// other than just the regular
/// [`sync_events`][ruma::api::client::sync::sync_events].

View File

@@ -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<P: ProfileProvider> TimelineInner<P> {
self.items.signal_vec_cloned()
}
pub(super) fn add_initial_events(&mut self, events: Vec<SyncTimelineEvent>) {
pub(super) async fn add_initial_events(&mut self, events: Vec<SyncTimelineEvent>) {
if events.is_empty() {
return;
}
@@ -77,7 +78,8 @@ impl<P: ProfileProvider> TimelineInner<P> {
&self.items,
timeline_meta,
&self.profile_provider,
);
)
.await;
}
}
@@ -94,7 +96,8 @@ impl<P: ProfileProvider> TimelineInner<P> {
&self.items,
&mut timeline_meta,
&self.profile_provider,
);
)
.await;
}
pub(super) async fn handle_local_event(
@@ -102,8 +105,11 @@ impl<P: ProfileProvider> TimelineInner<P> {
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<P: ProfileProvider> TimelineInner<P> {
&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<P: ProfileProvider> TimelineInner<P> {
&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<P: ProfileProvider>(
async fn handle_remote_event<P: ProfileProvider>(
raw: Raw<AnySyncTimelineEvent>,
encryption_info: Option<EncryptionInfo>,
position: TimelineItemPosition,
@@ -340,7 +365,9 @@ fn handle_remote_event<P: ProfileProvider>(
};
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();

View File

@@ -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<String>,
events: Vec<SyncTimelineEvent>,
) -> 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<TimelineInner>, prev_token: Option<String>) -> Timeline {
let room = inner.room();
let timeline_event_handle = room.add_event_handler({
let inner = inner.clone();

View File

@@ -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<Item = (&'a UserId, AnyMessageLikeEventContent)>,
) -> 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<C: MessageLikeEventContent>(sender: &UserId, content: C) -> JsonValue {

View File

@@ -235,16 +235,16 @@ impl SlidingSyncRoom {
/// `Timeline` of this room
#[cfg(feature = "experimental-timeline")]
pub async fn timeline(&self) -> Option<Timeline> {
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<Timeline> {
async fn timeline_no_fully_read_tracking(&self) -> Option<Timeline> {
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<EventTimelineItem> {
self.timeline_no_fully_read_tracking()?.latest_event()
pub async fn latest_event(&self) -> Option<EventTimelineItem> {
self.timeline_no_fully_read_tracking().await?.latest_event()
}
/// This rooms name as calculated by the server, if any