summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
Diffstat (limited to 'synapse')
-rw-r--r--synapse/__init__.py2
-rwxr-xr-xsynapse/app/homeserver.py8
-rw-r--r--synapse/config/logger.py5
-rw-r--r--synapse/config/server.py8
-rw-r--r--synapse/events/utils.py10
-rw-r--r--synapse/handlers/events.py7
-rw-r--r--synapse/handlers/federation.py6
-rw-r--r--synapse/handlers/message.py15
-rw-r--r--synapse/handlers/room.py17
-rw-r--r--synapse/handlers/typing.py8
-rw-r--r--synapse/media/v1/__init__.py6
-rw-r--r--synapse/media/v1/thumbnailer.py2
-rw-r--r--synapse/python_dependencies.py80
-rw-r--r--synapse/rest/events.py5
-rw-r--r--synapse/rest/initial_sync.py5
-rw-r--r--synapse/rest/room.py5
-rw-r--r--synapse/server.py4
-rw-r--r--synapse/storage/stream.py8
18 files changed, 174 insertions, 27 deletions
diff --git a/synapse/__init__.py b/synapse/__init__.py
index e7e27b06ec..895a0766d2 100644
--- a/synapse/__init__.py
+++ b/synapse/__init__.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-""" This is a reference implementation of a synapse home server.
+""" This is a reference implementation of a Matrix home server.
 """
 
 __version__ = "0.6.1b"
diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py
index 5fec8da7ca..43b5c26144 100755
--- a/synapse/app/homeserver.py
+++ b/synapse/app/homeserver.py
@@ -18,6 +18,8 @@ from synapse.storage import prepare_database, UpgradeDatabaseException
 
 from synapse.server import HomeServer
 
+from synapse.python_dependencies import check_requirements
+
 from twisted.internet import reactor
 from twisted.enterprise import adbapi
 from twisted.web.resource import Resource
@@ -39,6 +41,8 @@ from synapse.util.logcontext import LoggingContext
 from daemonize import Daemonize
 import twisted.manhole.telnet
 
+import synapse
+
 import logging
 import os
 import re
@@ -198,7 +202,10 @@ def setup():
 
     config.setup_logging()
 
+    check_requirements()
+
     logger.info("Server hostname: %s", config.server_name)
+    logger.info("Server version: %s", synapse.__version__)
 
     if re.search(":[0-9]+$", config.server_name):
         domain_with_port = config.server_name
@@ -277,6 +284,7 @@ def run():
 
 def main():
     with LoggingContext("main"):
+        check_requirements()
         setup()
 
 
diff --git a/synapse/config/logger.py b/synapse/config/logger.py
index 15383b3184..f9568ebd21 100644
--- a/synapse/config/logger.py
+++ b/synapse/config/logger.py
@@ -66,7 +66,10 @@ class LoggingConfig(Config):
 
             formatter = logging.Formatter(log_format)
             if self.log_file:
-                handler = logging.FileHandler(self.log_file)
+                # TODO: Customisable file size / backup count
+                handler = logging.handlers.RotatingFileHandler(
+                    self.log_file, maxBytes=(1000 * 1000 * 100), backupCount=3
+                )
             else:
                 handler = logging.StreamHandler()
             handler.setFormatter(formatter)
diff --git a/synapse/config/server.py b/synapse/config/server.py
index 4f73c85466..31e44cc857 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -47,8 +47,12 @@ class ServerConfig(Config):
     def add_arguments(cls, parser):
         super(ServerConfig, cls).add_arguments(parser)
         server_group = parser.add_argument_group("server")
-        server_group.add_argument("-H", "--server-name", default="localhost",
-                                  help="The name of the server")
+        server_group.add_argument(
+            "-H", "--server-name", default="localhost",
+            help="The domain name of the server, with optional explicit port. "
+                 "This is used by remote servers to connect to this server, "
+                 "e.g. matrix.org, localhost:8080, etc."
+        )
         server_group.add_argument("--signing-key-path",
                                   help="The signing key to sign messages with")
         server_group.add_argument("-p", "--bind-port", metavar="PORT",
diff --git a/synapse/events/utils.py b/synapse/events/utils.py
index 4ad37188ba..bcb5457278 100644
--- a/synapse/events/utils.py
+++ b/synapse/events/utils.py
@@ -89,13 +89,21 @@ def prune_event(event):
     return type(event)(allowed_fields)
 
 
-def serialize_event(hs, e):
+def serialize_event(hs, e, client_event=True):
     # FIXME(erikj): To handle the case of presence events and the like
     if not isinstance(e, EventBase):
         return e
 
     # Should this strip out None's?
     d = {k: v for k, v in e.get_dict().items()}
+
+    if not client_event:
+        # set the age and keep all other keys
+        if "age_ts" in d["unsigned"]:
+            now = int(hs.get_clock().time_msec())
+            d["unsigned"]["age"] = now - d["unsigned"]["age_ts"]
+        return d
+
     if "age_ts" in d["unsigned"]:
         now = int(hs.get_clock().time_msec())
         d["age"] = now - d["unsigned"]["age_ts"]
diff --git a/synapse/handlers/events.py b/synapse/handlers/events.py
index 808219bd10..c9ade253dd 100644
--- a/synapse/handlers/events.py
+++ b/synapse/handlers/events.py
@@ -46,7 +46,8 @@ class EventStreamHandler(BaseHandler):
 
     @defer.inlineCallbacks
     @log_function
-    def get_stream(self, auth_user_id, pagin_config, timeout=0):
+    def get_stream(self, auth_user_id, pagin_config, timeout=0,
+                   as_client_event=True):
         auth_user = self.hs.parse_userid(auth_user_id)
 
         try:
@@ -78,7 +79,9 @@ class EventStreamHandler(BaseHandler):
                     auth_user, room_ids, pagin_config, timeout
                 )
 
-            chunks = [self.hs.serialize_event(e) for e in events]
+            chunks = [
+                self.hs.serialize_event(e, as_client_event) for e in events
+            ]
 
             chunk = {
                 "chunk": chunks,
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index d26975a88a..195f7c618a 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -617,13 +617,13 @@ class FederationHandler(BaseHandler):
 
     @defer.inlineCallbacks
     @log_function
-    def on_backfill_request(self, origin, context, pdu_list, limit):
-        in_room = yield self.auth.check_host_in_room(context, origin)
+    def on_backfill_request(self, origin, room_id, pdu_list, limit):
+        in_room = yield self.auth.check_host_in_room(room_id, origin)
         if not in_room:
             raise AuthError(403, "Host not in room.")
 
         events = yield self.store.get_backfill_events(
-            context,
+            room_id,
             pdu_list,
             limit
         )
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index 7195de98b5..f2a2f16933 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -67,7 +67,7 @@ class MessageHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def get_messages(self, user_id=None, room_id=None, pagin_config=None,
-                     feedback=False):
+                     feedback=False, as_client_event=True):
         """Get messages in a room.
 
         Args:
@@ -76,6 +76,7 @@ class MessageHandler(BaseHandler):
             pagin_config (synapse.api.streams.PaginationConfig): The pagination
             config rules to apply, if any.
             feedback (bool): True to get compressed feedback with the messages
+            as_client_event (bool): True to get events in client-server format.
         Returns:
             dict: Pagination API results
         """
@@ -99,7 +100,9 @@ class MessageHandler(BaseHandler):
         )
 
         chunk = {
-            "chunk": [self.hs.serialize_event(e) for e in events],
+            "chunk": [
+                self.hs.serialize_event(e, as_client_event) for e in events
+            ],
             "start": pagin_config.from_token.to_string(),
             "end": next_token.to_string(),
         }
@@ -211,7 +214,7 @@ class MessageHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def snapshot_all_rooms(self, user_id=None, pagin_config=None,
-                           feedback=False):
+                           feedback=False, as_client_event=True):
         """Retrieve a snapshot of all rooms the user is invited or has joined.
 
         This snapshot may include messages for all rooms where the user is
@@ -222,6 +225,7 @@ class MessageHandler(BaseHandler):
             pagin_config (synapse.api.streams.PaginationConfig): The pagination
             config used to determine how many messages *PER ROOM* to return.
             feedback (bool): True to get feedback along with these messages.
+            as_client_event (bool): True to get events in client-server format.
         Returns:
             A list of dicts with "room_id" and "membership" keys for all rooms
             the user is currently invited or joined in on. Rooms where the user
@@ -280,7 +284,10 @@ class MessageHandler(BaseHandler):
                 end_token = now_token.copy_and_replace("room_key", token[1])
 
                 d["messages"] = {
-                    "chunk": [self.hs.serialize_event(m) for m in messages],
+                    "chunk": [
+                        self.hs.serialize_event(m, as_client_event)
+                        for m in messages
+                    ],
                     "start": start_token.to_string(),
                     "end": end_token.to_string(),
                 }
diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index 59719a1fae..6d0db18e51 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -425,10 +425,22 @@ class RoomMemberHandler(BaseHandler):
             event.room_id,
             self.hs.hostname
         )
+        if not is_host_in_room:
+            # is *anyone* in the room?
+            room_member_keys = [
+                v for (k, v) in context.current_state.keys() if (
+                    k == "m.room.member"
+                )
+            ]
+            if len(room_member_keys) == 0:
+                # has the room been created so we can join it?
+                create_event = context.current_state.get(("m.room.create", ""))
+                if create_event:
+                    is_host_in_room = True
 
         if is_host_in_room:
             should_do_dance = False
-        elif room_host:
+        elif room_host:  # TODO: Shouldn't this be remote_room_host?
             should_do_dance = True
         else:
             # TODO(markjh): get prev_state from snapshot
@@ -442,7 +454,8 @@ class RoomMemberHandler(BaseHandler):
                 should_do_dance = not self.hs.is_mine(inviter)
                 room_host = inviter.domain
             else:
-                should_do_dance = False
+                # return the same error as join_room_alias does
+                raise SynapseError(404, "No known servers")
 
         if should_do_dance:
             handler = self.hs.get_handlers().federation_handler
diff --git a/synapse/handlers/typing.py b/synapse/handlers/typing.py
index ab698b36e1..22ce7873d0 100644
--- a/synapse/handlers/typing.py
+++ b/synapse/handlers/typing.py
@@ -83,9 +83,15 @@ class TypingNotificationHandler(BaseHandler):
         if member in self._member_typing_timer:
             self.clock.cancel_call_later(self._member_typing_timer[member])
 
+        def _cb():
+            logger.debug(
+                "%s has timed out in %s", target_user.to_string(), room_id
+            )
+            self._stopped_typing(member)
+
         self._member_typing_until[member] = until
         self._member_typing_timer[member] = self.clock.call_later(
-            timeout / 1000, lambda: self._stopped_typing(member)
+            timeout / 1000.0, _cb
         )
 
         if was_present:
diff --git a/synapse/media/v1/__init__.py b/synapse/media/v1/__init__.py
index 619999d268..d6c6690577 100644
--- a/synapse/media/v1/__init__.py
+++ b/synapse/media/v1/__init__.py
@@ -22,7 +22,8 @@ except IOError as e:
     if str(e).startswith("decoder jpeg not available"):
         raise Exception(
             "FATAL: jpeg codec not supported. Install pillow correctly! "
-            " 'sudo apt-get install libjpeg-dev' then 'pip install -I pillow'"
+            " 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
+            " pip install pillow --user'"
         )
 except Exception:
     # any other exception is fine
@@ -36,7 +37,8 @@ except IOError as e:
     if str(e).startswith("decoder zip not available"):
         raise Exception(
             "FATAL: zip codec not supported. Install pillow correctly! "
-            " 'sudo apt-get install libjpeg-dev' then 'pip install -I pillow'"
+            " 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
+            " pip install pillow --user'"
         )
 except Exception:
     # any other exception is fine
diff --git a/synapse/media/v1/thumbnailer.py b/synapse/media/v1/thumbnailer.py
index bc86efea8f..28404f2b7b 100644
--- a/synapse/media/v1/thumbnailer.py
+++ b/synapse/media/v1/thumbnailer.py
@@ -82,7 +82,7 @@ class Thumbnailer(object):
 
     def save_image(self, output_image, output_type, output_path):
         output_bytes_io = BytesIO()
-        output_image.save(output_bytes_io, self.FORMATS[output_type])
+        output_image.save(output_bytes_io, self.FORMATS[output_type], quality=70)
         output_bytes = output_bytes_io.getvalue()
         with open(output_path, "wb") as output_file:
             output_file.write(output_bytes)
diff --git a/synapse/python_dependencies.py b/synapse/python_dependencies.py
new file mode 100644
index 0000000000..b1fae991e0
--- /dev/null
+++ b/synapse/python_dependencies.py
@@ -0,0 +1,80 @@
+import logging
+from distutils.version import LooseVersion
+
+logger = logging.getLogger(__name__)
+
+REQUIREMENTS = {
+    "syutil==0.0.2": ["syutil"],
+    "matrix_angular_sdk==0.6.0": ["syweb==0.6.0"],
+    "Twisted>=14.0.0": ["twisted>=14.0.0"],
+    "service_identity>=1.0.0": ["service_identity>=1.0.0"],
+    "pyopenssl>=0.14": ["OpenSSL>=0.14"],
+    "pyyaml": ["yaml"],
+    "pyasn1": ["pyasn1"],
+    "pynacl": ["nacl"],
+    "daemonize": ["daemonize"],
+    "py-bcrypt": ["bcrypt"],
+    "frozendict>=0.4": ["frozendict"],
+    "pillow": ["PIL"],
+}
+
+
+class MissingRequirementError(Exception):
+    pass
+
+
+def check_requirements():
+    """Checks that all the modules needed by synapse have been correctly
+    installed and are at the correct version"""
+    for dependency, module_requirements in REQUIREMENTS.items():
+        for module_requirement in module_requirements:
+            if ">=" in module_requirement:
+                module_name, required_version = module_requirement.split(">=")
+                version_test = ">="
+            elif "==" in module_requirement:
+                module_name, required_version = module_requirement.split("==")
+                version_test = "=="
+            else:
+                module_name = module_requirement
+                version_test = None
+
+            try:
+                module = __import__(module_name)
+            except ImportError:
+                logging.exception(
+                    "Can't import %r which is part of %r",
+                    module_name, dependency
+                )
+                raise MissingRequirementError(
+                    "Can't import %r which is part of %r"
+                    % (module_name, dependency)
+                )
+            version = getattr(module, "__version__", None)
+            file_path = getattr(module, "__file__", None)
+            logger.info(
+                "Using %r version %r from %r to satisfy %r",
+                module_name, version, file_path, dependency
+            )
+
+            if version_test == ">=":
+                if version is None:
+                    raise MissingRequirementError(
+                        "Version of %r isn't set as __version__ of module %r"
+                        % (dependency, module_name)
+                    )
+                if LooseVersion(version) < LooseVersion(required_version):
+                    raise MissingRequirementError(
+                        "Version of %r in %r is too old. %r < %r"
+                        % (dependency, file_path, version, required_version)
+                    )
+            elif version_test == "==":
+                if version is None:
+                    raise MissingRequirementError(
+                        "Version of %r isn't set as __version__ of module %r"
+                        % (dependency, module_name)
+                    )
+                if LooseVersion(version) != LooseVersion(required_version):
+                    raise MissingRequirementError(
+                        "Unexpected version of %r in %r. %r != %r"
+                        % (dependency, file_path, version, required_version)
+                    )
diff --git a/synapse/rest/events.py b/synapse/rest/events.py
index cf6d13f817..bedcb2bcc6 100644
--- a/synapse/rest/events.py
+++ b/synapse/rest/events.py
@@ -44,8 +44,11 @@ class EventStreamRestServlet(RestServlet):
                 except ValueError:
                     raise SynapseError(400, "timeout must be in milliseconds.")
 
+            as_client_event = "raw" not in request.args
+
             chunk = yield handler.get_stream(
-                auth_user.to_string(), pagin_config, timeout=timeout
+                auth_user.to_string(), pagin_config, timeout=timeout,
+                as_client_event=as_client_event
             )
         except:
             logger.exception("Event stream failed")
diff --git a/synapse/rest/initial_sync.py b/synapse/rest/initial_sync.py
index a571589581..b13d56b286 100644
--- a/synapse/rest/initial_sync.py
+++ b/synapse/rest/initial_sync.py
@@ -27,12 +27,15 @@ class InitialSyncRestServlet(RestServlet):
     def on_GET(self, request):
         user = yield self.auth.get_user_by_req(request)
         with_feedback = "feedback" in request.args
+        as_client_event = "raw" not in request.args
         pagination_config = PaginationConfig.from_request(request)
         handler = self.handlers.message_handler
         content = yield handler.snapshot_all_rooms(
             user_id=user.to_string(),
             pagin_config=pagination_config,
-            feedback=with_feedback)
+            feedback=with_feedback,
+            as_client_event=as_client_event
+        )
 
         defer.returnValue((200, content))
 
diff --git a/synapse/rest/room.py b/synapse/rest/room.py
index e40773758a..caafa959e6 100644
--- a/synapse/rest/room.py
+++ b/synapse/rest/room.py
@@ -314,12 +314,15 @@ class RoomMessageListRestServlet(RestServlet):
             request, default_limit=10,
         )
         with_feedback = "feedback" in request.args
+        as_client_event = "raw" not in request.args
         handler = self.handlers.message_handler
         msgs = yield handler.get_messages(
             room_id=room_id,
             user_id=user.to_string(),
             pagin_config=pagination_config,
-            feedback=with_feedback)
+            feedback=with_feedback,
+            as_client_event=as_client_event
+        )
 
         defer.returnValue((200, msgs))
 
diff --git a/synapse/server.py b/synapse/server.py
index c3bf46abbf..d861efd2fd 100644
--- a/synapse/server.py
+++ b/synapse/server.py
@@ -149,8 +149,8 @@ class BaseHomeServer(object):
         object."""
         return EventID.from_string(s)
 
-    def serialize_event(self, e):
-        return serialize_event(self, e)
+    def serialize_event(self, e, as_client_event=True):
+        return serialize_event(self, e, as_client_event)
 
     def get_ip_from_request(self, request):
         # May be an X-Forwarding-For header depending on config
diff --git a/synapse/storage/stream.py b/synapse/storage/stream.py
index bedc3c6c52..744c821dfe 100644
--- a/synapse/storage/stream.py
+++ b/synapse/storage/stream.py
@@ -284,8 +284,12 @@ class StreamStore(SQLBaseStore):
             rows.reverse()  # As we selected with reverse ordering
 
             if rows:
-                topo = rows[0]["topological_ordering"]
-                toke = rows[0]["stream_ordering"]
+                # XXX: Always subtract 1 since the start token always goes
+                # backwards (parity with paginate_room_events). It isn't
+                # obvious that this is correct; we should clarify the algorithm
+                # used here.
+                topo = rows[0]["topological_ordering"] - 1
+                toke = rows[0]["stream_ordering"] - 1
                 start_token = "t%s-%s" % (topo, toke)
 
                 token = (start_token, end_token)