Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 6b61deb

Browse files
authored
Do not remove status_msg when user going offline (#10550)
Signed-off-by: Dirk Klimpel [email protected]
1 parent 189c055 commit 6b61deb

File tree

3 files changed

+166
-9
lines changed

3 files changed

+166
-9
lines changed

changelog.d/10550.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix longstanding bug which caused the user "status" to be reset when the user went offline. Contributed by @dklimpel.

synapse/handlers/presence.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,8 +1184,7 @@ async def set_state(
11841184
new_fields = {"state": presence}
11851185

11861186
if not ignore_status_msg:
1187-
msg = status_msg if presence != PresenceState.OFFLINE else None
1188-
new_fields["status_msg"] = msg
1187+
new_fields["status_msg"] = status_msg
11891188

11901189
if presence == PresenceState.ONLINE or (
11911190
presence == PresenceState.BUSY and self._busy_presence_enabled
@@ -1478,7 +1477,7 @@ def format_user_presence_state(
14781477
content["user_id"] = state.user_id
14791478
if state.last_active_ts:
14801479
content["last_active_ago"] = now - state.last_active_ts
1481-
if state.status_msg and state.state != PresenceState.OFFLINE:
1480+
if state.status_msg:
14821481
content["status_msg"] = state.status_msg
14831482
if state.state == PresenceState.ONLINE:
14841483
content["currently_active"] = state.currently_active
@@ -1840,17 +1839,15 @@ def handle_timeout(
18401839
# don't set them as offline.
18411840
sync_or_active = max(state.last_user_sync_ts, state.last_active_ts)
18421841
if now - sync_or_active > SYNC_ONLINE_TIMEOUT:
1843-
state = state.copy_and_replace(
1844-
state=PresenceState.OFFLINE, status_msg=None
1845-
)
1842+
state = state.copy_and_replace(state=PresenceState.OFFLINE)
18461843
changed = True
18471844
else:
18481845
# We expect to be poked occasionally by the other side.
18491846
# This is to protect against forgetful/buggy servers, so that
18501847
# no one gets stuck online forever.
18511848
if now - state.last_federation_update_ts > FEDERATION_TIMEOUT:
18521849
# The other side seems to have disappeared.
1853-
state = state.copy_and_replace(state=PresenceState.OFFLINE, status_msg=None)
1850+
state = state.copy_and_replace(state=PresenceState.OFFLINE)
18541851
changed = True
18551852

18561853
return state if changed else None

tests/handlers/test_presence.py

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
15+
from typing import Optional
1616
from unittest.mock import Mock, call
1717

1818
from signedjson.key import generate_signing_key
@@ -339,67 +339,80 @@ def test_persisting_presence_updates(self):
339339

340340

341341
class PresenceTimeoutTestCase(unittest.TestCase):
342+
"""Tests different timers and that the timer does not change `status_msg` of user."""
343+
342344
def test_idle_timer(self):
343345
user_id = "@foo:bar"
346+
status_msg = "I'm here!"
344347
now = 5000000
345348

346349
state = UserPresenceState.default(user_id)
347350
state = state.copy_and_replace(
348351
state=PresenceState.ONLINE,
349352
last_active_ts=now - IDLE_TIMER - 1,
350353
last_user_sync_ts=now,
354+
status_msg=status_msg,
351355
)
352356

353357
new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
354358

355359
self.assertIsNotNone(new_state)
356360
self.assertEquals(new_state.state, PresenceState.UNAVAILABLE)
361+
self.assertEquals(new_state.status_msg, status_msg)
357362

358363
def test_busy_no_idle(self):
359364
"""
360365
Tests that a user setting their presence to busy but idling doesn't turn their
361366
presence state into unavailable.
362367
"""
363368
user_id = "@foo:bar"
369+
status_msg = "I'm here!"
364370
now = 5000000
365371

366372
state = UserPresenceState.default(user_id)
367373
state = state.copy_and_replace(
368374
state=PresenceState.BUSY,
369375
last_active_ts=now - IDLE_TIMER - 1,
370376
last_user_sync_ts=now,
377+
status_msg=status_msg,
371378
)
372379

373380
new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
374381

375382
self.assertIsNotNone(new_state)
376383
self.assertEquals(new_state.state, PresenceState.BUSY)
384+
self.assertEquals(new_state.status_msg, status_msg)
377385

378386
def test_sync_timeout(self):
379387
user_id = "@foo:bar"
388+
status_msg = "I'm here!"
380389
now = 5000000
381390

382391
state = UserPresenceState.default(user_id)
383392
state = state.copy_and_replace(
384393
state=PresenceState.ONLINE,
385394
last_active_ts=0,
386395
last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1,
396+
status_msg=status_msg,
387397
)
388398

389399
new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
390400

391401
self.assertIsNotNone(new_state)
392402
self.assertEquals(new_state.state, PresenceState.OFFLINE)
403+
self.assertEquals(new_state.status_msg, status_msg)
393404

394405
def test_sync_online(self):
395406
user_id = "@foo:bar"
407+
status_msg = "I'm here!"
396408
now = 5000000
397409

398410
state = UserPresenceState.default(user_id)
399411
state = state.copy_and_replace(
400412
state=PresenceState.ONLINE,
401413
last_active_ts=now - SYNC_ONLINE_TIMEOUT - 1,
402414
last_user_sync_ts=now - SYNC_ONLINE_TIMEOUT - 1,
415+
status_msg=status_msg,
403416
)
404417

405418
new_state = handle_timeout(
@@ -408,9 +421,11 @@ def test_sync_online(self):
408421

409422
self.assertIsNotNone(new_state)
410423
self.assertEquals(new_state.state, PresenceState.ONLINE)
424+
self.assertEquals(new_state.status_msg, status_msg)
411425

412426
def test_federation_ping(self):
413427
user_id = "@foo:bar"
428+
status_msg = "I'm here!"
414429
now = 5000000
415430

416431
state = UserPresenceState.default(user_id)
@@ -419,12 +434,13 @@ def test_federation_ping(self):
419434
last_active_ts=now,
420435
last_user_sync_ts=now,
421436
last_federation_update_ts=now - FEDERATION_PING_INTERVAL - 1,
437+
status_msg=status_msg,
422438
)
423439

424440
new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
425441

426442
self.assertIsNotNone(new_state)
427-
self.assertEquals(new_state, new_state)
443+
self.assertEquals(state, new_state)
428444

429445
def test_no_timeout(self):
430446
user_id = "@foo:bar"
@@ -444,6 +460,7 @@ def test_no_timeout(self):
444460

445461
def test_federation_timeout(self):
446462
user_id = "@foo:bar"
463+
status_msg = "I'm here!"
447464
now = 5000000
448465

449466
state = UserPresenceState.default(user_id)
@@ -452,6 +469,7 @@ def test_federation_timeout(self):
452469
last_active_ts=now,
453470
last_user_sync_ts=now,
454471
last_federation_update_ts=now - FEDERATION_TIMEOUT - 1,
472+
status_msg=status_msg,
455473
)
456474

457475
new_state = handle_timeout(
@@ -460,9 +478,11 @@ def test_federation_timeout(self):
460478

461479
self.assertIsNotNone(new_state)
462480
self.assertEquals(new_state.state, PresenceState.OFFLINE)
481+
self.assertEquals(new_state.status_msg, status_msg)
463482

464483
def test_last_active(self):
465484
user_id = "@foo:bar"
485+
status_msg = "I'm here!"
466486
now = 5000000
467487

468488
state = UserPresenceState.default(user_id)
@@ -471,6 +491,7 @@ def test_last_active(self):
471491
last_active_ts=now - LAST_ACTIVE_GRANULARITY - 1,
472492
last_user_sync_ts=now,
473493
last_federation_update_ts=now,
494+
status_msg=status_msg,
474495
)
475496

476497
new_state = handle_timeout(state, is_mine=True, syncing_user_ids=set(), now=now)
@@ -516,6 +537,144 @@ def test_external_process_timeout(self):
516537
)
517538
self.assertEqual(state.state, PresenceState.OFFLINE)
518539

540+
def test_user_goes_offline_by_timeout_status_msg_remain(self):
541+
"""Test that if a user doesn't update the records for a while
542+
users presence goes `OFFLINE` because of timeout and `status_msg` remains.
543+
"""
544+
user_id = "@test:server"
545+
status_msg = "I'm here!"
546+
547+
# Mark user as online
548+
self._set_presencestate_with_status_msg(
549+
user_id, PresenceState.ONLINE, status_msg
550+
)
551+
552+
# Check that if we wait a while without telling the handler the user has
553+
# stopped syncing that their presence state doesn't get timed out.
554+
self.reactor.advance(SYNC_ONLINE_TIMEOUT / 2)
555+
556+
state = self.get_success(
557+
self.presence_handler.get_state(UserID.from_string(user_id))
558+
)
559+
self.assertEqual(state.state, PresenceState.ONLINE)
560+
self.assertEqual(state.status_msg, status_msg)
561+
562+
# Check that if the timeout fires, then the syncing user gets timed out
563+
self.reactor.advance(SYNC_ONLINE_TIMEOUT)
564+
565+
state = self.get_success(
566+
self.presence_handler.get_state(UserID.from_string(user_id))
567+
)
568+
# status_msg should remain even after going offline
569+
self.assertEqual(state.state, PresenceState.OFFLINE)
570+
self.assertEqual(state.status_msg, status_msg)
571+
572+
def test_user_goes_offline_manually_with_no_status_msg(self):
573+
"""Test that if a user change presence manually to `OFFLINE`
574+
and no status is set, that `status_msg` is `None`.
575+
"""
576+
user_id = "@test:server"
577+
status_msg = "I'm here!"
578+
579+
# Mark user as online
580+
self._set_presencestate_with_status_msg(
581+
user_id, PresenceState.ONLINE, status_msg
582+
)
583+
584+
# Mark user as offline
585+
self.get_success(
586+
self.presence_handler.set_state(
587+
UserID.from_string(user_id), {"presence": PresenceState.OFFLINE}
588+
)
589+
)
590+
591+
state = self.get_success(
592+
self.presence_handler.get_state(UserID.from_string(user_id))
593+
)
594+
self.assertEqual(state.state, PresenceState.OFFLINE)
595+
self.assertEqual(state.status_msg, None)
596+
597+
def test_user_goes_offline_manually_with_status_msg(self):
598+
"""Test that if a user change presence manually to `OFFLINE`
599+
and a status is set, that `status_msg` appears.
600+
"""
601+
user_id = "@test:server"
602+
status_msg = "I'm here!"
603+
604+
# Mark user as online
605+
self._set_presencestate_with_status_msg(
606+
user_id, PresenceState.ONLINE, status_msg
607+
)
608+
609+
# Mark user as offline
610+
self._set_presencestate_with_status_msg(
611+
user_id, PresenceState.OFFLINE, "And now here."
612+
)
613+
614+
def test_user_reset_online_with_no_status(self):
615+
"""Test that if a user set again the presence manually
616+
and no status is set, that `status_msg` is `None`.
617+
"""
618+
user_id = "@test:server"
619+
status_msg = "I'm here!"
620+
621+
# Mark user as online
622+
self._set_presencestate_with_status_msg(
623+
user_id, PresenceState.ONLINE, status_msg
624+
)
625+
626+
# Mark user as online again
627+
self.get_success(
628+
self.presence_handler.set_state(
629+
UserID.from_string(user_id), {"presence": PresenceState.ONLINE}
630+
)
631+
)
632+
633+
state = self.get_success(
634+
self.presence_handler.get_state(UserID.from_string(user_id))
635+
)
636+
# status_msg should remain even after going offline
637+
self.assertEqual(state.state, PresenceState.ONLINE)
638+
self.assertEqual(state.status_msg, None)
639+
640+
def test_set_presence_with_status_msg_none(self):
641+
"""Test that if a user set again the presence manually
642+
and status is `None`, that `status_msg` is `None`.
643+
"""
644+
user_id = "@test:server"
645+
status_msg = "I'm here!"
646+
647+
# Mark user as online
648+
self._set_presencestate_with_status_msg(
649+
user_id, PresenceState.ONLINE, status_msg
650+
)
651+
652+
# Mark user as online and `status_msg = None`
653+
self._set_presencestate_with_status_msg(user_id, PresenceState.ONLINE, None)
654+
655+
def _set_presencestate_with_status_msg(
656+
self, user_id: str, state: PresenceState, status_msg: Optional[str]
657+
):
658+
"""Set a PresenceState and status_msg and check the result.
659+
660+
Args:
661+
user_id: User for that the status is to be set.
662+
PresenceState: The new PresenceState.
663+
status_msg: Status message that is to be set.
664+
"""
665+
self.get_success(
666+
self.presence_handler.set_state(
667+
UserID.from_string(user_id),
668+
{"presence": state, "status_msg": status_msg},
669+
)
670+
)
671+
672+
new_state = self.get_success(
673+
self.presence_handler.get_state(UserID.from_string(user_id))
674+
)
675+
self.assertEqual(new_state.state, state)
676+
self.assertEqual(new_state.status_msg, status_msg)
677+
519678

520679
class PresenceFederationQueueTestCase(unittest.HomeserverTestCase):
521680
def prepare(self, reactor, clock, hs):

0 commit comments

Comments
 (0)