diff options
Diffstat (limited to 'synapse/types.py')
-rw-r--r-- | synapse/types.py | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/synapse/types.py b/synapse/types.py new file mode 100644 index 00000000..28344d8b --- /dev/null +++ b/synapse/types.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +# Copyright 2014, 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. + +from synapse.api.errors import SynapseError + +from collections import namedtuple + + +class DomainSpecificString( + namedtuple("DomainSpecificString", ("localpart", "domain")) +): + """Common base class among ID/name strings that have a local part and a + domain name, prefixed with a sigil. + + Has the fields: + + 'localpart' : The local part of the name (without the leading sigil) + 'domain' : The domain part of the name + """ + + # Deny iteration because it will bite you if you try to create a singleton + # set by: + # users = set(user) + def __iter__(self): + raise ValueError("Attempted to iterate a %s" % (type(self).__name__,)) + + # Because this class is a namedtuple of strings and booleans, it is deeply + # immutable. + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + @classmethod + def from_string(cls, s): + """Parse the string given by 's' into a structure object.""" + if len(s) < 1 or s[0] != cls.SIGIL: + raise SynapseError(400, "Expected %s string to start with '%s'" % ( + cls.__name__, cls.SIGIL, + )) + + parts = s[1:].split(':', 1) + if len(parts) != 2: + raise SynapseError( + 400, "Expected %s of the form '%slocalname:domain'" % ( + cls.__name__, cls.SIGIL, + ) + ) + + domain = parts[1] + + # This code will need changing if we want to support multiple domain + # names on one HS + return cls(localpart=parts[0], domain=domain) + + def to_string(self): + """Return a string encoding the fields of the structure object.""" + return "%s%s:%s" % (self.SIGIL, self.localpart, self.domain) + + __str__ = to_string + + @classmethod + def create(cls, localpart, domain,): + return cls(localpart=localpart, domain=domain) + + +class UserID(DomainSpecificString): + """Structure representing a user ID.""" + SIGIL = "@" + + +class RoomAlias(DomainSpecificString): + """Structure representing a room name.""" + SIGIL = "#" + + +class RoomID(DomainSpecificString): + """Structure representing a room id. """ + SIGIL = "!" + + +class EventID(DomainSpecificString): + """Structure representing an event id. """ + SIGIL = "$" + + +class StreamToken( + namedtuple("Token", ( + "room_key", + "presence_key", + "typing_key", + "receipt_key", + "private_user_data_key", + )) +): + _SEPARATOR = "_" + + @classmethod + def from_string(cls, string): + try: + keys = string.split(cls._SEPARATOR) + while len(keys) < len(cls._fields): + # i.e. old token from before receipt_key + keys.append("0") + return cls(*keys) + except: + raise SynapseError(400, "Invalid Token") + + def to_string(self): + return self._SEPARATOR.join([str(k) for k in self]) + + @property + def room_stream_id(self): + # TODO(markjh): Awful hack to work around hacks in the presence tests + # which assume that the keys are integers. + if type(self.room_key) is int: + return self.room_key + else: + return int(self.room_key[1:].split("-")[-1]) + + def is_after(self, other): + """Does this token contain events that the other doesn't?""" + return ( + (other.room_stream_id < self.room_stream_id) + or (int(other.presence_key) < int(self.presence_key)) + or (int(other.typing_key) < int(self.typing_key)) + or (int(other.receipt_key) < int(self.receipt_key)) + or (int(other.private_user_data_key) < int(self.private_user_data_key)) + ) + + def copy_and_advance(self, key, new_value): + """Advance the given key in the token to a new value if and only if the + new value is after the old value. + """ + new_token = self.copy_and_replace(key, new_value) + if key == "room_key": + new_id = new_token.room_stream_id + old_id = self.room_stream_id + else: + new_id = int(getattr(new_token, key)) + old_id = int(getattr(self, key)) + if old_id < new_id: + return new_token + else: + return self + + def copy_and_replace(self, key, new_value): + d = self._asdict() + d[key] = new_value + return StreamToken(**d) + + +class RoomStreamToken(namedtuple("_StreamToken", "topological stream")): + """Tokens are positions between events. The token "s1" comes after event 1. + + s0 s1 + | | + [0] V [1] V [2] + + Tokens can either be a point in the live event stream or a cursor going + through historic events. + + When traversing the live event stream events are ordered by when they + arrived at the homeserver. + + When traversing historic events the events are ordered by their depth in + the event graph "topological_ordering" and then by when they arrived at the + homeserver "stream_ordering". + + Live tokens start with an "s" followed by the "stream_ordering" id of the + event it comes after. Historic tokens start with a "t" followed by the + "topological_ordering" id of the event it comes after, followed by "-", + followed by the "stream_ordering" id of the event it comes after. + """ + __slots__ = [] + + @classmethod + def parse(cls, string): + try: + if string[0] == 's': + return cls(topological=None, stream=int(string[1:])) + if string[0] == 't': + parts = string[1:].split('-', 1) + return cls(topological=int(parts[0]), stream=int(parts[1])) + except: + pass + raise SynapseError(400, "Invalid token %r" % (string,)) + + @classmethod + def parse_stream_token(cls, string): + try: + if string[0] == 's': + return cls(topological=None, stream=int(string[1:])) + except: + pass + raise SynapseError(400, "Invalid token %r" % (string,)) + + def __str__(self): + if self.topological is not None: + return "t%d-%d" % (self.topological, self.stream) + else: + return "s%d" % (self.stream,) |