[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