28
28
29
29
logger = logging .getLogger (__name__ )
30
30
31
+ # Don't refresh a stale user directory entry, using a Federation /profile request,
32
+ # for 60 seconds. This gives time for other state events to arrive (which will
33
+ # then be coalesced such that only one /profile request is made).
34
+ USER_DIRECTORY_STALE_REFRESH_TIME_MS = 60 * 1000
35
+
31
36
32
37
class UserDirectoryHandler (StateDeltasHandler ):
33
38
"""Handles queries and updates for the user_directory.
@@ -200,8 +205,8 @@ async def _handle_deltas(self, deltas: List[Dict[str, Any]]) -> None:
200
205
typ = delta ["type" ]
201
206
state_key = delta ["state_key" ]
202
207
room_id = delta ["room_id" ]
203
- event_id = delta ["event_id" ]
204
- prev_event_id = delta ["prev_event_id" ]
208
+ event_id : Optional [ str ] = delta ["event_id" ]
209
+ prev_event_id : Optional [ str ] = delta ["prev_event_id" ]
205
210
206
211
logger .debug ("Handling: %r %r, %s" , typ , state_key , event_id )
207
212
@@ -297,8 +302,8 @@ async def _handle_room_publicity_change(
297
302
async def _handle_room_membership_event (
298
303
self ,
299
304
room_id : str ,
300
- prev_event_id : str ,
301
- event_id : str ,
305
+ prev_event_id : Optional [ str ] ,
306
+ event_id : Optional [ str ] ,
302
307
state_key : str ,
303
308
) -> None :
304
309
"""Process a single room membershp event.
@@ -348,37 +353,22 @@ async def _handle_room_membership_event(
348
353
# Handle any profile changes for remote users.
349
354
# (For local users the rest of the application calls
350
355
# `handle_local_profile_change`.)
351
- if is_remote :
356
+ # Only process if there is an event_id.
357
+ if is_remote and event_id is not None :
352
358
await self ._handle_possible_remote_profile_change (
353
359
state_key , room_id , prev_event_id , event_id
354
360
)
355
361
elif joined is MatchChange .now_true : # The user joined
356
362
# This may be the first time we've seen a remote user. If
357
363
# so, ensure we have a directory entry for them. (For local users,
358
364
# the rest of the application calls `handle_local_profile_change`.)
359
- if is_remote :
360
- await self ._upsert_directory_entry_for_remote_user (state_key , event_id )
365
+ # Only process if there is an event_id.
366
+ if is_remote and event_id is not None :
367
+ await self ._handle_possible_remote_profile_change (
368
+ state_key , room_id , None , event_id
369
+ )
361
370
await self ._track_user_joined_room (room_id , state_key )
362
371
363
- async def _upsert_directory_entry_for_remote_user (
364
- self , user_id : str , event_id : str
365
- ) -> None :
366
- """A remote user has just joined a room. Ensure they have an entry in
367
- the user directory. The caller is responsible for making sure they're
368
- remote.
369
- """
370
- event = await self .store .get_event (event_id , allow_none = True )
371
- # It isn't expected for this event to not exist, but we
372
- # don't want the entire background process to break.
373
- if event is None :
374
- return
375
-
376
- logger .debug ("Adding new user to dir, %r" , user_id )
377
-
378
- await self .store .update_profile_in_user_dir (
379
- user_id , event .content .get ("displayname" ), event .content .get ("avatar_url" )
380
- )
381
-
382
372
async def _track_user_joined_room (self , room_id : str , joining_user_id : str ) -> None :
383
373
"""Someone's just joined a room. Update `users_in_public_rooms` or
384
374
`users_who_share_private_rooms` as appropriate.
@@ -460,14 +450,17 @@ async def _handle_possible_remote_profile_change(
460
450
user_id : str ,
461
451
room_id : str ,
462
452
prev_event_id : Optional [str ],
463
- event_id : Optional [ str ] ,
453
+ event_id : str ,
464
454
) -> None :
465
455
"""Check member event changes for any profile changes and update the
466
456
database if there are. This is intended for remote users only. The caller
467
457
is responsible for checking that the given user is remote.
468
458
"""
469
- if not prev_event_id or not event_id :
470
- return
459
+
460
+ if not prev_event_id :
461
+ # If we don't have an older event to fall back on, just fetch the same
462
+ # event itself.
463
+ prev_event_id = event_id
471
464
472
465
prev_event = await self .store .get_event (prev_event_id , allow_none = True )
473
466
event = await self .store .get_event (event_id , allow_none = True )
@@ -478,17 +471,37 @@ async def _handle_possible_remote_profile_change(
478
471
if event .membership != Membership .JOIN :
479
472
return
480
473
474
+ is_public = await self .store .is_room_world_readable_or_publicly_joinable (
475
+ room_id
476
+ )
477
+ if not is_public :
478
+ # Don't collect user profiles from private rooms as they are not guaranteed
479
+ # to be the same as the user's global profile.
480
+ now_ts = self .clock .time_msec ()
481
+ await self .store .set_remote_user_profile_in_user_dir_stale (
482
+ user_id ,
483
+ next_try_at_ms = now_ts + USER_DIRECTORY_STALE_REFRESH_TIME_MS ,
484
+ retry_counter = 0 ,
485
+ )
486
+ return
487
+
481
488
prev_name = prev_event .content .get ("displayname" )
482
489
new_name = event .content .get ("displayname" )
483
- # If the new name is an unexpected form, do not update the directory .
490
+ # If the new name is an unexpected form, replace with None .
484
491
if not isinstance (new_name , str ):
485
- new_name = prev_name
492
+ new_name = None
486
493
487
494
prev_avatar = prev_event .content .get ("avatar_url" )
488
495
new_avatar = event .content .get ("avatar_url" )
489
- # If the new avatar is an unexpected form, do not update the directory .
496
+ # If the new avatar is an unexpected form, replace with None .
490
497
if not isinstance (new_avatar , str ):
491
- new_avatar = prev_avatar
498
+ new_avatar = None
492
499
493
- if prev_name != new_name or prev_avatar != new_avatar :
500
+ if (
501
+ prev_name != new_name
502
+ or prev_avatar != new_avatar
503
+ or prev_event_id == event_id
504
+ ):
505
+ # Only update if something has changed, or we didn't have a previous event
506
+ # in the first place.
494
507
await self .store .update_profile_in_user_dir (user_id , new_name , new_avatar )
0 commit comments