refactor(sync/v3): Extract left room timeline logic into its own function

This commit is contained in:
Ginger
2025-12-03 16:04:32 +00:00
parent 07199f9f17
commit fa74747ab1
+103 -80
View File
@@ -15,7 +15,7 @@ use ruma::{
uint, uint,
}; };
use serde_json::value::RawValue; use serde_json::value::RawValue;
use service::Services; use service::{Services, rooms::short::ShortStateHash};
use crate::client::{ use crate::client::{
TimelinePdus, ignored_filter, TimelinePdus, ignored_filter,
@@ -41,7 +41,7 @@ pub(super) async fn load_left_room(
services: &Services, services: &Services,
sync_context: SyncContext<'_>, sync_context: SyncContext<'_>,
ref room_id: OwnedRoomId, ref room_id: OwnedRoomId,
leave_pdu: Option<PduEvent>, leave_membership_event: Option<PduEvent>,
) -> Result<Option<LeftRoom>> { ) -> Result<Option<LeftRoom>> {
let SyncContext { let SyncContext {
syncing_user, syncing_user,
@@ -77,9 +77,9 @@ pub(super) async fn load_left_room(
return Ok(None); return Ok(None);
} }
if let Some(ref leave_pdu) = leave_pdu { if let Some(ref leave_membership_event) = leave_membership_event {
debug_assert_eq!( debug_assert_eq!(
leave_pdu.kind, leave_membership_event.kind,
TimelineEventType::RoomMember, TimelineEventType::RoomMember,
"leave PDU should be m.room.member" "leave PDU should be m.room.member"
); );
@@ -87,36 +87,37 @@ pub(super) async fn load_left_room(
let does_not_exist = services.rooms.metadata.exists(room_id).eq(&false).await; let does_not_exist = services.rooms.metadata.exists(room_id).eq(&false).await;
let (timeline, state_events) = match leave_pdu { let (timeline, state_events) = match leave_membership_event {
| Some(leave_pdu) if does_not_exist => { | Some(leave_membership_event) if does_not_exist => {
/* /*
we have none PDUs with left beef for this room, likely because it was a rejected invite to a room we have none PDUs with left beef for this room, likely because it was a rejected invite to a room
which nobody on this homeserver is in. `leave_pdu` is the remote-assisted outlier leave event for the room, which nobody on this homeserver is in. `leave_pdu` is the remote-assisted outlier leave event for the room,
which is all we can send to the client. which is all we can send to the client.
*/ */
trace!("syncing remote-assisted leave PDU"); trace!("syncing remote-assisted leave PDU");
(TimelinePdus::default(), vec![leave_pdu]) (TimelinePdus::default(), vec![leave_membership_event])
}, },
| Some(leave_pdu) => { | Some(leave_membership_event) => {
// we have this room in our DB, and can fetch the state and timeline from when // we have this room in our DB, and can fetch the state and timeline from when
// the user left if they're allowed to see it. // the user left if they're allowed to see it.
let leave_state_key = syncing_user; let leave_state_key = syncing_user;
debug_assert_eq!( debug_assert_eq!(
Some(leave_state_key.as_str()), Some(leave_state_key.as_str()),
leave_pdu.state_key(), leave_membership_event.state_key(),
"leave PDU should be for the user requesting the sync" "leave PDU should be for the user requesting the sync"
); );
// the shortstatehash of the state _immediately before_ the syncing user left // the shortstatehash of the state _immediately before_ the syncing user left
// this room. the state represented here _does not_ include `leave_pdu`. // this room. the state represented here _does not_ include
// `leave_membership_event`.
let leave_shortstatehash = services let leave_shortstatehash = services
.rooms .rooms
.state_accessor .state_accessor
.pdu_shortstatehash(&leave_pdu.event_id) .pdu_shortstatehash(&leave_membership_event.event_id)
.await?; .await?;
let prev_member_event = services let prev_membership_event = services
.rooms .rooms
.state_accessor .state_accessor
.state_get( .state_get(
@@ -125,20 +126,100 @@ pub(super) async fn load_left_room(
leave_state_key.as_str(), leave_state_key.as_str(),
) )
.await?; .await?;
let current_membership: RoomMemberEventContent = leave_pdu.get_content()?; let current_membership: RoomMemberEventContent =
let prev_membership: RoomMemberEventContent = prev_member_event.get_content()?; leave_membership_event.get_content()?;
let prev_membership: RoomMemberEventContent = prev_membership_event.get_content()?;
match current_membership.membership_change( match current_membership.membership_change(
Some(prev_membership.details()), Some(prev_membership.details()),
&leave_pdu.sender, &leave_membership_event.sender,
leave_state_key, leave_state_key,
) { ) {
| MembershipChange::Left => { | MembershipChange::Left => {
// if the user went from `join` to `leave`, they should be able to view the // if the user went from `join` to `leave`, they should be able to view the
// timeline. // timeline.
let timeline_start_count = build_left_state_and_timeline(
if let Some(last_sync_end_count) = last_sync_end_count { services,
sync_context,
room_id,
leave_membership_event,
leave_shortstatehash,
prev_membership_event,
)
.await?
},
| other_membership => {
// otherwise, the user should not be able to view the timeline.
// only return their leave event.
trace!(
?other_membership,
"user did not leave happily, only syncing leave event"
);
(TimelinePdus::default(), vec![leave_membership_event])
},
}
},
| None => {
/*
no leave event was actually sent in this room, but we still need to pretend
like the user left it. this is usually because the room was banned by a server admin.
if this is an incremental sync, generate a fake leave event to make the room vanish from clients.
otherwise we don't tell the client about this room at all.
*/
if last_sync_end_count.is_none() {
return Ok(None);
}
trace!("syncing dummy leave event");
(TimelinePdus::default(), vec![create_dummy_leave_event(
services,
sync_context,
room_id,
)])
},
};
let raw_timeline_pdus = timeline
.pdus
.into_iter()
.stream()
// filter out ignored events from the timeline
.wide_filter_map(|item| ignored_filter(services, item, syncing_user))
.map(at!(1))
.map(Event::into_format)
.collect::<Vec<_>>()
.await;
Ok(Some(LeftRoom {
account_data: RoomAccountData { events: Vec::new() },
timeline: Timeline {
limited: timeline.limited,
prev_batch: Some(current_count.to_string()),
events: raw_timeline_pdus,
},
state: State {
events: state_events.into_iter().map(Event::into_format).collect(),
},
}))
}
async fn build_left_state_and_timeline(
services: &Services,
sync_context: SyncContext<'_>,
room_id: &RoomId,
leave_membership_event: PduEvent,
leave_shortstatehash: ShortStateHash,
prev_membership_event: PduEvent,
) -> Result<(TimelinePdus, Vec<PduEvent>)> {
let SyncContext {
syncing_user,
last_sync_end_count,
filter,
..
} = sync_context;
let timeline_start_count = if let Some(last_sync_end_count) = last_sync_end_count {
// for incremental syncs, start the timeline after `since` // for incremental syncs, start the timeline after `since`
PduCount::Normal(last_sync_end_count) PduCount::Normal(last_sync_end_count)
} else { } else {
@@ -149,7 +230,7 @@ pub(super) async fn load_left_room(
services services
.rooms .rooms
.timeline .timeline
.get_pdu_count(&prev_member_event.event_id) .get_pdu_count(&prev_membership_event.event_id)
.await? .await?
}; };
@@ -157,7 +238,7 @@ pub(super) async fn load_left_room(
let timeline_end_count = services let timeline_end_count = services
.rooms .rooms
.timeline .timeline
.get_pdu_count(leave_pdu.event_id()) .get_pdu_count(leave_membership_event.event_id())
.await?; .await?;
// limit the timeline using the same logic as for joined rooms // limit the timeline using the same logic as for joined rooms
@@ -196,12 +277,8 @@ pub(super) async fn load_left_room(
leave_shortstatehash leave_shortstatehash
}; };
let lazily_loaded_members = prepare_lazily_loaded_members( let lazily_loaded_members =
services, prepare_lazily_loaded_members(services, sync_context, room_id, timeline.senders());
sync_context,
room_id,
timeline.senders(),
);
let (timeline_start_shortstatehash, lazily_loaded_members) = let (timeline_start_shortstatehash, lazily_loaded_members) =
join(timeline_start_shortstatehash, lazily_loaded_members).await; join(timeline_start_shortstatehash, lazily_loaded_members).await;
@@ -255,61 +332,7 @@ pub(super) async fn load_left_room(
state.len() state.len()
); );
(timeline, state) Ok((timeline, state))
},
| other_membership => {
// otherwise, the user should not be able to view the timeline.
// only return their leave event.
trace!(
?other_membership,
"user did not leave happily, only syncing leave event"
);
(TimelinePdus::default(), vec![leave_pdu])
},
}
},
| None => {
/*
no leave event was actually sent in this room, but we still need to pretend
like the user left it. this is usually because the room was banned by a server admin.
if this is an incremental sync, generate a fake leave event to make the room vanish from clients.
otherwise we don't tell the client about this room at all.
*/
if last_sync_end_count.is_none() {
return Ok(None);
}
trace!("syncing dummy leave event");
(TimelinePdus::default(), vec![create_dummy_leave_event(
services,
sync_context,
room_id,
)])
},
};
let raw_timeline_pdus = timeline
.pdus
.into_iter()
.stream()
// filter out ignored events from the timeline
.wide_filter_map(|item| ignored_filter(services, item, syncing_user))
.map(at!(1))
.map(Event::into_format)
.collect::<Vec<_>>()
.await;
Ok(Some(LeftRoom {
account_data: RoomAccountData { events: Vec::new() },
timeline: Timeline {
limited: timeline.limited,
prev_batch: Some(current_count.to_string()),
events: raw_timeline_pdus,
},
state: State {
events: state_events.into_iter().map(Event::into_format).collect(),
},
}))
} }
fn create_dummy_leave_event( fn create_dummy_leave_event(