[CalendarServer-changes] [8184] CalendarServer/branches/users/sagen/applepush

source_changes at macosforge.org source_changes at macosforge.org
Tue Oct 11 13:01:36 PDT 2011


Revision: 8184
          http://trac.macosforge.org/projects/calendarserver/changeset/8184
Author:   sagen at apple.com
Date:     2011-10-11 13:01:35 -0700 (Tue, 11 Oct 2011)
Log Message:
-----------
Cleanup and docs

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/applepush/twistedcaldav/applepush.py
    CalendarServer/branches/users/sagen/applepush/twistedcaldav/notify.py
    CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_applepush.py
    CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_config.py
    CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_notify.py
    CalendarServer/branches/users/sagen/applepush/txdav/common/datastore/sql.py
    CalendarServer/branches/users/sagen/applepush/txdav/common/icommondatastore.py

Modified: CalendarServer/branches/users/sagen/applepush/twistedcaldav/applepush.py
===================================================================
--- CalendarServer/branches/users/sagen/applepush/twistedcaldav/applepush.py	2011-10-11 16:02:14 UTC (rev 8183)
+++ CalendarServer/branches/users/sagen/applepush/twistedcaldav/applepush.py	2011-10-11 20:01:35 UTC (rev 8184)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -34,27 +34,48 @@
 import struct
 import time
 
-"""
-ApplePushNotifierService is a MultiService responsible for setting up the
-APN provider and feedback connections.  Once connected, calling its enqueue( )
-method sends notifications to any device token which is subscribed to the
-enqueued key.
 
-The Apple Push Notification protocol is described here:
 
-http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html
-"""
-
-
 log = Logger()
 
 
 class ApplePushNotifierService(service.MultiService, LoggingMixIn):
+    """
+    ApplePushNotifierService is a MultiService responsible for
+    setting up the APN provider and feedback connections.  Once
+    connected, calling its enqueue( ) method sends notifications
+    to any device token which is subscribed to the enqueued key.
 
+    The Apple Push Notification protocol is described here:
+
+    http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html
+    """
+
     @classmethod
     def makeService(cls, settings, store, testConnectorClass=None,
         reactor=None):
+        """
+        Creates the various "subservices" that work together to implement
+        APN, including "provider" and "feedback" services for CalDAV and
+        CardDAV.
 
+        @param settings: The portion of the configuration specific to APN
+        @type settings: C{dict}
+
+        @param store: The db store for storing/retrieving subscriptions
+        @type store: L{IDataStore}
+
+        @param testConnectorClass: Used for unit testing; implements
+            connect( ) and receiveData( )
+        @type testConnectorClass: C{class}
+
+        @param reactor: Used for unit testing; allows tests to advance the
+            clock in order to test the feedback polling service.
+        @type reactor: L{twisted.internet.task.Clock}
+
+        @return: instance of L{ApplePushNotifierService}
+        """
+
         service = cls()
 
         service.store = store
@@ -101,7 +122,26 @@
 
     @inlineCallbacks
     def enqueue(self, op, id):
+        """
+        Sends an Apple Push Notification to any device token subscribed 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}
+        """
+
         try:
             protocol, id = id.split("|", 1)
         except ValueError:
@@ -177,10 +217,32 @@
             self.processError(status, identifier)
 
     def processError(self, status, identifier):
+        """
+        Handles an error message we've received from on feedback channel.
+        Not much to do here besides logging the error.
+
+        @param status: The status value returned from APN Feedback server
+        @type status: C{int}
+
+        @param identifier: The identifier of the outbound push notification
+            message which had a problem.
+        @type status: C{int}
+        """
         msg = self.STATUS_CODES.get(status, "Unknown status code")
         self.log_error("Received APN error %d on identifier %d: %s" % (status, identifier, msg))
 
-    def sendNotification(self, token, node):
+    def sendNotification(self, token, key):
+        """
+        Sends a push notification message for the key to the device associated
+        with the token.
+
+        @param token: The device token subscribed to the key
+        @type token: C{str}
+
+        @param key: The key we're sending a notification about
+        @type key: C{str}
+        """
+
         try:
             binaryToken = token.replace(" ", "").decode("hex")
         except:
@@ -188,7 +250,7 @@
             return
 
         self.identifier += 1
-        payload = '{"key" : "%s"}' % (node,)
+        payload = '{"key" : "%s"}' % (key,)
         payloadLength = len(payload)
         self.log_debug("Sending APNS notification to %s: id=%d payload=%s" %
             (token, self.identifier, payload))
@@ -316,6 +378,19 @@
 
     @inlineCallbacks
     def processFeedback(self, timestamp, token):
+        """
+        Handles a feedback message indicating that the given token is no
+        longer active as of the timestamp, and its subscription should be
+        removed as long as that device has not re-subscribed since the
+        timestamp.
+
+        @param timestamp: Seconds since the epoch
+        @type timestamp: C{int}
+
+        @param token: The device token to unsubscribe
+        @type token: C{str}
+        """
+
         self.log_debug("FeedbackProtocol processFeedback time=%d token=%s" %
             (timestamp, token))
         txn = self.factory.store.newTransaction()
@@ -374,13 +449,17 @@
             self.checkForFeedback)
 
 
-class APNSubscriptionResource(ReadOnlyNoCopyResourceMixIn, DAVResourceWithoutChildrenMixin, DAVResource, LoggingMixIn):
+class APNSubscriptionResource(ReadOnlyNoCopyResourceMixIn,
+    DAVResourceWithoutChildrenMixin, DAVResource, LoggingMixIn):
+    """
+    The DAV resource allowing clients to subscribe to Apple push notifications.
+    To subscribe, a client should first determine the key they are interested
+    in my examining the "pushkey" DAV property on the home or collection they
+    want to monitor.  Next the client sends an authenticated HTTP GET or POST
+    request to this resource, passing their device token and the key in either
+    the URL params or in the POST body.
+    """
 
-    # method can be GET or POST
-    # params are "token" (device token) and "key" (push key), e.g.:
-    # token=2d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df
-    # key=/CalDAV/calendar.example.com/E0B38B00-4166-11DD-B22C-A07C87F02F6A/
-
     def __init__(self, parent, store):
         DAVResource.__init__(
             self, principalCollections=parent.principalCollections()
@@ -444,6 +523,10 @@
     http_GET = http_POST
 
     def principalFromRequest(self, request):
+        """
+        Given an authenticated request, return the principal based on
+        request.authnUser
+        """
         principal = None
         for collection in self.principalCollections():
             data = request.authnUser.children[0].children[0].data
@@ -453,6 +536,14 @@
 
     @inlineCallbacks
     def processSubscription(self, request):
+        """
+        Given an authenticated request, use the token and key arguments
+        to add a subscription entry to the database.
+
+        @param request: The request to process
+        @type request: L{twext.web2.server.Request}
+        """
+
         token = request.args.get("token", None)
         key = request.args.get("key", None)
         if key and token:
@@ -471,11 +562,21 @@
 
     @inlineCallbacks
     def addSubscription(self, token, key, guid):
+        """
+        Add a subscription (or update its timestamp if already there).
+
+        @param token: The device token
+        @type token: C{str}
+
+        @param key: The push key
+        @type key: C{str}
+
+        @param guid: The GUID of the subscriber principal
+        @type guid: C{str}
+        """
         now = int(time.time()) # epoch seconds
         txn = self.store.newTransaction()
         yield txn.addAPNSubscription(token, key, now, guid)
-        # subscriptions = (yield txn.apnSubscriptionsByToken(token))
-        # print subscriptions
         yield txn.commit()
 
     def renderResponse(self, code, body=None):

Modified: CalendarServer/branches/users/sagen/applepush/twistedcaldav/notify.py
===================================================================
--- CalendarServer/branches/users/sagen/applepush/twistedcaldav/notify.py	2011-10-11 16:02:14 UTC (rev 8183)
+++ CalendarServer/branches/users/sagen/applepush/twistedcaldav/notify.py	2011-10-11 20:01:35 UTC (rev 8184)
@@ -1458,6 +1458,8 @@
             config.Memcached.MaxClients,
         )
 
+        # TODO: This code is copied from makeService_Slave, and needs to be
+        # refactored so it can be shared instead.
         from calendarserver.tap.util import (
             storeFromConfig, pgConnectorFromConfig, oracleConnectorFromConfig,
             pgServiceFromConfig

Modified: CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_applepush.py
===================================================================
--- CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_applepush.py	2011-10-11 16:02:14 UTC (rev 8183)
+++ CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_applepush.py	2011-10-11 20:01:35 UTC (rev 8184)
@@ -18,7 +18,7 @@
     ApplePushNotifierService, APNProviderProtocol
 )
 from twistedcaldav.test.util import TestCase
-from twisted.internet.defer import inlineCallbacks, succeed
+from twisted.internet.defer import inlineCallbacks
 from twisted.internet.task import Clock
 import struct
 from txdav.common.datastore.test.util import buildStore, CommonCommonTests

Modified: CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_config.py	2011-10-11 16:02:14 UTC (rev 8183)
+++ CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_config.py	2011-10-11 20:01:35 UTC (rev 8184)
@@ -95,7 +95,7 @@
 
     def testDefaults(self):
         for key, value in DEFAULT_CONFIG.iteritems():
-            if key in ("ServerHostName",):
+            if key in ("ServerHostName", "Notifications"):
                 # Value is calculated and may vary
                 continue
             for item in RELATIVE_PATHS:

Modified: CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_notify.py
===================================================================
--- CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_notify.py	2011-10-11 16:02:14 UTC (rev 8183)
+++ CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_notify.py	2011-10-11 20:01:35 UTC (rev 8184)
@@ -573,7 +573,7 @@
         # Overall notifications are disabled
         disabledConfig.Notifications["Enabled"] = False
         conf = getPubSubConfiguration(disabledConfig)
-        self.assertEquals(conf, { "enabled" : False })
+        self.assertEquals(conf, { "enabled" : False, "host" : "" })
         conf = getXMPPSettings(disabledConfig)
         self.assertEquals(conf, None)
 

Modified: CalendarServer/branches/users/sagen/applepush/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/sagen/applepush/txdav/common/datastore/sql.py	2011-10-11 16:02:14 UTC (rev 8183)
+++ CalendarServer/branches/users/sagen/applepush/txdav/common/datastore/sql.py	2011-10-11 20:01:35 UTC (rev 8184)
@@ -331,6 +331,7 @@
                        apn.MODIFIED: Parameter("modified"),
                        apn.SUBSCRIBER_GUID: Parameter("subscriber")})
 
+
     @classproperty
     def _updateAPNSubscriptionQuery(cls): #@NoSelf
         apn = schema.APN_SUBSCRIPTIONS
@@ -338,6 +339,7 @@
                       Where=(apn.TOKEN == Parameter("token")).And(
                              apn.RESOURCE_KEY == Parameter("resourceKey")))
 
+
     @classproperty
     def _selectAPNSubscriptionQuery(cls): #@NoSelf
         apn = schema.APN_SUBSCRIPTIONS
@@ -348,20 +350,9 @@
                 )
             )
 
+
     @inlineCallbacks
     def addAPNSubscription(self, token, key, timestamp, subscriber):
-        """
-        Add an Apple Push Notification subscription
-        """
-        # Select
-        # if not there, insert
-        #   if insert fails, pass (since someone just added it)
-        #   if it succeeds, return
-        # if there, update
-        #   if update fails, insert
-        #   if update succeeds, return
-
-
         row = yield self._selectAPNSubscriptionQuery.on(self,
             token=token, resourceKey=key)
         if not row: # Subscription does not yet exist
@@ -389,37 +380,34 @@
                       Where=(apn.TOKEN == Parameter("token")).And(
                           apn.RESOURCE_KEY == Parameter("resourceKey")))
 
+
     def removeAPNSubscription(self, token, key):
-        """
-        Remove an Apple Push Notification subscription
-        """
         return self._removeAPNSubscriptionQuery.on(self,
             token=token, resourceKey=key)
 
+
     @classproperty
     def _apnSubscriptionsByTokenQuery(cls): #@NoSelf
-        """
-        Look up Apple Push Notification subscriptions by device token
-        """
         apn = schema.APN_SUBSCRIPTIONS
         return Select([apn.RESOURCE_KEY, apn.MODIFIED, apn.SUBSCRIBER_GUID],
                       From=apn, Where=apn.TOKEN == Parameter("token"))
 
+
     def apnSubscriptionsByToken(self, token):
         return self._apnSubscriptionsByTokenQuery.on(self, token=token)
 
+
     @classproperty
     def _apnSubscriptionsByKeyQuery(cls): #@NoSelf
-        """
-        Look up Apple Push Notification subscriptions by key
-        """
         apn = schema.APN_SUBSCRIPTIONS
         return Select([apn.TOKEN, apn.SUBSCRIBER_GUID],
                       From=apn, Where=apn.RESOURCE_KEY == Parameter("resourceKey"))
 
+
     def apnSubscriptionsByKey(self, key):
         return self._apnSubscriptionsByKeyQuery.on(self, resourceKey=key)
 
+
     def postCommit(self, operation):
         """
         Run things after C{commit}.

Modified: CalendarServer/branches/users/sagen/applepush/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/branches/users/sagen/applepush/txdav/common/icommondatastore.py	2011-10-11 16:02:14 UTC (rev 8183)
+++ CalendarServer/branches/users/sagen/applepush/txdav/common/icommondatastore.py	2011-10-11 20:01:35 UTC (rev 8184)
@@ -149,18 +149,50 @@
 
     def addAPNSubscription(token, key, timestamp, subscriber):
         """
+        Add (or update) a subscription entry in the database.
+
+        @param token: The device token of the subscriber
+        @type token: C{str}
+
+        @param key: The push key to subscribe to
+        @type key: C{str}
+
+        @param timestamp: The number of seconds since the epoch
+        @type timestamp: C{int}
+
+        @param subscriber: The GUID of the subscribing principal
+        @type subscrbier: C{str}
         """
 
     def removeAPNSubscription(token, key):
         """
+        Remove a subscription entry from the database.
+
+        @param token: The device token of the subscriber
+        @type token: C{str}
+
+        @param key: The push key
+        @type key: C{str}
         """
 
     def apnSubscriptionsByToken(token):
         """
+        Retrieve all subscription entries for the token.
+
+        @param token: The device token of the subscriber
+        @type token: C{str}
+
+        @return: tuples of (key, timestamp, guid)
         """
 
     def apnSubscriptionsByKey(key):
         """
+        Retrieve all subscription entries for the key.
+
+        @param key: The push key
+        @type key: C{str}
+
+        @return: tuples of (token, guid)
         """
 
 class IShareableCollection(Interface):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20111011/0c859120/attachment-0001.html>


More information about the calendarserver-changes mailing list