summary refs log tree commit diff
diff options
context:
space:
mode:
authorKegan Dougal <kegan@matrix.org>2014-08-13 15:53:44 +0100
committerKegan Dougal <kegan@matrix.org>2014-08-13 15:53:44 +0100
commit38c7e923312047b2c26c6ca3f15a9813159088e7 (patch)
tree819149e28fe875a9e8ee1fe22a86f4d64cdd4355
parentMerge branch 'master' of github.com:matrix-org/synapse (diff)
parentmore quickstart fixes (diff)
downloadsynapse-38c7e923312047b2c26c6ca3f15a9813159088e7.tar.xz
Merge branch 'master' of github.com:matrix-org/synapse
-rw-r--r--MAP.rst35
-rw-r--r--README.rst133
-rw-r--r--docs/server-server/specification.rst14
-rw-r--r--synapse/api/constants.py8
-rw-r--r--synapse/app/homeserver.py5
-rw-r--r--synapse/federation/transport.py33
-rw-r--r--tests/federation/test_federation.py15
-rw-r--r--tests/handlers/test_presence.py24
-rw-r--r--tests/handlers/test_presencelike.py12
-rw-r--r--tests/rest/test_presence.py12
-rw-r--r--webclient/app.css2
-rw-r--r--webclient/room/room-controller.js11
-rw-r--r--webclient/room/room.html2
13 files changed, 160 insertions, 146 deletions
diff --git a/MAP.rst b/MAP.rst
new file mode 100644
index 0000000000..0f8e9818a8
--- /dev/null
+++ b/MAP.rst
@@ -0,0 +1,35 @@
+Directory Structure
+===================
+
+Warning: this may be a bit stale...
+
+::
+
+    .
+    ├── cmdclient           Basic CLI python Matrix client
+    ├── demo                Scripts for running standalone Matrix demos
+    ├── docs                All doc, including the draft Matrix API spec
+    │   ├── client-server       The client-server Matrix API spec
+    │   ├── model               Domain-specific elements of the Matrix API spec
+    │   ├── server-server       The server-server model of the Matrix API spec
+    │   └── sphinx              The internal API doc of the Synapse homeserver
+    ├── experiments         Early experiments of using Synapse's internal APIs
+    ├── graph               Visualisation of Matrix's distributed message store 
+    ├── synapse             The reference Matrix homeserver implementation
+    │   ├── api                 Common building blocks for the APIs
+    │   │   ├── events              Definition of state representation Events 
+    │   │   └── streams             Definition of streamable Event objects
+    │   ├── app                 The __main__ entry point for the homeserver
+    │   ├── crypto              The PKI client/server used for secure federation
+    │   │   └── resource            PKI helper objects (e.g. keys)
+    │   ├── federation          Server-server state replication logic
+    │   ├── handlers            The main business logic of the homeserver
+    │   ├── http                Wrappers around Twisted's HTTP server & client
+    │   ├── rest                Servlet-style RESTful API
+    │   ├── storage             Persistence subsystem (currently only sqlite3)
+    │   │   └── schema              sqlite persistence schema
+    │   └── util                Synapse-specific utilities
+    ├── tests               Unit tests for the Synapse homeserver
+    └── webclient           Basic AngularJS Matrix web client
+
+
diff --git a/README.rst b/README.rst
index 2885c9f2f2..319bbb6e4e 100644
--- a/README.rst
+++ b/README.rst
@@ -1,16 +1,52 @@
-About
-=====
+Quick Start
+===========
 
 Matrix is an ambitious new ecosystem for open federated Instant Messaging and
-VoIP[1].
+VoIP[1].  The basics you need to know to get up and running are:
+
+    - Chatrooms are distributed and do not exist on any single server.  Rooms 
+      can be found using names like ``#matrix:matrix.org`` or 
+      ``#test:localhost:8080`` or they can be ephemeral.
+    
+    - Matrix user IDs look like ``@matthew:matrix.org`` (although in the future
+      you will normally refer to yourself and others using a 3PID: email
+      address, phone number, etc rather than manipulating matrix user IDs)
 
-Matrix specifies a set of pragmatic RESTful HTTP JSON APIs as an open standard,
-providing:
+The overall architecture is::
+
+      client <----> homeserver <=================> homeserver  <-----> client
+                e.g. matrix.org:8080        e.g. mydomain.net:8080
+
+To get up and running:
+      
+    - To simply play with an **existing** homeserver you can
+      just go straight to http://matrix.org/alpha.
+    
+    - To run your own **private** homeserver on localhost:8080, install synapse 
+      with ``python setup.py develop --user`` and then run one with
+      ``python synapse/app/homeserver.py``
+      
+    - To run your own webclient:
+      ``cd webclient; python -m SimpleHTTPServer`` and hit http://localhost:8000
+      in your web browser (a recent Chrome, Safari or Firefox for now,
+      please...)
+             
+    - To make the homeserver **public** and let it exchange messages with 
+      other homeservers and participate in the overall Matrix federation, open 
+      up port 8080 and run ``python synapse/app/homeserver.py --host 
+      machine.my.domain.name``.  Then come join ``#matrix:matrix.org`` and
+      say hi! :)
+    
+About Matrix
+============
+
+Matrix specifies a set of pragmatic RESTful HTTP JSON APIs for VoIP and IM as an
+open standard, providing:
 
     - Creating and managing fully distributed chat rooms with no
       single points of control or failure
     - Eventually-consistent cryptographically secure[2] synchronisation of room 
-	  state across a global open network of federated servers and services
+      state across a global open network of federated servers and services
     - Sending and receiving extensible messages in a room with (optional)
       end-to-end encryption[3]
     - Inviting, joining, leaving, kicking, banning room members
@@ -57,74 +93,6 @@ Thanks for trying Matrix!
 
 [3] End-to-end encryption is currently in development
 
-Quick Start
-===========
-
-The basics you need to know about Matrix are:
-
-    - Chatrooms look like ``#matrix:matrix.org`` or ``#test:localhost:8080``
-    
-    - Matrix user IDs look like ``@matthew:matrix.org`` (although in the future
-      you will normally refer to yourself and others using a 3PID: email
-      address, phone number, etc rather than manipulating matrix user IDs)
-      
-    - To simply play with an **existing** homeserver (e.g. matrix.org), you can
-      just go straight to http://matrix.org/alpha, specify a homeserver 
-      (defaults to matrix.org) and sign up and use it. (Sign-up security is
-      currently work-in-progress)
-    
-    - To run your own **private** homeserver on localhost:8080, install synapse 
-      with ``python setup.py develop --user`` and then run one with
-      ``python synapse/app/homeserver.py``
-      
-    - To run your own webclient:
-      ``cd webclient; python -m SimpleHTTPServer`` and hit http://localhost:8000
-      in your web browser (a recent Chrome, Safari or Firefox for now,
-      please...)
-       
-    - For now, register some accounts like ``@testing:localhost:8080`` from 
-      different browsers, join a room like ``#test:localhost:8080`` and have a 
-      play.
-      
-    - To quickly run a **public** homeserver that can exchange messages with 
-      other homeservers and participate in the overall Matrix federation, open 
-      up port 8080 and run ``python synapse/app/homeserver.py --host 
-      machine.my.domain.name``.  Then come join ``#matrix:matrix.org`` and
-      say hi! :)
-    
-
-Directory Structure
-===================
-
-::
-
-    .
-    ├── cmdclient           Basic CLI python Matrix client
-    ├── demo                Scripts for running standalone Matrix demos
-    ├── docs                All doc, including the draft Matrix API spec
-    │   ├── client-server       The client-server Matrix API spec
-    │   ├── model               Domain-specific elements of the Matrix API spec
-    │   ├── server-server       The server-server model of the Matrix API spec
-    │   └── sphinx              The internal API doc of the Synapse homeserver
-    ├── experiments         Early experiments of using Synapse's internal APIs
-    ├── graph               Visualisation of Matrix's distributed message store 
-    ├── synapse             The reference Matrix homeserver implementation
-    │   ├── api                 Common building blocks for the APIs
-    │   │   ├── events              Definition of state representation Events 
-    │   │   └── streams             Definition of streamable Event objects
-    │   ├── app                 The __main__ entry point for the homeserver
-    │   ├── crypto              The PKI client/server used for secure federation
-    │   │   └── resource            PKI helper objects (e.g. keys)
-    │   ├── federation          Server-server state replication logic
-    │   ├── handlers            The main business logic of the homeserver
-    │   ├── http                Wrappers around Twisted's HTTP server & client
-    │   ├── rest                Servlet-style RESTful API
-    │   ├── storage             Persistence subsystem (currently only sqlite3)
-    │   │   └── schema              sqlite persistence schema
-    │   └── util                Synapse-specific utilities
-    ├── tests               Unit tests for the Synapse homeserver
-    └── webclient           Basic AngularJS Matrix web client
-
 
 Homeserver Installation
 =======================
@@ -151,6 +119,12 @@ may need to also run:
 
     $ sudo apt-get install python-pip
     $ sudo pip install --upgrade setuptools
+    
+If you get errors about ``sodium.h`` being missing, you may also need to
+manually install a newer PyNaCl via pip as setuptools installs an old one. Or
+you can check PyNaCl out of git directly (https://github.com/pyca/pynacl) and
+installing it. Installing PyNaCl using pip may also work (remember to remove any
+other versions installed by setuputils in, for example, ~/.local/lib).
 
 This will run a process of downloading and installing into your
 user's .local/lib directory all of the required dependencies that are
@@ -168,8 +142,8 @@ This should end with a 'PASSED' result::
     PASSED (successes=143)
 
 
-Running The Synapse Homeserver
-==============================
+Setting up Federation
+=====================
 
 In order for other homeservers to send messages to your server, it will need to
 be publicly visible on the internet, and they will need to know its host name.
@@ -215,7 +189,7 @@ Running a Demo Federation of Homeservers
 
 If you want to get up and running quickly with a trio of homeservers in a
 private federation (``localhost:8080``, ``localhost:8081`` and
-``localhost:8082``) which you can then point a demo webclient at, simply run::
+``localhost:8082``) which you can then access through the webclient running at http://localhost:8080.  Simply run::
 
     $ demo/start.sh
 
@@ -279,8 +253,8 @@ as the primary means of identity and E2E encryption is not complete. As such,
 we're not yet running an identity server in public.
 
 
-How does it all work?!
-======================
+Where's the spec?!
+==================
 
 For now, please go spelunking in the ``docs/`` directory to find out.
 
@@ -297,3 +271,4 @@ sphinxcontrib-napoleon::
 Building internal API documentation::
 
     $ python setup.py build_sphinx
+
diff --git a/docs/server-server/specification.rst b/docs/server-server/specification.rst
index e1e49cc069..a386bd3e7d 100644
--- a/docs/server-server/specification.rst
+++ b/docs/server-server/specification.rst
@@ -116,9 +116,13 @@ federation.]]
 Protocol URLs
 =============
 
+All these URLs are namespaced within a prefix of 
+
+  /matrix/federation/v1/...
+
 For active pushing of messages representing live activity "as it happens":
 
-  PUT /send/:transaction_id/
+  PUT .../send/:transaction_id/
     Body: JSON encoding of a single Transaction
 
     Response: [[TODO(paul): I don't actually understand what
@@ -132,7 +136,7 @@ For active pushing of messages representing live activity "as it happens":
 
 To fetch a particular PDU:
 
-  GET /pdu/:origin/:pdu_id/
+  GET .../pdu/:origin/:pdu_id/
 
     Response: JSON encoding of a single Transaction containing one PDU
 
@@ -142,7 +146,7 @@ To fetch a particular PDU:
 
 To fetch all the state of a given context:
 
-  GET /state/:context/
+  GET .../state/:context/
 
     Response: JSON encoding of a single Transaction containing multiple PDUs
 
@@ -153,7 +157,7 @@ To fetch all the state of a given context:
 
 To paginate events on a given context:
 
-  GET /paginate/:context/
+  GET .../paginate/:context/
     Query args: v, limit
 
     Response: JSON encoding of a single Transaction containing multiple PDUs
@@ -167,7 +171,7 @@ To paginate events on a given context:
 
 To stream events all the events:
 
-  GET /pull/
+  GET .../pull/
     Query args: origin, v
 
   Response: JSON encoding of a single Transaction consisting of multiple PDUs
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index 29687c3482..1ff1af76ec 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -37,7 +37,7 @@ class Feedback(object):
 
 class PresenceState(object):
     """Represents the presence state of a user."""
-    OFFLINE = 0
-    BUSY = 1
-    ONLINE = 2
-    FREE_FOR_CHAT = 3
+    OFFLINE = u"offline"
+    UNAVAILABLE = u"unavailable"
+    ONLINE = u"online"
+    FREE_FOR_CHAT = u"free_for_chat"
diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py
index bbc37dd026..f89b4652e3 100644
--- a/synapse/app/homeserver.py
+++ b/synapse/app/homeserver.py
@@ -88,9 +88,8 @@ def setup_logging(verbosity=0, filename=None, config_path=None):
             '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s'
         )
 
-        if not verbosity or verbosity == 0:
-            level = logging.INFO
-        elif verbosity == 1:
+        level = logging.INFO
+        if verbosity:
             level = logging.DEBUG
 
         # FIXME: we need a logging.WARN for a -q quiet option
diff --git a/synapse/federation/transport.py b/synapse/federation/transport.py
index 09a5e5901a..ff3fc34419 100644
--- a/synapse/federation/transport.py
+++ b/synapse/federation/transport.py
@@ -33,6 +33,9 @@ import re
 logger = logging.getLogger(__name__)
 
 
+PREFIX = "/matrix/federation/v1"
+
+
 class TransportLayer(object):
     """This is a basic implementation of the transport layer that translates
     transactions and other requests to/from HTTP.
@@ -84,9 +87,9 @@ class TransportLayer(object):
         logger.debug("get_context_state dest=%s, context=%s",
                      destination, context)
 
-        path = "/state/%s/" % context
+        subpath = "/state/%s/" % context
 
-        return self._do_request_for_transaction(destination, path)
+        return self._do_request_for_transaction(destination, subpath)
 
     @log_function
     def get_pdu(self, destination, pdu_origin, pdu_id):
@@ -104,9 +107,9 @@ class TransportLayer(object):
         logger.debug("get_pdu dest=%s, pdu_origin=%s, pdu_id=%s",
                      destination, pdu_origin, pdu_id)
 
-        path = "/pdu/%s/%s/" % (pdu_origin, pdu_id)
+        subpath = "/pdu/%s/%s/" % (pdu_origin, pdu_id)
 
-        return self._do_request_for_transaction(destination, path)
+        return self._do_request_for_transaction(destination, subpath)
 
     @log_function
     def paginate(self, dest, context, pdu_tuples, limit):
@@ -130,14 +133,14 @@ class TransportLayer(object):
         if not pdu_tuples:
             return
 
-        path = "/paginate/%s/" % context
+        subpath = "/paginate/%s/" % context
 
         args = {"v": ["%s,%s" % (i, o) for i, o in pdu_tuples]}
         args["limit"] = limit
 
         return self._do_request_for_transaction(
             dest,
-            path,
+            subpath,
             args=args,
         )
 
@@ -166,7 +169,7 @@ class TransportLayer(object):
 
         code, response = yield self.client.put_json(
             transaction.destination,
-            path="/send/%s/" % transaction.transaction_id,
+            path=PREFIX + "/send/%s/" % transaction.transaction_id,
             data=data
         )
 
@@ -189,7 +192,7 @@ class TransportLayer(object):
         # This is when someone is trying to send us a bunch of data.
         self.server.register_path(
             "PUT",
-            re.compile("^/send/([^/]*)/$"),
+            re.compile("^" + PREFIX + "/send/([^/]*)/$"),
             self._on_send_request
         )
 
@@ -207,7 +210,7 @@ class TransportLayer(object):
         # This is for when someone asks us for everything since version X
         self.server.register_path(
             "GET",
-            re.compile("^/pull/$"),
+            re.compile("^" + PREFIX + "/pull/$"),
             lambda request: handler.on_pull_request(
                 request.args["origin"][0],
                 request.args["v"]
@@ -218,7 +221,7 @@ class TransportLayer(object):
         # data_id pair.
         self.server.register_path(
             "GET",
-            re.compile("^/pdu/([^/]*)/([^/]*)/$"),
+            re.compile("^" + PREFIX + "/pdu/([^/]*)/([^/]*)/$"),
             lambda request, pdu_origin, pdu_id: handler.on_pdu_request(
                 pdu_origin, pdu_id
             )
@@ -227,7 +230,7 @@ class TransportLayer(object):
         # This is when someone asks for all data for a given context.
         self.server.register_path(
             "GET",
-            re.compile("^/state/([^/]*)/$"),
+            re.compile("^" + PREFIX + "/state/([^/]*)/$"),
             lambda request, context: handler.on_context_state_request(
                 context
             )
@@ -235,7 +238,7 @@ class TransportLayer(object):
 
         self.server.register_path(
             "GET",
-            re.compile("^/paginate/([^/]*)/$"),
+            re.compile("^" + PREFIX + "/paginate/([^/]*)/$"),
             lambda request, context: self._on_paginate_request(
                 context, request.args["v"],
                 request.args["limit"]
@@ -244,7 +247,7 @@ class TransportLayer(object):
 
         self.server.register_path(
             "GET",
-            re.compile("^/context/([^/]*)/$"),
+            re.compile("^" + PREFIX + "/context/([^/]*)/$"),
             lambda request, context: handler.on_context_pdus_request(context)
         )
 
@@ -300,7 +303,7 @@ class TransportLayer(object):
 
     @defer.inlineCallbacks
     @log_function
-    def _do_request_for_transaction(self, destination, path, args={}):
+    def _do_request_for_transaction(self, destination, subpath, args={}):
         """
         Args:
             destination (str)
@@ -313,7 +316,7 @@ class TransportLayer(object):
 
         data = yield self.client.get_json(
             destination,
-            path=path,
+            path=PREFIX + subpath,
             args=args,
         )
 
diff --git a/tests/federation/test_federation.py b/tests/federation/test_federation.py
index 99b15b2268..f493ee253e 100644
--- a/tests/federation/test_federation.py
+++ b/tests/federation/test_federation.py
@@ -96,7 +96,7 @@ class FederationTestCase(unittest.TestCase):
 
         # Empty context initially
         (code, response) = yield self.mock_http_server.trigger("GET",
-                "/state/my-context/", None)
+                "/matrix/federation/v1/state/my-context/", None)
         self.assertEquals(200, code)
         self.assertFalse(response["pdus"])
 
@@ -121,7 +121,7 @@ class FederationTestCase(unittest.TestCase):
         )
 
         (code, response) = yield self.mock_http_server.trigger("GET",
-                "/state/my-context/", None)
+                "/matrix/federation/v1/state/my-context/", None)
         self.assertEquals(200, code)
         self.assertEquals(1, len(response["pdus"]))
 
@@ -132,7 +132,7 @@ class FederationTestCase(unittest.TestCase):
         )
 
         (code, response) = yield self.mock_http_server.trigger("GET",
-                "/pdu/red/abc123def456/", None)
+                "/matrix/federation/v1/pdu/red/abc123def456/", None)
         self.assertEquals(404, code)
 
         # Now insert such a PDU
@@ -151,7 +151,7 @@ class FederationTestCase(unittest.TestCase):
         )
 
         (code, response) = yield self.mock_http_server.trigger("GET",
-                "/pdu/red/abc123def456/", None)
+                "/matrix/federation/v1/pdu/red/abc123def456/", None)
         self.assertEquals(200, code)
         self.assertEquals(1, len(response["pdus"]))
         self.assertEquals("m.text", response["pdus"][0]["pdu_type"])
@@ -177,7 +177,7 @@ class FederationTestCase(unittest.TestCase):
 
         self.mock_http_client.put_json.assert_called_with(
                 "remote",
-                path="/send/1000000/",
+                path="/matrix/federation/v1/send/1000000/",
                 data={
                     "ts": 1000000,
                     "origin": "test",
@@ -212,7 +212,7 @@ class FederationTestCase(unittest.TestCase):
         # MockClock ensures we can guess these timestamps
         self.mock_http_client.put_json.assert_called_with(
                 "remote",
-                path="/send/1000000/",
+                path="/matrix/federation/v1/send/1000000/",
                 data={
                     "origin": "test",
                     "ts": 1000000,
@@ -234,7 +234,8 @@ class FederationTestCase(unittest.TestCase):
 
         self.federation.register_edu_handler("m.test", recv_observer)
 
-        yield self.mock_http_server.trigger("PUT", "/send/1001000/",
+        yield self.mock_http_server.trigger("PUT",
+                "/matrix/federation/v1/send/1001000/",
                 """{
                     "origin": "remote",
                     "ts": 1001000,
diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py
index e7102e4ab0..2299a2a7ba 100644
--- a/tests/handlers/test_presence.py
+++ b/tests/handlers/test_presence.py
@@ -27,7 +27,7 @@ from synapse.handlers.presence import PresenceHandler, UserPresenceCache
 
 
 OFFLINE = PresenceState.OFFLINE
-BUSY = PresenceState.BUSY
+UNAVAILABLE = PresenceState.UNAVAILABLE
 ONLINE = PresenceState.ONLINE
 
 
@@ -149,12 +149,12 @@ class PresenceStateTestCase(unittest.TestCase):
 
         yield self.handler.set_state(
                 target_user=self.u_apple, auth_user=self.u_apple,
-                state={"state": BUSY, "status_msg": "Away"})
+                state={"state": UNAVAILABLE, "status_msg": "Away"})
 
         mocked_set.assert_called_with("apple",
-                {"state": 1, "status_msg": "Away"})
+                {"state": UNAVAILABLE, "status_msg": "Away"})
         self.mock_start.assert_called_with(self.u_apple,
-                state={"state": 1, "status_msg": "Away"})
+                state={"state": UNAVAILABLE, "status_msg": "Away"})
 
         yield self.handler.set_state(
                 target_user=self.u_apple, auth_user=self.u_apple,
@@ -555,7 +555,7 @@ class PresencePushTestCase(unittest.TestCase):
                     content={
                         "push": [
                             {"user_id": "@apple:test",
-                            "state": 2},
+                            "state": "online"},
                         ],
                     }),
                 call(
@@ -564,7 +564,7 @@ class PresencePushTestCase(unittest.TestCase):
                     content={
                         "push": [
                             {"user_id": "@apple:test",
-                             "state": 2},
+                             "state": "online"},
                         ],
                     })
         ], any_order=True)
@@ -582,7 +582,7 @@ class PresencePushTestCase(unittest.TestCase):
                 "remote", "m.presence", {
                     "push": [
                         {"user_id": "@potato:remote",
-                         "state": 2},
+                         "state": "online"},
                     ],
                 }
         )
@@ -646,7 +646,7 @@ class PresencePushTestCase(unittest.TestCase):
                     content={
                         "push": [
                             {"user_id": "@apple:test",
-                            "state": 2},
+                            "state": "online"},
                         ],
                     }),
                 call(
@@ -655,7 +655,7 @@ class PresencePushTestCase(unittest.TestCase):
                     content={
                         "push": [
                             {"user_id": "@banana:test",
-                            "state": 0},
+                            "state": "offline"},
                         ],
                     }),
         ], any_order=True)
@@ -666,7 +666,7 @@ class PresencePushTestCase(unittest.TestCase):
 
         self.handler._user_cachemap[self.u_clementine] = UserPresenceCache()
         self.handler._user_cachemap[self.u_clementine].update(
-                {"state": PresenceState.ONLINE}, self.u_clementine)
+                {"state": ONLINE}, self.u_clementine)
         self.room_members.append(self.u_potato)
 
         yield self.distributor.fire("user_joined_room", self.u_clementine,
@@ -680,7 +680,7 @@ class PresencePushTestCase(unittest.TestCase):
                     content={
                         "push": [
                             {"user_id": "@clementine:test",
-                            "state": 2},
+                            "state": "online"},
                         ],
                     }),
         )
@@ -882,7 +882,7 @@ class PresencePollingTestCase(unittest.TestCase):
                 content={
                     "push": [
                         {"user_id": "@banana:test",
-                         "state": 0,
+                         "state": "offline",
                          "status_msg": None},
                     ],
                 },
diff --git a/tests/handlers/test_presencelike.py b/tests/handlers/test_presencelike.py
index 12b7dca00d..224cf646f4 100644
--- a/tests/handlers/test_presencelike.py
+++ b/tests/handlers/test_presencelike.py
@@ -29,7 +29,7 @@ from synapse.handlers.profile import ProfileHandler
 
 
 OFFLINE = PresenceState.OFFLINE
-BUSY = PresenceState.BUSY
+UNAVAILABLE = PresenceState.UNAVAILABLE
 ONLINE = PresenceState.ONLINE
 
 
@@ -125,12 +125,12 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
 
         yield self.handlers.presence_handler.set_state(
                 target_user=self.u_apple, auth_user=self.u_apple,
-                state={"state": BUSY, "status_msg": "Away"})
+                state={"state": UNAVAILABLE, "status_msg": "Away"})
 
         mocked_set.assert_called_with("apple",
-                {"state": 1, "status_msg": "Away"})
+                {"state": UNAVAILABLE, "status_msg": "Away"})
         self.mock_start.assert_called_with(self.u_apple,
-                state={"state": 1, "status_msg": "Away",
+                state={"state": UNAVAILABLE, "status_msg": "Away",
                        "displayname": "Frank",
                        "avatar_url": "http://foo"})
 
@@ -220,7 +220,7 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
                 content={
                     "push": [
                         {"user_id": "@apple:test",
-                         "state": 2,
+                         "state": "online",
                          "displayname": "Frank",
                          "avatar_url": "http://foo"},
                     ],
@@ -238,7 +238,7 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase):
                 "remote", "m.presence", {
                     "push": [
                         {"user_id": "@potato:remote",
-                         "state": 2,
+                         "state": "online",
                          "displayname": "Frank",
                          "avatar_url": "http://foo"},
                     ],
diff --git a/tests/rest/test_presence.py b/tests/rest/test_presence.py
index 5b1e060c5f..7c54e067c9 100644
--- a/tests/rest/test_presence.py
+++ b/tests/rest/test_presence.py
@@ -31,7 +31,7 @@ logging.getLogger().addHandler(logging.NullHandler())
 
 
 OFFLINE = PresenceState.OFFLINE
-BUSY = PresenceState.BUSY
+UNAVAILABLE = PresenceState.UNAVAILABLE
 ONLINE = PresenceState.ONLINE
 
 
@@ -69,7 +69,7 @@ class PresenceStateTestCase(unittest.TestCase):
     def test_get_my_status(self):
         mocked_get = self.mock_handler.get_state
         mocked_get.return_value = defer.succeed(
-                {"state": 2, "status_msg": "Available"})
+                {"state": ONLINE, "status_msg": "Available"})
 
         (code, response) = yield self.mock_server.trigger("GET",
                 "/presence/%s/status" % (myid), None)
@@ -87,12 +87,12 @@ class PresenceStateTestCase(unittest.TestCase):
 
         (code, response) = yield self.mock_server.trigger("PUT",
                 "/presence/%s/status" % (myid),
-                '{"state": 1, "status_msg": "Away"}')
+                '{"state": "unavailable", "status_msg": "Away"}')
 
         self.assertEquals(200, code)
         mocked_set.assert_called_with(target_user=self.u_apple,
                 auth_user=self.u_apple,
-                state={"state": 1, "status_msg": "Away"})
+                state={"state": UNAVAILABLE, "status_msg": "Away"})
 
 
 class PresenceListTestCase(unittest.TestCase):
@@ -234,7 +234,7 @@ class PresenceEventStreamTestCase(unittest.TestCase):
         # I'll already get my own presence state change
         self.assertEquals({"start": "0", "end": "1", "chunk": [
             {"type": "m.presence",
-             "content": {"user_id": "@apple:test", "state": 2}},
+             "content": {"user_id": "@apple:test", "state": ONLINE}},
         ]}, response)
 
         self.mock_datastore.set_presence_state.return_value = defer.succeed(
@@ -251,5 +251,5 @@ class PresenceEventStreamTestCase(unittest.TestCase):
         self.assertEquals(200, code)
         self.assertEquals({"start": "1", "end": "2", "chunk": [
             {"type": "m.presence",
-             "content": {"user_id": "@banana:test", "state": 2}},
+             "content": {"user_id": "@banana:test", "state": ONLINE}},
         ]}, response)
diff --git a/webclient/app.css b/webclient/app.css
index 9a46613d49..15b6c91300 100644
--- a/webclient/app.css
+++ b/webclient/app.css
@@ -106,7 +106,7 @@ h1 {
     background-color: #38AF00;
 }
 
-.away {
+.unavailable {
     background-color: #FFCC00;
 }
 
diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js
index cbd5c1a175..5d1c65641e 100644
--- a/webclient/room/room-controller.js
+++ b/webclient/room/room-controller.js
@@ -123,17 +123,14 @@ angular.module('RoomController', [])
         var member = $scope.members[chunk.content.user_id];
 
         if ("state" in chunk.content) {
-            var ONLINE = 2;
-            var AWAY = 1;
-            var OFFLINE = 0;
-            if (chunk.content.state === ONLINE) {
+            if (chunk.content.state === "online") {
                 member.presenceState = "online";
             }
-            else if (chunk.content.state === OFFLINE) {
+            else if (chunk.content.state === "offline") {
                 member.presenceState = "offline";
             }
-            else if (chunk.content.state === AWAY) {
-                member.presenceState = "away";
+            else if (chunk.content.state === "unavailable") {
+                member.presenceState = "unavailable";
             }
         }
 
diff --git a/webclient/room/room.html b/webclient/room/room.html
index 9cd1ec3645..5de2190b8b 100644
--- a/webclient/room/room.html
+++ b/webclient/room/room.html
@@ -14,7 +14,7 @@
                     <img class="userAvatarGradient" src="img/gradient.png" width="80" height="24"/>
                     <div class="userName">{{ info.displayname || name }}</div>
                 </td>
-                <td class="userPresence" ng-class="info.presenceState === 'online' ? 'online' : (info.presenceState === 'away' ? 'away' : '')" />
+                <td class="userPresence" ng-class="info.presenceState === 'online' ? 'online' : (info.presenceState === 'unavailable' ? 'unavailable' : '')" />
         </table>
     </div>