@@ -151,15 +151,13 @@ def __init__(self, hs: "HomeServer"):
151151
152152 self ._federation_queue = PresenceFederationQueue (hs , self )
153153
154- self ._busy_presence_enabled = hs .config .experimental .msc3026_enabled
155-
156154 self .VALID_PRESENCE : Tuple [str , ...] = (
157155 PresenceState .ONLINE ,
158156 PresenceState .UNAVAILABLE ,
159157 PresenceState .OFFLINE ,
160158 )
161159
162- if self . _busy_presence_enabled :
160+ if hs . config . experimental . msc3026_enabled :
163161 self .VALID_PRESENCE += (PresenceState .BUSY ,)
164162
165163 active_presence = self .store .take_presence_startup_info ()
@@ -255,17 +253,19 @@ async def set_state(
255253 self ,
256254 target_user : UserID ,
257255 state : JsonDict ,
258- ignore_status_msg : bool = False ,
259256 force_notify : bool = False ,
257+ is_sync : bool = False ,
260258 ) -> None :
261259 """Set the presence state of the user.
262260
263261 Args:
264262 target_user: The ID of the user to set the presence state of.
265263 state: The presence state as a JSON dictionary.
266- ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
267- If False, the user's current status will be updated.
268264 force_notify: Whether to force notification of the update to clients.
265+ is_sync: True if this update was from a sync, which results in
266+ *not* overriding a previously set BUSY status, updating the
267+ user's last_user_sync_ts, and ignoring the "status_msg" field of
268+ the `state` dict.
269269 """
270270
271271 @abc .abstractmethod
@@ -491,23 +491,18 @@ async def user_syncing(
491491 if not affect_presence or not self ._presence_enabled :
492492 return _NullContextManager ()
493493
494- prev_state = await self .current_state_for_user (user_id )
495- if prev_state .state != PresenceState .BUSY :
496- # We set state here but pass ignore_status_msg = True as we don't want to
497- # cause the status message to be cleared.
498- # Note that this causes last_active_ts to be incremented which is not
499- # what the spec wants: see comment in the BasePresenceHandler version
500- # of this function.
501- await self .set_state (
502- UserID .from_string (user_id ),
503- {"presence" : presence_state },
504- ignore_status_msg = True ,
505- )
494+ # Note that this causes last_active_ts to be incremented which is not
495+ # what the spec wants.
496+ await self .set_state (
497+ UserID .from_string (user_id ),
498+ state = {"presence" : presence_state },
499+ is_sync = True ,
500+ )
506501
507502 curr_sync = self ._user_to_num_current_syncs .get (user_id , 0 )
508503 self ._user_to_num_current_syncs [user_id ] = curr_sync + 1
509504
510- # If we went from no in flight sync to some , notify replication
505+ # If this is the first in- flight sync, notify replication
511506 if self ._user_to_num_current_syncs [user_id ] == 1 :
512507 self .mark_as_coming_online (user_id )
513508
@@ -518,7 +513,7 @@ def _end() -> None:
518513 if user_id in self ._user_to_num_current_syncs :
519514 self ._user_to_num_current_syncs [user_id ] -= 1
520515
521- # If we went from one in flight sync to non , notify replication
516+ # If there are no more in- flight syncs , notify replication
522517 if self ._user_to_num_current_syncs [user_id ] == 0 :
523518 self .mark_as_going_offline (user_id )
524519
@@ -598,17 +593,19 @@ async def set_state(
598593 self ,
599594 target_user : UserID ,
600595 state : JsonDict ,
601- ignore_status_msg : bool = False ,
602596 force_notify : bool = False ,
597+ is_sync : bool = False ,
603598 ) -> None :
604599 """Set the presence state of the user.
605600
606601 Args:
607602 target_user: The ID of the user to set the presence state of.
608603 state: The presence state as a JSON dictionary.
609- ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
610- If False, the user's current status will be updated.
611604 force_notify: Whether to force notification of the update to clients.
605+ is_sync: True if this update was from a sync, which results in
606+ *not* overriding a previously set BUSY status, updating the
607+ user's last_user_sync_ts, and ignoring the "status_msg" field of
608+ the `state` dict.
612609 """
613610 presence = state ["presence" ]
614611
@@ -626,8 +623,8 @@ async def set_state(
626623 instance_name = self ._presence_writer_instance ,
627624 user_id = user_id ,
628625 state = state ,
629- ignore_status_msg = ignore_status_msg ,
630626 force_notify = force_notify ,
627+ is_sync = is_sync ,
631628 )
632629
633630 async def bump_presence_active_time (self , user : UserID ) -> None :
@@ -992,45 +989,13 @@ async def user_syncing(
992989 curr_sync = self .user_to_num_current_syncs .get (user_id , 0 )
993990 self .user_to_num_current_syncs [user_id ] = curr_sync + 1
994991
995- prev_state = await self .current_state_for_user (user_id )
996-
997- # If they're busy then they don't stop being busy just by syncing,
998- # so just update the last sync time.
999- if prev_state .state != PresenceState .BUSY :
1000- # XXX: We set_state separately here and just update the last_active_ts above
1001- # This keeps the logic as similar as possible between the worker and single
1002- # process modes. Using set_state will actually cause last_active_ts to be
1003- # updated always, which is not what the spec calls for, but synapse has done
1004- # this for... forever, I think.
1005- await self .set_state (
1006- UserID .from_string (user_id ),
1007- {"presence" : presence_state },
1008- ignore_status_msg = True ,
1009- )
1010- # Retrieve the new state for the logic below. This should come from the
1011- # in-memory cache.
1012- prev_state = await self .current_state_for_user (user_id )
1013-
1014- # To keep the single process behaviour consistent with worker mode, run the
1015- # same logic as `update_external_syncs_row`, even though it looks weird.
1016- if prev_state .state == PresenceState .OFFLINE :
1017- await self ._update_states (
1018- [
1019- prev_state .copy_and_replace (
1020- state = PresenceState .ONLINE ,
1021- last_active_ts = self .clock .time_msec (),
1022- last_user_sync_ts = self .clock .time_msec (),
1023- )
1024- ]
1025- )
1026- # otherwise, set the new presence state & update the last sync time,
1027- # but don't update last_active_ts as this isn't an indication that
1028- # they've been active (even though it's probably been updated by
1029- # set_state above)
1030- else :
1031- await self ._update_states (
1032- [prev_state .copy_and_replace (last_user_sync_ts = self .clock .time_msec ())]
1033- )
992+ # Note that this causes last_active_ts to be incremented which is not
993+ # what the spec wants.
994+ await self .set_state (
995+ UserID .from_string (user_id ),
996+ state = {"presence" : presence_state },
997+ is_sync = True ,
998+ )
1034999
10351000 async def _end () -> None :
10361001 try :
@@ -1080,32 +1045,27 @@ async def update_external_syncs_row(
10801045 process_id , set ()
10811046 )
10821047
1083- updates = []
1048+ # USER_SYNC is sent when a user starts or stops syncing on a remote
1049+ # process. (But only for the initial and last device.)
1050+ #
1051+ # When a user *starts* syncing it also calls set_state(...) which
1052+ # will update the state, last_active_ts, and last_user_sync_ts.
1053+ # Simply ensure the user is tracked as syncing in this case.
1054+ #
1055+ # When a user *stops* syncing, update the last_user_sync_ts and mark
1056+ # them as no longer syncing. Note this doesn't quite match the
1057+ # monolith behaviour, which updates last_user_sync_ts at the end of
1058+ # every sync, not just the last in-flight sync.
10841059 if is_syncing and user_id not in process_presence :
1085- if prev_state .state == PresenceState .OFFLINE :
1086- updates .append (
1087- prev_state .copy_and_replace (
1088- state = PresenceState .ONLINE ,
1089- last_active_ts = sync_time_msec ,
1090- last_user_sync_ts = sync_time_msec ,
1091- )
1092- )
1093- else :
1094- updates .append (
1095- prev_state .copy_and_replace (last_user_sync_ts = sync_time_msec )
1096- )
10971060 process_presence .add (user_id )
1098- elif user_id in process_presence :
1099- updates . append (
1100- prev_state . copy_and_replace ( last_user_sync_ts = sync_time_msec )
1061+ elif not is_syncing and user_id in process_presence :
1062+ new_state = prev_state . copy_and_replace (
1063+ last_user_sync_ts = sync_time_msec
11011064 )
1065+ await self ._update_states ([new_state ])
11021066
1103- if not is_syncing :
11041067 process_presence .discard (user_id )
11051068
1106- if updates :
1107- await self ._update_states (updates )
1108-
11091069 self .external_process_last_updated_ms [process_id ] = self .clock .time_msec ()
11101070
11111071 async def update_external_syncs_clear (self , process_id : str ) -> None :
@@ -1204,17 +1164,19 @@ async def set_state(
12041164 self ,
12051165 target_user : UserID ,
12061166 state : JsonDict ,
1207- ignore_status_msg : bool = False ,
12081167 force_notify : bool = False ,
1168+ is_sync : bool = False ,
12091169 ) -> None :
12101170 """Set the presence state of the user.
12111171
12121172 Args:
12131173 target_user: The ID of the user to set the presence state of.
12141174 state: The presence state as a JSON dictionary.
1215- ignore_status_msg: True to ignore the "status_msg" field of the `state` dict.
1216- If False, the user's current status will be updated.
12171175 force_notify: Whether to force notification of the update to clients.
1176+ is_sync: True if this update was from a sync, which results in
1177+ *not* overriding a previously set BUSY status, updating the
1178+ user's last_user_sync_ts, and ignoring the "status_msg" field of
1179+ the `state` dict.
12181180 """
12191181 status_msg = state .get ("status_msg" , None )
12201182 presence = state ["presence" ]
@@ -1227,18 +1189,27 @@ async def set_state(
12271189 return
12281190
12291191 user_id = target_user .to_string ()
1192+ now = self .clock .time_msec ()
12301193
12311194 prev_state = await self .current_state_for_user (user_id )
12321195
1196+ # Syncs do not override a previous presence of busy.
1197+ #
1198+ # TODO: This is a hack for lack of multi-device support. Unfortunately
1199+ # removing this requires coordination with clients.
1200+ if prev_state .state == PresenceState .BUSY and is_sync :
1201+ presence = PresenceState .BUSY
1202+
12331203 new_fields = {"state" : presence }
12341204
1235- if not ignore_status_msg :
1236- new_fields ["status_msg " ] = status_msg
1205+ if presence == PresenceState . ONLINE or presence == PresenceState . BUSY :
1206+ new_fields ["last_active_ts " ] = now
12371207
1238- if presence == PresenceState .ONLINE or (
1239- presence == PresenceState .BUSY and self ._busy_presence_enabled
1240- ):
1241- new_fields ["last_active_ts" ] = self .clock .time_msec ()
1208+ if is_sync :
1209+ new_fields ["last_user_sync_ts" ] = now
1210+ else :
1211+ # Syncs do not override the status message.
1212+ new_fields ["status_msg" ] = status_msg
12421213
12431214 await self ._update_states (
12441215 [prev_state .copy_and_replace (** new_fields )], force_notify = force_notify
0 commit comments