mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-13 12:16:54 +00:00
198 lines
5.5 KiB
Rust
198 lines
5.5 KiB
Rust
use std::collections::HashSet;
|
|
|
|
use conduwuit::{
|
|
Result, at,
|
|
matrix::{Event, pdu::PduEvent},
|
|
utils::{
|
|
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
|
|
stream::{BroadbandExt, TryIgnore},
|
|
},
|
|
};
|
|
use conduwuit_service::{
|
|
Services,
|
|
rooms::{lazy_loading::MemberSet, short::ShortStateHash},
|
|
};
|
|
use futures::{FutureExt, StreamExt};
|
|
use ruma::{OwnedEventId, UserId, events::StateEventType};
|
|
use tracing::trace;
|
|
|
|
use crate::client::TimelinePdus;
|
|
|
|
/// Calculate the state events to include in an initial sync response.
|
|
///
|
|
/// If lazy-loading is enabled (`lazily_loaded_members` is Some), the returned
|
|
/// Vec will include the membership events of exclusively the members in
|
|
/// `lazily_loaded_members`.
|
|
#[tracing::instrument(
|
|
name = "initial",
|
|
level = "trace",
|
|
skip_all,
|
|
fields(current_shortstatehash)
|
|
)]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(super) async fn build_state_initial(
|
|
services: &Services,
|
|
sender_user: &UserId,
|
|
timeline_end_shortstatehash: ShortStateHash,
|
|
timeline: &TimelinePdus,
|
|
use_state_after: bool,
|
|
lazily_loaded_members: Option<&MemberSet>,
|
|
) -> Result<Vec<PduEvent>> {
|
|
let event_ids_in_timeline: HashSet<_> =
|
|
timeline.pdus.iter().map(|pdu| &pdu.1.event_id).collect();
|
|
|
|
// load the keys and event IDs of the state events at the start of the timeline
|
|
let (shortstatekeys, event_ids): (Vec<_>, Vec<_>) = services
|
|
.rooms
|
|
.state_accessor
|
|
.state_full_ids(timeline_end_shortstatehash)
|
|
.ready_filter(|(_, event_id)| {
|
|
use_state_after || !event_ids_in_timeline.contains(event_id)
|
|
})
|
|
.unzip()
|
|
.await;
|
|
|
|
trace!("performing initial sync of {} state events", event_ids.len());
|
|
|
|
services
|
|
.rooms
|
|
.short
|
|
// look up the full state keys
|
|
.multi_get_statekey_from_short(shortstatekeys.into_iter().stream())
|
|
.zip(event_ids.into_iter().stream())
|
|
.ready_filter_map(|item| Some((item.0.ok()?, item.1)))
|
|
.ready_filter_map(|((event_type, state_key), event_id)| {
|
|
if let Some(lazily_loaded_members) = lazily_loaded_members {
|
|
/*
|
|
if lazy loading is enabled, filter out membership events which aren't for a user
|
|
included in `lazily_loaded_members` or for the user requesting the sync.
|
|
*/
|
|
let event_is_redundant = event_type == StateEventType::RoomMember
|
|
&& state_key.as_str().try_into().is_ok_and(|user_id: &UserId| {
|
|
sender_user != user_id && !lazily_loaded_members.contains(user_id)
|
|
});
|
|
|
|
event_is_redundant.or_some(event_id)
|
|
} else {
|
|
Some(event_id)
|
|
}
|
|
})
|
|
.broad_filter_map(|event_id: OwnedEventId| async move {
|
|
services.rooms.timeline.get_pdu(&event_id).await.ok()
|
|
})
|
|
.collect()
|
|
.map(Ok)
|
|
.await
|
|
}
|
|
|
|
/// Calculate the state events to include in an incremental sync response.
|
|
///
|
|
/// If lazy-loading is enabled (`lazily_loaded_members` is Some), the returned
|
|
/// Vec will include the membership events of all the members in
|
|
/// `lazily_loaded_members`.
|
|
#[tracing::instrument(name = "incremental", level = "trace", skip_all)]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(super) async fn build_state_incremental<'a>(
|
|
services: &Services,
|
|
sender_user: &'a UserId,
|
|
last_sync_end_shortstatehash: ShortStateHash,
|
|
timeline_end_shortstatehash: ShortStateHash,
|
|
timeline: &TimelinePdus,
|
|
use_state_after: bool,
|
|
lazily_loaded_members: Option<&'a MemberSet>,
|
|
) -> Result<Vec<PduEvent>> {
|
|
let mut state_event_ids: HashSet<OwnedEventId> = HashSet::new();
|
|
|
|
trace!(
|
|
%use_state_after,
|
|
%last_sync_end_shortstatehash,
|
|
%timeline_end_shortstatehash,
|
|
"computing state for incremental sync"
|
|
);
|
|
|
|
// Fetch lazy-loaded membership events if lazy-loading is enabled
|
|
if let Some(lazily_loaded_members) = lazily_loaded_members
|
|
&& !lazily_loaded_members.is_empty()
|
|
{
|
|
trace!("including lazy membership events for members: {:?}", lazily_loaded_members);
|
|
|
|
services
|
|
.rooms
|
|
.short
|
|
.multi_get_eventid_from_short::<'_, OwnedEventId, _>(
|
|
lazily_loaded_members
|
|
.iter()
|
|
.stream()
|
|
.broad_filter_map(|user_id| async move {
|
|
if user_id == sender_user {
|
|
return None;
|
|
}
|
|
|
|
services
|
|
.rooms
|
|
.state_accessor
|
|
.state_get_shortid(
|
|
timeline_end_shortstatehash,
|
|
&StateEventType::RoomMember,
|
|
user_id.as_str(),
|
|
)
|
|
.ok()
|
|
.await
|
|
}),
|
|
)
|
|
.ignore_err()
|
|
.ready_for_each(|event_id| {
|
|
state_event_ids.insert(event_id);
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Fetch the state events added since the last sync.
|
|
services
|
|
.rooms
|
|
.short
|
|
.multi_get_eventid_from_short::<'_, OwnedEventId, _>(
|
|
services
|
|
.rooms
|
|
.state_accessor
|
|
.state_added((last_sync_end_shortstatehash, timeline_end_shortstatehash))
|
|
.await?
|
|
.stream()
|
|
.map(at!(1)),
|
|
)
|
|
.ignore_err()
|
|
.ready_for_each(|event_id| {
|
|
state_event_ids.insert(event_id);
|
|
})
|
|
.await;
|
|
|
|
if !use_state_after {
|
|
// If state_after isn't enabled, filter out state events which also exist
|
|
// in the timeline. If splits exist in the DAG, this may not be exactly the same
|
|
// thing as the state diff ending at the start of the timeline, but Synapse
|
|
// also does this and it's technically more useful behavior anyway.
|
|
// See: https://github.com/element-hq/synapse/issues/16941
|
|
|
|
for (_, pdu) in &timeline.pdus {
|
|
state_event_ids.remove(pdu.event_id());
|
|
}
|
|
}
|
|
|
|
// Finally, fetch the PDU contents and collect them into a vec
|
|
let state_diff_pdus = state_event_ids
|
|
.stream()
|
|
.broad_filter_map(|event_id| async move {
|
|
services
|
|
.rooms
|
|
.timeline
|
|
.get_non_outlier_pdu(&event_id)
|
|
.await
|
|
.ok()
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.await;
|
|
|
|
trace!(?state_diff_pdus, "collected state PDUs for incremental sync");
|
|
Ok(state_diff_pdus)
|
|
}
|