[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