[CalendarServer-changes] [9509] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Jul 31 11:17:59 PDT 2012


Revision: 9509
          http://trac.macosforge.org/projects/calendarserver/changeset/9509
Author:   sagen at apple.com
Date:     2012-07-31 11:17:58 -0700 (Tue, 31 Jul 2012)
Log Message:
-----------
Advertise APNS refresh-interval to clients, and automatically purge old subscriptions.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/push/applepush.py
    CalendarServer/trunk/calendarserver/push/test/test_applepush.py
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/notify.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/icommondatastore.py

Modified: CalendarServer/trunk/calendarserver/push/applepush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/applepush.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/calendarserver/push/applepush.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -28,6 +28,7 @@
 from twisted.internet import protocol
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.internet.protocol import ClientFactory, ReconnectingClientFactory
+from twisted.internet.task import LoopingCall
 from twistedcaldav.extensions import DAVResource, DAVResourceWithoutChildrenMixin
 from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
 import OpenSSL
@@ -87,6 +88,9 @@
         service.providers = {}
         service.feedbacks = {}
         service.dataHost = settings["DataHost"]
+        service.purgeCall = None
+        service.purgeIntervalSeconds = settings["SubscriptionPurgeIntervalSeconds"]
+        service.purgeSeconds = settings["SubscriptionPurgeSeconds"]
 
         for protocol in ("CalDAV", "CardDAV"):
 
@@ -134,7 +138,44 @@
         return service
 
 
+    def startService(self):
+        """
+        In addition to starting the provider and feedback sub-services, start a
+        LoopingCall whose job it is to purge old subscriptions
+        """
+        service.MultiService.startService(self)
+        self.log_debug("ApplePushNotifierService startService")
+        self.purgeCall = LoopingCall(self.purgeOldSubscriptions, self.purgeSeconds)
+        self.purgeCall.start(self.purgeIntervalSeconds, now=False)
+
+
+    def stopService(self):
+        """
+        In addition to stopping the provider and feedback sub-services, stop the
+        LoopingCall
+        """
+        service.MultiService.stopService(self)
+        self.log_debug("ApplePushNotifierService stopService")
+        if self.purgeCall is not None:
+            self.purgeCall.stop()
+            self.purgeCall = None
+
+
     @inlineCallbacks
+    def purgeOldSubscriptions(self, purgeSeconds):
+        """
+        Remove any subscriptions that registered more than purgeSeconds ago
+
+        @param purgeSeconds: The cutoff given in seconds
+        @type purgeSeconds: C{int}
+        """
+        self.log_debug("ApplePushNotifierService purgeOldSubscriptions")
+        txn = self.store.newTransaction()
+        yield txn.purgeOldAPNSubscriptions(int(time.time()) - purgeSeconds)
+        yield txn.commit()
+
+
+    @inlineCallbacks
     def enqueue(self, op, id):
         """
         Sends an Apple Push Notification to any device token subscribed to
@@ -340,6 +381,7 @@
         self.store = store
         self.noisy = True
         self.maxDelay = 30 # max seconds between connection attempts
+        self.shuttingDown = False
 
     def clientConnectionMade(self):
         self.log_warn("Connection to APN server made")
@@ -347,7 +389,8 @@
         self.delay = 1.0
 
     def clientConnectionLost(self, connector, reason):
-        self.log_warn("Connection to APN server lost: %s" % (reason,))
+        if not self.shuttingDown:
+            self.log_warn("Connection to APN server lost: %s" % (reason,))
         ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
 
     def clientConnectionFailed(self, connector, reason):
@@ -360,8 +403,12 @@
         self.log_warn("Reconnecting to APN server")
         ReconnectingClientFactory.retry(self, connector)
 
+    def stopTrying(self):
+        self.shuttingDown = True
+        ReconnectingClientFactory.stopTrying(self)
 
 
+
 class APNConnectionService(service.Service, LoggingMixIn):
 
     def __init__(self, host, port, certPath, keyPath, chainPath="",
@@ -578,6 +625,7 @@
         yield txn.commit()
 
 
+
 class APNFeedbackFactory(ClientFactory, LoggingMixIn):
 
     protocol = APNFeedbackProtocol
@@ -594,8 +642,8 @@
 
 class APNFeedbackService(APNConnectionService):
 
-    def __init__(self, store, updateSeconds, host, port, certPath, keyPath,
-        chainPath="", passphrase="", sslMethod="TLSv1_METHOD",
+    def __init__(self, store, updateSeconds, host, port,
+        certPath, keyPath, chainPath="", passphrase="", sslMethod="TLSv1_METHOD",
         testConnector=None, reactor=None):
 
         APNConnectionService.__init__(self, host, port, certPath, keyPath,
@@ -623,6 +671,7 @@
             self.checkForFeedback)
 
 
+
 class APNSubscriptionResource(ReadOnlyNoCopyResourceMixIn,
     DAVResourceWithoutChildrenMixin, DAVResource, LoggingMixIn):
     """

Modified: CalendarServer/trunk/calendarserver/push/test/test_applepush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/test/test_applepush.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/calendarserver/push/test/test_applepush.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -14,6 +14,8 @@
 # limitations under the License.
 ##
 
+import struct
+import time
 from calendarserver.push.applepush import (
     ApplePushNotifierService, APNProviderProtocol
 )
@@ -21,7 +23,6 @@
 from twistedcaldav.test.util import TestCase
 from twisted.internet.defer import inlineCallbacks, succeed
 from twisted.internet.task import Clock
-import struct
 from txdav.common.datastore.test.util import buildStore, CommonCommonTests
 from txdav.common.icommondatastore import InvalidSubscriptionValues
 
@@ -39,6 +40,8 @@
             "Service" : "calendarserver.push.applepush.ApplePushNotifierService",
             "Enabled" : True,
             "SubscriptionURL" : "apn",
+            "SubscriptionPurgeSeconds" : 24 * 60 * 60,
+            "SubscriptionPurgeIntervalSeconds" : 24 * 60 * 60,
             "DataHost" : "calendars.example.com",
             "ProviderHost" : "gateway.push.apple.com",
             "ProviderPort" : 2195,
@@ -260,7 +263,31 @@
         yield txn.commit()
         self.assertEquals(subscriptions, [])
 
+        #
+        # Verify purgeOldAPNSubscriptions
+        #
 
+        # Create two subscriptions, one old and one new
+        txn = self.store.newTransaction()
+        now = int(time.time())
+        yield txn.addAPNSubscription(token2, key1, now - 2 * 24 * 60 * 60, uid) # old
+        yield txn.addAPNSubscription(token2, key2, now, uid) # recent
+        yield txn.commit()
+
+        # Purge old subscriptions
+        txn = self.store.newTransaction()
+        yield txn.purgeOldAPNSubscriptions(now - 60 * 60)
+        yield txn.commit()
+
+        # Check that only the recent subscription remains
+        txn = self.store.newTransaction()
+        subscriptions = (yield txn.apnSubscriptionsByToken(token2))
+        yield txn.commit()
+        self.assertEquals(len(subscriptions), 1)
+        self.assertEquals(subscriptions[0][0], key2)
+
+        service.stopService()
+
     def test_validToken(self):
         self.assertTrue(validToken("2d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df"))
         self.assertFalse(validToken("d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df"))

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -415,6 +415,14 @@
 
 
 @registerElement
+class PubSubAPSRefreshIntervalProperty (WebDAVTextElement):
+    namespace = calendarserver_namespace
+    name = "refresh-interval"
+    protected = True
+    hidden = True
+
+
+ at registerElement
 class PubSubXMPPPushKeyProperty (WebDAVTextElement):
     namespace = calendarserver_namespace
     name = "pushkey"

Modified: CalendarServer/trunk/twistedcaldav/notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notify.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/twistedcaldav/notify.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -1310,6 +1310,7 @@
             url = "http://%s:%s/%s" % (config.ServerHostName, config.HTTPPort,
                 applePushSettings.SubscriptionURL)
         settings["SubscriptionURL"] = url
+        settings["SubscriptionRefreshIntervalSeconds"] = applePushSettings.SubscriptionRefreshIntervalSeconds
         settings["APSEnvironment"] = applePushSettings.Environment
         return settings
 

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -2295,7 +2295,7 @@
 
         if qname == customxml.MaxCollections.qname() and config.MaxCollectionsPerHome:
             returnValue(customxml.MaxCollections.fromString(config.MaxCollectionsPerHome))
-            
+
         elif qname == (customxml.calendarserver_namespace, "push-transports"):
 
             if (config.Notifications.Services.XMPPNotifier.Enabled or
@@ -2322,6 +2322,9 @@
                                     customxml.PubSubAPSEnvironmentProperty(
                                         apsConfiguration["APSEnvironment"]
                                     ),
+                                    customxml.PubSubAPSRefreshIntervalProperty(
+                                        str(apsConfiguration["SubscriptionRefreshIntervalSeconds"])
+                                    ),
                                     type="APSD",
                                 )
                             )

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -698,6 +698,9 @@
                 "Service" : "calendarserver.push.applepush.ApplePushNotifierService",
                 "Enabled" : False,
                 "SubscriptionURL" : "apns",
+                "SubscriptionRefreshIntervalSeconds" : 2 * 24 * 60 * 60, # How often the client should re-register (2 days)
+                "SubscriptionPurgeIntervalSeconds" : 12 * 60 * 60, # How often a purge is done (12 hours)
+                "SubscriptionPurgeSeconds" : 14 * 24 * 60 * 60, # How old a subscription must be before it's purged (14 days)
                 "DataHost" : "",
                 "ProviderHost" : "gateway.push.apple.com",
                 "ProviderPort" : 2195,

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -257,6 +257,9 @@
     def removeAPNSubscription(self, token, key):
         return NotImplementedError
 
+    def purgeOldAPNSubscriptions(self, purgeSeconds):
+        return NotImplementedError
+
     def apnSubscriptionsByToken(self, token):
         return NotImplementedError
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -518,6 +518,18 @@
 
 
     @classproperty
+    def _purgeOldAPNSubscriptionQuery(cls): #@NoSelf
+        apn = schema.APN_SUBSCRIPTIONS
+        return Delete(From=apn,
+                      Where=(apn.MODIFIED < Parameter("olderThan")))
+
+
+    def purgeOldAPNSubscriptions(self, olderThan):
+        return self._purgeOldAPNSubscriptionQuery.on(self,
+            olderThan=olderThan)
+
+
+    @classproperty
     def _apnSubscriptionsByTokenQuery(cls): #@NoSelf
         apn = schema.APN_SUBSCRIPTIONS
         return Select([apn.RESOURCE_KEY, apn.MODIFIED, apn.SUBSCRIBER_GUID],

Modified: CalendarServer/trunk/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/trunk/txdav/common/icommondatastore.py	2012-07-30 20:50:34 UTC (rev 9508)
+++ CalendarServer/trunk/txdav/common/icommondatastore.py	2012-07-31 18:17:58 UTC (rev 9509)
@@ -195,6 +195,15 @@
         @type key: C{str}
         """
 
+    def purgeOldAPNSubscriptions(olderThan):
+        """
+        Remove all subscription entries whose modified timestamp
+        is older than the provided timestamp.
+
+        @param olderThan: The cutoff timestamp in seconds
+        @type token: C{int}
+        """
+
     def apnSubscriptionsByToken(token):
         """
         Retrieve all subscription entries for the token.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120731/2f3c042c/attachment-0001.html>


More information about the calendarserver-changes mailing list