[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