mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-16 03:55:42 -04:00
feat(base) In Sliding Sync, find out which rooms we have left from state events
In Sliding Sync, find out which rooms we have left from state events
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
use std::ops::Deref;
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt,
|
||||
fmt, iter,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -484,10 +484,16 @@ impl BaseClient {
|
||||
changes.stripped_state.insert(room_info.room_id().to_owned(), state_events);
|
||||
}
|
||||
|
||||
/// Process the events provided during a sync.
|
||||
///
|
||||
/// events must be exactly the same list of events that are in raw_events,
|
||||
/// but deserialised. We demand them here to avoid deserialising
|
||||
/// multiple times.
|
||||
#[instrument(skip_all, fields(room_id = ?room_info.room_id))]
|
||||
pub(crate) async fn handle_state(
|
||||
&self,
|
||||
events: &[Raw<AnySyncStateEvent>],
|
||||
raw_events: &[Raw<AnySyncStateEvent>],
|
||||
events: &[AnySyncStateEvent],
|
||||
room_info: &mut RoomInfo,
|
||||
changes: &mut StateChanges,
|
||||
ambiguity_cache: &mut AmbiguityCache,
|
||||
@@ -496,16 +502,8 @@ impl BaseClient {
|
||||
let mut user_ids = BTreeSet::new();
|
||||
let mut profiles = BTreeMap::new();
|
||||
|
||||
for raw_event in events {
|
||||
let event = match raw_event.deserialize() {
|
||||
Ok(ev) => ev,
|
||||
Err(e) => {
|
||||
warn!("Couldn't deserialize state event: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
room_info.handle_state_event(&event);
|
||||
for (raw_event, event) in iter::zip(raw_events, events) {
|
||||
room_info.handle_state_event(event);
|
||||
|
||||
if let AnySyncStateEvent::RoomMember(member) = &event {
|
||||
ambiguity_cache.handle_event(changes, &room_info.room_id, member).await?;
|
||||
@@ -720,9 +718,12 @@ impl BaseClient {
|
||||
room_info.set_prev_batch(new_info.timeline.prev_batch.as_deref());
|
||||
room_info.mark_state_fully_synced();
|
||||
|
||||
let deserialized_events = Self::deserialize_events(&new_info.state.events);
|
||||
|
||||
let mut user_ids = self
|
||||
.handle_state(
|
||||
&new_info.state.events,
|
||||
&deserialized_events,
|
||||
&mut room_info,
|
||||
&mut changes,
|
||||
&mut ambiguity_cache,
|
||||
@@ -806,9 +807,12 @@ impl BaseClient {
|
||||
room_info.mark_as_left();
|
||||
room_info.mark_state_partially_synced();
|
||||
|
||||
let deserialized_events = Self::deserialize_events(&new_info.state.events);
|
||||
|
||||
let mut user_ids = self
|
||||
.handle_state(
|
||||
&new_info.state.events,
|
||||
&deserialized_events,
|
||||
&mut room_info,
|
||||
&mut changes,
|
||||
&mut ambiguity_cache,
|
||||
@@ -1237,6 +1241,21 @@ impl BaseClient {
|
||||
pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<()> {
|
||||
self.ignore_user_list_changes_tx.subscribe()
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_events(
|
||||
raw_events: &[Raw<AnySyncStateEvent>],
|
||||
) -> Vec<AnySyncStateEvent> {
|
||||
raw_events
|
||||
.iter()
|
||||
.filter_map(|raw_event| match raw_event.deserialize() {
|
||||
Ok(ev) => Some(ev),
|
||||
Err(e) => {
|
||||
warn!("Couldn't deserialize state event: {e}");
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BaseClient {
|
||||
|
||||
@@ -25,10 +25,15 @@ use ruma::{
|
||||
ignored_user_list::IgnoredUserListEventContent,
|
||||
receipt::{Receipt, ReceiptThread, ReceiptType},
|
||||
room::{
|
||||
create::RoomCreateEventContent, encryption::RoomEncryptionEventContent,
|
||||
guest_access::GuestAccess, history_visibility::HistoryVisibility, join_rules::JoinRule,
|
||||
member::RoomMemberEventContent, name::RoomNameEventContent,
|
||||
redaction::OriginalSyncRoomRedactionEvent, tombstone::RoomTombstoneEventContent,
|
||||
create::RoomCreateEventContent,
|
||||
encryption::RoomEncryptionEventContent,
|
||||
guest_access::GuestAccess,
|
||||
history_visibility::HistoryVisibility,
|
||||
join_rules::JoinRule,
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
name::RoomNameEventContent,
|
||||
redaction::OriginalSyncRoomRedactionEvent,
|
||||
tombstone::RoomTombstoneEventContent,
|
||||
},
|
||||
tag::Tags,
|
||||
AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent,
|
||||
@@ -87,6 +92,21 @@ pub enum RoomState {
|
||||
Invited,
|
||||
}
|
||||
|
||||
impl From<&MembershipState> for RoomState {
|
||||
fn from(membership_state: &MembershipState) -> Self {
|
||||
// We consider Ban, Knock and Leave to be Left, because they all mean we are not
|
||||
// in the room.
|
||||
match membership_state {
|
||||
MembershipState::Ban => Self::Left,
|
||||
MembershipState::Invite => Self::Invited,
|
||||
MembershipState::Join => Self::Joined,
|
||||
MembershipState::Knock => Self::Left,
|
||||
MembershipState::Leave => Self::Left,
|
||||
_ => panic!("Unexpected MembershipState: {}", membership_state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Room {
|
||||
pub(crate) fn new(
|
||||
own_user_id: &UserId,
|
||||
@@ -680,6 +700,11 @@ impl RoomInfo {
|
||||
self.room_state = RoomState::Invited;
|
||||
}
|
||||
|
||||
/// Set the membership RoomState of this Room
|
||||
pub fn set_state(&mut self, room_state: RoomState) {
|
||||
self.room_state = room_state;
|
||||
}
|
||||
|
||||
/// Mark this Room as having all the members synced.
|
||||
pub fn mark_members_synced(&mut self) {
|
||||
self.members_synced = true;
|
||||
|
||||
@@ -20,6 +20,7 @@ use ruma::{
|
||||
v3::{self, InvitedRoom, RoomSummary},
|
||||
v4::{self, AccountData},
|
||||
},
|
||||
events::AnySyncStateEvent,
|
||||
RoomId,
|
||||
};
|
||||
use tracing::{debug, info, instrument};
|
||||
@@ -191,15 +192,30 @@ impl BaseClient {
|
||||
ambiguity_cache: &mut AmbiguityCache,
|
||||
account_data: &AccountData,
|
||||
) -> Result<(RoomInfo, Option<JoinedRoom>, Option<InvitedRoom>)> {
|
||||
let required_state = Self::deserialize_events(&room_data.required_state);
|
||||
|
||||
// Find or create the room in the store
|
||||
let (room, mut room_info, invited_room) =
|
||||
self.process_sliding_sync_room_membership(room_data, store, room_id, changes).await;
|
||||
let (room, mut room_info, invited_room) = self
|
||||
.process_sliding_sync_room_membership(
|
||||
room_data,
|
||||
&required_state,
|
||||
store,
|
||||
room_id,
|
||||
changes,
|
||||
)
|
||||
.await;
|
||||
|
||||
room_info.mark_state_partially_synced();
|
||||
|
||||
let mut user_ids = if !room_data.required_state.is_empty() {
|
||||
self.handle_state(&room_data.required_state, &mut room_info, changes, ambiguity_cache)
|
||||
.await?
|
||||
let mut user_ids = if !required_state.is_empty() {
|
||||
self.handle_state(
|
||||
&room_data.required_state,
|
||||
&required_state,
|
||||
&mut room_info,
|
||||
changes,
|
||||
ambiguity_cache,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
@@ -269,10 +285,12 @@ impl BaseClient {
|
||||
/// Look through the sliding sync data for this room, find/create it in the
|
||||
/// store, and process any invite information.
|
||||
/// If any invite_state exists, we take it to mean that we are invited to
|
||||
/// this room https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/sync-v3/proposals/3575-sync.md#room-list-parameters
|
||||
/// this room, unless that state contains membership events that specify
|
||||
/// otherwise. https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/sync-v3/proposals/3575-sync.md#room-list-parameters
|
||||
async fn process_sliding_sync_room_membership(
|
||||
&self,
|
||||
room_data: &v4::SlidingSyncRoom,
|
||||
required_state: &[AnySyncStateEvent],
|
||||
store: &Store,
|
||||
room_id: &RoomId,
|
||||
changes: &mut StateChanges,
|
||||
@@ -285,13 +303,13 @@ impl BaseClient {
|
||||
// might not contain an m.room.member event, or they might set the
|
||||
// membership to something other than invite. This would be very
|
||||
// weird behaviour by the server, because invite_state is supposed
|
||||
// to contain an m.room.member. Later, we will call
|
||||
// handle_invited_state, which will reflect any information found in
|
||||
// the real events inside invite_state, but we default to considering this room
|
||||
// invited simply because invite_state exists. This is needed in the normal
|
||||
// case, because the sliding sync server tries to send minimal
|
||||
// state, meaning that we normally actually just receive {"type":
|
||||
// "m.room.member"} with no content at all.
|
||||
// to contain an m.room.member. We will call handle_invited_state, which will
|
||||
// reflect any information found in the real events inside
|
||||
// invite_state, but we default to considering this room invited
|
||||
// simply because invite_state exists. This is needed in the normal
|
||||
// case, because the sliding sync server tries to send minimal state,
|
||||
// meaning that we normally actually just receive {"type": "m.room.member"} with
|
||||
// no content at all.
|
||||
room_info.mark_as_invited();
|
||||
|
||||
self.handle_invited_state(invite_state.as_slice(), &mut room_info, changes);
|
||||
@@ -305,15 +323,44 @@ impl BaseClient {
|
||||
let room = store.get_or_create_room(room_id, RoomState::Joined).await;
|
||||
let mut room_info = room.clone_info();
|
||||
|
||||
// We default to considering this room joined if it's not an invite, but we
|
||||
// expect to find a m.room.member event in the required_state, so
|
||||
// this should get fixed to the real correct value when we call
|
||||
// self.handle_state below.
|
||||
// We default to considering this room joined if it's not an invite. If it's
|
||||
// actually left (and we remembered to request membership events in
|
||||
// our sync request), then we can find this out from the events in
|
||||
// required_state by calling handle_own_room_membership.
|
||||
room_info.mark_as_joined();
|
||||
|
||||
// We don't need to do this in a v2 sync, because the membership of a room can
|
||||
// be figured out by whether the room is in the "join", "leave" etc.
|
||||
// property. In sliding sync we only have invite_state and
|
||||
// required_state, so we must process required_state looking for
|
||||
// relevant membership events.
|
||||
self.handle_own_room_membership(required_state, &mut room_info).await;
|
||||
|
||||
(room, room_info, None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Find any m.room.member events that refer to the current user, and update
|
||||
/// the state in room_info to reflect the "membership" property.
|
||||
pub(crate) async fn handle_own_room_membership(
|
||||
&self,
|
||||
required_state: &[AnySyncStateEvent],
|
||||
room_info: &mut RoomInfo,
|
||||
) {
|
||||
for event in required_state {
|
||||
if let AnySyncStateEvent::RoomMember(member) = &event {
|
||||
// If this event updates the current user's membership, record that in the
|
||||
// room_info.
|
||||
if let Some(meta) = self.session_meta() {
|
||||
if member.sender() == meta.user_id
|
||||
&& member.state_key() == meta.user_id.as_str()
|
||||
{
|
||||
room_info.set_state(member.membership().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_room_properties(room_data: &v4::SlidingSyncRoom, room_info: &mut RoomInfo) {
|
||||
@@ -345,9 +392,11 @@ mod test {
|
||||
device_id, event_id,
|
||||
events::{
|
||||
room::{
|
||||
avatar::RoomAvatarEventContent, canonical_alias::RoomCanonicalAliasEventContent,
|
||||
avatar::RoomAvatarEventContent,
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
StateEventContent,
|
||||
AnySyncStateEvent, StateEventContent,
|
||||
},
|
||||
mxc_uri, room_alias_id, room_id,
|
||||
serde::Raw,
|
||||
@@ -412,10 +461,11 @@ mod test {
|
||||
// Given a logged-in client
|
||||
let client = logged_in_client().await;
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
let user_id = user_id!("@w:e.uk");
|
||||
|
||||
// When I send sliding sync response containing a room with a name
|
||||
let mut room = v4::SlidingSyncRoom::new();
|
||||
set_room_invited(&mut room);
|
||||
set_room_invited(&mut room, user_id);
|
||||
room.name = Some("little room".to_owned());
|
||||
let response = response_with_room(room_id, room).await;
|
||||
client.process_sliding_sync(&response).await.expect("Failed to process sync");
|
||||
@@ -425,6 +475,41 @@ mod test {
|
||||
assert_eq!(client_room.name(), Some("little room".to_owned()));
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn can_be_reinvited_to_a_left_room() {
|
||||
// See https://github.com/matrix-org/matrix-rust-sdk/issues/1834
|
||||
|
||||
// Given a logged-in client
|
||||
let client = logged_in_client().await;
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
let user_id = user_id!("@u:e.uk");
|
||||
|
||||
// When I join...
|
||||
let mut room = v4::SlidingSyncRoom::new();
|
||||
set_room_joined(&mut room, user_id);
|
||||
let response = response_with_room(room_id, room).await;
|
||||
client.process_sliding_sync(&response).await.expect("Failed to process sync");
|
||||
// (sanity: state is join)
|
||||
assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
|
||||
|
||||
// And then leave...
|
||||
let mut room = v4::SlidingSyncRoom::new();
|
||||
set_room_left(&mut room, user_id);
|
||||
let response = response_with_room(room_id, room).await;
|
||||
client.process_sliding_sync(&response).await.expect("Failed to process sync");
|
||||
// (sanity: state is left)
|
||||
assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
|
||||
|
||||
// And then get invited back
|
||||
let mut room = v4::SlidingSyncRoom::new();
|
||||
set_room_invited(&mut room, user_id);
|
||||
let response = response_with_room(room_id, room).await;
|
||||
client.process_sliding_sync(&response).await.expect("Failed to process sync");
|
||||
|
||||
// Then the room is in the invite state
|
||||
assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn avatar_is_found_when_processing_sliding_sync_response() {
|
||||
// Given a logged-in client
|
||||
@@ -450,10 +535,11 @@ mod test {
|
||||
// Given a logged-in client
|
||||
let client = logged_in_client().await;
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
let user_id = user_id!("@u:e.uk");
|
||||
|
||||
// When I send sliding sync response containing an invited room
|
||||
let mut room = v4::SlidingSyncRoom::new();
|
||||
set_room_invited(&mut room);
|
||||
set_room_invited(&mut room, user_id);
|
||||
let response = response_with_room(room_id, room).await;
|
||||
let sync_resp =
|
||||
client.process_sliding_sync(&response).await.expect("Failed to process sync");
|
||||
@@ -477,7 +563,7 @@ mod test {
|
||||
|
||||
// When I send sliding sync response containing an invited room with an avatar
|
||||
let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
|
||||
set_room_invited(&mut room);
|
||||
set_room_invited(&mut room, user_id);
|
||||
let response = response_with_room(room_id, room).await;
|
||||
client.process_sliding_sync(&response).await.expect("Failed to process sync");
|
||||
|
||||
@@ -499,7 +585,7 @@ mod test {
|
||||
|
||||
// When I send sliding sync response containing an invited room with an avatar
|
||||
let mut room = room_with_canonical_alias(room_alias_id, user_id);
|
||||
set_room_invited(&mut room);
|
||||
set_room_invited(&mut room, user_id);
|
||||
let response = response_with_room(room_id, room).await;
|
||||
client.process_sliding_sync(&response).await.expect("Failed to process sync");
|
||||
|
||||
@@ -556,7 +642,7 @@ mod test {
|
||||
room
|
||||
}
|
||||
|
||||
fn set_room_invited(room: &mut v4::SlidingSyncRoom) {
|
||||
fn set_room_invited(room: &mut v4::SlidingSyncRoom, user_id: &UserId) {
|
||||
// MSC3575 shows an almost-empty event to indicate that we are invited to a
|
||||
// room. Just the type is supplied.
|
||||
|
||||
@@ -567,6 +653,22 @@ mod test {
|
||||
.cast();
|
||||
|
||||
room.invite_state = Some(vec![evt]);
|
||||
|
||||
// We expect that there will also be an invite event in the required_state,
|
||||
// assuming you've asked for this type of event.
|
||||
room.required_state.push(make_membership_event(user_id, MembershipState::Invite));
|
||||
}
|
||||
|
||||
fn set_room_joined(room: &mut v4::SlidingSyncRoom, user_id: &UserId) {
|
||||
room.required_state.push(make_membership_event(user_id, MembershipState::Join));
|
||||
}
|
||||
|
||||
fn set_room_left(room: &mut v4::SlidingSyncRoom, user_id: &UserId) {
|
||||
room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
|
||||
}
|
||||
|
||||
fn make_membership_event(user_id: &UserId, state: MembershipState) -> Raw<AnySyncStateEvent> {
|
||||
make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
|
||||
}
|
||||
|
||||
fn make_state_event<C: StateEventContent, E>(
|
||||
|
||||
Reference in New Issue
Block a user