Example #1
0
    def __init__(self, name):
        super(PubSubService, self).__init__(name)

        # channel : str -> set<Client>
        self.subscriptions = MultiDict()
        # Client -> set<channel : str>
        self.subscriptions_by_client = MultiDict()
Example #2
0
    def __init__(self, name):
        RPCService.__init__(self, name)

        # (document : obj) -> set<Client>
        self.clients_by_document = MultiDict()
        # (Client) -> set<document : obj>
        self.documents_by_client = MultiDict()
        # (document : obj) -> dict<public_handle : int, Cursor>
        self.cursors_by_document = {}
        # (Client) -> dict<private_handle : int, Cursor>
        self.cursors_by_client = {}
Example #3
0
    def __init__(self, name):
        super(ChatService, self).__init__(name)

        # (channel) : obj -> set<Client>
        self.clients_by_channel = MultiDict()
        # (Client) -> set<channel : obj>
        self.channels_by_client = MultiDict()
        # (identity : obj, channel : obj) -> set<Client>
        self.clients_by_identity_and_channel = MultiDict()
        # (channel : obj) -> set<Identity>
        self.identities_by_channel = MultiDict()
Example #4
0
    def __init__(self, name):
        super(ChatService, self).__init__(name)

        # (channel) : obj -> set<Client>
        self.clients_by_channel = MultiDict()
        # (Client) -> set<channel : obj>
        self.channels_by_client = MultiDict()
        # (identity : obj, channel : obj) -> set<Client>
        self.clients_by_identity_and_channel = MultiDict()
        # (channel : obj) -> set<Identity>
        self.identities_by_channel = MultiDict()
Example #5
0
class SimpleDealer(Dealer):
    """This dealer uses a key function in order to determine which subscription
    items match which models.
    """
    __slots__ = ('items_by_model_key',)

    def __init__(self):
        super(SimpleDealer, self).__init__()
        self.items_by_model_key = MultiDict()

    @abc.abstractmethod
    def get_key_for_model(self, model):
        """A subclass must define a function here that for each model returns
        a value, usually the value of a certain field.

        The dealer will forward deltas to those subscriptions whose query
        equals the value returned by this function.
        """
        pass

    def add_subscription_item(self, item):
        """Adds a subscription item. The query must contain the desired value
        of the model key which will cause this client to receive a
        notification."""
        self.items_by_model_key.add(item.query, item)

    def remove_subscription_item(self, item):
        """Removes a subscription item."""
        self.items_by_model_key.remove(item.query, item)

    def get_subscription_items_for_model(self, model):
        """Returns the subscription items whose queries match the key of the
        provided model."""
        query = self.get_key_for_model(model)

        # Return the set of items with this query, or an empty set if
        # there is none.
        return self.items_by_model_key.get_set(query)
Example #6
0
 def __init__(self):
     self.subscriptions_by_token = {}
     self.subscriptions_by_client = MultiDict()
Example #7
0
class SubscriptionManager(object):
    """Tracks the active subscriptions in the system."""
    def __init__(self):
        self.subscriptions_by_token = {}
        self.subscriptions_by_client = MultiDict()

    @staticmethod
    def generate_token():
        """Return a 32 character long random string, generated by a safe CSRPNG
        of the operating system."""
        return "".join([
            safe_random.choice("abcdefghijklmnopqrstuvwxyz1234567890")
            for i in range(32)
        ])

    def register_subscription(self, subscription):
        """
        Registers a Subscription in the SubscriptionManager, assigning a secret
        unique token to it.

        It is needed for a Subscription to be authorized before a client can
        acquire it.

        Returns the new token.
        """

        if subscription.token:
            raise RuntimeError("This subscription already has a token.")

        while True:
            token = self.generate_token()

            # It's almost impossible that the same token is generated twice,
            # but it's better to be safe than sorry.
            if not token in self.subscriptions_by_token:
                break

        subscription.token = token
        self.subscriptions_by_token[token] = subscription

        return token

    def unregister_subscription(self, subscription):
        """
        Unregisters the Subscription object and removes its token.

        Note: Calling this method does not dettach the subscription items from
        the dealer. To fully delete a subscription from the system see
        `DataSyncService.do_cancel_subscription`.
        """

        token = subscription.token
        del self.subscriptions_by_token[token]

        subscription.token = None

    def get_subscription_with_token(self, token):
        """Returns a Subscription with the specified token."""
        return self.subscriptions_by_token.get(token)

    def link_subscription_to_client(self, subscription, client):
        """Associates a client and a subscription, allowing the user to receive
        notifications."""
        subscription.got_client(client)

        self.subscriptions_by_client.add(client, subscription)

    def unlink_subscription_from_client(self, subscription):
        """Destroys the association between a client and a subscription."""
        client = subscription.client
        subscription.lost_client()

        self.subscriptions_by_client.remove(client, subscription)
Example #8
0
class CursorsService(RPCService):
    def __init__(self, name):
        RPCService.__init__(self, name)

        # (document : obj) -> set<Client>
        self.clients_by_document = MultiDict()
        # (Client) -> set<document : obj>
        self.documents_by_client = MultiDict()
        # (document : obj) -> dict<public_handle : int, Cursor>
        self.cursors_by_document = {}
        # (Client) -> dict<private_handle : int, Cursor>
        self.cursors_by_client = {}

    def check_identity(self, client):
        if client.identity is None:
            raise RPCError("Not authenticated")

    @rpc_command
    def join(self, req, document):
        client = req.client
        self.check_identity(client)

        if self.clients_by_document.in_set(document, client):
            raise RPCError("Already joined")

        self.clients_by_document.add(document, client)
        self.documents_by_client.add(client, document)
        cursors = values(self.cursors_by_document.get(document, {}))
        return {
            "cursors": [
                cursor.for_json()
                for cursor in cursors
            ]
        }

    @rpc_command
    def leave(self, req, document):
        client = req.client

        if not self.clients_by_document.in_set(document, client):
            raise RPCError("Not joined")

        self.do_leave(client, document)

    def check_new_cursor_allowed(self, client):
        # Limit to 16 cursors per client
        if len(self.cursors_by_client.get(client, {})) >= 16:
            raise RPCError("Too many cursors")

    @rpc_command
    def createCursor(self, req, privateHandle, document, position, status):
        client = req.client

        if not self.clients_by_document.in_set(document, client):
            raise RPCError("Not joined")

        if privateHandle in self.cursors_by_client.get(req.client, {}):
            raise RPCError("Reused handle")

        self.check_new_cursor_allowed(client)

        # Pick a random, free handle
        while True:
            handle = random.getrandbits(16)
            if handle not in self.cursors_by_client.get(client, {}):
                break

        cursor = Cursor(privateHandle, handle,
                        document, position, status, client)

        self.cursors_by_client.setdefault(client, {})
        self.cursors_by_client[client][privateHandle] = cursor
        self.cursors_by_document.setdefault(document, {})
        self.cursors_by_document[document][cursor.public_handle] = cursor

        # Publish
        for other_client in self.clients_by_document.get_set(document):
            if other_client is client:
                continue
            self.send_message_to(other_client, {
                "type": "cursorAdded",
                "cursor": cursor.for_json(),
            })

        return handle

    @rpc_command
    def updateCursor(self, req, privateHandle, newData):
        client = req.client

        if privateHandle not in self.cursors_by_client.get(client, {}):
            raise RPCError("No such cursor")

        cursor = self.cursors_by_client[client][privateHandle]

        if "position" in newData:
            cursor.position = newData["position"]
        if "status" in newData:
            cursor.status = newData["status"]

        # Publish
        for other_client in self.clients_by_document.get_set(cursor.document):
            if other_client is client:
                continue
            self.send_message_to(other_client, {
                "type": "cursorUpdated",
                "cursor": cursor.for_json(),
            })

    @rpc_command
    def removeCursor(self, req, privateHandle):
        client = req.client

        if privateHandle not in self.cursors_by_client.get(client, {}):
            raise RPCError("No such cursor")

        self.do_remove_cursor(client, privateHandle)

    def do_remove_cursor(self, client, private_handle):
        cursor = self.cursors_by_client[client][private_handle]

        del self.cursors_by_document[cursor.document][cursor.public_handle]
        if len(self.cursors_by_document[cursor.document]) == 0:
            del self.cursors_by_document[cursor.document]

        del self.cursors_by_client[client][private_handle]
        if len(self.cursors_by_client[client]) == 0:
            del self.cursors_by_client[client]

        # Publish
        for other_client in self.clients_by_document.get_set(cursor.document):
            if other_client is client:
                continue
            self.send_message_to(other_client, {
                "type": "cursorRemoved",
                "cursor": {
                    "publicHandle": cursor.public_handle,
                    "document": cursor.document,
                },
            })

    def do_leave(self, client, document):
        # Get list of cursor handles belonging to the specified user and
        # document
        handles = [private_handle for (private_handle, cursor)
                   in items(self.cursors_by_client.get(client, {}))
                   if cursor.document == document]

        for private_handle in handles:
            self.do_remove_cursor(client, private_handle)

        self.clients_by_document.remove(document, client)
        self.documents_by_client.remove(client, document)

    def client_disconnected(self, client):
        documents = list(self.documents_by_client.get_set(client))

        for document in documents:
            self.do_leave(client, document)
Example #9
0
class ChatService(RPCService):
    def __init__(self, name):
        super(ChatService, self).__init__(name)

        # (channel) : obj -> set<Client>
        self.clients_by_channel = MultiDict()
        # (Client) -> set<channel : obj>
        self.channels_by_client = MultiDict()
        # (identity : obj, channel : obj) -> set<Client>
        self.clients_by_identity_and_channel = MultiDict()
        # (channel : obj) -> set<Identity>
        self.identities_by_channel = MultiDict()

    def get_time(self):
        from dateutil.tz import tzlocal
        return datetime.now(tz=tzlocal()).isoformat()

    def check_identity(self, client):
        if client.identity is None:
            raise RPCError("Not authenticated")

    @rpc_command
    def join(self, req, channel):
        client = req.client
        identity = client.identity

        self.check_identity(client)

        if self.clients_by_channel.in_set(channel, client):
            raise RPCError("Already joined")

        if identity not in self.identities_by_channel.get_set(channel):
            # Send presence to other members
            self.send_presence(channel, client, "joined")
            self.identities_by_channel.add(channel, identity)

        self.clients_by_channel.add(channel, client)
        self.channels_by_client.add(client, channel)
        self.clients_by_identity_and_channel.add((identity, channel),
                                                 req.client)

        return {"members": list(self.identities_by_channel.get_set(channel))}

    @rpc_command
    def leave(self, req, channel):
        client = req.client

        if not self.clients_by_channel.in_set(channel, client):
            raise RPCError("Not joined")

        self.do_leave(client, channel)

    def do_leave(self, client, channel):
        identity = client.identity

        self.clients_by_channel.remove(channel, client)
        self.channels_by_client.remove(client, channel)
        self.clients_by_identity_and_channel.remove((identity, channel),
                                                    client)

        if len(
                self.clients_by_identity_and_channel.get_set(
                    (identity, channel))) == 0:
            # User is not connected from elsewhere
            self.identities_by_channel.remove(channel, identity)
            # Send presence to remaining users
            self.send_presence(channel, client, "left")

    @rpc_command
    def send(self, req, channel, body):
        if not self.clients_by_channel.in_set(channel, req.client):
            raise RPCError("Not joined")

        message = {
            "type": "message",
            "from": req.client.identity,
            "channel": channel,
            "body": body,
            "timestamp": self.get_time(),
        }

        for client in self.clients_by_channel.get_set(channel):
            self.send_message_to(client, message)

    def send_presence(self, channel, client, status):
        presence = {
            "type": "presence",
            "from": client.identity,
            "channel": channel,
            "status": status,
            "timestamp": self.get_time(),
        }
        for client in self.clients_by_channel.get_set(channel):
            self.send_message_to(client, presence)

    @rpc_command
    def read(self, req, channel):
        notification = {
            "type": "read",
            "channel": channel,
            "timestamp": self.get_time(),
        }

        for client in self.clients_by_identity_and_channel.get_set(
            (req.client.identity, channel)):
            if client is not req.client:
                self.send_message_to(client, notification)

    def client_disconnected(self, client):
        channels = list(self.channels_by_client.get_set(client))
        for channel in channels:
            self.do_leave(client, channel)
Example #10
0
 def __init__(self):
     super(SimpleDealer, self).__init__()
     self.items_by_model_key = MultiDict()
Example #11
0
class TestMultiDict(TestCase):
    def test_add(self):
        self.multidict = MultiDict()
        self.multidict.add("key", "val1")
        self.assertEqual(self.multidict["key"], {"val1"})
        self.multidict.add("key", "val2")
        self.assertEqual(self.multidict["key"], {"val1", "val2"})

    def test_remove(self):
        self.test_add()
        self.multidict.remove("key", "val1")
        self.assertEqual(self.multidict["key"], {"val2"})
        self.multidict.remove("key", "val2")
        self.assertTrue("key" not in self.multidict)

    def test_get_set(self):
        self.test_add()
        self.multidict.remove("key", "val1")
        self.assertEqual(self.multidict.get_set("key"), {"val2"})
        self.multidict.remove("key", "val2")
        self.assertEqual(self.multidict.get_set("key"), set())

    def test_in_set(self):
        self.test_add()
        self.assertTrue(self.multidict.in_set("key", "val1"))
        self.assertTrue(self.multidict.in_set("key", "val2"))
        self.multidict.remove("key", "val1")
        self.multidict.remove("key", "val2")
        self.assertFalse(self.multidict.in_set("key", "val1"))
        self.assertFalse(self.multidict.in_set("key", "val2"))
Example #12
0
 def test_add(self):
     self.multidict = MultiDict()
     self.multidict.add("key", "val1")
     self.assertEqual(self.multidict["key"], {"val1"})
     self.multidict.add("key", "val2")
     self.assertEqual(self.multidict["key"], {"val1", "val2"})
Example #13
0
    def __init__(self):
        self.dealers_by_name = {}
        """Dealers indexed by name."""

        self.dealers_by_model = MultiDict()
        """Dealers indexed by model."""
Example #14
0
class DealerManager(object):
    """Tracks dealers registered in the system."""

    __slots__ = ('dealers_by_name', 'dealers_by_model')

    def __init__(self):
        self.dealers_by_name = {}
        """Dealers indexed by name."""

        self.dealers_by_model = MultiDict()
        """Dealers indexed by model."""

    def register_dealer(self, dealer):
        """Registers a new dealer."""
        name = dealer.name
        model = dealer.model

        if self.dealers_by_name.get(name):
            raise RuntimeError(
                    "Dealer with name '%s' already is registered." % name)

        # Add to dealers_by_name
        self.dealers_by_name[name] = dealer

        # Add to dealers_by_model
        self.dealers_by_model.add(model, dealer)

    def unregister_dealer(self, dealer):
        """Remove a dealer from the system."""
        del self.dealers_by_name[dealer.name]
        self.dealers_by_model.remove(dealer.model, dealer)

    def get_dealer(self, name):
        """Returns a dealer with the specified name."""
        try:
            return self.dealers_by_name[name]
        except KeyError:
            raise UnknownDealer(name)

    def get_dealers_for_model_class(self, model):
        """Returns an iterable of dealers with the specified model.
        """
        try:
            return self.dealers_by_model[model]
        except KeyError:
            raise UnknownModelClass(model)

    def deliver_delta(self, delta):
        """Deliver a delta to its associated dealers.
        """
        for dealer in self.get_dealers_for_model_class(delta.model):
            dealer.deliver_delta(delta)

    def connect_subscription(self, subscription):
        """Bind each of the items of a subscription to their adequate dealers.
        """
        for item in subscription.items:
            dealer = self.get_dealer(item.dealer_name)
            dealer.add_subscription_item(item)

    def disconnect_subscription(self, subscription):
        """Disconnect each of the items of a subscription from their dealers.
        """
        for item in subscription.items:
            dealer = self.get_dealer(item.dealer_name)
            dealer.remove_subscription_item(item)
Example #15
0
 def __init__(self):
     self.subscriptions_by_token = {}
     self.subscriptions_by_client = MultiDict()
Example #16
0
class SubscriptionManager(object):
    """Tracks the active subscriptions in the system."""

    def __init__(self):
        self.subscriptions_by_token = {}
        self.subscriptions_by_client = MultiDict()

    @staticmethod
    def generate_token():
        """Return a 32 character long random string, generated by a safe CSRPNG
        of the operating system."""
        return "".join([
            safe_random.choice("abcdefghijklmnopqrstuvwxyz1234567890")
            for i in range(32)
        ])

    def register_subscription(self, subscription):
        """
        Registers a Subscription in the SubscriptionManager, assigning a secret
        unique token to it.

        It is needed for a Subscription to be authorized before a client can
        acquire it.

        Returns the new token.
        """

        if subscription.token:
            raise RuntimeError("This subscription already has a token.")

        while True:
            token = self.generate_token()

            # It's almost impossible that the same token is generated twice,
            # but it's better to be safe than sorry.
            if not token in self.subscriptions_by_token:
                break

        subscription.token = token
        self.subscriptions_by_token[token] = subscription

        return token

    def unregister_subscription(self, subscription):
        """
        Unregisters the Subscription object and removes its token.

        Note: Calling this method does not dettach the subscription items from
        the dealer. To fully delete a subscription from the system see
        `DataSyncService.do_cancel_subscription`.
        """

        token = subscription.token
        del self.subscriptions_by_token[token]

        subscription.token = None

    def get_subscription_with_token(self, token):
        """Returns a Subscription with the specified token."""
        return self.subscriptions_by_token.get(token)

    def link_subscription_to_client(self, subscription, client):
        """Associates a client and a subscription, allowing the user to receive
        notifications."""
        subscription.got_client(client)

        self.subscriptions_by_client.add(client, subscription)

    def unlink_subscription_from_client(self, subscription):
        """Destroys the association between a client and a subscription."""
        client = subscription.client
        subscription.lost_client()

        self.subscriptions_by_client.remove(client, subscription)
Example #17
0
class PubSubService(RPCService):
    """A service which allows clients to send messages to each other over Pub
    Sub channels."""
    def __init__(self, name):
        super(PubSubService, self).__init__(name)

        # channel : str -> set<Client>
        self.subscriptions = MultiDict()
        # Client -> set<channel : str>
        self.subscriptions_by_client = MultiDict()

    def do_publish(self, channel, message):
        """Common code for publishing a message."""
        for client in self.subscriptions.get_set(channel):
            self.send_message_to(client, {
                "type": "message",
                "channel": channel,
                "message": message
            })

    @rpc_command
    def publish(self, req, channel, message):
        """RPC command.

        Publish a message to a channel."""
        if self.can_publish(req.client, channel):
            self.do_publish(channel, message)
        else:
            raise RPCError("Not authorized")

    @rpc_command
    def subscribe(self, req, channel):
        """RPC command.

        Subscribe to a channel."""
        if self.subscriptions.in_set(channel, req.client):
            raise RPCError("Already subscribed")
        self.subscriptions.add(channel, req.client)
        self.subscriptions_by_client.add(req.client, channel)

    @rpc_command
    def unsubscribe(self, req, channel):
        """RPC command.

        Cancel the subscription to a channel."""
        try:
            self.subscriptions.remove(channel, req.client)
            self.subscriptions_by_client.remove(req.client, channel)
        except KeyError:
            raise RPCError("Not subscribed")

    def can_publish(self, client, channel):
        """Whether a client can publish to a certain channel.

        By default returns always ``True``."""
        return True

    def client_disconnected(self, client):
        """Exececuted when a client disconnects. Cancels all its subscriptions.
        """
        for channel in self.subscriptions_by_client.get_set(client):
            self.subscriptions.remove(channel, client)
        self.subscriptions_by_client.clear_set(client)
Example #18
0
class ChatService(RPCService):
    def __init__(self, name):
        super(ChatService, self).__init__(name)

        # (channel) : obj -> set<Client>
        self.clients_by_channel = MultiDict()
        # (Client) -> set<channel : obj>
        self.channels_by_client = MultiDict()
        # (identity : obj, channel : obj) -> set<Client>
        self.clients_by_identity_and_channel = MultiDict()
        # (channel : obj) -> set<Identity>
        self.identities_by_channel = MultiDict()

    def get_time(self):
        from dateutil.tz import tzlocal
        return datetime.now(tz=tzlocal()).isoformat()

    def check_identity(self, client):
        if client.identity is None:
            raise RPCError("Not authenticated")

    @rpc_command
    def join(self, req, channel):
        client = req.client
        identity = client.identity

        self.check_identity(client)

        if self.clients_by_channel.in_set(channel, client):
            raise RPCError("Already joined")

        if identity not in self.identities_by_channel.get_set(channel):
            # Send presence to other members
            self.send_presence(channel, client, "joined")
            self.identities_by_channel.add(channel, identity)

        self.clients_by_channel.add(channel, client)
        self.channels_by_client.add(client, channel)
        self.clients_by_identity_and_channel.add(
            (identity, channel), req.client)

        return {
            "members": list(self.identities_by_channel.get_set(channel))
        }

    @rpc_command
    def leave(self, req, channel):
        client = req.client

        if not self.clients_by_channel.in_set(channel, client):
            raise RPCError("Not joined")

        self.do_leave(client, channel)

    def do_leave(self, client, channel):
        identity = client.identity

        self.clients_by_channel.remove(channel, client)
        self.channels_by_client.remove(client, channel)
        self.clients_by_identity_and_channel.remove(
            (identity, channel), client)

        if len(self.clients_by_identity_and_channel.get_set(
            (identity, channel))) == 0:
            # User is not connected from elsewhere
            self.identities_by_channel.remove(channel, identity)
            # Send presence to remaining users
            self.send_presence(channel, client, "left")

    @rpc_command
    def send(self, req, channel, body):
        if not self.clients_by_channel.in_set(channel, req.client):
            raise RPCError("Not joined")

        message = {
            "type": "message",
            "from": req.client.identity,
            "channel": channel,
            "body": body,
            "timestamp": self.get_time(),
        }

        for client in self.clients_by_channel.get_set(channel):
            self.send_message_to(client, message)

    def send_presence(self, channel, client, status):
        presence = {
            "type": "presence",
            "from": client.identity,
            "channel": channel,
            "status": status,
            "timestamp": self.get_time(),
        }
        for client in self.clients_by_channel.get_set(channel):
            self.send_message_to(client, presence)

    @rpc_command
    def read(self, req, channel):
        notification = {
            "type": "read",
            "channel": channel,
            "timestamp": self.get_time(),
        }

        for client in self.clients_by_identity_and_channel.get_set(
                            (req.client.identity, channel)):
            if client is not req.client:
                self.send_message_to(client, notification)


    def client_disconnected(self, client):
        channels = list(self.channels_by_client.get_set(client))
        for channel in channels:
            self.do_leave(client, channel)