@@ -151,15 +151,13 @@ def __init__(self, hs: "HomeServer"):
151
151
152
152
self ._federation_queue = PresenceFederationQueue (hs , self )
153
153
154
- self ._busy_presence_enabled = hs .config .experimental .msc3026_enabled
155
-
156
154
self .VALID_PRESENCE : Tuple [str , ...] = (
157
155
PresenceState .ONLINE ,
158
156
PresenceState .UNAVAILABLE ,
159
157
PresenceState .OFFLINE ,
160
158
)
161
159
162
- if self . _busy_presence_enabled :
160
+ if hs . config . experimental . msc3026_enabled :
163
161
self .VALID_PRESENCE += (PresenceState .BUSY ,)
164
162
165
163
active_presence = self .store .take_presence_startup_info ()
@@ -255,17 +253,19 @@ async def set_state(
255
253
self ,
256
254
target_user : UserID ,
257
255
state : JsonDict ,
258
- ignore_status_msg : bool = False ,
259
256
force_notify : bool = False ,
257
+ is_sync : bool = False ,
260
258
) -> None :
261
259
"""Set the presence state of the user.
262
260
263
261
Args:
264
262
target_user: The ID of the user to set the presence state of.
265
263
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.
268
264
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.
269
269
"""
270
270
271
271
@abc .abstractmethod
@@ -491,23 +491,18 @@ async def user_syncing(
491
491
if not affect_presence or not self ._presence_enabled :
492
492
return _NullContextManager ()
493
493
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
+ )
506
501
507
502
curr_sync = self ._user_to_num_current_syncs .get (user_id , 0 )
508
503
self ._user_to_num_current_syncs [user_id ] = curr_sync + 1
509
504
510
- # If we went from no in flight sync to some , notify replication
505
+ # If this is the first in- flight sync, notify replication
511
506
if self ._user_to_num_current_syncs [user_id ] == 1 :
512
507
self .mark_as_coming_online (user_id )
513
508
@@ -518,7 +513,7 @@ def _end() -> None:
518
513
if user_id in self ._user_to_num_current_syncs :
519
514
self ._user_to_num_current_syncs [user_id ] -= 1
520
515
521
- # If we went from one in flight sync to non , notify replication
516
+ # If there are no more in- flight syncs , notify replication
522
517
if self ._user_to_num_current_syncs [user_id ] == 0 :
523
518
self .mark_as_going_offline (user_id )
524
519
@@ -598,17 +593,19 @@ async def set_state(
598
593
self ,
599
594
target_user : UserID ,
600
595
state : JsonDict ,
601
- ignore_status_msg : bool = False ,
602
596
force_notify : bool = False ,
597
+ is_sync : bool = False ,
603
598
) -> None :
604
599
"""Set the presence state of the user.
605
600
606
601
Args:
607
602
target_user: The ID of the user to set the presence state of.
608
603
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.
611
604
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.
612
609
"""
613
610
presence = state ["presence" ]
614
611
@@ -626,8 +623,8 @@ async def set_state(
626
623
instance_name = self ._presence_writer_instance ,
627
624
user_id = user_id ,
628
625
state = state ,
629
- ignore_status_msg = ignore_status_msg ,
630
626
force_notify = force_notify ,
627
+ is_sync = is_sync ,
631
628
)
632
629
633
630
async def bump_presence_active_time (self , user : UserID ) -> None :
@@ -992,45 +989,13 @@ async def user_syncing(
992
989
curr_sync = self .user_to_num_current_syncs .get (user_id , 0 )
993
990
self .user_to_num_current_syncs [user_id ] = curr_sync + 1
994
991
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
+ )
1034
999
1035
1000
async def _end () -> None :
1036
1001
try :
@@ -1080,32 +1045,27 @@ async def update_external_syncs_row(
1080
1045
process_id , set ()
1081
1046
)
1082
1047
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.
1084
1059
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
- )
1097
1060
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
1101
1064
)
1065
+ await self ._update_states ([new_state ])
1102
1066
1103
- if not is_syncing :
1104
1067
process_presence .discard (user_id )
1105
1068
1106
- if updates :
1107
- await self ._update_states (updates )
1108
-
1109
1069
self .external_process_last_updated_ms [process_id ] = self .clock .time_msec ()
1110
1070
1111
1071
async def update_external_syncs_clear (self , process_id : str ) -> None :
@@ -1204,17 +1164,19 @@ async def set_state(
1204
1164
self ,
1205
1165
target_user : UserID ,
1206
1166
state : JsonDict ,
1207
- ignore_status_msg : bool = False ,
1208
1167
force_notify : bool = False ,
1168
+ is_sync : bool = False ,
1209
1169
) -> None :
1210
1170
"""Set the presence state of the user.
1211
1171
1212
1172
Args:
1213
1173
target_user: The ID of the user to set the presence state of.
1214
1174
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.
1217
1175
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.
1218
1180
"""
1219
1181
status_msg = state .get ("status_msg" , None )
1220
1182
presence = state ["presence" ]
@@ -1227,18 +1189,27 @@ async def set_state(
1227
1189
return
1228
1190
1229
1191
user_id = target_user .to_string ()
1192
+ now = self .clock .time_msec ()
1230
1193
1231
1194
prev_state = await self .current_state_for_user (user_id )
1232
1195
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
+
1233
1203
new_fields = {"state" : presence }
1234
1204
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
1237
1207
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
1242
1213
1243
1214
await self ._update_states (
1244
1215
[prev_state .copy_and_replace (** new_fields )], force_notify = force_notify
0 commit comments