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

source_changes at macosforge.org source_changes at macosforge.org
Thu Sep 14 21:10:03 PDT 2006


Revision: 139
Author:   cdaboo at apple.com
Date:     2006-09-14 21:09:55 -0700 (Thu, 14 Sep 2006)

Log Message:
-----------
Quota support for CalDAV. Note I have chosen to exclude the index from counting towards quota.

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

Modified: CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/put_common.py	2006-09-15 03:40:47 UTC (rev 138)
+++ CalendarServer/branches/users/cdaboo/quota/twistedcaldav/method/put_common.py	2006-09-15 04:09:55 UTC (rev 139)
@@ -24,19 +24,18 @@
 
 __all__ = ["storeCalendarObjectResource"]
 
+from twisted.internet.defer import deferredGenerator
 from twisted.internet.defer import maybeDeferred
-from twisted.python import failure
-from twisted.python import log
+from twisted.internet.defer import waitForDeferred
+from twisted.python import failure, log
 from twisted.python.filepath import FilePath
 from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
 from twisted.web2.dav.element.base import PCDATAElement
-from twisted.web2.dav.fileop import copy
-from twisted.web2.dav.fileop import delete
-from twisted.web2.dav.fileop import put
+from twisted.web2.dav.fileop import copy, delete, put
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.util import joinURL, parentForURL
-from twisted.web2.http import HTTPError
+from twisted.web2.http import HTTPError, StatusResponse
 from twisted.web2.iweb import IResponse
 from twisted.web2.stream import MemoryStream
 
@@ -290,6 +289,7 @@
         Handle validation operations here.
         """
 
+        reserved = False
         if destinationcal:
             if not sourcecal:
                 # Valid content type check on the source resource if its not in a calendar collection
@@ -350,11 +350,33 @@
             # deferreds.
             destination_index = destinationparent.index()
             destination_index.reserveUID(uid)
+            reserved = True
         
         """
         Handle rollback setup here.
         """
 
+        # Do quota checks on destination and source before we start messing with adding other files
+        destquota = waitForDeferred(destination.quota(request))
+        yield destquota
+        destquota = destquota.getResult()
+        if destquota is not None and destination.exists():
+            old_dest_size = destination.quotaSize(request)
+        else:
+            old_dest_size = 0
+            
+        if source is not None:
+            sourcequota = waitForDeferred(source.quota(request))
+            yield sourcequota
+            sourcequota = sourcequota.getResult()
+            if sourcequota is not None and source.exists():
+                old_source_size = source.quotaSize(request)
+            else:
+                old_source_size = 0
+        else:
+            sourcequota = None
+            old_source_size = 0
+
         # We may need to restore the original resource data if the PUT/COPY/MOVE fails,
         # so rename the original file in case we need to rollback.
         overwrite = destination.exists()
@@ -389,9 +411,14 @@
 
         # Do put or copy based on whether source exists
         if source is not None:
-            d = maybeDeferred(copy, source.fp, destination.fp, destination_uri, "0")
+            response = maybeDeferred(copy, source.fp, destination.fp, destination_uri, "0")
         else:
-            d = maybeDeferred(put, MemoryStream(calendardata), destination.fp)
+            response = maybeDeferred(put, MemoryStream(calendardata), destination.fp)
+        response = waitForDeferred(response)
+        yield response
+        response = response.getResult()
+
+        response = IResponse(response)
         
         def doDestinationIndex(caltoindex):
             """
@@ -437,6 +464,13 @@
                 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")
@@ -461,45 +495,46 @@
             source.writeProperty(davxml.GETContentType.fromString("text/calendar"), request)
             return None
 
-        def doIndexing(response):
-            """
-            Callback after initial store operation succeeds.
-            """
-            logging.debug("Write to destination completed %r" % response, system="Store Resource")
-            response = IResponse(response)
-            if response.code in [responsecode.NO_CONTENT, responsecode.CREATED]:
-                if deletesource:
-                    doSourceDelete()
-        
-                if destinationcal:
-                    result = doDestinationIndex(calendar)
-                    if result is not None:
-                        rollback.Rollback()
-                        return result
-    
-                # Can now commit changes and forget the rollback details
-                rollback.Commit()
+        if destinationcal:
+            result = doDestinationIndex(calendar)
+            if result is not None:
+                rollback.Rollback()
+                yield result
+                return
 
-            return response
+        # Do quota check on destination
+        if destquota is not None:
+            # Get size of new/old resources
+            new_dest_size = destination.quotaSize(request)
+            diff_size = new_dest_size - old_dest_size
+            if diff_size >= destquota[0]:
+                log.err("Over quota: available %d, need %d" % (destquota[0], diff_size))
+                raise HTTPError(StatusResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, "Over quota"))
+            d = waitForDeferred(destination.quotaSizeAdjust(request, diff_size))
+            yield d
+            d.getResult()
 
-        def cleanUpIndex(f):
-            if destinationcal:
-                destination_index.unreserveUID(uid)
-            
-            # Always do the rollback operation: actually this will not
-            # rollback if the PUT was successful as the rollback will have
-            # been deactivated by a commit.
-            rollback.Rollback()
+        if deletesource:
+            doSourceDelete()
 
-            return f
+        # Can now commit changes and forget the rollback details
+        rollback.Commit()
 
-        d.addCallback(doIndexing)
-        d.addBoth(cleanUpIndex)
+        if reserved:
+            destination_index.unreserveUID(uid)
+            reserved = False
 
-        return d
+        yield response
+        return
 
     except:
+        if reserved:
+            destination_index.unreserveUID(uid)
+            reserved = False
+
         # Roll back changes to original server state. Note this may do nothing
         # if the rollback has already ocurred or changes already committed.
         rollback.Rollback()
         raise
+
+storeCalendarObjectResource = deferredGenerator(storeCalendarObjectResource)
\ No newline at end of file

Modified: CalendarServer/branches/users/cdaboo/quota/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/twistedcaldav/static.py	2006-09-15 03:40:47 UTC (rev 138)
+++ CalendarServer/branches/users/cdaboo/quota/twistedcaldav/static.py	2006-09-15 04:09:55 UTC (rev 139)
@@ -35,6 +35,7 @@
 
 import os
 import errno
+import stat
 from urlparse import urlsplit
 
 from twisted.internet.defer import deferredGenerator, fail, succeed, waitForDeferred
@@ -43,7 +44,7 @@
 from twisted.python.filepath import FilePath
 from twisted.web2 import responsecode
 from twisted.web2.dav import davxml
-from twisted.web2.dav.acl import TwistedPasswordProperty
+from twisted.web2.dav.auth import TwistedPasswordProperty
 from twisted.web2.dav.fileop import mkcollection, rmdir
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.idav import IDAVResource
@@ -307,6 +308,53 @@
         ]
 
     ##
+    # Quota
+    ##
+
+    def collectionQuotaUse(self, request):
+        """
+        Brute force determination of quota used by this collection.
+
+        @return: a C{int} containing the current used byte if this collection
+            is quota-controlled, or C{None} if not quota controlled.
+        """
+        assert self.isCollection(), "Only collections can have a quota root"
+
+        # Do default if not a calendar collection
+        if not self.isPseudoCalendarCollection():
+            return super(CalDAVFile, self).collectionQuotaUse(request)
+
+        def walktree(top, top_level = False):
+            """
+            Recursively descend the directory tree rooted at top,
+            calling the callback function for each regular file
+            """
+        
+            total = 0
+            for f in os.listdir(top):
+                
+                # Ignore the database
+                if top_level and f == db_basename:
+                    continue
+
+                pathname = os.path.join(top, f)
+                result = os.stat(pathname)
+                mode = result[stat.ST_MODE]
+                if stat.S_ISDIR(mode):
+                    # It's a directory, recurse into it
+                    total += walktree(pathname)
+                elif stat.S_ISREG(mode):
+                    # It's a file, call the callback function
+                    total += result[stat.ST_SIZE]
+                else:
+                    # Unknown file type, print a message
+                    pass
+        
+            return total
+        
+        return walktree(self.fp.path, True)
+
+    ##
     # Utilities
     ##
 

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


More information about the calendarserver-changes mailing list