[CalendarServer-changes] [129]
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted
source_changes at macosforge.org
source_changes at macosforge.org
Tue Sep 12 14:18:23 PDT 2006
Revision: 129
Author: cdaboo at apple.com
Date: 2006-09-12 14:18:22 -0700 (Tue, 12 Sep 2006)
Log Message:
-----------
Initial Quota (rfc4331) support. 4331 properties have been defined along with a pair
of private properties that indicate the current quota root and the current quota usage
on a collection. Quota checking and updating can be done. Put has been modified to use
a new storeResource api that takes care of quota checking and rollback if quota is
exceeded (basic behaviour is copied from CalDAV implementation). Copy/move have not yet
been modified to support this.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.resource.patch
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.static.patch
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.davxml.patch
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.idav.patch
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.method.put
CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
Added: CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.davxml.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.davxml.patch (rev 0)
+++ CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.davxml.patch 2006-09-12 21:18:22 UTC (rev 129)
@@ -0,0 +1,28 @@
+Index: twisted/web2/dav/davxml.py
+===================================================================
+--- twisted/web2/dav/davxml.py (revision 17935)
++++ twisted/web2/dav/davxml.py (working copy)
+@@ -45,6 +45,7 @@
+ from twisted.web2.dav.element.rfc2518 import *
+ from twisted.web2.dav.element.rfc3253 import *
+ from twisted.web2.dav.element.rfc3744 import *
++from twisted.web2.dav.element.rfc4331 import *
+
+ #
+ # Register all XML elements with the parser
+@@ -56,6 +57,7 @@
+ import twisted.web2.dav.element.rfc2518
+ import twisted.web2.dav.element.rfc3253
+ import twisted.web2.dav.element.rfc3744
++import twisted.web2.dav.element.rfc4331
+
+ __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) +
+- registerElements(twisted.web2.dav.element.rfc3744)
++ registerElements(twisted.web2.dav.element.rfc3744) +
++ registerElements(twisted.web2.dav.element.rfc4331)
+ )
Added: CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch (rev 0)
+++ CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch 2006-09-12 21:18:22 UTC (rev 129)
@@ -0,0 +1,10 @@
+Index: twisted/web2/dav/element/__init__.py
+===================================================================
+--- twisted/web2/dav/element/__init__.py (revision 17935)
++++ twisted/web2/dav/element/__init__.py (working copy)
+@@ -35,4 +35,5 @@
+ "rfc2518",
+ "rfc3253",
+ "rfc3744",
++ "rfc4331",
+ ]
Added: CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch (rev 0)
+++ CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch 2006-09-12 21:18:22 UTC (rev 129)
@@ -0,0 +1,60 @@
+Index: twisted/web2/dav/element/rfc4331.py
+===================================================================
+--- twisted/web2/dav/element/rfc4331.py (revision 0)
++++ twisted/web2/dav/element/rfc4331.py (revision 0)
+@@ -0,0 +1,55 @@
++##
++# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
++#
++# Permission is hereby granted, free of charge, to any person obtaining a copy
++# of this software and associated documentation files (the "Software"), to deal
++# in the Software without restriction, including without limitation the rights
++# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
++# copies of the Software, and to permit persons to whom the Software is
++# furnished to do so, subject to the following conditions:
++#
++# The above copyright notice and this permission notice shall be included in all
++# copies or substantial portions of the Software.
++#
++# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++# SOFTWARE.
++#
++# DRI: Cyrus Daboo, cdaboo at apple.com
++##
++
++"""
++RFC 4331 (Quota and Size Properties for WebDAV Collections) XML Elements
++
++This module provides XML element definitions for use with WebDAV.
++
++See RFC 4331: http://www.ietf.org/rfc/rfc4331.txt
++"""
++
++from twisted.web2.dav.element.base import WebDAVTextElement
++
++##
++# Section 3 & 4 (Quota Properties)
++##
++
++class QuotaAvailableBytes (WebDAVTextElement):
++ """
++ Property which contains the the number of bytes available under the
++ current quota to store data in a collection (RFC 4331, section 3)
++ """
++ name = "quota-available-bytes"
++ hidden = True
++ protected = True
++
++class QuotaUsedBytes (WebDAVTextElement):
++ """
++ Property which contains the the number of bytes used under the
++ current quota to store data in a collection (RFC 4331, section 4)
++ """
++ name = "quota-used-bytes"
++ hidden = True
++ protected = True
Added: CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.idav.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.idav.patch (rev 0)
+++ CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.idav.patch 2006-09-12 21:18:22 UTC (rev 129)
@@ -0,0 +1,77 @@
+Index: twisted/web2/dav/idav.py
+===================================================================
+--- twisted/web2/dav/idav.py (revision 18078)
++++ twisted/web2/dav/idav.py (working copy)
+@@ -187,6 +187,72 @@
+ the specified principal.
+ """
+
++ ##
++ # Quota
++ ##
++
++ def quota(request):
++ """
++ Get current available & used quota values for this resource's quota root
++ collection.
++
++ @return: a C{tuple} containing two C{int}'s the first is
++ quota-available-bytes, the second is quota-used-bytes, or
++ C{None} if quota is not defined on the resource.
++ """
++
++ def quotaRoot(request):
++ """
++ Get the quota root (max. allowed bytes) value for this collection.
++
++ @return: a C{int} containing the maximum allowed bytes if this collection
++ is quota-controlled, or C{None} if not quota controlled.
++ """
++
++ def setQuotaRoot(request, maxsize):
++ """
++ Set the quota root (max. allowed bytes) value for this collection.
++
++ @param maxsize: a C{int} containing the maximum allowed bytes for the contents
++ of this collection.
++ """
++
++ def quotaSize(request):
++ """
++ Get the size of this resource.
++ TODO: Take into account size of dead-properties.
++
++ @return: a C{int} containing the size of the resource.
++ """
++
++ def currentQuotaUse(request):
++ """
++ Get the cached quota use value, or if not present (or invalid) determine
++ quota use by brute force.
++
++ @return: a C{int} containing the current used byte if this collection
++ is quota-controlled, or C{None} if not quota controlled.
++ """
++
++ def updateQuotaUse(request, adjust):
++ """
++ Adjust current quota use on this all all parent collections that also
++ have quota roots.
++
++ @param adjust: a C{int} containing the number of bytes added (positive) or
++ removed (negative) that should be used to adjust the cached total.
++ @return: a C{int} containing the current used byte if this collection
++ is quota-controlled, or C{None} if not quota controlled.
++ """
++
++ def determineActualQuotaUse(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.
++ """
++
+ class IDAVPrincipalResource (IDAVResource):
+ """
+ WebDAV principal resource. (RFC 3744, section 2)
Added: CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.method.put
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.method.put (rev 0)
+++ CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.method.put 2006-09-12 21:18:22 UTC (rev 129)
@@ -0,0 +1,19 @@
+Index: twisted/web2/dav/method/put.py
+===================================================================
+--- twisted/web2/dav/method/put.py (revision 17951)
++++ twisted/web2/dav/method/put.py (working copy)
+@@ -22,6 +22,7 @@
+ #
+ # DRI: Wilfredo Sanchez, wsanchez at apple.com
+ ##
++from twisted.web2.dav.method import put_common
+
+ """
+ WebDAV PUT method
+@@ -107,4 +108,5 @@
+ # to return a MULTI_STATUS response, which is WebDAV-specific (and PUT is
+ # not).
+ #
+- return put(request.stream, self.fp)
++ #return put(request.stream, self.fp)
++ return put_common.storeResource(request, destination=self, destination_uri=request.uri)
Added: CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch (rev 0)
+++ CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch 2006-09-12 21:18:22 UTC (rev 129)
@@ -0,0 +1,237 @@
+Index: twisted/web2/dav/method/put_common.py
+===================================================================
+--- twisted/web2/dav/method/put_common.py (revision 0)
++++ twisted/web2/dav/method/put_common.py (revision 0)
+@@ -0,0 +1,232 @@
++##
++# 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
++##
++from twisted.internet.defer import waitForDeferred
++from twisted.internet.defer import deferredGenerator
++from twisted.web2.http import StatusResponse
++
++"""
++PUT/COPY/MOVE common behavior.
++"""
++
++__version__ = "0.0"
++
++__all__ = ["storeCalendarObjectResource"]
++
++from twisted.internet.defer import maybeDeferred
++from twisted.python import failure
++from twisted.python import log
++from twisted.python.filepath import FilePath
++from twisted.web2 import responsecode
++from twisted.web2.dav.fileop import copy
++from twisted.web2.dav.fileop import delete
++from twisted.web2.dav.fileop import put
++from twisted.web2.http import HTTPError
++from twisted.web2.iweb import IResponse
++
++def storeResource(
++ request,
++ source=None, source_uri=None,
++ destination=None, destination_uri=None,
++ deletesource=False
++):
++ """
++ Function that does common PUT/COPY/MOVE behaviour.
++
++ @param request: the L{twisted.web2.server.Request} for the current HTTP request.
++ @param source: the L{DAVFile} for the source resource to copy from, or None if source data
++ is to be read from the request.
++ @param source_uri: the URI for the source resource.
++ @param destination: the L{DAVFile} for the destination resource to copy into.
++ @param destination_uri: the URI for the destination resource.
++ @param deletesource: True if the source resource is to be deleted on successful completion, False otherwise.
++ @return: status response.
++ """
++
++ try:
++ assert request is not None and destination is not None and destination_uri is not None
++ assert (source is None) or (source is not None and source_uri is not None)
++ assert not deletesource or (deletesource and source is not None)
++ except AssertionError:
++ log.err("Invalid arguments to storeResource():")
++ log.err("request=%s\n" % (request,))
++ log.err("source=%s\n" % (source,))
++ log.err("source_uri=%s\n" % (source_uri,))
++ log.err("destination=%s\n" % (destination,))
++ log.err("destination_uri=%s\n" % (destination_uri,))
++ log.err("deletesource=%s\n" % (deletesource,))
++ raise
++
++ class RollbackState(object):
++ """
++ This class encapsulates the state needed to rollback the entire PUT/COPY/MOVE
++ transaction, leaving the server state the same as it was before the request was
++ processed. The DoRollback method will actually execute the rollback operations.
++ """
++
++ def __init__(self):
++ self.active = True
++ self.source_copy = None
++ self.destination_copy = None
++ self.destination_created = False
++ self.source_deleted = False
++
++ def Rollback(self):
++ """
++ Rollback the server state. Do not allow this to raise another exception. If
++ rollback fails then we are going to be left in an awkward state that will need
++ to be cleaned up eventually.
++ """
++ if self.active:
++ self.active = False
++ log.err("Rollback: rollback")
++ try:
++ if self.source_copy and self.source_deleted:
++ self.source_copy.moveTo(source.fp)
++ log.err("Rollback: source restored %s to %s" % (self.source_copy.path, source.fp.path))
++ self.source_copy = None
++ self.source_deleted = False
++ if self.destination_copy:
++ destination.fp.remove()
++ log.err("Rollback: destination restored %s to %s" % (self.destination_copy.path, destination.fp.path))
++ self.destination_copy.moveTo(destination.fp)
++ self.destination_copy = None
++ elif self.destination_created:
++ destination.fp.remove()
++ log.err("Rollback: destination removed %s" % (destination.fp.path,))
++ self.destination_created = False
++ except:
++ log.err("Rollback: exception caught and not handled: %s" % failure.Failure())
++
++ def Commit(self):
++ """
++ Commit the resource changes by wiping the rollback state.
++ """
++ if self.active:
++ log.err("Rollback: commit")
++ self.active = False
++ if self.source_copy:
++ self.source_copy.remove()
++ log.err("Rollback: removed source backup %s" % (self.source_copy.path,))
++ self.source_copy = None
++ if self.destination_copy:
++ self.destination_copy.remove()
++ log.err("Rollback: removed destination backup %s" % (self.destination_copy.path,))
++ self.destination_copy = None
++ self.destination_created = False
++ self.source_deleted = False
++
++ rollback = RollbackState()
++
++ try:
++ """
++ Handle validation operations here.
++ """
++
++ """
++ 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()
++ if overwrite:
++ rollback.destination_copy = FilePath(destination.fp.path)
++ rollback.destination_copy.path += ".rollback"
++ destination.fp.copyTo(rollback.destination_copy)
++ else:
++ rollback.destination_created = True
++
++ if deletesource:
++ rollback.source_copy = FilePath(source.fp.path)
++ rollback.source_copy.path += ".rollback"
++ source.fp.copyTo(rollback.source_copy)
++
++ """
++ Handle actual store operations here.
++ """
++
++ # Do put or copy based on whether source exists
++ if source is not None:
++ response = maybeDeferred(copy, source.fp, destination.fp, destination_uri, "0")
++ else:
++ response = maybeDeferred(put, request.stream, destination.fp)
++ response = waitForDeferred(response)
++ yield response
++ response = response.getResult()
++
++ response = IResponse(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))
++ rollback.Rollback()
++ raise HTTPError(StatusResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, "Over quota"))
++ d = waitForDeferred(destination.quotaSizeAdjust(request, diff_size))
++ yield d
++ d.getResult()
++
++ if deletesource:
++ # Delete the source resource
++ if sourcequota is not None:
++ delete_size = 0 - old_source_size
++ source.updateQuotaUse(request, delete_size)
++ d = waitForDeferred(source.quotaSizeAdjust(request, delete_size))
++ yield d
++ d.getResult()
++
++ delete(source_uri, source.fp, "0")
++ rollback.source_deleted = True
++
++ # Can now commit changes and forget the rollback details
++ rollback.Commit()
++
++ yield response
++ return
++
++ except:
++ # 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
++
++storeResource = deferredGenerator(storeResource)
Modified: CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.resource.patch 2006-09-12 21:02:19 UTC (rev 128)
+++ CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.resource.patch 2006-09-12 21:18:22 UTC (rev 129)
@@ -2,7 +2,49 @@
===================================================================
--- twisted/web2/dav/resource.py (revision 18074)
+++ twisted/web2/dav/resource.py (working copy)
-@@ -599,7 +599,7 @@
+@@ -125,6 +125,8 @@
+ (dav_namespace, "acl-restrictions" ), # RFC 3744, section 5.6
+ (dav_namespace, "inherited-acl-set" ), # RFC 3744, section 5.7
+ (dav_namespace, "principal-collection-set" ), # RFC 3744, section 5.8
++ (dav_namespace, "quota-available-bytes" ), # RFC 4331, section 3
++ (dav_namespace, "quota-used-bytes" ), # RFC 4331, section 4
+
+ (twisted_dav_namespace, "resource-class"),
+ )
+@@ -264,6 +266,32 @@
+ # TODO: Merge change from original patch
+ lambda: self.safeAccessControlList(request)
+ )
++
++ if name == "quota-available-bytes":
++ def callback(qvalue):
++ if qvalue is None:
++ raise HTTPError(StatusResponse(
++ responsecode.NOT_FOUND,
++ "Property %s does not exist." % (sname,)
++ ))
++ else:
++ return davxml.QuotaAvailableBytes(str(qvalue[0]))
++ d = self.quota(request)
++ d.addCallback(callback)
++ return d
++
++ if name == "quota-used-bytes":
++ def callback(qvalue):
++ if qvalue is None:
++ raise HTTPError(StatusResponse(
++ responsecode.NOT_FOUND,
++ "Property %s does not exist." % (sname,)
++ ))
++ else:
++ return davxml.QuotaUsedBytes(str(qvalue[1]))
++ d = self.quota(request)
++ d.addCallback(callback)
++ return d
+
+ if namespace == twisted_dav_namespace:
+ if name == "resource-class":
+@@ -599,7 +627,7 @@
else:
factory = request.credentialFactories[authHeader[0]]
@@ -11,3 +53,212 @@
# Try to match principals in each principal collection on
# the resource
+@@ -1526,6 +1554,179 @@
+ return None
+
+ ##
++ # Quota
++ ##
++
++ """
++ The basic policy here is to define a private 'quota-root' property on a collection.
++ That property will contain the maximum allowed bytes for the collections and all
++ its contents.
++
++ In order to determine the quota property values on a resource, the server must look
++ for the private property on that resource and any of its parents. If found on a parent,
++ then that parent should be queried for quota information. If not found, no quota
++ exists for the resource.
++
++ To determine tha actual quota in use we will cache the used byte count on the quota-root
++ collection in another private property. It is the servers responsibility to
++ keep that property up to date by adjusting it after every PUT, DELETE, COPY,
++ MOVE, MKCOL, PROPPATCH, ACL, POST or any other method that may affect the size of
++ stored data. If the private property is not present, the server will fall back to
++ getting the size by iterating over all resources (this is done in static.py).
++
++ """
++
++ def quota(self, request):
++ """
++ Get current available & used quota values for this resource's quota root
++ collection.
++
++ @return: an L{Defered} with result C{tuple} containing two C{int}'s the first is
++ quota-available-bytes, the second is quota-used-bytes, or
++ C{None} if quota is not defined on the resource.
++ """
++
++ # See if already cached
++ if hasattr(request, "quota"):
++ yield request.quota
++ return
++
++ # Check this resource first
++ if self.isCollection():
++ qroot = self.quotaRoot(request)
++ if qroot is not None:
++ used = self.currentQuotaUse(request)
++ available = qroot - used
++ if available < 0:
++ available = 0
++ request.quota = (available, used)
++ yield request.quota
++ return
++
++ # Check the next parent
++ url = request.urlForResource(self)
++ if url != "/":
++ parent = waitForDeferred(request.locateResource(parentForURL(url)))
++ yield parent
++ parent = parent.getResult()
++ d = waitForDeferred(parent.quota(request))
++ yield d
++ request.quota = d.getResult()
++ else:
++ request.quota = None
++
++ yield request.quota
++ return
++
++ quota = deferredGenerator(quota)
++
++ 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.
++ """
++ assert self.isCollection(), "Only collections can have a quota root"
++ if self.hasDeadProperty(TwistedQuotaRootProperty):
++ return int(str(self.readDeadProperty(TwistedQuotaRootProperty)))
++ else:
++ return None
++
++ def setQuotaRoot(self, request, maxsize):
++ """
++ @param maxsize: a C{int} containing the maximum allowed bytes for the contents
++ of this collection.
++ """
++ assert self.isCollection(), "Only collections can have a quota root"
++ assert isinstance(maxsize, int), "maxsize must be an int"
++
++ self.writeDeadProperty(TwistedQuotaRootProperty.fromString(str(maxsize)))
++
++ def quotaSize(self, request):
++ """
++ Get the size of this resource.
++ TODO: Take into account size of dead-properties.
++
++ @return: a C{int} containing the size of the resource.
++ """
++ unimplemented(self)
++
++ def quotaSizeAdjust(self, request, adjust):
++ """
++ Update the quota used value on all quota root parents of this resource.
++
++ @param adjust: a C{int} containing the number of bytes added (positive) or
++ removed (negative) that should be used to adjust the cached total.
++ """
++
++ # Check this resource first
++ if self.isCollection():
++ if self.hasDeadProperty(TwistedQuotaRootProperty):
++ self.updateQuotaUse(request, adjust)
++
++ # Check the next parent
++ url = request.urlForResource(self)
++ if url != "/":
++ parent = waitForDeferred(request.locateResource(parentForURL(url)))
++ yield parent
++ parent = parent.getResult()
++ d = waitForDeferred(parent.quotaSizeAdjust(request, adjust))
++ yield d
++ d.getResult()
++
++ yield None
++
++ quotaSizeAdjust = deferredGenerator(quotaSizeAdjust)
++
++ def currentQuotaUse(self, request):
++ """
++ Get the cached quota use value, or if not present (or invalid) determine
++ quota use by brute force.
++
++ @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"
++ assert self.hasDeadProperty(TwistedQuotaRootProperty), "Quota use only on quota root collection"
++
++ # Try to get the cached value property
++ if self.hasDeadProperty(TwistedQuotaUsedProperty):
++ return int(str(self.readDeadProperty(TwistedQuotaUsedProperty)))
++ else:
++ # Do brute force size determination
++ result = self.determineActualQuotaUse(request)
++
++ # Cache the brute force value in the private property
++ self.writeDeadProperty(TwistedQuotaUsedProperty.fromString(str(result)))
++
++ return result
++
++ def updateQuotaUse(self, request, adjust):
++ """
++ Update the quota used value on this resource.
++
++ @param adjust: a C{int} containing the number of bytes added (positive) or
++ removed (negative) that should be used to adjust the cached total.
++ @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"
++
++ # Get current value
++ size = self.currentQuotaUse(request)
++ size += adjust
++ self.writeDeadProperty(TwistedQuotaUsedProperty.fromString(str(size)))
++
++ def determineActualQuotaUse(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"
++ unimplemented(self)
++
++ ##
+ # HTTP
+ ##
+
+@@ -1631,6 +1832,28 @@
+
+ davxml.registerElement(TwistedAccessDisabledProperty)
+
++"""
++When set on a collection, this property indicates that the collection has a quota limit for
++the size of all resources stored in the collection (and any associate meta-data such as properties).
++The value is a number - the maximum size in bytes allowed.
++"""
++class TwistedQuotaRootProperty (davxml.WebDAVTextElement):
++ namespace = twisted_private_namespace
++ name = "quota-root"
++
++davxml.registerElement(TwistedQuotaRootProperty)
++
++"""
++When set on a collection, this property contains the cached running total of the size of all
++resources stored in the collection (and any associate meta-data such as properties).
++The value is a number - the size in bytes used.
++"""
++class TwistedQuotaUsedProperty (davxml.WebDAVTextElement):
++ namespace = twisted_private_namespace
++ name = "quota-used"
++
++davxml.registerElement(TwistedQuotaUsedProperty)
++
+ allACL = davxml.ACL(
+ davxml.ACE(
+ davxml.Principal(davxml.All()),
Modified: CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.static.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.static.patch 2006-09-12 21:02:19 UTC (rev 128)
+++ CalendarServer/branches/users/cdaboo/quota/lib-patches/Twisted/twisted.web2.dav.static.patch 2006-09-12 21:18:22 UTC (rev 129)
@@ -2,8 +2,16 @@
===================================================================
--- twisted/web2/dav/static.py (revision 18074)
+++ twisted/web2/dav/static.py (working copy)
-@@ -34,6 +34,8 @@
+@@ -29,6 +29,7 @@
+ __all__ = ["DAVFile"]
+
+ import os
++import stat
+
+ from twisted.python import log
from twisted.internet.defer import succeed, deferredGenerator, waitForDeferred
+@@ -34,6 +35,8 @@
+ from twisted.internet.defer import succeed, deferredGenerator, waitForDeferred
from twisted.web2.static import File
from twisted.web2 import dirlist
+from twisted.web2 import http
@@ -11,3 +19,59 @@
from twisted.web2.dav import davxml
from twisted.web2.dav.idav import IDAVResource
from twisted.web2.dav.resource import DAVResource
+@@ -145,6 +148,55 @@
+ return succeed(DAVFile._supportedPrivilegeSet)
+
+ ##
++ # Quota
++ ##
++
++ def quotaSize(self, request):
++ """
++ Get the size of this resource.
++ TODO: Take into account size of dead-properties. Does stat
++ include xattrs size?
++
++ @return: a C{int} containing the size of the resource.
++ """
++ result = os.stat(self.fp.path)
++ return result[stat.ST_SIZE]
++
++ def determineActualQuotaUse(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"
++
++ def walktree(top):
++ """
++ Recursively descend the directory tree rooted at top,
++ calling the callback function for each regular file
++ """
++
++ total = 0
++ for f in os.listdir(top):
++ 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)
++
++ ##
+ # Workarounds for issues with File
+ ##
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20060912/4e91e2c5/attachment.html
More information about the calendarserver-changes
mailing list