mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-06-09 17:04:26 -04:00
fix: Refresh timeline items when their sender's avatar URL changes
This is similar to what happens when a user display name changes, its ambiguity is calculated and if it changes, it reloads the associated timeline events. In fact, this change tries to follow the same strategy as `AmbiguityCache`.
This commit is contained in:
committed by
Damir Jelić
parent
e5a50089c2
commit
7c13b60e28
@@ -65,9 +65,9 @@ use crate::{
|
||||
Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMembersUpdate, RoomState,
|
||||
},
|
||||
store::{
|
||||
BaseStateStore, DynStateStore, MemoryStore, Result as StoreResult, RoomLoadSettings,
|
||||
StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt, StoreConfig,
|
||||
ambiguity_map::AmbiguityCache,
|
||||
AvatarCache, BaseStateStore, DynStateStore, MemoryStore, Result as StoreResult,
|
||||
RoomLoadSettings, StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt,
|
||||
StoreConfig, ambiguity_map::AmbiguityCache,
|
||||
},
|
||||
sync::{RoomUpdates, SyncResponse},
|
||||
};
|
||||
@@ -644,6 +644,7 @@ impl BaseClient {
|
||||
.collect();
|
||||
|
||||
let mut ambiguity_cache = AmbiguityCache::new(self.state_store.inner.clone());
|
||||
let mut avatar_cache = AvatarCache::new(self.state_store.inner.clone());
|
||||
|
||||
let global_account_data_processor =
|
||||
processors::account_data::global(&response.account_data.events);
|
||||
@@ -670,6 +671,7 @@ impl BaseClient {
|
||||
&room_id,
|
||||
requested_required_states,
|
||||
&mut ambiguity_cache,
|
||||
&mut avatar_cache,
|
||||
),
|
||||
joined_room,
|
||||
&mut updated_members_in_room,
|
||||
@@ -693,6 +695,7 @@ impl BaseClient {
|
||||
&room_id,
|
||||
requested_required_states,
|
||||
&mut ambiguity_cache,
|
||||
&mut avatar_cache,
|
||||
),
|
||||
left_room,
|
||||
processors::notification::Notification::new(
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
|
||||
use ruma::RoomId;
|
||||
|
||||
use crate::{RequestedRequiredStates, store::ambiguity_map::AmbiguityCache};
|
||||
use crate::{
|
||||
RequestedRequiredStates,
|
||||
store::{AvatarCache, ambiguity_map::AmbiguityCache},
|
||||
};
|
||||
|
||||
pub mod display_name;
|
||||
pub mod msc4186;
|
||||
@@ -25,6 +28,7 @@ pub struct RoomCreationData<'a> {
|
||||
room_id: &'a RoomId,
|
||||
requested_required_states: &'a RequestedRequiredStates,
|
||||
ambiguity_cache: &'a mut AmbiguityCache,
|
||||
avatar_cache: &'a mut AvatarCache,
|
||||
}
|
||||
|
||||
impl<'a> RoomCreationData<'a> {
|
||||
@@ -32,7 +36,8 @@ impl<'a> RoomCreationData<'a> {
|
||||
room_id: &'a RoomId,
|
||||
requested_required_states: &'a RequestedRequiredStates,
|
||||
ambiguity_cache: &'a mut AmbiguityCache,
|
||||
avatar_cache: &'a mut AvatarCache,
|
||||
) -> Self {
|
||||
Self { room_id, requested_required_states, ambiguity_cache }
|
||||
Self { room_id, requested_required_states, ambiguity_cache, avatar_cache }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ pub async fn update_any_room(
|
||||
) -> Result<Option<(RoomInfo, RoomUpdateKind)>> {
|
||||
let _timer = timer!(tracing::Level::TRACE, "update_any_room");
|
||||
|
||||
let RoomCreationData { room_id, requested_required_states, ambiguity_cache } =
|
||||
let RoomCreationData { room_id, requested_required_states, ambiguity_cache, avatar_cache } =
|
||||
room_creation_data;
|
||||
|
||||
// Read state events from the `required_state` field.
|
||||
@@ -118,6 +118,7 @@ pub async fn update_any_room(
|
||||
raw_state_events,
|
||||
&mut room_info,
|
||||
ambiguity_cache,
|
||||
avatar_cache,
|
||||
&mut new_user_ids,
|
||||
state_store,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
@@ -170,6 +171,7 @@ pub async fn update_any_room(
|
||||
room_info.update_notification_count(notification_count);
|
||||
|
||||
let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
|
||||
let avatar_changes = avatar_cache.remove_changes(room_id);
|
||||
let room_account_data = rooms_account_data.get(room_id);
|
||||
|
||||
match (room_info.state(), maybe_room_update_kind) {
|
||||
@@ -188,6 +190,7 @@ pub async fn update_any_room(
|
||||
ephemeral,
|
||||
notification_count,
|
||||
ambiguity_changes,
|
||||
avatar_changes,
|
||||
)),
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub async fn update_joined_room(
|
||||
notification: notification::Notification<'_>,
|
||||
#[cfg(feature = "e2e-encryption")] e2ee: &e2ee::E2EE<'_>,
|
||||
) -> Result<JoinedRoomUpdate> {
|
||||
let RoomCreationData { room_id, requested_required_states, ambiguity_cache } =
|
||||
let RoomCreationData { room_id, requested_required_states, ambiguity_cache, avatar_cache } =
|
||||
room_creation_data;
|
||||
|
||||
let state_store = notification.state_store;
|
||||
@@ -68,6 +68,7 @@ pub async fn update_joined_room(
|
||||
raw_state_events,
|
||||
&mut room_info,
|
||||
ambiguity_cache,
|
||||
avatar_cache,
|
||||
&mut new_user_ids,
|
||||
state_store,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
@@ -137,6 +138,7 @@ pub async fn update_joined_room(
|
||||
joined_room.ephemeral.events,
|
||||
notification_count,
|
||||
ambiguity_cache.changes.remove(room_id).unwrap_or_default(),
|
||||
avatar_cache.remove_changes(room_id),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -149,7 +151,7 @@ pub async fn update_left_room(
|
||||
notification: notification::Notification<'_>,
|
||||
#[cfg(feature = "e2e-encryption")] e2ee: &e2ee::E2EE<'_>,
|
||||
) -> Result<LeftRoomUpdate> {
|
||||
let RoomCreationData { room_id, requested_required_states, ambiguity_cache } =
|
||||
let RoomCreationData { room_id, requested_required_states, ambiguity_cache, avatar_cache } =
|
||||
room_creation_data;
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
@@ -172,6 +174,7 @@ pub async fn update_left_room(
|
||||
raw_state_events,
|
||||
&mut room_info,
|
||||
ambiguity_cache,
|
||||
avatar_cache,
|
||||
&mut (),
|
||||
state_store,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
|
||||
@@ -45,7 +45,9 @@ pub mod sync {
|
||||
use crate::response_processors::e2ee;
|
||||
use crate::{
|
||||
RoomInfo, RoomInfoNotableUpdateReasons, RoomState,
|
||||
store::{BaseStateStore, Result as StoreResult, ambiguity_map::AmbiguityCache},
|
||||
store::{
|
||||
AvatarCache, BaseStateStore, Result as StoreResult, ambiguity_map::AmbiguityCache,
|
||||
},
|
||||
sync::State,
|
||||
utils::RawStateEventWithKeys,
|
||||
};
|
||||
@@ -94,6 +96,7 @@ pub mod sync {
|
||||
raw_events: Vec<RawStateEventWithKeys<AnySyncStateEvent>>,
|
||||
room_info: &mut RoomInfo,
|
||||
ambiguity_cache: &mut AmbiguityCache,
|
||||
avatar_cache: &mut AvatarCache,
|
||||
new_users: &mut U,
|
||||
state_store: &BaseStateStore,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")] e2ee: &e2ee::E2EE<'_>,
|
||||
@@ -111,6 +114,7 @@ pub mod sync {
|
||||
&room_info.room_id,
|
||||
&mut raw_event,
|
||||
ambiguity_cache,
|
||||
avatar_cache,
|
||||
new_users,
|
||||
)
|
||||
.await?;
|
||||
@@ -177,6 +181,7 @@ pub mod sync {
|
||||
room_id: &RoomId,
|
||||
raw_event: &mut RawStateEventWithKeys<AnySyncStateEvent>,
|
||||
ambiguity_cache: &mut AmbiguityCache,
|
||||
avatar_cache: &mut AvatarCache,
|
||||
new_users: &mut U,
|
||||
) -> StoreResult<()>
|
||||
where
|
||||
@@ -189,6 +194,7 @@ pub mod sync {
|
||||
};
|
||||
|
||||
ambiguity_cache.handle_event(&context.state_changes, room_id, event).await?;
|
||||
avatar_cache.handle_event(&context.state_changes, room_id, event).await?;
|
||||
|
||||
match event.membership() {
|
||||
MembershipState::Join | MembershipState::Invite => {
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::{
|
||||
RequestedRequiredStates,
|
||||
error::Result,
|
||||
response_processors as processors,
|
||||
store::ambiguity_map::AmbiguityCache,
|
||||
store::{AvatarCache, ambiguity_map::AmbiguityCache},
|
||||
sync::{RoomUpdates, SyncResponse},
|
||||
};
|
||||
|
||||
@@ -120,6 +120,7 @@ impl BaseClient {
|
||||
|
||||
let state_store = self.state_store.clone();
|
||||
let mut ambiguity_cache = AmbiguityCache::new(state_store.inner.clone());
|
||||
let mut avatar_cache = AvatarCache::new(state_store.inner.clone());
|
||||
|
||||
let global_account_data_processor =
|
||||
processors::account_data::global(&extensions.account_data.global);
|
||||
@@ -142,6 +143,7 @@ impl BaseClient {
|
||||
room_id,
|
||||
requested_required_states,
|
||||
&mut ambiguity_cache,
|
||||
&mut avatar_cache,
|
||||
),
|
||||
room_response,
|
||||
&extensions.account_data.rooms,
|
||||
|
||||
101
crates/matrix-sdk-base/src/store/avatar_cache.rs
Normal file
101
crates/matrix-sdk-base/src/store/avatar_cache.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::{
|
||||
MxcUri, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
events::room::member::SyncRoomMemberEvent,
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{StateChanges, StateStore, StoreError, store::SaveLockedStateStore};
|
||||
|
||||
/// A cache for keeping track of avatar changes in sync responses.
|
||||
#[derive(Debug)]
|
||||
pub struct AvatarCache {
|
||||
store: SaveLockedStateStore,
|
||||
changes: BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
|
||||
}
|
||||
|
||||
impl AvatarCache {
|
||||
/// Creates a new [`AvatarCache`].
|
||||
pub fn new(store: SaveLockedStateStore) -> Self {
|
||||
Self { store, changes: BTreeMap::new() }
|
||||
}
|
||||
|
||||
/// Processes the room member event and checks if there was any change in
|
||||
/// the avatar URL for the room member.
|
||||
pub async fn handle_event(
|
||||
&mut self,
|
||||
state_changes: &StateChanges,
|
||||
room_id: &RoomId,
|
||||
member_event: &SyncRoomMemberEvent,
|
||||
) -> Result<(), StoreError> {
|
||||
let user_id = member_event.sender();
|
||||
if self.changes.get(room_id).is_some_and(|user_ids| user_ids.contains_key(user_id)) {
|
||||
return Ok(());
|
||||
}
|
||||
match member_event {
|
||||
SyncRoomMemberEvent::Original(original_event) => {
|
||||
let avatar_url = original_event.content.avatar_url.clone();
|
||||
self.add_to_changes_if_needed(state_changes, room_id, user_id, avatar_url).await;
|
||||
}
|
||||
SyncRoomMemberEvent::Redacted(_) => {
|
||||
trace!("Redacted event, discarding avatar change for {:?}", user_id);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_to_changes_if_needed(
|
||||
&mut self,
|
||||
state_changes: &StateChanges,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
avatar: Option<OwnedMxcUri>,
|
||||
) {
|
||||
if !self.is_same_avatar(state_changes, room_id, user_id, avatar.as_deref()).await {
|
||||
trace!("Avatar for {} is different, saving to changes", user_id);
|
||||
let change = self.changes.entry(room_id.to_owned()).or_default();
|
||||
change.insert(user_id.to_owned(), avatar);
|
||||
} else {
|
||||
trace!("Avatar for {} is the same, not saving", user_id);
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_same_avatar(
|
||||
&self,
|
||||
state_changes: &StateChanges,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
avatar: Option<&MxcUri>,
|
||||
) -> bool {
|
||||
let current_avatar = if let Some(event) = state_changes.member(room_id, user_id) {
|
||||
event.content.avatar_url
|
||||
} else {
|
||||
match self.store.get_profile(room_id, user_id).await {
|
||||
Ok(Some(profile)) => profile.content.avatar_url,
|
||||
Ok(None) => None,
|
||||
Err(_) => None,
|
||||
}
|
||||
};
|
||||
|
||||
trace!(
|
||||
"Current avatar for {} in {} is: {:?}, new avatar is: {:?}",
|
||||
user_id, room_id, current_avatar, avatar
|
||||
);
|
||||
|
||||
match (current_avatar, avatar) {
|
||||
(Some(current_avatar), Some(avatar)) => current_avatar == avatar,
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes and returns the avatar changes associated with the [`RoomId`],
|
||||
/// if any.
|
||||
pub fn remove_changes(
|
||||
&mut self,
|
||||
room_id: &RoomId,
|
||||
) -> Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>> {
|
||||
self.changes.remove(room_id)
|
||||
}
|
||||
}
|
||||
@@ -77,10 +77,13 @@ use crate::{
|
||||
};
|
||||
|
||||
pub(crate) mod ambiguity_map;
|
||||
mod avatar_cache;
|
||||
mod memory_store;
|
||||
pub mod migration_helpers;
|
||||
mod send_queue;
|
||||
|
||||
pub use avatar_cache::AvatarCache;
|
||||
|
||||
#[cfg(any(test, feature = "testing"))]
|
||||
pub use self::integration_tests::StateStoreIntegrationTests;
|
||||
#[cfg(feature = "unstable-msc4274")]
|
||||
|
||||
@@ -24,7 +24,7 @@ pub use ruma::api::client::sync::sync_events::v3::{
|
||||
InvitedRoom as InvitedRoomUpdate, KnockedRoom as KnockedRoomUpdate,
|
||||
};
|
||||
use ruma::{
|
||||
OwnedEventId, OwnedRoomId,
|
||||
OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId,
|
||||
api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
|
||||
events::{
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent,
|
||||
@@ -219,6 +219,8 @@ pub struct JoinedRoomUpdate {
|
||||
/// This is a map of event ID of the `m.room.member` event to the
|
||||
/// details of the ambiguity change.
|
||||
pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
|
||||
/// Collection of avatar changes that room member events trigger.
|
||||
pub avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
@@ -243,8 +245,17 @@ impl JoinedRoomUpdate {
|
||||
ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
|
||||
unread_notifications: UnreadNotificationsCount,
|
||||
ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
|
||||
avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
|
||||
) -> Self {
|
||||
Self { unread_notifications, timeline, state, account_data, ephemeral, ambiguity_changes }
|
||||
Self {
|
||||
unread_notifications,
|
||||
timeline,
|
||||
state,
|
||||
account_data,
|
||||
ephemeral,
|
||||
ambiguity_changes,
|
||||
avatar_changes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -273,15 +273,28 @@ pub(in crate::timeline) async fn room_event_cache_updates_task(
|
||||
timeline_controller.handle_ephemeral_events(events).await;
|
||||
}
|
||||
|
||||
RoomEventCacheUpdate::UpdateMembers { ambiguity_changes } => {
|
||||
if !ambiguity_changes.is_empty() {
|
||||
RoomEventCacheUpdate::UpdateMembers { ambiguity_changes, avatar_changes } => {
|
||||
if !ambiguity_changes.is_empty()
|
||||
|| !avatar_changes.as_ref().is_none_or(|avatars| avatars.is_empty())
|
||||
{
|
||||
let member_ambiguity_changes = ambiguity_changes
|
||||
.values()
|
||||
.flat_map(|change| change.user_ids())
|
||||
.collect::<BTreeSet<_>>();
|
||||
timeline_controller
|
||||
.force_update_sender_profiles(&member_ambiguity_changes)
|
||||
.await;
|
||||
|
||||
let mut user_ids_to_update = member_ambiguity_changes;
|
||||
|
||||
if let Some(avatar_changes) = &avatar_changes {
|
||||
let mut user_ids =
|
||||
avatar_changes.keys().map(|u| u.as_ref()).collect::<BTreeSet<_>>();
|
||||
user_ids_to_update.append(&mut user_ids)
|
||||
} else {
|
||||
warn!(
|
||||
"No avatar changes to update for {}, ignoring",
|
||||
room_event_cache.room_id()
|
||||
);
|
||||
}
|
||||
timeline_controller.force_update_sender_profiles(&user_ids_to_update).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ use matrix_sdk_base::{
|
||||
sync::{JoinedRoomUpdate, LeftRoomUpdate, Timeline},
|
||||
};
|
||||
use ruma::{
|
||||
EventId, OwnedEventId, OwnedRoomId, RoomId,
|
||||
EventId, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId,
|
||||
events::{AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent, relation::RelationType},
|
||||
serde::Raw,
|
||||
};
|
||||
@@ -359,7 +359,12 @@ impl RoomEventCache {
|
||||
#[instrument(skip_all, fields(room_id = %self.room_id()))]
|
||||
pub(super) async fn handle_joined_room_update(&self, updates: JoinedRoomUpdate) -> Result<()> {
|
||||
self.inner
|
||||
.handle_timeline(updates.timeline, updates.ephemeral.clone(), updates.ambiguity_changes)
|
||||
.handle_timeline(
|
||||
updates.timeline,
|
||||
updates.ephemeral.clone(),
|
||||
updates.ambiguity_changes,
|
||||
updates.avatar_changes,
|
||||
)
|
||||
.await?;
|
||||
self.inner.handle_account_data(updates.account_data);
|
||||
|
||||
@@ -369,7 +374,9 @@ impl RoomEventCache {
|
||||
/// Handle a [`LeftRoomUpdate`].
|
||||
#[instrument(skip_all, fields(room_id = %self.room_id()))]
|
||||
pub(super) async fn handle_left_room_update(&self, updates: LeftRoomUpdate) -> Result<()> {
|
||||
self.inner.handle_timeline(updates.timeline, Vec::new(), updates.ambiguity_changes).await?;
|
||||
self.inner
|
||||
.handle_timeline(updates.timeline, Vec::new(), updates.ambiguity_changes, None)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -508,12 +515,14 @@ impl RoomEventCacheInner {
|
||||
timeline: Timeline,
|
||||
ephemeral_events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
|
||||
ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
|
||||
avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
|
||||
) -> Result<()> {
|
||||
self.handle_timeline_inner(
|
||||
self.state.write().await?,
|
||||
timeline,
|
||||
ephemeral_events,
|
||||
ambiguity_changes,
|
||||
avatar_changes,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -534,6 +543,7 @@ impl RoomEventCacheInner {
|
||||
Timeline { limited: false, prev_batch: None, events: vec![event] },
|
||||
Vec::new(),
|
||||
BTreeMap::new(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -547,11 +557,13 @@ impl RoomEventCacheInner {
|
||||
timeline: Timeline,
|
||||
ephemeral_events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
|
||||
ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
|
||||
avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
|
||||
) -> Result<()> {
|
||||
if timeline.events.is_empty()
|
||||
&& timeline.prev_batch.is_none()
|
||||
&& ephemeral_events.is_empty()
|
||||
&& ambiguity_changes.is_empty()
|
||||
&& avatar_changes.as_ref().is_none_or(|avatars| avatars.is_empty())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
@@ -587,9 +599,11 @@ impl RoomEventCacheInner {
|
||||
.send(RoomEventCacheUpdate::AddEphemeralEvents { events: ephemeral_events }, None);
|
||||
}
|
||||
|
||||
if !ambiguity_changes.is_empty() {
|
||||
self.update_sender
|
||||
.send(RoomEventCacheUpdate::UpdateMembers { ambiguity_changes }, None);
|
||||
if !ambiguity_changes.is_empty() || avatar_changes.as_ref().is_some_and(|c| !c.is_empty()) {
|
||||
self.update_sender.send(
|
||||
RoomEventCacheUpdate::UpdateMembers { ambiguity_changes, avatar_changes },
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -19,7 +19,10 @@ use matrix_sdk_base::{
|
||||
event_cache::{Event, Gap},
|
||||
linked_chunk::{self, OwnedLinkedChunkId},
|
||||
};
|
||||
use ruma::{OwnedEventId, OwnedRoomId, events::AnySyncEphemeralRoomEvent, serde::Raw};
|
||||
use ruma::{
|
||||
OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, events::AnySyncEphemeralRoomEvent,
|
||||
serde::Raw,
|
||||
};
|
||||
use tokio::sync::broadcast::{Receiver, Sender};
|
||||
|
||||
use super::super::TimelineVectorDiffs;
|
||||
@@ -40,6 +43,9 @@ pub enum RoomEventCacheUpdate {
|
||||
/// This is a map of event ID of the `m.room.member` event to the
|
||||
/// details of the ambiguity change.
|
||||
ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
|
||||
|
||||
/// Collection of avatar changes that room member events trigger.
|
||||
avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
|
||||
},
|
||||
|
||||
/// The room has received updates for the timeline as _diffs_.
|
||||
|
||||
@@ -209,6 +209,7 @@ impl Client {
|
||||
account_data,
|
||||
ephemeral,
|
||||
ambiguity_changes: _,
|
||||
avatar_changes: _,
|
||||
} = room_info;
|
||||
|
||||
let room = Some(&room);
|
||||
|
||||
@@ -8,6 +8,7 @@ use matrix_sdk::{
|
||||
assert_let_timeout,
|
||||
authentication::oauth::{OAuthError, error::OAuthTokenRevocationError},
|
||||
config::{RequestConfig, StoreConfig, SyncSettings, SyncToken},
|
||||
event_cache::RoomEventCacheUpdate,
|
||||
live_locations_observer::BeaconInfoUpdate,
|
||||
sleep::sleep,
|
||||
store::{RoomLoadSettings, ThreadSubscriptionStatus},
|
||||
@@ -55,7 +56,7 @@ use ruma::{
|
||||
},
|
||||
tag::{TagInfo, TagName, Tags},
|
||||
},
|
||||
owned_device_id, owned_event_id, owned_room_id, owned_user_id,
|
||||
owned_device_id, owned_event_id, owned_mxc_uri, owned_room_id, owned_user_id,
|
||||
room::JoinRule,
|
||||
room_id,
|
||||
serde::Raw,
|
||||
@@ -1266,6 +1267,138 @@ async fn test_test_ambiguity_changes() {
|
||||
assert_pending!(updates);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_avatar_url_changes() {
|
||||
let (client, server) = logged_in_client_with_server().await;
|
||||
|
||||
let example_id = user_id!("@example:localhost");
|
||||
let example_2_id = user_id!("@example_2:localhost");
|
||||
|
||||
let mut updates = BroadcastStream::new(client.subscribe_to_room_updates(&DEFAULT_TEST_ROOM_ID));
|
||||
assert_pending!(updates);
|
||||
|
||||
// Initial sync, adds 2 members.
|
||||
mock_sync(&server, &*test_json::SYNC, None).await;
|
||||
let response =
|
||||
client.sync_once(SyncSettings::default().token(SyncToken::NoToken)).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
// No changes since the users didn't have any avatar URLs.
|
||||
assert!(response.rooms.joined.get(*DEFAULT_TEST_ROOM_ID).unwrap().avatar_changes.is_none());
|
||||
|
||||
let changes = assert_next_matches!(updates, Ok(RoomUpdate::Joined { updates, .. }) => updates.avatar_changes);
|
||||
assert!(changes.is_none());
|
||||
|
||||
// Subscribe to the event cache to receive RoomEventCacheUpdate
|
||||
client.event_cache().subscribe().expect("event cache subscription");
|
||||
let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("room");
|
||||
let (room_cache, _handle) = room.event_cache().await.expect("room cache");
|
||||
let (_, mut subscriber) = room_cache.subscribe().await.expect("subscription");
|
||||
|
||||
// Now we sync a room member with an avatar URL.
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
let joined_room = JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID).add_state_bulk([
|
||||
sync_state_event!({
|
||||
"content": {
|
||||
"avatar_url": "mxc://localhost/avatar",
|
||||
"displayname": "the first example",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": event_id!("$example_avatar"),
|
||||
"origin_server_ts": 151800140,
|
||||
"sender": example_id,
|
||||
"state_key": example_id,
|
||||
"type": "m.room.member",
|
||||
}),
|
||||
sync_state_event!({
|
||||
"content": {
|
||||
"avatar_url": "mxc://localhost/avatar2",
|
||||
"displayname": "the second example",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": event_id!("$example_avatar_2"),
|
||||
"origin_server_ts": 151800140,
|
||||
"sender": example_2_id,
|
||||
"state_key": example_2_id,
|
||||
"type": "m.room.member",
|
||||
}),
|
||||
]);
|
||||
sync_builder.add_joined_room(joined_room);
|
||||
|
||||
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
|
||||
client.sync_once(SyncSettings::default().token(SyncToken::NoToken)).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let changes = assert_next_matches!(updates, Ok(RoomUpdate::Joined { updates, .. }) => updates.avatar_changes.expect("avatar changes") );
|
||||
assert_eq!(changes.len(), 2);
|
||||
assert_let!(Some(Some(avatar_url)) = changes.get(example_id));
|
||||
assert_eq!(avatar_url, "mxc://localhost/avatar");
|
||||
assert_let!(Some(Some(avatar_url)) = changes.get(example_2_id));
|
||||
assert_eq!(avatar_url, "mxc://localhost/avatar2");
|
||||
|
||||
// The room event cache emits a RoomEventCacheUpdate when the avatar URL
|
||||
// changes. This will trigger a timeline item refresh.
|
||||
let changes = subscriber.recv().await.expect("subscription event");
|
||||
assert_let!(RoomEventCacheUpdate::UpdateMembers { avatar_changes, .. } = changes);
|
||||
assert!(avatar_changes.is_some());
|
||||
assert_eq!(
|
||||
avatar_changes.unwrap(),
|
||||
BTreeMap::from([
|
||||
(example_id.to_owned(), Some(owned_mxc_uri!("mxc://localhost/avatar"))),
|
||||
(example_2_id.to_owned(), Some(owned_mxc_uri!("mxc://localhost/avatar2")))
|
||||
])
|
||||
);
|
||||
|
||||
// And after that, receive the first room member without an avatar URL.
|
||||
let joined_room =
|
||||
JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID).add_state_bulk([sync_state_event!({
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "the first example",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": event_id!("$example_avatar_removal"),
|
||||
"origin_server_ts": 151800141,
|
||||
"sender": example_id,
|
||||
"state_key": example_id,
|
||||
"type": "m.room.member",
|
||||
})]);
|
||||
sync_builder.add_joined_room(joined_room);
|
||||
|
||||
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
|
||||
client.sync_once(SyncSettings::default().token(SyncToken::NoToken)).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
// There is a single change: the avatar is now None
|
||||
let changes = assert_next_matches!(updates, Ok(RoomUpdate::Joined { updates, .. }) => updates.avatar_changes.expect("avatar changes") );
|
||||
assert_eq!(changes.len(), 1);
|
||||
assert_let!(Some(None) = changes.get(example_id));
|
||||
|
||||
// If we receive the same event again, nothing should happen
|
||||
let joined_room =
|
||||
JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID).add_state_bulk([sync_state_event!({
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "the first example",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": event_id!("$example_avatar_removal"),
|
||||
"origin_server_ts": 151800141,
|
||||
"sender": example_id,
|
||||
"state_key": example_id,
|
||||
"type": "m.room.member",
|
||||
})]);
|
||||
sync_builder.add_joined_room(joined_room);
|
||||
|
||||
mock_sync(&server, sync_builder.build_json_sync_response(), None).await;
|
||||
client.sync_once(SyncSettings::default().token(SyncToken::NoToken)).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
// There aren't any changes
|
||||
let changes = assert_next_matches!(updates, Ok(RoomUpdate::Joined { updates, .. }) => updates.avatar_changes );
|
||||
assert!(changes.is_none());
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[async_test]
|
||||
async fn test_rooms_stream() {
|
||||
|
||||
Reference in New Issue
Block a user