summary refs log tree commit diff
path: root/synapse/handlers/sync.py
diff options
context:
space:
mode:
Diffstat (limited to 'synapse/handlers/sync.py')
-rw-r--r--synapse/handlers/sync.py238
1 files changed, 126 insertions, 112 deletions
diff --git a/synapse/handlers/sync.py b/synapse/handlers/sync.py
index 9d52d592ba..1942268c3c 100644
--- a/synapse/handlers/sync.py
+++ b/synapse/handlers/sync.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2015 OpenMarket Ltd
+# Copyright 2015 - 2016 OpenMarket Ltd
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
 
 from ._base import BaseHandler
 
+from synapse.streams.config import PaginationConfig
 from synapse.api.constants import Membership, EventTypes
-from synapse.api.errors import GuestAccessError
 from synapse.util import unwrapFirstError
 
 from twisted.internet import defer
@@ -29,8 +29,8 @@ logger = logging.getLogger(__name__)
 
 SyncConfig = collections.namedtuple("SyncConfig", [
     "user",
-    "is_guest",
     "filter",
+    "is_guest",
 ])
 
 
@@ -54,6 +54,7 @@ class JoinedSyncResult(collections.namedtuple("JoinedSyncResult", [
     "state",             # dict[(str, str), FrozenEvent]
     "ephemeral",
     "account_data",
+    "unread_notification_count",
 ])):
     __slots__ = []
 
@@ -66,6 +67,8 @@ class JoinedSyncResult(collections.namedtuple("JoinedSyncResult", [
             or self.state
             or self.ephemeral
             or self.account_data
+            # nb the notification count does not, er, count: if there's nothing
+            # else in the result, we don't need to send it.
         )
 
 
@@ -115,11 +118,9 @@ class SyncResult(collections.namedtuple("SyncResult", [
         events.
         """
         return bool(
-            self.presence or self.joined or self.invited
+            self.presence or self.joined or self.invited or self.archived
         )
 
-GuestRoom = collections.namedtuple("GuestRoom", ("room_id", "membership"))
-
 
 class SyncHandler(BaseHandler):
 
@@ -138,18 +139,6 @@ class SyncHandler(BaseHandler):
             A Deferred SyncResult.
         """
 
-        if sync_config.is_guest:
-            bad_rooms = []
-            for room_id in sync_config.filter.list_rooms():
-                world_readable = yield self._is_world_readable(room_id)
-                if not world_readable:
-                    bad_rooms.append(room_id)
-
-            if bad_rooms:
-                raise GuestAccessError(
-                    bad_rooms, 403, "Guest access not allowed"
-                )
-
         if timeout == 0 or since_token is None or full_state:
             # we are going to return immediately, so don't bother calling
             # notifier.wait_for_events.
@@ -166,17 +155,6 @@ class SyncHandler(BaseHandler):
             )
             defer.returnValue(result)
 
-    @defer.inlineCallbacks
-    def _is_world_readable(self, room_id):
-        state = yield self.hs.get_state_handler().get_current_state(
-            room_id,
-            EventTypes.RoomHistoryVisibility
-        )
-        if state and "history_visibility" in state.content:
-            defer.returnValue(state.content["history_visibility"] == "world_readable")
-        else:
-            defer.returnValue(False)
-
     def current_sync_for_user(self, sync_config, since_token=None,
                               full_state=False):
         """Get the sync for client needed to match what the server has now.
@@ -188,6 +166,18 @@ class SyncHandler(BaseHandler):
         else:
             return self.incremental_sync_with_gap(sync_config, since_token)
 
+    def last_read_event_id_for_room_and_user(self, room_id, user_id, ephemeral_by_room):
+        if room_id not in ephemeral_by_room:
+            return None
+        for e in ephemeral_by_room[room_id]:
+            if e['type'] != 'm.receipt':
+                continue
+            for receipt_event_id, val in e['content'].items():
+                if 'm.read' in val:
+                    if user_id in val['m.read']:
+                        return receipt_event_id
+        return None
+
     @defer.inlineCallbacks
     def full_state_sync(self, sync_config, timeline_since_token):
         """Get a sync for a client which is starting without any state.
@@ -200,52 +190,37 @@ class SyncHandler(BaseHandler):
         """
         now_token = yield self.event_sources.get_current_token()
 
-        if sync_config.is_guest:
-            room_list = [
-                GuestRoom(room_id, Membership.JOIN)
-                for room_id in sync_config.filter.list_rooms()
-            ]
-
-            account_data = {}
-            account_data_by_room = {}
-            tags_by_room = {}
+        now_token, ephemeral_by_room = yield self.ephemeral_by_room(
+            sync_config, now_token
+        )
 
-        else:
-            membership_list = (Membership.INVITE, Membership.JOIN)
-            if sync_config.filter.include_leave:
-                membership_list += (Membership.LEAVE, Membership.BAN)
+        presence_stream = self.event_sources.sources["presence"]
+        # TODO (mjark): This looks wrong, shouldn't we be getting the presence
+        # UP to the present rather than after the present?
+        pagination_config = PaginationConfig(from_token=now_token)
+        presence, _ = yield presence_stream.get_pagination_rows(
+            user=sync_config.user,
+            pagination_config=pagination_config.get_source_config("presence"),
+            key=None
+        )
 
-            room_list = yield self.store.get_rooms_for_user_where_membership_is(
-                user_id=sync_config.user.to_string(),
-                membership_list=membership_list
-            )
+        membership_list = (Membership.INVITE, Membership.JOIN)
+        if sync_config.filter.include_leave:
+            membership_list += (Membership.LEAVE, Membership.BAN)
 
-            account_data, account_data_by_room = (
-                yield self.store.get_account_data_for_user(
-                    sync_config.user.to_string()
-                )
-            )
+        room_list = yield self.store.get_rooms_for_user_where_membership_is(
+            user_id=sync_config.user.to_string(),
+            membership_list=membership_list
+        )
 
-            tags_by_room = yield self.store.get_tags_for_user(
+        account_data, account_data_by_room = (
+            yield self.store.get_account_data_for_user(
                 sync_config.user.to_string()
             )
-
-        presence_stream = self.event_sources.sources["presence"]
-
-        joined_room_ids = [
-            room.room_id for room in room_list
-            if room.membership == Membership.JOIN
-        ]
-
-        presence, _ = yield presence_stream.get_new_events(
-            from_key=0,
-            user=sync_config.user,
-            room_ids=joined_room_ids,
-            is_guest=sync_config.is_guest,
         )
 
-        now_token, ephemeral_by_room = yield self.ephemeral_by_room(
-            sync_config, now_token, joined_room_ids
+        tags_by_room = yield self.store.get_tags_for_user(
+            sync_config.user.to_string()
         )
 
         joined = []
@@ -314,6 +289,13 @@ class SyncHandler(BaseHandler):
             room_id, sync_config, now_token, since_token=timeline_since_token
         )
 
+        notifs = yield self.unread_notifs_for_room_id(
+            room_id, sync_config, ephemeral_by_room
+        )
+        notif_count = None
+        if notifs is not None:
+            notif_count = len(notifs)
+
         current_state = yield self.get_state_at(room_id, now_token)
 
         defer.returnValue(JoinedSyncResult(
@@ -324,6 +306,7 @@ class SyncHandler(BaseHandler):
             account_data=self.account_data_for_room(
                 room_id, tags_by_room, account_data_by_room
             ),
+            unread_notification_count=notif_count,
         ))
 
     def account_data_for_user(self, account_data):
@@ -356,13 +339,11 @@ class SyncHandler(BaseHandler):
         return account_data_events
 
     @defer.inlineCallbacks
-    def ephemeral_by_room(self, sync_config, now_token, room_ids,
-                          since_token=None):
+    def ephemeral_by_room(self, sync_config, now_token, since_token=None):
         """Get the ephemeral events for each room the user is in
         Args:
             sync_config (SyncConfig): The flags, filters and user for the sync.
             now_token (StreamToken): Where the server is currently up to.
-            room_ids (list): List of room id strings to get data for.
             since_token (StreamToken): Where the server was when the client
                 last synced.
         Returns:
@@ -373,13 +354,16 @@ class SyncHandler(BaseHandler):
 
         typing_key = since_token.typing_key if since_token else "0"
 
+        rooms = yield self.store.get_rooms_for_user(sync_config.user.to_string())
+        room_ids = [room.room_id for room in rooms]
+
         typing_source = self.event_sources.sources["typing"]
         typing, typing_key = yield typing_source.get_new_events(
             user=sync_config.user,
             from_key=typing_key,
             limit=sync_config.filter.ephemeral_limit(),
             room_ids=room_ids,
-            is_guest=False,
+            is_guest=sync_config.is_guest,
         )
         now_token = now_token.copy_and_replace("typing_key", typing_key)
 
@@ -402,8 +386,7 @@ class SyncHandler(BaseHandler):
             from_key=receipt_key,
             limit=sync_config.filter.ephemeral_limit(),
             room_ids=room_ids,
-            # /sync doesn't support guest access, they can't get to this point in code
-            is_guest=False,
+            is_guest=sync_config.is_guest,
         )
         now_token = now_token.copy_and_replace("receipt_key", receipt_key)
 
@@ -450,38 +433,8 @@ class SyncHandler(BaseHandler):
         """
         now_token = yield self.event_sources.get_current_token()
 
-        if sync_config.is_guest:
-            room_ids = sync_config.filter.list_rooms()
-
-            tags_by_room = {}
-            account_data = {}
-            account_data_by_room = {}
-
-        else:
-            rooms = yield self.store.get_rooms_for_user(
-                sync_config.user.to_string()
-            )
-            room_ids = [room.room_id for room in rooms]
-
-            now_token, ephemeral_by_room = yield self.ephemeral_by_room(
-                sync_config, now_token, since_token
-            )
-
-            tags_by_room = yield self.store.get_updated_tags(
-                sync_config.user.to_string(),
-                since_token.account_data_key,
-            )
-
-            account_data, account_data_by_room = (
-                yield self.store.get_updated_account_data_for_user(
-                    sync_config.user.to_string(),
-                    since_token.account_data_key,
-                )
-            )
-
-        now_token, ephemeral_by_room = yield self.ephemeral_by_room(
-            sync_config, now_token, room_ids, since_token
-        )
+        rooms = yield self.store.get_rooms_for_user(sync_config.user.to_string())
+        room_ids = [room.room_id for room in rooms]
 
         presence_source = self.event_sources.sources["presence"]
         presence, presence_key = yield presence_source.get_new_events(
@@ -493,6 +446,17 @@ class SyncHandler(BaseHandler):
         )
         now_token = now_token.copy_and_replace("presence_key", presence_key)
 
+        # We now fetch all ephemeral events for this room in order to get
+        # this users current read receipt. This could almost certainly be
+        # optimised.
+        _, all_ephemeral_by_room = yield self.ephemeral_by_room(
+            sync_config, now_token
+        )
+
+        now_token, ephemeral_by_room = yield self.ephemeral_by_room(
+            sync_config, now_token, since_token
+        )
+
         rm_handler = self.hs.get_handlers().room_member_handler
         app_service = yield self.store.get_app_service_by_user_id(
             sync_config.user.to_string()
@@ -512,8 +476,18 @@ class SyncHandler(BaseHandler):
             from_key=since_token.room_key,
             to_key=now_token.room_key,
             limit=timeline_limit + 1,
-            room_ids=room_ids if sync_config.is_guest else (),
-            is_guest=sync_config.is_guest,
+        )
+
+        tags_by_room = yield self.store.get_updated_tags(
+            sync_config.user.to_string(),
+            since_token.account_data_key,
+        )
+
+        account_data, account_data_by_room = (
+            yield self.store.get_updated_account_data_for_user(
+                sync_config.user.to_string(),
+                since_token.account_data_key,
+            )
         )
 
         joined = []
@@ -552,6 +526,13 @@ class SyncHandler(BaseHandler):
                 else:
                     prev_batch = now_token
 
+                notifs = yield self.unread_notifs_for_room_id(
+                    room_id, sync_config, all_ephemeral_by_room
+                )
+                notif_count = None
+                if notifs is not None:
+                    notif_count = len(notifs)
+
                 just_joined = yield self.check_joined_room(sync_config, state)
                 if just_joined:
                     logger.debug("User has just joined %s: needs full state",
@@ -572,6 +553,7 @@ class SyncHandler(BaseHandler):
                     account_data=self.account_data_for_room(
                         room_id, tags_by_room, account_data_by_room
                     ),
+                    unread_notification_count=notif_count
                 )
                 logger.debug("Result for room %s: %r", room_id, room_sync)
 
@@ -603,7 +585,8 @@ class SyncHandler(BaseHandler):
                 sync_config, leave_event, since_token, tags_by_room,
                 account_data_by_room
             )
-            archived.append(room_sync)
+            if room_sync:
+                archived.append(room_sync)
 
         invited = [
             InvitedSyncResult(room_id=event.room_id, invite=event)
@@ -688,7 +671,7 @@ class SyncHandler(BaseHandler):
             room_id, sync_config, now_token, since_token,
         )
 
-        logging.debug("Recents %r", batch)
+        logger.debug("Recents %r", batch)
 
         current_state = yield self.get_state_at(room_id, now_token)
 
@@ -706,6 +689,13 @@ class SyncHandler(BaseHandler):
         if just_joined:
             state = yield self.get_state_at(room_id, now_token)
 
+        notifs = yield self.unread_notifs_for_room_id(
+            room_id, sync_config, ephemeral_by_room
+        )
+        notif_count = None
+        if notifs is not None:
+            notif_count = len(notifs)
+
         room_sync = JoinedSyncResult(
             room_id=room_id,
             timeline=batch,
@@ -714,9 +704,10 @@ class SyncHandler(BaseHandler):
             account_data=self.account_data_for_room(
                 room_id, tags_by_room, account_data_by_room
             ),
+            unread_notification_count=notif_count,
         )
 
-        logging.debug("Room sync: %r", room_sync)
+        logger.debug("Room sync: %r", room_sync)
 
         defer.returnValue(room_sync)
 
@@ -736,11 +727,14 @@ class SyncHandler(BaseHandler):
 
         leave_token = since_token.copy_and_replace("room_key", stream_token)
 
+        if since_token.is_after(leave_token):
+            defer.returnValue(None)
+
         batch = yield self.load_filtered_recents(
             leave_event.room_id, sync_config, leave_token, since_token,
         )
 
-        logging.debug("Recents %r", batch)
+        logger.debug("Recents %r", batch)
 
         state_events_at_leave = yield self.store.get_state_for_event(
             leave_event.event_id
@@ -765,7 +759,7 @@ class SyncHandler(BaseHandler):
             ),
         )
 
-        logging.debug("Room sync: %r", room_sync)
+        logger.debug("Room sync: %r", room_sync)
 
         defer.returnValue(room_sync)
 
@@ -844,3 +838,23 @@ class SyncHandler(BaseHandler):
             if join_event.content["membership"] == Membership.JOIN:
                 return True
         return False
+
+    @defer.inlineCallbacks
+    def unread_notifs_for_room_id(self, room_id, sync_config, ephemeral_by_room):
+        # Temporarily disable notifications due to performance concerns.
+        defer.returnValue([])
+
+        last_unread_event_id = self.last_read_event_id_for_room_and_user(
+            room_id, sync_config.user.to_string(), ephemeral_by_room
+        )
+
+        notifs = []
+        if last_unread_event_id:
+            notifs = yield self.store.get_unread_event_push_actions_by_room_for_user(
+                room_id, sync_config.user.to_string(), last_unread_event_id
+            )
+        else:
+            # There is no new information in this period, so your notification
+            # count is whatever it was last time.
+            defer.returnValue(None)
+        defer.returnValue(notifs)