Compare commits

...

8 Commits

Author SHA1 Message Date
David Robertson
18df1c142a WIP fix bug? 2021-10-08 17:18:00 +01:00
David Robertson
3303dda8a8 changed -> joined 2021-10-08 17:18:00 +01:00
David Robertson
cd8ecee5e3 Rename to emphasise remote profile change+comments 2021-10-08 17:18:00 +01:00
David Robertson
79059f4e1e Remove redundant early returns. 2021-10-08 17:18:00 +01:00
David Robertson
cf0f93860c Group together the now_false cases
Justification:

- the only way to hit the final else branch is if `change is
  MatchChange.now_false`
- if `not is_in_room`, then the `state_key` will be
  one of the `user_id in user_ids` and so we'll call
  `self._handle_remove_user` with the `state_key` and return early
- therefore the call to `_handle_remove_user` in the final else branch
  was only hit if `is_in_room`, and so it can be moved to first `else`
  branch.

Having made this change we now have three mutually exclusive
cases (now_false, now_true, no_change). Therefore I can change if->elif
for the no_change branch.
2021-10-08 17:18:00 +01:00
David Robertson
0391b5ca15 if -> elif number 1
Suppose `change is MatchChange.no_change`. Then we will return on 332,
and hit neither the now_true branch nor the `else` branch. Therefore the
`if` can be turned into an `elif` with no behaviour change.
2021-10-08 17:18:00 +01:00
David Robertson
5e99901ab4 Discard excluded users early 2021-10-08 17:18:00 +01:00
David Robertson
aa4f0fe96a Pull out _handle_room_membership_event 2021-10-08 17:17:56 +01:00
2 changed files with 109 additions and 61 deletions

View File

@@ -196,63 +196,12 @@ class UserDirectoryHandler(StateDeltasHandler):
room_id, prev_event_id, event_id, typ
)
elif typ == EventTypes.Member:
change = await self._get_key_change(
await self._handle_room_membership_event(
room_id,
prev_event_id,
event_id,
key_name="membership",
public_value=Membership.JOIN,
state_key,
)
is_remote = not self.is_mine_id(state_key)
if change is MatchChange.now_false:
# Need to check if the server left the room entirely, if so
# we might need to remove all the users in that room
is_in_room = await self.store.is_host_joined(
room_id, self.server_name
)
if not is_in_room:
logger.debug("Server left room: %r", room_id)
# Fetch all the users that we marked as being in user
# directory due to being in the room and then check if
# need to remove those users or not
user_ids = await self.store.get_users_in_dir_due_to_room(
room_id
)
for user_id in user_ids:
await self._handle_remove_user(room_id, user_id)
continue
else:
logger.debug("Server is still in room: %r", room_id)
include_in_dir = (
is_remote
or await self.store.should_include_local_user_in_dir(state_key)
)
if include_in_dir:
if change is MatchChange.no_change:
# Handle any profile changes for remote users.
# (For local users we are not forced to scan membership
# events; instead the rest of the application calls
# `handle_local_profile_change`.)
if is_remote:
await self._handle_profile_change(
state_key, room_id, prev_event_id, event_id
)
continue
if change is MatchChange.now_true: # The user joined
# This may be the first time we've seen a remote user. If
# so, ensure we have a directory entry for them. (We don't
# need to do this for local users: their directory entry
# is created at the point of registration.
if is_remote:
await self._upsert_directory_entry_for_remote_user(
state_key, event_id
)
await self._track_user_joined_room(room_id, state_key)
else: # The user left
await self._handle_remove_user(room_id, state_key)
else:
logger.debug("Ignoring irrelevant type: %r", typ)
@@ -326,6 +275,68 @@ class UserDirectoryHandler(StateDeltasHandler):
for user_id in users_in_room:
await self._track_user_joined_room(room_id, user_id)
async def _handle_room_membership_event(
self,
room_id: str,
prev_event_id: str,
event_id: str,
state_key: str,
) -> None:
joined = await self._get_key_change(
prev_event_id,
event_id,
key_name="membership",
public_value=Membership.JOIN,
)
# We have to do two things:
# 1. Update the room-sharing tables.
# This applies to remote users and non-excluded local users.
# 2. Update the user_directory and user_directory_search tables.
# This applies to remote users only, because we only become aware of
# the (and any profile changes) by listening to these events.
# The rest of the application knows exactly when local users are
# created or their profile changed---it will directly call methods
# on this class.
# Both cases ignore excluded local users, so start by discarding them.
is_remote = not self.is_mine_id(state_key)
if not is_remote and not await self.store.should_include_local_user_in_dir(
state_key
):
return
if joined is MatchChange.now_false:
# Need to check if the server left the room entirely, if so
# we might need to remove all the users in that room
is_in_room = await self.store.is_host_joined(room_id, self.server_name)
if not is_in_room:
logger.debug("Server left room: %r", room_id)
# Fetch all the users that we marked as being in user
# directory due to being in the room and then check if
# need to remove those users or not
user_ids = await self.store.get_users_in_dir_due_to_room(room_id)
for user_id in user_ids:
await self._handle_remove_user(room_id, user_id)
else:
logger.debug("Server is still in room: %r", room_id)
await self._handle_remove_user(room_id, state_key)
elif joined is MatchChange.no_change:
# Handle any profile changes for remote users.
# (For local users the rest of the application calls
# `handle_local_profile_change`.)
if is_remote:
await self._handle_possible_remote_profile_change(
state_key, room_id, prev_event_id, event_id
)
elif joined is MatchChange.now_true: # The user joined
# This may be the first time we've seen a remote user. If
# so, ensure we have a directory entry for them. (For local users,
# the rest of the application calls `handle_local_profile_change`.)
if is_remote:
await self._upsert_directory_entry_for_remote_user(state_key, event_id)
await self._track_user_joined_room(room_id, state_key)
async def _upsert_directory_entry_for_remote_user(
self, user_id: str, event_id: str
) -> None:
@@ -386,7 +397,12 @@ class UserDirectoryHandler(StateDeltasHandler):
await self.store.add_users_who_share_private_room(room_id, to_insert)
async def _handle_remove_user(self, room_id: str, user_id: str) -> None:
"""Called when we might need to remove user from directory
"""Called when when someone leaves a room. The user may be local or remote.
(If the person who left was the last local user in this room, the server
is no longer in the room. We call this function to forget that the remaining
remote users are in the room, even though they haven't left. So the name is
a little misleading!)
Args:
room_id: The room ID that user left or stopped being public that
@@ -397,13 +413,15 @@ class UserDirectoryHandler(StateDeltasHandler):
# Remove user from sharing tables
await self.store.remove_user_who_share_room(user_id, room_id)
# Are they still in any rooms? If not, remove them entirely.
rooms_user_is_in = await self.store.get_user_dir_rooms_user_is_in(user_id)
# If they're a remote user and not in any rooms we can see,
# remove their user_directory entry.
if not self.is_mine_id(user_id):
rooms_user_is_in = await self.store.get_user_dir_rooms_user_is_in(user_id)
if len(rooms_user_is_in) == 0:
await self.store.remove_from_user_dir(user_id)
if len(rooms_user_is_in) == 0:
await self.store.remove_from_user_dir(user_id)
async def _handle_profile_change(
async def _handle_possible_remote_profile_change(
self,
user_id: str,
room_id: str,
@@ -411,7 +429,8 @@ class UserDirectoryHandler(StateDeltasHandler):
event_id: Optional[str],
) -> None:
"""Check member event changes for any profile changes and update the
database if there are.
database if there are. This is intended for remote users only. The caller
is responsible for checking that the given user is remote.
"""
if not prev_event_id or not event_id:
return

View File

@@ -503,6 +503,35 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
0,
)
def test_local_user_remains_in_directory_after_leaving_all_rooms(self) -> None:
"""We should preserve the invariant that local, non-excluded users are
always in the user_directory table.
This is a choice to simplify the implementation, and also ensure that
the config option to search for all users works in this case."""
alice = self.register_user("alice", "pass")
alice_token = self.login(alice, "pass")
# Alice should have a user directory entry created at registration.
users = self.get_success(self.user_dir_helper.get_profiles_in_user_directory())
self.assertEqual(
users, {alice: ProfileInfo(display_name="alice", avatar_url=None)}
)
# Alice makes a room for herself.
room = self.helper.create_room_as(alice, tok=alice_token)
# Alice leaves that room.
self.helper.leave(room, alice, tok=alice_token)
# Wait for background updates to ensure that the user directory handler
# handler has processed all events. Alice should remain in the directory.
self.wait_for_background_updates()
users = self.get_success(self.user_dir_helper.get_profiles_in_user_directory())
self.assertEqual(
users, {alice: ProfileInfo(display_name="alice", avatar_url=None)}
)
def test_private_room(self) -> None:
"""
A user can be searched for only by people that are either in a public