[CalendarServer-changes] [5598] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu May 13 14:18:08 PDT 2010


Revision: 5598
          http://trac.macosforge.org/projects/calendarserver/changeset/5598
Author:   sagen at apple.com
Date:     2010-05-13 14:18:06 -0700 (Thu, 13 May 2010)
Log Message:
-----------
Adds <push-transports> and <pushkey> properties for push notification.  Exposes collection-specific pushkeys to sharees.

Also some pyflakes-detected changes.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/changeip_calendar.py
    CalendarServer/trunk/calendarserver/tools/purge.py
    CalendarServer/trunk/calendarserver/tools/test/test_principals.py
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/notify.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/static.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/test/test_notify.py

Modified: CalendarServer/trunk/calendarserver/tools/changeip_calendar.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/changeip_calendar.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/calendarserver/tools/changeip_calendar.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -12,10 +12,7 @@
 
 from __future__ import with_statement
 
-import datetime
-import optparse
 import os
-import shutil
 import sys
 from getopt import getopt, GetoptError
 

Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/calendarserver/tools/purge.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -334,6 +334,8 @@
 def purgeGUIDs(directory, root, guids, verbose=False, dryrun=False):
     total = 0
 
+    allAssignments = { }
+
     for guid in guids:
         count, allAssignments[guid] = (yield purgeGUID(guid, directory, root,
             verbose=verbose, dryrun=dryrun))

Modified: CalendarServer/trunk/calendarserver/tools/test/test_principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_principals.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/calendarserver/tools/test/test_principals.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -87,7 +87,6 @@
         cwd = sourceRoot
 
         deferred = Deferred()
-        e = os.environ
         reactor.spawnProcess(CapturingProcessProtocol(deferred, None), python, args, env=os.environ, path=cwd)
         output = yield deferred
         returnValue(output)

Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2010-05-13 21:18:06 UTC (rev 5598)
@@ -512,6 +512,12 @@
           <key>ServiceAddress</key>
           <string>pubsub.xmpp.host.name</string>
 
+          <!-- Apple-specific config -->
+          <key>SubscriptionURL</key>
+          <string></string>
+          <key>APSBundleID</key>
+          <string></string>
+
           <key>NodeConfiguration</key>
           <dict>
             <key>pubsub#deliver_payloads</key>

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -275,6 +275,53 @@
     namespace = calendarserver_namespace
     name = "utc-offset"
 
+class PubSubPushTransportsProperty (davxml.WebDAVTextElement):
+    """
+    A calendar property describing the available push notification transports
+    available.
+    """
+    namespace = calendarserver_namespace
+    name = "push-transports"
+    protected = True
+    hidden = True
+    allowed_children = {
+        (calendarserver_namespace, "transport") : (0, 1),
+    }
+
+class PubSubTransportProperty (davxml.WebDAVTextElement):
+    namespace = calendarserver_namespace
+    name = "transport"
+    protected = True
+    hidden = True
+    allowed_attributes = {
+        "type" : True,
+    }
+    allowed_children = {
+        (calendarserver_namespace, "subscription-url") : (1, 1),
+        (calendarserver_namespace, "apsbundleid") : (1, 1),
+        (calendarserver_namespace, "xmpp-server") : (1, 1),
+        (calendarserver_namespace, "xmpp-uri") : (1, 1),
+    }
+
+class PubSubSubscriptionProperty (davxml.WebDAVTextElement):
+    namespace = calendarserver_namespace
+    name = "subscription-url"
+    protected = True
+    hidden = True
+    allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
+
+class PubSubAPSBundleIDProperty (davxml.WebDAVTextElement):
+    namespace = calendarserver_namespace
+    name = "apsbundleid"
+    protected = True
+    hidden = True
+
+class PubSubXMPPPushKeyProperty (davxml.WebDAVTextElement):
+    namespace = calendarserver_namespace
+    name = "pushkey"
+    protected = True
+    hidden = True
+
 class PubSubXMPPURIProperty (davxml.WebDAVTextElement):
     """
     A calendarhomefile property to indicate the pubsub XMPP URI to subscribe to

Modified: CalendarServer/trunk/twistedcaldav/notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notify.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/twistedcaldav/notify.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -113,35 +113,65 @@
 
 class ClientNotifier(LoggingMixIn):
     """
-    Provides a method to send change notifications to the L{NotificationClient}.
+    Provides a hook for sending changs notifications to the
+    L{NotificationClient}.
     """
 
-    def __init__(self, resource, configOverride=None):
-        self._resource = resource
+    def __init__(self, resource, label="default", id=None, configOverride=None):
+        self.ids = { label : (resource, self.normalizeID(id)) }
         self._notify = True
         self.config = configOverride or config
 
+    def normalizeID(self, id):
+        urn = "urn:uuid:"
+        try:
+            if id.startswith(urn):
+                return id[len(urn):]
+        except AttributeError:
+            pass
+        return id
+
+    def addResource(self, resource, label="default", id=None):
+        self.ids[label] = (resource, self.normalizeID(id))
+
     def enableNotify(self, arg):
-        url = self._resource.url()
-        self.log_debug("enableNotify: %s" % (url,))
+        self.log_debug("enableNotify: %s" % (self.ids['default'][1],))
         self._notify = True
 
     def disableNotify(self):
-        url = self._resource.url()
-        self.log_debug("disableNotify: %s" % (url,))
+        self.log_debug("disableNotify: %s" % (self.ids['default'][1],))
         self._notify = False
 
     def notify(self, op="update"):
-        url = self._resource.url()
-
         if self.config.Notifications.Enabled:
-            if self._notify:
-                self.log_debug("Notifications are enabled: %s %s" % (op, url))
-                return getNotificationClient().send(op, url)
-            else:
-                self.log_debug("Skipping notification for: %s" % (url,))
+            notificationClient = getNotificationClient()
+            for label, (resource, id) in self.ids.iteritems():
+                if id is None:
+                    id = self.getID(label=label)
+                if id is not None:
+                    if self._notify:
+                        self.log_debug("Notifications are enabled: %s %s %s" %
+                            (op, label, id))
+                        notificationClient.send(op, id)
+                    else:
+                        self.log_debug("Skipping notification for: %s" % (id,))
 
+    def clone(self, resource, label="default", id=None):
+        newNotifier = self.__class__(None, configOverride=self.config)
+        newNotifier.ids = self.ids.copy()
+        newNotifier.ids[label] = (resource, id)
+        return newNotifier
 
+    def getID(self, label="default"):
+        resource, id = self.ids.get(label, (None, None))
+        if id is not None:
+            return id
+        if resource is not None:
+            id = self.normalizeID(resource.resourceID())
+            self.ids[label] = (resource, id)
+            return id
+        return None
+
 class NotificationClientLineProtocol(LineReceiver, LoggingMixIn):
     """
     Notification Client Line Protocol
@@ -216,13 +246,13 @@
             from twisted.internet import reactor
         self.reactor = reactor
 
-    def send(self, op, uri):
+    def send(self, op, id):
         if self.factory is None:
             self.factory = NotificationClientFactory(self)
             self.reactor.connectTCP(self.host, self.port, self.factory)
             self.log_debug("Creating factory")
 
-        msg = "%s %s" % (op, str(uri))
+        msg = "%s %s" % (op, str(id))
         if self.factory.isReady() and self.observers:
             for observer in self.observers:
                 self.log_debug("Sending to notification server: %s" % (msg,))
@@ -329,8 +359,8 @@
     """
 
     def lineReceived(self, line):
-        op, uri = line.strip().split()
-        self.factory.coalescer.add(op, uri)
+        op, id = line.strip().split()
+        self.factory.coalescer.add(op, id)
 
 
 class InternalNotificationFactory(ServerFactory):
@@ -351,7 +381,7 @@
     """
     Coalescer
 
-    A queue which hangs on to incoming uris for some period of time before
+    A queue which hangs on to incoming ids for some period of time before
     passing them along to the external notifier listening for these updates.
     A chatty CalDAV client can make several changes in a short period of time,
     and the Coalescer buffers the external clients somewhat.
@@ -379,40 +409,40 @@
             from twisted.internet import reactor
         self.reactor = reactor
 
-        self.uris = {}
+        self.ids = {}
         self.notifiers = notifiers
 
-    def add(self, op, uri):
+    def add(self, op, id):
 
         if op == "create":
             # we don't want to delay a "create" notification; this opcode
             # is meant for XMPP pubsub -- it means create and configure the
             # node but don't publish to it
             for notifier in self.notifiers:
-                notifier.enqueue(op, uri)
+                notifier.enqueue(op, id)
 
         else: # normal update notification
-            delayed, count = self.uris.get(uri, [None, 0])
+            delayed, count = self.ids.get(id, [None, 0])
 
             if delayed and delayed.active():
                 count += 1
                 if count < self.sendAnywayAfterCount:
                     # reschedule for delaySeconds in the future
                     delayed.reset(self.delaySeconds)
-                    self.uris[uri][1] = count
-                    self.log_debug("Delaying: %s" % (uri,))
+                    self.ids[id][1] = count
+                    self.log_debug("Delaying: %s" % (id,))
                 else:
-                    self.log_debug("Not delaying to avoid starvation: %s" % (uri,))
+                    self.log_debug("Not delaying to avoid starvation: %s" % (id,))
             else:
-                self.log_debug("Scheduling: %s" % (uri,))
-                self.uris[uri] = [self.reactor.callLater(self.delaySeconds,
-                    self.delayedEnqueue, op, uri), 0]
+                self.log_debug("Scheduling: %s" % (id,))
+                self.ids[id] = [self.reactor.callLater(self.delaySeconds,
+                    self.delayedEnqueue, op, id), 0]
 
-    def delayedEnqueue(self, op, uri):
-        self.log_debug("Time to send: %s" % (uri,))
-        self.uris[uri][1] = 0
+    def delayedEnqueue(self, op, id):
+        self.log_debug("Time to send: %s" % (id,))
+        self.ids[id][1] = 0
         for notifier in self.notifiers:
-            notifier.enqueue(op, uri)
+            notifier.enqueue(op, id)
 
 
 
@@ -427,13 +457,13 @@
     Defines an enqueue method that Notifier classes need to implement.
     """
 
-    def enqueue(self, op, uri):
+    def enqueue(self, op, id):
         """
         Let's the notifier object know that a change has been made for this
-        uri, and enough time has passed to allow for coalescence.
+        id, and enough time has passed to allow for coalescence.
 
         @type op: C{str}
-        @type uri: C{str}
+        @type id: C{str}
         """
 
 
@@ -441,10 +471,10 @@
     """
     Simple Line Notifier
 
-    Listens for uris from the coalescer and writes them out to any
+    Listens for ids from the coalescer and writes them out to any
     connected clients.  Each line is simply a sequence number, a
-    space, and a uri string.  If the external client sends a sequence
-    number, this notifier will send notification lines for each uri
+    space, and an id string.  If the external client sends a sequence
+    number, this notifier will send notification lines for each id
     that was changed since that sequence number was originally sent.
     A history of such sequence numbers is stored in a python dict.
     If the external client sends a zero, then the history is cleared
@@ -466,32 +496,32 @@
         self.observers = set()
         self.sentReset = False
 
-    def enqueue(self, op, uri):
+    def enqueue(self, op, id):
 
         if op == "update":
 
             self.latestSeq += 1L
 
             # Update history
-            self.history[uri] = self.latestSeq
+            self.history[id] = self.latestSeq
 
             for observer in self.observers:
-                msg = "%d %s" % (self.latestSeq, uri)
+                msg = "%d %s" % (self.latestSeq, id)
                 self.log_debug("Sending %s" % (msg,))
                 observer.sendLine(msg)
 
     def reset(self):
         self.latestSeq = 0L
-        self.history = { } # keys=uri, values=sequenceNumber
+        self.history = { } # keys=id, values=sequenceNumber
 
     def playback(self, observer, oldSeq):
 
         hist = self.history
-        toSend = [(hist[uri], uri) for uri in hist if hist[uri] > oldSeq]
+        toSend = [(hist[id], id) for id in hist if hist[id] > oldSeq]
         toSend.sort() # sorts the tuples based on numeric sequence number
 
-        for seq, uri in toSend:
-            msg = "%d %s" % (seq, uri)
+        for seq, id in toSend:
+            msg = "%d %s" % (seq, id)
             self.log_debug("Sending %s" % (msg,))
             observer.sendLine(msg)
 
@@ -634,10 +664,10 @@
             self.reactor.callLater(self.settings['HeartbeatMinutes'] * 60,
                 self.sendHeartbeat)
 
-    def enqueue(self, op, uri, lock=True):
+    def enqueue(self, op, id, lock=True):
         if self.xmlStream is not None:
-            # Convert uri to node
-            nodeName = self.uriToNodeName(uri)
+            # Convert id to node
+            nodeName = getPubSubPath(id, getPubSubConfiguration(self.config))
             if op == "create":
                 if not self.lockNode(nodeName):
                     # this node is busy, so it must already be created, or at
@@ -647,9 +677,6 @@
             else:
                 self.publishNode(nodeName, lock=lock)
 
-    def uriToNodeName(self, uri):
-        return getPubSubPath(uri, getPubSubConfiguration(self.config))
-
     def publishNode(self, nodeName, lock=True):
         if self.xmlStream is None:
             # We lost our connection
@@ -1205,20 +1232,22 @@
             results['host'] = config.ServerHostName
             results['port'] = config.SSLPort or config.HTTPPort
             results['xmpp-server'] = settings['Host']
+            results['subscription-url'] = settings['SubscriptionURL']
+            results['aps-bundle-id'] = settings['APSBundleID']
             results['heartrate'] = settings['HeartbeatMinutes']
+            break
 
     return results
 
-def getPubSubPath(uri, pubSubConfiguration):
-    path = "/Public/CalDAV/%s/%d/" % (pubSubConfiguration['host'],
-        pubSubConfiguration['port'])
-    if uri:
-        path += "%s/" % (uri.strip("/"),)
+def getPubSubPath(id, pubSubConfiguration):
+    path = "/DAV/%s/" % (pubSubConfiguration['host'],)
+    if id:
+        path += "%s/" % (id,)
     return path
 
-def getPubSubXMPPURI(uri, pubSubConfiguration):
+def getPubSubXMPPURI(id, pubSubConfiguration):
     return "xmpp:%s?pubsub;node=%s" % (pubSubConfiguration['service'],
-        getPubSubPath(uri, pubSubConfiguration))
+        getPubSubPath(id, pubSubConfiguration))
 
 def getPubSubHeartbeatURI(pubSubConfiguration):
     return "xmpp:%s?pubsub;node=%s" % (pubSubConfiguration['service'],
@@ -1357,8 +1386,8 @@
         self.server = internet.TCPServer(settings["Port"],
             SimpleLineNotificationFactory(self.notifier))
 
-    def enqueue(self, op, uri):
-        self.notifier.enqueue(op, uri)
+    def enqueue(self, op, id):
+        self.notifier.enqueue(op, id)
 
     def startService(self):
         self.server.startService()
@@ -1381,8 +1410,8 @@
             self.client = internet.TCPClient(settings["Host"], settings["Port"],
                 XMPPNotificationFactory(self.notifier, settings))
 
-    def enqueue(self, op, uri):
-        self.notifier.enqueue(op, uri)
+    def enqueue(self, op, id):
+        self.notifier.enqueue(op, id)
 
     def startService(self):
         self.client.startService()

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -67,6 +67,8 @@
 from twistedcaldav.ical import Component as iComponent
 from twistedcaldav.ical import allowedComponents
 from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource
+from twistedcaldav.notify import getPubSubConfiguration, getPubSubPath
+from twistedcaldav.notify import getNodeCacher, NodeCreationException
 from twistedcaldav.sharing import SharedCollectionMixin
 from twistedcaldav.vcard import Component as vComponent
 
@@ -172,12 +174,14 @@
         if self.isCalendarCollection():
             baseProperties += (
                 davxml.ResourceID.qname(),
+                customxml.PubSubXMPPPushKeyProperty.qname(),
             )
 
         if self.isAddressBookCollection():
             baseProperties += (
                 davxml.ResourceID.qname(),
                 carddavxml.SupportedAddressData.qname(),
+                customxml.PubSubXMPPPushKeyProperty.qname(),
             )
 
         if config.EnableSyncReport and (self.isPseudoCalendarCollection() or self.isAddressBookCollection()):
@@ -284,6 +288,28 @@
             qname = property.qname()
 
         isvirt = (yield self.isVirtualShare(request))
+
+        if self.isCalendarCollection() or self.isAddressBookCollection():
+            # Push notification DAV property "pushkey"
+            if qname == customxml.PubSubXMPPPushKeyProperty.qname():
+                pubSubConfiguration = getPubSubConfiguration(config)
+                if (pubSubConfiguration['enabled'] and
+                    getattr(self, "clientNotifier", None) is not None):
+                    label = "collection" if isvirt else "default"
+                    id = self.clientNotifier.getID(label=label)
+                    nodeName = getPubSubPath(id, pubSubConfiguration)
+                    propVal = customxml.PubSubXMPPPushKeyProperty(nodeName)
+                    nodeCacher = getNodeCacher()
+                    try:
+                        (yield nodeCacher.waitForNode(self.clientNotifier, nodeName))
+                    except NodeCreationException:
+                        pass
+                    returnValue(propVal)
+
+                else:
+                    returnValue(customxml.PubSubXMPPPushKeyProperty())
+
+
         if isvirt:
             if self.isShadowableProperty(qname):
                 ownerPrincipal = (yield self.resourceOwnerPrincipal(request))

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -180,7 +180,7 @@
 
     def shareeAccessControlList(self):
 
-        assert self._isVirtualShare, "Only call this fort a virtual share"
+        assert self._isVirtualShare, "Only call this for a virtual share"
 
         # Get the invite for this sharee
         invite = self.invitesDB().recordForInviteUID(self._share.inviteuid)

Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/twistedcaldav/static.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -592,9 +592,6 @@
 
         if hasattr(self, 'clientNotifier'):
             self.clientNotifier.notify(op="update")
-        else:
-            log.debug("%r does not have a clientNotifier but the CTag changed"
-                      % (self,))
 
         return succeed(True)
 
@@ -957,6 +954,7 @@
     def liveProperties(self):
         
         return super(CalendarHomeFile, self).liveProperties() + (
+            (customxml.calendarserver_namespace, "push-transports"),
             (customxml.calendarserver_namespace, "xmpp-uri"),
             (customxml.calendarserver_namespace, "xmpp-heartbeat-uri"),
             (customxml.calendarserver_namespace, "xmpp-server"),
@@ -966,7 +964,11 @@
         """
         @param path: the path to the file which will back the resource.
         """
-        self.clientNotifier = ClientNotifier(self)
+
+        # TODO: when calendar home gets a resourceID( ) method, remove
+        # the "id=record.uid" keyword from this call:
+        self.clientNotifier = ClientNotifier(self, id=record.uid)
+
         CalDAVFile.__init__(self, path)
         DirectoryCalendarHomeResource.__init__(self, parent, record)
 
@@ -1002,16 +1004,19 @@
 
         if cls is not None:
             child = cls(self.fp.child(name).path, self)
-            child.clientNotifier = self.clientNotifier
+            child.clientNotifier = self.clientNotifier.clone(child,
+                label="collection")
             return child
         return self.createSimilarFile(self.fp.child(name).path)
 
     def createSimilarFile(self, path):
+
         if self.comparePath(path):
             return self
         else:
             similar = CalDAVFile(path, principalCollections=self.principalCollections())
-            similar.clientNotifier = self.clientNotifier
+            similar.clientNotifier = self.clientNotifier.clone(similar,
+                label="collection")
             return similar
 
     def getChild(self, name):
@@ -1028,22 +1033,81 @@
         else:
             qname = property.qname()
 
-        def doneWaiting(result, propVal):
-            return propVal
+        if qname == (customxml.calendarserver_namespace, "push-transports"):
+            pubSubConfiguration = getPubSubConfiguration(config)
+            if (pubSubConfiguration['enabled'] and
+                getattr(self, "clientNotifier", None) is not None):
+                    id = self.clientNotifier.getID()
+                    nodeName = getPubSubPath(id, pubSubConfiguration)
+                    children = []
+                    if pubSubConfiguration['aps-bundle-id']:
+                        children.append(
+                            customxml.PubSubTransportProperty(
+                                customxml.PubSubSubscriptionProperty(
+                                    davxml.HRef(
+                                        pubSubConfiguration['subscription-url']
+                                    ),
+                                ),
+                                customxml.PubSubAPSBundleIDProperty(
+                                    pubSubConfiguration['aps-bundle-id']
+                                ),
+                                type="APSD",
+                            )
+                        )
+                    if pubSubConfiguration['xmpp-server']:
+                        children.append(
+                            customxml.PubSubTransportProperty(
+                                customxml.PubSubXMPPServerProperty(
+                                    pubSubConfiguration['xmpp-server']
+                                ),
+                                customxml.PubSubXMPPURIProperty(
+                                    getPubSubXMPPURI(id, pubSubConfiguration)
+                                ),
+                                type="XMPP",
+                            )
+                        )
 
+                    propVal = customxml.PubSubPushTransportsProperty(*children)
+                    nodeCacher = getNodeCacher()
+                    d = nodeCacher.waitForNode(self.clientNotifier, nodeName)
+                    # In either case we're going to return the value
+                    d.addBoth(lambda ignored: propVal)
+                    return d
+
+
+            else:
+                return succeed(customxml.PubSubPushTransportsProperty())
+
+
+        if qname == (customxml.calendarserver_namespace, "pushkey"):
+            pubSubConfiguration = getPubSubConfiguration(config)
+            if pubSubConfiguration['enabled']:
+                if getattr(self, "clientNotifier", None) is not None:
+                    id = self.clientNotifier.getID()
+                    nodeName = getPubSubPath(id, pubSubConfiguration)
+                    propVal = customxml.PubSubXMPPPushKeyProperty(nodeName)
+                    nodeCacher = getNodeCacher()
+                    d = nodeCacher.waitForNode(self.clientNotifier, nodeName)
+                    # In either case we're going to return the xmpp-uri value
+                    d.addBoth(lambda ignored: propVal)
+                    return d
+            else:
+                return succeed(customxml.PubSubXMPPPushKeyProperty())
+
+
+
         if qname == (customxml.calendarserver_namespace, "xmpp-uri"):
             pubSubConfiguration = getPubSubConfiguration(config)
             if pubSubConfiguration['enabled']:
                 if getattr(self, "clientNotifier", None) is not None:
-                    url = self.url()
-                    nodeName = getPubSubPath(url, pubSubConfiguration)
+                    id = self.clientNotifier.getID()
+                    nodeName = getPubSubPath(id, pubSubConfiguration)
                     propVal = customxml.PubSubXMPPURIProperty(
-                        getPubSubXMPPURI(url, pubSubConfiguration))
+                        getPubSubXMPPURI(id, pubSubConfiguration))
                     nodeCacher = getNodeCacher()
                     d = nodeCacher.waitForNode(self.clientNotifier, nodeName)
                     # In either case we're going to return the xmpp-uri value
-                    d.addCallback(doneWaiting, propVal)
-                    d.addErrback(doneWaiting, propVal)
+                    d.addBoth(lambda ignored: propVal)
                     return d
             else:
                 return succeed(customxml.PubSubXMPPURIProperty())
@@ -1466,7 +1530,11 @@
         """
         @param path: the path to the file which will back the resource.
         """
-        self.clientNotifier = ClientNotifier(self)
+
+        # TODO: when addressbook home gets a resourceID( ) method, remove
+        # the "id=record.uid" keyword from this call:
+        self.clientNotifier = ClientNotifier(self, id=record.uid)
+
         CalDAVFile.__init__(self, path)
         DirectoryAddressBookHomeResource.__init__(self, parent, record)
 
@@ -1498,7 +1566,8 @@
 
         if cls is not None:
             child = cls(self.fp.child(name).path, self)
-            child.clientNotifier = self.clientNotifier
+            child.clientNotifier = self.clientNotifier.clone(child,
+                label="collection")
             return child
         return self.createSimilarFile(self.fp.child(name).path)
 
@@ -1507,7 +1576,8 @@
             return self
         else:
             similar = CalDAVFile(path, principalCollections=self.principalCollections())
-            similar.clientNotifier = self.clientNotifier
+            similar.clientNotifier = self.clientNotifier.clone(similar,
+                label="collection")
             return similar
 
     def getChild(self, name):
@@ -1524,22 +1594,18 @@
         else:
             qname = property.qname()
 
-        def doneWaiting(result, propVal):
-            return propVal
-
         if qname == (customxml.calendarserver_namespace, "xmpp-uri"):
             pubSubConfiguration = getPubSubConfiguration(config)
             if pubSubConfiguration['enabled']:
                 if getattr(self, "clientNotifier", None) is not None:
-                    url = self.url()
-                    nodeName = getPubSubPath(url, pubSubConfiguration)
+                    id = self.clientNotifier.getID()
+                    nodeName = getPubSubPath(id, pubSubConfiguration)
                     propVal = customxml.PubSubXMPPURIProperty(
-                        getPubSubXMPPURI(url, pubSubConfiguration))
+                        getPubSubXMPPURI(id, pubSubConfiguration))
                     nodeCacher = getNodeCacher()
                     d = nodeCacher.waitForNode(self.clientNotifier, nodeName)
                     # In either case we're going to return the xmpp-uri value
-                    d.addCallback(doneWaiting, propVal)
-                    d.addErrback(doneWaiting, propVal)
+                    d.addBoth(lambda ignored: propVal)
                     return d
             else:
                 return succeed(customxml.PubSubXMPPURIProperty())
@@ -1623,7 +1689,8 @@
             return self
         else:
             similar = CalDAVFile(path, principalCollections=self.principalCollections())
-            similar.clientNotifier = self.clientNotifier
+            similar.clientNotifier = self.clientNotifier.clone(similar,
+                label="collection")
             return similar
 
 ##

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -453,6 +453,8 @@
                 "JID" : "", # "jid at xmpp.host.name/resource"
                 "Password" : "",
                 "ServiceAddress" : "", # "pubsub.xmpp.host.name"
+                "APSBundleID" : "",
+                "SubscriptionURL" : "",
                 "NodeConfiguration" : {
                     "pubsub#deliver_payloads" : "1",
                     "pubsub#persist_items" : "1",

Modified: CalendarServer/trunk/twistedcaldav/test/test_notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_notify.py	2010-05-13 15:32:43 UTC (rev 5597)
+++ CalendarServer/trunk/twistedcaldav/test/test_notify.py	2010-05-13 21:18:06 UTC (rev 5598)
@@ -25,16 +25,16 @@
 
 class StubResource(object):
 
-    def __init__(self, url):
-        self._url = url
+    def __init__(self, id):
+        self._id = id
 
-    def url(self):
-        return self._url
+    def resourceID(self):
+        return self._id
 
 
 class NotificationClientUserTests(TestCase):
 
-    def test_installNoficationClient(self):
+    def test_installNotificationClient(self):
         self.assertEquals(getNotificationClient(), None)
         self.clock = Clock()
         installNotificationClient(None, None,
@@ -43,13 +43,50 @@
         self.assertNotEquals(notificationClient, None)
 
         enabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
-        enabledConfig.Notifications['Enabled'] = True
-        clientNotifier = ClientNotifier(StubResource("a"),
-            configOverride=enabledConfig)
+        enabledConfig.Notifications["Enabled"] = True
+        resource = StubResource("test")
+        clientNotifier = ClientNotifier(resource, configOverride=enabledConfig)
         clientNotifier.notify()
-        self.assertEquals(notificationClient.lines, ["a"])
+        self.assertEquals(notificationClient.lines, ["test"])
 
+        notificationClient.clear()
+        clientNotifier = clientNotifier.clone(StubResource("sub"), label="alt")
+        clientNotifier.notify()
+        self.assertEquals(notificationClient.lines, ["test", "sub"])
 
+
+class ClientNotifierTests(TestCase):
+
+    def test_clientNotifier(self):
+        enabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
+        enabledConfig.Notifications["Enabled"] = True
+        resource = StubResource("test")
+        subResource = StubResource("sub")
+        clientNotifier = ClientNotifier(resource, configOverride=enabledConfig)
+
+        self.assertEquals(clientNotifier.ids, {"default": (resource, None)})
+        clone = clientNotifier.clone(subResource, label="alt", id="altID")
+        self.assertEquals("altID", clone.getID(label="alt"))
+        self.assertEquals(clone.ids, {
+            "default" : (resource, None),
+            "alt"     : (subResource, "altID"),
+        })
+        self.assertEquals("test", clientNotifier.getID())
+        self.assertEquals(clientNotifier.ids, {
+            "default" : (resource, "test"),
+        })
+        self.assertEquals(None, clientNotifier.getID(label="notthere"))
+
+        resource = StubResource("urn:uuid:foo")
+        clientNotifier = ClientNotifier(resource, configOverride=enabledConfig)
+        self.assertEquals("foo", clientNotifier.getID())
+
+        clientNotifier.disableNotify()
+        self.assertEquals(clientNotifier._notify, False)
+        clientNotifier.enableNotify(None)
+        self.assertEquals(clientNotifier._notify, True)
+
+
 class NotificationClientFactoryTests(TestCase):
 
     def setUp(self):
@@ -76,8 +113,8 @@
         self.lines = []
         self.observers = set()
 
-    def send(self, op, uri):
-        self.lines.append(uri)
+    def send(self, op, id):
+        self.lines.append(id)
 
     def addObserver(self, observer):
         self.observers.add(observer)
@@ -88,6 +125,9 @@
     def connectionMade(self):
         pass
 
+    def clear(self):
+        self.lines = []
+
 class StubNotificationClientProtocol(object):
 
     def __init__(self):
@@ -172,8 +212,8 @@
         self.observers = set()
         self.playbackHistory = []
 
-    def enqueue(self, op, uri):
-        self.notifications.append(uri)
+    def enqueue(self, op, id):
+        self.notifications.append(id)
 
     def playback(self, protocol, old_seq):
         self.playbackHistory.append((protocol, old_seq))
@@ -343,39 +383,39 @@
 class XMPPNotifierTests(TestCase):
 
     xmppEnabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
-    xmppEnabledConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = True
+    xmppEnabledConfig.Notifications["Services"]["XMPPNotifier"]["Enabled"] = True
     xmppEnabledConfig.ServerHostName = "server.example.com"
     xmppEnabledConfig.HTTPPort = 80
 
     xmppDisabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
-    xmppDisabledConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = False
+    xmppDisabledConfig.Notifications["Services"]["XMPPNotifier"]["Enabled"] = False
 
     def setUp(self):
         TestCase.setUp(self)
         self.xmlStream = StubXmlStream()
-        self.settings = { 'ServiceAddress' : 'pubsub.example.com',
-            'NodeConfiguration' : { 'pubsub#deliver_payloads' : '1' },
-            'HeartbeatMinutes' : 30,
+        self.settings = { "ServiceAddress" : "pubsub.example.com",
+            "NodeConfiguration" : { "pubsub#deliver_payloads" : "1" },
+            "HeartbeatMinutes" : 30,
         }
         self.notifier = XMPPNotifier(self.settings, reactor=Clock(),
             configOverride=self.xmppEnabledConfig, heartbeat=False)
         self.notifier.streamOpened(self.xmlStream)
 
     def test_sendWhileConnected(self):
-        self.notifier.enqueue("update", "/principals/__uids__/test")
+        self.notifier.enqueue("update", "test")
 
         iq = self.xmlStream.elements[1]
         self.assertEquals(iq.name, "iq")
 
         pubsubElement = list(iq.elements())[0]
         self.assertEquals(pubsubElement.name, "pubsub")
-        self.assertEquals(pubsubElement.uri, 'http://jabber.org/protocol/pubsub')
+        self.assertEquals(pubsubElement.uri, "http://jabber.org/protocol/pubsub")
 
         publishElement = list(pubsubElement.elements())[0]
         self.assertEquals(publishElement.name, "publish")
-        self.assertEquals(publishElement.uri, 'http://jabber.org/protocol/pubsub')
-        self.assertEquals(publishElement['node'],
-            "/Public/CalDAV/server.example.com/80/principals/__uids__/test/")
+        self.assertEquals(publishElement.uri, "http://jabber.org/protocol/pubsub")
+        self.assertEquals(publishElement["node"],
+            "/DAV/server.example.com/test/")
 
     def test_sendWhileNotConnected(self):
         notifier = XMPPNotifier(self.settings, reactor=Clock(),
@@ -389,39 +429,39 @@
         self.assertEquals(iq.name, "iq")
 
     def test_publishReponse400(self):
-        failure = StubFailure(StanzaError('bad-request'))
+        failure = StubFailure(StanzaError("bad-request"))
         self.assertEquals(len(self.xmlStream.elements), 1)
         self.notifier.publishNodeFailure(failure, "testNodeName")
         self.assertEquals(len(self.xmlStream.elements), 2)
         iq = self.xmlStream.elements[1]
         self.assertEquals(iq.name, "iq")
-        self.assertEquals(iq['type'], "get")
+        self.assertEquals(iq["type"], "get")
 
         pubsubElement = list(iq.elements())[0]
         self.assertEquals(pubsubElement.name, "pubsub")
         self.assertEquals(pubsubElement.uri,
-            'http://jabber.org/protocol/pubsub#owner')
+            "http://jabber.org/protocol/pubsub#owner")
         configElement = list(pubsubElement.elements())[0]
         self.assertEquals(configElement.name, "configure")
-        self.assertEquals(configElement['node'], "testNodeName")
+        self.assertEquals(configElement["node"], "testNodeName")
 
 
     def test_publishReponse404(self):
         self.assertEquals(len(self.xmlStream.elements), 1)
-        failure = StubFailure(StanzaError('item-not-found'))
+        failure = StubFailure(StanzaError("item-not-found"))
         self.notifier.publishNodeFailure(failure, "testNodeName")
         self.assertEquals(len(self.xmlStream.elements), 2)
         iq = self.xmlStream.elements[1]
         self.assertEquals(iq.name, "iq")
-        self.assertEquals(iq['type'], "set")
+        self.assertEquals(iq["type"], "set")
 
         pubsubElement = list(iq.elements())[0]
         self.assertEquals(pubsubElement.name, "pubsub")
         self.assertEquals(pubsubElement.uri,
-            'http://jabber.org/protocol/pubsub')
+            "http://jabber.org/protocol/pubsub")
         createElement = list(pubsubElement.elements())[0]
         self.assertEquals(createElement.name, "create")
-        self.assertEquals(createElement['node'], "testNodeName")
+        self.assertEquals(createElement["node"], "testNodeName")
 
 
     def test_configureResponse(self):
@@ -432,11 +472,11 @@
                     return child
             return None
 
-        response = IQ(self.xmlStream, type='result')
-        pubsubElement = response.addElement('pubsub')
-        configElement = pubsubElement.addElement('configure')
-        formElement = configElement.addElement('x')
-        formElement['type'] = 'form'
+        response = IQ(self.xmlStream, type="result")
+        pubsubElement = response.addElement("pubsub")
+        configElement = pubsubElement.addElement("configure")
+        formElement = configElement.addElement("x")
+        formElement["type"] = "form"
         fields = [
             ( "unknown", "don't edit me", "text-single" ),
             ( "pubsub#deliver_payloads", "0", "boolean" ),
@@ -449,9 +489,9 @@
         }
         for field in fields:
             fieldElement = formElement.addElement("field")
-            fieldElement['var'] = field[0]
-            fieldElement['type'] = field[2]
-            fieldElement.addElement('value', content=field[1])
+            fieldElement["var"] = field[0]
+            fieldElement["type"] = field[2]
+            fieldElement.addElement("value", content=field[1])
 
         self.assertEquals(len(self.xmlStream.elements), 1)
         self.notifier.requestConfigurationFormSuccess(response, "testNodeName",
@@ -460,35 +500,35 @@
 
         iq = self.xmlStream.elements[1]
         self.assertEquals(iq.name, "iq")
-        self.assertEquals(iq['type'], "set")
+        self.assertEquals(iq["type"], "set")
 
         pubsubElement = list(iq.elements())[0]
         self.assertEquals(pubsubElement.name, "pubsub")
         configElement = list(pubsubElement.elements())[0]
         self.assertEquals(configElement.name, "configure")
-        self.assertEquals(configElement['node'], "testNodeName")
+        self.assertEquals(configElement["node"], "testNodeName")
         formElement = list(configElement.elements())[0]
-        self.assertEquals(formElement['type'], "submit")
+        self.assertEquals(formElement["type"], "submit")
         for field in formElement.elements():
             valueElement = _getChild(field, "value")
             if valueElement is not None:
-                self.assertEquals(expectedFields[field['var']],
+                self.assertEquals(expectedFields[field["var"]],
                     str(valueElement))
 
 
     def test_sendHeartbeat(self):
 
         xmppConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
-        xmppConfig.Notifications['Services']['XMPPNotifier']['Enabled'] = True
+        xmppConfig.Notifications["Services"]["XMPPNotifier"]["Enabled"] = True
         xmppConfig.ServerHostName = "server.example.com"
         xmppConfig.HTTPPort = 80
 
         clock = Clock()
         xmlStream = StubXmlStream()
-        settings = { 'ServiceAddress' : 'pubsub.example.com', 'JID' : 'jid',
-            'Password' : 'password', 'KeepAliveSeconds' : 5,
-            'NodeConfiguration' : { 'pubsub#deliver_payloads' : "1" },
-            'HeartbeatMinutes' : 30 }
+        settings = { "ServiceAddress" : "pubsub.example.com", "JID" : "jid",
+            "Password" : "password", "KeepAliveSeconds" : 5,
+            "NodeConfiguration" : { "pubsub#deliver_payloads" : "1" },
+            "HeartbeatMinutes" : 30 }
         notifier = XMPPNotifier(settings, reactor=clock, heartbeat=True,
             roster=False, configOverride=xmppConfig)
         factory = XMPPNotificationFactory(notifier, settings, reactor=clock,
@@ -498,13 +538,13 @@
 
         self.assertEquals(len(xmlStream.elements), 1)
         heartbeat = xmlStream.elements[0]
-        self.assertEquals(heartbeat.name, 'iq')
+        self.assertEquals(heartbeat.name, "iq")
 
         clock.advance(1800)
 
         self.assertEquals(len(xmlStream.elements), 2)
         heartbeat = xmlStream.elements[1]
-        self.assertEquals(heartbeat.name, 'iq')
+        self.assertEquals(heartbeat.name, "iq")
 
         factory.disconnected(xmlStream)
         clock.advance(1800)
@@ -517,9 +557,9 @@
     def test_sendPresence(self):
         clock = Clock()
         xmlStream = StubXmlStream()
-        settings = { 'ServiceAddress' : 'pubsub.example.com', 'JID' : 'jid',
-            'NodeConfiguration' : { 'pubsub#deliver_payloads' : "1" },
-            'Password' : 'password', 'KeepAliveSeconds' : 5 }
+        settings = { "ServiceAddress" : "pubsub.example.com", "JID" : "jid",
+            "NodeConfiguration" : { "pubsub#deliver_payloads" : "1" },
+            "Password" : "password", "KeepAliveSeconds" : 5 }
         notifier = XMPPNotifier(settings, reactor=clock, heartbeat=False)
         factory = XMPPNotificationFactory(notifier, settings, reactor=clock)
         factory.connected(xmlStream)
@@ -527,15 +567,15 @@
 
         self.assertEquals(len(xmlStream.elements), 2)
         presence = xmlStream.elements[0]
-        self.assertEquals(presence.name, 'presence')
+        self.assertEquals(presence.name, "presence")
         iq = xmlStream.elements[1]
-        self.assertEquals(iq.name, 'iq')
+        self.assertEquals(iq.name, "iq")
 
         clock.advance(5)
 
         self.assertEquals(len(xmlStream.elements), 3)
         presence = xmlStream.elements[2]
-        self.assertEquals(presence.name, 'presence')
+        self.assertEquals(presence.name, "presence")
 
         factory.disconnected(xmlStream)
         clock.advance(5)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100513/6b1de7d4/attachment-0001.html>


More information about the calendarserver-changes mailing list