[CalendarServer-changes] [8186] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Oct 11 14:32:18 PDT 2011


Revision: 8186
          http://trac.macosforge.org/projects/calendarserver/changeset/8186
Author:   sagen at apple.com
Date:     2011-10-11 14:32:18 -0700 (Tue, 11 Oct 2011)
Log Message:
-----------
Implements direct-to-APN notifications (without going through XMPP and APN bridge).

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/util.py
    CalendarServer/trunk/twistedcaldav/notify.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/test/test_config.py
    CalendarServer/trunk/twistedcaldav/test/test_notify.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
    CalendarServer/trunk/txdav/common/icommondatastore.py

Added Paths:
-----------
    CalendarServer/trunk/calendarserver/push/
    CalendarServer/trunk/calendarserver/push/__init__.py
    CalendarServer/trunk/calendarserver/push/applepush.py
    CalendarServer/trunk/calendarserver/push/test/
    CalendarServer/trunk/calendarserver/push/test/__init__.py
    CalendarServer/trunk/calendarserver/push/test/test_applepush.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_5_to_6.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_5_to_6.sql

Property Changed:
----------------
    CalendarServer/trunk/
    CalendarServer/trunk/support/build.sh
    CalendarServer/trunk/txdav/caldav/datastore/index_file.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py


Property changes on: CalendarServer/trunk
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593

Added: CalendarServer/trunk/calendarserver/push/__init__.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/push/__init__.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -0,0 +1,19 @@
+##
+# 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+CalendarServer push notification code.
+"""

Copied: CalendarServer/trunk/calendarserver/push/applepush.py (from rev 8184, CalendarServer/branches/users/sagen/applepush/twistedcaldav/applepush.py)
===================================================================
--- CalendarServer/trunk/calendarserver/push/applepush.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/push/applepush.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -0,0 +1,585 @@
+##
+# 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.internet.ssl import ChainingOpenSSLContextFactory
+from twext.python.log import Logger, LoggingMixIn
+from twext.python.log import LoggingMixIn
+from twext.web2 import responsecode
+from twext.web2.dav import davxml
+from twext.web2.dav.noneprops import NonePropertyStore
+from twext.web2.dav.resource import DAVResource
+from twext.web2.http import Response
+from twext.web2.http_headers import MimeType
+from twext.web2.server import parsePOSTData
+from twisted.application import service
+from twisted.internet import reactor, protocol
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.protocol import ClientFactory, ReconnectingClientFactory
+from twistedcaldav.extensions import DAVResource, DAVResourceWithoutChildrenMixin
+from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
+import OpenSSL
+import struct
+import time
+
+
+
+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
+        service.providers = {}
+        service.feedbacks = {}
+        service.dataHost = settings["DataHost"]
+
+        for protocol in ("CalDAV", "CardDAV"):
+
+            providerTestConnector = None
+            feedbackTestConnector = None
+            if testConnectorClass is not None:
+                providerTestConnector = testConnectorClass()
+                feedbackTestConnector = testConnectorClass()
+
+            provider = APNProviderService(
+                settings["ProviderHost"],
+                settings["ProviderPort"],
+                settings[protocol]["CertificatePath"],
+                settings[protocol]["PrivateKeyPath"],
+                testConnector=providerTestConnector,
+                reactor=reactor,
+            )
+            provider.setServiceParent(service)
+            service.providers[protocol] = provider
+            service.log_info("APNS %s topic: %s" %
+                (protocol, settings[protocol]["Topic"]))
+
+            feedback = APNFeedbackService(
+                service.store,
+                settings["FeedbackUpdateSeconds"],
+                settings["FeedbackHost"],
+                settings["FeedbackPort"],
+                settings[protocol]["CertificatePath"],
+                settings[protocol]["PrivateKeyPath"],
+                testConnector=feedbackTestConnector,
+                reactor=reactor,
+            )
+            feedback.setServiceParent(service)
+            service.feedbacks[protocol] = feedback
+
+        return service
+
+
+    @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:
+            # id has no protocol, so we can't do anything with it
+            self.log_error("Notification id '%s' is missing protocol" % (id,))
+            return
+
+        provider = self.providers.get(protocol, None)
+        if provider is not None:
+            key = "/%s/%s/%s/" % (protocol, self.dataHost, id)
+
+            # Look up subscriptions for this key
+            txn = self.store.newTransaction()
+            subscriptions = (yield txn.apnSubscriptionsByKey(key))
+            yield txn.commit()
+
+            numSubscriptions = len(subscriptions)
+            if numSubscriptions > 0:
+                self.log_debug("Sending %d APNS notifications for %s" %
+                    (numSubscriptions, key))
+                for token, guid in subscriptions:
+                    provider.sendNotification(token, key)
+
+
+
+class APNProviderProtocol(protocol.Protocol, LoggingMixIn):
+    """
+    Implements the Provider portion of APNS
+    """
+
+    # Sent by provider
+    COMMAND_SIMPLE   = 0
+    COMMAND_ENHANCED = 1
+
+    # Received by provider
+    COMMAND_ERROR    = 8
+
+    # Returned only for an error.  Successful notifications get no response.
+    STATUS_CODES = {
+        0   : "No errors encountered",
+        1   : "Processing error",
+        2   : "Missing device token",
+        3   : "Missing topic",
+        4   : "Missing payload",
+        5   : "Invalid token size",
+        6   : "Invalid topic size",
+        7   : "Invalid payload size",
+        8   : "Invalid token",
+        255 : "None (unknown)",
+    }
+
+    def makeConnection(self, transport):
+        self.identifier = 0
+        # self.log_debug("ProviderProtocol makeConnection")
+        protocol.Protocol.makeConnection(self, transport)
+
+    def connectionMade(self):
+        self.log_debug("ProviderProtocol connectionMade")
+        # Store a reference to ourself on the factory so the service can
+        # later call us
+        self.factory.connection = self
+        self.factory.clientConnectionMade()
+
+    def connectionLost(self, reason=None):
+        # self.log_debug("ProviderProtocol connectionLost: %s" % (reason,))
+        # Clear the reference to us from the factory
+        self.factory.connection = None
+
+    def dataReceived(self, data):
+        self.log_debug("ProviderProtocol dataReceived %d bytes" % (len(data),))
+        command, status, identifier = struct.unpack("!BBI", data)
+        if command == self.COMMAND_ERROR:
+            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, 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:
+            self.log_error("Invalid APN token in database: %s" % (token,))
+            return
+
+        self.identifier += 1
+        payload = '{"key" : "%s"}' % (key,)
+        payloadLength = len(payload)
+        self.log_debug("Sending APNS notification to %s: id=%d payload=%s" %
+            (token, self.identifier, payload))
+
+        self.transport.write(
+            struct.pack("!BIIH32sH%ds" % (payloadLength,),
+                self.COMMAND_ENHANCED,  # Command
+                self.identifier,        # Identifier
+                0,                      # Expiry
+                32,                     # Token Length
+                binaryToken,            # Token
+                payloadLength,          # Payload Length
+                payload,                # Payload in JSON format
+            )
+        )
+
+
+class APNProviderFactory(ReconnectingClientFactory, LoggingMixIn):
+
+    protocol = APNProviderProtocol
+
+    def __init__(self, service):
+        self.service = service
+
+    def clientConnectionMade(self):
+        self.service.clientConnectionMade()
+
+    def clientConnectionLost(self, connector, reason):
+        # self.log_info("Connection to APN server lost: %s" % (reason,))
+        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
+
+    def clientConnectionFailed(self, connector, reason):
+        self.log_error("Unable to connect to APN server: %s" % (reason,))
+        self.connected = False
+        ReconnectingClientFactory.clientConnectionFailed(self, connector,
+            reason)
+
+
+class APNConnectionService(service.Service, LoggingMixIn):
+
+    def __init__(self, host, port, certPath, keyPath, chainPath="",
+        sslMethod="TLSv1_METHOD", testConnector=None, reactor=None):
+
+        self.host = host
+        self.port = port
+        self.certPath = certPath
+        self.keyPath = keyPath
+        self.chainPath = chainPath
+        self.sslMethod = sslMethod
+        self.testConnector = testConnector
+
+        if reactor is None:
+            from twisted.internet import reactor
+        self.reactor = reactor
+
+    def connect(self, factory):
+        if self.testConnector is not None:
+            # For testing purposes
+            self.testConnector.connect(self, factory)
+        else:
+            context = ChainingOpenSSLContextFactory(
+                self.keyPath,
+                self.certPath,
+                certificateChainFile=self.chainPath,
+                sslmethod=getattr(OpenSSL.SSL, self.sslMethod)
+            )
+            reactor.connectSSL(self.host, self.port, factory, context)
+
+
+class APNProviderService(APNConnectionService):
+
+    def __init__(self, host, port, certPath, keyPath, chainPath="",
+        sslMethod="TLSv1_METHOD", testConnector=None, reactor=None):
+
+        APNConnectionService.__init__(self, host, port, certPath, keyPath,
+            chainPath="", sslMethod=sslMethod,
+            testConnector=testConnector, reactor=reactor)
+
+        self.factory = None
+        self.queue = []
+
+    def startService(self):
+        self.log_debug("APNProviderService startService")
+        self.factory = APNProviderFactory(self)
+        self.connect(self.factory)
+
+    def stopService(self):
+        self.log_debug("APNProviderService stopService")
+
+    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 in queued:
+                self.sendNotification(token, key)
+
+    def sendNotification(self, token, key):
+        # Service has reference to factory has reference to protocol instance
+        connection = getattr(self.factory, "connection", None)
+        if connection is None:
+            self.log_debug("APNProviderService has no connection; queuing: %s %s" % (token, key))
+            tokenKeyPair = (token, key)
+            if tokenKeyPair not in self.queue:
+                self.queue.append(tokenKeyPair)
+        else:
+            connection.sendNotification(token, key)
+
+
+class APNFeedbackProtocol(protocol.Protocol, LoggingMixIn):
+    """
+    Implements the Feedback portion of APNS
+    """
+
+    def connectionMade(self):
+        self.log_debug("FeedbackProtocol connectionMade")
+
+    def dataReceived(self, data):
+        self.log_debug("FeedbackProtocol dataReceived %d bytes" % (len(data),))
+        timestamp, tokenLength, binaryToken = struct.unpack("!IH32s", data)
+        token = binaryToken.encode("hex")
+        return self.processFeedback(timestamp, token)
+
+    @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()
+        subscriptions = (yield txn.apnSubscriptionsByToken(token))
+
+        for key, modified, guid in subscriptions:
+            if timestamp > modified:
+                self.log_debug("FeedbackProtocol removing subscription: %s %s" %
+                    (token, key))
+                yield txn.removeAPNSubscription(token, key)
+        yield txn.commit()
+
+
+class APNFeedbackFactory(ClientFactory, LoggingMixIn):
+
+    protocol = APNFeedbackProtocol
+
+    def __init__(self, store):
+        self.store = store
+
+    def clientConnectionFailed(self, connector, reason):
+        self.log_error("Unable to connect to APN feedback server: %s" %
+            (reason,))
+        self.connected = False
+        ClientFactory.clientConnectionFailed(self, connector, reason)
+
+
+class APNFeedbackService(APNConnectionService):
+
+    def __init__(self, store, updateSeconds, host, port, certPath, keyPath,
+        chainPath="", sslMethod="TLSv1_METHOD", testConnector=None,
+        reactor=None):
+
+        APNConnectionService.__init__(self, host, port, certPath, keyPath,
+            chainPath="", sslMethod=sslMethod,
+            testConnector=testConnector, reactor=reactor)
+
+        self.store = store
+        self.updateSeconds = updateSeconds
+
+    def startService(self):
+        self.log_debug("APNFeedbackService startService")
+        self.factory = APNFeedbackFactory(self.store)
+        self.checkForFeedback()
+
+    def stopService(self):
+        self.log_debug("APNFeedbackService stopService")
+        if self.nextCheck is not None:
+            self.nextCheck.cancel()
+
+    def checkForFeedback(self):
+        self.nextCheck = None
+        self.log_debug("APNFeedbackService checkForFeedback")
+        self.connect(self.factory)
+        self.nextCheck = self.reactor.callLater(self.updateSeconds,
+            self.checkForFeedback)
+
+
+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.
+    """
+
+    def __init__(self, parent, store):
+        DAVResource.__init__(
+            self, principalCollections=parent.principalCollections()
+        )
+        self.parent = parent
+        self.store = store
+
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            self._dead_properties = NonePropertyStore(self)
+        return self._dead_properties
+
+    def etag(self):
+        return None
+
+    def checkPreconditions(self, request):
+        return None
+
+    def defaultAccessControlList(self):
+        return davxml.ACL(
+            # DAV:Read for authenticated principals
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Read()),
+                ),
+                davxml.Protected(),
+            ),
+            # DAV:Write for authenticated principals
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(
+                    davxml.Privilege(davxml.Write()),
+                ),
+                davxml.Protected(),
+            ),
+        )
+
+    def contentType(self):
+        return MimeType.fromString("text/html; charset=utf-8");
+
+    def resourceType(self):
+        return None
+
+    def isCollection(self):
+        return False
+
+    def isCalendarCollection(self):
+        return False
+
+    def isPseudoCalendarCollection(self):
+        return False
+
+    @inlineCallbacks
+    def http_POST(self, request):
+        yield self.authorize(request, (davxml.Write(),))
+        yield parsePOSTData(request)
+        code, msg = (yield self.processSubscription(request))
+        returnValue(self.renderResponse(code, body=msg))
+
+    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
+            principal = collection._principalForURI(data)
+            if principal is not None:
+                return principal
+
+    @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:
+            key = key[0]
+            token = token[0].replace(" ", "")
+            principal = self.principalFromRequest(request)
+            guid = principal.record.guid
+            yield self.addSubscription(token, key, guid)
+            code = responsecode.OK
+            msg = None
+        else:
+            code = responsecode.BAD_REQUEST
+            msg = "Invalid request: both 'token' and 'key' must be provided"
+
+        returnValue((code, msg))
+
+    @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)
+        yield txn.commit()
+
+    def renderResponse(self, code, body=None):
+        response = Response(code, {}, body)
+        response.headers.setHeader("content-type", MimeType("text", "html"))
+        return response

Added: CalendarServer/trunk/calendarserver/push/test/__init__.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/test/__init__.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/push/test/__init__.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -0,0 +1,19 @@
+##
+# 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+CalendarServer push notification code.
+"""

Copied: CalendarServer/trunk/calendarserver/push/test/test_applepush.py (from rev 8184, CalendarServer/branches/users/sagen/applepush/twistedcaldav/test/test_applepush.py)
===================================================================
--- CalendarServer/trunk/calendarserver/push/test/test_applepush.py	                        (rev 0)
+++ CalendarServer/trunk/calendarserver/push/test/test_applepush.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -0,0 +1,153 @@
+##
+# 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from calendarserver.push.applepush import (
+    ApplePushNotifierService, APNProviderProtocol
+)
+from twistedcaldav.test.util import TestCase
+from twisted.internet.defer import inlineCallbacks
+from twisted.internet.task import Clock
+import struct
+from txdav.common.datastore.test.util import buildStore, CommonCommonTests
+
+class ApplePushNotifierServiceTests(CommonCommonTests, TestCase):
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(ApplePushNotifierServiceTests, self).setUp()
+        self.store = yield buildStore(self, None)
+
+    @inlineCallbacks
+    def test_ApplePushNotifierService(self):
+
+        settings = {
+            "Service" : "calendarserver.push.applepush.ApplePushNotifierService",
+            "Enabled" : True,
+            "SubscriptionURL" : "apn",
+            "DataHost" : "calendars.example.com",
+            "ProviderHost" : "gateway.push.apple.com",
+            "ProviderPort" : 2195,
+            "FeedbackHost" : "feedback.push.apple.com",
+            "FeedbackPort" : 2196,
+            "FeedbackUpdateSeconds" : 300,
+            "CalDAV" : {
+                "CertificatePath" : "caldav.cer",
+                "PrivateKeyPath" : "caldav.pem",
+                "Topic" : "caldav_topic",
+            },
+            "CardDAV" : {
+                "CertificatePath" : "carddav.cer",
+                "PrivateKeyPath" : "carddav.pem",
+                "Topic" : "carddav_topic",
+            },
+        }
+
+
+        # Add subscriptions
+        txn = self.store.newTransaction()
+        token = "2d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df"
+        key1 = "/CalDAV/calendars.example.com/user01/calendar/"
+        timestamp1 = 1000
+        guid = "D2256BCC-48E2-42D1-BD89-CBA1E4CCDFFB"
+        yield txn.addAPNSubscription(token, key1, timestamp1, guid)
+
+        key2 = "/CalDAV/calendars.example.com/user02/calendar/"
+        timestamp2 = 3000
+        yield txn.addAPNSubscription(token, key2, timestamp2, guid)
+        yield txn.commit()
+
+        # Set up the service
+        clock = Clock()
+        service = (yield ApplePushNotifierService.makeService(settings,
+            self.store, testConnectorClass=TestConnector, reactor=clock))
+        self.assertEquals(set(service.providers.keys()), set(["CalDAV","CardDAV"]))
+        self.assertEquals(set(service.feedbacks.keys()), set(["CalDAV","CardDAV"]))
+
+        # First, enqueue a notification while we have no connection, in this
+        # case by doing it prior to startService()
+
+        # Notification arrives from calendar server
+        yield service.enqueue("update", "CalDAV|user01/calendar")
+
+        # The notification should be in the queue
+        self.assertEquals(service.providers["CalDAV"].queue, [(token, key1)])
+
+        # Start the service, making the connection which should service the
+        # queue
+        service.startService()
+
+        # The queue should be empty
+        self.assertEquals(service.providers["CalDAV"].queue, [])
+
+        # Verify data sent to APN
+        connector = service.providers["CalDAV"].testConnector
+        rawData = connector.transport.data
+        self.assertEquals(len(rawData), 103)
+        data = struct.unpack("!BIIH32sH", rawData[:45])
+        self.assertEquals(data[0], 1) # command
+        self.assertEquals(data[4].encode("hex"), token.replace(" ", "")) # token
+        payloadLength = data[5]
+        payload = struct.unpack("%ds" % (payloadLength,),
+            rawData[45:])
+        self.assertEquals(payload[0], '{"key" : "%s"}' % (key1,))
+
+        # Simulate an error
+        errorData = struct.pack("!BBI", APNProviderProtocol.COMMAND_ERROR, 1, 1)
+        yield connector.receiveData(errorData)
+        clock.advance(301)
+
+        # Prior to feedback, there are 2 subscriptions
+        txn = self.store.newTransaction()
+        subscriptions = (yield txn.apnSubscriptionsByToken(token))
+        yield txn.commit()
+        self.assertEquals(len(subscriptions), 2)
+
+        # Simulate feedback
+        timestamp = 2000
+        connector = service.feedbacks["CalDAV"].testConnector
+        binaryToken = token.decode("hex")
+        feedbackData = struct.pack("!IH32s", timestamp, len(binaryToken),
+            binaryToken)
+        yield connector.receiveData(feedbackData)
+
+        # The second subscription should now be gone
+        # Prior to feedback, there are 2 subscriptions
+        txn = self.store.newTransaction()
+        subscriptions = (yield txn.apnSubscriptionsByToken(token))
+        yield txn.commit()
+        self.assertEquals(len(subscriptions), 1)
+
+
+class TestConnector(object):
+
+    def connect(self, service, factory):
+        self.service = service
+        service.protocol = factory.buildProtocol(None)
+        service.connected = 1
+        self.transport = StubTransport()
+        service.protocol.makeConnection(self.transport)
+
+    def receiveData(self, data):
+        return self.service.protocol.dataReceived(data)
+
+
+class StubTransport(object):
+
+    def __init__(self):
+        self.data = None
+
+    def write(self, data):
+        self.data = data

Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/calendarserver/tap/util.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -56,6 +56,7 @@
 from twistedcaldav.directory.sudo import SudoDirectoryService
 from twistedcaldav.directory.wiki import WikiDirectoryService
 from twistedcaldav.notify import NotifierFactory, getPubSubConfiguration
+from calendarserver.push.applepush import APNSubscriptionResource
 from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
 from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
 from twistedcaldav.schedule import IScheduleInboxResource
@@ -358,6 +359,7 @@
     webAdminResourceClass           = WebAdminResource
     addressBookResourceClass        = DirectoryAddressBookHomeProvisioningResource
     directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookResource
+    apnSubscriptionResourceClass    = APNSubscriptionResource
 
     directory = directoryFromConfig(config)
 
@@ -593,6 +595,16 @@
         root.putChild("admin", webAdmin)
 
     #
+    # Apple Push Notification Subscriptions
+    #
+    apnConfig = config.Notifications.Services["ApplePushNotifier"]
+    if apnConfig.Enabled:
+        log.info("Setting up APNS resource at /%s" %
+            (apnConfig["SubscriptionURL"],))
+        apnResource = apnSubscriptionResourceClass(root, newStore)
+        root.putChild(apnConfig["SubscriptionURL"], apnResource)
+
+    #
     # Configure ancillary data
     #
     log.info("Setting up Timezone Cache")


Property changes on: CalendarServer/trunk/support/build.sh
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace/support/build.sh:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pods/support/build.sh:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/support/build.sh:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/deploybuild/support/build.sh:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/support/build.sh:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/new-export/support/build.sh:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/support/build.sh:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/support/build.sh:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593
   + /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace/support/build.sh:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pods/support/build.sh:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/support/build.sh:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/deploybuild/support/build.sh:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/support/build.sh:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/new-export/support/build.sh:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/support/build.sh:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/applepush/support/build.sh:8126-8184
/CalendarServer/branches/users/sagen/inboxitems/support/build.sh:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593

Modified: CalendarServer/trunk/twistedcaldav/notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notify.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/twistedcaldav/notify.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -147,15 +147,16 @@
         id = self.getID(label=label)
         pubSubConfig = self._notifierFactory.pubSubConfig
         name = getPubSubPath(id, pubSubConfig)
-        try:
-            if self._notifierFactory.nodeCacher:
-                nodeCacher = self._notifierFactory.nodeCacher
-            else:
-                nodeCacher = getNodeCacher()
-            (yield nodeCacher.waitForNode(self, name))
-        except NodeCreationException, e:
-            self.log_warn(e)
-            returnValue(None)
+        if pubSubConfig["enabled"]:
+            try:
+                if self._notifierFactory.nodeCacher:
+                    nodeCacher = self._notifierFactory.nodeCacher
+                else:
+                    nodeCacher = getNodeCacher()
+                (yield nodeCacher.waitForNode(self, name))
+            except NodeCreationException, e:
+                self.log_warn(e)
+                returnValue(None)
         returnValue(name)
 
 class NotificationClientLineProtocol(LineReceiver, LoggingMixIn):
@@ -458,7 +459,7 @@
 
     def enqueue(self, op, id):
         """
-        Let's the notifier object know that a change has been made for this
+        Let the notifier object know that a change has been made for this
         id, and enough time has passed to allow for coalescence.
 
         @type op: C{str}
@@ -1258,12 +1259,11 @@
 
 def getPubSubConfiguration(config):
     # TODO: Should probably cache this
-    results = { 'enabled' : False }
+    results = { 'enabled' : False, 'host' : config.ServerHostName }
     settings = getXMPPSettings(config)
     if settings is not None:
         results['enabled'] = True
         results['service'] = settings['ServiceAddress']
-        results['host'] = config.ServerHostName
         results['port'] = config.SSLPort or config.HTTPPort
         results['xmpp-server'] = (
             settings['Host'] if settings['Port'] == 5222
@@ -1278,16 +1278,33 @@
     Returns the Apple push notification settings specific to the notifier
     ID, which includes a prefix that is either "CalDAV" or "CardDAV"
     """
-    settings = getXMPPSettings(config)
-    if settings is None:
-        return None
-
     try:
         prefix, id = id.split("|", 1)
     except ValueError:
         # id has no prefix, so we can't look up APS config
         return None
 
+    # If we are directly talking to apple push, advertise those settings
+    applePushSettings = config.Notifications.Services.ApplePushNotifier
+    if applePushSettings.Enabled:
+        settings = {}
+        settings["APSBundleID"] = applePushSettings[prefix]["Topic"]
+        if config.EnableSSL:
+            url = "https://%s:%s/%s" % (config.ServerHostName, config.SSLPort,
+                applePushSettings.SubscriptionURL)
+        else:
+            url = "http://%s:%s/%s" % (config.ServerHostName, config.HTTPPort,
+                applePushSettings.SubscriptionURL)
+        settings["SubscriptionURL"] = url
+        settings["APSEnvironment"] = applePushSettings.Environment
+        return settings
+
+    # ...otherwise pick up the apple push settings we get via XMPP and
+    # apn bridge
+    settings = getXMPPSettings(config)
+    if settings is None:
+        return None
+
     if (settings.has_key(prefix) and
         settings[prefix]["APSBundleID"] and
         settings[prefix]["SubscriptionURL"]):
@@ -1441,12 +1458,49 @@
             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
+        )
+        from twext.enterprise.ienterprise import POSTGRES_DIALECT
+        from twext.enterprise.ienterprise import ORACLE_DIALECT
+        from twext.enterprise.adbapi2 import ConnectionPool
+
+        pool = None
+        if not config.UseDatabase:
+            txnFactory = None
+        else:
+            dialect = POSTGRES_DIALECT
+            paramstyle = 'pyformat'
+            if config.DBType == '':
+                # get a PostgresService to tell us what the local connection
+                # info is, but *don't* start it (that would start one postgres
+                # master per slave, resulting in all kinds of mayhem...)
+                connectionFactory = pgServiceFromConfig(
+                    config, None).produceConnection
+            elif config.DBType == 'postgres':
+                connectionFactory = pgConnectorFromConfig(config)
+            elif config.DBType == 'oracle':
+                dialect = ORACLE_DIALECT
+                paramstyle = 'numeric'
+                connectionFactory = oracleConnectorFromConfig(config)
+            else:
+                raise UsageError("unknown DB type: %r" % (config.DBType,))
+            pool = ConnectionPool(connectionFactory, dialect=dialect,
+                                  paramstyle=paramstyle)
+            txnFactory = pool.connection
+
+        store = storeFromConfig(config, txnFactory)
+
         multiService = service.MultiService()
 
         notifiers = []
         for key, settings in config.Notifications.Services.iteritems():
             if settings["Enabled"]:
-                notifier = namedClass(settings["Service"])(settings)
+                notifier = namedClass(settings["Service"]).makeService(settings,
+                    store)
                 notifier.setServiceParent(multiService)
                 notifiers.append(notifier)
 
@@ -1462,6 +1516,10 @@
 
 class SimpleLineNotifierService(service.Service):
 
+    @classmethod
+    def makeService(cls, settings, store):
+        return cls(settings)
+
     def __init__(self, settings):
         self.notifier = SimpleLineNotifier(settings)
         self.server = internet.TCPServer(settings["Port"],
@@ -1479,6 +1537,10 @@
 
 class XMPPNotifierService(service.Service):
 
+    @classmethod
+    def makeService(cls, settings, store):
+        return cls(settings)
+
     def __init__(self, settings):
         self.notifier = XMPPNotifier(settings)
 

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -2224,7 +2224,10 @@
             returnValue(customxml.MaxCollections.fromString(config.MaxCollectionsPerHome))
             
         elif qname == (customxml.calendarserver_namespace, "push-transports"):
-            if config.Notifications.Services.XMPPNotifier.Enabled:
+
+            if (config.Notifications.Services.XMPPNotifier.Enabled or
+                config.Notifications.Services.ApplePushNotifier.Enabled):
+
                 nodeName = (yield self._newStoreHome.nodeName())
                 if nodeName:
                     notifierID = self._newStoreHome.notifierID()
@@ -2251,7 +2254,8 @@
                             )
 
                         pubSubConfiguration = getPubSubConfiguration(config)
-                        if pubSubConfiguration['xmpp-server']:
+                        if (pubSubConfiguration['enabled'] and
+                            pubSubConfiguration['xmpp-server']):
                             children.append(
                                 customxml.PubSubTransportProperty(
                                     customxml.PubSubXMPPServerProperty(
@@ -2268,7 +2272,8 @@
             returnValue(None)
 
         elif qname == (customxml.calendarserver_namespace, "pushkey"):
-            if config.Notifications.Services.XMPPNotifier.Enabled:
+            if (config.Notifications.Services.XMPPNotifier.Enabled or
+                config.Notifications.Services.ApplePushNotifier.Enabled):
                 nodeName = (yield self._newStoreHome.nodeName())
                 if nodeName:
                     returnValue(customxml.PubSubXMPPPushKeyProperty(nodeName))

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -631,6 +631,28 @@
                 "Enabled" : False,
                 "Port" : 62308,
             },
+            "ApplePushNotifier" : {
+                "Service" : "calendarserver.push.applepush.ApplePushNotifierService",
+                "Enabled" : False,
+                "SubscriptionURL" : "apns",
+                "DataHost" : "",
+                "ProviderHost" : "gateway.push.apple.com",
+                "ProviderPort" : 2195,
+                "FeedbackHost" : "feedback.push.apple.com",
+                "FeedbackPort" : 2196,
+                "FeedbackUpdateSeconds" : 300, # 5 minutes
+                "Environment" : "PRODUCTION",
+                "CalDAV" : {
+                    "CertificatePath" : "",
+                    "PrivateKeyPath" : "",
+                    "Topic" : "",
+                },
+                "CardDAV" : {
+                    "CertificatePath" : "",
+                    "PrivateKeyPath" : "",
+                    "Topic" : "",
+                },
+            },
             "XMPPNotifier" : {
                 "Service" : "twistedcaldav.notify.XMPPNotifierService",
                 "Enabled" : False,
@@ -1185,7 +1207,15 @@
         configDict.Notifications["Enabled"] = False
 
     for key, service in configDict.Notifications["Services"].iteritems():
+
+        # The default for apple push DataHost is ServerHostName
         if (
+            service["Service"] == "calendarserver.push.applepush.ApplePushNotifierService" and
+            service["DataHost"] == ""
+        ):
+            service["DataHost"] = configDict.ServerHostName
+
+        if (
             service["Service"] == "twistedcaldav.notify.XMPPNotifierService" and
             service["Enabled"]
         ):

Modified: CalendarServer/trunk/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_config.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/twistedcaldav/test/test_config.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -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/trunk/twistedcaldav/test/test_notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_notify.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/twistedcaldav/test/test_notify.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -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)
 


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace/txdav/caldav/datastore/index_file.py:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/index_file.py:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/caldav/datastore/index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/caldav/datastore/index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace/txdav/caldav/datastore/index_file.py:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/index_file.py:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/caldav/datastore/index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/caldav/datastore/index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/applepush/txdav/caldav/datastore/index_file.py:8126-8184
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_index_file.py:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/caldav/datastore/test/test_index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/caldav/datastore/test/test_index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_index_file.py:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/caldav/datastore/test/test_index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/caldav/datastore/test/test_index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/applepush/txdav/caldav/datastore/test/test_index_file.py:8126-8184
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace/txdav/carddav/datastore/index_file.py:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/index_file.py:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/carddav/datastore/index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/carddav/datastore/index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace/txdav/carddav/datastore/index_file.py:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/index_file.py:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/carddav/datastore/index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/carddav/datastore/index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/applepush/txdav/carddav/datastore/index_file.py:8126-8184
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/test_index_file.py:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/carddav/datastore/test/test_index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/carddav/datastore/test/test_index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/test_index_file.py:7443-7699
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/carddav/datastore/test/test_index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/carddav/datastore/test/test_index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/applepush/txdav/carddav/datastore/test/test_index_file.py:8126-8184
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -232,6 +232,20 @@
         return NotificationCollection.notificationsFromHome(self, home)
 
 
+    # File-based storage of APN subscriptions not implementated.
+    def addAPNSubscription(self, token, key, timestamp, subscriber):
+        return NotImplementedError
+
+    def removeAPNSubscription(self, token, key):
+        return NotImplementedError
+
+    def apnSubscriptionsByToken(self, token):
+        return NotImplementedError
+
+    def apnSubscriptionsByKey(self, key):
+        return NotImplementedError
+
+
 class StubResource(object):
     """
     Just enough resource to keep the shared sql DB classes going.

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -324,6 +324,91 @@
         return NotificationCollection.notificationsWithUID(self, uid)
 
 
+    @classproperty
+    def _insertAPNSubscriptionQuery(cls): #@NoSelf
+        apn = schema.APN_SUBSCRIPTIONS
+        return Insert({apn.TOKEN: Parameter("token"),
+                       apn.RESOURCE_KEY: Parameter("resourceKey"),
+                       apn.MODIFIED: Parameter("modified"),
+                       apn.SUBSCRIBER_GUID: Parameter("subscriber")})
+
+
+    @classproperty
+    def _updateAPNSubscriptionQuery(cls): #@NoSelf
+        apn = schema.APN_SUBSCRIPTIONS
+        return Update({apn.MODIFIED: Parameter("modified")},
+                      Where=(apn.TOKEN == Parameter("token")).And(
+                             apn.RESOURCE_KEY == Parameter("resourceKey")))
+
+
+    @classproperty
+    def _selectAPNSubscriptionQuery(cls): #@NoSelf
+        apn = schema.APN_SUBSCRIPTIONS
+        return Select([apn.MODIFIED, apn.SUBSCRIBER_GUID], From=apn,
+                Where=(
+                    apn.TOKEN == Parameter("token")).And(
+                    apn.RESOURCE_KEY == Parameter("resourceKey")
+                )
+            )
+
+
+    @inlineCallbacks
+    def addAPNSubscription(self, token, key, timestamp, subscriber):
+        row = yield self._selectAPNSubscriptionQuery.on(self,
+            token=token, resourceKey=key)
+        if not row: # Subscription does not yet exist
+            try:
+                yield self._insertAPNSubscriptionQuery.on(self,
+                    token=token, resourceKey=key, modified=timestamp,
+                    subscriber=subscriber)
+            except Exception:
+                # Subscription may have been added by someone else, which is fine
+                pass
+
+        else: # Subscription exists, so update with new timestamp
+            try:
+                yield self._updateAPNSubscriptionQuery.on(self,
+                    token=token, resourceKey=key, modified=timestamp)
+            except Exception:
+                # Subscription may have been added by someone else, which is fine
+                pass
+
+
+    @classproperty
+    def _removeAPNSubscriptionQuery(cls): #@NoSelf
+        apn = schema.APN_SUBSCRIPTIONS
+        return Delete(From=apn,
+                      Where=(apn.TOKEN == Parameter("token")).And(
+                          apn.RESOURCE_KEY == Parameter("resourceKey")))
+
+
+    def removeAPNSubscription(self, token, key):
+        return self._removeAPNSubscriptionQuery.on(self,
+            token=token, resourceKey=key)
+
+
+    @classproperty
+    def _apnSubscriptionsByTokenQuery(cls): #@NoSelf
+        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
+        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/trunk/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql	2011-10-11 21:32:18 UTC (rev 8186)
@@ -435,6 +435,24 @@
 );
 
 
+-------------------------------------------
+-- Apple Push Notification Subscriptions --
+-------------------------------------------
+
+create table APN_SUBSCRIPTIONS (
+  TOKEN                         varchar(255) not null,
+  RESOURCE_KEY                  varchar(255) not null,
+  MODIFIED                      integer not null,
+  SUBSCRIBER_GUID               varchar(255) not null,
+  unique(TOKEN, RESOURCE_KEY) -- implicit index
+);
+
+create index APN_SUBSCRIPTIONS_TOKENS
+   on APN_SUBSCRIPTIONS(TOKEN);
+create index APN_SUBSCRIPTIONS_RESOURCE_KEY
+   on APN_SUBSCRIPTIONS(RESOURCE_KEY);
+
+
 --------------------
 -- Schema Version --
 --------------------
@@ -444,5 +462,5 @@
   VALUE                         varchar(255)
 );
 
-insert into CALENDARSERVER values ('VERSION', '5');
+insert into CALENDARSERVER values ('VERSION', '6');
 

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_5_to_6.sql (from rev 8184, CalendarServer/branches/users/sagen/applepush/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_5_to_6.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_5_to_6.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_5_to_6.sql	2011-10-11 21:32:18 UTC (rev 8186)
@@ -0,0 +1,40 @@
+----
+-- 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.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+----
+
+-------------------------------------------------
+-- Upgrade database schema from VERSION 5 to 6 --
+-------------------------------------------------
+
+---------------------------------------------------------
+-- New table for Apple Push Notification Subscriptions --
+---------------------------------------------------------
+
+create table APN_SUBSCRIPTIONS (
+  TOKEN                         varchar(255) not null,
+  RESOURCE_KEY                  varchar(255) not null,
+  MODIFIED                      integer not null,
+  SUBSCRIBER_GUID               varchar(255) not null,
+  unique(TOKEN, RESOURCE_KEY) -- implicit index
+);
+
+create index APN_SUBSCRIPTIONS_TOKENS
+  on APN_SUBSCRIPTIONS(TOKEN);
+create index APN_SUBSCRIPTIONS_RESOURCE_KEY
+  on APN_SUBSCRIPTIONS(RESOURCE_KEY);
+
+-- Now update the version
+update CALENDARSERVER set VALUE = '6' where NAME = 'VERSION';
+

Copied: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_5_to_6.sql (from rev 8184, CalendarServer/branches/users/sagen/applepush/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_5_to_6.sql)
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_5_to_6.sql	                        (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_5_to_6.sql	2011-10-11 21:32:18 UTC (rev 8186)
@@ -0,0 +1,40 @@
+----
+-- 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.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+----
+
+-------------------------------------------------
+-- Upgrade database schema from VERSION 5 to 6 --
+-------------------------------------------------
+
+---------------------------------------------------------
+-- New table for Apple Push Notification Subscriptions --
+---------------------------------------------------------
+
+create table APN_SUBSCRIPTIONS (
+  TOKEN                         varchar(255) not null,
+  RESOURCE_KEY                  varchar(255) not null,
+  MODIFIED                      integer not null,
+  SUBSCRIBER_GUID               varchar(255) not null,
+  unique(TOKEN, RESOURCE_KEY) -- implicit index
+);
+
+create index APN_SUBSCRIPTIONS_TOKENS
+  on APN_SUBSCRIPTIONS(TOKEN);
+create index APN_SUBSCRIPTIONS_RESOURCE_KEY
+  on APN_SUBSCRIPTIONS(RESOURCE_KEY);
+
+-- Now update the version
+update CALENDARSERVER set VALUE = '6' where NAME = 'VERSION';
+

Modified: CalendarServer/trunk/txdav/common/icommondatastore.py
===================================================================
--- CalendarServer/trunk/txdav/common/icommondatastore.py	2011-10-11 20:06:44 UTC (rev 8185)
+++ CalendarServer/trunk/txdav/common/icommondatastore.py	2011-10-11 21:32:18 UTC (rev 8186)
@@ -147,8 +147,54 @@
             notification collection exists.
         """
 
+    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):
     """
     A collection resource which may be shared.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20111011/c6e60696/attachment-0001.html>


More information about the calendarserver-changes mailing list