[CalendarServer-changes] [6800] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Jan 25 08:18:02 PST 2011


Revision: 6800
          http://trac.macosforge.org/projects/calendarserver/changeset/6800
Author:   cdaboo at apple.com
Date:     2011-01-25 08:18:01 -0800 (Tue, 25 Jan 2011)
Log Message:
-----------
Changes to quota handling to add limits for calendar/address data and quota on attachments only. Also
changed attachments to be referenced by dropbox_id rather than resource_id. Fixed an issue with one of
the implicit UID queries added a short while ago.

Modified Paths:
--------------
    CalendarServer/trunk/conf/caldavd-apple.plist
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/conf/caldavd.plist
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/calendar_store/ho/me/home1/calendar_1/1.ics
    CalendarServer/trunk/txdav/carddav/datastore/file.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql

Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/conf/caldavd-apple.plist	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-    Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+    Copyright (c) 2006-2011 Apple 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.
@@ -133,14 +133,24 @@
         Quotas and limits
       -->
 
-    <!-- User quota (in bytes) [0 = no quota] -->
+    <!-- User quota (in bytes) [0 = no quota] applies to attachments only -->
     <key>UserQuota</key>
     <integer>104857600</integer><!-- 100Mb -->
 
-    <!-- Attachment size limit (in bytes) -->
-    <key>MaximumAttachmentSize</key>
-    <integer>1048576</integer><!-- 1Mb -->
+    <!-- Maximum number of calendars/address books allowed in a home -->
+    <!-- 0 for no limit -->
+    <key>MaxCollectionsPerHome</key>
+    <integer>50</integer>
 
+    <!-- Maximum number of resources in a calendar/address book -->
+    <!-- 0 for no limit -->
+    <key>MaxResourcesPerCollection</key>
+    <integer>10000</integer>
+
+    <!-- Maximum resource size (in bytes) -->
+    <key>MaxResourceSize</key>
+    <integer>1048576</integer> <!-- 1Mb -->
+
     <!-- Maximum number of unique attendees per entire event -->
     <!-- 0 for no limit -->
     <key>MaxAttendeesPerInstance</key>

Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-    Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+    Copyright (c) 2006-2011 Apple 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.
@@ -131,14 +131,24 @@
         Quotas and limits
       -->
 
-    <!-- User quota (in bytes) [0 = no quota] -->
+    <!-- User quota (in bytes) [0 = no quota] applies to attachments only -->
     <key>UserQuota</key>
     <integer>104857600</integer><!-- 100Mb -->
 
-    <!-- Attachment size limit (in bytes) -->
-    <key>MaximumAttachmentSize</key>
-    <integer>1048576</integer><!-- 1Mb -->
+    <!-- Maximum number of calendars/address books allowed in a home -->
+    <!-- 0 for no limit -->
+    <key>MaxCollectionsPerHome</key>
+    <integer>50</integer>
 
+    <!-- Maximum number of resources in a calendar/address book -->
+    <!-- 0 for no limit -->
+    <key>MaxResourcesPerCollection</key>
+    <integer>10000</integer>
+
+    <!-- Maximum resource size (in bytes) -->
+    <key>MaxResourceSize</key>
+    <integer>1048576</integer> <!-- 1Mb -->
+
     <!-- Maximum number of unique attendees per entire event -->
     <!-- 0 for no limit -->
     <key>MaxAttendeesPerInstance</key>

Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/conf/caldavd.plist	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-    Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+    Copyright (c) 2006-2011 Apple 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.
@@ -113,12 +113,22 @@
         Quotas and limits
       -->
 
-    <!-- User quota (in bytes) [0 = no quota] -->
+    <!-- User quota (in bytes) [0 = no quota] applies to attachments only -->
     <key>UserQuota</key>
     <integer>104857600</integer> <!-- 100Mb -->
 
-    <!-- Attachment size limit (in bytes) -->
-    <key>MaximumAttachmentSize</key>
+    <!-- Maximum number of calendars/address books allowed in a home -->
+    <!-- 0 for no limit -->
+    <key>MaxCollectionsPerHome</key>
+    <integer>50</integer>
+
+    <!-- Maximum number of resources in a calendar/address book -->
+    <!-- 0 for no limit -->
+    <key>MaxResourcesPerCollection</key>
+    <integer>10000</integer>
+
+    <!-- Maximum resource size (in bytes) -->
+    <key>MaxResourceSize</key>
     <integer>1048576</integer> <!-- 1Mb -->
 
     <!-- Maximum number of unique attendees per entire event -->

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 Apple 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.
@@ -224,6 +224,24 @@
 
         return found
 
+class MaxCollections (davxml.WebDAVTextElement):
+    """
+    Maximum number of child collections in a home collection
+    """
+    namespace = calendarserver_namespace
+    name = "max-collections"
+    hidden = True
+    protected = True
+
+class MaxResources (davxml.WebDAVTextElement):
+    """
+    Maximum number of child resources in a collection
+    """
+    namespace = calendarserver_namespace
+    name = "max-resources"
+    hidden = True
+    protected = True
+
 class Timezones (davxml.WebDAVEmptyElement):
     """
     Denotes a timezone service resource.

Modified: CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/twistedcaldav/method/put_addressbook_common.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple 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.
@@ -31,7 +31,6 @@
 from twisted.internet.defer import returnValue
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
-from twext.web2.dav.element.base import dav_namespace
 from twext.web2.dav.http import ErrorResponse
 from twext.web2.dav.util import joinURL, parentForURL
 from twext.web2.http import HTTPError
@@ -41,6 +40,7 @@
 
 from twistedcaldav.config import config
 from twistedcaldav.carddavxml import NoUIDConflict, carddav_namespace
+from twistedcaldav import customxml
 from twistedcaldav.vcard import Component
 from twext.python.log import Logger
 
@@ -162,6 +162,12 @@
                 log.err(message)
                 raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Resource name not allowed"))
 
+            # Valid collection size check on the destination parent resource
+            result, message = (yield self.validCollectionSize())
+            if not result:
+                log.err(message)
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, customxml.MaxResources.qname()))
+
             if not self.sourceadbk:
                 # Valid content type check on the source resource if its not in a vcard collection
                 if self.source is not None:
@@ -237,6 +243,21 @@
 
         return result, message
         
+    @inlineCallbacks
+    def validCollectionSize(self):
+        """
+        Make sure that any limits on the number of resources in a collection are enforced.
+        """
+        result = True
+        message = ""
+        if not self.destination.exists() and \
+            config.MaxResourcesPerCollection and \
+            len((yield self.destinationparent.listChildren())) >= config.MaxResourcesPerCollection:
+                result = False
+                message = "Too many resources in collection %s" % (self.destinationparent,)
+
+        returnValue((result, message,))
+        
     def validAddressDataCheck(self):
         """
         Check that the vcard data is valid vCard.
@@ -264,11 +285,11 @@
         """
         result = True
         message = ""
-        if config.MaximumAttachmentSize:
+        if config.MaxResourceSize:
             vcardsize = len(str(self.vcard))
-            if vcardsize > config.MaximumAttachmentSize:
+            if vcardsize > config.MaxResourceSize:
                 result = False
-                message = "Data size %d bytes is larger than allowed limit %d bytes" % (vcardsize, config.MaximumAttachmentSize)
+                message = "Data size %d bytes is larger than allowed limit %d bytes" % (vcardsize, config.MaxResourceSize)
 
         return result, message
 
@@ -358,16 +379,6 @@
         returnValue(None)
 
     @inlineCallbacks
-    def doDestinationQuotaCheck(self):
-        """
-        Look at current quota after changes and see if we have gone over the top.
-        """
-        quota = (yield self.destination.quota(self.request))
-        if quota[0] < 0:
-            log.err("Over quota by %d" % (-quota[0],))
-            raise HTTPError(ErrorResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, (dav_namespace, "quota-not-exceeded")))
-
-    @inlineCallbacks
     def run(self):
         """
         Function that does common PUT/COPY/MOVE behavior.
@@ -414,8 +425,6 @@
                 )
 
             # Do quota check on destination
-            yield self.doDestinationQuotaCheck()
-    
             if reservation:
                 yield reservation.unreserve()
     

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: twistedcaldav.test.test_validation -*-
 ##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple 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.
@@ -33,12 +33,10 @@
 from twext.web2.dav.util import joinURL, parentForURL
 from twext.web2 import responsecode
 from twext.web2.dav import davxml
-from twext.web2.dav.element.base import dav_namespace
 from twext.web2.dav.element.base import PCDATAElement
 
 from twext.web2.http import HTTPError
 from twext.web2.http import StatusResponse
-from twext.web2.http_headers import generateContentType, MimeType
 from twext.web2.iweb import IResponse
 from twext.web2.stream import MemoryStream
 
@@ -51,6 +49,7 @@
 from twistedcaldav.caldavxml import NoUIDConflict
 from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
 from twistedcaldav.caldavxml import caldav_namespace, MaxAttendeesPerInstance
+from twistedcaldav import customxml
 from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
 
@@ -209,6 +208,12 @@
                 log.err(message)
                 raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Resource name not allowed"))
 
+            # Valid collection size check on the destination parent resource
+            result, message = (yield self.validCollectionSize())
+            if not result:
+                log.err(message)
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, customxml.MaxResources.qname()))
+
             # Valid data sizes - do before parsing the data
             if self.source is not None:
                 # Valid content length check on the source resource
@@ -384,13 +389,28 @@
         """
         result = True
         message = ""
-        if config.MaximumAttachmentSize:
+        if config.MaxResourceSize:
             calsize = self.source.contentLength()
-            if calsize is not None and calsize > config.MaximumAttachmentSize:
+            if calsize is not None and calsize > config.MaxResourceSize:
                 result = False
-                message = "File size %d bytes is larger than allowed limit %d bytes" % (calsize, config.MaximumAttachmentSize)
+                message = "File size %d bytes is larger than allowed limit %d bytes" % (calsize, config.MaxResourceSize)
 
         return result, message
+    
+    @inlineCallbacks
+    def validCollectionSize(self):
+        """
+        Make sure that any limits on the number of resources in a collection are enforced.
+        """
+        result = True
+        message = ""
+        if not self.destination.exists() and \
+            config.MaxResourcesPerCollection and \
+            len((yield self.destinationparent.listChildren())) >= config.MaxResourcesPerCollection:
+                result = False
+                message = "Too many resources in collection %s" % (self.destinationparent,)
+
+        returnValue((result, message,))
         
     def validCalendarDataCheck(self):
         """
@@ -438,11 +458,11 @@
         """
         result = True
         message = ""
-        if config.MaximumAttachmentSize:
+        if config.MaxResourceSize:
             calsize = len(str(self.calendar))
-            if calsize > config.MaximumAttachmentSize:
+            if calsize > config.MaxResourceSize:
                 result = False
-                message = "Data size %d bytes is larger than allowed limit %d bytes" % (calsize, config.MaximumAttachmentSize)
+                message = "Data size %d bytes is larger than allowed limit %d bytes" % (calsize, config.MaxResourceSize)
 
         return result, message
 
@@ -797,17 +817,6 @@
         returnValue(None)
 
     @inlineCallbacks
-    def doDestinationQuotaCheck(self):
-        """
-        Look at current quota after changes and see if we have gone over the top.
-        """
-        quota = (yield self.destination.quota(self.request))
-        if quota[0] < 0:
-            log.err("Over quota by %d" % (-quota[0],))
-            raise HTTPError(ErrorResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, (dav_namespace, "quota-not-exceeded")))
-
-
-    @inlineCallbacks
     def run(self):
         """
         Function that does common PUT/COPY/MOVE behavior.
@@ -890,9 +899,6 @@
 
                 self.request.addResponseFilter(_removeEtag, atEnd=True)
 
-            # Do quota check on destination
-            yield self.doDestinationQuotaCheck()
-    
             if reservation:
                 yield reservation.unreserve()
     

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: twistedcaldav.test.test_resource,twistedcaldav.test.test_wrapping -*-
 ##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple 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.
@@ -388,7 +388,7 @@
                 caldavxml.SupportedCalendarData.qname(),
                 customxml.GETCTag.qname(),
             )
-            if config.MaximumAttachmentSize:
+            if config.MaxResourceSize:
                 baseProperties += (
                     caldavxml.MaxResourceSize.qname(),
                 )
@@ -410,7 +410,7 @@
                 customxml.GETCTag.qname(),
                 customxml.PubSubXMPPPushKeyProperty.qname(),
             )
-            if config.MaximumAttachmentSize:
+            if config.MaxResourceSize:
                 baseProperties += (
                     carddavxml.MaxResourceSize.qname(),
                 )
@@ -609,9 +609,9 @@
 
         elif qname == caldavxml.MaxResourceSize.qname():
             # CalDAV-access-15, section 5.2.5
-            if config.MaximumAttachmentSize:
+            if config.MaxResourceSize:
                 returnValue(caldavxml.MaxResourceSize.fromString(
-                    str(config.MaximumAttachmentSize)
+                    str(config.MaxResourceSize)
                 ))
 
         elif qname == caldavxml.MaxAttendeesPerInstance.qname():
@@ -650,9 +650,9 @@
 
         elif qname == carddavxml.MaxResourceSize.qname():
             # CardDAV, section 6.2.3
-            if config.MaximumAttachmentSize:
+            if config.MaxResourceSize:
                 returnValue(carddavxml.MaxResourceSize.fromString(
-                    str(config.MaximumAttachmentSize)
+                    str(config.MaxResourceSize)
                 ))
 
         elif qname == customxml.Invite.qname():
@@ -1415,6 +1415,7 @@
 
         return super(CalDAVResource, self).checkPreconditions(request)
 
+    @inlineCallbacks
     def createCalendar(self, request):
         """
         External API for creating a calendar.  Verify that the parent is a
@@ -1442,21 +1443,28 @@
         # Verify that no parent collection is a calendar also
         #
 
-        def _defer(parent):
-            if parent is not None:
-                self.log_error("Cannot create a calendar collection within a calendar collection %s" % (parent,))
+        parent = (yield self._checkParents(request, isPseudoCalendarCollectionResource))
+
+        if parent is not None:
+            self.log_error("Cannot create a calendar collection within a calendar collection %s" % (parent,))
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (caldavxml.caldav_namespace, "calendar-collection-location-ok")
+            ))
+
+        # Check for any quota limits
+        if config.MaxCollectionsPerHome:
+            parent = (yield self.locateParent(request, request.urlForResource(self)))
+            if (yield parent.countOwnedChildren()) >= config.MaxCollectionsPerHome: # NB this ignores shares
+                self.log_error("Cannot create a calendar collection because there are too many already present in %s" % (parent,))
                 raise HTTPError(ErrorResponse(
                     responsecode.FORBIDDEN,
-                    (caldavxml.caldav_namespace, "calendar-collection-location-ok")
+                    customxml.MaxCollections.qname()
                 ))
+                
+        returnValue((yield self.createCalendarCollection()))
 
-            return self.createCalendarCollection()
 
-        parent = self._checkParents(request, isPseudoCalendarCollectionResource)
-        parent.addCallback(_defer)
-        return parent
-
-
     def createCalendarCollection(self):
         """
         Internal API for creating a calendar collection.
@@ -1496,6 +1504,7 @@
         raise NotImplementedError()
 
 
+    @inlineCallbacks
     def createAddressBook(self, request):
         """
         External API for creating an addressbook.  Verify that the parent is a
@@ -1528,20 +1537,26 @@
         # Verify that no parent collection is a calendar also
         #
 
-        def _defer(parent):
-            if parent is not None:
-                self.log_error("Cannot create an address book collection within an address book collection %s" % (parent,))
+        parent = (yield self._checkParents(request, isAddressBookCollectionResource))
+        if parent is not None:
+            self.log_error("Cannot create an address book collection within an address book collection %s" % (parent,))
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (carddavxml.carddav_namespace, "addressbook-collection-location-ok")
+            ))
+
+        # Check for any quota limits
+        if config.MaxCollectionsPerHome:
+            parent = (yield self.locateParent(request, request.urlForResource(self)))
+            if (yield parent.countOwnedChildren()) >= config.MaxCollectionsPerHome: # NB this ignores shares
+                self.log_error("Cannot create a calendar collection because there are too many already present in %s" % (parent,))
                 raise HTTPError(ErrorResponse(
                     responsecode.FORBIDDEN,
-                    (carddavxml.carddav_namespace, "addressbook-collection-location-ok")
+                    customxml.MaxCollections.qname()
                 ))
+                
+        returnValue((yield self.createAddressBookCollection()))
 
-            return self.createAddressBookCollection()
-
-        parent = self._checkParents(request, isAddressBookCollectionResource)
-        parent.addCallback(_defer)
-        return parent
-
     def createAddressBookCollection(self):
         """
         Internal API for creating an addressbook collection.
@@ -2021,11 +2036,16 @@
 
     def liveProperties(self):
 
-        return super(CommonHomeResource, self).liveProperties() + (
+        props = super(CommonHomeResource, self).liveProperties() + (
             (customxml.calendarserver_namespace, "push-transports"),
             (customxml.calendarserver_namespace, "pushkey"),
         )
+        
+        if config.MaxCollectionsPerHome:
+            props += (customxml.MaxCollections.qname(),)
 
+        return props
+
     def sharesDB(self):
         """
         Retrieve the new-style shares DB wrapper.
@@ -2176,13 +2196,24 @@
 
 
     @inlineCallbacks
+    def countOwnedChildren(self):
+        """
+        @return: the number of children (not shared ones).
+        """
+        returnValue(len(list((yield self._newStoreHome.listChildren()))))
+
+
+    @inlineCallbacks
     def readProperty(self, property, request):
         if type(property) is tuple:
             qname = property
         else:
             qname = property.qname()
 
-        if qname == (customxml.calendarserver_namespace, "push-transports"):
+        if qname == customxml.MaxCollections.qname() and config.MaxCollectionsPerHome:
+            returnValue(customxml.MaxCollections.fromString(config.MaxCollectionsPerHome))
+            
+        elif qname == (customxml.calendarserver_namespace, "push-transports"):
             notifierID = self._newStoreHome.notifierID()
             if notifierID is not None and config.Notifications.Services.XMPPNotifier.Enabled:
                 children = []
@@ -2223,7 +2254,7 @@
             else:
                 returnValue(customxml.PubSubPushTransportsProperty())
 
-        if qname == (customxml.calendarserver_namespace, "pushkey"):
+        elif qname == (customxml.calendarserver_namespace, "pushkey"):
             notifierID = self._newStoreHome.notifierID()
             if notifierID is not None and config.Notifications.Services.XMPPNotifier.Enabled:
                 pubSubConfiguration = getPubSubConfiguration(config)
@@ -2242,7 +2273,7 @@
                 returnValue(customxml.PubSubXMPPPushKeyProperty())
 
 
-        if qname == (customxml.calendarserver_namespace, "xmpp-uri"):
+        elif qname == (customxml.calendarserver_namespace, "xmpp-uri"):
             notifierID = self._newStoreHome.notifierID()
             if notifierID is not None and config.Notifications.Services.XMPPNotifier.Enabled:
                 pubSubConfiguration = getPubSubConfiguration(config)

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: twistedcaldav.test.test_stdconfig -*-
 ##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple 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.
@@ -193,11 +193,21 @@
     "ConfigRoot"              : "/etc/caldavd",
     "LogRoot"                 : "/var/log/caldavd",
     "RunRoot"                 : "/var/run/caldavd",
-    "UserQuota"               : 104857600, # User quota (in bytes)
-    "MaximumAttachmentSize"   :   1048576, # Attachment size limit (in bytes)
-    "MaxAttendeesPerInstance" :       100, # Maximum number of unique attendees
-    "MaxInstancesForRRULE"    :       400, # Maximum number of instances for an RRULE
     "WebCalendarRoot"         : "/usr/share/collabd",
+    
+    #
+    # Quotas
+    #
+    
+    # Attachments
+    "UserQuota"                 : 104857600, # User attachment quota (in bytes)
+    
+    # Resource data
+    "MaxCollectionsPerHome"     :      50, # Maximum number of calendars/address books allowed in a home
+    "MaxResourcesPerCollection" :   10000, # Maximum number of resources in a calendar/address book
+    "MaxResourceSize"           : 1048576, # Maximum resource size (in bytes)
+    "MaxAttendeesPerInstance"   :     100, # Maximum number of unique attendees
+    "MaxInstancesForRRULE"      :     400, # Maximum number of instances for an RRULE
 
     # Set to URL path of wiki authentication service, e.g. "/auth", in order
     # to use javascript authentication dialog.  Empty string indicates standard

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: twistedcaldav.test.test_wrapping -*-
 ##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 Apple 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.
@@ -48,6 +48,7 @@
 
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.config import config
+from twistedcaldav import customxml
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 from twistedcaldav.notifications import NotificationCollectionResource, \
     NotificationResource
@@ -216,6 +217,27 @@
         ) if self._newStoreObject else NonePropertyStore(self)
 
 
+    def liveProperties(self):
+
+        props = super(_CommonHomeChildCollectionMixin, self).liveProperties()
+        
+        if config.MaxResourcesPerCollection:
+            props += (customxml.MaxResources.qname(),)
+
+        return props
+
+    @inlineCallbacks
+    def readProperty(self, property, request):
+        if type(property) is tuple:
+            qname = property
+        else:
+            qname = property.qname()
+
+        if qname == customxml.MaxResources.qname() and config.MaxResourcesPerCollection:
+            returnValue(customxml.MaxResources.fromString(config.MaxResourcesPerCollection))
+
+        returnValue((yield super(_CommonHomeChildCollectionMixin, self).readProperty(property, request)))
+
     def index(self):
         """
         Retrieve the new-style index wrapper.

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: txdav.caldav.datastore.test.test_file -*-
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 Apple 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.
@@ -292,8 +292,6 @@
     @writeOperation
     def setComponent(self, component, inserting=False):
 
-        old_size = 0 if inserting else self.size()
-
         validateCalendarComponent(self, self._calendar, component, inserting)
 
         self._calendar.retrieveOldIndex().addResource(
@@ -328,16 +326,11 @@
             # Now re-write the original properties on the updated file
             self.properties().flush()
 
-            # Adjust quota
-            quota_adjustment = self.size() - old_size
-            self._calendar._home.adjustQuotaUsedBytes(quota_adjustment)
-
             def undo():
                 if backup:
                     backup.moveTo(self._path)
                 else:
                     self._path.remove()
-                self._calendar._home.adjustQuotaUsedBytes(-quota_adjustment)
             return undo
         self._transaction.addOperation(do, "set calendar component %r" % (self.name(),))
 

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 Apple 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.
@@ -374,8 +374,6 @@
     @inlineCallbacks
     def setComponent(self, component, inserting=False):
 
-        old_size = 0 if inserting else self.size()
-
         validateCalendarComponent(self, self._calendar, component, inserting)
 
         yield self.updateDatabase(component, inserting=inserting)
@@ -384,9 +382,6 @@
         else:
             yield self._calendar._updateRevision(self._name)
 
-        # Adjust quota
-        yield self._calendar._home.adjustQuotaUsedBytes(self.size() - old_size)
-
         self._calendar.notifyChanged()
 
 
@@ -674,11 +669,11 @@
         attachment = Attachment(self, name)
         yield self._txn.execSQL(
             """
-            insert into ATTACHMENT (CALENDAR_OBJECT_RESOURCE_ID, CONTENT_TYPE,
+            insert into ATTACHMENT (DROPBOX_ID, CONTENT_TYPE,
             SIZE, MD5, PATH) values (%s, %s, %s, %s, %s)
             """,
             [
-                self._resourceID, generateContentType(contentType), 0, "",
+                self._dropboxID, generateContentType(contentType), 0, "",
                 name,
             ]
         )
@@ -692,15 +687,17 @@
         self._txn.postCommit(attachment._path.remove)
         yield self._txn.execSQL(
             """
-            delete from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s AND
+            delete from ATTACHMENT where DROPBOX_ID = %s AND
             PATH = %s
-            """, [self._resourceID, name]
+            """, [self._dropboxID, name]
         )
 
         # Adjust quota
         yield self._calendar._home.adjustQuotaUsedBytes(-old_size)
+        
+        # Send change notification to home
+        yield self._calendar._home.notifyChanged()
 
-
     @inlineCallbacks
     def attachmentWithName(self, name):
         attachment = Attachment(self, name)
@@ -726,8 +723,8 @@
     def attachments(self):
         rows = yield self._txn.execSQL(
             """
-            select PATH from ATTACHMENT where CALENDAR_OBJECT_RESOURCE_ID = %s
-            """, [self._resourceID])
+            select PATH from ATTACHMENT where DROPBOX_ID = %s
+            """, [self._dropboxID])
         result = []
         for row in rows:
             result.append((yield self.attachmentWithName(row[0])))
@@ -803,6 +800,9 @@
 
         # Adjust quota
         yield self.attachment._calendarObject._calendar._home.adjustQuotaUsedBytes(self.attachment.size() - old_size)
+        
+        # Send change notification to home
+        yield self.attachment._calendarObject._calendar._home.notifyChanged()
 
 
 def sqltime(value):

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/calendar_store/ho/me/home1/calendar_1/1.ics
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/calendar_store/ho/me/home1/calendar_1/1.ics	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/calendar_store/ho/me/home1/calendar_1/1.ics	2011-01-25 16:18:01 UTC (rev 6800)
@@ -32,6 +32,8 @@
 LOCATION:Wilfredo's Office
 SEQUENCE:2
 X-APPLE-EWS-BUSYSTATUS:BUSY
+X-APPLE-DROPBOX:/calendars/__uids__/user01/dropbox/FE5CDC6F-7776-4607-83
+ A9-B90FF7ACC8D0.dropbox
 SUMMARY:CalDAV protocol updates
 DTSTART;TZID=US/Pacific:20090324T121500
 CREATED:20090326T145440Z

Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: txdav.carddav.datastore.test.test_file -*-
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 Apple 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.
@@ -175,8 +175,6 @@
     @writeOperation
     def setComponent(self, component, inserting=False):
 
-        old_size = 0 if inserting else self.size()
-
         validateAddressBookComponent(self, self._addressbook, component, inserting)
 
         self._addressbook.retrieveOldIndex().addResource(
@@ -211,16 +209,11 @@
             # Now re-write the original properties on the updated file
             self.properties().flush()
 
-            # Adjust quota
-            quota_adjustment = self.size() - old_size
-            self._addressbook._home.adjustQuotaUsedBytes(quota_adjustment)
-
             def undo():
                 if backup:
                     backup.moveTo(self._path)
                 else:
                     self._path.remove()
-                self._addressbook._home.adjustQuotaUsedBytes(-quota_adjustment)
             return undo
         self._transaction.addOperation(do, "set addressbook component %r" % (self.name(),))
 

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: txdav.carddav.datastore.test.test_sql -*-
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 Apple 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.
@@ -192,8 +192,6 @@
     @inlineCallbacks
     def setComponent(self, component, inserting=False):
 
-        old_size = 0 if inserting else self.size()
-
         validateAddressBookComponent(self, self._addressbook, component, inserting)
 
         yield self.updateDatabase(component, inserting=inserting)
@@ -202,9 +200,6 @@
         else:
             yield self._addressbook._updateRevision(self._name)
 
-        # Adjust quota
-        yield self._addressbook._home.adjustQuotaUsedBytes(self.size() - old_size)
-
         self._addressbook.notifyChanged()
 
 

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: txdav.caldav.datastore.test.test_file -*-
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 Apple 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.
@@ -765,10 +765,6 @@
 
         objectResourcePath = self._path.child(name)
         if objectResourcePath.isfile():
-            # Handle quota adjustment
-            child = self.objectResourceWithName(name)
-            old_size = child.size()
-
             self._removedObjectResources.add(name)
             # FIXME: test for undo
             def do():
@@ -777,9 +773,6 @@
             self._transaction.addOperation(do, "remove object resource object %r" %
                                            (name,))
 
-            # Adjust quota
-            self._home.adjustQuotaUsedBytes(-old_size)
-
             self.notifyChanged()
         else:
             raise NoSuchObjectResourceError(name)

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,6 +1,6 @@
 # -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 Apple 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.
@@ -701,11 +701,11 @@
         ))
 
         if rows:
-            childID, objectID = rows[0]
-            child = (yield self.childWithID(childID))
-            if child and child.name() not in ignore_children:
-                objectResource = (yield child.objectResourceWithID(objectID))
-                results.append(objectResource)
+            for childID, objectID in rows:
+                child = (yield self.childWithID(childID))
+                if child and child.name() not in ignore_children:
+                    objectResource = (yield child.objectResourceWithID(objectID))
+                    results.append(objectResource)
         
         returnValue(results)
 
@@ -1284,40 +1284,34 @@
     @inlineCallbacks
     def removeObjectResourceWithName(self, name):
 
-        uid, old_size = (yield self._txn.execSQL(
+        uid = (yield self._txn.execSQL(
             "delete from %(name)s "
             "where %(column_RESOURCE_NAME)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s "
-            "returning %(column_UID)s, character_length(%(column_TEXT)s)" % self._objectTable,
+            "returning %(column_UID)s" % self._objectTable,
             [name, self._resourceID],
             raiseOnZeroRowCount=lambda:NoSuchObjectResourceError()
-        ))[0]
+        ))[0][0]
         self._objects.pop(name, None)
         self._objects.pop(uid, None)
         yield self._deleteRevision(name)
 
-        # Adjust quota
-        yield self._home.adjustQuotaUsedBytes(-old_size)
-
         self.notifyChanged()
 
 
     @inlineCallbacks
     def removeObjectResourceWithUID(self, uid):
 
-        name, old_size = (yield self._txn.execSQL(
+        name = (yield self._txn.execSQL(
             "delete from %(name)s "
             "where %(column_UID)s = %%s and %(column_PARENT_RESOURCE_ID)s = %%s "
-            "returning %(column_RESOURCE_NAME)s, character_length(%(column_TEXT)s)" % self._objectTable,
+            "returning %(column_RESOURCE_NAME)s" % self._objectTable,
             [uid, self._resourceID],
             raiseOnZeroRowCount=lambda:NoSuchObjectResourceError()
-        ))[0]
+        ))[0][0]
         self._objects.pop(name, None)
         self._objects.pop(uid, None)
         yield self._deleteRevision(name)
 
-        # Adjust quota
-        yield self._home.adjustQuotaUsedBytes(-old_size)
-
         self.notifyChanged()
 
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql	2011-01-25 16:07:58 UTC (rev 6799)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema_v1.sql	2011-01-25 16:18:01 UTC (rev 6800)
@@ -1,7 +1,7 @@
 -- -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
 
 ----
--- Copyright (c) 2010 Apple Inc. All rights reserved.
+-- Copyright (c) 2010-2011 Apple 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.
@@ -288,7 +288,7 @@
 ----------------
 
 create table ATTACHMENT (
-  CALENDAR_OBJECT_RESOURCE_ID integer       not null references CALENDAR_OBJECT on delete cascade,
+  DROPBOX_ID                  varchar(255)  not null,
   CONTENT_TYPE                varchar(255)  not null,
   SIZE                        integer       not null,
   MD5                         char(32)      not null,
@@ -296,11 +296,10 @@
   MODIFIED                    timestamp default timezone('UTC', CURRENT_TIMESTAMP),
   PATH                        varchar(1024) not null,
 
-  unique(CALENDAR_OBJECT_RESOURCE_ID, PATH)
+  unique(DROPBOX_ID, PATH)
 );
 
-create index ATTACHMENT_CALENDAR_OBJECT_RESOURCE_ID on
-  ATTACHMENT(CALENDAR_OBJECT_RESOURCE_ID);
+create index ATTACHMENT_DROPBOX_ID on ATTACHMENT(DROPBOX_ID);
 
 
 ------------------
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110125/8af4b915/attachment-0001.html>


More information about the calendarserver-changes mailing list