[CalendarServer-changes] [2779] CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Thu Aug 7 07:24:31 PDT 2008


Revision: 2779
          http://trac.macosforge.org/projects/calendarserver/changeset/2779
Author:   cdaboo at apple.com
Date:     2008-08-07 07:24:30 -0700 (Thu, 07 Aug 2008)
Log Message:
-----------
Handle implicit scheduling deletes. This is not incomplete - need a transaction based model to ensure
both the file delete and iTIP scheduling succeed before the delete is actually done.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/method/delete.py
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/test/test_icaldiff.py

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/method/delete.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/method/delete.py	2008-08-06 22:15:42 UTC (rev 2778)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/method/delete.py	2008-08-07 14:24:30 UTC (rev 2779)
@@ -20,35 +20,42 @@
 
 __all__ = ["http_DELETE"]
 
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.web2 import responsecode
 from twisted.web2.dav.util import parentForURL
 
 from twistedcaldav.resource import isPseudoCalendarCollectionResource
+from twistedcaldav.scheduling.implicit import ImplicitScheduler
 
+ at inlineCallbacks
 def http_DELETE(self, request):
     #
     # Override base DELETE request handling to ensure that the calendar
     # index file has the entry for the deleted calendar component removed.
     #
-    def gotParent(parent):
-        def gotResponse(response):
-            if response == responsecode.NO_CONTENT:
-                if isPseudoCalendarCollectionResource(parent):
-                    index = parent.index()
-                    index.deleteResource(self.fp.basename())
 
-                    # Change CTag on the parent calendar collection
-                    d = parent.updateCTag()
-                    d.addCallback(lambda _: response)
-                    return d
+    # 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.
 
-            return response
+    parentURL = parentForURL(request.uri)
+    parent = (yield request.locateResource(parentURL))
 
-        d = super(CalDAVFile, self).http_DELETE(request)
-        d.addCallback(gotResponse)
-        return d
+    if isPseudoCalendarCollectionResource(parent) and self.exists():
+        calendar = self.iCalendar()
 
-    parentURL = parentForURL(request.uri)
-    d = request.locateResource(parentURL)
-    d.addCallback(gotParent)
-    return d
+    response = (yield super(CalDAVFile, self).http_DELETE(request))
+
+    if response == responsecode.NO_CONTENT:
+        if isPseudoCalendarCollectionResource(parent):
+
+            index = parent.index()
+            index.deleteResource(self.fp.basename())
+
+            # Change CTag on the parent calendar collection
+            yield parent.updateCTag()
+
+            # Do scheduling
+            scheduler = ImplicitScheduler()
+            yield scheduler.doImplicitScheduling(request, self, calendar, True)
+
+    returnValue(response)

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/method/put_common.py	2008-08-06 22:15:42 UTC (rev 2778)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/method/put_common.py	2008-08-07 14:24:30 UTC (rev 2779)
@@ -679,7 +679,7 @@
             # Do scheduling
             if not self.isiTIP:
                 scheduler = ImplicitScheduler()
-                self.calendar = (yield scheduler.doImplicitScheduling(self.request, self.destination, self.calendar))
+                self.calendar = (yield scheduler.doImplicitScheduling(self.request, self.destination, self.calendar, False))
 
             # Initialize the rollback system
             self.setupRollback()

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/implicit.py	2008-08-06 22:15:42 UTC (rev 2778)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/implicit.py	2008-08-07 14:24:30 UTC (rev 2779)
@@ -14,7 +14,7 @@
 # limitations under the License.
 ##
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.web2 import responsecode
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.http import HTTPError
@@ -22,6 +22,8 @@
 from twistedcaldav.itip import iTipGenerator
 from twistedcaldav.log import Logger
 from twistedcaldav.scheduling.scheduler import CalDAVScheduler
+from twistedcaldav.method import report_common
+from twistedcaldav.scheduling.icaldiff import iCalDiff
 
 __all__ = [
     "ImplicitScheduler",
@@ -29,13 +31,19 @@
 
 log = Logger()
 
+# TODO:
+#
+# Handle the case where a PUT removes the ORGANIZER property. That should be equivalent to cancelling the entire meeting.
+#
+# Figure out proper behavior for what happens when an attendee deletes their copy of an event. For now disallow.
+
 class ImplicitScheduler(object):
     
     def __init__(self):
         pass
 
     @inlineCallbacks
-    def doImplicitScheduling(self, request, resource, calendar):
+    def doImplicitScheduling(self, request, resource, calendar, deleting):
         """
         Do implicit scheduling operation based on the calendar data that is being PUT
 
@@ -43,9 +51,11 @@
         @type request:
         @param resource:
         @type resource:
-        @param calendar:
-        @type calendar:
-        
+        @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
+
         @return: a new calendar object modified with scheduling information
         """
         
@@ -53,8 +63,12 @@
         self.resource = resource
         self.calendar = calendar
         self.calendar_owner = (yield self.resource.owner(self.request))
+        self.deleting = deleting
 
-        
+        # When deleting we MUST have the calendar as the actual resource
+        # will have been deleted by now
+        assert deleting and calendar or not deleting
+
         # Get some useful information from the calendar
         self.extractCalendarData()
 
@@ -75,6 +89,7 @@
             if self.organizer:
                 if organizer != self.organizer:
                     # We have different ORGANIZERs in the same iCalendar object - this is an error
+                    log.error("Only one ORGANIZER is allowed in an iCalendar object:\n%s" % (self.calendar,))
                     raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "single-organizer")))
             else:
                 self.organizer = organizer
@@ -84,6 +99,9 @@
         self.attendees = set()
         for attendee, _ignore in self.attendeesByInstance:
             self.attendees.add(attendee)
+            
+        # Some other useful things
+        self.uid = self.calendar.resourceUID()
     
     def isOrganizerScheduling(self):
         """
@@ -124,29 +142,42 @@
     @inlineCallbacks
     def doImplicitOrganizer(self):
         
+        # Check for a delete
+        if self.deleting:
+
+            log.debug("Implicit - organizer '%s' is deleting 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
-        if self.resource.exists():
-            
+        elif self.resource.exists():
+
             # Read in existing data
             self.oldcalendar = self.resource.iCalendar()
             
             # Significant change
-            if not self.isChangeSignificant():
+            if self.isChangeInsignificant():
                 # Nothing to do
+                log.debug("Implicit - organizer '%s' is updating UID: '%s' but change is not significant" % (self.organizer, self.uid))
                 return
             
+            log.debug("Implicit - organizer '%s' is updating UID: '%s'" % (self.organizer, self.uid))
+
             # Check for removed attendees
             self.findRemovedAttendees()
         else:
+            log.debug("Implicit - organizer '%s' is creating UID: '%s'" % (self.organizer, self.uid))
             self.oldcalendar = None
             self.cancelledAttendees = ()   
             
         yield self.scheduleWithAttendees()
 
-    def isChangeSignificant(self):
+    def isChangeInsignificant(self):
         
-        # TODO: diff two calendars and see what happened. For now treat any change as significant.
-        return True
+        differ = iCalDiff(self.oldcalendar, self.calendar)
+        return differ.organizerDiff()
     
     def findRemovedAttendees(self):
         """
@@ -168,7 +199,8 @@
         yield self.processCancels()
         
         # Process regular requests next
-        yield self.processRequests()
+        if not self.deleting:
+            yield self.processRequests()
 
     @inlineCallbacks
     def processCancels(self):
@@ -203,11 +235,10 @@
             scheduler = CalDAVScheduler(self.request, self.resource)
     
             # Do the PUT processing
+            log.info("Implicit CANCEL - organizer: '%s' to attendee: '%s', UID: '%s', RIDs: '%s'" % (self.organizer, attendee, self.uid, rids))
             response = (yield scheduler.doSchedulingViaPUT(self.organizer, (attendee,), itipmsg))
+            self.handleSchedulingResponse(response, True)
             
-            # TODO: need to figure out how to process the response for a CANCEL
-            returnValue(response)
-            
     @inlineCallbacks
     def processRequests(self):
         
@@ -229,23 +260,35 @@
             scheduler = CalDAVScheduler(self.request, self.resource)
     
             # Do the PUT processing
+            log.info("Implicit REQUEST - organizer: '%s' to attendee: '%s', UID: '%s'" % (self.organizer, attendee, self.uid,))
             response = (yield scheduler.doSchedulingViaPUT(self.organizer, (attendee,), itipmsg))
-            
-            # TODO: need to figure out how to process the response for a REQUEST
-            returnValue(response)
-            
+            self.handleSchedulingResponse(response, True)
+
+    def handleSchedulingResponse(self, response, is_organizer):
+        
+        # TODO: need to figure out how to process the response
+        pass
+
     @inlineCallbacks
     def doImplicitAttendee(self):
 
+        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")))
+
         # Get the ORGANIZER's current copy of the calendar object
-        self.orgcalendar = self.getOrganizersCopy()
+        yield self.getOrganizersCopy()
+        assert self.organizer_calendar, "Must have the organizer's copy of an invite"
         
         # Determine whether the current change is allowed
-        if not self.isAttendeeChangeSignificant():
+        if self.isAttendeeChangeInsignificant():
+            log.debug("Implicit - attendee '%s' is updating UID: '%s' but change is not significant" % (self.attendee, self.uid))
             return
             
+        log.debug("Implicit - attendee '%s' is updating UID: '%s'" % (self.attendee, self.uid))
         yield self.scheduleWithOrganizer()
 
+    @inlineCallbacks
     def getOrganizersCopy(self):
         """
         Get the Organizer's copy of the event being processed.
@@ -255,20 +298,44 @@
         the attendee does the right thing about changing the details in the event.
         """
         
-        # TODO: extract UID and ORGANIZER, find match in ORGANIZER's calendars.
+        self.organizer_calendar = None
+        if self.organizerPrincipal:
+            # Get Organizer's calendar-home
+            calendar_home = self.organizerPrincipal.calendarHome()
+            
+            # 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
 
-        return None
+            def queryCalendarCollection(collection, uri):
+                rname = collection.index().resourceNameForUID(self.uid)
+                if rname:
+                    self.organizer_calendar = collection.iCalendar(rname)
+                    return succeed(False)
+                else:
+                    return succeed(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)
     
-    def isAttendeeChangeSignificant(self):
+    def isAttendeeChangeInsignificant(self):
         """
         Check whether the change is significant (PARTSTAT) or allowed
         (attendee can only change their property, alarms, TRANSP, and
         instances. Raise an exception if it is not allowed.
         """
         
-        # TODO: all of the above.
-        return True
+        differ = iCalDiff(self.organizer_calendar, self.calendar)
+        change_allowed, no_itip = differ.attendeeMerge(self.attendee)
+        if not change_allowed:
+            log.error("Attendee '%s' is not allowed to make an unauthorized change to an organized event: UID:%s" % (self.attendeePrincipal, self.uid,))
+            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-attendee-change")))
 
+        return no_itip
+
     @inlineCallbacks
     def scheduleWithOrganizer(self):
 
@@ -280,7 +347,6 @@
         scheduler = CalDAVScheduler(self.request, self.resource)
 
         # Do the PUT processing
+        log.info("Implicit REPLY - attendee: '%s' to organizer: '%s', UID: '%s'" % (self.attendee, self.organizer, self.uid,))
         response = (yield scheduler.doSchedulingViaPUT(self.attendee, (self.organizer,), itipmsg))
-        
-        # TODO: need to figure out how to process the response for a REQUEST
-        returnValue(response)
+        self.handleSchedulingResponse(response, False)

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/test/test_icaldiff.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/test/test_icaldiff.py	2008-08-06 22:15:42 UTC (rev 2778)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/test/test_icaldiff.py	2008-08-07 14:24:30 UTC (rev 2779)
@@ -15,7 +15,7 @@
 ##
 from twistedcaldav.scheduling.icaldiff import iCalDiff
 
-from twistedcaldav.ical import Property, Component
+from twistedcaldav.ical import Component
 import twistedcaldav.test.util
 
 class ICalDiff (twistedcaldav.test.util.TestCase):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080807/ce7a17e5/attachment-0001.html 


More information about the calendarserver-changes mailing list