[CalendarServer-changes] [472] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed Nov 15 12:33:55 PST 2006
Revision: 472
http://trac.macosforge.org/projects/calendarserver/changeset/472
Author: cdaboo at apple.com
Date: 2006-11-15 12:33:54 -0800 (Wed, 15 Nov 2006)
Log Message:
-----------
svn merge -r288:450 https://svn.opensource.apple.com/repository/calendarserver/CalendarServer/branches/users/cdaboo/dropbox.
Modified Paths:
--------------
CalendarServer/trunk/bin/caldavd
CalendarServer/trunk/conf/caldavd-dev.plist
CalendarServer/trunk/conf/caldavd.plist
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch
CalendarServer/trunk/twistedcaldav/__init__.py
CalendarServer/trunk/twistedcaldav/caldavxml.py
CalendarServer/trunk/twistedcaldav/customxml.py
CalendarServer/trunk/twistedcaldav/icaldav.py
CalendarServer/trunk/twistedcaldav/method/__init__.py
CalendarServer/trunk/twistedcaldav/method/delete.py
CalendarServer/trunk/twistedcaldav/method/mkcol.py
CalendarServer/trunk/twistedcaldav/method/put.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/repository.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/static.py
Added Paths:
-----------
CalendarServer/trunk/twistedcaldav/dropbox.py
CalendarServer/trunk/twistedcaldav/method/x_apple_subscribe.py
CalendarServer/trunk/twistedcaldav/method/x_apple_unsubscribe.py
CalendarServer/trunk/twistedcaldav/notifications.py
Modified: CalendarServer/trunk/bin/caldavd
===================================================================
--- CalendarServer/trunk/bin/caldavd 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/bin/caldavd 2006-11-15 20:33:54 UTC (rev 472)
@@ -65,6 +65,12 @@
self.certfile = "/etc/certificates/Default.crt"
self.manhole = 0
+ self.dropbox = True
+ self.dropboxName = "dropbox"
+ self.dropboxACLs = True
+ self.notifications = False
+ self.notifcationName = "notifications"
+
self.serverlogfile = "/var/log/caldavd/server.log"
self.errorlogfile = "/var/log/caldavd/error.log"
self.pidfile = "/var/run/caldavd.pid"
@@ -96,6 +102,11 @@
print "Only Use SSL: %s" % (self.onlyssl,)
print "SSL Private Key File: %s" % (self.keyfile,)
print "SSL Certificate File: %s" % (self.certfile,)
+ print "Drop Box Enabled: %s" % (self.dropbox,)
+ print "Drop Box Name: %s" % (self.dropboxName,)
+ print "Drop Box ACLs are Inherited %s" % (self.dropboxACLs,)
+ print "Notifications Enabled: %s" % (self.notifications,)
+ print "Notification Collection Name: %s" % (self.notifcationName,)
print "Server Log File: %s" % (self.serverlogfile,)
print "Error Log File: %s" % (self.errorlogfile,)
print "PID File: %s" % (self.pidfile,)
@@ -259,6 +270,8 @@
self.action = args[0]
def parsePlist(self):
+ print "Reading configuration file %s." % (self.plistfile,)
+
root = readPlist(self.plistfile)
# dict that maps between plist keys and class attributes
@@ -273,6 +286,11 @@
"SSLPrivateKey": "keyfile",
"SSLCertificate": "certfile",
"ManholePort": "manhole",
+ "DropBoxEnabled": "dropbox",
+ "DropBoxName": "dropboxName",
+ "DropBoxInheritedACLs": "dropboxACLs",
+ "NotificationsEnabled": "notifications",
+ "NotificationCollectionName": "notifcationName",
"ServerLogFile": "serverlogfile",
"ErrorLogFile": "errorlogfile",
"PIDFile": "pidfile",
@@ -336,40 +354,69 @@
def generateTAC(self):
return """
-docroot = "%(docroot)s"
-repo = "%(repo)s"
-doacct = %(doacct)s
-doacl = %(doacl)s
-dossl = %(dossl)s
-keyfile = "%(keyfile)s"
-certfile = "%(certfile)s"
-onlyssl = %(onlyssl)s
-port = %(port)d
-sslport = %(sslport)d
-maxsize = %(maxsize)d
-quota = %(quota)d
-serverlogfile = "%(serverlogfile)s"
-manhole = %(manhole)d
+docroot = "%(docroot)s"
+repo = "%(repo)s"
+doacct = %(doacct)s
+doacl = %(doacl)s
+dossl = %(dossl)s
+keyfile = "%(keyfile)s"
+certfile = "%(certfile)s"
+onlyssl = %(onlyssl)s
+port = %(port)d
+sslport = %(sslport)d
+maxsize = %(maxsize)d
+quota = %(quota)d
+serverlogfile = "%(serverlogfile)s"
+dropbox = "%(dropbox)s"
+dropboxName = "%(dropboxName)s"
+dropboxACLs = "%(dropboxACLs)s"
+notifications = "%(notifications)s"
+notifcationName = "%(notifcationName)s"
+manhole = %(manhole)d
from twistedcaldav.repository import startServer
-application, site = startServer(docroot, repo, doacct, doacl, dossl, keyfile, certfile, onlyssl, port, sslport, maxsize, quota, serverlogfile, manhole)
+application, site = startServer(docroot,
+ repo,
+ doacct,
+ doacl,
+ dossl,
+ keyfile,
+ certfile,
+ onlyssl,
+ port,
+ sslport,
+ maxsize,
+ quota,
+ serverlogfile,
+ dropbox,
+ dropboxName,
+ dropboxACLs,
+ notifications,
+ notifcationName,
+ manhole)
""" % {
- "docroot": self.docroot,
- "repo": self.repo,
- "doacct": self.doacct,
- "doacl": self.doacl,
- "dossl": self.dossl,
- "keyfile": self.keyfile,
- "certfile": self.certfile,
- "onlyssl": self.onlyssl,
- "port": self.port,
- "sslport": self.sslport,
- "maxsize": self.maxsize,
- "quota": self.quota,
- "serverlogfile": self.serverlogfile,
- "manhole": self.manhole,
+ "docroot": self.docroot,
+ "repo": self.repo,
+ "doacct": self.doacct,
+ "doacl": self.doacl,
+ "dossl": self.dossl,
+ "keyfile": self.keyfile,
+ "certfile": self.certfile,
+ "onlyssl": self.onlyssl,
+ "port": self.port,
+ "sslport": self.sslport,
+ "maxsize": self.maxsize,
+ "quota": self.quota,
+ "serverlogfile": self.serverlogfile,
+ "dropbox": self.dropbox,
+ "dropboxName": self.dropboxName,
+ "dropboxACLs": self.dropboxACLs,
+ "notifications": self.notifications,
+ "notifcationName": self.notifcationName,
+ "manhole": self.manhole,
+
}
if __name__ == "__main__":
Modified: CalendarServer/trunk/conf/caldavd-dev.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-dev.plist 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/conf/caldavd-dev.plist 2006-11-15 20:33:54 UTC (rev 472)
@@ -65,6 +65,21 @@
<key>ResetAccountACLs</key>
<true/>
+ <key>DropBoxEnabled</key>
+ <true/>
+
+ <key>DropBoxName</key>
+ <string>dropbox</string>
+
+ <key>DropBoxInheritedACLs</key>
+ <true/>
+
+ <key>NotificationsEnabled</key>
+ <true/>
+
+ <key>NotificationCollectionName</key>
+ <string>notifications</string>
+
<key>twistdLocation</key>
<string>../Twisted/bin/twistd</string>
Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/conf/caldavd.plist 2006-11-15 20:33:54 UTC (rev 472)
@@ -65,6 +65,21 @@
<key>ResetAccountACLs</key>
<true/>
+ <key>DropBoxEnabled</key>
+ <true/>
+
+ <key>DropBoxName</key>
+ <string>dropbox</string>
+
+ <key>DropBoxInheritedACLs</key>
+ <true/>
+
+ <key>NotificationsEnabled</key>
+ <true/>
+
+ <key>NotificationCollectionName</key>
+ <string>notifications</string>
+
<key>twistdLocation</key>
<string>/usr/share/caldavd/bin/twistd</string>
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch 2006-11-15 20:33:54 UTC (rev 472)
@@ -10,7 +10,7 @@
#
# Register all XML elements with the parser
-@@ -56,6 +57,7 @@
+@@ -56,11 +57,13 @@
import twisted.web2.dav.element.rfc2518
import twisted.web2.dav.element.rfc3253
import twisted.web2.dav.element.rfc3744
@@ -18,7 +18,6 @@
__all__ = (
registerElements(twisted.web2.dav.element.base ) +
-@@ -62,5 +64,6 @@
registerElements(twisted.web2.dav.element.parser ) +
registerElements(twisted.web2.dav.element.rfc2518) +
registerElements(twisted.web2.dav.element.rfc3253) +
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch 2006-11-15 20:33:54 UTC (rev 472)
@@ -25,7 +25,7 @@
+ def writeToStream(self, output, ns, level, pretty):
+ """
+ Fast XML output.
-+
+
+ @param output: C{stream} to write to.
+ @param ns: C{str} containing the namespace of the enclosing element.
+ @param level: C{int} containing the element nesting level (starts at 0).
@@ -84,7 +84,7 @@
+
+ if pretty and level:
+ output.write("\r\n")
-
++
+ def writeAttributeToStream(self, output, name, value):
+
+ # Quote any single quotes. We do not need to be any smarter than this.
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch 2006-11-15 20:33:54 UTC (rev 472)
@@ -22,7 +22,7 @@
yield x
- yield x.getResult()
+ result = x.getResult()
-+
+
+ # Adjust quota
+ if myquota is not None:
+ d = waitForDeferred(self.quotaSizeAdjust(request, -old_size))
@@ -30,5 +30,5 @@
+ d.getResult()
+
+ yield result
-
++
http_DELETE = deferredGenerator(http_DELETE)
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch 2006-11-15 20:33:54 UTC (rev 472)
@@ -35,7 +35,7 @@
return d
def ifAllowed(privileges, callback):
-@@ -286,6 +296,32 @@
+@@ -286,7 +296,33 @@
d.addCallback(gotACL)
return d
return ifAllowed((davxml.ReadACL(),), callback)
@@ -52,7 +52,7 @@
+ d = self.quota(request)
+ d.addCallback(callback)
+ return d
-+
+
+ if name == "quota-used-bytes":
+ def callback(qvalue):
+ if qvalue is None:
@@ -65,10 +65,11 @@
+ d = self.quota(request)
+ d.addCallback(callback)
+ return d
-
++
elif namespace == twisted_dav_namespace:
if name == "resource-class":
-@@ -366,6 +402,18 @@
+ class ResourceClass (davxml.WebDAVTextElement):
+@@ -366,12 +402,26 @@
# FIXME: A set would be better here, that that's a python 2.4+ feature.
qnames = list(self.liveProperties)
@@ -87,17 +88,15 @@
for qname in self.deadProperties().list():
if (qname not in qnames) and (qname[0] != twisted_private_namespace):
qnames.append(qname)
-@@ -370,7 +418,9 @@
- if (qname not in qnames) and (qname[0] != twisted_private_namespace):
- qnames.append(qname)
- return succeed(qnames)
+ yield qnames
-+
-+ listProperties = deferredGenerator(listProperties)
++ listProperties = deferredGenerator(listProperties)
++
def listAllprop(self, request):
"""
+ Some DAV properties should not be returned to a C{DAV:allprop} query.
@@ -509,6 +559,9 @@
reactor.callLater(0, getChild)
@@ -169,7 +168,7 @@
response = UnauthorizedResponse(request.credentialFactories,
request.remoteAddr)
else:
-@@ -600,8 +656,13 @@
+@@ -600,16 +656,22 @@
def authenticate(self, request):
def loginSuccess(result):
@@ -185,7 +184,6 @@
if not (
hasattr(request, 'portal') and
-@@ -608,8 +669,9 @@
hasattr(request, 'credentialFactories') and
hasattr(request, 'loginInterfaces')
):
@@ -212,7 +210,7 @@
def login(pcreds):
d = request.portal.login(pcreds, None, *request.loginInterfaces)
-@@ -635,7 +699,7 @@
+@@ -635,13 +699,14 @@
return d
@@ -221,9 +219,6 @@
d.addCallback(gotDetails).addCallback(login)
return d
-@@ -640,8 +704,9 @@
-
- return d
else:
- request.user = davxml.Principal(davxml.Unauthenticated())
- return request.user
@@ -247,7 +242,7 @@
else:
return unauthenticatedPrincipal
-@@ -666,31 +731,25 @@
+@@ -666,32 +731,26 @@
present on this resource, it tries to get it from the parent, unless it
is the root or has no parent.
"""
@@ -271,16 +266,19 @@
- myURL = request.urlForResource(self)
- if myURL != "/":
- parentURL = parentForURL(myURL)
--
-- parent = waitForDeferred(request.locateResource(parentURL))
-- yield parent
-- parent = parent.getResult()
+ def gotParent(parent):
+ if parent is None:
+ return ()
+ else:
+ return parent.principalCollections(request)
+- parent = waitForDeferred(request.locateResource(parentURL))
+- yield parent
+- parent = parent.getResult()
++ d = request.locateResource(parentForURL(myURL))
++ d.addCallback(gotParent)
++ return d
+
- if parent:
- principalCollections = waitForDeferred(parent.principalCollections(request))
- yield principalCollections
@@ -289,13 +287,11 @@
- yield principalCollections
-
- principalCollections = deferredGenerator(principalCollections)
-+ d = request.locateResource(parentForURL(myURL))
-+ d.addCallback(gotParent)
-+ return d
-
+-
def defaultAccessControlList(self):
"""
-@@ -1146,11 +1205,14 @@
+ @return: the L{davxml.ACL} element containing the default access control
+@@ -1146,49 +1205,95 @@
This implementation returns an empty set.
"""
@@ -312,9 +308,6 @@
@param request: the L{IRequest} for the request in progress.
@param authid: a string containing the
authentication/authorization identifier for the principal
-@@ -1155,12 +1217,47 @@
- @param authid: a string containing the
- authentication/authorization identifier for the principal
to lookup.
- @return: a deferred tuple of C{(principal, principalURI)}
- where: C{principal} is the L{Principal} that is found;
@@ -363,7 +356,6 @@
# Try to match principals in each principal collection on the resource
collections = waitForDeferred(self.principalCollections(request))
yield collections
-@@ -1167,7 +1264,7 @@
collections = collections.getResult()
for collection in collections:
@@ -372,8 +364,6 @@
principal = waitForDeferred(request.locateResource(principalURI))
yield principal
-@@ -1173,22 +1270,30 @@
- yield principal
principal = principal.getResult()
- if isPrincipalResource(principal):
@@ -385,15 +375,15 @@
- principalCollections = waitForDeferred(self.principalCollections(request))
- yield principalCollections
- principalCollections = principalCollections.getResult()
--
++ yield None
++ return
+
- if len(principalCollections) == 0:
- log.msg("DAV:principal-collection-set property cannot be found on the resource being authorized: %s" % self)
- else:
- log.msg("Could not find principal matching user id: %s" % authid)
- raise HTTPError(responsecode.FORBIDDEN)
-+ yield None
-+ return
-
+-
findPrincipalForAuthID = deferredGenerator(findPrincipalForAuthID)
+ def authorizationPrincipal(self, request, authid, authnPrincipal, authnURI):
Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.test.test_prop.patch 2006-11-15 20:33:54 UTC (rev 472)
@@ -11,7 +11,7 @@
import random
-@@ -37,7 +39,14 @@
+@@ -37,8 +39,15 @@
from twisted.web2.dav.test.util import serialize
import twisted.web2.dav.test.util
@@ -21,9 +21,10 @@
+ (dav_namespace, "quota-available-bytes" ),
+ (dav_namespace, "quota-used-bytes" ),
+)
-+
+
+live_properties = [lookupElement(qname)() for qname in DAVResource.liveProperties if (qname[0] == dav_namespace) and qname not in dynamicLiveProperties]
+print live_properties
-
++
#
# See whether dead properties are available
+ #
Modified: CalendarServer/trunk/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/__init__.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/__init__.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -30,6 +30,7 @@
"dateops",
"db",
"directory",
+ "dropbox",
"ical",
"index",
"instance",
@@ -48,5 +49,7 @@
import twisted.web2.dav.davxml
import twistedcaldav.caldavxml
+import twistedcaldav.customxml
twisted.web2.dav.davxml.registerElements(twistedcaldav.caldavxml)
+twisted.web2.dav.davxml.registerElements(twistedcaldav.customxml)
Modified: CalendarServer/trunk/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/caldavxml.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/caldavxml.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -27,6 +27,8 @@
See draft spec: http://ietf.webdav.org/caldav/draft-dusseault-caldav.txt
"""
+from twisted.python import log
+
from twisted.web2.dav import davxml
from twistedcaldav.dateops import clipPeriod, timeRangesOverlap
@@ -894,26 +896,32 @@
if level == 0:
# Must have VCALENDAR at the top
if (self.filter_name != "VCALENDAR") or timerange:
+ log.msg("Top-level comp-filter must be VCALENDAR, instead: %s" % (self.filter_name,))
return False
elif level == 1:
# Dissallow VCALENDAR, VALARM, STANDARD, DAYLIGHT at the top, everything else is OK
if self.filter_name in ("VCALENDAR", "VALARM", "STANDARD", "DAYLIGHT"):
+ log.msg("comp-filter wrong component type: %s" % (self.filter_name,))
return False
# time-range only on VEVENT, VTODO, VJOURNAL, VFREEBUSY
if timerange and self.filter_name not in ("VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY"):
+ log.msg("time-range cannot be used with component %s" % (self.filter_name,))
return False
elif level == 2:
# Dissallow VCALENDAR, VTIMEZONE, VEVENT, VTODO, VJOURNAL, VFREEBUSY at the top, everything else is OK
if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY")):
+ log.msg("comp-filter wrong sub-component type: %s" % (self.filter_name,))
return False
# time-range only on VALARM
if timerange and self.filter_name not in ("VALARM",):
+ log.msg("time-range cannot be used with sub-component %s" % (self.filter_name,))
return False
else:
# Dissallow all std iCal components anywhere else
if (self.filter_name in ("VCALENDAR", "VTIMEZONE", "VEVENT", "VTODO", "VJOURNAL", "VFREEBUSY", "VALARM", "STANDARD", "DAYLIGHT")) or timerange:
+ log.msg("comp-filter wrong standard component type: %s" % (self.filter_name,))
return False
# Test each property
@@ -984,6 +992,7 @@
# time-range only on COMPLETED, CREATED, DTSTAMP, LAST-MODIFIED
if timerange and self.filter_name not in ("COMPLETED", "CREATED", "DTSTAMP", "LAST-MODIFIED"):
+ log.msg("time-range cannot be used with property %s" % (self.filter_name,))
return False
# Test the time-range
@@ -1186,12 +1195,16 @@
"""
if not isinstance(self.start, datetime.datetime):
+ log.msg("start attribute in <time-range> is not a date-time: %s" % (self.start,))
return False
if not isinstance(self.end, datetime.datetime):
+ log.msg("end attribute in <time-range> is not a date-time: %s" % (self.end,))
return False
if self.start.tzinfo != utc:
+ log.msg("start attribute in <time-range> is not UTC: %s" % (self.start,))
return False
if self.end.tzinfo != utc:
+ log.msg("end attribute in <time-range> is not UTC: %s" % (self.start,))
return False
# No other tests
Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/customxml.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -25,10 +25,11 @@
change.
"""
-from twisted.web2.dav.element import parser
from twisted.web2.dav.resource import twisted_dav_namespace
from twisted.web2.dav import davxml
+apple_namespace = "http://apple.com/ns/calendarserver/"
+
class TwistedGUIDProperty (davxml.WebDAVTextElement):
"""
Contains the GUID value for a directory record corresponding to a principal.
@@ -40,8 +41,6 @@
def getValue(self):
return str(self)
-parser.registerElement(TwistedGUIDProperty)
-
class TwistedLastModifiedProperty (davxml.WebDAVTextElement):
"""
Contains the Last-Modified value for a directory record corresponding to a principal.
@@ -53,8 +52,6 @@
def getValue(self):
return str(self)
-parser.registerElement(TwistedLastModifiedProperty)
-
class TwistedCalendarPrincipalURI(davxml.WebDAVTextElement):
"""
Contains the calendarPrincipalURI value for a directory record corresponding to a principal.
@@ -66,8 +63,6 @@
def getValue(self):
return str(self)
-parser.registerElement(TwistedCalendarPrincipalURI)
-
class TwistedGroupMemberGUIDs(davxml.WebDAVElement):
"""
Contains a list of GUIDs (TwistedGUIDProperty) for members of a group. Only used on group principals.
@@ -78,8 +73,6 @@
allowed_children = { (twisted_dav_namespace, "guid"): (0, None) }
-parser.registerElement(TwistedGroupMemberGUIDs)
-
class TwistedScheduleAutoRespond(davxml.WebDAVEmptyElement):
"""
When set on an Inbox, scheduling requests are automatically handled.
@@ -88,4 +81,105 @@
name = "schedule-auto-respond"
hidden = True
-parser.registerElement(TwistedScheduleAutoRespond)
+class DropBoxHome (davxml.WebDAVEmptyElement):
+ """
+ Denotes a drop box home collection (a collection that will contain drop boxes).
+ (Apple Extension to CalDAV)
+ """
+ namespace = apple_namespace
+ name = "dropbox-home"
+
+class DropBox (davxml.WebDAVEmptyElement):
+ """
+ Denotes a drop box collection.
+ (Apple Extension to CalDAV)
+ """
+ namespace = apple_namespace
+ name = "dropbox"
+
+class Notifications (davxml.WebDAVEmptyElement):
+ """
+ Denotes a notifications collection.
+ (Apple Extension to CalDAV)
+ """
+ namespace = apple_namespace
+ name = "notifications"
+
+class DropBoxHomeURL (davxml.WebDAVElement):
+ """
+ A principal property to indicate the location of the drop box home.
+ (Apple Extension to CalDAV)
+ """
+ namespace = apple_namespace
+ name = "dropbox-home-URL"
+ hidden = True
+ protected = True
+
+ allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
+
+class NotificationsURL (davxml.WebDAVElement):
+ """
+ A principal property to indicate the location of the notification collection.
+ (Apple Extension to CalDAV)
+ """
+ namespace = apple_namespace
+ name = "notifications-URL"
+ hidden = True
+ protected = True
+
+ allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
+
+class Notification(davxml.WebDAVElement):
+ """
+ Root element for XML data in a notification resource.
+ """
+ namespace = apple_namespace
+ name = "notification"
+
+ allowed_children = {
+ (apple_namespace, "time-stamp" ): (1, 1),
+ (apple_namespace, "changed" ): (1, 1),
+ }
+
+class TimeStamp (davxml.WebDAVTextElement):
+ """
+ A property to indicate the timestamp of a notification resource.
+ (Apple Extension to CalDAV)
+ """
+ namespace = apple_namespace
+ name = "time-stamp"
+ hidden = True
+ protected = True
+
+class Changed (davxml.WebDAVElement):
+ """
+ A property to indicate the URI of the drop box that generated
+ notification resource.
+ (Apple Extension to CalDAV)
+ """
+ namespace = apple_namespace
+ name = "changed"
+ hidden = True
+ protected = True
+
+ allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
+
+class Subscribed (davxml.WebDAVElement):
+ """
+ A property to indicate which principals will receive notifications.
+ (Apple Extension to CalDAV)
+ """
+ namespace = apple_namespace
+ name = "subscribed"
+ hidden = True
+ protected = True
+
+ allowed_children = { (davxml.dav_namespace, "principal"): (0, None) }
+
+##
+# Extensions to davxml.ResourceType
+##
+
+davxml.ResourceType.dropboxhome = davxml.ResourceType(davxml.Collection(), DropBoxHome())
+davxml.ResourceType.dropbox = davxml.ResourceType(davxml.Collection(), DropBox())
+davxml.ResourceType.notifications = davxml.ResourceType(davxml.Collection(), Notifications())
Copied: CalendarServer/trunk/twistedcaldav/dropbox.py (from rev 450, CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/dropbox.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/dropbox.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/dropbox.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -0,0 +1,111 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+Implements drop-box functionality. A drop box is an external attachment store that provides
+for automatic notification of changes to subscribed users.
+"""
+
+__all__ = [
+ "DropBox",
+]
+
+from twistedcaldav.customxml import davxml, apple_namespace
+
+import os
+
+class DropBox(object):
+
+ # These are all options that will be set from a .plist configuration file.
+
+ enabled = True # Whether or not drop box functionaility is enabled.
+ dropboxName = "dropbox" # Name of the collection in which drop boxes can be created.
+ inheritedACLs = True # Whether or not ACLs set on a drop box collection are automatically
+ # inherited by child resources.
+
+ notifications = True # Whether to post notification messages into per-user notification collection.
+ notifcationName = "notify" # Name of the collection in which notifications will be stored.
+
+ @classmethod
+ def enable(clzz, enabled, dropboxName=None, inheritedACLs=None, notifications=None, notificationName=None):
+ """
+ This method must be used to enable drop box support as it will setup live properties etc,
+ and turn on the notification system. It must only be called once
+
+ @param enable: C{True} if drop box feature is enabled, C{False} otherwise
+ @param dropboxName: C{str} containing the name of the drop box home collection
+ @param inheritedACLs: C{True} if ACLs on drop boxes should be inherited by their contents, C{False} otehrwise.
+ @param notifications: C{True} if automatic notifications are to be sent when a drop box changes, C{False} otherwise.
+ @param notificationName: C{str} containing the name of the collection used to store per-user notifications.
+ """
+ DropBox.enabled = enabled
+ if dropboxName:
+ DropBox.dropboxName = dropboxName
+ if inheritedACLs:
+ DropBox.inheritedACLs = inheritedACLs
+ if notifications:
+ DropBox.notifications = notifications
+ if notificationName:
+ DropBox.notifcationName = notificationName
+
+ if DropBox.enabled:
+
+ # Need to setup live properties
+ from twistedcaldav.resource import CalendarPrincipalResource
+ assert (apple_namespace, "dropbox-home-URL") not in CalendarPrincipalResource.liveProperties, \
+ "DropBox.enable must only be called once"
+
+ CalendarPrincipalResource.liveProperties += (
+ (apple_namespace, "dropbox-home-URL" ),
+ (apple_namespace, "notifications-URL" ),
+ )
+
+ @classmethod
+ def provision(clzz, principal, cuhome):
+ """
+ Provision user account with appropriate collections for drop box
+ and notifications.
+
+ @param principal: the L{CalendarPrincipalResource} for the principal to provision
+ @param cuhome: C{tuple} of (C{str} - URI of user calendar home, L{DAVResource} - resource of user calendar home)
+ """
+
+ # Only if enabled
+ if not DropBox.enabled:
+ return
+
+ # Create drop box collection in calendar-home collection resource if not already present.
+
+ from twistedcaldav.static import CalDAVFile
+ child = CalDAVFile(os.path.join(cuhome[1].fp.path, DropBox.dropboxName))
+ child_exists = child.exists()
+ if not child_exists:
+ c = child.createSpecialCollection(davxml.ResourceType.dropboxhome)
+ assert c.called
+ c = c.result
+
+ if not DropBox.notifications:
+ return
+
+ child = CalDAVFile(os.path.join(cuhome[1].fp.path, DropBox.notifcationName))
+ child_exists = child.exists()
+ if not child_exists:
+ c = child.createSpecialCollection(davxml.ResourceType.notifications)
+ assert c.called
+ c = c.result
+
\ No newline at end of file
Modified: CalendarServer/trunk/twistedcaldav/icaldav.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/icaldav.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/icaldav.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -35,6 +35,14 @@
otherwise.
"""
+ def isSpecialCollection(collectiontype):
+ """
+ (CalDAV-access-10, Section 4.2)
+ @param collectiontype: L{WebDAVElement} for the collection type to test for.
+ @return: True if this resource is a collection that also has the specified type,
+ False otherwise.
+ """
+
def isPseudoCalendarCollection():
"""
@return: True if this resource is a calendar collection like (e.g.
@@ -42,6 +50,20 @@
otherwise.
"""
+ def isNonCalendarCollectionParent():
+ """
+ @return: True if this resource is a collection that does not allow
+ calendar collections to be created inside of it anywhere, False
+ otherwise.
+ """
+
+ def isNonCollectionParent():
+ """
+ @return: True if this resource is a collection that does not allow
+ collections to be created inside of it anywhere, False
+ otherwise.
+ """
+
def findCalendarCollections(depth):
"""
Returns an iterable of child calendar collection resources for the given
Modified: CalendarServer/trunk/twistedcaldav/method/__init__.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/__init__.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/method/__init__.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -36,4 +36,6 @@
"report_multiget",
"report_freebusy",
"schedule_common",
+ "x_apple_subscribe",
+ "x_apple_unsubscribe",
]
Modified: CalendarServer/trunk/twistedcaldav/method/delete.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/delete.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/method/delete.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -22,10 +22,13 @@
__all__ = ["http_DELETE"]
-from twisted.internet.defer import maybeDeferred
+from twisted.internet.defer import deferredGenerator, waitForDeferred
from twisted.web2 import responsecode
-from twisted.web2.iweb import IResponse
+from twisted.web2.dav.util import parentForURL
+from twistedcaldav import customxml
+from twistedcaldav.dropbox import DropBox
+from twistedcaldav.notifications import Notification
from twistedcaldav.resource import isPseudoCalendarCollectionResource
def http_DELETE(self, request):
@@ -33,24 +36,31 @@
# Override base DELETE request handling to ensure that the calendar
# index file has the entry for the deleted calendar component removed.
#
- def deleteFromIndex(response):
- response = IResponse(response)
+ # Also handle notifications in a drop box collection.
+ #
- if response.code == responsecode.NO_CONTENT:
- def deleteFromParent(parent):
- if isPseudoCalendarCollectionResource(parent):
- index = parent.index()
- index.deleteResource(self.fp.basename())
+ parentURL = parentForURL(request.uri)
+ parent = waitForDeferred(request.locateResource(parentURL))
+ yield parent
+ parent = parent.getResult()
- return response
-
- # Remove index entry if we are a child of a calendar collection
- d = self.locateParent(request, request.uri)
- d.addCallback(deleteFromParent)
- return d
+ d = waitForDeferred(super(CalDAVFile, self).http_DELETE(request))
+ yield d
+ response = d.getResult()
- return response
+ if response == responsecode.NO_CONTENT:
- d = maybeDeferred(super(CalDAVFile, self).http_DELETE, request)
- d.addCallback(deleteFromIndex)
- return d
+ if isPseudoCalendarCollectionResource(parent):
+ index = parent.index()
+ index.deleteResource(self.fp.basename())
+
+ elif DropBox.enabled and parent.isSpecialCollection(customxml.DropBox):
+ # We need to handle notificiations
+ notification = Notification(parentURL=parentURL)
+ d = waitForDeferred(notification.doNotification(request, parent))
+ yield d
+ d.getResult()
+
+ yield response
+
+http_DELETE = deferredGenerator(http_DELETE)
Modified: CalendarServer/trunk/twistedcaldav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/mkcol.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/method/mkcol.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -24,15 +24,18 @@
from twisted.internet.defer import deferredGenerator, waitForDeferred
from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.util import parentForURL
from twisted.web2.http import HTTPError, StatusResponse
-from twistedcaldav.resource import isPseudoCalendarCollectionResource
+from twistedcaldav import customxml
+from twistedcaldav.resource import isNonCollectionParentResource
def http_MKCOL(self, request):
#
# Don't allow DAV collections in a calendar collection for now
#
- parent = waitForDeferred(self._checkParents(request, isPseudoCalendarCollectionResource))
+ parent = waitForDeferred(self._checkParents(request, isNonCollectionParentResource))
yield parent
parent = parent.getResult()
if parent is not None:
@@ -43,6 +46,17 @@
d = waitForDeferred(super(CalDAVFile, self).http_MKCOL(request))
yield d
- yield d.getResult()
+ result = d.getResult()
+
+ # Check for drop box creation and give it a special resource type
+ from twistedcaldav.dropbox import DropBox
+ if result == responsecode.CREATED and DropBox.enabled:
+ parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
+ yield parent
+ parent = parent.getResult()
+ if parent.isSpecialCollection(customxml.DropBoxHome):
+ self.writeDeadProperty(davxml.ResourceType.dropbox)
+
+ yield result
http_MKCOL = deferredGenerator(http_MKCOL)
\ No newline at end of file
Modified: CalendarServer/trunk/twistedcaldav/method/put.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/method/put.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -25,17 +25,22 @@
from twisted.internet.defer import deferredGenerator, waitForDeferred
from twisted.python import log
from twisted.web2 import responsecode
+from twisted.web2.dav.element.base import twisted_dav_namespace
from twisted.web2.dav.http import ErrorResponse
from twisted.web2.dav.util import allDataFromStream, parentForURL
from twisted.web2.http import HTTPError, StatusResponse
+from twistedcaldav import customxml
from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.dropbox import DropBox
from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.notifications import Notification
from twistedcaldav.resource import isPseudoCalendarCollectionResource
def http_PUT(self, request):
- parent = waitForDeferred(request.locateResource(parentForURL(request.uri)))
+ parentURL = parentForURL(request.uri)
+ parent = waitForDeferred(request.locateResource(parentURL))
yield parent
parent = parent.getResult()
@@ -71,10 +76,32 @@
yield d
yield d.getResult()
return
+
except ValueError, e:
log.err("Error while handling (calendar) PUT: %s" % (e,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
+ elif DropBox.enabled and parent.isSpecialCollection(customxml.DropBoxHome):
+ # Cannot create resources in a drop box home collection
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (twisted_dav_namespace, "valid-drop-box")))
+
+ elif DropBox.enabled and parent.isSpecialCollection(customxml.DropBox):
+ # We need to handle notificiations
+
+ # Do the normal http_PUT behavior
+ d = waitForDeferred(super(CalDAVFile, self).http_PUT(request))
+ yield d
+ response = d.getResult()
+
+ if response.code in (responsecode.OK, responsecode.CREATED, responsecode.NO_CONTENT):
+ notification = Notification(parentURL=parentURL)
+ d = waitForDeferred(notification.doNotification(request, parent))
+ yield d
+ d.getResult()
+
+ yield response
+ return
+
else:
d = waitForDeferred(super(CalDAVFile, self).http_PUT(request))
yield d
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -72,7 +72,7 @@
@param destinationparent: the L{CalDAVFile} for the destination resource's parent collection.
@param deletesource: True if the source resource is to be deleted on successful completion, False otherwise.
@param isiTIP: True if relaxed calendar data validation is to be done, False otherwise.
- @return: status response.
+ @return: a Deferred with a status response result.
"""
try:
Copied: CalendarServer/trunk/twistedcaldav/method/x_apple_subscribe.py (from rev 450, CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/x_apple_subscribe.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/x_apple_subscribe.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/method/x_apple_subscribe.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -0,0 +1,70 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+CalDAV X_APPLE_SUBSCRIBE method.
+"""
+
+__all__ = ["http_X_APPLE_SUBSCRIBE"]
+
+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.element.base import twisted_dav_namespace
+from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.http import HTTPError, StatusResponse
+
+from twistedcaldav import customxml
+from twistedcaldav.dropbox import DropBox
+
+def http_X_APPLE_SUBSCRIBE(self, request):
+
+ # Only for drop box collections
+ if not DropBox.enabled or not self.isSpecialCollection(customxml.DropBox):
+ log.err("Cannot x-apple-subscribe to resource %s" % (request.uri,))
+ raise HTTPError(StatusResponse(
+ responsecode.FORBIDDEN,
+ "Cannot x-apple-subscribe to resource %s" % (request.uri,))
+ )
+
+ d = waitForDeferred(self.authorize(request, (davxml.Read(),)))
+ yield d
+ d.getResult()
+ authid = request.authnUser
+
+ # Get current list of subscribed principals
+ principals = []
+ if self.hasDeadProperty(customxml.Subscribed):
+ subs = self.readDeadProperty(customxml.Subscribed).children
+ principals.extend(subs)
+
+ # Error if attempt to subscribe more than once
+ if authid in principals:
+ log.err("Cannot x_apple_subscribe to resource %s as principal %s is already subscribed" % (request.uri, repr(authid),))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (twisted_dav_namespace, "principal-must-not-be-subscribed"))
+ )
+
+ principals.append(authid)
+ self.writeDeadProperty(customxml.Subscribed(*principals))
+
+ yield responsecode.OK
+
+http_X_APPLE_SUBSCRIBE = deferredGenerator(http_X_APPLE_SUBSCRIBE)
Copied: CalendarServer/trunk/twistedcaldav/method/x_apple_unsubscribe.py (from rev 450, CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/method/x_apple_unsubscribe.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/x_apple_unsubscribe.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/method/x_apple_unsubscribe.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -0,0 +1,71 @@
+##
+# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at apple.com
+##
+
+"""
+CalDAV X_APPLE_UNSUBSCRIBE method.
+"""
+
+__all__ = ["http_X_APPLE_UNSUBSCRIBE"]
+
+from twisted.internet.defer import deferredGenerator, waitForDeferred
+from twisted.python import log
+from twisted.web2 import responsecode
+from twisted.web2.dav.element.base import twisted_dav_namespace
+from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.http import HTTPError, StatusResponse
+
+from twistedcaldav import customxml
+from twistedcaldav.dropbox import DropBox
+
+def http_X_APPLE_UNSUBSCRIBE(self, request):
+
+ # Only for drop box collections
+ if not DropBox.enabled or not self.isSpecialCollection(customxml.DropBox):
+ log.err("Cannot x_apple_unsubscribe to resource %s" % (request.uri,))
+ raise HTTPError(StatusResponse(
+ responsecode.FORBIDDEN,
+ "Cannot x_apple_unsubscribe to resource %s" % (request.uri,))
+ )
+
+ # We do not check any privileges. If a principal is subscribed we always allow them to
+ # unsubscribe provided they have at least authenticated.
+ d = waitForDeferred(self.authorize(request, ()))
+ yield d
+ d.getResult()
+ authid = request.authnUser
+
+ # Get current list of subscribed principals
+ principals = []
+ if self.hasDeadProperty(customxml.Subscribed):
+ subs = self.readDeadProperty(customxml.Subscribed).children
+ principals.extend(subs)
+
+ # Error if attempt to subscribe more than once
+ if authid not in principals:
+ log.err("Cannot x_apple_unsubscribe from resource %s as principal %s is not currently subscribed" % (request.uri, repr(authid),))
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (twisted_dav_namespace, "principal-must-be-subscribed"))
+ )
+
+ principals.remove(authid)
+ self.writeDeadProperty(customxml.Subscribed(*principals))
+
+ yield responsecode.OK
+
+http_X_APPLE_UNSUBSCRIBE = deferredGenerator(http_X_APPLE_UNSUBSCRIBE)
Copied: CalendarServer/trunk/twistedcaldav/notifications.py (from rev 450, CalendarServer/branches/users/cdaboo/dropbox/twistedcaldav/notifications.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/notifications.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/notifications.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -0,0 +1,182 @@
+##
+# Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# DRI: Cyrus Daboo, cdaboo at 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 apple_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",
+ "NotificationResource",
+ "NotificationFile",
+]
+
+class Notification(object):
+ """
+ Encapsulates a notification message.
+ """
+
+ def __init__(self, parentURL):
+ self.timestamp = datetime.datetime.utcnow()
+ self.parentURL = parentURL
+
+ def doNotification(self, request, parent):
+ """
+ Put the supplied noitification 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:
+
+ 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
+
+ _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 parent.hasDeadProperty(customxml.Subscribed):
+ yield None
+ return
+
+ 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()
+
+ 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(),)
+ 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 NotificationResource(DAVResource):
+ """
+ Resource that gets stored in a notification collection and which contains
+ the notification details in its content as well as via properties.
+ """
+
+ liveProperties = DAVResource.liveProperties + (
+ (apple_namespace, "time-stamp" ),
+ (apple_namespace, "changed" ),
+ )
+
+class NotificationFile(DAVResource, 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/repository.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/repository.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/repository.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -41,11 +41,11 @@
from twisted.web2.log import LogWrapperResource
from twisted.web2.server import Site
-from twistedcaldav import caldavxml, customxml
+from twistedcaldav.dropbox import DropBox
from twistedcaldav import authkerb
from twistedcaldav.logging import RotatingFileAccessLoggingObserver
from twistedcaldav.resource import CalDAVResource
-from twistedcaldav.static import CalDAVFile, CalendarHomeFile, CalendarPrincipalFile
+from twistedcaldav.static import CalendarHomeFile, CalendarPrincipalFile
from twistedcaldav.directory.cred import DirectoryCredentialsChecker
import os
@@ -124,7 +124,12 @@
ELEMENT_CANPROXY = "canproxy"
ATTRIBUTE_REPEAT = "repeat"
-def startServer(docroot, repo, doacct, doacl, dossl, keyfile, certfile, onlyssl, port, sslport, maxsize, quota, serverlogfile, manhole):
+def startServer(docroot, repo, doacct, doacl, dossl,
+ keyfile, certfile, onlyssl, port, sslport, maxsize,
+ quota, serverlogfile,
+ dropbox, dropboxName, dropboxACLs,
+ notifications, notifcationName,
+ manhole):
"""
Start the server using XML-based configuration details and supplied .plist based options.
"""
@@ -169,6 +174,9 @@
MultiService.stopService(self)
self.logObserver.stop()
+ # Turn on drop box support before building the repository
+ DropBox.enable(dropbox, dropboxName, dropboxACLs, notifications, notifcationName)
+
# Build the server
builder = RepositoryBuilder(docroot,
doAccounts=doacct,
@@ -725,126 +733,27 @@
principal.open("w").close()
log.msg("Created principal: %s" % principalURL)
principal = CalendarPrincipalFile(principal.path, principalURL)
- if len(item.pswd):
- principal.writeDeadProperty(auth.TwistedPasswordProperty.fromString(item.pswd))
- else:
- principal.removeDeadProperty(auth.TwistedPasswordProperty())
- if len(item.name):
- principal.writeDeadProperty(davxml.DisplayName.fromString(item.name))
- else:
- principal.removeDeadProperty(davxml.DisplayName())
- if len(item.cuaddrs):
- principal.writeDeadProperty(caldavxml.CalendarUserAddressSet(*[davxml.HRef(addr) for addr in item.cuaddrs]))
- else:
- principal.removeDeadProperty(caldavxml.CalendarUserAddressSet())
-
- if resetACLs or not principal_exists:
- principal.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.HRef.fromString(principalURL)),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- ),
- ),
- )
- )
-
- # If the user does not have any calendar user addresses we do not create a calendar home for them
- if not item.cuaddrs and not item.calendars:
- return
-
+
+ # Special case: if we have an explicit cuhome URL, we will use that,
+ # otherwise we fall back to the inferred home and resource
if item.cuhome:
- principal.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(item.cuhome)))
+ cuhome = (item.cuhome, None)
else:
- # Create calendar home
- homeURL = joinURL(self.calendarHome.uri, item.uid)
- home = FilePath(os.path.join(self.calendarHome.resource.fp.path, item.uid))
- home_exists = home.exists()
- if not home_exists:
- home.createDirectory()
- home = CalendarHomeFile(home.path)
-
- # Handle ACLs on calendar home
- if resetACLs or not home_exists:
- if item.acl:
- home.setAccessControlList(item.acl.acl)
- else:
- home.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- ),
- ),
- davxml.ACE(
- davxml.Principal(davxml.HRef.fromString(principalURL)),
- davxml.Grant(
- davxml.Privilege(davxml.All()),
- ),
- TwistedACLInheritable(),
- ),
- )
- )
+ cuhome = (self.calendarHome.uri, self.calendarHome.resource)
- # Handle quota on calendar home
- home.setQuotaRoot(None, item.quota)
-
- # Save the calendar-home-set, schedule-inbox and schedule-outbox properties
- principal.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(homeURL + "/")))
- principal.writeDeadProperty(caldavxml.ScheduleInboxURL(davxml.HRef.fromString(joinURL(homeURL, "inbox/"))))
- principal.writeDeadProperty(caldavxml.ScheduleOutboxURL(davxml.HRef.fromString(joinURL(homeURL, "outbox/"))))
-
- # Set ACLs on inbox and outbox
- if resetACLs or not home_exists:
- inbox = home.getChild("inbox")
- inbox.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(caldavxml.Schedule()),
- ),
- ),
- )
- )
- if item.autorespond:
- inbox.writeDeadProperty(customxml.TwistedScheduleAutoRespond())
-
- outbox = home.getChild("outbox")
- if outbox.hasDeadProperty(davxml.ACL()):
- outbox.removeDeadProperty(davxml.ACL())
-
- calendars = []
- for calendar in item.calendars:
- childURL = joinURL(homeURL, calendar)
- child = CalDAVFile(os.path.join(home.fp.path, calendar))
- child_exists = child.exists()
- if not child_exists:
- c = child.createCalendarCollection()
- assert c.called
- c = c.result
-
- calendars.append(childURL)
- if (resetACLs or not child_exists):
- child.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(caldavxml.ReadFreeBusy()),
- ),
- TwistedACLInheritable(),
- ),
- )
- )
-
- # Set calendar-free-busy-set on Inbox if not already present
- inbox = home.getChild("inbox")
- if not inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet()):
- fbset = caldavxml.CalendarFreeBusySet(*[davxml.HRef.fromString(uri) for uri in calendars])
- inbox.writeDeadProperty(fbset)
+ # Principal knows how to provision itself in the appropriate manner
+ principal.provisionCalendarAccount(
+ item.name,
+ item.pswd,
+ resetACLs or not principal_exists,
+ item.cuaddrs,
+ cuhome,
+ item.acl,
+ item.quota,
+ item.calendars,
+ item.autorespond,
+ True
+ )
class ProvisionPrincipal (object):
"""
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -55,9 +55,10 @@
import twisted.web2.server
import twistedcaldav
-from twistedcaldav import caldavxml
+from twistedcaldav import caldavxml, customxml
from twistedcaldav.icaldav import ICalDAVResource, ICalendarPrincipalResource, ICalendarSchedulingCollectionResource
from twistedcaldav.caldavxml import caldav_namespace
+from twistedcaldav.customxml import apple_namespace
from twistedcaldav.ical import Component as iComponent
from twistedcaldav.extensions import DAVResource
@@ -295,11 +296,17 @@
"""
See L{ICalDAVResource.isCalendarCollection}.
"""
+ return self.isSpecialCollection(caldavxml.Calendar)
+
+ def isSpecialCollection(self, collectiontype):
+ """
+ See L{ICalDAVResource.isSpecialCollection}.
+ """
if not self.isCollection(): return False
try:
resourcetype = self.readDeadProperty((dav_namespace, "resourcetype"))
- return resourcetype.isCalendar()
+ return bool(resourcetype.childrenOfType(collectiontype))
except HTTPError, e:
assert e.response.code == responsecode.NOT_FOUND
return False
@@ -310,6 +317,22 @@
"""
return self.isCalendarCollection()
+ def isNonCalendarCollectionParent(self):
+ """
+ See L{ICalDAVResource.isNonCalendarCollectionParent}.
+ """
+
+ # Cannot create calendars inside other calendars or a drop box home
+ return self.isPseudoCalendarCollection() or self.isSpecialCollection(customxml.DropBoxHome)
+
+ def isNonCollectionParent(self):
+ """
+ See L{ICalDAVResource.isNonCalendarCollectionParent}.
+ """
+
+ # Cannot create collections inside a drop box
+ return self.isPseudoCalendarCollection() or self.isSpecialCollection(customxml.DropBox)
+
def findCalendarCollections(self, depth, request, callback, privileges=None):
"""
See L{ICalDAVResource.findCalendarCollections}.
@@ -435,13 +458,17 @@
"""
Write a new ACL to the resource's property store. We override this for calendar collections
and force all the ACEs to be inheritable so that all calendar object resources within the
- calendar collection have the same privileges unless explicitly overridden.
+ calendar collection have the same privileges unless explicitly overridden. The same applies
+ to drop box collections as we want all resources (attachments) to have the same privileges as
+ the drop box collection.
@param newaces: C{list} of L{ACE} for ACL being set.
"""
# Do this only for regular calendar collections and Inbox/Outbox
- if self.isPseudoCalendarCollection():
+ from twistedcaldav.dropbox import DropBox
+ if self.isPseudoCalendarCollection() or \
+ DropBox.enabled and self.isSpecialCollection(customxml.DropBox):
# Add inheritable option to each ACE in the list
for ace in newaces:
if TwistedACLInheritable() not in ace.children:
@@ -646,6 +673,22 @@
else:
return caldavxml.ScheduleOutboxURL(davxml.HRef(url))
+ elif namespace == apple_namespace:
+ if name == "dropbox-home-URL":
+ url = self.dropboxURL()
+ if url is None:
+ return None
+ else:
+ return customxml.DropBoxHomeURL(davxml.HRef(url))
+
+ if name == "notifications-URL":
+ # Use the first calendar home only
+ url = self.notificationsURL()
+ if url is None:
+ return None
+ else:
+ return customxml.NotificationsURL(davxml.HRef(url))
+
return super(CalendarPrincipalResource, self).readProperty(property, request)
return maybeDeferred(defer)
@@ -720,6 +763,30 @@
else:
return None
+ def dropboxURL(self):
+ """
+ @return: the drop box home collection URL for this principal.
+ """
+ # Use the first calendar home only
+ from twistedcaldav.dropbox import DropBox
+ url = None
+ for home in self.calendarHomeURLs():
+ url = joinURL(home, DropBox.dropboxName) + "/"
+ break
+ return url
+
+ def notificationsURL(self):
+ """
+ @return: the notifications collection URL for this principal.
+ """
+ # Use the first calendar home only
+ from twistedcaldav.dropbox import DropBox
+ url = None
+ for home in self.calendarHomeURLs():
+ url = joinURL(home, DropBox.notifcationName) + "/"
+ break
+ return url
+
def matchesCalendarUserAddress(self, request, address):
"""
Determine whether this principal matches the supplied calendar user
@@ -841,6 +908,22 @@
else:
return resource.isPseudoCalendarCollection()
+def isNonCalendarCollectionParentResource(resource):
+ try:
+ resource = ICalDAVResource(resource)
+ except TypeError:
+ return False
+ else:
+ return resource.isNonCalendarCollectionParent()
+
+def isNonCollectionParentResource(resource):
+ try:
+ resource = ICalDAVResource(resource)
+ except TypeError:
+ return False
+ else:
+ return resource.isNonCollectionParent()
+
def isScheduleInboxResource(resource):
try:
resource = ICalendarSchedulingCollectionResource(resource)
Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py 2006-11-15 19:58:49 UTC (rev 471)
+++ CalendarServer/trunk/twistedcaldav/static.py 2006-11-15 20:33:54 UTC (rev 472)
@@ -54,7 +54,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.resource import CalDAVResource, isPseudoCalendarCollectionResource, CalendarPrincipalResource
+from twistedcaldav.resource import CalDAVResource, isNonCalendarCollectionParentResource, CalendarPrincipalResource
from twistedcaldav.resource import ScheduleInboxResource, ScheduleOutboxResource, CalendarPrincipalCollectionResource
from twistedcaldav.resource import isCalendarCollectionResource
from twistedcaldav.extensions import DAVFile
@@ -103,7 +103,7 @@
return self.createCalendarCollection()
- parent = self._checkParents(request, isPseudoCalendarCollectionResource)
+ parent = self._checkParents(request, isNonCalendarCollectionParentResource)
parent.addCallback(_defer)
return parent
@@ -111,22 +111,29 @@
#
# Create the collection once we know it is safe to do so
#
+ return self.createSpecialCollection(davxml.ResourceType.calendar)
+
+ def createSpecialCollection(self, resourceType=None):
+ #
+ # Create the collection once we know it is safe to do so
+ #
def onCollection(status):
if status != responsecode.CREATED:
raise HTTPError(status)
- self.writeDeadProperty(davxml.ResourceType.calendar)
+ self.writeDeadProperty(resourceType)
return status
def onError(f):
try:
rmdir(self.fp)
except Exception, e:
- log.err("Unable to clean up after failed MKCALENDAR: %s" % e)
+ log.err("Unable to clean up after failed MKCOL (special resource type: %s): %s" % (e, resourceType,))
return f
d = mkcollection(self.fp)
- d.addCallback(onCollection)
+ if resourceType is not None:
+ d.addCallback(onCollection)
d.addErrback(onError)
return d
@@ -722,7 +729,7 @@
"""
return self.fp.basename()
- def provisionCalendarAccount(self, name, pswd, resetacl, cuaddrs, cuhome, cuhomeacls, cals, autorespond):
+ def provisionCalendarAccount(self, name, pswd, resetacl, cuaddrs, cuhome, cuhomeacls, quota, cals, autorespond, allowdropbox):
"""
Provision the principal and a calendar account for it.
@@ -734,6 +741,7 @@
@param cuhomeacls: L{ACL} acls to use on calendar home when resetting ACLs, or C{None} to use default set.
@param cals: C{list} list of calendar names to create in the calendar home for this prinicpal.
@param autorespond: C{True} if iTIP auto-response is required, C{False} otherwise.
+ @param allowdropbox: C{True} if drop box should be enabled for this user is drop box is supproted, C{False} otherwise.
"""
if pswd:
@@ -765,89 +773,101 @@
if not cuaddrs and not cals:
return
- # Create calendar home
- homeURL = joinURL(cuhome[0], self.principalUID())
- home = FilePath(os.path.join(cuhome[1].fp.path, self.principalUID()))
- home_exists = home.exists()
- if not home_exists:
- home.createDirectory()
- home = CalendarHomeFile(home.path)
-
- if resetacl or not home_exists:
- if cuhomeacls:
- home.setAccessControlList(cuhomeacls.acl)
- else:
- home.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
+ # Create calendar home if we already have the resource, otherwise simply record
+ # the URL as the calendar-home-set
+ if cuhome[1] is None:
+ self.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(cuhome[0])))
+ else:
+ homeURL = joinURL(cuhome[0], self.principalUID())
+ home = FilePath(os.path.join(cuhome[1].fp.path, self.principalUID()))
+ home_exists = home.exists()
+ if not home_exists:
+ home.createDirectory()
+ home = CalendarHomeFile(home.path)
+
+ if resetacl or not home_exists:
+ if cuhomeacls:
+ home.setAccessControlList(cuhomeacls.acl)
+ else:
+ home.setAccessControlList(
+ davxml.ACL(
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ ),
),
- ),
- davxml.ACE(
- davxml.Principal(davxml.HRef.fromString(self._url)),
- davxml.Grant(
- davxml.Privilege(davxml.All()),
+ davxml.ACE(
+ davxml.Principal(davxml.HRef.fromString(self._url)),
+ davxml.Grant(
+ davxml.Privilege(davxml.All()),
+ ),
+ TwistedACLInheritable(),
),
- TwistedACLInheritable(),
- ),
+ )
)
- )
+
+ # Handle quota on calendar home
+ home.setQuotaRoot(None, quota)
- # Save the calendar-home-set, schedule-inbox and schedule-outbox properties
- self.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(homeURL + "/")))
- self.writeDeadProperty(caldavxml.ScheduleInboxURL(davxml.HRef.fromString(joinURL(homeURL, "inbox/"))))
- self.writeDeadProperty(caldavxml.ScheduleOutboxURL(davxml.HRef.fromString(joinURL(homeURL, "outbox/"))))
-
- # Set ACLs on inbox and outbox
- if resetacl or not home_exists:
- inbox = home.getChild("inbox")
- inbox.setAccessControlList(
- davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(caldavxml.Schedule()),
- ),
- ),
- )
- )
- if autorespond:
- inbox.writeDeadProperty(customxml.TwistedScheduleAutoRespond())
-
- outbox = home.getChild("outbox")
- if outbox.hasDeadProperty(davxml.ACL()):
- outbox.removeDeadProperty(davxml.ACL())
-
- calendars = []
- for calendar in cals:
- childURL = joinURL(homeURL, calendar)
- child = CalDAVFile(os.path.join(home.fp.path, calendar))
- child_exists = child.exists()
- if not child_exists:
- c = child.createCalendarCollection()
- assert c.called
- c = c.result
- calendars.append(childURL)
- if (resetacl or not child_exists):
- child.setAccessControlList(
+ # Save the calendar-home-set, schedule-inbox and schedule-outbox properties
+ self.writeDeadProperty(caldavxml.CalendarHomeSet(davxml.HRef.fromString(homeURL + "/")))
+ self.writeDeadProperty(caldavxml.ScheduleInboxURL(davxml.HRef.fromString(joinURL(homeURL, "inbox/"))))
+ self.writeDeadProperty(caldavxml.ScheduleOutboxURL(davxml.HRef.fromString(joinURL(homeURL, "outbox/"))))
+
+ # Set ACLs on inbox and outbox
+ if resetacl or not home_exists:
+ inbox = home.getChild("inbox")
+ inbox.setAccessControlList(
davxml.ACL(
davxml.ACE(
davxml.Principal(davxml.Authenticated()),
davxml.Grant(
- davxml.Privilege(caldavxml.ReadFreeBusy()),
+ davxml.Privilege(caldavxml.Schedule()),
),
- TwistedACLInheritable(),
),
)
)
-
- # Set calendar-free-busy-set on Inbox if not already present
- inbox = home.getChild("inbox")
- if not inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet()):
- fbset = caldavxml.CalendarFreeBusySet(*[davxml.HRef.fromString(uri) for uri in calendars])
- inbox.writeDeadProperty(fbset)
+ if autorespond:
+ inbox.writeDeadProperty(customxml.TwistedScheduleAutoRespond())
+
+ outbox = home.getChild("outbox")
+ if outbox.hasDeadProperty(davxml.ACL()):
+ outbox.removeDeadProperty(davxml.ACL())
+
+ calendars = []
+ for calendar in cals:
+ childURL = joinURL(homeURL, calendar)
+ child = CalDAVFile(os.path.join(home.fp.path, calendar))
+ child_exists = child.exists()
+ if not child_exists:
+ c = child.createCalendarCollection()
+ assert c.called
+ c = c.result
+ calendars.append(childURL)
+ if (resetacl or not child_exists):
+ child.setAccessControlList(
+ davxml.ACL(
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(
+ davxml.Privilege(caldavxml.ReadFreeBusy()),
+ ),
+ TwistedACLInheritable(),
+ ),
+ )
+ )
+
+ # Set calendar-free-busy-set on Inbox if not already present
+ inbox = home.getChild("inbox")
+ if not inbox.hasDeadProperty(caldavxml.CalendarFreeBusySet()):
+ fbset = caldavxml.CalendarFreeBusySet(*[davxml.HRef.fromString(uri) for uri in calendars])
+ inbox.writeDeadProperty(fbset)
+
+ # Do drop box if requested
+ if allowdropbox:
+ from twistedcaldav.dropbox import DropBox
+ DropBox.provision(self, (homeURL, home))
class CalendarPrincipalCollectionFile (CalendarPrincipalCollectionResource, DAVFile):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20061115/d5da209a/attachment.html
More information about the calendarserver-changes
mailing list