[CalendarServer-changes] [146] CalendarServer/branches/users/cdaboo/quota/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Sep 18 12:47:37 PDT 2006


Revision: 146
Author:   cdaboo at apple.com
Date:     2006-09-18 12:47:33 -0700 (Mon, 18 Sep 2006)

Log Message:
-----------
Quota and max resource size support. Quota's are applied to a user's calendar home collection if set.
Static provisioning can override the global per user quota value set in the .plist file.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/quota/twistedcaldav/caldavxml.py
    CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/schedule_common.py
    CalendarServer/branches/users/cdaboo/quota/twistedcaldav/repository.py
    CalendarServer/branches/users/cdaboo/quota/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/quota/twistedcaldav/static.py

Modified: CalendarServer/branches/users/cdaboo/quota/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/twistedcaldav/caldavxml.py	2006-09-18 19:41:51 UTC (rev 145)
+++ CalendarServer/branches/users/cdaboo/quota/twistedcaldav/caldavxml.py	2006-09-18 19:47:33 UTC (rev 146)
@@ -256,6 +256,15 @@
 
     allowed_children = { (caldav_namespace, "calendar-data"): (0, None) }
 
+class MaxResourceSize (CalDAVTextElement):
+    """
+    Specifies restrictions on a calendar collection.
+    (CalDAV-access-15, section 5.2.5)
+    """
+    name = "max-resource-size"
+    hidden = True
+    protected = True
+
 class Calendar (CalDAVEmptyElement):
     """
     Denotes a calendar collection.

Modified: CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/put_common.py	2006-09-18 19:41:51 UTC (rev 145)
+++ CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/put_common.py	2006-09-18 19:47:33 UTC (rev 146)
@@ -45,6 +45,7 @@
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.ical import Component
 from twistedcaldav.instance import TooManyInstancesError
+from twistedcaldav.resource import CalDAVResource
 
 def storeCalendarObjectResource(
     request,
@@ -235,6 +236,23 @@
         
         return result, message
     
+    def validSizeCheck():
+        """
+        Make sure that the content-type of the source resource is text/calendar.
+        This test is only needed when the source is not in a calendar collection.
+        @param request: the L{twisted.web2.server.Request} for the current HTTP request.
+        @param source:  the L{Component} for the calendar to test.
+        """
+        result = True
+        message = ""
+        if CalDAVResource.sizeLimit is not None:
+            calsize = len(str(calendar))
+            if calsize > CalDAVResource.sizeLimit:
+                result = False
+                message = "Data size %d bytes is larger than allowed limit %d bytes" % (calsize, CalDAVResource.sizeLimit)
+
+        return result, message
+
     def noUIDConflict(uid):
         """
         Check that the UID of the new calendar object conforms to the requirements of
@@ -283,7 +301,6 @@
         
         return result, message, rname
 
-
     try:
         """
         Handle validation operations here.
@@ -333,7 +350,13 @@
                 # FIXME: We need this here because we have to re-index the destination. Ideally it
                 # would be better to copy the index entries from the source and add to the destination.
                 calendar = source.iCalendar()
-                
+
+            # Valid calendar data size check
+            result, message = validSizeCheck()
+            if not result:
+                log.err(message)
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "max-resource-size")))
+
             # uid conflict check
             if not isiTIP:
                 result, message, rname = noUIDConflict(uid)
@@ -464,17 +487,10 @@
                 logging.debug("Source index removed %s" % (source.fp.path,), system="Store Resource")
 
             # Delete the source resource
-            if sourcequota is not None:
-                delete_size = 0 - old_source_size
-                d = waitForDeferred(source.quotaSizeAdjust(request, delete_size))
-                yield d
-                d.getResult()
-
-            # Delete the source resource
             delete(source_uri, source.fp, "0")
             rollback.source_deleted = True
             logging.debug("Source removed %s" % (source.fp.path,), system="Store Resource")
-        
+
         def doSourceIndexRecover():
             """
             Do source resource indexing. This only gets called when restoring
@@ -495,6 +511,16 @@
             source.writeProperty(davxml.GETContentType.fromString("text/calendar"), request)
             return None
 
+        if deletesource:
+            doSourceDelete()
+            # Update quota
+            if sourcequota is not None:
+                delete_size = 0 - old_source_size
+                d = waitForDeferred(source.quotaSizeAdjust(request, delete_size))
+                yield d
+                d.getResult()
+
+
         if destinationcal:
             result = doDestinationIndex(calendar)
             if result is not None:
@@ -514,9 +540,6 @@
             yield d
             d.getResult()
 
-        if deletesource:
-            doSourceDelete()
-
         # Can now commit changes and forget the rollback details
         rollback.Commit()
 

Modified: CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/schedule_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/schedule_common.py	2006-09-18 19:41:51 UTC (rev 145)
+++ CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/schedule_common.py	2006-09-18 19:47:33 UTC (rev 146)
@@ -43,10 +43,8 @@
 from twistedcaldav.method import report_common
 from twistedcaldav.method.put_common import storeCalendarObjectResource
 from twistedcaldav.resource import CalendarPrincipalCollectionResource, isScheduleOutboxResource, isCalendarCollectionResource
-from twistedcaldav.static import CalDAVFile
 
 import md5
-import os
 import time
 
 def processScheduleRequest(self, method, request):
@@ -195,8 +193,10 @@
         name = md5.new(str(calendar) + str(time.time()) + self.fp.path).hexdigest() + ".ics"
         
         # Save a copy of the calendar data into the Outbox
-        child = CalDAVFile(os.path.join(self.fp.path, name))
-        childURL = request.uri + name
+        childURL = joinURL(request.uri, name)
+        child = waitForDeferred(request.locateResource(childURL))
+        yield child
+        child = child.getResult()
         responses.setLocation(childURL)
         
         d = waitForDeferred(
@@ -297,7 +297,6 @@
                             # properly manage the free busy set that should not prevent us from working.
                             continue
                          
-                        # TODO: make this a waitForDeferred and yield it
                         matchtotal = waitForDeferred(report_common.generateFreeBusyInfo(request, cal, fbinfo, timerange, matchtotal))
                         yield matchtotal
                         matchtotal = matchtotal.getResult()
@@ -319,8 +318,10 @@
                 name = md5.new(str(calendar) + str(time.time()) + inbox.fp.path).hexdigest() + ".ics"
                 
                 # Get a resource for the new item
-                child = CalDAVFile(os.path.join(inbox.fp.path, name))
                 childURL = joinURL(inboxURL, name)
+                child = waitForDeferred(request.locateResource(childURL))
+                yield child
+                child = child.getResult()
             
                 # Copy calendar to inbox (doing fan-out)
                 d = waitForDeferred(

Modified: CalendarServer/branches/users/cdaboo/quota/twistedcaldav/repository.py
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/twistedcaldav/repository.py	2006-09-18 19:41:51 UTC (rev 145)
+++ CalendarServer/branches/users/cdaboo/quota/twistedcaldav/repository.py	2006-09-18 19:47:33 UTC (rev 146)
@@ -39,6 +39,7 @@
 from twistedcaldav.directory import DirectoryGroupPrincipalProvisioningResource
 from twistedcaldav.directory import DirectoryUserPrincipalProvisioningResource
 from twistedcaldav.directory import DirectoryPrincipalProvisioningResource
+from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.static import CalDAVFile, CalendarHomeFile, CalendarPrincipalFile
 from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.static import CalendarPrincipalProvisioningResource
@@ -97,6 +98,7 @@
 ELEMENT_NAME = "name"
 ELEMENT_CUADDR = "cuaddr"
 ELEMENT_CALENDAR = "calendar"
+ELEMENT_QUOTA = "quota"
 ELEMENT_AUTORESPOND = "autorespond"
 ATTRIBUTE_REPEAT = "repeat"
 
@@ -127,19 +129,30 @@
     and optionally provisions accounts.
     """
     
-    def __init__(self, docroot, doAccounts, resetACLs = False):
+    def __init__(self, docroot, doAccounts, resetACLs = False, maxsize = None, quota = None):
         """
         @param docroot:    file system path to use as the root.
         @param doAccounts: if True accounts will be auto-provisioned, if False
             no auto-provisioning is done
         @param resetACLs:  if True, when auto-provisioning access control privileges are initialised
             in an appropriate fashion for user accounts, if False no privileges are set or changed.
+        @param maxsize:    maximum size in bytes for any calendar object resource, C{int} to set size,
+            if <= 0, then no limit will be set.
+        @param quota:    maximum quota size in bytes for a user's calendar home, C{int} to set size,
+            if <= 0, then no limit will be set.
         """
         self.docRoot = DocRoot(docroot)
         self.doAccounts = doAccounts
         self.accounts = Provisioner()
         self.resetACLs = resetACLs
+        self.maxsize = maxsize
+        self.quota = quota
         
+        if self.maxsize <= 0:
+            self.maxsized = None
+        if self.quota <= 0:
+            self.quota = None
+        
     def buildFromFile(self, file):
         """
         Parse the required information from an XML file.
@@ -161,6 +174,10 @@
         self.docRoot.build()
         if self.doAccounts:
             self.accounts.provision(self.docRoot.principalCollection, self.docRoot.calendarHome, self.resetACLs)
+            
+        # Handle global quota value
+        CalendarHomeFile.quotaLimit = self.quota
+        CalDAVResource.sizeLimit = self.maxsize
 
     def parseXML(self, node):
         """
@@ -509,7 +526,7 @@
                 else:
                     repeat = 1
 
-                principal = ProvisionPrincipal("", "", "", [], [], None, False)
+                principal = ProvisionPrincipal("", "", "", [], [], None, None, False)
                 principal.parseXML( child )
                 self.items.append((repeat, principal))
     
@@ -588,6 +605,7 @@
             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)
@@ -610,6 +628,9 @@
                     )
                 )
         
+        # 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/"))))
@@ -669,7 +690,7 @@
     """
     Contains provision information for one user.
     """
-    def __init__(self, uid, pswd, name, cuaddrs, calendars, acl, autorespond):
+    def __init__(self, uid, pswd, name, cuaddrs, calendars, acl, quota, autorespond):
         """
         @param uid:           user id.
         @param pswd:          clear-text password for this user.
@@ -677,6 +698,8 @@
         @param cuaddr:        list of calendar user addresses.
         @param calendars:     list of calendars to auto-create.
         @param acl:           ACL to apply to calendar home
+        @param quota:         quota allowed on user's calendar home C{int} size in bytes
+            or C{None} if no quota
         @param autorespond    auto-respond to scheduling requests
         """
         
@@ -686,6 +709,7 @@
         self.cuaddrs = cuaddrs
         self.calendars = calendars
         self.acl = acl
+        self.quota = quota
         self.autorespond = autorespond
 
     def repeat(self, ctr):
@@ -714,7 +738,7 @@
             else:
                 cuaddrs.append(cuaddr)
         
-        return ProvisionPrincipal(uid, pswd, name, cuaddrs, self.calendars, self.acl, self.autorespond)
+        return ProvisionPrincipal(uid, pswd, name, cuaddrs, self.calendars, self.acl, self.quota, self.autorespond)
 
     def parseXML( self, node ):
         for child in node._get_childNodes():
@@ -733,6 +757,9 @@
             elif child._get_localName() == ELEMENT_CALENDAR:
                 if child.firstChild is not None:
                    self.calendars.append(child.firstChild.data.encode("utf-8"))
+            elif child._get_localName() == ELEMENT_QUOTA:
+                if child.firstChild is not None:
+                   self.quota = int(child.firstChild.data.encode("utf-8"))
             elif child._get_localName() == ELEMENT_ACL:
                 self.acl = ACL()
                 self.acl.parseXML(child)

Modified: CalendarServer/branches/users/cdaboo/quota/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/twistedcaldav/resource.py	2006-09-18 19:41:51 UTC (rev 145)
+++ CalendarServer/branches/users/cdaboo/quota/twistedcaldav/resource.py	2006-09-18 19:47:33 UTC (rev 146)
@@ -41,8 +41,8 @@
 from twisted.internet.defer import Deferred, maybeDeferred, succeed
 from twisted.internet.defer import deferredGenerator, waitForDeferred
 from twisted.web2 import responsecode
-from twisted.web2.dav import auth, davxml
-from twisted.web2.dav.resource import DAVPrincipalResource
+from twisted.web2.dav import davxml
+from twisted.web2.dav.resource import AccessDeniedError, DAVPrincipalResource
 from twisted.web2.dav.davxml import dav_namespace
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.resource import DAVResource, TwistedACLInheritable
@@ -72,6 +72,10 @@
     """
     implements(ICalDAVResource)
 
+    # A global limit for the size of calendar object resources. Either a C{int} (size in bytes) to limit
+    # resources to that size, or C{None} for no limit.
+    sizeLimit = None
+
     ##
     # HTTP
     ##
@@ -150,6 +154,12 @@
                         "version"     : "2.0",
                     }),
                 ))
+            elif name == "max-resource-size":
+                # CalDAV-access-15, section 5.2.5
+                if CalDAVResource.sizeLimit is not None:
+                    return succeed(caldavxml.MaxResourceSize.fromString(
+                        str(CalDAVResource.sizeLimit)
+                    ))
 
         return super(CalDAVResource, self).readProperty(property, request)
 
@@ -220,7 +230,6 @@
         assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
 
         def _checkAccessEb(failure):
-            from twisted.web2.dav.acl import AccessDeniedError
             failure.trap(AccessDeniedError)
             
             reactor.callLater(0, _getChild)

Modified: CalendarServer/branches/users/cdaboo/quota/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/twistedcaldav/static.py	2006-09-18 19:41:51 UTC (rev 145)
+++ CalendarServer/branches/users/cdaboo/quota/twistedcaldav/static.py	2006-09-18 19:47:33 UTC (rev 146)
@@ -40,7 +40,6 @@
 
 from twisted.internet.defer import deferredGenerator, fail, succeed, waitForDeferred
 from twisted.python import log
-from twisted.python.failure import Failure
 from twisted.python.filepath import FilePath
 from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
@@ -50,6 +49,7 @@
 from twisted.web2.dav.idav import IDAVResource
 from twisted.web2.dav.resource import TwistedACLInheritable
 from twisted.web2.dav.resource import TwistedACLProperty
+from twisted.web2.dav.resource import TwistedQuotaRootProperty
 from twisted.web2.dav.static import DAVFile
 from twisted.web2.dav.util import parentForURL, joinURL, bindMethods
 from twisted.web2.http import HTTPError, StatusResponse
@@ -58,7 +58,6 @@
 from twistedcaldav import customxml
 from twistedcaldav.ical import Component as iComponent
 from twistedcaldav.ical import Property as iProperty
-from twistedcaldav.icaldav import ICalDAVResource
 from twistedcaldav.index import Index, IndexSchedule, db_basename
 from twistedcaldav.resource import CalDAVResource, isPseudoCalendarCollectionResource, CalendarPrincipalResource
 from twistedcaldav.resource import ScheduleInboxResource, ScheduleOutboxResource, CalendarPrincipalCollectionResource
@@ -584,6 +583,11 @@
     """
     L{CalDAVFile} calendar home collection resource.
     """
+    
+    # A global quota limit for all calendar homes. Either a C{int} (size in bytes) to limit
+    # quota to that size, or C{None} for no limit.
+    quotaLimit = None
+
     def __init__(self, path):
         """
         @param path: the path to the file which will back the resource.
@@ -605,6 +609,26 @@
     def createSimilarFile(self, path):
         return CalDAVFile(path)
 
+    ##
+    # Quota
+    ##
+
+    def hasQuotaRoot(self, request):
+        """
+        @return: a C{True} if this resource has quota root, C{False} otherwise.
+        """
+        return self.hasDeadProperty(TwistedQuotaRootProperty) or CalendarHomeFile.quotaLimit is not None
+    
+    def quotaRoot(self, request):
+        """
+        @return: a C{int} containing the maximum allowed bytes if this collection
+            is quota-controlled, or C{None} if not quota controlled.
+        """
+        if self.hasDeadProperty(TwistedQuotaRootProperty):
+            return int(str(self.readDeadProperty(TwistedQuotaRootProperty)))
+        else:
+            return CalendarHomeFile.quotaLimit
+
 class CalendarHomeProvisioningFile (CalDAVFile):
     """
     L{CalDAVFile} resource which provisions calendar home collections as needed.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20060918/b3856d61/attachment.html


More information about the calendarserver-changes mailing list