Diff
Modified: CalendarServer/trunk/twistedcaldav/directory/calendar.py (852 => 853)
--- CalendarServer/trunk/twistedcaldav/directory/calendar.py 2006-12-19 02:48:43 UTC (rev 852)
+++ CalendarServer/trunk/twistedcaldav/directory/calendar.py 2006-12-19 02:50:03 UTC (rev 853)
@@ -34,6 +34,7 @@
from twistedcaldav.config import config
from twistedcaldav.dropbox import DropBoxHomeResource
from twistedcaldav.extensions import ReadOnlyResourceMixIn, DAVResource
+from twistedcaldav.notifications import NotificationsCollectionResource
from twistedcaldav.resource import CalDAVResource
from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
from twistedcaldav.directory.idirectory import IDirectoryService
@@ -188,8 +189,11 @@
if config.DropBoxEnabled:
childlist += (
("dropbox" , DropBoxHomeResource ),
- #("notifications", NotificationsHomeResource),
)
+ if config.NotificationsEnabled:
+ childlist += (
+ ("notifications", NotificationsCollectionResource),
+ )
for name, cls in childlist:
child = self.provisionChild(name)
assert isinstance(child, cls), "Child %r is not a %s: %r" % (name, cls.__name__, child)
Modified: CalendarServer/trunk/twistedcaldav/dropbox.py (852 => 853)
--- CalendarServer/trunk/twistedcaldav/dropbox.py 2006-12-19 02:48:43 UTC (rev 852)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py 2006-12-19 02:50:03 UTC (rev 853)
@@ -23,19 +23,25 @@
__all__ = [
"DropBoxHomeResource",
+ "DropBoxCollectionResource",
+ "DropBoxChildResource",
]
+import datetime
+import md5
+import time
+
from twisted.internet.defer import deferredGenerator, waitForDeferred
from twisted.python import log
from twisted.web2 import responsecode
from twisted.web2.dav import davxml
from twisted.web2.dav.http import HTTPError, ErrorResponse
-from twisted.web2.dav.resource import DAVResource, TwistedACLInheritable
+from twisted.web2.dav.resource import DAVResource, DAVPrincipalResource, TwistedACLInheritable
from twisted.web2.dav.util import parentForURL
from twistedcaldav import customxml
+from twistedcaldav.config import config
from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.notifications import Notification
class DropBoxHomeResource (DAVResource):
"""
@@ -80,25 +86,104 @@
# Do inherited with possibly modified set of aces
super(DropBoxCollectionResource, self).writeNewACEs(edited_aces)
+ def doNotification(self, request, myURI):
+ """
+
+ @param childURI: URI of the child that changed and triggered the notification.
+ """
+ # First determine which principals should get notified
+ #
+ # Procedure:
+ #
+ # 1. Get the list of auto-subscribed principals from the parent collection property.
+ # 2. Expand any group principals in the list into their user principals.
+ # 3. Get the list of unsubscribed principals from the parent collection property.
+ # 4. Expand any group principals in the list into their user principals.
+ # 5. Generate a set from the difference between the subscribed list and unsubscribed list.
+
+ def _expandPrincipals(principals):
+ result = []
+ for principal in principals:
+
+ principal = waitForDeferred(self.resolvePrincipal(principal.children[0], request))
+ yield principal
+ principal = principal.getResult()
+ if principal is None:
+ continue
+
+ presource = waitForDeferred(request.locateResource(str(principal)))
+ yield presource
+ presource = presource.getResult()
+
+ if not isinstance(presource, DAVPrincipalResource):
+ continue
+
+ # Step 2. Expand groups.
+ members = presource.groupMembers()
+
+ if members:
+ for member in members:
+ result.append(davxml.Principal(davxml.HRef.fromString(member)))
+ else:
+ result.append(davxml.Principal(principal))
+ yield result
+
+ _expandPrincipals = deferredGenerator(_expandPrincipals)
+
+ # For drop box we look at the parent collection of the target resource and get the
+ # set of subscribed principals.
+ if not config.NotificationsEnabled or not self.hasDeadProperty(customxml.Subscribed):
+ yield None
+ return
+
+ principals = set()
+ autosubs = self.readDeadProperty(customxml.Subscribed).children
+ d = waitForDeferred(_expandPrincipals(autosubs))
+ yield d
+ autosubs = d.getResult()
+ principals.update(autosubs)
+
+ for principal in principals:
+ if not isinstance(principal.children[0], davxml.HRef):
+ continue
+ purl = str(principal.children[0])
+ d = waitForDeferred(request.locateResource(purl))
+ yield d
+ presource = d.getResult()
+
+ collectionURL = presource.notificationsURL()
+ if collectionURL is None:
+ continue
+ d = waitForDeferred(request.locateResource(collectionURL))
+ yield d
+ collection = d.getResult()
+
+ name = "%s.xml" % (md5.new(str(self) + str(time.time()) + collectionURL).hexdigest(),)
+
+ # Create new resource in the collection
+ d = waitForDeferred(request.locateChildResource(collection, name)) # This ensures the URI for the resource is mapped
+ yield d
+ child = d.getResult()
+
+ d = waitForDeferred(child.create(request, datetime.datetime.utcnow(), myURI))
+ yield d
+ d.getResult()
+
+ doNotification = deferredGenerator(doNotification)
+
def http_DELETE(self, request):
#
- # Handle notificiations
+ # Handle notification of this drop box collection being deleted
#
- parentURL=parentForURL(request.uri)
- def gotParent(parent):
- def gotResponse(response):
- notification = Notification(parentURL=parentURL)
- d = notification.doNotification(request, parent)
+ def gotResponse(response):
+ if response in (responsecode.OK, responsecode.NO_CONTENT):
+ d = self.doNotification(request, request.uri)
d.addCallback(lambda _: response)
- return d
-
- d = super(DropBoxCollectionResource, self).http_DELETE(request)
- d.addCallback(gotResponse)
return d
- d = request.locateResource(parentURL)
- d.addCallback(gotParent)
+ d = super(DropBoxCollectionResource, self).http_DELETE(request)
+ d.addCallback(gotResponse)
return d
def http_PUT(self, request):
@@ -190,8 +275,7 @@
def gotParent(parent):
def gotResponse(response):
if response.code in (responsecode.OK, responsecode.CREATED, responsecode.NO_CONTENT):
- notification = Notification(parentURL=parentForURL(request.uri))
- d = notification.doNotification(request, parent)
+ d = parent.doNotification(request, parentURL)
d.addCallback(lambda _: response)
return d
@@ -202,3 +286,25 @@
d = request.locateResource(parentURL)
d.addCallback(gotParent)
return d
+
+ def http_DELETE(self, request):
+ #
+ # Handle notificiations
+ #
+ parentURL=parentForURL(request.uri)
+
+ def gotParent(parent):
+ def gotResponse(response):
+ if response in (responsecode.OK, responsecode.NO_CONTENT):
+ d = parent.doNotification(request, parentURL)
+ d.addCallback(lambda _: response)
+ return d
+
+ d = super(DropBoxChildResource, self).http_DELETE(request)
+ d.addCallback(gotResponse)
+ return d
+
+ d = request.locateResource(parentURL)
+ d.addCallback(gotParent)
+ return d
+
Modified: CalendarServer/trunk/twistedcaldav/notifications.py (852 => 853)
--- CalendarServer/trunk/twistedcaldav/notifications.py 2006-12-19 02:48:43 UTC (rev 852)
+++ CalendarServer/trunk/twistedcaldav/notifications.py 2006-12-19 02:50:03 UTC (rev 853)
@@ -16,144 +16,53 @@
# DRI: Cyrus Daboo, cdaboo@apple.com
##
-from twisted.internet.defer import deferredGenerator
-from twisted.internet.defer import waitForDeferred
-from twisted.web2.dav.method import put_common
-from twisted.web2.dav.resource import DAVPrincipalResource
-from twisted.web2.dav import davxml
-from twistedcaldav import customxml
-from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.extensions import DAVFile
-from twistedcaldav.extensions import DAVResource
-
-import datetime
-import md5
-import os
-import time
-
"""
Implements collection change notification functionality. Any change to the contents of a collection will
result in a notification resource deposited into subscriber's notifications collection.
"""
__all__ = [
- "Notification",
+ "NotificationCollectionResource",
"NotificationResource",
- "NotificationFile",
]
-class Notification(object):
- """
- Encapsulates a notification message.
- """
-
- def __init__(self, parentURL):
- self.timestamp = datetime.datetime.utcnow()
- self.parentURL = parentURL
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.http import ErrorResponse
- def doNotification(self, request, parent):
- """
- Put the supplied notification into the notification collection of the specified principal.
-
- @param request: L{Request} for request in progress.
- @param parent: L{DAVResource} for parent of resource trigerring the notification.
- """
-
- # First determine which principals should get notified
- #
- # Procedure:
- #
- # 1. Get the list of auto-subscribed principals from the parent collection property.
- # 2. Expand any group principals in the list into their user principals.
- # 3. Get the list of unsubscribed principals from the parent collection property.
- # 4. Expand any group principals in the list into their user principals.
- # 5. Generate a set from the difference between the subscribed list and unsubscribed list.
-
- def _expandPrincipals(principals):
- result = []
- for principal in principals:
+from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.extensions import DAVResource
- principal = waitForDeferred(parent.resolvePrincipal(principal.children[0], request))
- yield principal
- principal = principal.getResult()
- if principal is None:
- continue
-
- presource = waitForDeferred(request.locateResource(str(principal)))
- yield presource
- presource = presource.getResult()
-
- if not isinstance(presource, DAVPrincipalResource):
- continue
-
- # Step 2. Expand groups.
- members = presource.groupMembers()
-
- if members:
- for member in members:
- result.append(davxml.Principal(davxml.HRef.fromString(member)))
- else:
- result.append(davxml.Principal(principal))
- yield result
+class NotificationsCollectionResource (DAVResource):
+ def resourceType(self):
+ return davxml.ResourceType.notifications
- _expandPrincipals = deferredGenerator(_expandPrincipals)
+ def isCollection(self):
+ return True
- # For drop box we look at the parent collection of the target resource and get the
- # set of subscribed principals.
- if not parent.hasDeadProperty(customxml.Subscribed):
- yield None
- return
+ def notify(self):
+ # FIXME: Move doNotification() logic from above class to here
+ pass
- principals = set()
- autosubs = parent.readDeadProperty(customxml.Subscribed).children
- d = waitForDeferred(_expandPrincipals(autosubs))
- yield d
- autosubs = d.getResult()
- principals.update(autosubs)
-
- for principal in principals:
- if not isinstance(principal.children[0], davxml.HRef):
- continue
- purl = str(principal.children[0])
- d = waitForDeferred(request.locateResource(purl))
- yield d
- presource = d.getResult()
+ def http_PUT(self, request):
+ return ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "notifications-collection-no-client-resources")
+ )
- collectionURL = presource.notificationsURL()
- if collectionURL is None:
- continue
- d = waitForDeferred(request.locateResource(collectionURL))
- yield d
- collection = d.getResult()
+ def http_MKCOL (self, request):
+ return ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "notifications-collection-no-client-resources")
+ )
- name = "%s.xml" % (md5.new(str(self) + str(time.time()) + collectionURL).hexdigest(),)
- path = os.path.join(collection.fp.path, name)
-
- # Create new resource in the collection
- child = NotificationFile(path=path)
- collection.putChild(name, child)
- d = waitForDeferred(request.locateChildResource(collection, name)) # This ensures the URI for the resource is mapped
- yield d
- child = d.getResult()
-
- d = waitForDeferred(child.create(request, self))
- yield d
- d.getResult()
-
- doNotification = deferredGenerator(doNotification)
-
-class NotificationCollectionResource (DAVResource):
- def resourceType(self):
- return davxml.ResourceType(
- davxml.ResourceType.collection,
- davxml.ResourceType.notifications,
+ def http_MKCALENDAR (self, request):
+ return ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "notifications-collection-no-client-resources")
)
- def notify(self):
- # FIXME: Move doNotification() logic from above class to here
- pass
-
class NotificationResource(DAVResource):
"""
Resource that gets stored in a notification collection and which contains
@@ -163,29 +72,3 @@
(calendarserver_namespace, "time-stamp"),
(calendarserver_namespace, "changed" ),
)
-
-# FIXME: This needs to be in static.py, but it's referred to in doNotification() above, which is probably incorrect.
-class NotificationFile(NotificationResource, DAVFile):
- def __init__(self, path):
- super(NotificationFile, self).__init__(path)
-
- def create(self, request, notification):
- """
- Create the resource, fill out the body, and add properties.
- """
- # Create body XML
- elements = []
- elements.append(customxml.TimeStamp.fromString(notification.timestamp))
- elements.append(customxml.Changed(davxml.HRef.fromString(notification.parentURL)))
-
- xml = customxml.Notification(*elements)
-
- d = waitForDeferred(put_common.storeResource(request, data=xml.toxml(), destination=self, destination_uri=request.urlForResource(self)))
- yield d
- d.getResult()
-
- # Write properties
- for element in elements:
- self.writeDeadProperty(element)
-
- create = deferredGenerator(create)
Modified: CalendarServer/trunk/twistedcaldav/static.py (852 => 853)
--- CalendarServer/trunk/twistedcaldav/static.py 2006-12-19 02:48:43 UTC (rev 852)
+++ CalendarServer/trunk/twistedcaldav/static.py 2006-12-19 02:50:03 UTC (rev 853)
@@ -29,6 +29,8 @@
"DropBoxHomeFile",
"DropBoxCollectionFile",
"DropBoxChildFile",
+ "NotificationsCollectionFile",
+ "NotificationFile",
]
import os
@@ -43,6 +45,7 @@
from twisted.web2.dav.fileop import mkcollection, rmdir
from twisted.web2.dav.http import ErrorResponse
from twisted.web2.dav.idav import IDAVResource
+from twisted.web2.dav.method import put_common as put_common_base
from twisted.web2.dav.resource import davPrivilegeSet
from twisted.web2.dav.util import parentForURL, bindMethods
@@ -53,6 +56,7 @@
from twistedcaldav.ical import Component as iComponent
from twistedcaldav.ical import Property as iProperty
from twistedcaldav.index import Index, IndexSchedule, db_basename
+from twistedcaldav.notifications import NotificationsCollectionResource, NotificationResource
from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource, DropBoxChildResource
@@ -447,16 +451,18 @@
def provisionChild(self, name):
if config.DropBoxEnabled:
DropBoxHomeFileClass = DropBoxHomeFile
- #NotificationsCollectionFileClass = NotificationsCollectionFile
else:
DropBoxHomeFileClass = None
- #NotificationsCollectionFileClass = None
-
+ if config.NotificationsEnabled:
+ NotificationsCollectionFileClass = NotificationsCollectionFile
+ else:
+ NotificationsCollectionFileClass = None
+
cls = {
"inbox" : ScheduleInboxFile,
"outbox" : ScheduleOutboxFile,
"dropbox" : DropBoxHomeFileClass,
- #"notifications": NotificationsCollectionFileClass,
+ "notifications": NotificationsCollectionFileClass,
}.get(name, None)
if cls is not None:
@@ -550,6 +556,9 @@
else:
return DropBoxCollectionFile(path, self)
+ def __repr__(self):
+ return "<%s (dropbox home collection): %s>" % (self.__class__.__name__, self.fp.path)
+
class DropBoxCollectionFile (DropBoxCollectionResource, CalDAVFile):
def __init__(self, path, parent):
DropBoxCollectionResource.__init__(self)
@@ -561,6 +570,9 @@
else:
return DropBoxChildFile(path, self)
+ def __repr__(self):
+ return "<%s (dropbox collection): %s>" % (self.__class__.__name__, self.fp.path)
+
http_DELETE = DropBoxCollectionResource.http_DELETE
http_PUT = DropBoxCollectionResource.http_PUT
http_MKCALENDAR = DropBoxCollectionResource.http_MKCALENDAR
@@ -584,6 +596,50 @@
http_MKCALENDAR = DropBoxChildResource.http_MKCALENDAR
http_PUT = DropBoxChildResource.http_PUT
+class NotificationsCollectionFile (AutoProvisioningFileMixIn, NotificationsCollectionResource, CalDAVFile):
+ def __init__(self, path, parent):
+ NotificationsCollectionResource.__init__(self)
+ CalDAVFile.__init__(self, path, principalCollections=parent.principalCollections())
+ self._parent = parent
+
+ def createSimilarFile(self, path):
+ if path == self.fp.path:
+ return self
+ else:
+ return NotificationFile(path, self)
+
+ def __repr__(self):
+ return "<%s (notifications collection): %s>" % (self.__class__.__name__, self.fp.path)
+
+ http_PUT = NotificationsCollectionResource.http_PUT
+ http_MKCOL = NotificationsCollectionResource.http_MKCOL
+ http_MKCALENDAR = NotificationsCollectionResource.http_MKCALENDAR
+
+class NotificationFile(NotificationResource, DAVFile):
+ def __init__(self, path, parent):
+ super(NotificationFile, self).__init__(path, principalCollections=parent.principalCollections())
+
+ def create(self, request, timestamp, parentURL):
+ """
+ Create the resource, fill out the body, and add properties.
+ """
+ # Create body XML
+ elements = []
+ elements.append(customxml.TimeStamp.fromString(timestamp))
+ elements.append(customxml.Changed(davxml.HRef.fromString(parentURL)))
+
+ xml = customxml.Notification(*elements)
+
+ d = waitForDeferred(put_common_base.storeResource(request, data=xml.toxml(), destination=self, destination_uri=request.urlForResource(self)))
+ yield d
+ d.getResult()
+
+ # Write properties
+ for element in elements:
+ self.writeDeadProperty(element)
+
+ create = deferredGenerator(create)
+
##
# Utilities
##