diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py
index 6332f50974..42fb622c48 100644
--- a/synapse/handlers/presence.py
+++ b/synapse/handlers/presence.py
@@ -135,6 +135,9 @@ class PresenceHandler(BaseHandler):
self._remote_sendmap = {}
# map remote users to sets of local users who're interested in them
self._remote_recvmap = {}
+ # list of (serial, set of(userids)) tuples, ordered by serial, latest
+ # first
+ self._remote_offline_serials = []
# map any user to a UserPresenceCache
self._user_cachemap = {}
@@ -715,6 +718,10 @@ class PresenceHandler(BaseHandler):
)
if state["presence"] == PresenceState.OFFLINE:
+ self._remote_offline_serials.insert(
+ 0,
+ (self._user_cachemap_latest_serial, set([user.to_string()]))
+ )
del self._user_cachemap[user]
for poll in content.get("poll", []):
@@ -856,6 +863,20 @@ class PresenceEventSource(object):
# TODO(paul): limit
+ for serial, user_ids in presence._remote_offline_serials:
+ if serial < from_key:
+ break
+
+ for u in user_ids:
+ updates.append({
+ "type": "m.presence",
+ "content": {"user_id": u, "presence": PresenceState.OFFLINE},
+ })
+ # TODO(paul): For the v2 API we want to tell the client their from_key
+ # is too old if we fell off the end of the _remote_offline_serials
+ # list, and get them to invalidate+resync. In v1 we have no such
+ # concept so this is a best-effort result.
+
if updates:
defer.returnValue((updates, latest_serial))
else:
diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py
index 04eba4289e..bb497b6f09 100644
--- a/tests/handlers/test_presence.py
+++ b/tests/handlers/test_presence.py
@@ -879,6 +879,44 @@ class PresencePushTestCase(MockedDatastorePresenceTestCase):
)
@defer.inlineCallbacks
+ def test_recv_remote_offline(self):
+ """ Various tests relating to SYN-261 """
+ potato_set = self.handler._remote_recvmap.setdefault(self.u_potato,
+ set())
+ potato_set.add(self.u_apple)
+
+ self.room_members = [self.u_banana, self.u_potato]
+
+ self.assertEquals(self.event_source.get_current_key(), 0)
+
+ yield self.mock_federation_resource.trigger("PUT",
+ "/_matrix/federation/v1/send/1000000/",
+ _make_edu_json("elsewhere", "m.presence",
+ content={
+ "push": [
+ {"user_id": "@potato:remote",
+ "presence": "offline"},
+ ],
+ }
+ )
+ )
+
+ self.assertEquals(self.event_source.get_current_key(), 1)
+
+ (events, _) = yield self.event_source.get_new_events_for_user(
+ self.u_apple, 0, None
+ )
+ self.assertEquals(events,
+ [
+ {"type": "m.presence",
+ "content": {
+ "user_id": "@potato:remote",
+ "presence": OFFLINE,
+ }}
+ ]
+ )
+
+ @defer.inlineCallbacks
def test_join_room_local(self):
self.room_members = [self.u_apple, self.u_banana]
|