[CalendarServer-changes] [1971]
CalendarServer/branches/users/cdaboo/server2server-1965/
twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Fri Oct 19 10:56:31 PDT 2007
Revision: 1971
http://trac.macosforge.org/projects/calendarserver/changeset/1971
Author: cdaboo at apple.com
Date: 2007-10-19 10:56:30 -0700 (Fri, 19 Oct 2007)
Log Message:
-----------
Refactor storeCalendarObjectResource into a class to make it easier to override the behavior for other
applications on top of caldav.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/itip.py
CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/copymove.py
CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/put.py
CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/put_common.py
CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule_common.py
Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/itip.py 2007-10-19 17:55:07 UTC (rev 1970)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/itip.py 2007-10-19 17:56:30 UTC (rev 1971)
@@ -48,7 +48,7 @@
from twistedcaldav import logging
from twistedcaldav.ical import Property, iCalendarProductID
from twistedcaldav.method import report_common
-from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
from twistedcaldav.resource import isCalendarCollectionResource
__version__ = "0.0"
@@ -615,22 +615,18 @@
itipper = False
# Now write it to the resource
- try:
- d = waitForDeferred(storeCalendarObjectResource(
- request=self.request,
- sourcecal = False,
- destination = newchild,
- destination_uri = newchildURL,
- calendardata = str(calendar),
- destinationparent = collection,
- destinationcal = True,
- isiTIP = itipper
- ))
- yield d
- d.getResult()
- except:
- yield None
- return
+ storer = StoreCalendarObjectResource(
+ request=self.request,
+ destination = newchild,
+ destination_uri = newchildURL,
+ destinationparent = collection,
+ destinationcal = True,
+ calendar = calendar,
+ isiTIP = itipper
+ )
+ d = waitForDeferred(storer.run())
+ yield d
+ d.getResult()
yield newchild
Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/copymove.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/copymove.py 2007-10-19 17:55:07 UTC (rev 1970)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/copymove.py 2007-10-19 17:56:30 UTC (rev 1971)
@@ -34,7 +34,7 @@
from twisted.web2.http import StatusResponse, HTTPError
from twistedcaldav.caldavxml import caldav_namespace
-from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
from twistedcaldav.resource import isCalendarCollectionResource
def http_COPY(self, request):
@@ -101,7 +101,7 @@
# May need to add a location header
addLocation(request, destination_uri)
- x = waitForDeferred(storeCalendarObjectResource(
+ storer = StoreCalendarObjectResource(
request = request,
source = self,
source_uri = request.uri,
@@ -111,7 +111,8 @@
destination_uri = destination_uri,
destinationparent = destinationparent,
destinationcal = destinationcal,
- ))
+ )
+ x = waitForDeferred(storer.run())
yield x
yield x.getResult()
@@ -185,18 +186,19 @@
# May need to add a location header
addLocation(request, destination_uri)
- x = waitForDeferred(storeCalendarObjectResource(
+ storer = StoreCalendarObjectResource(
request = request,
source = self,
source_uri = request.uri,
sourceparent = sourceparent,
sourcecal = sourcecal,
+ deletesource = True,
destination = destination,
destination_uri = destination_uri,
destinationparent = destinationparent,
destinationcal = destinationcal,
- deletesource = True,
- ))
+ )
+ x = waitForDeferred(storer.run())
yield x
yield x.getResult()
@@ -209,15 +211,15 @@
if that is the case.
@return: tuple::
result: True if special CalDAV processing required, False otherwise
- NB If there is any type of error with the request, return False
- and allow normal COPY/MOVE processing to return the error.
+ NB If there is any type of error with the request, return False
+ and allow normal COPY/MOVE processing to return the error.
sourcecal: True if source is in a calendar collection, False otherwise
sourceparent: The parent resource for the source
destination_uri: The URI of the destination resource
destination: CalDAVFile of destination if special proccesing required,
None otherwise
destinationcal: True if the destination is in a calendar collection,
- False otherwise
+ False otherwise
destinationparent:The parent resource for the destination
"""
Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/put.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/put.py 2007-10-19 17:55:07 UTC (rev 1970)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/put.py 2007-10-19 17:56:30 UTC (rev 1971)
@@ -30,7 +30,7 @@
from twisted.web2.http import HTTPError, StatusResponse
from twistedcaldav.caldavxml import caldav_namespace
-from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
from twistedcaldav.resource import isPseudoCalendarCollectionResource
def http_PUT(self, request):
@@ -60,15 +60,15 @@
# Use correct DAV:error response
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
- d = waitForDeferred(storeCalendarObjectResource(
+ storer = StoreCalendarObjectResource(
request = request,
- sourcecal = False,
- calendardata = calendardata,
destination = self,
destination_uri = request.uri,
destinationcal = True,
- destinationparent = parent,)
+ destinationparent = parent,
+ calendar = calendardata,
)
+ d = waitForDeferred(storer.run())
yield d
yield d.getResult()
return
Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/put_common.py 2007-10-19 17:55:07 UTC (rev 1970)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/method/put_common.py 2007-10-19 17:56:30 UTC (rev 1971)
@@ -15,12 +15,13 @@
#
# DRI: Cyrus Daboo, cdaboo at apple.com
##
+import types
"""
PUT/COPY/MOVE common behavior.
"""
-__all__ = ["storeCalendarObjectResource"]
+__all__ = ["StoreCalendarObjectResource"]
from twisted.internet import reactor
from twisted.internet.defer import Deferred
@@ -52,55 +53,8 @@
from twistedcaldav.index import ReservationError
from twistedcaldav.instance import TooManyInstancesError
-def storeCalendarObjectResource(
- request,
- sourcecal, destinationcal,
- source=None, source_uri=None, sourceparent=None,
- destination=None, destination_uri=None, destinationparent=None,
- calendardata=None,
- deletesource=False,
- isiTIP=False
-):
- """
- Function that does common PUT/COPY/MOVE behaviour.
+class StoreCalendarObjectResource(object):
- @param request: the L{twisted.web2.server.Request} for the current HTTP request.
- @param source: the L{CalDAVFile} 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{CalDAVFile} for the destination resource to copy into.
- @param destination_uri: the URI for the destination resource.
- @param calendardata: the string data read directly from the request body if there is no source, None otherwise.
- @param sourcecal: True if the source resource is in a calendar collection, False otherwise.
- @param destinationcal: True if the destination resource is in a calendar collection, False otherwise
- @param sourceparent: the L{CalDAVFile} for the source resource's parent collection, or None if source is None.
- @param destinationparent: the L{CalDAVFile} for the destination resource's parent collection.
- @param deletesource: True if the source resource is to be deleted on successful completion, False otherwise.
- @param isiTIP: True if relaxed calendar data validation is to be done, False otherwise.
- @return: a Deferred with a status response result.
- """
-
- try:
- assert destination is not None and destinationparent is not None
- assert (source is None and sourceparent is None) or (source is not None and sourceparent is not None)
- assert (calendardata is None and source is not None) or (calendardata is not None and source is None)
- assert not deletesource or (deletesource and source is not None)
- except AssertionError:
- log.err("Invalid arguments to storeCalendarObjectResource():")
- log.err("request=%s\n" % (request,))
- log.err("sourcecal=%s\n" % (sourcecal,))
- log.err("destinationcal=%s\n" % (destinationcal,))
- log.err("source=%s\n" % (source,))
- log.err("source_uri=%s\n" % (source_uri,))
- log.err("sourceparent=%s\n" % (sourceparent,))
- log.err("destination=%s\n" % (destination,))
- log.err("destination_uri=%s\n" % (destination_uri,))
- log.err("destinationparent=%s\n" % (destinationparent,))
- log.err("calendardata=%s\n" % (calendardata,))
- log.err("deletesource=%s\n" % (deletesource,))
- log.err("isiTIP=%s\n" % (isiTIP,))
- raise
-
class RollbackState(object):
"""
This class encapsulates the state needed to rollback the entire PUT/COPY/MOVE
@@ -108,7 +62,8 @@
processed. The DoRollback method will actually execute the rollback operations.
"""
- def __init__(self):
+ def __init__(self, storer):
+ self.storer = storer
self.active = True
self.source_copy = None
self.destination_copy = None
@@ -128,32 +83,32 @@
logging.debug("Rollback: rollback", system="Store Resource")
try:
if self.source_copy and self.source_deleted:
- self.source_copy.moveTo(source.fp)
- logging.debug("Rollback: source restored %s to %s" % (self.source_copy.path, source.fp.path), system="Store Resource")
+ self.source_copy.moveTo(self.storer.source.fp)
+ logging.debug("Rollback: source restored %s to %s" % (self.source_copy.path, self.storer.source.fp.path), system="Store Resource")
self.source_copy = None
self.source_deleted = False
if self.destination_copy:
- destination.fp.remove()
- logging.debug("Rollback: destination restored %s to %s" % (self.destination_copy.path, destination.fp.path), system="Store Resource")
- self.destination_copy.moveTo(destination.fp)
+ self.storer.destination.fp.remove()
+ logging.debug("Rollback: destination restored %s to %s" % (self.destination_copy.path, self.storer.destination.fp.path), system="Store Resource")
+ self.destination_copy.moveTo(self.storer.destination.fp)
self.destination_copy = None
elif self.destination_created:
- if destinationcal:
- doRemoveDestinationIndex()
- logging.debug("Rollback: destination index removed %s" % (destination.fp.path,), system="Store Resource")
+ if self.storer.destinationcal:
+ self.storer.doRemoveDestinationIndex()
+ logging.debug("Rollback: destination index removed %s" % (self.storer.destination.fp.path,), system="Store Resource")
self.destination_index_deleted = False
- destination.fp.remove()
- logging.debug("Rollback: destination removed %s" % (destination.fp.path,), system="Store Resource")
+ self.storer.destination.fp.remove()
+ logging.debug("Rollback: destination removed %s" % (self.storer.destination.fp.path,), system="Store Resource")
self.destination_created = False
if self.destination_index_deleted:
# Must read in calendar for destination being re-indexed
- doDestinationIndex(destination.iCalendar())
+ self.storer.doDestinationIndex(self.storer.destination.iCalendar())
self.destination_index_deleted = False
- logging.debug("Rollback: destination re-indexed %s" % (destination.fp.path,), system="Store Resource")
+ logging.debug("Rollback: destination re-indexed %s" % (self.storer.destination.fp.path,), system="Store Resource")
if self.source_index_deleted:
- doSourceIndexRecover()
+ self.storer.doSourceIndexRecover()
self.destination_index_deleted = False
- logging.debug("Rollback: soyurce re-indexed %s" % (source.fp.path,), system="Store Resource")
+ logging.debug("Rollback: soyurce re-indexed %s" % (self.storer.source.fp.path,), system="Store Resource")
except:
log.err("Rollback: exception caught and not handled: %s" % failure.Failure())
@@ -176,37 +131,201 @@
self.source_deleted = False
self.source_index_deleted = False
self.destination_index_deleted = False
+
+ class UIDReservation(object):
+
+ def __init__(self, index, uid, uri):
+ self.reserved = False
+ self.index = index
+ self.uid = uid
+ self.uri = uri
+
+ @deferredGenerator
+ def reserve(self):
+
+ # Lets use a deferred for this and loop a few times if we cannot reserve so that we give
+ # time to whoever has the reservation to finish and release it.
+ failure_count = 0
+ while(failure_count < 5):
+ try:
+ self.index.reserveUID(self.uid)
+ self.reserved = True
+ break
+ except ReservationError:
+ self.reserved = False
+ failure_count += 1
+
+ d = Deferred()
+ def _timedDeferred():
+ d.callback(True)
+ reactor.callLater(0.1, _timedDeferred)
+ pause = waitForDeferred(d)
+ yield pause
+ pause.getResult()
+
+ if not self.reserved:
+ raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use." % (self.uri,)))
+
+ def unreserve(self):
+ if self.reserved:
+ self.index.unreserveUID(self.uid)
+ self.reserved = False
+
+ def __init__(
+ self,
+ request,
+ source=None, source_uri=None, sourceparent=None, sourcecal=False, deletesource=False,
+ destination=None, destination_uri=None, destinationparent=None, destinationcal=True,
+ calendar=None,
+ isiTIP=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{CalDAVFile} 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{CalDAVFile} for the destination resource to copy into.
+ @param destination_uri: the URI for the destination resource.
+ @param calendar: the C{str} or L{Component} calendar data if there is no source, None otherwise.
+ @param sourcecal: True if the source resource is in a calendar collection, False otherwise.
+ @param destinationcal: True if the destination resource is in a calendar collection, False otherwise
+ @param sourceparent: the L{CalDAVFile} for the source resource's parent collection, or None if source is None.
+ @param destinationparent: the L{CalDAVFile} for the destination resource's parent collection.
+ @param deletesource: True if the source resource is to be deleted on successful completion, False otherwise.
+ @param isiTIP: True if relaxed calendar data validation is to be done, False otherwise.
+ """
+
+ # Check that all arguments are valid
+ try:
+ assert destination is not None and destinationparent is not None
+ assert (source is None and sourceparent is None) or (source is not None and sourceparent is not None)
+ assert (calendar is None and source is not None) or (calendar is not None and source is None)
+ assert not deletesource or (deletesource and source is not None)
+ except AssertionError:
+ log.err("Invalid arguments to StoreCalendarObjectResource.__init__():")
+ log.err("request=%s\n" % (request,))
+ log.err("sourcecal=%s\n" % (sourcecal,))
+ log.err("destinationcal=%s\n" % (destinationcal,))
+ log.err("source=%s\n" % (source,))
+ log.err("source_uri=%s\n" % (source_uri,))
+ log.err("sourceparent=%s\n" % (sourceparent,))
+ log.err("destination=%s\n" % (destination,))
+ log.err("destination_uri=%s\n" % (destination_uri,))
+ log.err("destinationparent=%s\n" % (destinationparent,))
+ log.err("calendar=%s\n" % (calendar,))
+ log.err("deletesource=%s\n" % (deletesource,))
+ log.err("isiTIP=%s\n" % (isiTIP,))
+ raise
- rollback = RollbackState()
+ self.request = request
+ self.sourcecal = sourcecal
+ self.destinationcal = destinationcal
+ self.source = source
+ self.source_uri = source_uri
+ self.sourceparent = sourceparent
+ self.destination = destination
+ self.destination_uri = destination_uri
+ self.destinationparent = destinationparent
+ self.calendar = calendar
+ self.calendardata = None
+ self.deletesource = deletesource
+ self.isiTIP = isiTIP
+
+ self.rollback = None
- def validResourceName():
+ def fullValidation(self):
"""
+ Do full validaion of source and destination calendar data.
+ """
+
+ if self.destinationcal:
+ # Valid resource name check
+ result, message = self.validResourceName()
+ if not result:
+ log.err(message)
+ raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Resource name not allowed"))
+
+ if not self.sourcecal:
+ # Valid content type check on the source resource if its not in a calendar collection
+ if self.source is not None:
+ result, message = self.validContentType()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
+
+ # At this point we need the calendar data to do more tests
+ self.calendar = self.source.iCalendar()
+ else:
+ try:
+ if type(self.calendar) in (types.StringType, types.UnicodeType,):
+ self.calendardata = self.calendar
+ self.calendar = Component.fromString(self.calendar)
+ except ValueError, e:
+ log.err(str(e))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+ # Valid calendar data check
+ result, message = self.validCalendarDataCheck()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+
+ # Valid calendar data for CalDAV check
+ result, message = self.validCalDAVDataCheck()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-object-resource")))
+
+ # Must have a valid UID at this point
+ self.uid = self.calendar.resourceUID()
+ else:
+ # Get uid from original resource
+ self.source_index = self.sourceparent.index()
+ self.uid = self.source_index.resourceUIDForName(self.source.fp.basename())
+ if self.uid is None:
+ log.err("Source calendar does not have a UID: %s" % self.source.fp.basename())
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-object-resource")))
+
+ # 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.
+ self.calendar = self.source.iCalendar()
+
+ # Valid calendar data size check
+ result, message = self.validSizeCheck()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "max-resource-size")))
+
+ def validResourceName(self):
+ """
Make sure that the resource name for the new resource is valid.
"""
result = True
message = ""
- filename = destination.fp.basename()
+ filename = self.destination.fp.basename()
if filename.startswith("."):
result = False
message = "File name %s not allowed in calendar collection" % (filename,)
return result, message
- def validContentType():
+ def validContentType(self):
"""
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.
"""
result = True
message = ""
- content_type = source.contentType()
+ content_type = self.source.contentType()
if not ((content_type.mediaType == "text") and (content_type.mediaSubtype == "calendar")):
result = False
message = "MIME type %s not allowed in calendar collection" % (content_type,)
return result, message
- def validCalendarDataCheck():
+ def validCalendarDataCheck(self):
"""
Check that the calendar data is valid iCalendar.
@return: tuple: (True/False if the calendra data is valid,
@@ -214,19 +333,19 @@
"""
result = True
message = ""
- if calendar is None:
+ if self.calendar is None:
result = False
message = "Empty resource not allowed in calendar collection"
else:
try:
- calendar.validCalendarForCalDAV()
+ self.calendar.validCalendarForCalDAV()
except ValueError, e:
result = False
message = "Invalid calendar data: %s" % (e,)
return result, message
- def validCalDAVDataCheck():
+ def validCalDAVDataCheck(self):
"""
Check that the calendar data is valid as a CalDAV calendar object resource.
@return: tuple: (True/False if the calendar data is valid,
@@ -235,17 +354,17 @@
result = True
message = ""
try:
- if isiTIP:
- calendar.validateComponentsForCalDAV(True)
+ if self.isiTIP:
+ self.calendar.validateComponentsForCalDAV(True)
else:
- calendar.validateForCalDAV()
+ self.calendar.validateForCalDAV()
except ValueError, e:
result = False
message = "Calendar data does not conform to CalDAV requirements: %s" % (e,)
return result, message
- def validSizeCheck():
+ def validSizeCheck(self):
"""
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.
@@ -253,14 +372,14 @@
result = True
message = ""
if config.MaximumAttachmentSize:
- calsize = len(str(calendar))
+ calsize = len(str(self.calendar))
if calsize > config.MaximumAttachmentSize:
result = False
message = "Data size %d bytes is larger than allowed limit %d bytes" % (calsize, config.MaximumAttachmentSize)
return result, message
- def noUIDConflict(uid):
+ def noUIDConflict(self, uid):
"""
Check that the UID of the new calendar object conforms to the requirements of
CalDAV, i.e. it must be unique in the collection and we must not overwrite a
@@ -276,12 +395,12 @@
# Adjust for a move into same calendar collection
oldname = None
- if sourceparent and (sourceparent.fp.path == destinationparent.fp.path) and deletesource:
- oldname = source.fp.basename()
+ if self.sourceparent and (self.sourceparent.fp.path == self.destinationparent.fp.path) and self.deletesource:
+ oldname = self.source.fp.basename()
# UID must be unqiue
- index = destinationparent.index()
- if not index.isAllowedUID(uid, oldname, destination.fp.basename()):
+ index = self.destinationparent.index()
+ if not index.isAllowedUID(uid, oldname, self.destination.fp.basename()):
rname = index.resourceNameForUID(uid)
# This can happen if two simulataneous PUTs occur with the same UID.
# i.e. one PUT has reserved the UID but has not yet written the resource,
@@ -293,330 +412,306 @@
message = "Calendar resource %s already exists with same UID %s" % (rname, uid)
else:
# Cannot overwrite a resource with different UID
- if destination.fp.exists():
- olduid = index.resourceUIDForName(destination.fp.basename())
+ if self.destination.fp.exists():
+ olduid = index.resourceUIDForName(self.destination.fp.basename())
if olduid != uid:
- rname = destination.fp.basename()
+ rname = self.destination.fp.basename()
result = False
message = "Cannot overwrite calendar resource %s with different UID %s" % (rname, olduid)
return result, message, rname
- try:
+ @deferredGenerator
+ def checkQuota(self):
"""
- Handle validation operations here.
+ Get quota details for destination and source before we start messing with adding other files.
"""
- reserved = False
- if destinationcal:
- # Valid resource name check
- result, message = validResourceName()
- if not result:
- log.err(message)
- raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Resource name not allowed"))
- if not sourcecal:
- # Valid content type check on the source resource if its not in a calendar collection
- if source is not None:
- result, message = validContentType()
- if not result:
- log.err(message)
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "supported-calendar-data")))
-
- # At this point we need the calendar data to do more tests
- calendar = source.iCalendar()
- else:
- try:
- calendar = Component.fromString(calendardata)
- except ValueError, e:
- log.err(str(e))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
-
- # Valid calendar data check
- result, message = validCalendarDataCheck()
- if not result:
- log.err(message)
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
-
- # Valid calendar data for CalDAV check
- result, message = validCalDAVDataCheck()
- if not result:
- log.err(message)
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-object-resource")))
-
- # Must have a valid UID at this point
- uid = calendar.resourceUID()
- else:
- # Get uid from original resource
- source_index = sourceparent.index()
- uid = source_index.resourceUIDForName(source.fp.basename())
- if uid is None:
- log.err("Source calendar does not have a UID: %s" % source.fp.basename())
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-object-resource")))
-
- # 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")))
-
- # Reserve UID
- destination_index = destinationparent.index()
-
- # Lets use a deferred for this and loop a few times if we cannot reserve so that we give
- # time to whoever has the reservation to finish and release it.
- failure_count = 0
- while(failure_count < 5):
- try:
- destination_index.reserveUID(uid)
- reserved = True
- break
- except ReservationError:
- reserved = False
- failure_count += 1
-
- d = Deferred()
- def _timedDeferred():
- d.callback(True)
- reactor.callLater(0.1, _timedDeferred)
- pause = waitForDeferred(d)
- yield pause
- pause.getResult()
-
- if not reserved:
- raise HTTPError(StatusResponse(responsecode.CONFLICT, "Resource: %s currently in use." % (destination_uri,)))
-
- # uid conflict check - note we do this after reserving the UID to avoid a race condition where two requests
- # try to write the same calendar data to two different resource URIs.
- if not isiTIP:
- result, message, rname = noUIDConflict(uid)
- if not result:
- log.err(message)
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN,
- NoUIDConflict(davxml.HRef.fromString(joinURL(parentForURL(destination_uri), rname.encode("utf-8"))))
- ))
-
- """
- Handle rollback setup here.
- """
-
- # Do quota checks on destination and source before we start messing with adding other files
- if request is None:
- destquota = None
+ if self.request is None:
+ self.destquota = None
else:
- destquota = waitForDeferred(destination.quota(request))
- yield destquota
- destquota = destquota.getResult()
- if destquota is not None and destination.exists():
- old_dest_size = waitForDeferred(destination.quotaSize(request))
- yield old_dest_size
- old_dest_size = old_dest_size.getResult()
+ self.destquota = waitForDeferred(self.destination.quota(self.request))
+ yield self.destquota
+ self.destquota = self.destquota.getResult()
+ if self.destquota is not None and self.destination.exists():
+ self.old_dest_size = waitForDeferred(self.destination.quotaSize(self.request))
+ yield self.old_dest_size
+ self.old_dest_size = self.old_dest_size.getResult()
else:
- old_dest_size = 0
+ self.old_dest_size = 0
- if request is None:
- sourcequota = None
- elif 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 = waitForDeferred(source.quotaSize(request))
- yield old_source_size
- old_source_size = old_source_size.getResult()
+ if self.request is None:
+ self.sourcequota = None
+ elif self.source is not None:
+ self.sourcequota = waitForDeferred(self.source.quota(self.request))
+ yield self.sourcequota
+ self.sourcequota = self.sourcequota.getResult()
+ if self.sourcequota is not None and self.source.exists():
+ self.old_source_size = waitForDeferred(self.source.quotaSize(self.request))
+ yield self.old_source_size
+ self.old_source_size = self.old_source_size.getResult()
else:
- old_source_size = 0
+ self.old_source_size = 0
else:
- sourcequota = None
- old_source_size = 0
+ self.sourcequota = None
+ self.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)
- logging.debug("Rollback: backing up destination %s to %s" % (destination.fp.path, rollback.destination_copy.path), system="Store Resource")
- else:
- rollback.destination_created = True
- logging.debug("Rollback: will create new destination %s" % (destination.fp.path,), system="Store Resource")
+ yield None
- if deletesource:
- rollback.source_copy = FilePath(source.fp.path)
- rollback.source_copy.path += ".rollback"
- source.fp.copyTo(rollback.source_copy)
- logging.debug("Rollback: backing up source %s to %s" % (source.fp.path, rollback.source_copy.path), system="Store Resource")
-
+ def setupRollback(self):
"""
- Handle actual store operations here.
-
- The order in which this is done is import:
-
- 1. Do store operation for new data
- 2. Delete source and source index if needed
- 3. Do new indexing if needed
-
- Note that we need to remove the source index BEFORE doing the destination index to cover the
- case of a resource being 'renamed', i.e. moved within the same collection. Since the index UID
- column must be unique in SQL, we cannot add the new index before remove the old one.
+ 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.
"""
+ self.rollback = StoreCalendarObjectResource.RollbackState(self)
+ self.overwrite = self.destination.exists()
+ if self.overwrite:
+ self.rollback.destination_copy = FilePath(self.destination.fp.path)
+ self.rollback.destination_copy.path += ".rollback"
+ self.destination.fp.copyTo(self.rollback.destination_copy)
+ logging.debug("Rollback: backing up destination %s to %s" % (self.destination.fp.path, self.rollback.destination_copy.path), system="Store Resource")
+ else:
+ self.rollback.destination_created = True
+ logging.debug("Rollback: will create new destination %s" % (self.destination.fp.path,), system="Store Resource")
+
+ if self.deletesource:
+ self.rollback.source_copy = FilePath(self.source.fp.path)
+ self.rollback.source_copy.path += ".rollback"
+ self.source.fp.copyTo(self.rollback.source_copy)
+ logging.debug("Rollback: backing up source %s to %s" % (self.source.fp.path, self.rollback.source_copy.path), system="Store Resource")
+
+ @deferredGenerator
+ def doStore(self):
# Do put or copy based on whether source exists
- if source is not None:
- response = maybeDeferred(copy, source.fp, destination.fp, destination_uri, "0")
+ if self.source is not None:
+ response = maybeDeferred(copy, self.source.fp, self.destination.fp, self.destination_uri, "0")
else:
- md5 = MD5StreamWrapper(MemoryStream(calendardata))
- response = maybeDeferred(put, md5, destination.fp)
+ if self.calendardata is None:
+ self.calendardata = str(self.calendar)
+ md5 = MD5StreamWrapper(MemoryStream(self.calendardata))
+ response = maybeDeferred(put, md5, self.destination.fp)
response = waitForDeferred(response)
yield response
response = response.getResult()
# Update the MD5 value on the resource
- if source is not None:
+ if self.source is not None:
# Copy MD5 value from source to destination
- if source.hasDeadProperty(TwistedGETContentMD5):
- md5 = source.readDeadProperty(TwistedGETContentMD5)
- destination.writeDeadProperty(md5)
+ if self.source.hasDeadProperty(TwistedGETContentMD5):
+ md5 = self.source.readDeadProperty(TwistedGETContentMD5)
+ self.destination.writeDeadProperty(md5)
else:
# Finish MD5 calc and write dead property
md5.close()
md5 = md5.getMD5()
- destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
+ self.destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
+
+ yield IResponse(response)
- response = IResponse(response)
-
- def doDestinationIndex(caltoindex):
- """
- Do destination resource indexing, replacing any index previous stored.
-
- @return: None if successful, ErrorResponse on failure
- """
-
- # Delete index for original item
- if overwrite:
- doRemoveDestinationIndex()
-
- # Add or update the index for this resource.
- try:
- destination_index.addResource(destination.fp.basename(), caltoindex)
- logging.debug("Destination indexed %s" % (destination.fp.path,), system="Store Resource")
- except TooManyInstancesError, ex:
- log.err("Cannot index calendar resource as there are too many recurrence instances %s" % destination)
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- NumberOfRecurrencesWithinLimits(PCDATAElement(str(ex.max_allowed)))
- ))
- except (ValueError, TypeError), ex:
- log.err("Cannot index calendar resource: %s" % (ex,))
- raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+ @deferredGenerator
+ def doSourceDelete(self):
+ # Delete index for original item
+ if self.sourcecal:
+ self.source_index.deleteResource(self.source.fp.basename())
+ self.rollback.source_index_deleted = True
+ logging.debug("Source index removed %s" % (self.source.fp.path,), system="Store Resource")
- destination.writeDeadProperty(davxml.GETContentType.fromString("text/calendar"))
- return None
+ # Delete the source resource
+ delete(self.source_uri, self.source.fp, "0")
+ self.rollback.source_deleted = True
+ logging.debug("Source removed %s" % (self.source.fp.path,), system="Store Resource")
- def doRemoveDestinationIndex():
- """
- Remove any existing destination index.
- """
-
- # Delete index for original item
- if destinationcal:
- destination_index.deleteResource(destination.fp.basename())
- rollback.destination_index_deleted = True
- logging.debug("Destination index removed %s" % (destination.fp.path,), system="Store Resource")
+ # Update quota
+ if self.sourcequota is not None:
+ delete_size = 0 - self.old_source_size
+ d = waitForDeferred(self.source.quotaSizeAdjust(self.request, delete_size))
+ yield d
+ d.getResult()
- def doSourceDelete():
- # Delete index for original item
- if sourcecal:
- source_index.deleteResource(source.fp.basename())
- rollback.source_index_deleted = True
- logging.debug("Source index removed %s" % (source.fp.path,), system="Store Resource")
+ # Change CTag on the parent calendar collection
+ if self.sourcecal:
+ self.sourceparent.updateCTag()
- # 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")
+ yield None
- def doSourceIndexRecover():
- """
- Do source resource indexing. This only gets called when restoring
- the source after its index has been deleted.
-
- @return: None if successful, ErrorResponse on failure
- """
-
- # Add or update the index for this resource.
- try:
- source_index.addResource(source.fp.basename(), calendar)
- except TooManyInstancesError, ex:
- raise HTTPError(ErrorResponse(
- responsecode.FORBIDDEN,
- NumberOfRecurrencesWithinLimits(PCDATAElement(str(ex.max_allowed)))
- ))
+ @deferredGenerator
+ def doDestinationQuotaCheck(self):
+ # Get size of new/old resources
+ new_dest_size = waitForDeferred(self.destination.quotaSize(self.request))
+ yield new_dest_size
+ new_dest_size = new_dest_size.getResult()
- source.writeDeadProperty(davxml.GETContentType.fromString("text/calendar"))
- return None
+ diff_size = new_dest_size - self.old_dest_size
- 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 diff_size >= self.destquota[0]:
+ log.err("Over quota: available %d, need %d" % (self.destquota[0], diff_size))
+ raise HTTPError(ErrorResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, (dav_namespace, "quota-not-exceeded")))
+ d = waitForDeferred(self.destination.quotaSizeAdjust(self.request, diff_size))
+ yield d
+ d.getResult()
- if sourcecal:
- # Change CTag on the parent calendar collection
- sourceparent.updateCTag()
+ yield None
- if destinationcal:
- result = doDestinationIndex(calendar)
- if result is not None:
- rollback.Rollback()
- yield result
- return
+ def doSourceIndexRecover(self):
+ """
+ Do source resource indexing. This only gets called when restoring
+ the source after its index has been deleted.
+
+ @return: None if successful, ErrorResponse on failure
+ """
+
+ # Add or update the index for this resource.
+ try:
+ self.source_index.addResource(self.source.fp.basename(), self.calendar)
+ except TooManyInstancesError, ex:
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ NumberOfRecurrencesWithinLimits(PCDATAElement(str(ex.max_allowed)))
+ ))
- # Do quota check on destination
- if destquota is not None:
- # Get size of new/old resources
- new_dest_size = waitForDeferred(destination.quotaSize(request))
- yield new_dest_size
- new_dest_size = new_dest_size.getResult()
- 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(ErrorResponse(responsecode.INSUFFICIENT_STORAGE_SPACE, (dav_namespace, "quota-not-exceeded")))
- d = waitForDeferred(destination.quotaSizeAdjust(request, diff_size))
- yield d
- d.getResult()
+ self.source.writeDeadProperty(davxml.GETContentType.fromString("text/calendar"))
+ return None
+ def doDestinationIndex(self, caltoindex):
+ """
+ Do destination resource indexing, replacing any index previous stored.
+
+ @return: None if successful, ErrorResponse on failure
+ """
+
+ # Delete index for original item
+ if self.overwrite:
+ self.doRemoveDestinationIndex()
+
+ # Add or update the index for this resource.
+ try:
+ self.destination_index.addResource(self.destination.fp.basename(), caltoindex)
+ logging.debug("Destination indexed %s" % (self.destination.fp.path,), system="Store Resource")
+ except TooManyInstancesError, ex:
+ log.err("Cannot index calendar resource as there are too many recurrence instances %s" % self.destination)
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ NumberOfRecurrencesWithinLimits(PCDATAElement(str(ex.max_allowed)))
+ ))
+ except (ValueError, TypeError), ex:
+ log.err("Cannot index calendar resource: %s" % (ex,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
- if destinationcal:
- # Change CTag on the parent calendar collection
- destinationparent.updateCTag()
+ self.destination.writeDeadProperty(davxml.GETContentType.fromString("text/calendar"))
+ return None
- # Can now commit changes and forget the rollback details
- rollback.Commit()
+ def doRemoveDestinationIndex(self):
+ """
+ Remove any existing destination index.
+ """
+
+ # Delete index for original item
+ if self.destinationcal:
+ self.destination_index.deleteResource(self.destination.fp.basename())
+ self.rollback.destination_index_deleted = True
+ logging.debug("Destination index removed %s" % (self.destination.fp.path,), system="Store Resource")
- if reserved:
- destination_index.unreserveUID(uid)
- reserved = False
+ @deferredGenerator
+ def run(self):
+ """
+ Function that does common PUT/COPY/MOVE behaviour.
- yield response
- return
+ @return: a Deferred with a status response result.
+ """
- except:
- if reserved:
- destination_index.unreserveUID(uid)
- reserved = False
+ try:
+ reservation = None
+
+ # Handle all validation operations here.
+ self.fullValidation()
- # 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
+ # Reservation and UID conflict checking is next.
+ if self.destinationcal:
+ # Reserve UID
+ self.destination_index = self.destinationparent.index()
+ reservation = StoreCalendarObjectResource.UIDReservation(self.destination_index, self.uid, self.destination_uri)
+ d = waitForDeferred(reservation.reserve())
+ yield d
+ d.getResult()
+
+ # uid conflict check - note we do this after reserving the UID to avoid a race condition where two requests
+ # try to write the same calendar data to two different resource URIs.
+ if not self.isiTIP:
+ result, message, rname = self.noUIDConflict(self.uid)
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN,
+ NoUIDConflict(davxml.HRef.fromString(joinURL(parentForURL(self.destination_uri), rname.encode("utf-8"))))
+ ))
+
+ # Get current quota state.
+ d = waitForDeferred(self.checkQuota())
+ yield d
+ d.getResult()
+
+ # Initialize the rollback system
+ self.setupRollback()
+
+ """
+ Handle actual store operations here.
+
+ The order in which this is done is import:
+
+ 1. Do store operation for new data
+ 2. Delete source and source index if needed
+ 3. Do new indexing if needed
+
+ Note that we need to remove the source index BEFORE doing the destination index to cover the
+ case of a resource being 'renamed', i.e. moved within the same collection. Since the index UID
+ column must be unique in SQL, we cannot add the new index before remove the old one.
+ """
+
+ # Do the actual put or copy
+ response = waitForDeferred(self.doStore())
+ yield response
+ response = response.getResult()
+
+ # Delete the original source if needed.
+ if self.deletesource:
+ d = waitForDeferred(self.doSourceDelete())
+ yield d
+ d.getResult()
+
+ # Index the new resource if storing to a calendar.
+ if self.destinationcal:
+ result = self.doDestinationIndex(self.calendar)
+ if result is not None:
+ self.rollback.Rollback()
+ yield result
+ return
+
+ # Do quota check on destination
+ if self.destquota is not None:
+ d = waitForDeferred(self.doDestinationQuotaCheck())
+ yield d
+ d.getResult()
+
+ if self.destinationcal:
+ # Change CTag on the parent calendar collection
+ self.destinationparent.updateCTag()
+
+ # Can now commit changes and forget the rollback details
+ self.rollback.Commit()
+
+ if reservation:
+ reservation.unreserve()
+
+ yield response
+ return
+
+ except:
+ if reservation:
+ reservation.unreserve()
+
+ # Roll back changes to original server state. Note this may do nothing
+ # if the rollback has already ocurred or changes already committed.
+ if self.rollback:
+ self.rollback.Rollback()
-storeCalendarObjectResource = deferredGenerator(storeCalendarObjectResource)
+ raise
Modified: CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule_common.py 2007-10-19 17:55:07 UTC (rev 1970)
+++ CalendarServer/branches/users/cdaboo/server2server-1965/twistedcaldav/schedule_common.py 2007-10-19 17:56:30 UTC (rev 1971)
@@ -46,7 +46,7 @@
from twistedcaldav.ical import Component
from twistedcaldav.itip import iTipProcessor
from twistedcaldav.method import report_common
-from twistedcaldav.method.put_common import storeCalendarObjectResource
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
from twistedcaldav.resource import isCalendarCollectionResource
from twistedcaldav.servertoserver import ServerToServer
from twistedcaldav.servertoserver import ServerToServerRequest
@@ -448,19 +448,16 @@
# Copy calendar to inbox (doing fan-out)
try:
- d = waitForDeferred(
- maybeDeferred(
- storeCalendarObjectResource,
- request=self.request,
- sourcecal = False,
- destination = child,
- destination_uri = childURL,
- calendardata = calendar_str,
- destinationparent = recipient.inbox,
- destinationcal = True,
- isiTIP = True
- )
- )
+ storer = StoreCalendarObjectResource(
+ request=self.request,
+ destination = child,
+ destination_uri = childURL,
+ destinationparent = recipient.inbox,
+ destinationcal = True,
+ calendar = self.calendar,
+ isiTIP = True
+ )
+ d = waitForDeferred(storer.run())
yield d
d.getResult()
responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success")
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20071019/2607b5ae/attachment-0001.html
More information about the calendarserver-changes
mailing list