def __init__(self, client_id, topic, publish): """ Create a subscription for the specified client on the specified topic, with callback publish Keyword arguments: client_id -- the ID of the client making this subscription topic -- the name of the topic to subscribe to publish -- the callback function for incoming messages """ self.client_id = client_id self.topic = topic self.publish = publish self.clients = {} self.handler = MessageHandler(None, self._publish) self.handler_lock = Lock() self.update_params()
class Subscription(): """ Keeps track of the clients multiple calls to subscribe. Chooses the most appropriate settings to send messages """ def __init__(self, client_id, topic, publish): """ Create a subscription for the specified client on the specified topic, with callback publish Keyword arguments: client_id -- the ID of the client making this subscription topic -- the name of the topic to subscribe to publish -- the callback function for incoming messages """ self.client_id = client_id self.topic = topic self.publish = publish self.clients = {} self.handler = MessageHandler(None, self._publish) self.handler_lock = Lock() self.update_params() def unregister(self): """ Unsubscribes this subscription and cleans up resources """ manager.unsubscribe(self.client_id, self.topic) with self.handler_lock: self.handler.finish() self.clients.clear() def subscribe(self, sid=None, msg_type=None, throttle_rate=0, queue_length=0, fragment_size=None, compression="none"): """ Add another client's subscription request If there are multiple calls to subscribe, the values actually used for queue_length, fragment_size, compression and throttle_rate are chosen to encompass all subscriptions' requirements Keyword arguments: sid -- the subscription id from the client msg_type -- the type of the message to subscribe to throttle_rate -- the minimum time (in ms) allowed between messages being sent. If multiple subscriptions, the lower of these is used queue_length -- the number of messages that can be buffered. If multiple subscriptions, the lower of these is used fragment_size -- None if no fragmentation, or the maximum length of allowed outgoing messages compression -- "none" if no compression, or some other value if compression is to be used (current valid values are 'png') """ client_details = { "throttle_rate": throttle_rate, "queue_length": queue_length, "fragment_size": fragment_size, "compression": compression } self.clients[sid] = client_details self.update_params() # Subscribe with the manager. This will propagate any exceptions manager.subscribe(self.client_id, self.topic, self.on_msg, msg_type) def unsubscribe(self, sid=None): """ Unsubscribe this particular client's subscription Keyword arguments: sid -- the individual subscription id. If None, all are unsubscribed """ if sid is None: self.clients.clear() elif sid in self.clients: del self.clients[sid] if not self.is_empty(): self.update_params() def is_empty(self): """ Return true if there are no subscriptions currently """ return len(self.clients) == 0 def _publish(self, message): """ Internal method to propagate published messages to the registered publish callback """ self.publish(message, self.fragment_size, self.compression) def on_msg(self, msg): """ Raw callback called by subscription manager for all incoming messages. Incoming messages are passed to the message handler which may drop, buffer, or propagate the message """ with self.handler_lock: self.handler.handle_message(msg) def update_params(self): """ Determine the 'lowest common denominator' params to satisfy all subscribed clients. """ if len(self.clients) == 0: self.throttle_rate = 0 self.queue_length = 0 self.fragment_size = None self.compression = "none" return def f(fieldname): return [x[fieldname] for x in self.clients.values()] self.throttle_rate = min(f("throttle_rate")) self.queue_length = min(f("queue_length")) frags = [x for x in f("fragment_size") if x != None] if frags == []: self.fragment_size = None else: self.fragment_size = min(frags) self.compression = "png" if "png" in f("compression") else "none" with self.handler_lock: self.handler = self.handler.set_throttle_rate(self.throttle_rate) self.handler = self.handler.set_queue_length(self.queue_length)
class Subscription(): """ Keeps track of the clients multiple calls to subscribe. Chooses the most appropriate settings to send messages """ def __init__(self, client_id, topic, publish): """ Create a subscription for the specified client on the specified topic, with callback publish Keyword arguments: client_id -- the ID of the client making this subscription topic -- the name of the topic to subscribe to publish -- the callback function for incoming messages """ self.client_id = client_id self.topic = topic self.publish = publish self.clients = {} self.handler = MessageHandler(None, self._publish) self.handler_lock = Lock() self.update_params() def unregister(self): """ Unsubscribes this subscription and cleans up resources """ manager.unsubscribe(self.client_id, self.topic) with self.handler_lock: self.handler.finish() self.clients.clear() def subscribe(self, sid=None, msg_type=None, throttle_rate=0, queue_length=0, fragment_size=None, compression="none"): """ Add another client's subscription request If there are multiple calls to subscribe, the values actually used for queue_length, fragment_size, compression and throttle_rate are chosen to encompass all subscriptions' requirements Keyword arguments: sid -- the subscription id from the client msg_type -- the type of the message to subscribe to throttle_rate -- the minimum time (in ms) allowed between messages being sent. If multiple subscriptions, the lower of these is used queue_length -- the number of messages that can be buffered. If multiple subscriptions, the lower of these is used fragment_size -- None if no fragmentation, or the maximum length of allowed outgoing messages compression -- "none" if no compression, or some other value if compression is to be used (current valid values are 'png') """ # Subscribe with the manager. This will propagate any exceptions manager.subscribe(self.client_id, self.topic, self.on_msg, msg_type) client_details = { "throttle_rate": throttle_rate, "queue_length": queue_length, "fragment_size": fragment_size, "compression": compression } self.clients[sid] = client_details self.update_params() def unsubscribe(self, sid=None): """ Unsubscribe this particular client's subscription Keyword arguments: sid -- the individual subscription id. If None, all are unsubscribed """ if sid is None: self.clients.clear() elif sid in self.clients: del self.clients[sid] if not self.is_empty(): self.update_params() def is_empty(self): """ Return true if there are no subscriptions currently """ return len(self.clients) == 0 def _publish(self, message): """ Internal method to propagate published messages to the registered publish callback """ self.publish(message, self.fragment_size, self.compression) def on_msg(self, msg): """ Raw callback called by subscription manager for all incoming messages. Incoming messages are passed to the message handler which may drop, buffer, or propagate the message """ with self.handler_lock: self.handler.handle_message(msg) def update_params(self): """ Determine the 'lowest common denominator' params to satisfy all subscribed clients. """ if len(self.clients) == 0: self.throttle_rate = 0 self.queue_length = 0 self.fragment_size = None self.compression = "none" return def f(fieldname): return [x[fieldname] for x in self.clients.values()] self.throttle_rate = min(f("throttle_rate")) self.queue_length = min(f("queue_length")) frags = [x for x in f("fragment_size") if x != None] if frags == []: self.fragment_size = None else: self.fragment_size = min(frags) self.compression = "png" if "png" in f("compression") else "none" with self.handler_lock: self.handler = self.handler.set_throttle_rate(self.throttle_rate) self.handler = self.handler.set_queue_length(self.queue_length)