Skip to content

Add spaces filtering to Sliding Sync /sync #17248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/17248.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `spaces` filtering to experimental [MSC3575](https://github.com/matrix-org/matrix-spec-proposals/pull/3575) Sliding Sync `/sync` endpoint.
20 changes: 12 additions & 8 deletions synapse/handlers/room_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,11 +452,15 @@ async def _summarize_local_room(
return _RoomEntry(room_id, room_entry)

# Otherwise, look for child rooms/spaces.
child_events = await self._get_child_events(room_id)
space_child_events = await self._get_space_child_events(room_id)
# Sort the results for stability.
space_child_events = sorted(
space_child_events, key=_child_events_comparison_key
)

if suggested_only:
# we only care about suggested children
child_events = filter(_is_suggested_child_event, child_events)
space_child_events = filter(_is_suggested_child_event, space_child_events)

stripped_events: List[JsonDict] = [
{
Expand All @@ -466,7 +470,7 @@ async def _summarize_local_room(
"sender": e.sender,
"origin_server_ts": e.origin_server_ts,
}
for e in child_events
for e in space_child_events
]
return _RoomEntry(room_id, room_entry, stripped_events)

Expand Down Expand Up @@ -763,11 +767,9 @@ async def _build_room_entry(self, room_id: str, for_federation: bool) -> JsonDic

return room_entry

async def _get_child_events(self, room_id: str) -> Iterable[EventBase]:
async def _get_space_child_events(self, room_id: str) -> Iterable[EventBase]:
"""
Get the child events for a given room.

The returned results are sorted for stability.
Get the space child events for a given room.

Args:
room_id: The room id to get the children of.
Expand All @@ -791,7 +793,9 @@ async def _get_child_events(self, room_id: str) -> Iterable[EventBase]:

# filter out any events without a "via" (which implies it has been redacted),
# and order to ensure we return stable results.
return sorted(filter(_has_valid_via, events), key=_child_events_comparison_key)
filtered_events = filter(_has_valid_via, events)

return filtered_events

async def get_room_summary(
self,
Expand Down
46 changes: 42 additions & 4 deletions synapse/handlers/sliding_sync.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from enum import Enum
from typing import TYPE_CHECKING, AbstractSet, Dict, Final, List, Optional, Tuple
from typing import TYPE_CHECKING, AbstractSet, Dict, Final, List, Optional, Set, Tuple

import attr

Expand Down Expand Up @@ -184,11 +184,13 @@ def __bool__(self) -> bool:
class SlidingSyncHandler:
def __init__(self, hs: "HomeServer"):
self.hs_config = hs.config
self.rooms_to_exclude_globally = hs.config.server.rooms_to_exclude_from_sync
self.store = hs.get_datastores().main
self.storage_controllers = hs.get_storage_controllers()
self.auth_blocking = hs.get_auth_blocking()
self.notifier = hs.get_notifier()
self.event_sources = hs.get_event_sources()
self.rooms_to_exclude_globally = hs.config.server.rooms_to_exclude_from_sync
self.room_summary_handler = hs.get_room_summary_handler()

async def wait_for_sync_for_user(
self,
Expand Down Expand Up @@ -501,7 +503,9 @@ async def filter_rooms(
"""
user_id = user.to_string()

# TODO: Apply filters
# TODO: Re-order filters so that the easiest, most likely to eliminate rooms,
# are first. This way when people use multiple filters, we can eliminate rooms
# and do less work for the subsequent filters.
#
# TODO: Exclude partially stated rooms unless the `required_state` has
# `["m.room.member", "$LAZY"]`
Expand Down Expand Up @@ -535,8 +539,42 @@ async def filter_rooms(
# Only non-DM rooms please
filtered_room_id_set = filtered_room_id_set.difference(dm_room_id_set)

# Filter the room based on the space they belong to according to `m.space.child`
# state events. If multiple spaces are present, a room can be part of any one of
# the listed spaces (OR'd).
if filters.spaces:
raise NotImplementedError()
# Only use spaces that we're joined to to avoid leaking private space
# information that the user is not part of. We could probably allow
# public spaces here but the spec says "joined" only.
joined_space_room_ids = set()
for space_room_id in set(filters.spaces):
# TODO: Is there a good method to look up all space rooms at once? (N+1 query problem)
is_user_in_room = await self.store.check_local_user_in_room(
user_id=user.to_string(), room_id=space_room_id
)

if is_user_in_room:
joined_space_room_ids.add(space_room_id)

# Flatten the child rooms in the spaces
space_child_room_ids: Set[str] = set()
for space_room_id in joined_space_room_ids:
space_child_events = (
await self.room_summary_handler._get_space_child_events(
space_room_id
)
)
space_child_room_ids.update(
event.state_key for event in space_child_events
)
# TODO: The spec says that if the child room has a `m.room.tombstone`
# event, we should recursively navigate until we find the latest room
# and include those IDs (although this point is under scrutiny).

# Only rooms in the spaces please
filtered_room_id_set = filtered_room_id_set.intersection(
space_child_room_ids
)

if filters.is_encrypted:
raise NotImplementedError()
Expand Down
47 changes: 47 additions & 0 deletions synapse/rest/client/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,53 @@ class SlidingSyncList(CommonRoomParameters):
"""

class Filters(RequestBodyModel):
"""
All fields are applied with AND operators, hence if `is_dm: True` and
`is_encrypted: True` then only Encrypted DM rooms will be returned. The absence
of fields implies no filter on that criteria: it does NOT imply `False`.
These fields may be expanded through use of extensions.

Attributes:
is_dm: Flag which only returns rooms present (or not) in the DM section
of account data. If unset, both DM rooms and non-DM rooms are returned.
If False, only non-DM rooms are returned. If True, only DM rooms are
returned.
spaces: Filter the room based on the space they belong to according to
`m.space.child` state events. If multiple spaces are present, a room can
be part of any one of the listed spaces (OR'd). The server will inspect
the `m.space.child` state events for the JOINED space room IDs given.
Servers MUST NOT navigate subspaces. It is up to the client to give a
complete list of spaces to navigate. Only rooms directly mentioned as
`m.space.child` events in these spaces will be returned. Unknown spaces
or spaces the user is not joined to will be ignored.
is_encrypted: Flag which only returns rooms which have an
`m.room.encryption` state event. If unset, both encrypted and
unencrypted rooms are returned. If `False`, only unencrypted rooms are
returned. If `True`, only encrypted rooms are returned.
is_invite: Flag which only returns rooms the user is currently invited
to. If unset, both invited and joined rooms are returned. If `False`, no
invited rooms are returned. If `True`, only invited rooms are returned.
room_types: If specified, only rooms where the `m.room.create` event has
a `type` matching one of the strings in this array will be returned. If
this field is unset, all rooms are returned regardless of type. This can
be used to get the initial set of spaces for an account. For rooms which
do not have a room type, use `null`/`None` to include them.
not_room_types: Same as `room_types` but inverted. This can be used to
filter out spaces from the room list. If a type is in both `room_types`
and `not_room_types`, then `not_room_types` wins and they are not included
in the result.
room_name_like: Filter the room name. Case-insensitive partial matching
e.g 'foo' matches 'abFooab'. The term 'like' is inspired by SQL 'LIKE',
and the text here is similar to '%foo%'.
tags: Filter the room based on its room tags. If multiple tags are
present, a room can have any one of the listed tags (OR'd).
not_tags: Filter the room based on its room tags. Takes priority over
`tags`. For example, a room with tags A and B with filters `tags: [A]`
`not_tags: [B]` would NOT be included because `not_tags` takes priority over
`tags`. This filter is useful if your rooms list does NOT include the
list of favourite rooms again.
"""

is_dm: Optional[StrictBool] = None
spaces: Optional[List[StrictStr]] = None
is_encrypted: Optional[StrictBool] = None
Expand Down
Loading
Loading