[prev in list] [next in list] [prev in thread] [next in thread] 

List:       calendarserver-changes
Subject:    [CalendarServer-changes] [3295] CalendarServer/trunk/twistedcaldav
From:       source_changes () macosforge ! org
Date:       2008-10-31 2:33:19
Message-ID: 20081031023319.ADB2B5F7A38 () beta ! macosforge ! org
[Download RAW message or body]

[Attachment #2 (multipart/alternative)]


Revision: 3295
          http://trac.macosforge.org/projects/calendarserver/changeset/3295
Author:   cdaboo@apple.com
Date:     2008-10-30 19:33:19 -0700 (Thu, 30 Oct 2008)
Log Message:
-----------
Major refactor to implement proper PUT/COPY/MOVE/DELETE logic with implicit \
scheduling and also to restrict duplicate UIDs for scheduling messages.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/method/delete.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
    CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2008-10-31 02:20:00 UTC (rev \
                3294)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2008-10-31 02:33:19 UTC (rev \
3295) @@ -60,6 +60,14 @@
     def getValue(self):
         return str(self)
 
+class TwistedSchedulingObjectResource (davxml.WebDAVEmptyElement):
+    """
+    Indicates that the resource is a scheduling object resource.    
+    """
+    namespace = twisted_private_namespace
+    name = "scheduling-object-resource"
+    hidden = True
+
 class TwistedCalendarHasPrivateCommentsProperty (davxml.WebDAVEmptyElement):
     """
     Indicates that a calendar resource has private comments.

Modified: CalendarServer/trunk/twistedcaldav/method/delete.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/delete.py	2008-10-31 02:20:00 UTC (rev \
                3294)
+++ CalendarServer/trunk/twistedcaldav/method/delete.py	2008-10-31 02:33:19 UTC (rev \
3295) @@ -22,13 +22,18 @@
 
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.fileop import delete
 from twisted.web2.dav.util import parentForURL
 from twisted.web2.http import HTTPError, StatusResponse
 
 from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
 from twistedcaldav.resource import isCalendarCollectionResource
 from twistedcaldav.scheduling.implicit import ImplicitScheduler
+from twistedcaldav.log import Logger
 
+log = Logger()
+
 @inlineCallbacks
 def http_DELETE(self, request):
     #
@@ -39,10 +44,28 @@
     # TODO: need to use transaction based delete on live scheduling object resources
     # as the iTIP operation may fail and may need to prevent the delete from \
happening.  
+    if not self.fp.exists():
+        log.err("File not found: %s" % (self.fp.path,))
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    depth = request.headers.getHeader("depth", "infinity")
+
+    #
+    # Check authentication and access controls
+    #
     parentURL = parentForURL(request.uri)
     parent = (yield request.locateResource(parentURL))
 
-    calendar = None
+    yield parent.authorize(request, (davxml.Unbind(),))
+
+    # Do quota checks before we start deleting things
+    myquota = (yield self.quota(request))
+    if myquota is not None:
+        old_size = (yield self.quotaSize(request))
+    else:
+        old_size = 0
+
+    scheduler = None
     isCalendarCollection = False
     isCalendarResource = False
     lock = None
@@ -51,7 +74,12 @@
         if isCalendarCollectionResource(parent):
             isCalendarResource = True
             calendar = self.iCalendar()
-            lock = MemcacheLock("ImplicitUIDLock", calendar.resourceUID(), \
timeout=60.0) +            scheduler = ImplicitScheduler()
+            do_implicit_action, _ignore = (yield \
scheduler.testImplicitSchedulingDELETE(request, self, calendar)) +            if \
do_implicit_action: +                lock = MemcacheLock("ImplicitUIDLock", \
calendar.resourceUID(), timeout=60.0) +            else:
+                scheduler = None
             
         elif isCalendarCollectionResource(self):
             isCalendarCollection = True
@@ -60,8 +88,14 @@
         if lock:
             yield lock.acquire()
 
-        response = (yield super(CalDAVFile, self).http_DELETE(request))
+        # Do delete
+        response = (yield delete(request.uri, self.fp, depth))
     
+
+        # Adjust quota
+        if myquota is not None:
+            yield self.quotaSizeAdjust(request, -old_size)
+
         if response == responsecode.NO_CONTENT:
             if isCalendarResource:
     
@@ -72,8 +106,8 @@
                 yield parent.updateCTag()
     
                 # Do scheduling
-                scheduler = ImplicitScheduler()
-                yield scheduler.doImplicitScheduling(request, self, calendar, True)
+                if scheduler:
+                    yield scheduler.doImplicitScheduling()
      
             elif isCalendarCollection:
                 

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2008-10-31 02:20:00 UTC \
                (rev 3294)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2008-10-31 02:33:19 UTC \
(rev 3295) @@ -25,7 +25,7 @@
 
 from twisted.internet import reactor
 from twisted.internet.defer import Deferred, inlineCallbacks, succeed
-from twisted.internet.defer import maybeDeferred, returnValue
+from twisted.internet.defer import returnValue
 from twisted.python import failure
 from twisted.python.filepath import FilePath
 from twisted.web2 import responsecode
@@ -48,9 +48,9 @@
 from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.customxml import calendarserver_namespace ,\
-    TwistedCalendarHasPrivateCommentsProperty
+    TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource
 from twistedcaldav.customxml import TwistedCalendarAccessProperty
-from twistedcaldav.fileops import copyToWithXAttrs
+from twistedcaldav.fileops import copyToWithXAttrs, copyXAttrs
 from twistedcaldav.fileops import putWithXAttrs
 from twistedcaldav.fileops import copyWithXAttrs
 from twistedcaldav.ical import Component, Property
@@ -265,11 +265,15 @@
         self.rollback = None
         self.access = None
 
+    @inlineCallbacks
     def fullValidation(self):
         """
         Do full validation of source and destination calendar data.
         """
 
+        # Basic validation
+        yield self.validCopyMoveOperation()
+
         if self.destinationcal:
             # Valid resource name check
             result, message = self.validResourceName()
@@ -330,10 +334,38 @@
 
             # Check access
             if self.destinationcal and config.EnablePrivateEvents:
-                return self.validAccess()
+                result = (yield self.validAccess())
+                returnValue(result)
             else:
-                return succeed(None)
+                returnValue(None)
+
+        elif self.sourcecal:
+            self.source_index = self.sourceparent.index()
+            self.calendar = self.source.iCalendar()
     
+    @inlineCallbacks
+    def validCopyMoveOperation(self):
+        """
+        Check that copy/move type behavior is valid.
+        """
+        if self.source:
+            if not self.destinationcal:
+                # Don't care about copies/moves to non-calendar destinations
+                # In theory this state should not occur here as COPY/MOVE won't call \
into this as +                # they detect this state and do regular WebDAV \
copy/move. +                pass
+            elif not self.sourcecal:
+                # Moving into a calendar requires regular checks
+                pass
+            else:
+                # Calendar to calendar moves are OK if the owner is the same
+                sourceowner = (yield self.sourceparent.owner(self.request))
+                destowner = (yield self.destinationparent.owner(self.request))
+                if sourceowner != destowner:
+                    msg = "Calendar-to-calendar %s with different owners are not \
supported" % ("moves" if self.deletesource else "copies",) +                    \
log.debug(msg) +                    raise \
HTTPError(StatusResponse(responsecode.FORBIDDEN, msg)) +
     def validResourceName(self):
         """
         Make sure that the resource name for the new resource is valid.
@@ -546,29 +578,99 @@
             copyToWithXAttrs(self.source.fp, self.rollback.source_copy)
             log.debug("Rollback: backing up source %s to %s" % (self.source.fp.path, \
self.rollback.source_copy.path))  
+    def preservePrivateComments(self):
+        # Check for private comments on the old resource and the new resource and \
re-insert +        # ones that are lost.
+        #
+        # NB Do this before implicit scheduling as we don't want old clients to \
trigger scheduling when +        # the X- property is missing.
+        new_has_private_comments = False
+        if config.Scheduling["CalDAV"].get("EnablePrivateComments", True) and \
self.calendar is not None: +            old_has_private_comments = \
self.destination.exists() and self.destinationcal and \
self.destination.hasDeadProperty(TwistedCalendarHasPrivateCommentsProperty) +         \
new_has_private_comments = self.calendar.hasPropertyInAnyComponent(( +                \
"X-CALENDARSERVER-PRIVATE-COMMENT", +                \
"X-CALENDARSERVER-ATTENDEE-COMMENT", +            ))
+            
+            if old_has_private_comments and not new_has_private_comments:
+                # Transfer old comments to new calendar
+                log.debug("Private Comments properties were entirely removed by the \
client. Restoring existing properties.") +                old_calendar = \
self.destination.iCalendar() +                \
self.calendar.transferProperties(old_calendar, ( +                    \
"X-CALENDARSERVER-PRIVATE-COMMENT", +                    \
"X-CALENDARSERVER-ATTENDEE-COMMENT", +                ))
+                self.calendardata = None
+        
+        return new_has_private_comments
+
     @inlineCallbacks
-    def doStore(self):
-        # Do put or copy based on whether source exists
-        if self.source is not None:
-            response = maybeDeferred(copyWithXAttrs, self.source.fp, \
self.destination.fp, self.destination_uri) +    def doImplicitScheduling(self):
+        data_changed = False
+
+        # Do scheduling
+        if not self.isiTIP:
+            scheduler = ImplicitScheduler()
+            
+            # Determine type of operation PUT, COPY or DELETE
+            if not self.source:
+                # PUT
+                do_implicit_action, is_scheduling_resource = (yield \
scheduler.testImplicitSchedulingPUT( +                    self.request,
+                    self.destination,
+                    self.destination_uri,
+                    self.calendar,
+                    internal_request=self.internal_request,
+                ))
+            elif self.deletesource:
+                # MOVE
+                do_implicit_action, is_scheduling_resource = (yield \
scheduler.testImplicitSchedulingMOVE( +                    self.request,
+                    self.source,
+                    self.sourcecal,
+                    self.source_uri,
+                    self.destination,
+                    self.destinationcal,
+                    self.destination_uri,
+                    self.calendar,
+                    internal_request=self.internal_request,
+                ))
+            else:
+                # COPY
+                do_implicit_action, is_scheduling_resource = (yield \
scheduler.testImplicitSchedulingCOPY( +                    self.request,
+                    self.source,
+                    self.sourcecal,
+                    self.source_uri,
+                    self.destination,
+                    self.destinationcal,
+                    self.destination_uri,
+                    self.calendar,
+                    internal_request=self.internal_request,
+                ))
+            
+            if do_implicit_action and self.allowImplicitSchedule:
+                new_calendar = (yield scheduler.doImplicitScheduling())
+                if new_calendar:
+                    self.calendar = new_calendar
+                    self.calendardata = str(self.calendar)
+                    data_changed = True
         else:
-            if self.calendardata is None:
-                self.calendardata = str(self.calendar)
-            md5 = MD5StreamWrapper(MemoryStream(self.calendardata))
-            response = maybeDeferred(putWithXAttrs, md5, self.destination.fp)
-        response = (yield response)
+            is_scheduling_resource = False
+            
+        returnValue((is_scheduling_resource, data_changed,))
 
-        # Update the MD5 value on the resource
+    @inlineCallbacks
+    def doStore(self, implicit):
+        # Do put or copy based on whether source exists
         if self.source is not None:
-            # Copy MD5 value from source to destination
-            if self.source.hasDeadProperty(TwistedGETContentMD5):
-                md5 = self.source.readDeadProperty(TwistedGETContentMD5)
-                self.destination.writeDeadProperty(md5)
+            if implicit:
+                response = (yield self.doStorePut())
+                copyXAttrs(self.source.fp, self.destination.fp)
+            else:
+                response = (yield copyWithXAttrs(self.source.fp, \
self.destination.fp, self.destination_uri))  else:
-            # Finish MD5 calculation and write dead property
-            md5.close()
-            md5 = md5.getMD5()
-            self.destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
+            response = (yield self.doStorePut())
     
         # Update calendar-access property value on the resource
         if self.access:
@@ -583,6 +685,21 @@
         returnValue(IResponse(response))
 
     @inlineCallbacks
+    def doStorePut(self):
+
+        if self.calendardata is None:
+            self.calendardata = str(self.calendar)
+        md5 = MD5StreamWrapper(MemoryStream(self.calendardata))
+        response = (yield putWithXAttrs(md5, self.destination.fp))
+
+        # Finish MD5 calculation and write dead property
+        md5.close()
+        md5 = md5.getMD5()
+        self.destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
+
+        returnValue(response)
+
+    @inlineCallbacks
     def doSourceDelete(self):
         # Delete index for original item
         if self.sourcecal:
@@ -715,35 +832,11 @@
             # Get current quota state.
             yield self.checkQuota()
     
-            # Check for private comments on the old resource and the new resource \
                and re-insert
-            # ones that are lost.
-            #
-            # NB Do this before implicit scheduling as we don't want old clients to \
                trigger scheduling when
-            # the X- property is missing.
-            if config.Scheduling["CalDAV"].get("EnablePrivateComments", True):
-                old_has_private_comments = self.destination.exists() and \
self.destinationcal and \
                self.destination.hasDeadProperty(TwistedCalendarHasPrivateCommentsProperty)
                
-                new_has_private_comments = self.calendar.hasPropertyInAnyComponent((
-                    "X-CALENDARSERVER-PRIVATE-COMMENT",
-                    "X-CALENDARSERVER-ATTENDEE-COMMENT",
-                ))
-                
-                if old_has_private_comments and not new_has_private_comments:
-                    # Transfer old comments to new calendar
-                    log.debug("Private Comments properties were entirely removed by \
                the client. Restoring existing properties.")
-                    old_calendar = self.destination.iCalendar()
-                    self.calendar.transferProperties(old_calendar, (
-                        "X-CALENDARSERVER-PRIVATE-COMMENT",
-                        "X-CALENDARSERVER-ATTENDEE-COMMENT",
-                    ))
-                    self.calendardata = None
+            # Preserve private comments
+            new_has_private_comments = self.preservePrivateComments()
     
             # Do scheduling
-            if not self.isiTIP and self.allowImplicitSchedule:
-                scheduler = ImplicitScheduler()
-                new_calendar = (yield scheduler.doImplicitScheduling(self.request, \
                self.destination, self.calendar, False, \
                internal_request=self.internal_request))
-                if new_calendar:
-                    self.calendar = new_calendar
-                    self.calendardata = str(self.calendar)
+            is_scheduling_resource, data_changed = (yield \
self.doImplicitScheduling())  
             # Initialize the rollback system
             self.setupRollback()
@@ -763,9 +856,15 @@
             """
     
             # Do the actual put or copy
-            response = (yield self.doStore())
+            response = (yield self.doStore(data_changed))
             
 
+            # Check for scheduling object resource and write property
+            if is_scheduling_resource:
+                self.destination.writeDeadProperty(TwistedSchedulingObjectResource())
 +            elif not self.destinationcal:
+                self.destination.removeDeadProperty(TwistedSchedulingObjectResource) \
 +
             # Check for existence of private comments and write property
             if config.Scheduling["CalDAV"].get("EnablePrivateComments", True):
                 if new_has_private_comments:

Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2008-10-31 02:20:00 UTC \
                (rev 3294)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2008-10-31 02:33:19 UTC \
(rev 3295) @@ -17,7 +17,10 @@
 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.web2 import responsecode
 from twisted.web2.dav.http import ErrorResponse
+from twisted.web2.dav.util import joinURL
+from twisted.web2.dav.util import parentForURL
 from twisted.web2.http import HTTPError
+
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.scheduling.itip import iTipGenerator
 from twistedcaldav.log import Logger
@@ -29,6 +32,7 @@
 from twistedcaldav.scheduling import addressmapping
 from twistedcaldav.scheduling.cuaddress import InvalidCalendarUser,\
     LocalCalendarUser
+from twistedcaldav.customxml import TwistedSchedulingObjectResource
 
 __all__ = [
     "ImplicitScheduler",
@@ -51,48 +55,154 @@
         pass
 
     @inlineCallbacks
-    def doImplicitScheduling(self, request, resource, calendar, deleting, \
                internal_request=False):
-        """
-        Do implicit scheduling operation based on the calendar data that is being \
PUT +    def testImplicitSchedulingPUT(self, request, resource, resource_uri, \
calendar, internal_request=False): +        
+        self.request = request
+        self.resource = resource
+        self.calendar = calendar
+        self.internal_request = internal_request
 
-        @param request:
-        @type request:
-        @param resource:
-        @type resource:
-        @param calendar: the calendar data being written, or None if deleting
-        @type calendar: L{Component} or C{None}
-        @param deleting: C{True} if the resource is being deleting
-        @type deleting: bool
+        existing_resource = resource.exists()
+        existing_type = "schedule" if existing_resource and \
resource.hasDeadProperty(TwistedSchedulingObjectResource()) else "calendar" +        \
new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"  
-        @return: a new calendar object modified with scheduling information,
-            or C{None} if nothing happened
-        """
+        if existing_type == "calendar":
+            self.action = "create" if new_type == "schedule" else "none"
+        else:
+            self.action = "modify" if new_type == "schedule" else "remove"
+                
+        # Cannot create new resource with existing UID
+        if not existing_resource or self.action == "create":
+            yield self.hasCalendarResourceUIDSomewhereElse(None, resource_uri, \
new_type) +
+        # If action is remove we actually need to get state from the existing \
scheduling object resource +        if self.action == "remove":
+            # Also make sure that we return the new calendar being be written rather \
than the old one +            # when the implicit action is executed
+            self.return_calendar = calendar
+            self.calendar = resource.iCalendar()
+            yield self.checkImplicitState()
         
+        # Attendees are not allowed to overwrite one type with another
+        if self.state == "attendee" and (existing_type != new_type) and \
existing_resource: +            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, \
(caldav_namespace, "valid-attendee-change"))) +
+        returnValue((self.action != "none", new_type == "schedule",))
+
+    @inlineCallbacks
+    def testImplicitSchedulingMOVE(self, request, srcresource, srccal, src_uri, \
destresource, destcal, dest_uri, calendar, internal_request=False): +        
         self.request = request
+        self.resource = destresource
+        self.calendar = calendar
+        self.internal_request = internal_request
+
+        new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
+
+        dest_exists = destresource.exists()
+        dest_is_implicit = \
destresource.hasDeadProperty(TwistedSchedulingObjectResource()) if dest_exists else \
False +        src_is_implicit = \
srcresource.hasDeadProperty(TwistedSchedulingObjectResource()) or new_type == \
"schedule" +
+        if srccal and destcal:
+            if src_is_implicit and dest_exists or dest_is_implicit:
+                log.debug("Implicit - cannot MOVE with a scheduling object \
resource") +                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, \
(caldav_namespace, "unique-scheduling-object-resource"))) +            else:
+                self.action = "none"
+        elif srccal and not destcal:
+            result = (yield self.testImplicitSchedulingDELETE(request, srcresource, \
calendar)) +            returnValue((result[0], new_type == "schedule",))
+        elif not srccal and destcal:
+            result = (yield self.testImplicitSchedulingPUT(request, destresource, \
dest_uri, calendar)) +            returnValue(result)
+        else:
+            self.action = "none"
+
+        returnValue((self.action != "none", new_type == "schedule",))
+
+    @inlineCallbacks
+    def testImplicitSchedulingCOPY(self, request, srcresource, srccal, src_uri, \
destresource, destcal, dest_uri, calendar, internal_request=False): +        
+        self.request = request
+        self.resource = destresource
+        self.calendar = calendar
+        self.internal_request = internal_request
+
+        new_type = "schedule" if (yield self.checkImplicitState()) else "calendar"
+
+        dest_exists = destresource.exists()
+        dest_is_implicit = \
destresource.hasDeadProperty(TwistedSchedulingObjectResource()) if dest_exists else \
False +        src_is_implicit = \
srcresource.hasDeadProperty(TwistedSchedulingObjectResource()) or new_type == \
"schedule" +
+        if srccal and destcal:
+            if src_is_implicit or dest_is_implicit:
+                log.debug("Implicit - cannot COPY with a scheduling object \
resource") +                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, \
(caldav_namespace, "unique-scheduling-object-resource"))) +            else:
+                self.action = "none"
+        elif srccal and not destcal:
+            self.action = "none"
+        elif not srccal and destcal:
+            result = (yield self.testImplicitSchedulingPUT(request, destresource, \
dest_uri, calendar)) +            returnValue(result)
+        else:
+            self.action = "none"
+
+        returnValue((self.action != "none", src_is_implicit,))
+
+    @inlineCallbacks
+    def testImplicitSchedulingDELETE(self, request, resource, calendar, \
internal_request=False): +        
+        self.request = request
         self.resource = resource
         self.calendar = calendar
-        self.calendar_owner = (yield self.resource.owner(self.request))
-        self.deleting = deleting
         self.internal_request = internal_request
-        self.except_attendees = ()
 
-        # When deleting we MUST have the calendar as the actual resource
-        # will have been deleted by now
-        assert deleting and calendar or not deleting
+        yield self.checkImplicitState()
 
+        resource_type = "schedule" if \
resource.hasDeadProperty(TwistedSchedulingObjectResource()) else "calendar" +        \
self.action = "remove" if resource_type == "schedule" else "none" +
+        returnValue((self.action != "none", False,))
+
+    @inlineCallbacks
+    def checkImplicitState(self):
         # Get some useful information from the calendar
         yield self.extractCalendarData()
+        self.calendar_owner = (yield self.resource.owner(self.request))
 
         # Determine what type of scheduling this is: Organizer triggered or Attendee \
triggered  organizer_scheduling = (yield self.isOrganizerScheduling())
         if organizer_scheduling:
+            self.state = "organizer"
+        elif self.isAttendeeScheduling():
+            self.state = "attendee"
+        else:
+            self.state = None
+
+        returnValue(self.state is not None)
+
+    @inlineCallbacks
+    def doImplicitScheduling(self):
+        """
+        Do implicit scheduling operation based on the data already set by call to \
checkImplicitScheduling. +
+        @return: a new calendar object modified with scheduling information,
+            or C{None} if nothing happened
+        """
+        
+        # Setup some parameters
+        self.except_attendees = ()
+
+        # Determine what type of scheduling this is: Organizer triggered or Attendee \
triggered +        if self.state == "organizer":
             yield self.doImplicitOrganizer()
-        elif self.isAttendeeScheduling():
+        elif self.state == "attendee":
             yield self.doImplicitAttendee()
         else:
             returnValue(None)
 
-        returnValue(self.calendar)
+        returnValue(self.return_calendar if hasattr(self, "return_calendar") else \
self.calendar)  
     @inlineCallbacks
     def refreshAllAttendeesExceptSome(self, request, resource, calendar, attendees):
@@ -109,8 +219,10 @@
         self.request = request
         self.resource = resource
         self.calendar = calendar
+        self.state = "organizer"
+        self.action = "modify"
+
         self.calendar_owner = None
-        self.deleting = False
         self.internal_request = True
         self.except_attendees = attendees
         self.changed_rids = None
@@ -134,8 +246,10 @@
         self.request = request
         self.resource = resource
         self.calendar = calendar
+        self.action = "modify"
+        self.state = "attendee"
+
         self.calendar_owner = None
-        self.deleting = False
         self.internal_request = True
         self.changed_rids = None
         
@@ -191,6 +305,53 @@
         self.uid = self.calendar.resourceUID()
     
     @inlineCallbacks
+    def hasCalendarResourceUIDSomewhereElse(self, src_uri, dest_uri, type):
+        """
+        See if a calendar component with a matching UID exists anywhere in the \
calendar home of the +        current recipient owner and is not the resource being \
targeted. +        """
+
+        # Don't care in some cases
+        if self.internal_request or self.action == "remove":
+            returnValue(None)
+
+        # Get owner's calendar-home
+        calendar_owner_principal = (yield \
self.resource.ownerPrincipal(self.request)) +        calendar_home = \
calendar_owner_principal.calendarHome() +        
+        source_parent_uri = parentForURL(src_uri)[:-1] if src_uri else None
+        destination_parent_uri = parentForURL(dest_uri)[:-1] if dest_uri else None
+
+        # FIXME: because of the URL->resource request mapping thing, we have to \
force the request +        # to recognize this resource
+        self.request._rememberResource(calendar_home, calendar_home.url())
+
+        # Run a UID query against the UID
+
+        @inlineCallbacks
+        def queryCalendarCollection(collection, uri):
+            rname = collection.index().resourceNameForUID(self.uid)
+            if rname:
+                child = (yield self.request.locateResource(joinURL(uri, rname)))
+                matched_type = "schedule" if child and \
child.hasDeadProperty(TwistedSchedulingObjectResource()) else "calendar" +            \
if ( +                    uri != destination_parent_uri and
+                    (source_parent_uri is None or uri != source_parent_uri) and
+                    (type == "schedule" or matched_type == "schedule")
+                ):
+                    log.debug("Implicit - found component with same UID in a \
different collection: %s" % (uri,)) +                    raise \
HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, \
"unique-scheduling-object-resource"))) +
+                # Here we can always return true as the unique UID in a calendar \
collection +                # requirement will already have been tested.
+
+            returnValue(True)
+
+        # NB We are by-passing privilege checking here. That should be OK as the \
data found is not +        # exposed to the user.
+        yield report_common.applyToCalendarCollections(calendar_home, self.request, \
calendar_home.url(), "infinity", queryCalendarCollection, None) +
+    @inlineCallbacks
     def isOrganizerScheduling(self):
         """
         Test whether this is a scheduling operation by an organizer
@@ -253,16 +414,16 @@
             yield self.doAccessControl(self.organizerPrincipal, True)
 
         # Check for a delete
-        if self.deleting:
+        if self.action == "remove":
 
-            log.debug("Implicit - organizer '%s' is deleting UID: '%s'" % \
(self.organizer, self.uid)) +            log.debug("Implicit - organizer '%s' is \
removing UID: '%s'" % (self.organizer, self.uid))  self.oldcalendar = self.calendar
 
             # Cancel all attendees
             self.cancelledAttendees = [(attendee, None) for attendee in \
self.attendees]  
         # Check for a new resource or an update
-        elif self.resource.exists():
+        elif self.action == "modify":
 
             # Read in existing data
             self.oldcalendar = self.resource.iCalendar()
@@ -271,14 +432,15 @@
             no_change, self.changed_rids = self.isChangeInsignificant()
             if no_change:
                 # Nothing to do
-                log.debug("Implicit - organizer '%s' is updating UID: '%s' but \
change is not significant" % (self.organizer, self.uid)) +                \
log.debug("Implicit - organizer '%s' is modifying UID: '%s' but change is not \
significant" % (self.organizer, self.uid))  returnValue(None)
             
-            log.debug("Implicit - organizer '%s' is updating UID: '%s'" % \
(self.organizer, self.uid)) +            log.debug("Implicit - organizer '%s' is \
modifying UID: '%s'" % (self.organizer, self.uid))  
             # Check for removed attendees
             self.findRemovedAttendees()
-        else:
+
+        elif self.action == "create":
             log.debug("Implicit - organizer '%s' is creating UID: '%s'" % \
(self.organizer, self.uid))  self.oldcalendar = None
             self.changed_rids = None
@@ -377,7 +539,7 @@
         yield self.processCancels()
         
         # Process regular requests next
-        if not self.deleting:
+        if self.action in ("create", "modify",):
             yield self.processRequests()
 
     @inlineCallbacks
@@ -402,7 +564,7 @@
             
             if None in rids:
                 # One big CANCEL will do
-                itipmsg = iTipGenerator.generateCancel(self.oldcalendar, \
(attendee,), None, self.deleting) +                itipmsg = \
iTipGenerator.generateCancel(self.oldcalendar, (attendee,), None, self.action == \
"remove")  else:
                 # Multiple CANCELs
                 itipmsg = iTipGenerator.generateCancel(self.oldcalendar, \
(attendee,), rids) @@ -470,9 +632,7 @@
         if not self.internal_request:
             yield self.doAccessControl(self.attendeePrincipal, False)
 
-        if self.deleting:
-            #log.error("Attendee '%s' is not allowed to delete an organized event: \
                UID:%s" % (self.attendeePrincipal, self.uid,))
-            #raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, \
(caldav_namespace, "valid-attendee-change"))) +        if self.action == "remove":
             log.debug("Implicit - attendee '%s' is cancelling UID: '%s'" % \
(self.attendee, self.uid))  yield self.scheduleCancelWithOrganizer()
         

Modified: CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py	2008-10-31 \
                02:20:00 UTC (rev 3294)
+++ CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py	2008-10-31 \
02:33:19 UTC (rev 3295) @@ -13,8 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
-from twistedcaldav.memcachelock import MemcacheLock
-from twistedcaldav.memcacher import Memcacher
 
 import os
 
@@ -26,6 +24,9 @@
 from twisted.web2.test.test_server import SimpleRequest
 
 from twistedcaldav.ical import Component
+from twistedcaldav.memcachelock import MemcacheLock
+from twistedcaldav.memcacher import Memcacher
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
 import twistedcaldav.test.util
 
 class CollectionContents (twistedcaldav.test.util.TestCase):
@@ -35,6 +36,8 @@
     data_dir = os.path.join(os.path.dirname(__file__), "data")
 
     def setUp(self):
+        
+        # Need to fake out memcache
         def _getFakeMemcacheProtocol(self):
             
             result = super(MemcacheLock, self)._getMemcacheProtocol()
@@ -45,6 +48,12 @@
         
         MemcacheLock._getMemcacheProtocol = _getFakeMemcacheProtocol
 
+        # Need to not do implicit behavior during these tests
+        def _fakeDoImplicitScheduling(self):
+            return False, False
+        
+        StoreCalendarObjectResource.doImplicitScheduling = _fakeDoImplicitScheduling
+
         super(CollectionContents, self).setUp()
 
     def test_collection_in_calendar(self):


[Attachment #5 (text/html)]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[3295] CalendarServer/trunk/twistedcaldav</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: \
verdana,arial,helvetica,sans-serif; font-size: 10pt;  } #msg dl a { font-weight: \
bold} #msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: \
bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: \
6px; } #logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em \
0; } #logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg \
h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; } \
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; \
} #logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: \
-1.5em; padding-left: 1.5em; } #logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em \
1em 0 1em; background: white;} #logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid \
#fa0; border-bottom: 1px solid #fa0; background: #fff; } #logmsg table th { \
text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted \
#fa0; } #logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: \
0.2em 0.5em; } #logmsg table thead th { text-align: center; border-bottom: 1px solid \
#fa0; } #logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: \
6px; } #patch { width: 100%; }
#patch h4 {font-family: \
verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
 #patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, \
#patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins \
{background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del \
{background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, \
                .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a \
href="http://trac.macosforge.org/projects/calendarserver/changeset/3295">3295</a></dd>
 <dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2008-10-30 19:33:19 -0700 (Thu, 30 Oct 2008)</dd>
</dl>

<h3>Log Message</h3>
<pre>Major refactor to implement proper PUT/COPY/MOVE/DELETE logic with implicit \
scheduling and also to restrict duplicate UIDs for scheduling messages.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunktwistedcaldavcustomxmlpy">CalendarServer/trunk/twistedcaldav/customxml.py</a></li>
 <li><a href="#CalendarServertrunktwistedcaldavmethoddeletepy">CalendarServer/trunk/twistedcaldav/method/delete.py</a></li>
 <li><a href="#CalendarServertrunktwistedcaldavmethodput_commonpy">CalendarServer/trunk/twistedcaldav/method/put_common.py</a></li>
 <li><a href="#CalendarServertrunktwistedcaldavschedulingimplicitpy">CalendarServer/trunk/twistedcaldav/scheduling/implicit.py</a></li>
 <li><a href="#CalendarServertrunktwistedcaldavtesttest_collectioncontentspy">CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py</a></li>
 </ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunktwistedcaldavcustomxmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/customxml.py \
(3294 => 3295)</h4> <pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/customxml.py	2008-10-31 \
                02:20:00 UTC (rev 3294)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2008-10-31 02:33:19 UTC (rev \
3295) </span><span class="lines">@@ -60,6 +60,14 @@
</span><span class="cx">     def getValue(self):
</span><span class="cx">         return str(self)
</span><span class="cx"> 
</span><ins>+class TwistedSchedulingObjectResource (davxml.WebDAVEmptyElement):
+    &quot;&quot;&quot;
+    Indicates that the resource is a scheduling object resource.    
+    &quot;&quot;&quot;
+    namespace = twisted_private_namespace
+    name = &quot;scheduling-object-resource&quot;
+    hidden = True
+
</ins><span class="cx"> class TwistedCalendarHasPrivateCommentsProperty \
(davxml.WebDAVEmptyElement): </span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Indicates that a calendar resource has private comments.
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethoddeletepy"></a>
<div class="modfile"><h4>Modified: \
CalendarServer/trunk/twistedcaldav/method/delete.py (3294 => 3295)</h4> <pre \
class="diff"><span> <span class="info">--- \
CalendarServer/trunk/twistedcaldav/method/delete.py	2008-10-31 02:20:00 UTC (rev \
                3294)
+++ CalendarServer/trunk/twistedcaldav/method/delete.py	2008-10-31 02:33:19 UTC (rev \
3295) </span><span class="lines">@@ -22,13 +22,18 @@
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, \
returnValue </span><span class="cx"> from twisted.web2 import responsecode
</span><ins>+from twisted.web2.dav import davxml
+from twisted.web2.dav.fileop import delete
</ins><span class="cx"> from twisted.web2.dav.util import parentForURL
</span><span class="cx"> from twisted.web2.http import HTTPError, StatusResponse
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.memcachelock import MemcacheLock, \
MemcacheLockTimeoutError </span><span class="cx"> from twistedcaldav.resource import \
isCalendarCollectionResource </span><span class="cx"> from \
twistedcaldav.scheduling.implicit import ImplicitScheduler </span><ins>+from \
twistedcaldav.log import Logger </ins><span class="cx"> 
</span><ins>+log = Logger()
+
</ins><span class="cx"> @inlineCallbacks
</span><span class="cx"> def http_DELETE(self, request):
</span><span class="cx">     #
</span><span class="lines">@@ -39,10 +44,28 @@
</span><span class="cx">     # TODO: need to use transaction based delete on live \
scheduling object resources </span><span class="cx">     # as the iTIP operation may \
fail and may need to prevent the delete from happening. </span><span class="cx"> 
</span><ins>+    if not self.fp.exists():
+        log.err(&quot;File not found: %s&quot; % (self.fp.path,))
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    depth = request.headers.getHeader(&quot;depth&quot;, &quot;infinity&quot;)
+
+    #
+    # Check authentication and access controls
+    #
</ins><span class="cx">     parentURL = parentForURL(request.uri)
</span><span class="cx">     parent = (yield request.locateResource(parentURL))
</span><span class="cx"> 
</span><del>-    calendar = None
</del><ins>+    yield parent.authorize(request, (davxml.Unbind(),))
+
+    # Do quota checks before we start deleting things
+    myquota = (yield self.quota(request))
+    if myquota is not None:
+        old_size = (yield self.quotaSize(request))
+    else:
+        old_size = 0
+
+    scheduler = None
</ins><span class="cx">     isCalendarCollection = False
</span><span class="cx">     isCalendarResource = False
</span><span class="cx">     lock = None
</span><span class="lines">@@ -51,7 +74,12 @@
</span><span class="cx">         if isCalendarCollectionResource(parent):
</span><span class="cx">             isCalendarResource = True
</span><span class="cx">             calendar = self.iCalendar()
</span><del>-            lock = MemcacheLock(&quot;ImplicitUIDLock&quot;, \
calendar.resourceUID(), timeout=60.0) </del><ins>+            scheduler = \
ImplicitScheduler() +            do_implicit_action, _ignore = (yield \
scheduler.testImplicitSchedulingDELETE(request, self, calendar)) +            if \
do_implicit_action: +                lock = MemcacheLock(&quot;ImplicitUIDLock&quot;, \
calendar.resourceUID(), timeout=60.0) +            else:
+                scheduler = None
</ins><span class="cx">             
</span><span class="cx">         elif isCalendarCollectionResource(self):
</span><span class="cx">             isCalendarCollection = True
</span><span class="lines">@@ -60,8 +88,14 @@
</span><span class="cx">         if lock:
</span><span class="cx">             yield lock.acquire()
</span><span class="cx"> 
</span><del>-        response = (yield super(CalDAVFile, self).http_DELETE(request))
</del><ins>+        # Do delete
+        response = (yield delete(request.uri, self.fp, depth))
</ins><span class="cx">     
</span><ins>+
+        # Adjust quota
+        if myquota is not None:
+            yield self.quotaSizeAdjust(request, -old_size)
+
</ins><span class="cx">         if response == responsecode.NO_CONTENT:
</span><span class="cx">             if isCalendarResource:
</span><span class="cx">     
</span><span class="lines">@@ -72,8 +106,8 @@
</span><span class="cx">                 yield parent.updateCTag()
</span><span class="cx">     
</span><span class="cx">                 # Do scheduling
</span><del>-                scheduler = ImplicitScheduler()
-                yield scheduler.doImplicitScheduling(request, self, calendar, True)
</del><ins>+                if scheduler:
+                    yield scheduler.doImplicitScheduling()
</ins><span class="cx">      
</span><span class="cx">             elif isCalendarCollection:
</span><span class="cx">                 
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethodput_commonpy"></a>
<div class="modfile"><h4>Modified: \
CalendarServer/trunk/twistedcaldav/method/put_common.py (3294 => 3295)</h4> <pre \
class="diff"><span> <span class="info">--- \
CalendarServer/trunk/twistedcaldav/method/put_common.py	2008-10-31 02:20:00 UTC (rev \
                3294)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2008-10-31 02:33:19 UTC \
(rev 3295) </span><span class="lines">@@ -25,7 +25,7 @@
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet import reactor
</span><span class="cx"> from twisted.internet.defer import Deferred, \
inlineCallbacks, succeed </span><del>-from twisted.internet.defer import \
maybeDeferred, returnValue </del><ins>+from twisted.internet.defer import returnValue
</ins><span class="cx"> from twisted.python import failure
</span><span class="cx"> from twisted.python.filepath import FilePath
</span><span class="cx"> from twisted.web2 import responsecode
</span><span class="lines">@@ -48,9 +48,9 @@
</span><span class="cx"> from twistedcaldav.caldavxml import \
NumberOfRecurrencesWithinLimits </span><span class="cx"> from twistedcaldav.caldavxml \
import caldav_namespace </span><span class="cx"> from twistedcaldav.customxml import \
calendarserver_namespace ,\ </span><del>-    \
TwistedCalendarHasPrivateCommentsProperty </del><ins>+    \
TwistedCalendarHasPrivateCommentsProperty, TwistedSchedulingObjectResource \
</ins><span class="cx"> from twistedcaldav.customxml import \
TwistedCalendarAccessProperty </span><del>-from twistedcaldav.fileops import \
copyToWithXAttrs </del><ins>+from twistedcaldav.fileops import copyToWithXAttrs, \
copyXAttrs </ins><span class="cx"> from twistedcaldav.fileops import putWithXAttrs
</span><span class="cx"> from twistedcaldav.fileops import copyWithXAttrs
</span><span class="cx"> from twistedcaldav.ical import Component, Property
</span><span class="lines">@@ -265,11 +265,15 @@
</span><span class="cx">         self.rollback = None
</span><span class="cx">         self.access = None
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
</ins><span class="cx">     def fullValidation(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Do full validation of source and destination \
calendar data. </span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        # Basic validation
+        yield self.validCopyMoveOperation()
+
</ins><span class="cx">         if self.destinationcal:
</span><span class="cx">             # Valid resource name check
</span><span class="cx">             result, message = self.validResourceName()
</span><span class="lines">@@ -330,10 +334,38 @@
</span><span class="cx"> 
</span><span class="cx">             # Check access
</span><span class="cx">             if self.destinationcal and \
config.EnablePrivateEvents: </span><del>-                return self.validAccess()
</del><ins>+                result = (yield self.validAccess())
+                returnValue(result)
</ins><span class="cx">             else:
</span><del>-                return succeed(None)
</del><ins>+                returnValue(None)
+
+        elif self.sourcecal:
+            self.source_index = self.sourceparent.index()
+            self.calendar = self.source.iCalendar()
</ins><span class="cx">     
</span><ins>+    @inlineCallbacks
+    def validCopyMoveOperation(self):
+        &quot;&quot;&quot;
+        Check that copy/move type behavior is valid.
+        &quot;&quot;&quot;
+        if self.source:
+            if not self.destinationcal:
+                # Don't care about copies/moves to non-calendar destinations
+                # In theory this state should not occur here as COPY/MOVE won't call \
into this as +                # they detect this state and do regular WebDAV \
copy/move. +                pass
+            elif not self.sourcecal:
+                # Moving into a calendar requires regular checks
+                pass
+            else:
+                # Calendar to calendar moves are OK if the owner is the same
+                sourceowner = (yield self.sourceparent.owner(self.request))
+                destowner = (yield self.destinationparent.owner(self.request))
+                if sourceowner != destowner:
+                    msg = &quot;Calendar-to-calendar %s with different owners are \
not supported&quot; % (&quot;moves&quot; if self.deletesource else \
&quot;copies&quot;,) +                    log.debug(msg)
+                    raise HTTPError(StatusResponse(responsecode.FORBIDDEN, msg))
+
</ins><span class="cx">     def validResourceName(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Make sure that the resource name for the new \
resource is valid. </span><span class="lines">@@ -546,29 +578,99 @@
</span><span class="cx">             copyToWithXAttrs(self.source.fp, \
self.rollback.source_copy) </span><span class="cx">             \
log.debug(&quot;Rollback: backing up source %s to %s&quot; % (self.source.fp.path, \
self.rollback.source_copy.path)) </span><span class="cx"> 
</span><ins>+    def preservePrivateComments(self):
+        # Check for private comments on the old resource and the new resource and \
re-insert +        # ones that are lost.
+        #
+        # NB Do this before implicit scheduling as we don't want old clients to \
trigger scheduling when +        # the X- property is missing.
+        new_has_private_comments = False
+        if config.Scheduling[&quot;CalDAV&quot;].get(&quot;EnablePrivateComments&quot;, \
True) and self.calendar is not None: +            old_has_private_comments = \
self.destination.exists() and self.destinationcal and \
self.destination.hasDeadProperty(TwistedCalendarHasPrivateCommentsProperty) +         \
new_has_private_comments = self.calendar.hasPropertyInAnyComponent(( +                \
&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;, +                \
&quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;, +            ))
+            
+            if old_has_private_comments and not new_has_private_comments:
+                # Transfer old comments to new calendar
+                log.debug(&quot;Private Comments properties were entirely removed by \
the client. Restoring existing properties.&quot;) +                old_calendar = \
self.destination.iCalendar() +                \
self.calendar.transferProperties(old_calendar, ( +                    \
&quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;, +                    \
&quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;, +                ))
+                self.calendardata = None
+        
+        return new_has_private_comments
+
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def doStore(self):
-        # Do put or copy based on whether source exists
-        if self.source is not None:
-            response = maybeDeferred(copyWithXAttrs, self.source.fp, \
self.destination.fp, self.destination_uri) </del><ins>+    def \
doImplicitScheduling(self): +        data_changed = False
+
+        # Do scheduling
+        if not self.isiTIP:
+            scheduler = ImplicitScheduler()
+            
+            # Determine type of operation PUT, COPY or DELETE
+            if not self.source:
+                # PUT
+                do_implicit_action, is_scheduling_resource = (yield \
scheduler.testImplicitSchedulingPUT( +                    self.request,
+                    self.destination,
+                    self.destination_uri,
+                    self.calendar,
+                    internal_request=self.internal_request,
+                ))
+            elif self.deletesource:
+                # MOVE
+                do_implicit_action, is_scheduling_resource = (yield \
scheduler.testImplicitSchedulingMOVE( +                    self.request,
+                    self.source,
+                    self.sourcecal,
+                    self.source_uri,
+                    self.destination,
+                    self.destinationcal,
+                    self.destination_uri,
+                    self.calendar,
+                    internal_request=self.internal_request,
+                ))
+            else:
+                # COPY
+                do_implicit_action, is_scheduling_resource = (yield \
scheduler.testImplicitSchedulingCOPY( +                    self.request,
+                    self.source,
+                    self.sourcecal,
+                    self.source_uri,
+                    self.destination,
+                    self.destinationcal,
+                    self.destination_uri,
+                    self.calendar,
+                    internal_request=self.internal_request,
+                ))
+            
+            if do_implicit_action and self.allowImplicitSchedule:
+                new_calendar = (yield scheduler.doImplicitScheduling())
+                if new_calendar:
+                    self.calendar = new_calendar
+                    self.calendardata = str(self.calendar)
+                    data_changed = True
</ins><span class="cx">         else:
</span><del>-            if self.calendardata is None:
-                self.calendardata = str(self.calendar)
-            md5 = MD5StreamWrapper(MemoryStream(self.calendardata))
-            response = maybeDeferred(putWithXAttrs, md5, self.destination.fp)
-        response = (yield response)
</del><ins>+            is_scheduling_resource = False
+            
+        returnValue((is_scheduling_resource, data_changed,))
</ins><span class="cx"> 
</span><del>-        # Update the MD5 value on the resource
</del><ins>+    @inlineCallbacks
+    def doStore(self, implicit):
+        # Do put or copy based on whether source exists
</ins><span class="cx">         if self.source is not None:
</span><del>-            # Copy MD5 value from source to destination
-            if self.source.hasDeadProperty(TwistedGETContentMD5):
-                md5 = self.source.readDeadProperty(TwistedGETContentMD5)
-                self.destination.writeDeadProperty(md5)
</del><ins>+            if implicit:
+                response = (yield self.doStorePut())
+                copyXAttrs(self.source.fp, self.destination.fp)
+            else:
+                response = (yield copyWithXAttrs(self.source.fp, \
self.destination.fp, self.destination_uri)) </ins><span class="cx">         else:
</span><del>-            # Finish MD5 calculation and write dead property
-            md5.close()
-            md5 = md5.getMD5()
-            self.destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
</del><ins>+            response = (yield self.doStorePut())
</ins><span class="cx">     
</span><span class="cx">         # Update calendar-access property value on the \
resource </span><span class="cx">         if self.access:
</span><span class="lines">@@ -583,6 +685,21 @@
</span><span class="cx">         returnValue(IResponse(response))
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def doStorePut(self):
+
+        if self.calendardata is None:
+            self.calendardata = str(self.calendar)
+        md5 = MD5StreamWrapper(MemoryStream(self.calendardata))
+        response = (yield putWithXAttrs(md5, self.destination.fp))
+
+        # Finish MD5 calculation and write dead property
+        md5.close()
+        md5 = md5.getMD5()
+        self.destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
+
+        returnValue(response)
+
+    @inlineCallbacks
</ins><span class="cx">     def doSourceDelete(self):
</span><span class="cx">         # Delete index for original item
</span><span class="cx">         if self.sourcecal:
</span><span class="lines">@@ -715,35 +832,11 @@
</span><span class="cx">             # Get current quota state.
</span><span class="cx">             yield self.checkQuota()
</span><span class="cx">     
</span><del>-            # Check for private comments on the old resource and the new \
                resource and re-insert
-            # ones that are lost.
-            #
-            # NB Do this before implicit scheduling as we don't want old clients to \
                trigger scheduling when
-            # the X- property is missing.
-            if config.Scheduling[&quot;CalDAV&quot;].get(&quot;EnablePrivateComments&quot;, \
                True):
-                old_has_private_comments = self.destination.exists() and \
self.destinationcal and \
                self.destination.hasDeadProperty(TwistedCalendarHasPrivateCommentsProperty)
                
-                new_has_private_comments = self.calendar.hasPropertyInAnyComponent((
-                    &quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;,
-                    &quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;,
-                ))
-                
-                if old_has_private_comments and not new_has_private_comments:
-                    # Transfer old comments to new calendar
-                    log.debug(&quot;Private Comments properties were entirely \
                removed by the client. Restoring existing properties.&quot;)
-                    old_calendar = self.destination.iCalendar()
-                    self.calendar.transferProperties(old_calendar, (
-                        &quot;X-CALENDARSERVER-PRIVATE-COMMENT&quot;,
-                        &quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;,
-                    ))
-                    self.calendardata = None
</del><ins>+            # Preserve private comments
+            new_has_private_comments = self.preservePrivateComments()
</ins><span class="cx">     
</span><span class="cx">             # Do scheduling
</span><del>-            if not self.isiTIP and self.allowImplicitSchedule:
-                scheduler = ImplicitScheduler()
-                new_calendar = (yield scheduler.doImplicitScheduling(self.request, \
                self.destination, self.calendar, False, \
                internal_request=self.internal_request))
-                if new_calendar:
-                    self.calendar = new_calendar
-                    self.calendardata = str(self.calendar)
</del><ins>+            is_scheduling_resource, data_changed = (yield \
self.doImplicitScheduling()) </ins><span class="cx"> 
</span><span class="cx">             # Initialize the rollback system
</span><span class="cx">             self.setupRollback()
</span><span class="lines">@@ -763,9 +856,15 @@
</span><span class="cx">             &quot;&quot;&quot;
</span><span class="cx">     
</span><span class="cx">             # Do the actual put or copy
</span><del>-            response = (yield self.doStore())
</del><ins>+            response = (yield self.doStore(data_changed))
</ins><span class="cx">             
</span><span class="cx"> 
</span><ins>+            # Check for scheduling object resource and write property
+            if is_scheduling_resource:
+                self.destination.writeDeadProperty(TwistedSchedulingObjectResource())
 +            elif not self.destinationcal:
+                self.destination.removeDeadProperty(TwistedSchedulingObjectResource) \
 +
</ins><span class="cx">             # Check for existence of private comments and \
write property </span><span class="cx">             if \
config.Scheduling[&quot;CalDAV&quot;].get(&quot;EnablePrivateComments&quot;, True): \
</span><span class="cx">                 if new_has_private_comments: \
</span></span></pre></div> <a \
id="CalendarServertrunktwistedcaldavschedulingimplicitpy"></a> <div \
class="modfile"><h4>Modified: \
CalendarServer/trunk/twistedcaldav/scheduling/implicit.py (3294 => 3295)</h4> <pre \
class="diff"><span> <span class="info">--- \
CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2008-10-31 02:20:00 UTC \
                (rev 3294)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2008-10-31 02:33:19 UTC \
(rev 3295) </span><span class="lines">@@ -17,7 +17,10 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, \
returnValue, succeed </span><span class="cx"> from twisted.web2 import responsecode
</span><span class="cx"> from twisted.web2.dav.http import ErrorResponse
</span><ins>+from twisted.web2.dav.util import joinURL
+from twisted.web2.dav.util import parentForURL
</ins><span class="cx"> from twisted.web2.http import HTTPError
</span><ins>+
</ins><span class="cx"> from twistedcaldav.caldavxml import caldav_namespace
</span><span class="cx"> from twistedcaldav.scheduling.itip import iTipGenerator
</span><span class="cx"> from twistedcaldav.log import Logger
</span><span class="lines">@@ -29,6 +32,7 @@
</span><span class="cx"> from twistedcaldav.scheduling import addressmapping
</span><span class="cx"> from twistedcaldav.scheduling.cuaddress import \
InvalidCalendarUser,\ </span><span class="cx">     LocalCalendarUser
</span><ins>+from twistedcaldav.customxml import TwistedSchedulingObjectResource
</ins><span class="cx"> 
</span><span class="cx"> __all__ = [
</span><span class="cx">     &quot;ImplicitScheduler&quot;,
</span><span class="lines">@@ -51,48 +55,154 @@
</span><span class="cx">         pass
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def doImplicitScheduling(self, request, resource, calendar, \
                deleting, internal_request=False):
-        &quot;&quot;&quot;
-        Do implicit scheduling operation based on the calendar data that is being \
PUT </del><ins>+    def testImplicitSchedulingPUT(self, request, resource, \
resource_uri, calendar, internal_request=False): +        
+        self.request = request
+        self.resource = resource
+        self.calendar = calendar
+        self.internal_request = internal_request
</ins><span class="cx"> 
</span><del>-        @param request:
-        @type request:
-        @param resource:
-        @type resource:
-        @param calendar: the calendar data being written, or None if deleting
-        @type calendar: L{Component} or C{None}
-        @param deleting: C{True} if the resource is being deleting
-        @type deleting: bool
</del><ins>+        existing_resource = resource.exists()
+        existing_type = &quot;schedule&quot; if existing_resource and \
resource.hasDeadProperty(TwistedSchedulingObjectResource()) else &quot;calendar&quot; \
+        new_type = &quot;schedule&quot; if (yield self.checkImplicitState()) else \
&quot;calendar&quot; </ins><span class="cx"> 
</span><del>-        @return: a new calendar object modified with scheduling \
                information,
-            or C{None} if nothing happened
-        &quot;&quot;&quot;
</del><ins>+        if existing_type == &quot;calendar&quot;:
+            self.action = &quot;create&quot; if new_type == &quot;schedule&quot; \
else &quot;none&quot; +        else:
+            self.action = &quot;modify&quot; if new_type == &quot;schedule&quot; \
else &quot;remove&quot; +                
+        # Cannot create new resource with existing UID
+        if not existing_resource or self.action == &quot;create&quot;:
+            yield self.hasCalendarResourceUIDSomewhereElse(None, resource_uri, \
new_type) +
+        # If action is remove we actually need to get state from the existing \
scheduling object resource +        if self.action == &quot;remove&quot;:
+            # Also make sure that we return the new calendar being be written rather \
than the old one +            # when the implicit action is executed
+            self.return_calendar = calendar
+            self.calendar = resource.iCalendar()
+            yield self.checkImplicitState()
</ins><span class="cx">         
</span><ins>+        # Attendees are not allowed to overwrite one type with another
+        if self.state == &quot;attendee&quot; and (existing_type != new_type) and \
existing_resource: +            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, \
(caldav_namespace, &quot;valid-attendee-change&quot;))) +
+        returnValue((self.action != &quot;none&quot;, new_type == \
&quot;schedule&quot;,)) +
+    @inlineCallbacks
+    def testImplicitSchedulingMOVE(self, request, srcresource, srccal, src_uri, \
destresource, destcal, dest_uri, calendar, internal_request=False): +        
</ins><span class="cx">         self.request = request
</span><ins>+        self.resource = destresource
+        self.calendar = calendar
+        self.internal_request = internal_request
+
+        new_type = &quot;schedule&quot; if (yield self.checkImplicitState()) else \
&quot;calendar&quot; +
+        dest_exists = destresource.exists()
+        dest_is_implicit = \
destresource.hasDeadProperty(TwistedSchedulingObjectResource()) if dest_exists else \
False +        src_is_implicit = \
srcresource.hasDeadProperty(TwistedSchedulingObjectResource()) or new_type == \
&quot;schedule&quot; +
+        if srccal and destcal:
+            if src_is_implicit and dest_exists or dest_is_implicit:
+                log.debug(&quot;Implicit - cannot MOVE with a scheduling object \
resource&quot;) +                raise \
HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, \
&quot;unique-scheduling-object-resource&quot;))) +            else:
+                self.action = &quot;none&quot;
+        elif srccal and not destcal:
+            result = (yield self.testImplicitSchedulingDELETE(request, srcresource, \
calendar)) +            returnValue((result[0], new_type == &quot;schedule&quot;,))
+        elif not srccal and destcal:
+            result = (yield self.testImplicitSchedulingPUT(request, destresource, \
dest_uri, calendar)) +            returnValue(result)
+        else:
+            self.action = &quot;none&quot;
+
+        returnValue((self.action != &quot;none&quot;, new_type == \
&quot;schedule&quot;,)) +
+    @inlineCallbacks
+    def testImplicitSchedulingCOPY(self, request, srcresource, srccal, src_uri, \
destresource, destcal, dest_uri, calendar, internal_request=False): +        
+        self.request = request
+        self.resource = destresource
+        self.calendar = calendar
+        self.internal_request = internal_request
+
+        new_type = &quot;schedule&quot; if (yield self.checkImplicitState()) else \
&quot;calendar&quot; +
+        dest_exists = destresource.exists()
+        dest_is_implicit = \
destresource.hasDeadProperty(TwistedSchedulingObjectResource()) if dest_exists else \
False +        src_is_implicit = \
srcresource.hasDeadProperty(TwistedSchedulingObjectResource()) or new_type == \
&quot;schedule&quot; +
+        if srccal and destcal:
+            if src_is_implicit or dest_is_implicit:
+                log.debug(&quot;Implicit - cannot COPY with a scheduling object \
resource&quot;) +                raise \
HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, \
&quot;unique-scheduling-object-resource&quot;))) +            else:
+                self.action = &quot;none&quot;
+        elif srccal and not destcal:
+            self.action = &quot;none&quot;
+        elif not srccal and destcal:
+            result = (yield self.testImplicitSchedulingPUT(request, destresource, \
dest_uri, calendar)) +            returnValue(result)
+        else:
+            self.action = &quot;none&quot;
+
+        returnValue((self.action != &quot;none&quot;, src_is_implicit,))
+
+    @inlineCallbacks
+    def testImplicitSchedulingDELETE(self, request, resource, calendar, \
internal_request=False): +        
+        self.request = request
</ins><span class="cx">         self.resource = resource
</span><span class="cx">         self.calendar = calendar
</span><del>-        self.calendar_owner = (yield self.resource.owner(self.request))
-        self.deleting = deleting
</del><span class="cx">         self.internal_request = internal_request
</span><del>-        self.except_attendees = ()
</del><span class="cx"> 
</span><del>-        # When deleting we MUST have the calendar as the actual resource
-        # will have been deleted by now
-        assert deleting and calendar or not deleting
</del><ins>+        yield self.checkImplicitState()
</ins><span class="cx"> 
</span><ins>+        resource_type = &quot;schedule&quot; if \
resource.hasDeadProperty(TwistedSchedulingObjectResource()) else &quot;calendar&quot; \
+        self.action = &quot;remove&quot; if resource_type == &quot;schedule&quot; \
else &quot;none&quot; +
+        returnValue((self.action != &quot;none&quot;, False,))
+
+    @inlineCallbacks
+    def checkImplicitState(self):
</ins><span class="cx">         # Get some useful information from the calendar
</span><span class="cx">         yield self.extractCalendarData()
</span><ins>+        self.calendar_owner = (yield self.resource.owner(self.request))
</ins><span class="cx"> 
</span><span class="cx">         # Determine what type of scheduling this is: \
Organizer triggered or Attendee triggered </span><span class="cx">         \
organizer_scheduling = (yield self.isOrganizerScheduling()) </span><span class="cx">  \
if organizer_scheduling: </span><ins>+            self.state = &quot;organizer&quot;
+        elif self.isAttendeeScheduling():
+            self.state = &quot;attendee&quot;
+        else:
+            self.state = None
+
+        returnValue(self.state is not None)
+
+    @inlineCallbacks
+    def doImplicitScheduling(self):
+        &quot;&quot;&quot;
+        Do implicit scheduling operation based on the data already set by call to \
checkImplicitScheduling. +
+        @return: a new calendar object modified with scheduling information,
+            or C{None} if nothing happened
+        &quot;&quot;&quot;
+        
+        # Setup some parameters
+        self.except_attendees = ()
+
+        # Determine what type of scheduling this is: Organizer triggered or Attendee \
triggered +        if self.state == &quot;organizer&quot;:
</ins><span class="cx">             yield self.doImplicitOrganizer()
</span><del>-        elif self.isAttendeeScheduling():
</del><ins>+        elif self.state == &quot;attendee&quot;:
</ins><span class="cx">             yield self.doImplicitAttendee()
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="cx"> 
</span><del>-        returnValue(self.calendar)
</del><ins>+        returnValue(self.return_calendar if hasattr(self, \
&quot;return_calendar&quot;) else self.calendar) </ins><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def refreshAllAttendeesExceptSome(self, request, \
resource, calendar, attendees): </span><span class="lines">@@ -109,8 +219,10 @@
</span><span class="cx">         self.request = request
</span><span class="cx">         self.resource = resource
</span><span class="cx">         self.calendar = calendar
</span><ins>+        self.state = &quot;organizer&quot;
+        self.action = &quot;modify&quot;
+
</ins><span class="cx">         self.calendar_owner = None
</span><del>-        self.deleting = False
</del><span class="cx">         self.internal_request = True
</span><span class="cx">         self.except_attendees = attendees
</span><span class="cx">         self.changed_rids = None
</span><span class="lines">@@ -134,8 +246,10 @@
</span><span class="cx">         self.request = request
</span><span class="cx">         self.resource = resource
</span><span class="cx">         self.calendar = calendar
</span><ins>+        self.action = &quot;modify&quot;
+        self.state = &quot;attendee&quot;
+
</ins><span class="cx">         self.calendar_owner = None
</span><del>-        self.deleting = False
</del><span class="cx">         self.internal_request = True
</span><span class="cx">         self.changed_rids = None
</span><span class="cx">         
</span><span class="lines">@@ -191,6 +305,53 @@
</span><span class="cx">         self.uid = self.calendar.resourceUID()
</span><span class="cx">     
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def hasCalendarResourceUIDSomewhereElse(self, src_uri, dest_uri, \
type): +        &quot;&quot;&quot;
+        See if a calendar component with a matching UID exists anywhere in the \
calendar home of the +        current recipient owner and is not the resource being \
targeted. +        &quot;&quot;&quot;
+
+        # Don't care in some cases
+        if self.internal_request or self.action == &quot;remove&quot;:
+            returnValue(None)
+
+        # Get owner's calendar-home
+        calendar_owner_principal = (yield \
self.resource.ownerPrincipal(self.request)) +        calendar_home = \
calendar_owner_principal.calendarHome() +        
+        source_parent_uri = parentForURL(src_uri)[:-1] if src_uri else None
+        destination_parent_uri = parentForURL(dest_uri)[:-1] if dest_uri else None
+
+        # FIXME: because of the URL-&gt;resource request mapping thing, we have to \
force the request +        # to recognize this resource
+        self.request._rememberResource(calendar_home, calendar_home.url())
+
+        # Run a UID query against the UID
+
+        @inlineCallbacks
+        def queryCalendarCollection(collection, uri):
+            rname = collection.index().resourceNameForUID(self.uid)
+            if rname:
+                child = (yield self.request.locateResource(joinURL(uri, rname)))
+                matched_type = &quot;schedule&quot; if child and \
child.hasDeadProperty(TwistedSchedulingObjectResource()) else &quot;calendar&quot; +  \
if ( +                    uri != destination_parent_uri and
+                    (source_parent_uri is None or uri != source_parent_uri) and
+                    (type == &quot;schedule&quot; or matched_type == \
&quot;schedule&quot;) +                ):
+                    log.debug(&quot;Implicit - found component with same UID in a \
different collection: %s&quot; % (uri,)) +                    raise \
HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, \
&quot;unique-scheduling-object-resource&quot;))) +
+                # Here we can always return true as the unique UID in a calendar \
collection +                # requirement will already have been tested.
+
+            returnValue(True)
+
+        # NB We are by-passing privilege checking here. That should be OK as the \
data found is not +        # exposed to the user.
+        yield report_common.applyToCalendarCollections(calendar_home, self.request, \
calendar_home.url(), &quot;infinity&quot;, queryCalendarCollection, None) +
+    @inlineCallbacks
</ins><span class="cx">     def isOrganizerScheduling(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test whether this is a scheduling operation by an \
organizer </span><span class="lines">@@ -253,16 +414,16 @@
</span><span class="cx">             yield \
self.doAccessControl(self.organizerPrincipal, True) </span><span class="cx"> 
</span><span class="cx">         # Check for a delete
</span><del>-        if self.deleting:
</del><ins>+        if self.action == &quot;remove&quot;:
</ins><span class="cx"> 
</span><del>-            log.debug(&quot;Implicit - organizer '%s' is deleting UID: \
'%s'&quot; % (self.organizer, self.uid)) </del><ins>+            \
log.debug(&quot;Implicit - organizer '%s' is removing UID: '%s'&quot; % \
(self.organizer, self.uid)) </ins><span class="cx">             self.oldcalendar = \
self.calendar </span><span class="cx"> 
</span><span class="cx">             # Cancel all attendees
</span><span class="cx">             self.cancelledAttendees = [(attendee, None) for \
attendee in self.attendees] </span><span class="cx"> 
</span><span class="cx">         # Check for a new resource or an update
</span><del>-        elif self.resource.exists():
</del><ins>+        elif self.action == &quot;modify&quot;:
</ins><span class="cx"> 
</span><span class="cx">             # Read in existing data
</span><span class="cx">             self.oldcalendar = self.resource.iCalendar()
</span><span class="lines">@@ -271,14 +432,15 @@
</span><span class="cx">             no_change, self.changed_rids = \
self.isChangeInsignificant() </span><span class="cx">             if no_change:
</span><span class="cx">                 # Nothing to do
</span><del>-                log.debug(&quot;Implicit - organizer '%s' is updating \
UID: '%s' but change is not significant&quot; % (self.organizer, self.uid)) \
</del><ins>+                log.debug(&quot;Implicit - organizer '%s' is modifying \
UID: '%s' but change is not significant&quot; % (self.organizer, self.uid)) \
</ins><span class="cx">                 returnValue(None) </span><span class="cx">    \
 </span><del>-            log.debug(&quot;Implicit - organizer '%s' is updating UID: \
'%s'&quot; % (self.organizer, self.uid)) </del><ins>+            \
log.debug(&quot;Implicit - organizer '%s' is modifying UID: '%s'&quot; % \
(self.organizer, self.uid)) </ins><span class="cx"> 
</span><span class="cx">             # Check for removed attendees
</span><span class="cx">             self.findRemovedAttendees()
</span><del>-        else:
</del><ins>+
+        elif self.action == &quot;create&quot;:
</ins><span class="cx">             log.debug(&quot;Implicit - organizer '%s' is \
creating UID: '%s'&quot; % (self.organizer, self.uid)) </span><span class="cx">       \
self.oldcalendar = None </span><span class="cx">             self.changed_rids = None
</span><span class="lines">@@ -377,7 +539,7 @@
</span><span class="cx">         yield self.processCancels()
</span><span class="cx">         
</span><span class="cx">         # Process regular requests next
</span><del>-        if not self.deleting:
</del><ins>+        if self.action in (&quot;create&quot;, &quot;modify&quot;,):
</ins><span class="cx">             yield self.processRequests()
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -402,7 +564,7 @@
</span><span class="cx">             
</span><span class="cx">             if None in rids:
</span><span class="cx">                 # One big CANCEL will do
</span><del>-                itipmsg = iTipGenerator.generateCancel(self.oldcalendar, \
(attendee,), None, self.deleting) </del><ins>+                itipmsg = \
iTipGenerator.generateCancel(self.oldcalendar, (attendee,), None, self.action == \
&quot;remove&quot;) </ins><span class="cx">             else:
</span><span class="cx">                 # Multiple CANCELs
</span><span class="cx">                 itipmsg = \
iTipGenerator.generateCancel(self.oldcalendar, (attendee,), rids) </span><span \
class="lines">@@ -470,9 +632,7 @@ </span><span class="cx">         if not \
self.internal_request: </span><span class="cx">             yield \
self.doAccessControl(self.attendeePrincipal, False) </span><span class="cx"> 
</span><del>-        if self.deleting:
-            #log.error(&quot;Attendee '%s' is not allowed to delete an organized \
                event: UID:%s&quot; % (self.attendeePrincipal, self.uid,))
-            #raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, \
(caldav_namespace, &quot;valid-attendee-change&quot;))) </del><ins>+        if \
self.action == &quot;remove&quot;: </ins><span class="cx">             \
log.debug(&quot;Implicit - attendee '%s' is cancelling UID: '%s'&quot; % \
(self.attendee, self.uid)) </span><span class="cx">             yield \
self.scheduleCancelWithOrganizer() </span><span class="cx">         
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavtesttest_collectioncontentspy"></a>
<div class="modfile"><h4>Modified: \
CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py (3294 => \
3295)</h4> <pre class="diff"><span>
<span class="info">--- \
CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py	2008-10-31 \
                02:20:00 UTC (rev 3294)
+++ CalendarServer/trunk/twistedcaldav/test/test_collectioncontents.py	2008-10-31 \
02:33:19 UTC (rev 3295) </span><span class="lines">@@ -13,8 +13,6 @@
</span><span class="cx"> # See the License for the specific language governing \
permissions and </span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><del>-from twistedcaldav.memcachelock import MemcacheLock
-from twistedcaldav.memcacher import Memcacher
</del><span class="cx"> 
</span><span class="cx"> import os
</span><span class="cx"> 
</span><span class="lines">@@ -26,6 +24,9 @@
</span><span class="cx"> from twisted.web2.test.test_server import SimpleRequest
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.ical import Component
</span><ins>+from twistedcaldav.memcachelock import MemcacheLock
+from twistedcaldav.memcacher import Memcacher
+from twistedcaldav.method.put_common import StoreCalendarObjectResource
</ins><span class="cx"> import twistedcaldav.test.util
</span><span class="cx"> 
</span><span class="cx"> class CollectionContents (twistedcaldav.test.util.TestCase):
</span><span class="lines">@@ -35,6 +36,8 @@
</span><span class="cx">     data_dir = os.path.join(os.path.dirname(__file__), \
&quot;data&quot;) </span><span class="cx"> 
</span><span class="cx">     def setUp(self):
</span><ins>+        
+        # Need to fake out memcache
</ins><span class="cx">         def _getFakeMemcacheProtocol(self):
</span><span class="cx">             
</span><span class="cx">             result = super(MemcacheLock, \
self)._getMemcacheProtocol() </span><span class="lines">@@ -45,6 +48,12 @@
</span><span class="cx">         
</span><span class="cx">         MemcacheLock._getMemcacheProtocol = \
_getFakeMemcacheProtocol </span><span class="cx"> 
</span><ins>+        # Need to not do implicit behavior during these tests
+        def _fakeDoImplicitScheduling(self):
+            return False, False
+        
+        StoreCalendarObjectResource.doImplicitScheduling = _fakeDoImplicitScheduling
+
</ins><span class="cx">         super(CollectionContents, self).setUp()
</span><span class="cx"> 
</span><span class="cx">     def test_collection_in_calendar(self):
</span></span></pre>
</div>
</div>

</body>
</html>



_______________________________________________
calendarserver-changes mailing list
calendarserver-changes@lists.macosforge.org
http://lists.macosforge.org/mailman/listinfo.cgi/calendarserver-changes


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic