[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