From 84b1b741a3b3d1ed722c253ce58b68bae5a49bcd Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 20 Sep 2023 12:00:33 +0200 Subject: [PATCH 01/18] feat(ui): `all_rooms` requires `m.room.member: $LAZY`. This patch updates `all_rooms` to require the `m.room.member` state set to `$LAZY`. That way, it's more likely to get the `m.room.member` event associated to the latest event without needing to sync all the members. --- crates/matrix-sdk-ui/src/room_list_service/mod.rs | 1 + crates/matrix-sdk-ui/tests/integration/room_list_service.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/matrix-sdk-ui/src/room_list_service/mod.rs b/crates/matrix-sdk-ui/src/room_list_service/mod.rs index f3c1906af..fcd5cf90e 100644 --- a/crates/matrix-sdk-ui/src/room_list_service/mod.rs +++ b/crates/matrix-sdk-ui/src/room_list_service/mod.rs @@ -177,6 +177,7 @@ impl RoomListService { .required_state(vec![ (StateEventType::RoomAvatar, "".to_owned()), (StateEventType::RoomEncryption, "".to_owned()), + (StateEventType::RoomMember, "$LAZY".to_owned()), (StateEventType::RoomPowerLevels, "".to_owned()), ]), )) diff --git a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs index 99daa4e80..e0f2740e0 100644 --- a/crates/matrix-sdk-ui/tests/integration/room_list_service.rs +++ b/crates/matrix-sdk-ui/tests/integration/room_list_service.rs @@ -274,6 +274,7 @@ async fn test_sync_all_states() -> Result<(), Error> { "required_state": [ ["m.room.avatar", ""], ["m.room.encryption", ""], + ["m.room.member", "$LAZY"], ["m.room.power_levels", ""], ], "filters": { From 9027607347e6cc399b4c9532305009ac67018709 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 21 Sep 2023 09:00:27 +0200 Subject: [PATCH 02/18] doc(ui): Format doc. --- crates/matrix-sdk-ui/src/timeline/event_item/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index ad6d4e16e..d1d486a76 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -90,8 +90,9 @@ impl EventTimelineItem { Self { sender, sender_profile, timestamp, content, kind } } - /// If the supplied low-level SyncTimelineEventy is suitable for use as the - /// latest_event in a message preview, wrap it as an EventTimelineItem, + /// If the supplied low-level `SyncTimelineEventy` is suitable for use as + /// the `latest_event `` in a message preview, wrap it as an + /// `EventTimelineItem`. pub async fn from_latest_event( client: Client, room_id: &RoomId, @@ -112,8 +113,8 @@ impl EventTimelineItem { let event_id = event.event_id().to_owned(); let is_own = client.user_id().map(|uid| uid == sender).unwrap_or(false); - // If we don't (yet) know how to handle this type of message, return None here. - // If we do, convert it into a TimelineItemContent. + // If we don't (yet) know how to handle this type of message, return `None` + // here. If we do, convert it into a `TimelineItemContent`. let item_content = TimelineItemContent::from_latest_event_content(event)?; // We don't currently bundle any reactions with the main event. This could From f71a6fc7386691044a12d839e0a3eb4f47861e3f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 21 Sep 2023 14:23:24 +0200 Subject: [PATCH 03/18] feat(base): Latest event is represented by the new `LatestEvent` type. This patch adds a new `LatestEvent` type. It wraps the `SyncTimelineEvent` that was used before. The idea is to add more fields onto this `LatestEvent` struct, but this patch starts easy by using `LatestEvent` everywhere where it's required. --- bindings/matrix-sdk-ffi/src/room.rs | 4 +- crates/matrix-sdk-base/src/client.rs | 9 +-- crates/matrix-sdk-base/src/latest_event.rs | 48 +++++++++++++++- crates/matrix-sdk-base/src/rooms/normal.rs | 44 ++++++++------ crates/matrix-sdk-base/src/sliding_sync.rs | 57 +++++++++++++------ .../src/timeline/event_item/mod.rs | 18 ++++-- crates/matrix-sdk/src/sliding_sync/room.rs | 4 +- 7 files changed, 132 insertions(+), 52 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index a02e7904a..bdacecbe7 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -315,10 +315,10 @@ impl Room { // Otherwise, fallback to the classical path. let latest_event = match self.inner.latest_event() { - Some(ev) => matrix_sdk_ui::timeline::EventTimelineItem::from_latest_event( + Some(latest_event) => matrix_sdk_ui::timeline::EventTimelineItem::from_latest_event( self.inner.client(), self.inner.room_id(), - ev, + latest_event, ) .await .map(EventTimelineItem) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 93833bc80..fc8a20387 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -54,7 +54,7 @@ use tokio::sync::RwLockReadGuard; use tracing::{debug, info, instrument, trace, warn}; #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] -use crate::latest_event::{is_suitable_for_latest_event, PossibleLatestEvent}; +use crate::latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent}; use crate::{ deserialized_responses::{AmbiguityChanges, MembersResponse, SyncTimelineEvent}, error::Result, @@ -620,10 +620,7 @@ impl BaseClient { /// decrypted event if we found one, along with its index in the /// latest_encrypted_events list, or None if we didn't find one. #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] - async fn decrypt_latest_suitable_event( - &self, - room: &Room, - ) -> Option<(SyncTimelineEvent, usize)> { + async fn decrypt_latest_suitable_event(&self, room: &Room) -> Option<(LatestEvent, usize)> { let enc_events = room.latest_encrypted_events(); // Walk backwards through the encrypted events, looking for one we can decrypt @@ -636,7 +633,7 @@ impl BaseClient { is_suitable_for_latest_event(&any_sync_event) { // The event is the right type for us to use as latest_event - return Some((decrypted, i)); + return Some((LatestEvent::new(decrypted), i)); } } } diff --git a/crates/matrix-sdk-base/src/latest_event.rs b/crates/matrix-sdk-base/src/latest_event.rs index eea4075e6..d4f0415ba 100644 --- a/crates/matrix-sdk-base/src/latest_event.rs +++ b/crates/matrix-sdk-base/src/latest_event.rs @@ -3,10 +3,17 @@ #![cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] -use ruma::events::{ - poll::unstable_start::SyncUnstablePollStartEvent, room::message::SyncRoomMessageEvent, - AnySyncMessageLikeEvent, AnySyncTimelineEvent, +use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; +use ruma::{ + events::{ + poll::unstable_start::SyncUnstablePollStartEvent, room::message::SyncRoomMessageEvent, + AnySyncMessageLikeEvent, AnySyncTimelineEvent, + }, + OwnedEventId, }; +use serde::{Deserialize, Serialize}; + +use crate::RoomMember; /// Represents a decision about whether an event could be stored as the latest /// event in a room. Variants starting with Yes indicate that this message could @@ -58,6 +65,41 @@ pub fn is_suitable_for_latest_event(event: &AnySyncTimelineEvent) -> PossibleLat } } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct LatestEvent { + event: SyncTimelineEvent, + // member: Option, +} + +impl LatestEvent { + pub fn new(event: SyncTimelineEvent) -> Self { + Self { event } + } + + // pub fn new_with_member(event: SyncTimelineEvent, member: RoomMember) -> Self + // { Self { event, member: Some(member) } + // } + + pub fn into_event(self) -> SyncTimelineEvent { + self.event + } + + pub fn event(&self) -> &SyncTimelineEvent { + &self.event + } + + pub fn event_mut(&mut self) -> &mut SyncTimelineEvent { + &mut self.event + } + + pub fn event_id(&self) -> Option { + self.event.event_id() + } + // pub fn member(&self) -> Option<&RoomMember> { + // self.member.as_ref() + // } +} + #[cfg(test)] mod tests { use std::collections::BTreeMap; diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 10cdf90df..098ccfa1a 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -62,6 +62,7 @@ use super::{ }; use crate::{ deserialized_responses::MemberEvent, + latest_event::LatestEvent, store::{DynStateStore, Result as StoreResult, StateStoreExt}, sync::UnreadNotificationsCount, MinimalStateEvent, OriginalMinimalStateEvent, RoomMemberships, @@ -383,13 +384,13 @@ impl Room { /// Return the last event in this room, if one has been cached during /// sliding sync. #[cfg(feature = "experimental-sliding-sync")] - pub fn latest_event(&self) -> Option { + pub fn latest_event(&self) -> Option { self.inner.read().latest_event.clone() } /// Update the last event in the room #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] - pub(crate) fn set_latest_event(&self, latest_event: Option) { + pub(crate) fn set_latest_event(&self, latest_event: Option) { self.inner.update(|info| info.latest_event = latest_event); } @@ -410,8 +411,8 @@ impl Room { /// Panics if index is not a valid index in the latest_encrypted_events /// list. #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] - pub(crate) fn on_latest_event_decrypted(&self, event: SyncTimelineEvent, index: usize) { - self.set_latest_event(Some(event)); + pub(crate) fn on_latest_event_decrypted(&self, latest_event: LatestEvent, index: usize) { + self.set_latest_event(Some(latest_event)); self.latest_encrypted_events.write().unwrap().drain(0..=index); } @@ -713,7 +714,7 @@ pub struct RoomInfo { pub(crate) encryption_state_synced: bool, /// The last event send by sliding sync #[cfg(feature = "experimental-sliding-sync")] - pub(crate) latest_event: Option, + pub(crate) latest_event: Option, /// Base room info which holds some basic event contents important for the /// room state. pub(crate) base_info: BaseRoomInfo, @@ -869,9 +870,9 @@ impl RoomInfo { if let Some(latest_event) = &mut self.latest_event { trace!("Checking if redaction applies to latest event"); if latest_event.event_id().as_deref() == Some(redacts) { - match apply_redaction(&latest_event.event, _raw, room_version) { + match apply_redaction(&latest_event.event().event, _raw, room_version) { Some(redacted) => { - latest_event.event = redacted; + latest_event.event_mut().event = redacted; debug!("Redacted latest event"); } None => { @@ -1133,6 +1134,8 @@ mod tests { #[cfg(feature = "experimental-sliding-sync")] use super::SyncInfo; use super::{Room, RoomInfo, RoomState}; + #[cfg(any(feature = "experimental-sliding-sync", feature = "e2e-encryption"))] + use crate::latest_event::LatestEvent; use crate::{ store::{MemoryStore, StateChanges, StateStore}, DisplayName, MinimalStateEvent, OriginalMinimalStateEvent, @@ -1163,9 +1166,9 @@ mod tests { last_prev_batch: Some("pb".to_owned()), sync_info: SyncInfo::FullySynced, encryption_state_synced: true, - latest_event: Some( + latest_event: Some(LatestEvent::new( Raw::from_json_string(json!({"sender": "@u:i.uk"}).to_string()).unwrap().into(), - ), + )), base_info: BaseRoomInfo::new(), }; @@ -1185,7 +1188,14 @@ mod tests { "last_prev_batch": "pb", "sync_info": "FullySynced", "encryption_state_synced": true, - "latest_event": {"encryption_info": null, "event": {"sender": "@u:i.uk"}}, + "latest_event": { + "event": { + "encryption_info": null, + "event": { + "sender": "@u:i.uk", + }, + }, + }, "base_info": { "avatar": null, "canonical_alias": null, @@ -1546,7 +1556,7 @@ mod tests { assert!(room.latest_event().is_none()); // When I provide a decrypted event to replace the encrypted one - let event = make_event("$A"); + let event = make_latest_event("$A"); room.on_latest_event_decrypted(event.clone(), 0); // Then is it stored @@ -1558,14 +1568,14 @@ mod tests { fn when_a_newly_decrypted_event_appears_we_delete_all_older_encrypted_events() { // Given a room with some encrypted events and a latest event let (_store, room) = make_room(RoomState::Joined); - room.inner.update(|info| info.latest_event = Some(make_event("$A"))); + room.inner.update(|info| info.latest_event = Some(make_latest_event("$A"))); add_encrypted_event(&room, "$0"); add_encrypted_event(&room, "$1"); add_encrypted_event(&room, "$2"); add_encrypted_event(&room, "$3"); // When I provide a latest event - let new_event = make_event("$1"); + let new_event = make_latest_event("$1"); let new_event_index = 1; room.on_latest_event_decrypted(new_event.clone(), new_event_index); @@ -1590,7 +1600,7 @@ mod tests { add_encrypted_event(&room, "$3"); // When I provide a latest event and say it was the very latest - let new_event = make_event("$3"); + let new_event = make_latest_event("$3"); let new_event_index = 3; room.on_latest_event_decrypted(new_event, new_event_index); @@ -1608,9 +1618,9 @@ mod tests { } #[cfg(feature = "experimental-sliding-sync")] - fn make_event(event_id: &str) -> SyncTimelineEvent { - SyncTimelineEvent::new( + fn make_latest_event(event_id: &str) -> LatestEvent { + LatestEvent::new(SyncTimelineEvent::new( Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap(), - ) + )) } } diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index b511816f9..727064469 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -32,7 +32,7 @@ use tracing::{instrument, trace, warn}; use super::BaseClient; #[cfg(feature = "e2e-encryption")] -use crate::latest_event::{is_suitable_for_latest_event, PossibleLatestEvent}; +use crate::latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent}; #[cfg(feature = "e2e-encryption")] use crate::RoomMemberships; use crate::{ @@ -461,16 +461,18 @@ impl BaseClient { fn cache_latest_events(room: &Room, room_info: &mut RoomInfo, events: &[SyncTimelineEvent]) { let mut encrypted_events = Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity()); - for e in events.iter().rev() { - if let Ok(timeline_event) = e.event.deserialize() { + + for event in events.iter().rev() { + if let Ok(timeline_event) = event.event.deserialize() { match is_suitable_for_latest_event(&timeline_event) { PossibleLatestEvent::YesRoomMessage(_) | PossibleLatestEvent::YesPoll(_) => { // m.room.message or m.poll.start - we found one! Store it. // Store it in the return RoomInfo, and in the Room, to make sure they are // consistent - room_info.latest_event = Some(e.clone()); - room.set_latest_event(Some(e.clone())); + let latest_event = LatestEvent::new(event.clone()); + room_info.latest_event = Some(latest_event.clone()); + room.set_latest_event(Some(latest_event)); // We don't need any of the older encrypted events because we have a new // decrypted one. room.latest_encrypted_events.write().unwrap().clear(); @@ -485,7 +487,7 @@ fn cache_latest_events(room: &Room, room_info: &mut RoomInfo, events: &[SyncTime // Check how many encrypted events we have seen. Only store another if we // haven't already stored the maximum number. if encrypted_events.len() < encrypted_events.capacity() { - encrypted_events.push(e.event.clone()); + encrypted_events.push(event.event.clone()); } } _ => { @@ -495,7 +497,7 @@ fn cache_latest_events(room: &Room, room_info: &mut RoomInfo, events: &[SyncTime } else { warn!( "Failed to deserialize event as AnySyncTimelineEvent. ID={}", - e.event_id().expect("Event has no ID!") + event.event_id().expect("Event has no ID!") ); } } @@ -964,7 +966,10 @@ mod tests { // Then the room holds the latest event let client_room = client.get_room(room_id).expect("No room found"); - assert_eq!(ev_id(client_room.latest_event()), "$idb"); + assert_eq!( + ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())), + "$idb" + ); } #[async_test] @@ -987,7 +992,10 @@ mod tests { // Then the room holds the latest event let client_room = client.get_room(room_id).expect("No room found"); - assert_eq!(ev_id(client_room.latest_event()), "$ida"); + assert_eq!( + ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())), + "$ida" + ); let redaction = json!({ "sender": "@alice:example.com", @@ -1010,7 +1018,7 @@ mod tests { // But it's now redacted assert_matches!( - latest_event.event.deserialize().unwrap(), + latest_event.event().event.deserialize().unwrap(), AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage( SyncRoomMessageEvent::Redacted(_) )) @@ -1075,8 +1083,14 @@ mod tests { cache_latest_events(&room, &mut room_info, events); // The latest message is stored - assert_eq!(ev_id(room_info.latest_event), rawev_id(event2.clone())); - assert_eq!(ev_id(room.latest_event()), rawev_id(event2)); + assert_eq!( + ev_id(room_info.latest_event.map(|latest_event| latest_event.event().clone())), + rawev_id(event2.clone()) + ); + assert_eq!( + ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())), + rawev_id(event2) + ); // And also the two encrypted ones assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event4])); @@ -1096,7 +1110,10 @@ mod tests { cache_latest_events(&room, &mut room_info, events); // The latest message is stored - assert_eq!(ev_id(room.latest_event()), rawev_id(event2)); + assert_eq!( + ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())), + rawev_id(event2) + ); // And also the encrypted one that was after it, but not the one before assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3])); @@ -1119,7 +1136,10 @@ mod tests { cache_latest_events(&room, &mut room_info, events); // The latest message is stored, ignoring the receipt - assert_eq!(ev_id(room.latest_event()), rawev_id(event2)); + assert_eq!( + ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())), + rawev_id(event2) + ); // The two encrypted ones are stored, but not the receipt assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event5])); @@ -1168,7 +1188,10 @@ mod tests { cache_latest_events(&room, &mut room_info, events); // The latest message is stored, ignoring encrypted and receipts - assert_eq!(ev_id(room.latest_event()), rawev_id(eventd)); + assert_eq!( + ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())), + rawev_id(eventd) + ); // Only 10 encrypted are stored, even though there were more assert_eq!( @@ -1235,14 +1258,14 @@ mod tests { assert_eq!(rawevs_ids(&room.latest_encrypted_events), &["$b"]); // The decrypted one is stored as the latest - assert_eq!(rawev_id(room.latest_event().unwrap()), "$a"); + assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a"); } fn choose_event_to_cache(events: &[SyncTimelineEvent]) -> Option { let room = make_room(); let mut room_info = room.clone_info(); cache_latest_events(&room, &mut room_info, events); - room.latest_event() + room.latest_event().map(|latest_event| latest_event.event().clone()) } fn rawev_id(event: SyncTimelineEvent) -> String { diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index d1d486a76..df88fec71 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use indexmap::IndexMap; use matrix_sdk::{deserialized_responses::EncryptionInfo, Client, Error}; -use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_base::latest_event::LatestEvent; use once_cell::sync::Lazy; use ruma::{ events::{receipt::Receipt, room::message::MessageType, AnySyncTimelineEvent}, @@ -96,10 +96,11 @@ impl EventTimelineItem { pub async fn from_latest_event( client: Client, room_id: &RoomId, - sync_event: SyncTimelineEvent, + latest_event: LatestEvent, ) -> Option { use super::traits::RoomDataProvider; + let sync_event = latest_event.into_event(); let raw_sync_event = sync_event.event; let encryption_info = sync_event.encryption_info; @@ -499,7 +500,10 @@ pub enum EventItemOrigin { mod tests { use assert_matches::assert_matches; use matrix_sdk::{config::RequestConfig, Client, ClientBuilder}; - use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, BaseClient, SessionMeta}; + use matrix_sdk_base::{ + deserialized_responses::SyncTimelineEvent, latest_event::LatestEvent, BaseClient, + SessionMeta, + }; use matrix_sdk_test::async_test; use ruma::{ api::{client::sync::sync_events::v4, MatrixVersion}, @@ -528,7 +532,9 @@ mod tests { // When we construct a timeline event from it let timeline_item = - EventTimelineItem::from_latest_event(client, room_id, event).await.unwrap(); + EventTimelineItem::from_latest_event(client, room_id, LatestEvent::new(event)) + .await + .unwrap(); // Then its properties correctly translate assert_eq!(timeline_item.sender, user_id); @@ -563,7 +569,9 @@ mod tests { // When we construct a timeline event from it let timeline_item = - EventTimelineItem::from_latest_event(client, room_id, event).await.unwrap(); + EventTimelineItem::from_latest_event(client, room_id, LatestEvent::new(event)) + .await + .unwrap(); // Then its sender is properly populated let profile = assert_matches!(timeline_item.sender_profile, TimelineDetails::Ready(p) => p); diff --git a/crates/matrix-sdk/src/sliding_sync/room.rs b/crates/matrix-sdk/src/sliding_sync/room.rs index 53a276f00..597d614d6 100644 --- a/crates/matrix-sdk/src/sliding_sync/room.rs +++ b/crates/matrix-sdk/src/sliding_sync/room.rs @@ -5,7 +5,7 @@ use std::{ }; use eyeball_im::Vector; -use matrix_sdk_base::deserialized_responses::SyncTimelineEvent; +use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, latest_event::LatestEvent}; use ruma::{ api::client::sync::sync_events::{v4, UnreadNotificationsCount}, events::AnySyncStateEvent, @@ -133,7 +133,7 @@ impl SlidingSyncRoom { } /// Find the latest event in this room - pub fn latest_event(&self) -> Option { + pub fn latest_event(&self) -> Option { self.inner.client.get_room(&self.inner.room_id).and_then(|room| room.latest_event()) } From 83e7d7df119635a1cf3a8d120cd19e40c230630f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 27 Sep 2023 17:02:33 +0200 Subject: [PATCH 04/18] feat(base): `LatestEvent` can hold a `MinimalRoomMemberEvent`. This patch adds a `sender_profile: Option` field. Thus, `cache_latest_events` now receives an `Option<&StateChanges>` and an `Option<&Store>`. The `StateChanges` is used to read the most recent sender profile, otherwise the sender profile will be fetch from the storage. --- crates/matrix-sdk-base/src/latest_event.rs | 35 ++++-- crates/matrix-sdk-base/src/rooms/normal.rs | 2 - crates/matrix-sdk-base/src/sliding_sync.rs | 121 ++++++++++++++------- 3 files changed, 107 insertions(+), 51 deletions(-) diff --git a/crates/matrix-sdk-base/src/latest_event.rs b/crates/matrix-sdk-base/src/latest_event.rs index d4f0415ba..2ccae83e7 100644 --- a/crates/matrix-sdk-base/src/latest_event.rs +++ b/crates/matrix-sdk-base/src/latest_event.rs @@ -13,7 +13,7 @@ use ruma::{ }; use serde::{Deserialize, Serialize}; -use crate::RoomMember; +use crate::MinimalRoomMemberEvent; /// Represents a decision about whether an event could be stored as the latest /// event in a room. Variants starting with Yes indicate that this message could @@ -65,39 +65,56 @@ pub fn is_suitable_for_latest_event(event: &AnySyncTimelineEvent) -> PossibleLat } } +/// Represent all information required to represent a latest event in an +/// efficient way. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct LatestEvent { + /// The actual event. event: SyncTimelineEvent, - // member: Option, + + /// The member profile of the event' sender. + #[serde(skip_serializing_if = "Option::is_none")] + sender_profile: Option, } impl LatestEvent { + /// Create a new [`LatestEvent`] without the sender's profile. pub fn new(event: SyncTimelineEvent) -> Self { - Self { event } + Self { event, sender_profile: None } } - // pub fn new_with_member(event: SyncTimelineEvent, member: RoomMember) -> Self - // { Self { event, member: Some(member) } - // } + /// Create a new [`LatestEvent`] with maybe the sender's profile. + pub fn new_with_sender_profile( + event: SyncTimelineEvent, + sender_profile: Option, + ) -> Self { + Self { event, sender_profile } + } + /// Transform [`Self`] into an event. pub fn into_event(self) -> SyncTimelineEvent { self.event } + /// Get a reference to the event. pub fn event(&self) -> &SyncTimelineEvent { &self.event } + /// Get a mutable reference to the event. pub fn event_mut(&mut self) -> &mut SyncTimelineEvent { &mut self.event } + /// Get the event ID. pub fn event_id(&self) -> Option { self.event.event_id() } - // pub fn member(&self) -> Option<&RoomMember> { - // self.member.as_ref() - // } + + /// Get a reference to the sender profile if any. + pub fn sender_profile(&self) -> Option<&MinimalRoomMemberEvent> { + self.sender_profile.as_ref() + } } #[cfg(test)] diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 098ccfa1a..898b146b4 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -23,8 +23,6 @@ use std::{ use bitflags::bitflags; use eyeball::{SharedObservable, Subscriber}; use futures_util::stream::{self, StreamExt}; -#[cfg(feature = "experimental-sliding-sync")] -use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; #[cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] use matrix_sdk_common::ring_buffer::RingBuffer; #[cfg(feature = "experimental-sliding-sync")] diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index 727064469..8197d5bb6 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -295,7 +295,8 @@ impl BaseClient { // Cache the latest decrypted event in room_info, and also keep any later // encrypted events, so we can slot them in when we get the keys. #[cfg(feature = "e2e-encryption")] - cache_latest_events(&room, &mut room_info, &timeline.events); + cache_latest_events(&room, &mut room_info, &timeline.events, Some(changes), Some(store)) + .await; #[cfg(feature = "e2e-encryption")] if room_info.is_encrypted() { @@ -458,7 +459,13 @@ impl BaseClient { /// If any encrypted events are found after that one, store them in the RoomInfo /// too so we can use them when we get the relevant keys. #[cfg(feature = "e2e-encryption")] -fn cache_latest_events(room: &Room, room_info: &mut RoomInfo, events: &[SyncTimelineEvent]) { +async fn cache_latest_events( + room: &Room, + room_info: &mut RoomInfo, + events: &[SyncTimelineEvent], + changes: Option<&StateChanges>, + store: Option<&Store>, +) { let mut encrypted_events = Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity()); @@ -468,9 +475,37 @@ fn cache_latest_events(room: &Room, room_info: &mut RoomInfo, events: &[SyncTime PossibleLatestEvent::YesRoomMessage(_) | PossibleLatestEvent::YesPoll(_) => { // m.room.message or m.poll.start - we found one! Store it. + // Ideally, we should just use `Option::or_else`, `::map` etc., but since the + // `Store` has an async API, we have no choice than doing this manually. + // + // First off, look up the sender's profile from the `changes`. + let mut sender_profile = None; + + if let Some(changes) = changes { + sender_profile = changes + .profiles + .get(room.room_id()) + .map(|profiles_by_user| profiles_by_user.get(timeline_event.sender())) + .flatten() + .cloned(); + } + + // Second, look up the sender's profile from the storage if none was found. + if sender_profile.is_none() { + if let Some(store) = store { + sender_profile = store + .get_profile(room.room_id(), timeline_event.sender()) + .await + .ok() + .flatten(); + } + } + + let latest_event = + LatestEvent::new_with_sender_profile(event.clone(), sender_profile); + // Store it in the return RoomInfo, and in the Room, to make sure they are // consistent - let latest_event = LatestEvent::new(event.clone()); room_info.latest_event = Some(latest_event.clone()); room.set_latest_event(Some(latest_event)); // We don't need any of the older encrypted events because we have a new @@ -1025,51 +1060,51 @@ mod tests { ); } - #[test] - fn when_no_events_we_dont_cache_any() { + #[async_test] + async fn when_no_events_we_dont_cache_any() { let events = &[]; - let chosen = choose_event_to_cache(events); + let chosen = choose_event_to_cache(events).await; assert!(chosen.is_none()); } - #[test] - fn when_only_one_event_we_cache_it() { + #[async_test] + async fn when_only_one_event_we_cache_it() { let event1 = make_event("m.room.message", "$1"); let events = &[event1.clone()]; - let chosen = choose_event_to_cache(events); + let chosen = choose_event_to_cache(events).await; assert_eq!(ev_id(chosen), rawev_id(event1)); } - #[test] - fn with_multiple_events_we_cache_the_last_one() { + #[async_test] + async fn with_multiple_events_we_cache_the_last_one() { let event1 = make_event("m.room.message", "$1"); let event2 = make_event("m.room.message", "$2"); let events = &[event1, event2.clone()]; - let chosen = choose_event_to_cache(events); + let chosen = choose_event_to_cache(events).await; assert_eq!(ev_id(chosen), rawev_id(event2)); } - #[test] - fn cache_the_latest_relevant_event_and_ignore_irrelevant_ones_even_if_later() { + #[async_test] + async fn cache_the_latest_relevant_event_and_ignore_irrelevant_ones_even_if_later() { let event1 = make_event("m.room.message", "$1"); let event2 = make_event("m.room.message", "$2"); let event3 = make_event("m.room.powerlevels", "$3"); let event4 = make_event("m.room.powerlevels", "$5"); let events = &[event1, event2.clone(), event3, event4]; - let chosen = choose_event_to_cache(events); + let chosen = choose_event_to_cache(events).await; assert_eq!(ev_id(chosen), rawev_id(event2)); } - #[test] - fn prefer_to_cache_nothing_rather_than_irrelevant_events() { + #[async_test] + async fn prefer_to_cache_nothing_rather_than_irrelevant_events() { let event1 = make_event("m.room.power_levels", "$1"); let events = &[event1]; - let chosen = choose_event_to_cache(events); + let chosen = choose_event_to_cache(events).await; assert!(chosen.is_none()); } - #[test] - fn cache_encrypted_events_that_are_after_latest_message() { + #[async_test] + async fn cache_encrypted_events_that_are_after_latest_message() { // Given two message events followed by two encrypted let event1 = make_event("m.room.message", "$1"); let event2 = make_event("m.room.message", "$2"); @@ -1080,7 +1115,7 @@ mod tests { // When I ask to cache events let room = make_room(); let mut room_info = room.clone_info(); - cache_latest_events(&room, &mut room_info, events); + cache_latest_events(&room, &mut room_info, events, None, None).await; // The latest message is stored assert_eq!( @@ -1096,8 +1131,8 @@ mod tests { assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event4])); } - #[test] - fn dont_cache_encrypted_events_that_are_before_latest_message() { + #[async_test] + async fn dont_cache_encrypted_events_that_are_before_latest_message() { // Given an encrypted event before and after the message let event1 = make_encrypted_event("$1"); let event2 = make_event("m.room.message", "$2"); @@ -1107,7 +1142,7 @@ mod tests { // When I ask to cache events let room = make_room(); let mut room_info = room.clone_info(); - cache_latest_events(&room, &mut room_info, events); + cache_latest_events(&room, &mut room_info, events, None, None).await; // The latest message is stored assert_eq!( @@ -1119,8 +1154,8 @@ mod tests { assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3])); } - #[test] - fn skip_irrelevant_events_eg_receipts_even_if_after_message() { + #[async_test] + async fn skip_irrelevant_events_eg_receipts_even_if_after_message() { // Given two message events followed by two encrypted, with a receipt in the // middle let event1 = make_event("m.room.message", "$1"); @@ -1133,7 +1168,7 @@ mod tests { // When I ask to cache events let room = make_room(); let mut room_info = room.clone_info(); - cache_latest_events(&room, &mut room_info, events); + cache_latest_events(&room, &mut room_info, events, None, None).await; // The latest message is stored, ignoring the receipt assert_eq!( @@ -1145,8 +1180,8 @@ mod tests { assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event5])); } - #[test] - fn only_store_the_max_number_of_encrypted_events() { + #[async_test] + async fn only_store_the_max_number_of_encrypted_events() { // Given two message events followed by lots of encrypted and other irrelevant // events let evente = make_event("m.room.message", "$e"); @@ -1185,7 +1220,7 @@ mod tests { // When I ask to cache events let room = make_room(); let mut room_info = room.clone_info(); - cache_latest_events(&room, &mut room_info, events); + cache_latest_events(&room, &mut room_info, events, None, None).await; // The latest message is stored, ignoring encrypted and receipts assert_eq!( @@ -1202,8 +1237,8 @@ mod tests { ); } - #[test] - fn dont_overflow_capacity_if_previous_encrypted_events_exist() { + #[async_test] + async fn dont_overflow_capacity_if_previous_encrypted_events_exist() { // Given a RoomInfo with lots of encrypted events already inside it let room = make_room(); let mut room_info = room.clone_info(); @@ -1222,14 +1257,17 @@ mod tests { make_encrypted_event("$8"), make_encrypted_event("$9"), ], - ); + None, + None, + ) + .await; // Sanity: room_info has 10 encrypted events inside it assert_eq!(room.latest_encrypted_events.read().unwrap().len(), 10); // When I ask to cache more encrypted events let eventa = make_encrypted_event("$a"); let mut room_info = room.clone_info(); - cache_latest_events(&room, &mut room_info, &[eventa]); + cache_latest_events(&room, &mut room_info, &[eventa], None, None).await; // The oldest event is gone assert!(!rawevs_ids(&room.latest_encrypted_events).contains(&"$0".to_owned())); @@ -1238,8 +1276,8 @@ mod tests { assert_eq!(rawevs_ids(&room.latest_encrypted_events)[9], "$a"); } - #[test] - fn existing_encrypted_events_are_deleted_if_we_receive_unencrypted() { + #[async_test] + async fn existing_encrypted_events_are_deleted_if_we_receive_unencrypted() { // Given a RoomInfo with some encrypted events already inside it let room = make_room(); let mut room_info = room.clone_info(); @@ -1247,12 +1285,15 @@ mod tests { &room, &mut room_info, &[make_encrypted_event("$0"), make_encrypted_event("$1"), make_encrypted_event("$2")], - ); + None, + None, + ) + .await; // When I ask to cache an unecnrypted event, and some more encrypted events let eventa = make_event("m.room.message", "$a"); let eventb = make_encrypted_event("$b"); - cache_latest_events(&room, &mut room_info, &[eventa, eventb]); + cache_latest_events(&room, &mut room_info, &[eventa, eventb], None, None).await; // The only encrypted events stored are the ones after the decrypted one assert_eq!(rawevs_ids(&room.latest_encrypted_events), &["$b"]); @@ -1261,10 +1302,10 @@ mod tests { assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a"); } - fn choose_event_to_cache(events: &[SyncTimelineEvent]) -> Option { + async fn choose_event_to_cache(events: &[SyncTimelineEvent]) -> Option { let room = make_room(); let mut room_info = room.clone_info(); - cache_latest_events(&room, &mut room_info, events); + cache_latest_events(&room, &mut room_info, events, None, None).await; room.latest_event().map(|latest_event| latest_event.event().clone()) } From c0a3cc478d56eac288499e5a210d9ffa9d9583cd Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 28 Sep 2023 09:16:57 +0200 Subject: [PATCH 05/18] feat(base): `LatestEvent` has more methods. This patch implements `LatestEvent::sender_display_name()`, `::sender_name_ambiguous()` and `::sender_avatar_url()`. --- crates/matrix-sdk-base/src/latest_event.rs | 37 +++++++++++++++-- crates/matrix-sdk-base/src/sliding_sync.rs | 46 +++++++++++++++++----- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/crates/matrix-sdk-base/src/latest_event.rs b/crates/matrix-sdk-base/src/latest_event.rs index 2ccae83e7..d3c8e394e 100644 --- a/crates/matrix-sdk-base/src/latest_event.rs +++ b/crates/matrix-sdk-base/src/latest_event.rs @@ -9,7 +9,7 @@ use ruma::{ poll::unstable_start::SyncUnstablePollStartEvent, room::message::SyncRoomMessageEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, }, - OwnedEventId, + MxcUri, OwnedEventId, }; use serde::{Deserialize, Serialize}; @@ -75,20 +75,26 @@ pub struct LatestEvent { /// The member profile of the event' sender. #[serde(skip_serializing_if = "Option::is_none")] sender_profile: Option, + + // sender_room_member_event: Option, } impl LatestEvent { /// Create a new [`LatestEvent`] without the sender's profile. pub fn new(event: SyncTimelineEvent) -> Self { - Self { event, sender_profile: None } + Self { event, sender_profile: None, sender_name_is_ambiguous: None } } /// Create a new [`LatestEvent`] with maybe the sender's profile. - pub fn new_with_sender_profile( + pub fn new_with_sender_details( event: SyncTimelineEvent, sender_profile: Option, + sender_name_is_ambiguous: Option, ) -> Self { - Self { event, sender_profile } + Self { event, sender_profile, sender_name_is_ambiguous } } /// Transform [`Self`] into an event. @@ -115,6 +121,29 @@ impl LatestEvent { pub fn sender_profile(&self) -> Option<&MinimalRoomMemberEvent> { self.sender_profile.as_ref() } + + /// Return the sender's display name if it was known at the time [`Self`] + /// was built. + pub fn sender_display_name(&self) -> Option<&str> { + self.sender_profile.as_ref().and_then(|profile| { + profile.as_original().and_then(|event| event.content.displayname.as_deref()) + }) + } + + /// Return `Some(true)` if the sender's name is ambiguous, `Some(false)` if + /// it isn't, `None` if ambiguity detection wasn't possible at the time + /// [`Self`] was built. + pub fn sender_name_ambiguous(&self) -> Option { + self.sender_name_is_ambiguous + } + + /// Return the sender's avatar URL if it was known at the time [`Self`] was + /// built. + pub fn sender_avatar_url(&self) -> Option<&MxcUri> { + self.sender_profile.as_ref().and_then(|profile| { + profile.as_original().and_then(|event| event.content.avatar_url.as_deref()) + }) + } } #[cfg(test)] diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index 8197d5bb6..5df9aedae 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -475,22 +475,42 @@ async fn cache_latest_events( PossibleLatestEvent::YesRoomMessage(_) | PossibleLatestEvent::YesPoll(_) => { // m.room.message or m.poll.start - we found one! Store it. - // Ideally, we should just use `Option::or_else`, `::map` etc., but since the - // `Store` has an async API, we have no choice than doing this manually. - // - // First off, look up the sender's profile from the `changes`. - let mut sender_profile = None; + // In order to make the latest event fast to read, we want to keep the + // associated sender in cache. This is a best-effort to gather enough + // information for creating a user profile as fast as possible. If information + // are missing, let's go back on the “slow” path. + let mut sender_profile = None; + let mut sender_name_is_ambiguous = None; + + // First off, look up the sender's profile from the `StateChanges`, they are + // likely to be the most recent information. if let Some(changes) = changes { sender_profile = changes .profiles .get(room.room_id()) - .map(|profiles_by_user| profiles_by_user.get(timeline_event.sender())) - .flatten() + .and_then(|profiles_by_user| { + profiles_by_user.get(timeline_event.sender()) + }) .cloned(); + + if let Some(sender_profile) = sender_profile.as_ref() { + sender_name_is_ambiguous = sender_profile + .as_original() + .and_then(|profile| profile.content.displayname.as_ref()) + .and_then(|display_name| { + changes.ambiguity_maps.get(room.room_id()).and_then( + |map_for_room| { + map_for_room + .get(display_name) + .map(|user_ids| user_ids.len() > 1) + }, + ) + }); + } } - // Second, look up the sender's profile from the storage if none was found. + // Otherwise, look up the sender's profile from the `Store`. if sender_profile.is_none() { if let Some(store) = store { sender_profile = store @@ -498,11 +518,17 @@ async fn cache_latest_events( .await .ok() .flatten(); + + // TODO: need to update `sender_name_is_ambiguous`, + // but how? } } - let latest_event = - LatestEvent::new_with_sender_profile(event.clone(), sender_profile); + let latest_event = LatestEvent::new_with_sender_details( + event.clone(), + sender_profile, + sender_name_is_ambiguous, + ); // Store it in the return RoomInfo, and in the Room, to make sure they are // consistent From 5c55c2c51ffa7273143ccdba6a7e581b7151bf34 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 28 Sep 2023 10:42:40 +0200 Subject: [PATCH 06/18] feat(base): Rename `LatestEvent::sender_profile` to `::has_sender_profile`. --- crates/matrix-sdk-base/src/latest_event.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk-base/src/latest_event.rs b/crates/matrix-sdk-base/src/latest_event.rs index d3c8e394e..7f22ff6b4 100644 --- a/crates/matrix-sdk-base/src/latest_event.rs +++ b/crates/matrix-sdk-base/src/latest_event.rs @@ -117,9 +117,9 @@ impl LatestEvent { self.event.event_id() } - /// Get a reference to the sender profile if any. - pub fn sender_profile(&self) -> Option<&MinimalRoomMemberEvent> { - self.sender_profile.as_ref() + /// Check whether [`Self`] has a sender a profile. + pub fn has_sender_profile(&self) -> bool { + self.sender_profile.is_some() } /// Return the sender's display name if it was known at the time [`Self`] From 86ed58274488916a5edc66aeb93ef548d6b52345 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 28 Sep 2023 10:43:56 +0200 Subject: [PATCH 07/18] feat(ui): `RoomDataProvider` has `profile_from_latest_event`. This patch renames `RoomDataProvider::profile` to `::profile_from_user_id`. This patch also adds `RoomDataProvider::profile_from_latest_event`. --- .../src/timeline/event_item/content/message.rs | 5 +++-- .../src/timeline/event_item/mod.rs | 9 ++++----- crates/matrix-sdk-ui/src/timeline/inner/mod.rs | 8 ++++---- .../matrix-sdk-ui/src/timeline/inner/state.rs | 2 +- crates/matrix-sdk-ui/src/timeline/tests/mod.rs | 7 ++++++- crates/matrix-sdk-ui/src/timeline/traits.rs | 18 ++++++++++++++++-- 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs b/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs index 8e60c7c8a..aa8395452 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/content/message.rs @@ -268,8 +268,9 @@ impl RepliedToEvent { let content = TimelineItemContent::Message(Message::from_event(c, event.relations(), &vector![])); let sender = event.sender().to_owned(); - let sender_profile = - TimelineDetails::from_initial_value(room_data_provider.profile(&sender).await); + let sender_profile = TimelineDetails::from_initial_value( + room_data_provider.profile_from_user_id(&sender).await, + ); Ok(Self { content, sender, sender_profile }) } diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index df88fec71..7143a09f4 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use indexmap::IndexMap; use matrix_sdk::{deserialized_responses::EncryptionInfo, Client, Error}; -use matrix_sdk_base::latest_event::LatestEvent; +use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, latest_event::LatestEvent}; use once_cell::sync::Lazy; use ruma::{ events::{receipt::Receipt, room::message::MessageType, AnySyncTimelineEvent}, @@ -100,9 +100,8 @@ impl EventTimelineItem { ) -> Option { use super::traits::RoomDataProvider; - let sync_event = latest_event.into_event(); - let raw_sync_event = sync_event.event; - let encryption_info = sync_event.encryption_info; + let SyncTimelineEvent { event: raw_sync_event, encryption_info, .. } = + latest_event.event().clone(); let Ok(event) = raw_sync_event.deserialize_as::() else { warn!("Unable to deserialize latest_event as an AnySyncTimelineEvent!"); @@ -150,7 +149,7 @@ impl EventTimelineItem { let room = client.get_room(room_id); let sender_profile = if let Some(room) = room { - room.profile(&sender) + room.profile_from_latest_event(&latest_event) .await .map(TimelineDetails::Ready) .unwrap_or(TimelineDetails::Unavailable) diff --git a/crates/matrix-sdk-ui/src/timeline/inner/mod.rs b/crates/matrix-sdk-ui/src/timeline/inner/mod.rs index 976ec6df7..787c7cb5e 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner/mod.rs @@ -205,7 +205,7 @@ impl TimelineInner

{ }; let sender = self.room_data_provider.own_user_id().to_owned(); - let sender_profile = self.room_data_provider.profile(&sender).await; + let sender_profile = self.room_data_provider.profile_from_user_id(&sender).await; let reaction_state = match (to_redact_local, to_redact_remote) { (None, None) => { // No record of the reaction, create a local echo @@ -343,7 +343,7 @@ impl TimelineInner

{ content: AnyMessageLikeEventContent, ) { let sender = self.room_data_provider.own_user_id().to_owned(); - let profile = self.room_data_provider.profile(&sender).await; + let profile = self.room_data_provider.profile_from_user_id(&sender).await; let mut state = self.state.write().await; state.handle_local_event(sender, profile, txn_id, content, &self.settings); @@ -358,7 +358,7 @@ impl TimelineInner

{ content: RoomRedactionEventContent, ) { let sender = self.room_data_provider.own_user_id().to_owned(); - let profile = self.room_data_provider.profile(&sender).await; + let profile = self.room_data_provider.profile_from_user_id(&sender).await; let mut state = self.state.write().await; state.handle_local_redaction(sender, profile, txn_id, to_redact, content, &self.settings); @@ -738,7 +738,7 @@ impl TimelineInner

{ continue; } - match self.room_data_provider.profile(event_item.sender()).await { + match self.room_data_provider.profile_from_user_id(event_item.sender()).await { Some(profile) => { trace!(event_id, transaction_id, "Adding profile"); let updated_item = diff --git a/crates/matrix-sdk-ui/src/timeline/inner/state.rs b/crates/matrix-sdk-ui/src/timeline/inner/state.rs index 8904db63e..94bfd45d5 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner/state.rs @@ -544,7 +544,7 @@ impl TimelineInnerStateTransaction<'_> { }; let is_own_event = sender == room_data_provider.own_user_id(); - let sender_profile = room_data_provider.profile(&sender).await; + let sender_profile = room_data_provider.profile_from_user_id(&sender).await; let ctx = TimelineEventContext { sender, sender_profile, diff --git a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs index 2a8691938..c9d2f78aa 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs @@ -23,6 +23,7 @@ use futures_core::Stream; use futures_util::{FutureExt, StreamExt}; use indexmap::IndexMap; use matrix_sdk::deserialized_responses::{SyncTimelineEvent, TimelineEvent}; +use matrix_sdk_base::latest_event::LatestEvent; use matrix_sdk_test::{EventBuilder, ALICE}; use ruma::{ events::{ @@ -238,7 +239,11 @@ impl RoomDataProvider for TestRoomDataProvider { RoomVersionId::V10 } - async fn profile(&self, _user_id: &UserId) -> Option { + async fn profile_from_user_id(&self, _user_id: &UserId) -> Option { + None + } + + async fn profile_from_latest_event(&self, _latest_event: &LatestEvent) -> Option { None } diff --git a/crates/matrix-sdk-ui/src/timeline/traits.rs b/crates/matrix-sdk-ui/src/timeline/traits.rs index 03994cb98..1f03849e1 100644 --- a/crates/matrix-sdk-ui/src/timeline/traits.rs +++ b/crates/matrix-sdk-ui/src/timeline/traits.rs @@ -17,6 +17,7 @@ use indexmap::IndexMap; use matrix_sdk::Room; #[cfg(feature = "e2e-encryption")] use matrix_sdk::{deserialized_responses::TimelineEvent, Result}; +use matrix_sdk_base::latest_event::LatestEvent; use ruma::{ events::receipt::{Receipt, ReceiptThread, ReceiptType}, push::{PushConditionRoomCtx, Ruleset}, @@ -66,7 +67,8 @@ impl RoomExt for Room { pub(super) trait RoomDataProvider: Clone + Send + Sync + 'static { fn own_user_id(&self) -> &UserId; fn room_version(&self) -> RoomVersionId; - async fn profile(&self, user_id: &UserId) -> Option; + async fn profile_from_user_id(&self, user_id: &UserId) -> Option; + async fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option; async fn read_receipts_for_event(&self, event_id: &EventId) -> IndexMap; async fn push_rules_and_context(&self) -> Option<(Ruleset, PushConditionRoomCtx)>; } @@ -84,7 +86,7 @@ impl RoomDataProvider for Room { }) } - async fn profile(&self, user_id: &UserId) -> Option { + async fn profile_from_user_id(&self, user_id: &UserId) -> Option { match self.get_member_no_sync(user_id).await { Ok(Some(member)) => Some(Profile { display_name: member.display_name().map(ToOwned::to_owned), @@ -104,6 +106,18 @@ impl RoomDataProvider for Room { } } + async fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option { + if !latest_event.has_sender_profile() { + return None; + } + + Some(Profile { + display_name: latest_event.sender_display_name().map(ToOwned::to_owned), + display_name_ambiguous: latest_event.sender_name_ambiguous().unwrap_or(false), + avatar_url: latest_event.sender_avatar_url().map(ToOwned::to_owned), + }) + } + async fn read_receipts_for_event(&self, event_id: &EventId) -> IndexMap { match self.event_receipts(ReceiptType::Read, ReceiptThread::Unthreaded, event_id).await { Ok(receipts) => receipts.into_iter().collect(), From 58998e4bbe8379dea3afad0456a4956e5fd49488 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Oct 2023 09:31:07 +0200 Subject: [PATCH 08/18] feat(ui): `Profile` implements `Default`. --- crates/matrix-sdk-ui/src/timeline/event_item/mod.rs | 2 +- crates/matrix-sdk-ui/src/timeline/traits.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 7143a09f4..042e20c6f 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -434,7 +434,7 @@ impl From for EventTimelineItemKind { } /// The display name and avatar URL of a room member. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Profile { /// The display name, if set. pub display_name: Option, diff --git a/crates/matrix-sdk-ui/src/timeline/traits.rs b/crates/matrix-sdk-ui/src/timeline/traits.rs index 1f03849e1..cde72a4c4 100644 --- a/crates/matrix-sdk-ui/src/timeline/traits.rs +++ b/crates/matrix-sdk-ui/src/timeline/traits.rs @@ -93,11 +93,7 @@ impl RoomDataProvider for Room { display_name_ambiguous: member.name_ambiguous(), avatar_url: member.avatar_url().map(ToOwned::to_owned), }), - Ok(None) if self.are_members_synced() => Some(Profile { - display_name: None, - display_name_ambiguous: false, - avatar_url: None, - }), + Ok(None) if self.are_members_synced() => Some(Profile::default()), Ok(None) => None, Err(e) => { error!(%user_id, "Failed to fetch room member information: {e}"); @@ -113,7 +109,7 @@ impl RoomDataProvider for Room { Some(Profile { display_name: latest_event.sender_display_name().map(ToOwned::to_owned), - display_name_ambiguous: latest_event.sender_name_ambiguous().unwrap_or(false), + display_name_ambiguous: latest_event.sender_name_ambiguous().unwrap_or_default(), avatar_url: latest_event.sender_avatar_url().map(ToOwned::to_owned), }) } From 0a119b55b322bf02097fce86aef75c0311510f60 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Oct 2023 10:01:48 +0200 Subject: [PATCH 09/18] fix(base): Fix migration helper for `RoomInfo`. --- crates/matrix-sdk-base/src/store/migration_helpers.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk-base/src/store/migration_helpers.rs b/crates/matrix-sdk-base/src/store/migration_helpers.rs index d7f53d2ea..b9812ae11 100644 --- a/crates/matrix-sdk-base/src/store/migration_helpers.rs +++ b/crates/matrix-sdk-base/src/store/migration_helpers.rs @@ -40,6 +40,7 @@ use serde::{Deserialize, Serialize}; use crate::{ deserialized_responses::SyncOrStrippedState, + latest_event::LatestEvent, rooms::{ normal::{RoomSummary, SyncInfo}, BaseRoomInfo, @@ -115,7 +116,7 @@ impl RoomInfoV1 { sync_info, encryption_state_synced, #[cfg(feature = "experimental-sliding-sync")] - latest_event, + latest_event: latest_event.map(LatestEvent::new), base_info: base_info.migrate(create), } } From ba21e3bb026d7414cb97e77976c8b0a882da4a8f Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Oct 2023 10:02:03 +0200 Subject: [PATCH 10/18] feat(ui): Fallback to the slow path if `profile` is `None`. --- crates/matrix-sdk-ui/src/timeline/event_item/mod.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 042e20c6f..09a747594 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -149,10 +149,14 @@ impl EventTimelineItem { let room = client.get_room(room_id); let sender_profile = if let Some(room) = room { - room.profile_from_latest_event(&latest_event) - .await - .map(TimelineDetails::Ready) - .unwrap_or(TimelineDetails::Unavailable) + let mut profile = room.profile_from_latest_event(&latest_event).await; + + // Fallback to the slow path. + if profile.is_none() { + profile = room.profile_from_user_id(&sender).await; + } + + profile.map(TimelineDetails::Ready).unwrap_or(TimelineDetails::Unavailable) } else { TimelineDetails::Unavailable }; From ed759cae85bd7c150b6b5c6acfd24a600263771a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Oct 2023 10:11:17 +0200 Subject: [PATCH 11/18] chore(ui): Replace `EventTimelineItem` with `Self`. --- crates/matrix-sdk-ui/src/timeline/event_item/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 09a747594..6e34cb2ce 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -161,7 +161,7 @@ impl EventTimelineItem { TimelineDetails::Unavailable }; - Some(EventTimelineItem::new(sender, sender_profile, timestamp, item_content, event_kind)) + Some(Self::new(sender, sender_profile, timestamp, item_content, event_kind)) } /// Check whether this item is a local echo. From 160f9233fa5ede36d74e837fde53a39b25d8ef9e Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Oct 2023 10:44:50 +0200 Subject: [PATCH 12/18] chore(sdk): Fix imports and features. --- crates/matrix-sdk/src/sliding_sync/cache.rs | 7 +++---- crates/matrix-sdk/src/sliding_sync/mod.rs | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/matrix-sdk/src/sliding_sync/cache.rs b/crates/matrix-sdk/src/sliding_sync/cache.rs index 65ca3bd8b..0c3d64ef2 100644 --- a/crates/matrix-sdk/src/sliding_sync/cache.rs +++ b/crates/matrix-sdk/src/sliding_sync/cache.rs @@ -15,10 +15,9 @@ use super::{ FrozenSlidingSync, FrozenSlidingSyncList, SlidingSync, SlidingSyncList, SlidingSyncPositionMarkers, }; -use crate::{ - sliding_sync::{FrozenSlidingSyncPos, SlidingSyncListCachePolicy}, - Client, Result, -}; +#[cfg(feature = "e2e-encryption")] +use crate::sliding_sync::FrozenSlidingSyncPos; +use crate::{sliding_sync::SlidingSyncListCachePolicy, Client, Result}; /// Be careful: as this is used as a storage key; changing it requires migrating /// data! diff --git a/crates/matrix-sdk/src/sliding_sync/mod.rs b/crates/matrix-sdk/src/sliding_sync/mod.rs index c55bffaff..629e63c87 100644 --- a/crates/matrix-sdk/src/sliding_sync/mod.rs +++ b/crates/matrix-sdk/src/sliding_sync/mod.rs @@ -50,12 +50,13 @@ use tokio::{ use tracing::{debug, error, info, instrument, trace, warn, Instrument, Span}; use url::Url; +#[cfg(feature = "e2e-encryption")] +use self::utils::JoinHandleExt as _; pub use self::{builder::*, error::*, list::*, room::*}; use self::{ cache::restore_sliding_sync_state, client::SlidingSyncResponseProcessor, sticky_parameters::{LazyTransactionId, SlidingSyncStickyManager, StickyData}, - utils::JoinHandleExt as _, }; use crate::{config::RequestConfig, Client, Result}; From 584db1d54c2f96e09ca4200e08a102023329d146 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 2 Oct 2023 10:45:08 +0200 Subject: [PATCH 13/18] chore(base): Fix imports and features. --- crates/matrix-sdk-base/src/latest_event.rs | 15 ++++++++------- crates/matrix-sdk-base/src/rooms/normal.rs | 3 ++- .../src/store/migration_helpers.rs | 3 ++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk-base/src/latest_event.rs b/crates/matrix-sdk-base/src/latest_event.rs index 7f22ff6b4..11c3bdfbc 100644 --- a/crates/matrix-sdk-base/src/latest_event.rs +++ b/crates/matrix-sdk-base/src/latest_event.rs @@ -1,16 +1,15 @@ //! Utilities for working with events to decide whether they are suitable for //! use as a [crate::Room::latest_event]. -#![cfg(all(feature = "e2e-encryption", feature = "experimental-sliding-sync"))] +#![cfg(feature = "experimental-sliding-sync")] use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; -use ruma::{ - events::{ - poll::unstable_start::SyncUnstablePollStartEvent, room::message::SyncRoomMessageEvent, - AnySyncMessageLikeEvent, AnySyncTimelineEvent, - }, - MxcUri, OwnedEventId, +#[cfg(feature = "e2e-encryption")] +use ruma::events::{ + poll::unstable_start::SyncUnstablePollStartEvent, room::message::SyncRoomMessageEvent, + AnySyncMessageLikeEvent, AnySyncTimelineEvent, }; +use ruma::{MxcUri, OwnedEventId}; use serde::{Deserialize, Serialize}; use crate::MinimalRoomMemberEvent; @@ -19,6 +18,7 @@ use crate::MinimalRoomMemberEvent; /// event in a room. Variants starting with Yes indicate that this message could /// be stored, and provide the inner event information, and those starting with /// a No indicate that it could not, and give a reason. +#[cfg(feature = "e2e-encryption")] #[derive(Debug)] pub enum PossibleLatestEvent<'a> { /// This message is suitable - it is an m.room.message @@ -37,6 +37,7 @@ pub enum PossibleLatestEvent<'a> { /// Decide whether an event could be stored as the latest event in a room. /// Returns a LatestEvent representing our decision. +#[cfg(feature = "e2e-encryption")] pub fn is_suitable_for_latest_event(event: &AnySyncTimelineEvent) -> PossibleLatestEvent<'_> { match event { // Suitable - we have an m.room.message that was not redacted diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 898b146b4..979cb1b0c 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -58,9 +58,10 @@ use super::{ members::{MemberInfo, MemberRoomInfo}, BaseRoomInfo, DisplayName, RoomCreateWithCreatorEventContent, RoomMember, }; +#[cfg(feature = "experimental-sliding-sync")] +use crate::latest_event::LatestEvent; use crate::{ deserialized_responses::MemberEvent, - latest_event::LatestEvent, store::{DynStateStore, Result as StoreResult, StateStoreExt}, sync::UnreadNotificationsCount, MinimalStateEvent, OriginalMinimalStateEvent, RoomMemberships, diff --git a/crates/matrix-sdk-base/src/store/migration_helpers.rs b/crates/matrix-sdk-base/src/store/migration_helpers.rs index b9812ae11..438e43294 100644 --- a/crates/matrix-sdk-base/src/store/migration_helpers.rs +++ b/crates/matrix-sdk-base/src/store/migration_helpers.rs @@ -38,9 +38,10 @@ use ruma::{ }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "experimental-sliding-sync")] +use crate::latest_event::LatestEvent; use crate::{ deserialized_responses::SyncOrStrippedState, - latest_event::LatestEvent, rooms::{ normal::{RoomSummary, SyncInfo}, BaseRoomInfo, From 438c51708d62aed04374469a17c5aa4c60516a21 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 Oct 2023 09:52:29 +0200 Subject: [PATCH 14/18] chore: Address feedbacks. --- crates/matrix-sdk-base/src/latest_event.rs | 2 +- .../src/timeline/event_item/content/mod.rs | 18 +++++++++--------- .../src/timeline/event_item/mod.rs | 2 +- crates/matrix-sdk-ui/src/timeline/traits.rs | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/matrix-sdk-base/src/latest_event.rs b/crates/matrix-sdk-base/src/latest_event.rs index 11c3bdfbc..e8aa5bf86 100644 --- a/crates/matrix-sdk-base/src/latest_event.rs +++ b/crates/matrix-sdk-base/src/latest_event.rs @@ -118,7 +118,7 @@ impl LatestEvent { self.event.event_id() } - /// Check whether [`Self`] has a sender a profile. + /// Check whether [`Self`] has a sender profile. pub fn has_sender_profile(&self) -> bool { self.sender_profile.is_some() } diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs index 5af79d733..b6dec3305 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs @@ -109,9 +109,9 @@ pub enum TimelineItemContent { } impl TimelineItemContent { - /// If the supplied event is suitable to be used as a latest_event in a + /// If the supplied event is suitable to be used as a `latest_event` in a /// message preview, extract its contents and wrap it as a - /// TimelineItemContent. + /// `TimelineItemContent`. pub(crate) fn from_latest_event_content( event: AnySyncTimelineEvent, ) -> Option { @@ -146,23 +146,23 @@ impl TimelineItemContent { /// Given some message content that is from an event that we have already /// determined is suitable for use as a latest event in a message preview, - /// extract its contents and wrap it as a TimelineItemContent. + /// extract its contents and wrap it as a `TimelineItemContent`. fn from_suitable_latest_event_content(event: &SyncRoomMessageEvent) -> TimelineItemContent { match event { SyncRoomMessageEvent::Original(event) => { // Grab the content of this event let event_content = event.content.clone(); - // We don't have access to any relations via the AnySyncTimelineEvent (I think - - // andyb) so we pretend there are none. This might be OK for the message preview - // use case. + // We don't have access to any relations via the `AnySyncTimelineEvent` (I think + // - andyb) so we pretend there are none. This might be OK for + // the message preview use case. let relations = BundledMessageLikeRelations::new(); // If this message is a reply, we would look up in this list the message it was // replying to. Since we probably won't show this in the message preview, // it's probably OK to supply an empty list here. - // Message::from_event marks the original event as Unavailable if it can't be - // found inside the timeline_items. + // `Message::from_event` marks the original event as `Unavailable` if it can't + // be found inside the timeline_items. let timeline_items = Vector::new(); TimelineItemContent::Message(Message::from_event( event_content, @@ -174,7 +174,7 @@ impl TimelineItemContent { } } - /// extracts a TimelineItemContent from a poll start event for use as a + /// Extracts a `TimelineItemContent` from a poll start event for use as a /// latest event in a message preview. fn from_suitable_latest_poll_event_content( event: &SyncUnstablePollStartEvent, diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 6e34cb2ce..7c4346752 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -91,7 +91,7 @@ impl EventTimelineItem { } /// If the supplied low-level `SyncTimelineEventy` is suitable for use as - /// the `latest_event `` in a message preview, wrap it as an + /// the `latest_event` in a message preview, wrap it as an /// `EventTimelineItem`. pub async fn from_latest_event( client: Client, diff --git a/crates/matrix-sdk-ui/src/timeline/traits.rs b/crates/matrix-sdk-ui/src/timeline/traits.rs index cde72a4c4..afd7a169b 100644 --- a/crates/matrix-sdk-ui/src/timeline/traits.rs +++ b/crates/matrix-sdk-ui/src/timeline/traits.rs @@ -109,7 +109,7 @@ impl RoomDataProvider for Room { Some(Profile { display_name: latest_event.sender_display_name().map(ToOwned::to_owned), - display_name_ambiguous: latest_event.sender_name_ambiguous().unwrap_or_default(), + display_name_ambiguous: latest_event.sender_name_ambiguous().unwrap_or(false), avatar_url: latest_event.sender_avatar_url().map(ToOwned::to_owned), }) } From c5b137a83c064c2680aa1f4ac6f91e18fc9c77aa Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 Oct 2023 10:24:23 +0200 Subject: [PATCH 15/18] test(ui): Write test for `EventTimelineItem::from_latest_event` with cached sender info. --- .../src/timeline/event_item/mod.rs | 97 ++++++++++++++----- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index 7c4346752..aa4f23ce1 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -505,14 +505,17 @@ mod tests { use matrix_sdk::{config::RequestConfig, Client, ClientBuilder}; use matrix_sdk_base::{ deserialized_responses::SyncTimelineEvent, latest_event::LatestEvent, BaseClient, - SessionMeta, + MinimalStateEvent, OriginalMinimalStateEvent, SessionMeta, }; - use matrix_sdk_test::async_test; + use matrix_sdk_test::{async_test, sync_timeline_event}; use ruma::{ api::{client::sync::sync_events::v4, MatrixVersion}, device_id, events::{ - room::message::{MessageFormat, MessageType}, + room::{ + member::RoomMemberEventContent, + message::{MessageFormat, MessageType}, + }, AnySyncTimelineEvent, }, room_id, @@ -554,7 +557,7 @@ mod tests { } #[async_test] - async fn latest_message_event_can_be_wrapped_as_a_timeline_item_with_sender() { + async fn latest_message_event_can_be_wrapped_as_a_timeline_item_with_sender_from_the_storage() { // Given a sync event that is suitable to be used as a latest_event, and a room // with a member event for the sender @@ -588,34 +591,76 @@ mod tests { ); } + #[async_test] + async fn latest_message_event_can_be_wrapped_as_a_timeline_item_with_sender_from_the_cache() { + // Given a sync event that is suitable to be used as a latest_event, a room, and + // a member even for the sender (which isn't part of the room yet). + + use ruma::owned_mxc_uri; + let room_id = room_id!("!q:x.uk"); + let user_id = user_id!("@t:o.uk"); + let event = message_event(room_id, user_id, "**My M**", "My M", 122344); + let client = logged_in_client(None).await; + + let member_event = MinimalStateEvent::Original( + member_event(room_id, user_id, "Alice Margatroid", "mxc://e.org/SEs") + .deserialize_as::>() + .unwrap(), + ); + + let room = v4::SlidingSyncRoom::new(); + // Do not push the `member_event` inside the room. Let's say it's flying in the + // `StateChanges`. + + // And the room is stored in the client so it can be extracted when needed + let response = response_with_room(room_id, room).await; + client.process_sliding_sync(&response).await.unwrap(); + + // When we construct a timeline event from it + let timeline_item = EventTimelineItem::from_latest_event( + client, + room_id, + LatestEvent::new_with_sender_details(event, Some(member_event), None), + ) + .await + .unwrap(); + + // Then its sender is properly populated + let profile = assert_matches!(timeline_item.sender_profile, TimelineDetails::Ready(p) => p); + assert_eq!( + profile, + Profile { + display_name: Some("Alice Margatroid".to_owned()), + display_name_ambiguous: false, + avatar_url: Some(owned_mxc_uri!("mxc://e.org/SEs")) + } + ); + } + fn member_event( room_id: &RoomId, user_id: &UserId, display_name: &str, avatar_url: &str, ) -> Raw { - Raw::from_json_string( - json!({ - "type": "m.room.member", - "content": { - "avatar_url": avatar_url, - "displayname": display_name, - "membership": "join", - "reason": "" - }, - "event_id": "$143273582443PhrSn:example.org", - "origin_server_ts": 143273583, - "room_id": room_id, - "sender": "@example:example.org", - "state_key": user_id, - "type": "m.room.member", - "unsigned": { - "age": 1234 - } - }) - .to_string(), - ) - .unwrap() + sync_timeline_event!({ + "type": "m.room.member", + "content": { + "avatar_url": avatar_url, + "displayname": display_name, + "membership": "join", + "reason": "" + }, + "event_id": "$143273582443PhrSn:example.org", + "origin_server_ts": 143273583, + "room_id": room_id, + "sender": "@example:example.org", + "state_key": user_id, + "type": "m.room.member", + "unsigned": { + "age": 1234 + } + }) } async fn response_with_room(room_id: &RoomId, room: v4::SlidingSyncRoom) -> v4::Response { From 7842611ad9c38044bbead8bad66eb9aceb61fa30 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 Oct 2023 10:26:08 +0200 Subject: [PATCH 16/18] test: Use the `sync_timeline_event!` macro once more. --- .../src/timeline/event_item/mod.rs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index aa4f23ce1..f1a538013 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -522,7 +522,6 @@ mod tests { serde::Raw, user_id, RoomId, UInt, UserId, }; - use serde_json::json; use super::{EventTimelineItem, Profile}; use crate::timeline::TimelineDetails; @@ -676,25 +675,20 @@ mod tests { formatted_body: &str, ts: u64, ) -> SyncTimelineEvent { - SyncTimelineEvent::new( - Raw::from_json_string( - json!({ - "event_id": "$eventid6", - "sender": user_id, - "origin_server_ts": ts, - "type": "m.room.message", - "room_id": room_id.to_string(), - "content": { - "body": body, - "format": "org.matrix.custom.html", - "formatted_body": formatted_body, - "msgtype": "m.text" - }, - }) - .to_string(), - ) - .unwrap(), - ) + sync_timeline_event!({ + "event_id": "$eventid6", + "sender": user_id, + "origin_server_ts": ts, + "type": "m.room.message", + "room_id": room_id.to_string(), + "content": { + "body": body, + "format": "org.matrix.custom.html", + "formatted_body": formatted_body, + "msgtype": "m.text" + }, + }) + .into() } /// Copied from matrix_sdk_base::sliding_sync::test From a61bc775729fe800d8f61b60eefb003086014ba1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 Oct 2023 11:21:56 +0200 Subject: [PATCH 17/18] chore(base): Clean up. --- crates/matrix-sdk-base/src/latest_event.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/matrix-sdk-base/src/latest_event.rs b/crates/matrix-sdk-base/src/latest_event.rs index e8aa5bf86..1cdfe8ec5 100644 --- a/crates/matrix-sdk-base/src/latest_event.rs +++ b/crates/matrix-sdk-base/src/latest_event.rs @@ -77,7 +77,6 @@ pub struct LatestEvent { #[serde(skip_serializing_if = "Option::is_none")] sender_profile: Option, - // sender_room_member_event: Option, From 76ace82298d458b17883cad603d01a186bc21739 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Mon, 9 Oct 2023 11:22:43 +0200 Subject: [PATCH 18/18] doc(ui): Fix a typo. --- crates/matrix-sdk-ui/src/timeline/event_item/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs index f1a538013..54c015703 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/mod.rs @@ -593,7 +593,7 @@ mod tests { #[async_test] async fn latest_message_event_can_be_wrapped_as_a_timeline_item_with_sender_from_the_cache() { // Given a sync event that is suitable to be used as a latest_event, a room, and - // a member even for the sender (which isn't part of the room yet). + // a member event for the sender (which isn't part of the room yet). use ruma::owned_mxc_uri; let room_id = room_id!("!q:x.uk");