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 __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 __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()
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)
def __init__(self): self.subscriptions_by_token = {} self.subscriptions_by_client = MultiDict()
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)
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)
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)
def __init__(self): super(SimpleDealer, self).__init__() self.items_by_model_key = MultiDict()
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"))
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 __init__(self): self.dealers_by_name = {} """Dealers indexed by name.""" self.dealers_by_model = MultiDict() """Dealers indexed by model."""
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)
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)
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)