From 4697e73880eb09d1bfe9991f7da52f22c5a1681a Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 29 May 2026 15:02:16 +0300 Subject: [PATCH] Simplify collecting profile field updates for sync Change `get_profile_data_for_users` to compile a friendly dictionary, so we don't need to special if/else case when collecting data for sync. The sync methods just treat every field identically, without displayname or avatar_url having any specific importance. --- synapse/handlers/sync.py | 51 ++++++++++------------- synapse/storage/databases/main/profile.py | 39 ++++++++--------- 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/synapse/handlers/sync.py b/synapse/handlers/sync.py index dacba0027b..86e48a4f94 100644 --- a/synapse/handlers/sync.py +++ b/synapse/handlers/sync.py @@ -26,6 +26,7 @@ from typing import ( Any, Mapping, Sequence, + cast, ) import attr @@ -37,7 +38,6 @@ from synapse.api.constants import ( EventContentFields, EventTypes, Membership, - ProfileFields, StickyEvent, ) from synapse.api.filtering import FilterCollection @@ -2159,29 +2159,27 @@ class SyncHandler: profile_data_by_user = await self.store.get_profile_data_for_users(user_ids) - all_updates: dict[str, dict[str, JsonValue | None]] = {} + # Serialise the profile updates into the sync response format. + profile_updates: dict[str, dict[str, JsonValue | None]] = {} for other_user_id in user_ids: profile_data = profile_data_by_user.get(other_user_id) if profile_data is None: # Don't generate anything for users with no profile data + # in initial sync. continue - displayname, avatar_url, custom_fields = profile_data - - per_user_updates: dict[str, JsonValue | None] = {} + per_user_updates: dict[str, JsonValue] = {} for field_name in profile_fields: - if displayname and field_name == ProfileFields.DISPLAYNAME: - per_user_updates[field_name] = displayname - elif avatar_url and field_name == ProfileFields.AVATAR_URL: - per_user_updates[field_name] = avatar_url - elif custom_fields.get(field_name): - per_user_updates[field_name] = custom_fields.get(field_name) + if profile_data.get(field_name): + per_user_updates[field_name] = cast( + JsonValue, profile_data[field_name] + ) - if len(all_updates.keys()): - all_updates[other_user_id] = per_user_updates + if per_user_updates: + profile_updates[other_user_id] = per_user_updates - sync_result_builder.profile_updates = all_updates - return + if profile_updates: + sync_result_builder.profile_updates = profile_updates async def _generate_sync_entry_for_profile_updates( self, sync_result_builder: "SyncResultBuilder" @@ -2246,22 +2244,19 @@ class SyncHandler: # Serialise the profile updates into the sync response format. profile_updates: dict[str, dict[str, JsonValue | None]] = {} for other_user_id, fields in user_fields.items(): - displayname = None - avatar_url = None - custom_fields: JsonDict = {} - profile_data = profile_data_by_user.get(other_user_id) - if profile_data is not None: - displayname, avatar_url, custom_fields = profile_data + if profile_data is None: + # No profile data for this user, just return a blank dictionary + # in incremental sync, telling the clients to remove all profile + # information for this user. + profile_updates[other_user_id] = {} + continue - per_user_updates: dict[str, JsonValue | None] = {} + per_user_updates: dict[str, JsonValue] = {} for field_name in fields: - if field_name == ProfileFields.DISPLAYNAME: - per_user_updates[field_name] = displayname - elif field_name == ProfileFields.AVATAR_URL: - per_user_updates[field_name] = avatar_url - else: - per_user_updates[field_name] = custom_fields.get(field_name) + per_user_updates[field_name] = cast( + JsonValue, profile_data.get(field_name) + ) if per_user_updates: profile_updates[other_user_id] = per_user_updates diff --git a/synapse/storage/databases/main/profile.py b/synapse/storage/databases/main/profile.py index ae1279fbf3..d6e503fcfd 100644 --- a/synapse/storage/databases/main/profile.py +++ b/synapse/storage/databases/main/profile.py @@ -27,7 +27,7 @@ from canonicaljson import encode_canonical_json from synapse.api.constants import ProfileFields from synapse.api.errors import Codes, StoreError from synapse.replication.tcp.streams._base import ProfileUpdatesStream -from synapse.storage._base import SQLBaseStore, db_to_json, make_in_list_sql_clause +from synapse.storage._base import SQLBaseStore, make_in_list_sql_clause from synapse.storage.database import ( DatabasePool, LoggingDatabaseConnection, @@ -408,32 +408,33 @@ class ProfileWorkerStore(SQLBaseStore): async def get_profile_data_for_users( self, user_ids: Collection[str] - ) -> dict[str, tuple[str | None, str | None, JsonDict]]: + ) -> dict[str, dict[str, str | JsonDict | None]]: """Fetch displayname/avatar_url/custom fields for a list of users.""" if not user_ids: return {} - rows = cast( - list[tuple[str, str | None, str | None, object | None]], - await self.db_pool.simple_select_many_batch( - table="profiles", - column="full_user_id", - iterable=user_ids, - retcols=("full_user_id", "displayname", "avatar_url", "fields"), - desc="get_profile_data_for_users", - ), + rows = await self.db_pool.simple_select_many_batch( + table="profiles", + column="full_user_id", + iterable=user_ids, + retcols=("full_user_id", "displayname", "avatar_url", "fields"), + desc="get_profile_data_for_users", ) - results: dict[str, tuple[str | None, str | None, JsonDict]] = {} + results: dict[str, dict[str, str | JsonDict | None]] = {} for full_user_id, displayname, avatar_url, fields in rows: - if fields is None: - fields_dict: JsonDict = {} - elif isinstance(fields, (str, bytes, bytearray, memoryview)): - fields_dict = cast(JsonDict, db_to_json(fields)) - else: - fields_dict = cast(JsonDict, fields) + user_fields = fields + # The SQLite driver doesn't automatically convert JSON to + # Python objects + if isinstance(self.database_engine, Sqlite3Engine) and fields: + user_fields = json.loads(fields) + base_fields = { + ProfileFields.DISPLAYNAME: displayname, + ProfileFields.AVATAR_URL: avatar_url, + } + user_fields.update(base_fields) - results[full_user_id] = (displayname, avatar_url, fields_dict) + results[full_user_id] = user_fields return results