From 405aeb0b2c40443d22ce8c265df18e81bd995b44 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 18 Mar 2021 16:34:47 +0100 Subject: [PATCH 1/4] Implement MSC3026: busy presence state --- changelog.d/9644.feature | 1 + synapse/api/constants.py | 1 + synapse/app/generic_worker.py | 1 + synapse/handlers/presence.py | 3 ++- synapse/rest/client/versions.py | 2 ++ tests/handlers/test_presence.py | 20 ++++++++++++++++++++ 6 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 changelog.d/9644.feature diff --git a/changelog.d/9644.feature b/changelog.d/9644.feature new file mode 100644 index 000000000000..556bcf0f9f40 --- /dev/null +++ b/changelog.d/9644.feature @@ -0,0 +1 @@ +Implement the busy presence state as described in [MSC3026](https://github.com/matrix-org/matrix-doc/pull/3026). diff --git a/synapse/api/constants.py b/synapse/api/constants.py index 691f8f9adf07..cc8541bc16be 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -51,6 +51,7 @@ class PresenceState: OFFLINE = "offline" UNAVAILABLE = "unavailable" ONLINE = "online" + BUSY = "org.matrix.msc3026.busy" class JoinRules: diff --git a/synapse/app/generic_worker.py b/synapse/app/generic_worker.py index 274d582d0734..236d98a29dda 100644 --- a/synapse/app/generic_worker.py +++ b/synapse/app/generic_worker.py @@ -439,6 +439,7 @@ async def set_state(self, target_user, state, ignore_status_msg=False): PresenceState.ONLINE, PresenceState.UNAVAILABLE, PresenceState.OFFLINE, + PresenceState.BUSY, ) if presence not in valid_presence: raise SynapseError(400, "Invalid presence state") diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 54631b4ee2c9..bcb99f627b8a 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -730,6 +730,7 @@ async def set_state(self, target_user, state, ignore_status_msg=False): PresenceState.ONLINE, PresenceState.UNAVAILABLE, PresenceState.OFFLINE, + PresenceState.BUSY, ) if presence not in valid_presence: raise SynapseError(400, "Invalid presence state") @@ -744,7 +745,7 @@ async def set_state(self, target_user, state, ignore_status_msg=False): msg = status_msg if presence != PresenceState.OFFLINE else None new_fields["status_msg"] = msg - if presence == PresenceState.ONLINE: + if presence == PresenceState.ONLINE or presence == PresenceState.BUSY: new_fields["last_active_ts"] = self.clock.time_msec() await self._update_states([prev_state.copy_and_replace(**new_fields)]) diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index d24a199318a2..f387d29b57d4 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -81,6 +81,8 @@ def on_GET(self, request): "io.element.e2ee_forced.public": self.e2ee_forced_public, "io.element.e2ee_forced.private": self.e2ee_forced_private, "io.element.e2ee_forced.trusted_private": self.e2ee_forced_trusted_private, + # Supports the busy presence state described in MSC3026. + "org.matrix.msc3026.busy_presence": True, }, }, ) diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py index 996c6141982a..77330f59a987 100644 --- a/tests/handlers/test_presence.py +++ b/tests/handlers/test_presence.py @@ -310,6 +310,26 @@ def test_idle_timer(self): self.assertIsNotNone(new_state) self.assertEquals(new_state.state, PresenceState.UNAVAILABLE) + def test_busy_no_idle(self): + """ + Tests that a user setting their presence to busy but idling doesn't turn their + presence state into unavailable. + """ + user_id = "@foo:bar" + now = 5000000 + + state = UserPresenceState.default(user_id) + state = state.copy_and_replace( + state=PresenceState.BUSY, + last_active_ts=now - IDLE_TIMER - 1, + last_user_sync_ts=now, + ) + + new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now) + + self.assertIsNotNone(new_state) + self.assertEquals(new_state.state, PresenceState.BUSY) + def test_sync_timeout(self): user_id = "@foo:bar" now = 5000000 From 066c703729d72b5da8bb6574d7f7f5f13e12f773 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 18 Mar 2021 18:37:19 +0100 Subject: [PATCH 2/4] Move support for MSC3026 behind an experimental flag --- synapse/app/generic_worker.py | 7 ++++++- synapse/config/experimental.py | 2 ++ synapse/handlers/presence.py | 12 ++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/synapse/app/generic_worker.py b/synapse/app/generic_worker.py index 236d98a29dda..207d5ccd0277 100644 --- a/synapse/app/generic_worker.py +++ b/synapse/app/generic_worker.py @@ -302,6 +302,8 @@ def __init__(self, hs): self.send_stop_syncing, UPDATE_SYNCING_USERS_MS ) + self._busy_presence_enabled = hs.config.experimental.msc3026_enabled + hs.get_reactor().addSystemEventTrigger( "before", "shutdown", @@ -439,8 +441,11 @@ async def set_state(self, target_user, state, ignore_status_msg=False): PresenceState.ONLINE, PresenceState.UNAVAILABLE, PresenceState.OFFLINE, - PresenceState.BUSY, ) + + if self._busy_presence_enabled: + valid_presence += (PresenceState.BUSY,) + if presence not in valid_presence: raise SynapseError(400, "Invalid presence state") diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index b1c1c51e4dcc..2f0cd0cfdf14 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -27,3 +27,5 @@ def read_config(self, config: JsonDict, **kwargs): # MSC2858 (multiple SSO identity providers) self.msc2858_enabled = experimental.get("msc2858_enabled", False) # type: bool + # MSC3026 (busy presence state) + self.msc3026_enabled = experimental.get("msc3026_enabled", False) # type: bool diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index bcb99f627b8a..372017590d99 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -104,6 +104,8 @@ def __init__(self, hs: "HomeServer"): self.clock = hs.get_clock() self.store = hs.get_datastore() + self._busy_presence_enabled = hs.config.experimental.msc3026_enabled + active_presence = self.store.take_presence_startup_info() self.user_to_current_state = {state.user_id: state for state in active_presence} @@ -730,8 +732,11 @@ async def set_state(self, target_user, state, ignore_status_msg=False): PresenceState.ONLINE, PresenceState.UNAVAILABLE, PresenceState.OFFLINE, - PresenceState.BUSY, ) + + if self._busy_presence_enabled: + valid_presence += (PresenceState.BUSY,) + if presence not in valid_presence: raise SynapseError(400, "Invalid presence state") @@ -745,7 +750,10 @@ async def set_state(self, target_user, state, ignore_status_msg=False): msg = status_msg if presence != PresenceState.OFFLINE else None new_fields["status_msg"] = msg - if presence == PresenceState.ONLINE or presence == PresenceState.BUSY: + if ( + presence == PresenceState.ONLINE or + (self._busy_presence_enabled and presence == PresenceState.BUSY) + ): new_fields["last_active_ts"] = self.clock.time_msec() await self._update_states([prev_state.copy_and_replace(**new_fields)]) From 0b56481caafc56c2e624d4b6506c91fc3913615e Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 19 Mar 2021 16:11:08 +0100 Subject: [PATCH 3/4] Fix lint --- synapse/app/generic_worker.py | 8 ++++---- synapse/handlers/presence.py | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/synapse/app/generic_worker.py b/synapse/app/generic_worker.py index 207d5ccd0277..caef394e1d4d 100644 --- a/synapse/app/generic_worker.py +++ b/synapse/app/generic_worker.py @@ -441,12 +441,12 @@ async def set_state(self, target_user, state, ignore_status_msg=False): PresenceState.ONLINE, PresenceState.UNAVAILABLE, PresenceState.OFFLINE, + PresenceState.BUSY, ) - if self._busy_presence_enabled: - valid_presence += (PresenceState.BUSY,) - - if presence not in valid_presence: + if presence not in valid_presence or ( + presence == PresenceState.BUSY and not self._busy_presence_enabled + ): raise SynapseError(400, "Invalid presence state") user_id = target_user.to_string() diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 372017590d99..492c4478fa4d 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -732,12 +732,12 @@ async def set_state(self, target_user, state, ignore_status_msg=False): PresenceState.ONLINE, PresenceState.UNAVAILABLE, PresenceState.OFFLINE, + PresenceState.BUSY, ) - if self._busy_presence_enabled: - valid_presence += (PresenceState.BUSY,) - - if presence not in valid_presence: + if presence not in valid_presence or ( + presence == PresenceState.BUSY and not self._busy_presence_enabled + ): raise SynapseError(400, "Invalid presence state") user_id = target_user.to_string() @@ -750,9 +750,8 @@ async def set_state(self, target_user, state, ignore_status_msg=False): msg = status_msg if presence != PresenceState.OFFLINE else None new_fields["status_msg"] = msg - if ( - presence == PresenceState.ONLINE or - (self._busy_presence_enabled and presence == PresenceState.BUSY) + if presence == PresenceState.ONLINE or ( + self._busy_presence_enabled and presence == PresenceState.BUSY ): new_fields["last_active_ts"] = self.clock.time_msec() From b6ed4f55acf7af44af1d33097407d2dd7f08b5a5 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 19 Mar 2021 18:19:50 +0100 Subject: [PATCH 4/4] Incorporate review --- synapse/handlers/presence.py | 2 +- synapse/rest/client/versions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 492c4478fa4d..da92feacc9eb 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -751,7 +751,7 @@ async def set_state(self, target_user, state, ignore_status_msg=False): new_fields["status_msg"] = msg if presence == PresenceState.ONLINE or ( - self._busy_presence_enabled and presence == PresenceState.BUSY + presence == PresenceState.BUSY and self._busy_presence_enabled ): new_fields["last_active_ts"] = self.clock.time_msec() diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index f387d29b57d4..3e3d8839f494 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -82,7 +82,7 @@ def on_GET(self, request): "io.element.e2ee_forced.private": self.e2ee_forced_private, "io.element.e2ee_forced.trusted_private": self.e2ee_forced_trusted_private, # Supports the busy presence state described in MSC3026. - "org.matrix.msc3026.busy_presence": True, + "org.matrix.msc3026.busy_presence": self.config.experimental.msc3026_enabled, }, }, )