diff --git a/README.rst b/README.rst
index 92b94bcd7d..768da3df64 100644
--- a/README.rst
+++ b/README.rst
@@ -108,6 +108,15 @@ To install the synapse homeserver run::
This installs synapse, along with the libraries it uses, into
``$HOME/.local/lib/`` on Linux or ``$HOME/Library/Python/2.7/lib/`` on OSX.
+Your python may not give priority to locally installed libraries over system
+libraries, in which case you must add your local packages to your python path::
+
+ $ # on Linux:
+ $ export PYTHONPATH=$HOME/.local/lib/python2.7/site-packages:$PYTHONPATH
+
+ $ # on OSX:
+ $ export PYTHONPATH=$HOME/Library/Python/2.7/lib/python/site-packages:$PYTHONPATH
+
For reliable VoIP calls to be routed via this homeserver, you MUST configure
a TURN server. See docs/turn-howto.rst for details.
diff --git a/graph/graph2.py b/graph/graph2.py
index b9b8a562a0..6b551d42e5 100644
--- a/graph/graph2.py
+++ b/graph/graph2.py
@@ -23,14 +23,27 @@ import argparse
from synapse.events import FrozenEvent
-def make_graph(db_name, room_id, file_prefix):
+def make_graph(db_name, room_id, file_prefix, limit):
conn = sqlite3.connect(db_name)
- c = conn.execute(
- "SELECT json FROM event_json where room_id = ?",
- (room_id,)
+ sql = (
+ "SELECT json FROM event_json as j "
+ "INNER JOIN events as e ON e.event_id = j.event_id "
+ "WHERE j.room_id = ?"
)
+ args = [room_id]
+
+ if limit:
+ sql += (
+ " ORDER BY topological_ordering DESC, stream_ordering DESC "
+ "LIMIT ?"
+ )
+
+ args.append(limit)
+
+ c = conn.execute(sql, args)
+
events = [FrozenEvent(json.loads(e[0])) for e in c.fetchall()]
events.sort(key=lambda e: e.depth)
@@ -128,11 +141,16 @@ if __name__ == "__main__":
)
parser.add_argument(
"-p", "--prefix", dest="prefix",
- help="String to prefix output files with"
+ help="String to prefix output files with",
+ default="graph_output"
+ )
+ parser.add_argument(
+ "-l", "--limit",
+ help="Only retrieve the last N events.",
)
parser.add_argument('db')
parser.add_argument('room')
args = parser.parse_args()
- make_graph(args.db, args.room, args.prefix)
+ make_graph(args.db, args.room, args.prefix, args.limit)
diff --git a/scripts/copyrighter-sql.pl b/scripts/copyrighter-sql.pl
new file mode 100755
index 0000000000..890e51e587
--- /dev/null
+++ b/scripts/copyrighter-sql.pl
@@ -0,0 +1,33 @@
+#!/usr/bin/perl -pi
+# Copyright 2015 OpenMarket Ltd
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+$copyright = <<EOT;
+/* Copyright 2015 OpenMarket Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+EOT
+
+s/^(# -\*- coding: utf-8 -\*-\n)?/$1$copyright/ if ($. == 1);
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)
diff --git a/tests/handlers/test_typing.py b/tests/handlers/test_typing.py
index 0d4b368a43..6a498b23a4 100644
--- a/tests/handlers/test_typing.py
+++ b/tests/handlers/test_typing.py
@@ -352,3 +352,29 @@ class TypingNotificationsTestCase(unittest.TestCase):
}},
]
)
+
+ # SYN-230 - see if we can still set after timeout
+
+ yield self.handler.started_typing(
+ target_user=self.u_apple,
+ auth_user=self.u_apple,
+ room_id=self.room_id,
+ timeout=10000,
+ )
+
+ self.on_new_user_event.assert_has_calls([
+ call(rooms=[self.room_id]),
+ ])
+ self.on_new_user_event.reset_mock()
+
+ self.assertEquals(self.event_source.get_current_key(), 3)
+ self.assertEquals(
+ self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
+ [
+ {"type": "m.typing",
+ "room_id": self.room_id,
+ "content": {
+ "user_ids": [self.u_apple.to_string()],
+ }},
+ ]
+ )
diff --git a/tests/rest/test_rooms.py b/tests/rest/test_rooms.py
index 84fd730afc..8e65ff9a1c 100644
--- a/tests/rest/test_rooms.py
+++ b/tests/rest/test_rooms.py
@@ -294,7 +294,7 @@ class RoomPermissionsTestCase(RestTestCase):
# set [invite/join/left] of self, set [invite/join/left] of other,
# expect all 403s
for usr in [self.user_id, self.rmcreator_id]:
- yield self.join(room=room, user=usr, expect_code=403)
+ yield self.join(room=room, user=usr, expect_code=404)
yield self.leave(room=room, user=usr, expect_code=403)
@defer.inlineCallbacks
diff --git a/tests/rest/test_typing.py b/tests/rest/test_typing.py
index c550294d59..18138af1b5 100644
--- a/tests/rest/test_typing.py
+++ b/tests/rest/test_typing.py
@@ -21,7 +21,7 @@ from twisted.internet import defer
import synapse.rest.room
from synapse.server import HomeServer
-from ..utils import MockHttpResource, SQLiteMemoryDbPool, MockKey
+from ..utils import MockHttpResource, MockClock, SQLiteMemoryDbPool, MockKey
from .utils import RestTestCase
from mock import Mock, NonCallableMock
@@ -36,6 +36,8 @@ class RoomTypingTestCase(RestTestCase):
@defer.inlineCallbacks
def setUp(self):
+ self.clock = MockClock()
+
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
self.auth_user_id = self.user_id
@@ -47,6 +49,7 @@ class RoomTypingTestCase(RestTestCase):
hs = HomeServer(
"red",
+ clock=self.clock,
db_pool=db_pool,
http_client=None,
replication_layer=Mock(),
@@ -77,6 +80,30 @@ class RoomTypingTestCase(RestTestCase):
return defer.succeed(None)
hs.get_datastore().insert_client_ip = _insert_client_ip
+ def get_room_members(room_id):
+ if room_id == self.room_id:
+ return defer.succeed([hs.parse_userid(self.user_id)])
+ else:
+ return defer.succeed([])
+
+ @defer.inlineCallbacks
+ def fetch_room_distributions_into(room_id, localusers=None,
+ remotedomains=None, ignore_user=None):
+
+ members = yield get_room_members(room_id)
+ for member in members:
+ if ignore_user is not None and member == ignore_user:
+ continue
+
+ if hs.is_mine(member):
+ if localusers is not None:
+ localusers.add(member)
+ else:
+ if remotedomains is not None:
+ remotedomains.add(member.domain)
+ hs.get_handlers().room_member_handler.fetch_room_distributions_into = (
+ fetch_room_distributions_into)
+
synapse.rest.room.register_servlets(hs, self.mock_resource)
self.room_id = yield self.create_room_as(self.user_id)
@@ -113,3 +140,25 @@ class RoomTypingTestCase(RestTestCase):
'{"typing": false}'
)
self.assertEquals(200, code)
+
+ @defer.inlineCallbacks
+ def test_typing_timeout(self):
+ (code, _) = yield self.mock_resource.trigger("PUT",
+ "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
+ '{"typing": true, "timeout": 30000}'
+ )
+ self.assertEquals(200, code)
+
+ self.assertEquals(self.event_source.get_current_key(), 1)
+
+ self.clock.advance_time(31);
+
+ self.assertEquals(self.event_source.get_current_key(), 2)
+
+ (code, _) = yield self.mock_resource.trigger("PUT",
+ "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
+ '{"typing": true, "timeout": 30000}'
+ )
+ self.assertEquals(200, code)
+
+ self.assertEquals(self.event_source.get_current_key(), 3)
diff --git a/tests/unittest.py b/tests/unittest.py
index a9c0e05541..fe26b7574f 100644
--- a/tests/unittest.py
+++ b/tests/unittest.py
@@ -69,6 +69,8 @@ class TestCase(unittest.TestCase):
return ret
logging.getLogger().setLevel(level)
+ # Don't set SQL logging
+ logging.getLogger("synapse.storage").setLevel(old_level)
return orig()
def assertObjectHasAttributes(self, attrs, obj):
|