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.
This commit is contained in:
Jason Robinson
2026-05-29 15:02:16 +03:00
parent 92bfed7c18
commit 4697e73880
2 changed files with 43 additions and 47 deletions
+23 -28
View File
@@ -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
+20 -19
View File
@@ -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