summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--docs/specification.rst109
-rw-r--r--synapse/handlers/message.py8
-rw-r--r--synapse/handlers/presence.py6
-rw-r--r--tests/rest/test_rooms.py12
-rw-r--r--tests/utils.py5
-rw-r--r--webclient/index.html2
-rw-r--r--webclient/recents/recents-controller.js5
-rw-r--r--webclient/recents/recents.html4
8 files changed, 126 insertions, 25 deletions
diff --git a/docs/specification.rst b/docs/specification.rst
index d47705ca0f..9a494a4c0f 100644
--- a/docs/specification.rst
+++ b/docs/specification.rst
@@ -391,22 +391,101 @@ If all members in a room leave, that room becomes eligible for deletion.
 
 Events in a room
 ----------------
-- Split into state and non-state data
-- Explain what they are, semantics, give examples of clobbering / not, use cases (msgs vs room names).
-  Not too much detail on the actual event contents.
-- API to hit.
-- Extensibility provided by the API for custom events. Examples.
-- How this hooks into ``initialSync``.
-- See the "Room Events" section for actual spec on each type.
-
-Syncing a room
---------------
-- Single room initial sync. ``rooms/<room id>/initialSync`` API to hit. Why it might be used (lazy loading)
+Room events can be split into two categories:
+
+:State Events:
+  These are events which replace events that came before it, depending on a set of unique keys.
+  These keys are the event ``type`` and a ``state_key``. Events with the same set of keys will
+  be overwritten. Typically, state events are used to store state, hence their name.
+
+:Non-state events:
+  These are events which cannot be overwritten after sending. The list of events continues
+  to grow as more events are sent. As this list grows, it becomes necessary to
+  provide a mechanism for navigating this list. Pagination APIs are used to view the list
+  of historical non-state events. Typically, non-state events are used to send messages.
+
+This specification outlines several events, all with the event type prefix ``m.``. However,
+applications may wish to add their own type of event, and this can be achieved using the 
+REST API detailed in the following sections. If new events are added, the event ``type`` 
+key SHOULD follow the Java package naming convention, e.g. ``com.example.myapp.event``. 
+This ensures event types are suitably namespaced for each application and reduces the 
+risk of clashes.
+
+State events
+------------
+State events can be sent by ``PUT`` ing to ``/rooms/<room id>/state/<event type>/<state key>``.
+These events will be overwritten if ``<room id>``, ``<event type>`` and ``<state key>`` all match.
+If the state event has no ``state_key``, it can be omitted from the path. These requests 
+**cannot use transaction IDs** like other ``PUT`` paths because they cannot be differentiated 
+from the ``state key``. Furthermore, ``POST`` is unsupported on state paths. Valid requests
+look like::
+
+  PUT /rooms/!roomid:domain/state/m.example.event
+  { "key" : "without a state key" }
+
+  PUT /rooms/!roomid:domain/state/m.another.example.event/foo
+  { "key" : "with 'foo' as the state key" }
+
+In contrast, these requests are invalid::
+
+  POST /rooms/!roomid:domain/state/m.example.event/
+  { "key" : "cannot use POST here" }
+
+  PUT /rooms/!roomid:domain/state/m.another.example.event/foo/11
+  { "key" : "txnIds are not supported" }
+
+Care should be taken to avoid setting the wrong ``state key``::
+
+  PUT /rooms/!roomid:domain/state/m.another.example.event/11
+  { "key" : "with '11' as the state key, but was probably intended to be a txnId" }
+
+The ``state_key`` is often used to store state about individual users, by using the user ID as the
+value. For example::
+
+  PUT /rooms/!roomid:domain/state/m.favorite.animal.event/%40my_user%3Adomain.com
+  { "animal" : "cat", "reason": "fluffy" }
+
+In some cases, there may be no need for a ``state_key``, so it can be omitted::
+
+  PUT /rooms/!roomid:domain/state/m.room.bgd.color
+  { "color": "red", "hex": "#ff0000" }
+
+See "Room Events" for the ``m.`` event specification.
+
+Non-state events
+----------------
+Non-state events can be sent by sending a request to ``/rooms/<room id>/send/<event type>``.
+These requests *can* use transaction IDs and ``PUT``/``POST`` methods. Non-state events 
+allow access to historical events and pagination, making it best suited for sending messages.
+For example::
+
+  POST /rooms/!roomid:domain/send/m.custom.example.message
+  { "text": "Hello world!" }
+
+  PUT /rooms/!roomid:domain/send/m.custom.example.message/11
+  { "text": "Goodbye world!" }
+
+See "Room Events" for the ``m.`` event specification.
+
+Syncing rooms
+-------------
+When a client logs in, they may have a list of rooms which they have already joined. These rooms
+may also have a list of events associated with them. The purpose of 'syncing' is to present the
+current room and event information in a convenient, compact manner. There are two APIs provided:
+
+ - ``/initialSync`` : A global sync which will present room and event information for all rooms
+   the user has joined.
+
+ - ``/rooms/<room id>/initialSync`` : A sync scoped to a single room. Presents room and event
+   information for this room only.
+
+- TODO: JSON response format for both types
+- TODO: when would you use global? when would you use scoped?
 
-Getting grouped state events
-----------------------------
-- ``/members`` and ``/messages`` and the events they return.
-- ``/state`` and it returns ALL THE THINGS.
+Getting grouped state events for a room
+---------------------------------------
+- ``/members`` and ``/messages`` and the event types they return. Spec JSON response format.
+- ``/state`` and it returns ALL THE THINGS. 
 
 Room Events
 ===========
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index c8ff34e5f5..4c74ce3eff 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -76,6 +76,10 @@ class MessageHandler(BaseRoomHandler):
         Raises:
             SynapseError if something went wrong.
         """
+        # TODO(paul): Why does 'event' not have a 'user' object?
+        user = self.hs.parse_userid(event.user_id)
+        assert(user.is_mine)
+
         if stamp_event:
             event.content["hsob_ts"] = int(self.clock.time_msec())
 
@@ -86,6 +90,10 @@ class MessageHandler(BaseRoomHandler):
 
         yield self._on_new_room_event(event, snapshot)
 
+        self.hs.get_handlers().presence_handler.bump_presence_active_time(
+            user
+        )
+
     @defer.inlineCallbacks
     def get_messages(self, user_id=None, room_id=None, pagin_config=None,
                      feedback=False):
diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py
index c1af07133f..f55bea58a5 100644
--- a/synapse/handlers/presence.py
+++ b/synapse/handlers/presence.py
@@ -266,6 +266,12 @@ class PresenceHandler(BaseHandler):
         #   we don't have to do this all the time
         self.changed_presencelike_data(target_user, state)
 
+    def bump_presence_active_time(self, user, now=None):
+        if now is None:
+            now = self.clock.time_msec()
+
+        self.changed_presencelike_data(user, {"last_active": now})
+
     def changed_presencelike_data(self, user, state):
         statuscache = self._get_or_make_usercache(user)
 
diff --git a/tests/rest/test_rooms.py b/tests/rest/test_rooms.py
index b432cf254e..cdaf948a3b 100644
--- a/tests/rest/test_rooms.py
+++ b/tests/rest/test_rooms.py
@@ -51,7 +51,7 @@ class RoomPermissionsTestCase(RestTestCase):
         persistence_service.get_latest_pdus_in_context.return_value = []
 
         hs = HomeServer(
-            "test",
+            "red",
             db_pool=None,
             http_client=None,
             datastore=MemoryDataStore(),
@@ -398,7 +398,7 @@ class RoomsMemberListTestCase(RestTestCase):
         persistence_service.get_latest_pdus_in_context.return_value = []
 
         hs = HomeServer(
-            "test",
+            "red",
             db_pool=None,
             http_client=None,
             datastore=MemoryDataStore(),
@@ -476,7 +476,7 @@ class RoomsCreateTestCase(RestTestCase):
         persistence_service.get_latest_pdus_in_context.return_value = []
 
         hs = HomeServer(
-            "test",
+            "red",
             db_pool=None,
             http_client=None,
             datastore=MemoryDataStore(),
@@ -566,7 +566,7 @@ class RoomTopicTestCase(RestTestCase):
         persistence_service.get_latest_pdus_in_context.return_value = []
 
         hs = HomeServer(
-            "test",
+            "red",
             db_pool=None,
             http_client=None,
             datastore=MemoryDataStore(),
@@ -669,7 +669,7 @@ class RoomMemberStateTestCase(RestTestCase):
         persistence_service.get_latest_pdus_in_context.return_value = []
 
         hs = HomeServer(
-            "test",
+            "red",
             db_pool=None,
             http_client=None,
             datastore=MemoryDataStore(),
@@ -794,7 +794,7 @@ class RoomMessagesTestCase(RestTestCase):
         persistence_service.get_latest_pdus_in_context.return_value = []
 
         hs = HomeServer(
-            "test",
+            "red",
             db_pool=None,
             http_client=None,
             datastore=MemoryDataStore(),
diff --git a/tests/utils.py b/tests/utils.py
index 37b759febc..ea7d6893c6 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -188,8 +188,9 @@ class MemoryDataStore(object):
 
     def get_rooms_for_user_where_membership_is(self, user_id, membership_list):
         return [
-            r for r in self.members
-            if self.members[r].get(user_id).membership in membership_list
+            self.members[r].get(user_id) for r in self.members
+            if user_id in self.members[r] and
+                self.members[r][user_id].membership in membership_list
         ]
 
     def get_room_events_stream(self, user_id=None, from_key=None, to_key=None,
diff --git a/webclient/index.html b/webclient/index.html
index 3c31a8a051..bf24e392ac 100644
--- a/webclient/index.html
+++ b/webclient/index.html
@@ -56,7 +56,7 @@
 
     <div id="footer" ng-hide="location.indexOf('/room') == 0">
         <div id="footerContent">
-            &copy 2014 Matrix.org
+            &copy; 2014 Matrix.org
         </div>
     </div>
 </body>
diff --git a/webclient/recents/recents-controller.js b/webclient/recents/recents-controller.js
index d33d41a922..c9fd022d7f 100644
--- a/webclient/recents/recents-controller.js
+++ b/webclient/recents/recents-controller.js
@@ -43,6 +43,11 @@ angular.module('RecentsController', ['matrixService', 'eventHandlerService'])
                 $scope.rooms[event.room_id].lastMsg = event;              
             }
         });
+        $scope.$on(eventHandlerService.CALL_EVENT, function(ngEvent, event, isLive) {
+            if (isLive) {
+                $scope.rooms[event.room_id].lastMsg = event;
+            }
+        });
     };
 
     
diff --git a/webclient/recents/recents.html b/webclient/recents/recents.html
index 3f025a98d8..56fb38b02a 100644
--- a/webclient/recents/recents.html
+++ b/webclient/recents/recents.html
@@ -51,7 +51,9 @@
                         </div>
 
                         <div ng-switch-default>
-                            {{ room.lastMsg }}
+                            <div ng-if="room.lastMsg.type.indexOf('m.call.') == 0">
+                                Call
+                            </div>
                         </div>
                     </div>
                 </td>