[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