Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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
Exemple #4
0
    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
Exemple #5
0
    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
Exemple #6
0
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)
Exemple #7
0
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)
Exemple #9
0
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)
Exemple #10
0
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)