def __init__(self, store, host, port, certPath, keyPath, chainPath="", passphrase="", sslMethod="TLSv1_METHOD", staggerNotifications=False, staggerSeconds=3, testConnector=None, reactor=None): APNConnectionService.__init__(self, host, port, certPath, keyPath, chainPath=chainPath, passphrase=passphrase, sslMethod=sslMethod, testConnector=testConnector, reactor=reactor) self.store = store self.factory = None self.queue = [] if staggerNotifications: self.scheduler = PushScheduler(self.reactor, self.sendNotification, staggerSeconds=staggerSeconds) else: self.scheduler = None
def __init__(self, controlSocket, parentService, port, enableStaggering, staggerSeconds, reactor=None): if reactor is None: from twisted.internet import reactor from twisted.application.strports import service as strPortsService if port: # Service which listens for client subscriptions and sends # notifications to them strPortsService(str(port), AMPPushNotifierFactory(self), reactor=reactor).setServiceParent(parentService) if controlSocket is not None: # Set up the listener which gets notifications from the slaves controlSocket.addFactory(PUSH_ROUTE, AMPPushMasterListenerFactory(self)) self.subscribers = [] if enableStaggering: self.scheduler = PushScheduler(reactor, self.sendNotification, staggerSeconds=staggerSeconds) else: self.scheduler = None
def __init__( self, controlSocket, parentService, port, enableStaggering, staggerSeconds, reactor=None ): if reactor is None: from twisted.internet import reactor from twisted.application.strports import service as strPortsService if port: # Service which listens for client subscriptions and sends # notifications to them strPortsService( str(port), AMPPushNotifierFactory(self), reactor=reactor).setServiceParent(parentService) if controlSocket is not None: # Set up the listener which gets notifications from the slaves controlSocket.addFactory( PUSH_ROUTE, AMPPushMasterListenerFactory(self) ) self.subscribers = [] if enableStaggering: self.scheduler = PushScheduler( reactor, self.sendNotification, staggerSeconds=staggerSeconds) else: self.scheduler = None
def __init__(self, settings, serverHostName, reactor=None): if reactor is None: from twisted.internet import reactor factory = AMPPushNotifierFactory(self) endpoint = TCP4ServerEndpoint(reactor, settings["Port"]) super(AMPPushNotifierService, self).__init__(endpoint, factory) self.subscribers = [] if settings["EnableStaggering"]: self.scheduler = PushScheduler(reactor, self.sendNotification, staggerSeconds=settings["StaggerSeconds"]) else: self.scheduler = None self.serverHostName = serverHostName
class APNProviderService(APNConnectionService): def __init__(self, store, host, port, certPath, keyPath, chainPath="", passphrase="", sslMethod="TLSv1_METHOD", staggerNotifications=False, staggerSeconds=3, testConnector=None, reactor=None): APNConnectionService.__init__(self, host, port, certPath, keyPath, chainPath=chainPath, passphrase=passphrase, sslMethod=sslMethod, testConnector=testConnector, reactor=reactor) self.store = store self.factory = None self.queue = [] if staggerNotifications: self.scheduler = PushScheduler(self.reactor, self.sendNotification, staggerSeconds=staggerSeconds) else: self.scheduler = None def startService(self): self.log.debug("APNProviderService startService") self.factory = APNProviderFactory(self, self.store) self.connect(self.factory) def stopService(self): self.log.debug("APNProviderService stopService") if self.factory is not None: self.factory.stopTrying() if self.scheduler is not None: self.scheduler.stop() def clientConnectionMade(self): # Service the queue if self.queue: # Copy and clear the queue. Any notifications that don't get # sent will be put back into the queue. queued = list(self.queue) self.queue = [] for (token, key), dataChangedTimestamp, priority in queued: if token and key and dataChangedTimestamp and priority: self.sendNotification(token, key, dataChangedTimestamp, priority) def scheduleNotifications(self, tokens, key, dataChangedTimestamp, priority): """ The starting point for getting notifications to the APNS server. If there is a connection to the APNS server, these notifications are scheduled (or directly sent if there is no scheduler). If there is no connection, the notifications are saved for later. @param tokens: The device tokens to schedule notifications for @type tokens: List of strings @param key: The key to use for this batch of notifications @type key: String @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification @type key: C{int} """ # Service has reference to factory has reference to protocol instance connection = getattr(self.factory, "connection", None) if connection is not None: if self.scheduler is not None: self.scheduler.schedule(tokens, key, dataChangedTimestamp, priority) else: for token in tokens: self.sendNotification(token, key, dataChangedTimestamp, priority) else: self._saveForWhenConnected(tokens, key, dataChangedTimestamp, priority) def _saveForWhenConnected(self, tokens, key, dataChangedTimestamp, priority): """ Called in order to save notifications that can't be sent now because there is no connection to the APNS server. (token, key) tuples are appended to the queue which is serviced during clientConnectionMade() @param tokens: The device tokens to schedule notifications for @type tokens: List of C{str} @param key: The key to use for this batch of notifications @type key: C{str} @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification @type key: C{int} """ for token in tokens: tokenKeyPair = (token, key) for existingPair, _ignore_timstamp, priority in self.queue: if tokenKeyPair == existingPair: self.log.debug( "APNProviderService has no connection; skipping duplicate: %s %s" % (token, key)) break # Already scheduled else: self.log.debug( "APNProviderService has no connection; queuing: %s %s" % (token, key)) self.queue.append( ((token, key), dataChangedTimestamp, priority)) def sendNotification(self, token, key, dataChangedTimestamp, priority): """ If there is a connection the notification is sent right away, otherwise the notification is saved for later. @param token: The device token to send a notifications to @type token: C{str} @param key: The key to use for this notification @type key: C{str} @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification @type key: C{int} """ if not (token and key and dataChangedTimestamp, priority): return # Service has reference to factory has reference to protocol instance connection = getattr(self.factory, "connection", None) if connection is None: self._saveForWhenConnected([token], key, dataChangedTimestamp, priority) else: connection.sendNotification(token, key, dataChangedTimestamp, priority)
class AMPPushMaster(object): """ AMPPushNotifierService allows clients to use AMP to subscribe to, and receive, change notifications. """ log = Logger() def __init__(self, controlSocket, parentService, port, enableStaggering, staggerSeconds, reactor=None): if reactor is None: from twisted.internet import reactor from twisted.application.strports import service as strPortsService if port: # Service which listens for client subscriptions and sends # notifications to them strPortsService(str(port), AMPPushNotifierFactory(self), reactor=reactor).setServiceParent(parentService) if controlSocket is not None: # Set up the listener which gets notifications from the slaves controlSocket.addFactory(PUSH_ROUTE, AMPPushMasterListenerFactory(self)) self.subscribers = [] if enableStaggering: self.scheduler = PushScheduler(reactor, self.sendNotification, staggerSeconds=staggerSeconds) else: self.scheduler = None def addSubscriber(self, p): self.log.debug("Added subscriber") self.subscribers.append(p) def removeSubscriber(self, p): self.log.debug("Removed subscriber") self.subscribers.remove(p) def enqueue(self, transaction, pushKey, dataChangedTimestamp=None, priority=PushPriority.high): """ Sends an AMP push notification to any clients subscribing to this pushKey. @param pushKey: The identifier of the resource that was updated, including a prefix indicating whether this is CalDAV or CardDAV related. "/CalDAV/abc/def/" @type pushKey: C{str} @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification (Only used for unit tests) @type key: C{int} """ # Unit tests can pass this value in; otherwise it defaults to now if dataChangedTimestamp is None: dataChangedTimestamp = int(time.time()) tokens = [] for subscriber in self.subscribers: token = subscriber.subscribedToID(pushKey) if token is not None: tokens.append(token) if tokens: return self.scheduleNotifications(tokens, pushKey, dataChangedTimestamp, priority) @inlineCallbacks def sendNotification(self, token, id, dataChangedTimestamp, priority): for subscriber in self.subscribers: if subscriber.subscribedToID(id): yield subscriber.notify(token, id, dataChangedTimestamp, priority) @inlineCallbacks def scheduleNotifications(self, tokens, id, dataChangedTimestamp, priority): if self.scheduler is not None: self.scheduler.schedule(tokens, id, dataChangedTimestamp, priority) else: for token in tokens: yield self.sendNotification(token, id, dataChangedTimestamp, priority)
class AMPPushMaster(object): """ AMPPushNotifierService allows clients to use AMP to subscribe to, and receive, change notifications. """ log = Logger() def __init__(self, controlSocket, parentService, port, enableStaggering, staggerSeconds, reactor=None): if reactor is None: from twisted.internet import reactor from twisted.application.strports import service as strPortsService if port: # Service which listens for client subscriptions and sends # notifications to them strPortsService(str(port), AMPPushNotifierFactory(self), reactor=reactor).setServiceParent(parentService) if controlSocket is not None: # Set up the listener which gets notifications from the slaves controlSocket.addFactory(PUSH_ROUTE, AMPPushMasterListenerFactory(self)) self.subscribers = [] if enableStaggering: self.scheduler = PushScheduler(reactor, self.sendNotification, staggerSeconds=staggerSeconds) else: self.scheduler = None def addSubscriber(self, p): self.log.debug("Added subscriber") self.subscribers.append(p) def removeSubscriber(self, p): self.log.debug("Removed subscriber") self.subscribers.remove(p) def enqueue(self, transaction, pushKey, dataChangedTimestamp=None): """ Sends an AMP push notification to any clients subscribing to this pushKey. @param pushKey: The identifier of the resource that was updated, including a prefix indicating whether this is CalDAV or CardDAV related. "/CalDAV/abc/def/" @type pushKey: C{str} @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification (Only used for unit tests) @type key: C{int} """ # Unit tests can pass this value in; otherwise it defaults to now if dataChangedTimestamp is None: dataChangedTimestamp = int(time.time()) tokens = [] for subscriber in self.subscribers: token = subscriber.subscribedToID(pushKey) if token is not None: tokens.append(token) if tokens: return self.scheduleNotifications(tokens, pushKey, dataChangedTimestamp) @inlineCallbacks def sendNotification(self, token, id, dataChangedTimestamp): for subscriber in self.subscribers: if subscriber.subscribedToID(id): yield subscriber.notify(token, id, dataChangedTimestamp) @inlineCallbacks def scheduleNotifications(self, tokens, id, dataChangedTimestamp): if self.scheduler is not None: self.scheduler.schedule(tokens, id, dataChangedTimestamp) else: for token in tokens: yield self.sendNotification(token, id, dataChangedTimestamp)
class APNProviderService(APNConnectionService): def __init__(self, store, host, port, certPath, keyPath, chainPath="", passphrase="", sslMethod="TLSv1_METHOD", staggerNotifications=False, staggerSeconds=3, testConnector=None, reactor=None): APNConnectionService.__init__(self, host, port, certPath, keyPath, chainPath=chainPath, passphrase=passphrase, sslMethod=sslMethod, testConnector=testConnector, reactor=reactor) self.store = store self.factory = None self.queue = [] if staggerNotifications: self.scheduler = PushScheduler(self.reactor, self.sendNotification, staggerSeconds=staggerSeconds) else: self.scheduler = None def startService(self): self.log_debug("APNProviderService startService") self.factory = APNProviderFactory(self, self.store) self.connect(self.factory) def stopService(self): self.log_debug("APNProviderService stopService") if self.factory is not None: self.factory.stopTrying() if self.scheduler is not None: self.scheduler.stop() def clientConnectionMade(self): # Service the queue if self.queue: # Copy and clear the queue. Any notifications that don't get # sent will be put back into the queue. queued = list(self.queue) self.queue = [] for (token, key), dataChangedTimestamp in queued: if token and key and dataChangedTimestamp: self.sendNotification(token, key, dataChangedTimestamp) def scheduleNotifications(self, tokens, key, dataChangedTimestamp): """ The starting point for getting notifications to the APNS server. If there is a connection to the APNS server, these notifications are scheduled (or directly sent if there is no scheduler). If there is no connection, the notifications are saved for later. @param tokens: The device tokens to schedule notifications for @type tokens: List of strings @param key: The key to use for this batch of notifications @type key: String @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification @type key: C{int} """ # Service has reference to factory has reference to protocol instance connection = getattr(self.factory, "connection", None) if connection is not None: if self.scheduler is not None: self.scheduler.schedule(tokens, key, dataChangedTimestamp) else: for token in tokens: self.sendNotification(token, key, dataChangedTimestamp) else: self._saveForWhenConnected(tokens, key, dataChangedTimestamp) def _saveForWhenConnected(self, tokens, key, dataChangedTimestamp): """ Called in order to save notifications that can't be sent now because there is no connection to the APNS server. (token, key) tuples are appended to the queue which is serviced during clientConnectionMade() @param tokens: The device tokens to schedule notifications for @type tokens: List of C{str} @param key: The key to use for this batch of notifications @type key: C{str} @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification @type key: C{int} """ for token in tokens: tokenKeyPair = (token, key) for existingPair, ignored in self.queue: if tokenKeyPair == existingPair: self.log_debug("APNProviderService has no connection; skipping duplicate: %s %s" % (token, key)) break # Already scheduled else: self.log_debug("APNProviderService has no connection; queuing: %s %s" % (token, key)) self.queue.append(((token, key), dataChangedTimestamp)) def sendNotification(self, token, key, dataChangedTimestamp): """ If there is a connection the notification is sent right away, otherwise the notification is saved for later. @param token: The device token to send a notifications to @type token: C{str} @param key: The key to use for this notification @type key: C{str} @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification @type key: C{int} """ if not (token and key and dataChangedTimestamp): return # Service has reference to factory has reference to protocol instance connection = getattr(self.factory, "connection", None) if connection is None: self._saveForWhenConnected([token], key, dataChangedTimestamp) else: connection.sendNotification(token, key, dataChangedTimestamp)
class AMPPushNotifierService(StreamServerEndpointService, LoggingMixIn): """ AMPPushNotifierService allows clients to use AMP to subscribe to, and receive, change notifications. """ @classmethod def makeService(cls, settings, ignored, serverHostName, reactor=None): return cls(settings, serverHostName, reactor=reactor) def __init__(self, settings, serverHostName, reactor=None): if reactor is None: from twisted.internet import reactor factory = AMPPushNotifierFactory(self) endpoint = TCP4ServerEndpoint(reactor, settings["Port"]) super(AMPPushNotifierService, self).__init__(endpoint, factory) self.subscribers = [] if settings["EnableStaggering"]: self.scheduler = PushScheduler(reactor, self.sendNotification, staggerSeconds=settings["StaggerSeconds"]) else: self.scheduler = None self.serverHostName = serverHostName def addSubscriber(self, p): self.log_debug("Added subscriber") self.subscribers.append(p) def removeSubscriber(self, p): self.log_debug("Removed subscriber") self.subscribers.remove(p) def enqueue(self, op, id, dataChangedTimestamp=None): """ Sends an AMP push notification to any clients subscribing to this id. @param op: The operation that took place, either "create" or "update" (ignored in this implementation) @type op: C{str} @param id: The identifier of the resource that was updated, including a prefix indicating whether this is CalDAV or CardDAV related. The prefix is separated from the id with "|", e.g.: "CalDAV|abc/def" The id is an opaque token as far as this code is concerned, and is used in conjunction with the prefix and the server hostname to build the actual key value that devices subscribe to. @type id: C{str} @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change which triggered this notification (Only used for unit tests) @type key: C{int} """ try: id.split("|", 1) except ValueError: # id has no protocol, so we can't do anything with it self.log_error("Notification id '%s' is missing protocol" % (id,)) return id = getPubSubPath(id, {"host": self.serverHostName}) # Unit tests can pass this value in; otherwise it defaults to now if dataChangedTimestamp is None: dataChangedTimestamp = int(time.time()) tokens = [] for subscriber in self.subscribers: token = subscriber.subscribedToID(id) if token is not None: tokens.append(token) if tokens: return self.scheduleNotifications(tokens, id, dataChangedTimestamp) @inlineCallbacks def sendNotification(self, token, id, dataChangedTimestamp): for subscriber in self.subscribers: if subscriber.subscribedToID(id): yield subscriber.notify(token, id, dataChangedTimestamp) @inlineCallbacks def scheduleNotifications(self, tokens, id, dataChangedTimestamp): if self.scheduler is not None: self.scheduler.schedule(tokens, id, dataChangedTimestamp) else: for token in tokens: yield self.sendNotification(token, id, dataChangedTimestamp)