[CalendarServer-changes] [1963] CalendarServer/branches/users/cdaboo/server2server-1941/ twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Tue Oct 16 12:54:36 PDT 2007


Revision: 1963
          http://trac.macosforge.org/projects/calendarserver/changeset/1963
Author:   cdaboo at apple.com
Date:     2007-10-16 12:54:36 -0700 (Tue, 16 Oct 2007)

Log Message:
-----------
Refactor itip processing into a class so it can be re-used and overridden for other specialized behaviors.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/itip.py
    CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/schedule_common.py

Modified: CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/itip.py	2007-10-15 20:39:54 UTC (rev 1962)
+++ CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/itip.py	2007-10-16 19:54:36 UTC (rev 1963)
@@ -54,955 +54,904 @@
 __version__ = "0.0"
 
 __all__ = [
-    "handleRequest",
-    "canAutoRespond",
+    "iTipProcessor",
 ]
 
 class iTipException(Exception):
     pass
 
-def handleRequest(request, principal, inbox, calendar, child):
-    """
-    Handle an iTIP response automatically using a deferredGenerator.
-
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
-    @return: L{Deferred} that is a L{deferredGenerator}
-    """
+class iTipProcessor(object):
     
-    method = calendar.propertyValue("METHOD")
-    if method == "REQUEST":
-        f = processRequest
-    elif method == "ADD":
-        f = processAdd
-    elif method == "CANCEL":
-        f = processCancel
+    def handleRequest(self, request, principal, inbox, calendar, child):
+        """
+        Handle an iTIP response automatically using a deferredGenerator.
+    
+        @param request: the L{twisted.web2.server.Request} for the current request.
+        @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
+        @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
+        @param calendar: the L{Component} for the iTIP message we are processing.
+        @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
+        @return: L{Deferred} that is a L{deferredGenerator}
+        """
+        
+        method = calendar.propertyValue("METHOD")
+        if method == "REQUEST":
+            f = self.processRequest
+        elif method == "ADD":
+            f = self.processAdd
+        elif method == "CANCEL":
+            f = self.processCancel
 
-    return f(request, principal, inbox, calendar, child)
+        self.request = request
+        self.principal = principal
+        self.inbox = inbox
+        self.calendar = calendar
+        self.child = child
+        if self.child:
+            self.childname = self.child.fp.basename()
+        else:
+            self.childname = ""
+ 
+        return f()
 
-def processRequest(request, principal, inbox, calendar, child):
-    """
-    Process a METHOD=REQUEST.
-    This is a deferredGenerator function so use yield whenever we have a deferred.
-
-    Steps:
+    @deferredGenerator
+    def processRequest(self):
+        """
+        Process a METHOD=REQUEST.
+        This is a deferredGenerator function so use yield whenever we have a deferred.
     
-      1. See if this updates existing ones in Inbox.
-          1. If so,
-              1. Remove existing ones in Inbox.
-              2. See if this updates existing ones in free-busy-set calendars.
-              3. Remove existing ones in those calendars.
-              4. See if this fits into a free slot:
-                  1. If not, send REPLY with failure status
-                  2. If so
-                      1. send REPLY with success
-                      2. add to f-b-s calendar
-          2. If not,
-              1. remove the one we got - its 'stale'
-          3. Delete the request from the Inbox.
+        Steps:
+        
+          1. See if this updates existing ones in Inbox.
+              1. If so,
+                  1. Remove existing ones in Inbox.
+                  2. See if this updates existing ones in free-busy-set calendars.
+                  3. Remove existing ones in those calendars.
+                  4. See if this fits into a free slot:
+                      1. If not, send REPLY with failure status
+                      2. If so
+                          1. send REPLY with success
+                          2. add to f-b-s calendar
+              2. If not,
+                  1. remove the one we got - its 'stale'
+              3. Delete the request from the Inbox.
+        
+        """
+        
+        logging.info("[ITIP]: Auto-processing iTIP REQUEST for: %s" % (str(self.principal),))
+        processed = "ignored"
     
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
-    """
+        # First determine whether this is a full or partial update. A full update is one containing the master
+        # component in a recurrence set (or non-recurring event). Partial is one where overridden instances only are
+        # being changed.
+        
+        new_master = self.calendar.masterComponent()
     
-    logging.info("[ITIP]: Auto-processing iTIP REQUEST for: %s" % (str(principal),))
-    processed = "ignored"
-
-    # First determine whether this is a full or partial update. A full update is one containing the master
-    # component in a recurrence set (or non-recurring event). Partial is one where overridden instances only are
-    # being changed.
-    
-    new_master = calendar.masterComponent()
-
-    # Next we want to try and find a match to any components on existing calendars listed as contributing
-    # to free-busy as we will need to update those with the new one.
-    d = waitForDeferred(findCalendarMatch(request, principal, calendar))
-    yield d
-    calmatch, updatecal, calURL = d.getResult()
-    
-    if new_master:
-        # So we have a full update. That means we need to delete any existing events completely and
-        # replace with the ones provided so long as the new one is newer.
+        # Next we want to try and find a match to any components on existing calendars listed as contributing
+        # to free-busy as we will need to update those with the new one.
+        d = waitForDeferred(self.findCalendarMatch())
+        yield d
+        calmatch, updatecal, calURL = d.getResult()
         
-        # If we have a match then we need to check whether we are updating etc
-        check_reply = False
-        if calmatch:
-            # See whether the new component is older than any existing ones and throw it away if so
-            newinfo = (None,) + getComponentSyncInfo(new_master)
-            cal = updatecal.iCalendar(calmatch)
-            info = getSyncInfo(calmatch, cal)
-            if compareSyncInfo(info, newinfo) < 0:
-                # Existing resource is older and will be replaced
-                check_reply = True
+        if new_master:
+            # So we have a full update. That means we need to delete any existing events completely and
+            # replace with the ones provided so long as the new one is newer.
+            
+            # If we have a match then we need to check whether we are updating etc
+            check_reply = False
+            if calmatch:
+                # See whether the new component is older than any existing ones and throw it away if so
+                newinfo = (None,) + self.getComponentSyncInfo(new_master)
+                cal = updatecal.iCalendar(calmatch)
+                info = self.getSyncInfo(calmatch, cal)
+                if self.compareSyncInfo(info, newinfo) < 0:
+                    # Existing resource is older and will be replaced
+                    check_reply = True
+                else:
+                    processed = "older"
             else:
-                processed = "older"
+                # We have a new request which we can reply to
+                check_reply = True
+                
+            if check_reply:
+                # Process the reply by determining PARTSTAT and sending the reply and booking the event.
+                d = waitForDeferred(self.checkForReply())
+                yield d
+                doreply, replycal, accepted = d.getResult()
+                
+                try:
+                    if accepted:
+                        if calmatch:
+                            newchild = waitForDeferred(self.writeResource(calURL, updatecal, calmatch, self.calendar))
+                            yield newchild
+                            newchild = newchild.getResult()
+                            logging.info("[ITIP]: replaced calendar component %s with new iTIP message in %s." % (calmatch, calURL))
+                        else:
+                            newchild = waitForDeferred(self.writeResource(calURL, updatecal, None, self.calendar))
+                            yield newchild
+                            newchild.getResult()
+                            logging.info("[ITIP]: added new calendar component in %s." % (calURL,))
+                    else:
+                        if calmatch:
+                            d = waitForDeferred(self.deleteResource(updatecal, calmatch))
+                            yield d
+                            d.getResult()
+                            logging.info("[ITIP]: deleted calendar component %s in %s as update was not accepted." % (calmatch, calURL))
+                            
+                    # Send a reply if needed. 
+                    if doreply:
+                        logging.info("[ITIP]: sending iTIP REPLY %s" % (("declined","accepted")[accepted],))
+                        newchild = waitForDeferred(self.writeReply(replycal))
+                        yield newchild
+                        newchild = newchild.getResult()
+                    processed = "processed"
+                except:
+                    log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
+                    raise iTipException
+                
         else:
-            # We have a new request which we can reply to
-            check_reply = True
-            
-        if check_reply:
-            # Process the reply by determining PARTSTAT and sending the reply and booking the event.
-            d = waitForDeferred(checkForReply(request, principal, calendar))
-            yield d
-            doreply, replycal, accepted = d.getResult()
-            
-            try:
-                if accepted:
+            # So we have a partial update. That means we have to do partial updates to instances in
+            # the existing calendar component.
+    
+            # If we have a match then we need to check whether we are updating etc
+            check_reply = False
+            if calmatch:
+                # Check each component to see whether its new
+                cal = updatecal.iCalendar(calmatch)
+                old_master = cal.masterComponent()
+                processed = "older"
+                new_components = [component for component in self.calendar.subcomponents()]
+                for component in new_components:
+                    if component.name() == "VTIMEZONE":
+                        continue
+                    
+                    newinfo = (None,) + self.getComponentSyncInfo(component)
+                    old_component = self.findMatchingComponent(component, cal)
+                    if old_component:
+                        info = (None,) + self.getComponentSyncInfo(old_component)
+                    elif old_master:
+                        info = (None,) + self.getComponentSyncInfo(old_master)
+                    else:
+                        info = None
+                        
+                    if info is None or self.compareSyncInfo(info, newinfo) < 0:
+                        # Existing resource is older and will be replaced
+                        check_reply = True
+                        processed = "processed"
+                    else:
+                        self.calendar.removeComponent(component)
+            else:
+                # We have a new request which we can reply to
+                check_reply = True
+    
+            if check_reply:
+                # Process the reply by determining PARTSTAT and sending the reply and booking the event.
+                d = waitForDeferred(self.checkForReply())
+                yield d
+                doreply, replycal, accepted = d.getResult()
+                
+                try:
                     if calmatch:
-                        newchild = waitForDeferred(writeResource(request, calURL, updatecal, calmatch, calendar))
+                        # Merge the new instances with the old ones
+                        self.mergeComponents(self.calendar, cal)
+                        newchild = waitForDeferred(self.writeResource(calURL, updatecal, calmatch, cal))
                         yield newchild
                         newchild = newchild.getResult()
-                        logging.info("[ITIP]: replaced calendar component %s with new iTIP message in %s." % (calmatch, calURL))
+                        logging.info("[ITIP]: merged calendar component %s with new iTIP message in %s." % (calmatch, calURL))
                     else:
-                        newchild = waitForDeferred(writeResource(request, calURL, updatecal, None, calendar))
+                        if accepted:
+                            newchild = waitForDeferred(self.writeResource(calURL, updatecal, None, self.calendar))
+                            yield newchild
+                            newchild.getResult()
+                            logging.info("[ITIP]: added new calendar component in %s." % (calURL,))
+                            
+                    # Do reply if needed. 
+                    if doreply:
+                        logging.info("[ITIP]: sending iTIP REPLY %s" % (("declined","accepted")[accepted],))
+                        newchild = waitForDeferred(self.writeReply(replycal))
                         yield newchild
-                        newchild.getResult()
-                        logging.info("[ITIP]: added new calendar component in %s." % (calURL,))
-                else:
-                    if calmatch:
-                        d = waitForDeferred(deleteResource(updatecal, calmatch))
-                        yield d
-                        d.getResult()
-                        logging.info("[ITIP]: deleted calendar component %s in %s as update was not accepted." % (calmatch, calURL))
+                        newchild = newchild.getResult()
                         
-                # Send a reply if needed. 
-                if doreply:
-                    logging.info("[ITIP]: sending iTIP REPLY %s" % (("declined","accepted")[accepted],))
-                    newchild = waitForDeferred(writeReply(request, principal, replycal, inbox))
-                    yield newchild
-                    newchild = newchild.getResult()
-                    newInboxResource(child, newchild)
-                processed = "processed"
-            except:
-                log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
-                raise iTipException
-            
-    else:
-        # So we have a partial update. That means we have to do partial updates to instances in
-        # the existing calendar component.
-
-        # If we have a match then we need to check whether we are updating etc
-        check_reply = False
-        if calmatch:
-            # Check each component to see whether its new
-            cal = updatecal.iCalendar(calmatch)
-            old_master = cal.masterComponent()
-            processed = "older"
-            new_components = [component for component in calendar.subcomponents()]
-            for component in new_components:
-                if component.name() == "VTIMEZONE":
-                    continue
-                
-                newinfo = (None,) + getComponentSyncInfo(component)
-                old_component = findMatchingComponent(component, cal)
-                if old_component:
-                    info = (None,) + getComponentSyncInfo(old_component)
-                elif old_master:
-                    info = (None,) + getComponentSyncInfo(old_master)
-                else:
-                    info = None
-                    
-                if info is None or compareSyncInfo(info, newinfo) < 0:
-                    # Existing resource is older and will be replaced
-                    check_reply = True
                     processed = "processed"
-                else:
-                    calendar.removeComponent(component)
-        else:
-            # We have a new request which we can reply to
-            check_reply = True
-
-        if check_reply:
-            # Process the reply by determining PARTSTAT and sending the reply and booking the event.
-            d = waitForDeferred(checkForReply(request, principal, calendar))
-            yield d
-            doreply, replycal, accepted = d.getResult()
-            
-            try:
-                if calmatch:
-                    # Merge the new instances with the old ones
-                    mergeComponents(calendar, cal)
-                    newchild = waitForDeferred(writeResource(request, calURL, updatecal, calmatch, cal))
-                    yield newchild
-                    newchild = newchild.getResult()
-                    logging.info("[ITIP]: merged calendar component %s with new iTIP message in %s." % (calmatch, calURL))
-                else:
-                    if accepted:
-                        newchild = waitForDeferred(writeResource(request, calURL, updatecal, None, calendar))
-                        yield newchild
-                        newchild.getResult()
-                        logging.info("[ITIP]: added new calendar component in %s." % (calURL,))
-                        
-                # Do reply if needed. 
-                if doreply:
-                    logging.info("[ITIP]: sending iTIP REPLY %s" % (("declined","accepted")[accepted],))
-                    newchild = waitForDeferred(writeReply(request, principal, replycal, inbox))
-                    yield newchild
-                    newchild = newchild.getResult()
-                    newInboxResource(child, newchild)
-                    
-                processed = "processed"
-            except:
-                log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
-                raise iTipException
-
-    # Remove the now processed incoming request.
-    try:
-        d = waitForDeferred(deleteResource(inbox, child.fp.basename()))
-        yield d
-        d.getResult()
-        logging.info("[ITIP]: deleted new iTIP message %s in Inbox because it has been %s." %
-            (child.fp.basename(),
-             {"processed":"processed",
-              "older":    "ignored: older",
-              "ignored":  "ignored: no match"}[processed],))
-    except:
-        log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
-        raise iTipException
-    yield None
-    return
-
-processRequest = deferredGenerator(processRequest)
-
-def processAdd(request, principal, inbox, calendar, child):
-    """
-    Process a METHOD=ADD.
-    This is a deferredGenerator function so use yield whenever we have a deferred.
-
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
-    """
-    logging.info("[ITIP]: Auto-processing iTIP ADD for: %s" % (str(principal),))
-
-    raise NotImplementedError
-
-processAdd = deferredGenerator(processAdd)
-
-def processCancel(request, principal, inbox, calendar, child):
-    """
-    Process a METHOD=CANCEL.
-    This is a deferredGenerator function so use yield whenever we have a deferred.
-
-    Policy find all components that match UID, SEQ and R-ID and remove them.
-
-    Steps:
+                except:
+                    log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
+                    raise iTipException
     
-      1. See if this updates existing ones in Inbox.
-      2. Remove existing ones in Inbox.
-      3. See if this updates existing ones in free-busy-set calendars.
-      4. Remove existing ones in those calendars.
-      5. Remove the incoming request.
+        # Remove the now processed incoming request.
+        if self.inbox:
+            self.deleteInboxResource({
+                "processed":"processed",
+                "older":    "ignored: older",
+                "ignored":  "ignored: no match"
+            }[processed])
 
-    NB Removal can be complex as we need to take RECURRENCE-ID into account - i.e a single
-    instance may be cancelled. What we need to do for this is:
+        yield None
+        return
     
-      1. If the R-ID of iTIP component matches the R-ID of one in Inbox then it is an exact match, so
-         delete the old one.
-      2. If the R-ID of iTIP does not match an R-ID in Inbox, then we are adding a cancellation as an override, so
-         leave the new and existing ones in the Inbox.
-      3. If the R-ID of iTIP component matches the R-ID of an overridden component in an f-b-s calendar, then
-         remove the overridden component from the f-b-s resource.
-      4. Add an EXDATE to the f-b-s resource to 'cancel' that instance.
+    @deferredGenerator
+    def processAdd(self):
+        """
+        Process a METHOD=ADD.
+        This is a deferredGenerator function so use yield whenever we have a deferred.
+        """
+        logging.info("[ITIP]: Auto-processing iTIP ADD for: %s" % (str(self.principal),))
     
-    TODO: Yes, I am going to ignore RANGE= on RECURRENCE-ID for now...
+        raise NotImplementedError
     
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param inbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @param child: the L{CalDAVFile} for the iTIP message resource already saved to the Inbox.
-    """
+    @deferredGenerator
+    def processCancel(self):
+        """
+        Process a METHOD=CANCEL.
+        This is a deferredGenerator function so use yield whenever we have a deferred.
     
-    logging.info("[ITIP]: Auto-processing iTIP CANCEL for: %s" % (str(principal),))
-    processed = "ignored"
-
-    # Get all component info for this iTIP message
-    newinfo = getSyncInfo(child.fp.basename(), calendar)
-    info = getAllInfo(inbox, calendar, child)
-
-    # First see if we have a recurrence id which will force extra work
-    has_rid = False
-    if newinfo[4] is not None:
-        has_rid = True
-    else:
-        for i in info:
-            if i[4] is not None:
-                has_rid = True
-                break
-            
-    if not has_rid:
-        # Compare the new one with each existing one.
-        d = waitForDeferred(processOthersInInbox(info, newinfo, inbox, child))
-        yield d
-        delete_child = d.getResult()
-
-        if delete_child:
-            yield None
-            return
-
+        Policy find all components that match UID, SEQ and R-ID and remove them.
+    
+        Steps:
+        
+            1. See if this updates existing ones in Inbox.
+            2. Remove existing ones in Inbox.
+            3. See if this updates existing ones in free-busy-set calendars.
+            4. Remove existing ones in those calendars.
+            5. Remove the incoming request.
+    
+        NB Removal can be complex as we need to take RECURRENCE-ID into account - i.e a single
+        instance may be cancelled. What we need to do for this is:
+        
+            1. If the R-ID of iTIP component matches the R-ID of one in Inbox then it is an exact match, so
+               delete the old one.
+            2. If the R-ID of iTIP does not match an R-ID in Inbox, then we are adding a cancellation as an override, so
+               leave the new and existing ones in the Inbox.
+            3. If the R-ID of iTIP component matches the R-ID of an overridden component in an f-b-s calendar, then
+               remove the overridden component from the f-b-s resource.
+            4. Add an EXDATE to the f-b-s resource to 'cancel' that instance.
+        
+        TODO: Yes, I am going to ignore RANGE= on RECURRENCE-ID for now...
+        """
+        
+        logging.info("[ITIP]: Auto-processing iTIP CANCEL for: %s" % (str(self.principal),))
+        processed = "ignored"
+    
+        # Get all component info for this iTIP message
+        newinfo = self.getSyncInfo(self.childname, self.calendar)
+    
+        # First see if we have a recurrence id which will force extra work
+        has_rid = False
+        if newinfo[4] is not None:
+            has_rid = True
+        else:
+            for i in self.getAllInfo(self.inbox, self.calendar, self.child):
+                if i[4] is not None:
+                    has_rid = True
+                    break
+                
         # Next we want to try and find a match to any components on existing calendars listed as contributing
         # to free-busy as we will need to update those with the new one.
-        d = waitForDeferred(findCalendarMatch(request, principal, calendar))
+        d = waitForDeferred(self.findCalendarMatch())
         yield d
         calmatch, updatecal, calURL = d.getResult()
         
-        # If we have a match then we need to check whether we are updating etc
-        if calmatch:
-            # See whether the current component is older than any existing ones and throw it away if so
-            cal = updatecal.iCalendar(calmatch)
-            info = getSyncInfo(calmatch, cal)
-            if compareSyncInfo(info, newinfo) < 0:
-                # Delete existing resource which has been cancelled
-                try:
-                    d = waitForDeferred(deleteResource(updatecal, calmatch,))
-                    yield d
-                    d.getResult()
-                    logging.info("[ITIP]: delete calendar component %s in %s as it was cancelled." % (calmatch, calURL))
-                except:
-                    log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
-                    raise iTipException
-                processed = "processed"
+        if not has_rid:
+            # If we have a match then we need to check whether we are updating etc
+            if calmatch:
+                # See whether the current component is older than any existing ones and throw it away if so
+                cal = updatecal.iCalendar(calmatch)
+                info = self.getSyncInfo(calmatch, cal)
+                if self.compareSyncInfo(info, newinfo) < 0:
+                    # Delete existing resource which has been cancelled
+                    try:
+                        d = waitForDeferred(self.deleteResource(updatecal, calmatch,))
+                        yield d
+                        d.getResult()
+                        logging.info("[ITIP]: delete calendar component %s in %s as it was cancelled." % (calmatch, calURL))
+                    except:
+                        log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
+                        raise iTipException
+                    processed = "processed"
+                else:
+                    processed = "older"
             else:
-                processed = "older"
+                # Nothing to do except delete the inbox item as we have nothing to cancel.
+                processed = "ignored"
         else:
-            # Nothing to do except delete the inbox item as we have nothing to cancel.
-            processed = "ignored"
-    else:
-        # Try and find a match to any components on existing calendars listed as contributing
-        # to free-busy as we will need to update those with the new one.
-        d = waitForDeferred(findCalendarMatch(request, principal, calendar))
-        yield d
-        calmatch, updatecal, calURL = d.getResult()
-        
-        # If we have a match then we need to check whether we are updating etc
-        if calmatch:
-            # iTIP CANCEL can contain multiple components being cancelled in the RECURRENCE-ID case.
-            # So we need to iterate over each iTIP component.
-
-            # Get the existing calendar object
-            existing_calendar = updatecal.iCalendar(calmatch)
-            existing_master = existing_calendar.masterComponent()
-            exdates = []
-
-            for component in calendar.subcomponents():
-                if component.name() == "VTIMEZONE":
-                    continue
-            
-                # Find matching component in existing calendar
-                old_component = findMatchingComponent(component, existing_calendar)
+            # If we have a match then we need to check whether we are updating etc
+            if calmatch:
+                # iTIP CANCEL can contain multiple components being cancelled in the RECURRENCE-ID case.
+                # So we need to iterate over each iTIP component.
+    
+                # Get the existing calendar object
+                existing_calendar = updatecal.iCalendar(calmatch)
+                existing_master = existing_calendar.masterComponent()
+                exdates = []
+    
+                for component in self.calendar.subcomponents():
+                    if component.name() == "VTIMEZONE":
+                        continue
                 
-                if old_component:
-                    # We are cancelling an overridden component, so we need to check the
-                    # SEQUENCE/DTSAMP with the master.
-                    if compareComponents(old_component, component) < 0:
-                        # Exclude the cancelled instance
-                        exdates.append(component.getRecurrenceIDUTC())
-                        
-                        # Remove the existing component.
-                        existing_calendar.removeComponent(old_component)
-                elif existing_master:
-                    # We are trying to CANCEL a non-overridden instance, so we need to
-                    # check SEQUENCE/DTSTAMP with the master.
-                    if compareComponents(existing_master, component) < 0:
-                        # Exclude the cancelled instance
-                        exdates.append(component.getRecurrenceIDUTC())
-
-            # If we have any EXDATEs lets add them to the existing calendar object and write
-            # it back.
-            if exdates:
-                if existing_master:
-                    existing_master.addProperty(Property("EXDATE", exdates))
-
-                # See if there are still components in the calendar - we might have deleted the last overridden instance
-                # in which case the calendar object is empty (except for VTIMEZONEs).
-                if existing_calendar.mainType() is None:
-                    # Delete the now empty calendar object
-                    d = waitForDeferred(deleteResource(updatecal, calmatch))
-                    yield d
-                    d.getResult()
-                    logging.info("[ITIP]: deleted calendar component %s after cancellations from iTIP message in %s." % (calmatch, calURL))
+                    # Find matching component in existing calendar
+                    old_component = self.findMatchingComponent(component, existing_calendar)
+                    
+                    if old_component:
+                        # We are cancelling an overridden component, so we need to check the
+                        # SEQUENCE/DTSAMP with the master.
+                        if self.compareComponents(old_component, component) < 0:
+                            # Exclude the cancelled instance
+                            exdates.append(component.getRecurrenceIDUTC())
+                            
+                            # Remove the existing component.
+                            existing_calendar.removeComponent(old_component)
+                    elif existing_master:
+                        # We are trying to CANCEL a non-overridden instance, so we need to
+                        # check SEQUENCE/DTSTAMP with the master.
+                        if self.compareComponents(existing_master, component) < 0:
+                            # Exclude the cancelled instance
+                            exdates.append(component.getRecurrenceIDUTC())
+    
+                # If we have any EXDATEs lets add them to the existing calendar object and write
+                # it back.
+                if exdates:
+                    if existing_master:
+                        existing_master.addProperty(Property("EXDATE", exdates))
+    
+                    # See if there are still components in the calendar - we might have deleted the last overridden instance
+                    # in which case the calendar object is empty (except for VTIMEZONEs).
+                    if existing_calendar.mainType() is None:
+                        # Delete the now empty calendar object
+                        d = waitForDeferred(self.deleteResource(updatecal, calmatch))
+                        yield d
+                        d.getResult()
+                        logging.info("[ITIP]: deleted calendar component %s after cancellations from iTIP message in %s." % (calmatch, calURL))
+                    else:
+                        # Update the existing calendar object
+                        newchild = waitForDeferred(self.writeResource(calURL, updatecal, calmatch, existing_calendar))
+                        yield newchild
+                        newchild = newchild.getResult()
+                        logging.info("[ITIP]: updated calendar component %s with cancellations from iTIP message in %s." % (calmatch, calURL))
+                    processed = "processed"
                 else:
-                    # Update the existing calendar object
-                    newchild = waitForDeferred(writeResource(request, calURL, updatecal, calmatch, existing_calendar))
-                    yield newchild
-                    newchild = newchild.getResult()
-                    logging.info("[ITIP]: updated calendar component %s with cancellations from iTIP message in %s." % (calmatch, calURL))
-                processed = "processed"
+                    processed = "older"
             else:
-                processed = "older"
-        else:
-            # Nothing to do except delete the inbox item as we have nothing to cancel.
-            processed = "ignored"
-
-    # Remove the now processed incoming request.
-    try:
-        d = waitForDeferred(deleteResource(inbox, child.fp.basename()))
-        yield d
-        d.getResult()
-        logging.info("[ITIP]: deleted new iTIP message %s in Inbox because it has been %s." %
-            (child.fp.basename(),
-             {"processed":"processed",
-              "older":    "ignored: older",
-              "ignored":  "ignored: no match"}[processed],))
-    except:
-        log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
-        raise iTipException
-    yield None
-    return
-
-processCancel = deferredGenerator(processCancel)
-
-def checkForReply(request, principal, calendar):
-    """
-    Check whether a reply to the given iTIP message is needed. A reply will be needed if the
-    RSVP=TRUE. A reply will either be positive (accepted
-    invitation) or negative (denied invitation). In addition we will modify calendar to reflect
-    any new state (e.g. remove RSVP, set PARTSTAT to ACCEPTED or DECLINED).
+                # Nothing to do except delete the inbox item as we have nothing to cancel.
+                processed = "ignored"
     
-    BTW The incoming iTIP message may contain multiple components so we need to iterate over all those.
-    At the moment we will treat a failure on one isntances as a DECLINE of the entire set.
+        # Remove the now processed incoming request.
+        if self.inbox:
+            self.deleteInboxResource({
+                "processed":"processed",
+                "older":    "ignored: older",
+                "ignored":  "ignored: no match"
+            }[processed])
 
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param calendar: the L{Component} for the iTIP message we are processing.
-    @return: C{True} if a reply is needed, C{False} otherwise.
-    """
+        yield None
+        return
     
-    # We need to fugure out whether the specified component will clash with any others in the f-b-set calendars
-    accepted = True
+    @deferredGenerator
+    def checkForReply(self):
+        """
+        Check whether a reply to the given iTIP message is needed. A reply will be needed if the
+        RSVP=TRUE. A reply will either be positive (accepted
+        invitation) or negative (denied invitation). In addition we will modify calendar to reflect
+        any new state (e.g. remove RSVP, set PARTSTAT to ACCEPTED or DECLINED).
         
-    # First expand current one to get instances (only go 1 year into the future)
-    default_future_expansion_duration = datetime.timedelta(days=356*1)
-    expand_max = datetime.date.today() + default_future_expansion_duration
-    instances = calendar.expandTimeRanges(expand_max)
-    
-    # Extract UID from primary component as we want to ignore this one if we match it
-    # in any calendars.
-    comp = calendar.mainComponent(allow_multiple=True)
-    uid = comp.propertyValue("UID")
+        BTW The incoming iTIP message may contain multiple components so we need to iterate over all those.
+        At the moment we will treat a failure on one isntances as a DECLINE of the entire set.
 
-    # Now compare each instance time-range with the index and see if there is an overlap
-    fbset = waitForDeferred(principal.calendarFreeBusyURIs(request))
-    yield fbset
-    fbset = fbset.getResult()
-
-    for calURL in fbset:
-        testcal = waitForDeferred(request.locateResource(calURL))
-        yield testcal
-        testcal = testcal.getResult()
+        @return: C{True} if a reply is needed, C{False} otherwise.
+        """
         
-        # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
-        fbinfo = ([], [], [])
+        # We need to fugure out whether the specified component will clash with any others in the f-b-set calendars
+        accepted = True
+            
+        # First expand current one to get instances (only go 1 year into the future)
+        default_future_expansion_duration = datetime.timedelta(days=356*1)
+        expand_max = datetime.date.today() + default_future_expansion_duration
+        instances = self.calendar.expandTimeRanges(expand_max)
         
-        # Now do search for overlapping time-range
-        for instance in instances.instances.itervalues():
-            try:
-                tr = caldavxml.TimeRange(start="20000101", end="20000101")
-                tr.start = instance.start
-                tr.end = instance.end
-                d = waitForDeferred(report_common.generateFreeBusyInfo(request, testcal, fbinfo, tr, 0, uid))
-                yield d
-                d.getResult()
-                
-                # If any fbinfo entries exist we have an overlap
-                if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
+        # Extract UID from primary component as we want to ignore this one if we match it
+        # in any calendars.
+        comp = self.calendar.mainComponent(allow_multiple=True)
+        uid = comp.propertyValue("UID")
+    
+        # Now compare each instance time-range with the index and see if there is an overlap
+        fbset = waitForDeferred(self.principal.calendarFreeBusyURIs(self.request))
+        yield fbset
+        fbset = fbset.getResult()
+    
+        for calURL in fbset:
+            testcal = waitForDeferred(self.request.locateResource(calURL))
+            yield testcal
+            testcal = testcal.getResult()
+            
+            # First list is BUSY, second BUSY-TENTATIVE, third BUSY-UNAVAILABLE
+            fbinfo = ([], [], [])
+            
+            # Now do search for overlapping time-range
+            for instance in instances.instances.itervalues():
+                try:
+                    tr = caldavxml.TimeRange(start="20000101", end="20000101")
+                    tr.start = instance.start
+                    tr.end = instance.end
+                    d = waitForDeferred(report_common.generateFreeBusyInfo(self.request, testcal, fbinfo, tr, 0, uid))
+                    yield d
+                    d.getResult()
+                    
+                    # If any fbinfo entries exist we have an overlap
+                    if len(fbinfo[0]) or len(fbinfo[1]) or len(fbinfo[2]):
+                        accepted = False
+                        break
+                except NumberOfMatchesWithinLimits:
                     accepted = False
+                    logging.info("[ITIP]: exceeded number of matches whilst trying to find free-time.")
                     break
-            except NumberOfMatchesWithinLimits:
-                accepted = False
-                logging.info("[ITIP]: exceeded number of matches whilst trying to find free-time.")
+                
+            if not accepted:
                 break
-            
-        if not accepted:
-            break
-     
-    # Extract the ATTENDEE property matching current recipient from the calendar data
-    cuas = principal.calendarUserAddresses()
-    attendeeProps = calendar.getAttendeeProperties(cuas)
-    if not attendeeProps:
-        yield False, None, accepted
-        return
-
-    # Look for specific parameters
-    rsvp = False
-    for attendeeProp in attendeeProps:
-        if "RSVP" in attendeeProp.params():
-            if attendeeProp.params()["RSVP"][0] == "TRUE":
-                rsvp = True
+         
+        # Extract the ATTENDEE property matching current recipient from the calendar data
+        cuas = self.principal.calendarUserAddresses()
+        attendeeProps = self.calendar.getAttendeeProperties(cuas)
+        if not attendeeProps:
+            yield False, None, accepted
+            return
     
-            # Now modify the original component
-            del attendeeProp.params()["RSVP"]
-
-    if accepted:
-        partstat = "ACCEPTED"
-    else:
-        partstat = "DECLINED"
-    for attendeeProp in attendeeProps:
-        if "PARTSTAT" in attendeeProp.params():
-            attendeeProp.params()["PARTSTAT"][0] = partstat
-        else:
-            attendeeProp.params()["PARTSTAT"] = [partstat]
+        # Look for specific parameters
+        rsvp = False
+        for attendeeProp in attendeeProps:
+            if "RSVP" in attendeeProp.params():
+                if attendeeProp.params()["RSVP"][0] == "TRUE":
+                    rsvp = True
+        
+                # Now modify the original component
+                del attendeeProp.params()["RSVP"]
     
-    # Now create a new calendar object for the reply
-    
-    # First get useful props from the original
-    replycal = calendar.duplicate()
-    
-    # Change METHOD
-    replycal.getProperty("METHOD").setValue("REPLY")
-    
-    # Change PRODID to this server
-    replycal.getProperty("PRODID").setValue(iCalendarProductID)
-    
-    # Add REQUEST-STATUS
-    for component in replycal.subcomponents():
         if accepted:
-            component.addProperty(Property(name="REQUEST-STATUS", value="2.0; Success."))
+            partstat = "ACCEPTED"
         else:
-            component.addProperty(Property(name="REQUEST-STATUS", value="4.0; Event conflict. Date/time is busy."))
-
-    # Remove all attendees other than ourselves
-    for component in replycal.subcomponents():
-        if component.name() == "VTIMEZONE":
-            continue
-        attendeeProp = component.getAttendeeProperty(cuas)
-        attendees = tuple(component.properties("ATTENDEE"))
-        for attendee in attendees:
-            if attendeeProp is None or (attendee.value() != attendeeProp.value()):
-                replycal.mainComponent().removeProperty(attendee)
-
-    yield rsvp, replycal, accepted
-
-checkForReply = deferredGenerator(checkForReply)
-
-def writeReply(request, principal, replycal, ainbox):
-    """
-    Write an iTIP message reply into the specified Inbox.
+            partstat = "DECLINED"
+        for attendeeProp in attendeeProps:
+            if "PARTSTAT" in attendeeProp.params():
+                attendeeProp.params()["PARTSTAT"][0] = partstat
+            else:
+                attendeeProp.params()["PARTSTAT"] = [partstat]
+        
+        # Now create a new calendar object for the reply
+        
+        # First get useful props from the original
+        replycal = self.calendar.duplicate()
+        
+        # Change METHOD
+        replycal.getProperty("METHOD").setValue("REPLY")
+        
+        # Change PRODID to this server
+        replycal.getProperty("PRODID").setValue(iCalendarProductID)
+        
+        # Add REQUEST-STATUS
+        for component in replycal.subcomponents():
+            if accepted:
+                component.addProperty(Property(name="REQUEST-STATUS", value="2.0; Success."))
+            else:
+                component.addProperty(Property(name="REQUEST-STATUS", value="4.0; Event conflict. Date/time is busy."))
     
-    @param request: the L{twisted.web2.server.Request} for the current request.
-    @param principal: the L{CalendarPrincipalFile} principal resource for the principal we are dealing with.
-    @param replycal: the L{Component} for the iTIP message reply.
-    @param ainbox: the L{ScheduleInboxFile} for the principal's Inbox.
-    """
+        # Remove all attendees other than ourselves
+        for component in replycal.subcomponents():
+            if component.name() == "VTIMEZONE":
+                continue
+            attendeeProp = component.getAttendeeProperty(cuas)
+            attendees = tuple(component.properties("ATTENDEE"))
+            for attendee in attendees:
+                if attendeeProp is None or (attendee.value() != attendeeProp.value()):
+                    replycal.mainComponent().removeProperty(attendee)
     
-    # Get the Inbox of the ORGANIZER
-    organizer = replycal.getOrganizer()
-    assert organizer is not None
-    inboxURL = ainbox.principalForCalendarUserAddress(organizer).scheduleInboxURL()
-    assert inboxURL
+        yield rsvp, replycal, accepted
     
-    # Determine whether current principal has CALDAV:schedule right on that Inbox
-    inbox = waitForDeferred(request.locateResource(inboxURL))
-    yield inbox
-    inbox = inbox.getResult()
+    @deferredGenerator
+    def writeReply(self, replycal):
+        """
+        Write an iTIP message reply into the specified Inbox.
 
-    try:
-        d = waitForDeferred(inbox.checkPrivileges(request, (caldavxml.Schedule(),), principal=davxml.Principal(davxml.HRef.fromString(principal.principalURL()))))
-        yield d
-        d.getResult()
-    except AccessDeniedError:
-        logging.info("[ITIP]: could not send reply as %s does not have CALDAV:schedule permission on %s Inbox." % (principal.principalURL(), organizer))
-        yield None
-        return
+        @param replycal: the L{Component} for the iTIP message reply.
+        """
+        
+        # Get the Inbox of the ORGANIZER
+        organizer = replycal.getOrganizer()
+        assert organizer is not None
+        inboxURL = self.inbox.principalForCalendarUserAddress(organizer).scheduleInboxURL()
+        assert inboxURL
+        
+        # Determine whether current principal has CALDAV:schedule right on that Inbox
+        writeinbox = waitForDeferred(self.request.locateResource(inboxURL))
+        yield writeinbox
+        writeinbox = writeinbox.getResult()
     
-    # Now deposit the new calendar into the inbox
-    d = waitForDeferred(writeResource(request, inboxURL, inbox, None, replycal))
-    yield d
-    yield d.getResult()
+        try:
+            d = waitForDeferred(writeinbox.checkPrivileges(self.request, (caldavxml.Schedule(),), principal=davxml.Principal(davxml.HRef.fromString(self.principal.principalURL()))))
+            yield d
+            d.getResult()
+        except AccessDeniedError:
+            logging.info("[ITIP]: could not send reply as %s does not have CALDAV:schedule permission on %s Inbox." % (self.principal.principalURL(), organizer))
+            yield None
+            return
+        
+        # Now deposit the new calendar into the inbox
+        newchild = waitForDeferred(self.writeResource(inboxURL, writeinbox, None, replycal))
+        yield newchild
+        newchild = newchild.getResult()
 
-writeReply = deferredGenerator(writeReply)
-
-def writeResource(request, collURL, collection, name, calendar):
-    """
-    Write out the calendar resource (iTIP) message to the specified calendar, either over-writing the named
-    resource or by creating a new one.
-    
-    @param request: the L{IRequest} for the current request.
-    @param collURL: the C{str} containing the URL of the calendar collection.
-    @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
-    @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
-    @param calendar: the L{Component} calendar to write.
-    @return: C{tuple} of L{Deferred}, L{CalDAVFile}
-    """
-    
-    # Create a new name if one was not provided
-    if name is None:
-        name =  md5.new(str(calendar) + str(time.time()) + collection.fp.path).hexdigest() + ".ics"
-
-    # Get a resource for the new item
-    newchildURL = joinURL(collURL, name)
-    newchild = waitForDeferred(request.locateResource(newchildURL))
-    yield newchild
-    newchild = newchild.getResult()
-    
-    # Modify the original calendar data by removing the METHOD property - everything else is left as-is,
-    # as any other needed changes (e.g. RSVP/PARTSTAT) will have been updated.
-    # NB Only do this when writing to something other than an Inbox or Outbox
-    itipper = True
-    if collection.isCalendarCollection():
-        method = calendar.getProperty("METHOD")
-        if method:
-            calendar.removeProperty(method)
-        itipper = False
-    
-    # Now write it to the resource
-    try:
-        d = waitForDeferred(storeCalendarObjectResource(
-                request=request,
-                sourcecal = False,
-                destination = newchild,
-                destination_uri = newchildURL,
-                calendardata = str(calendar),
-                destinationparent = collection,
-                destinationcal = True,
-                isiTIP = itipper
-            ))
-        yield d
-        d.getResult()
-    except:
-        yield None
+        self.newInboxResource(self.child, newchild)
+        
+        yield newchild
         return
     
-    yield newchild
-
-writeResource = deferredGenerator(writeResource)    
-
-def newInboxResource(child, newchild):
-    """
-    Copy recipient and organizer properties from one iTIP resource, to another,
-    switching them as appropriate for a reply, and also set the state.
+    @deferredGenerator
+    def writeResource(self, collURL, collection, name, calendar):
+        """
+        Write out the calendar resource (iTIP) message to the specified calendar, either over-writing the named
+        resource or by creating a new one.
+        
+        @param collURL: the C{str} containing the URL of the calendar collection.
+        @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
+        @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
+        @param calendar: the L{Component} calendar to write.
+        @return: C{tuple} of L{Deferred}, L{CalDAVFile}
+        """
+        
+        # Create a new name if one was not provided
+        if name is None:
+            name =  md5.new(str(calendar) + str(time.time()) + collection.fp.path).hexdigest() + ".ics"
     
-    @param child: the L{CalDAVFile} for the original iTIP message.
-    @param newchild: the L{CalDAVFile} for the iTIP message reply.
-    """
-    # Make previous Recipient the new Originator
-    if child.hasDeadProperty(caldavxml.Recipient):
-        recip = child.readDeadProperty(caldavxml.Recipient)
-        if recip.children:
-            # Store CALDAV:originator property
-            newchild.writeDeadProperty(caldavxml.Originator(davxml.HRef.fromString(str(recip.children[0]))))
+        # Get a resource for the new item
+        newchildURL = joinURL(collURL, name)
+        newchild = waitForDeferred(self.request.locateResource(newchildURL))
+        yield newchild
+        newchild = newchild.getResult()
+        
+        # Modify the original calendar data by removing the METHOD property - everything else is left as-is,
+        # as any other needed changes (e.g. RSVP/PARTSTAT) will have been updated.
+        # NB Only do this when writing to something other than an Inbox or Outbox
+        itipper = True
+        if collection.isCalendarCollection():
+            method = calendar.getProperty("METHOD")
+            if method:
+                calendar.removeProperty(method)
+            itipper = False
+        
+        # Now write it to the resource
+        try:
+            d = waitForDeferred(storeCalendarObjectResource(
+                    request=self.request,
+                    sourcecal = False,
+                    destination = newchild,
+                    destination_uri = newchildURL,
+                    calendardata = str(calendar),
+                    destinationparent = collection,
+                    destinationcal = True,
+                    isiTIP = itipper
+                ))
+            yield d
+            d.getResult()
+        except:
+            yield None
+            return
+        
+        yield newchild
     
-    # Make previous Originator the new Recipient
-    if child.hasDeadProperty(caldavxml.Originator):
-        orig = child.readDeadProperty(caldavxml.Originator)
-        if orig.children:
-            # Store CALDAV:originator property
-            newchild.writeDeadProperty(caldavxml.Recipient(davxml.HRef.fromString(str(orig.children[0]))))
-  
-def deleteResource(collection, name):
-    """
-    Delete the calendar resource in the specified calendar.
-    
-    @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
-    @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
-    @return: L{Deferred}
-    """
-    
-    delchild = collection.getChild(name)
-    index = collection.index()
-    index.deleteResource(delchild.fp.basename())
-    
-    def _deletedResourced(result):
-        # Change CTag on the parent calendar collection
-        collection.updateCTag()
+    def newInboxResource(self, child, newchild):
+        """
+        Copy recipient and organizer properties from one iTIP resource, to another,
+        switching them as appropriate for a reply, and also set the state.
         
-        return result
-
-    d = maybeDeferred(delete, "", delchild.fp, "0")
-    d.addCallback(_deletedResourced)
-    return d
-
-def canAutoRespond(calendar):
-    """
-    Check whether the METHOD of this iTIP calendar object is one we can process. Also,
-    we will only handle VEVENTs right now.
-
-    @param calendar: L{Component} for calendar to examine.
-    @return: C{True} if we can auto-respond, C{False} if not.
-    """
-
-    try:
-        method = calendar.propertyValue("METHOD")
-        if method not in ("REQUEST", "ADD", "CANCEL"):
-            return False
-        if calendar.mainType() not in ("VEVENT"):
-            return False
-    except ValueError:
-        return False
+        @param child: the L{CalDAVFile} for the original iTIP message.
+        @param newchild: the L{CalDAVFile} for the iTIP message reply.
+        """
+        # Make previous Recipient the new Originator
+        if child.hasDeadProperty(caldavxml.Recipient):
+            recip = child.readDeadProperty(caldavxml.Recipient)
+            if recip.children:
+                # Store CALDAV:originator property
+                newchild.writeDeadProperty(caldavxml.Originator(davxml.HRef.fromString(str(recip.children[0]))))
+        
+        # Make previous Originator the new Recipient
+        if child.hasDeadProperty(caldavxml.Originator):
+            orig = child.readDeadProperty(caldavxml.Originator)
+            if orig.children:
+                # Store CALDAV:originator property
+                newchild.writeDeadProperty(caldavxml.Recipient(davxml.HRef.fromString(str(orig.children[0]))))
     
-    return True
-
-def processOthersInInbox(info, newinfo, inbox, child):
-    # Compare the new one with each existing one.
-    delete_child = False
-    for i in info:
-        # For any that are older, delete them.
-        if compareSyncInfo(i, newinfo) < 0:
-            try:
-                d = waitForDeferred(deleteResource(inbox, i[0]))
-                yield d
-                d.getResult()
-                logging.info("[ITIP]: deleted iTIP message %s in Inbox that was older than the new one." % (i[0],))
-            except:
-                log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
-                raise iTipException
-        else:
-            # For any that are newer or the same, mark the new one to be deleted.
-            delete_child = True
-
-    # Delete the new one if so marked.
-    if delete_child:
+    @deferredGenerator
+    def deleteInboxResource(self, processed_state):
+        # Remove the now processed incoming request.
         try:
-            d = waitForDeferred(deleteResource(inbox, child.fp.basename()))
+            d = waitForDeferred(self.deleteResource(self.inbox, self.childname))
             yield d
             d.getResult()
-            logging.info("[ITIP]: deleted new iTIP message %s in Inbox because it was older than existing ones." % (child.fp.basename(),))
+            logging.info("[ITIP]: deleted new iTIP message %s in Inbox because it has been %s." %
+                (self.childname, processed_state,))
         except:
             log.err("Error while auto-processing iTIP: %s" % (failure.Failure(),))
             raise iTipException
-    
-    yield delete_child
 
-processOthersInInbox = deferredGenerator(processOthersInInbox)    
-
-def findCalendarMatch(request, principal, calendar):
-    # Try and find a match to any components on existing calendars listed as contributing
-    # to free-busy as we will need to update those with the new one.
+    def deleteResource(self, collection, name):
+        """
+        Delete the calendar resource in the specified calendar.
+        
+        @param collection: the L{CalDAVFile} for the calendar collection to store the resource in.
+        @param name: the C{str} for the resource name to write into, or {None} to write a new resource.
+        @return: L{Deferred}
+        """
+        
+        delchild = collection.getChild(name)
+        index = collection.index()
+        index.deleteResource(delchild.fp.basename())
+        
+        def _deletedResourced(result):
+            # Change CTag on the parent calendar collection
+            collection.updateCTag()
+            
+            return result
     
-    # Find the current recipients calendar-free-busy-set
-    fbset = waitForDeferred(principal.calendarFreeBusyURIs(request))
-    yield fbset
-    fbset = fbset.getResult()
-
-    # Find the first calendar in the list with a component matching the one we are processing
-    calmatch = None
-    updatecal = None
-    calURL = None
-    for calURL in fbset:
-        updatecal = waitForDeferred(request.locateResource(calURL))
-        yield updatecal
-        updatecal = updatecal.getResult()
-        if updatecal is None or not updatecal.exists() or not isCalendarCollectionResource(updatecal):
-            # We will ignore missing calendars. If the recipient has failed to
-            # properly manage the free busy set that should not prevent us from working.
-            continue
-        calmatch = matchComponentInCalendar(updatecal, calendar)
-        if calmatch:
-            logging.info("[ITIP]: found calendar component %s matching new iTIP message in %s." % (calmatch, calURL))
-            break
+        d = maybeDeferred(delete, "", delchild.fp, "0")
+        d.addCallback(_deletedResourced)
+        return d
     
-    if calmatch is None and len(fbset):
-        calURL = fbset[0]
-        updatecal = waitForDeferred(request.locateResource(calURL))
-        yield updatecal
-        updatecal = updatecal.getResult()
-
-    yield calmatch, updatecal, calURL
-
-findCalendarMatch = deferredGenerator(findCalendarMatch)    
-
-def matchComponentInCalendar(collection, calendar):
-    """
-    See if the component in the provided iTIP calendar object matches any in the specified calendar
-    collection.
+    @staticmethod
+    def canAutoRespond(calendar):
+        """
+        Check whether the METHOD of this iTIP calendar object is one we can process. Also,
+        we will only handle VEVENTs right now.
     
-    @param collection: L{CalDAVFile} for the calendar collection to examine.
-    @param calendar: L{Component} for calendar to examine.
-    @return: C{list} of resource names found.
-    """
-
-    try:
-        # Extract UID from primary component (note we allow multiple components to be present
-        # because CANCEL requests can have multiple components).
-        comp = calendar.mainComponent(allow_multiple=True)
-        uid = comp.propertyValue("UID")
+        @param calendar: L{Component} for calendar to examine.
+        @return: C{True} if we can auto-respond, C{False} if not.
+        """
+    
+        try:
+            method = calendar.propertyValue("METHOD")
+            if method not in ("REQUEST", "ADD", "CANCEL"):
+                return False
+            if calendar.mainType() not in ("VEVENT"):
+                return False
+        except ValueError:
+            return False
         
-        # Now use calendar collection index to find all other resources with the same UID
-        index = collection.index()
-        result = index.resourceNamesForUID(uid)
+        return True
+    
+    @deferredGenerator
+    def findCalendarMatch(self):
+        # Try and find a match to any components on existing calendars listed as contributing
+        # to free-busy as we will need to update those with the new one.
         
-        # There can be only one
-        if len(result) > 0: 
-            return result[0]
-        else:
-            return None
-    except ValueError:
-        return None
-
-def findMatchingComponent(component, calendar):
-    """
-    See if any overridden component in the provided iTIP calendar object matches the specified component.
+        # Find the current recipients calendar-free-busy-set
+        calendars = waitForDeferred(self.getCalendarsToMatch())
+        yield calendars
+        calendars = calendars.getResult()
     
-    @param component: the component to try and match.
-    @type component: L{Component}
-    @param calendar: the calendar to find a match in.
-    @type calendar: L{Component}
-    @return: L{Component} for matching component,
-        or C{None} if not found.
-    """
-
-    # Extract RECURRENCE-ID value from component
-    rid = component.getRecurrenceIDUTC()
+        # Find the first calendar in the list with a component matching the one we are processing
+        calmatch = None
+        updatecal = None
+        calURL = None
+        for calURL in calendars:
+            updatecal = waitForDeferred(self.request.locateResource(calURL))
+            yield updatecal
+            updatecal = updatecal.getResult()
+            if updatecal is None or not updatecal.exists() or not isCalendarCollectionResource(updatecal):
+                # We will ignore missing calendars. If the recipient has failed to
+                # properly manage the free busy set that should not prevent us from working.
+                continue
+            calmatch = self.matchComponentInCalendar(updatecal, self.calendar)
+            if calmatch:
+                logging.info("[ITIP]: found calendar component %s matching new iTIP message in %s." % (calmatch, calURL))
+                break
+        
+        if calmatch is None and len(calendars):
+            calURL = calendars[0]
+            updatecal = waitForDeferred(self.request.locateResource(calURL))
+            yield updatecal
+            updatecal = updatecal.getResult()
     
-    # Return the one that matches in the calendar
-    return calendar.overriddenComponent(rid)
-
-def mergeComponents(newcal, oldcal):
-    """
-    Merge the overridden instance components in newcal into old cal replacing any
-    matching components there.
-
-    @param newcal: the new overridden instances to use.
-    @type newcal: L{Component}
-    @param oldcal: the component to merge into.
-    @type oldcal: L{Component}
-    """
+        yield calmatch, updatecal, calURL
     
-    # FIXME: going to ignore VTIMEZONE - i.e. will assume that the component being added
-    # use a TZID that is already specified in the old component set.
-
-    # We will update the SEQUENCE on the master to the highest value of the current one on the master
-    # or the ones in the components we are changing.
-
-    for component in newcal.subcomponents():
-        if component.name() == "VTIMEZONE":
-            continue
+    @deferredGenerator
+    def getCalendarsToMatch(self):
+        # Determine the set of calendar URIs for a principal need to be searched.
         
-        rid = component.getRecurrenceIDUTC()
-        old_component = oldcal.overriddenComponent(rid)
-        if old_component:
-            oldcal.removeComponent(old_component)
-        oldcal.addComponent(component)
+        # Find the current recipients calendar-free-busy-set
+        fbset = waitForDeferred(self.principal.calendarFreeBusyURIs(self.request))
+        yield fbset
+        fbset = fbset.getResult()
+    
+        yield fbset
 
-def getAllInfo(collection, calendar, ignore):
-    """
-    Find each component in the calendar collection that has a matching UID with
-    the supplied component, and get useful synchronization details from it, ignoring
-    the one with the supplied resource name.
-
-    @param collection: the L{CalDAVFile} for the calendar collection.
-    @param calendar: the L{Component} for the component being compared with.
-    @param ignore: the C{str} containing the name of a resource to ignore,
-        or C{None} if none to ignore.
-    @return: C{list} of synchronization information for each resource found.
-    """
-    names = []
-    try:
-        # Extract UID from primary component (note we allow multiple components to be present
-        # because CANCEL requests can have multiple components).
-        comp = calendar.mainComponent(allow_multiple=True)
-        uid = comp.propertyValue("UID")
+    def matchComponentInCalendar(self, collection, calendar):
+        """
+        See if the component in the provided iTIP calendar object matches any in the specified calendar
+        collection.
         
-        # Now use calendar collection index to find all other resources with the same UID
-        index = collection.index()
-        names = index.resourceNamesForUID(uid)
+        @param collection: L{CalDAVFile} for the calendar collection to examine.
+        @param calendar: L{Component} for calendar to examine.
+        @return: C{list} of resource names found.
+        """
+    
+        try:
+            # Extract UID from primary component (note we allow multiple components to be present
+            # because CANCEL requests can have multiple components).
+            comp = calendar.mainComponent(allow_multiple=True)
+            uid = comp.propertyValue("UID")
+            
+            # Now use calendar collection index to find all other resources with the same UID
+            index = collection.index()
+            result = index.resourceNamesForUID(uid)
+            
+            # There can be only one
+            if len(result) > 0: 
+                return result[0]
+            else:
+                return None
+        except ValueError:
+            return None
+    
+    def findMatchingComponent(self, component, calendar):
+        """
+        See if any overridden component in the provided iTIP calendar object matches the specified component.
         
-        # Remove the one we want to ignore
-        if ignore is not None:
-            names = [name for name in names if name != ignore.fp.basename()]
-    except ValueError:
-        return []
+        @param component: the component to try and match.
+        @type component: L{Component}
+        @param calendar: the calendar to find a match in.
+        @type calendar: L{Component}
+        @return: L{Component} for matching component,
+            or C{None} if not found.
+        """
     
-    # Now get info for each name
-    result = []
-    for name in names:
-        cal = collection.iCalendar(name)
-        result.append(getSyncInfo(name, cal))
-
-    return result
+        # Extract RECURRENCE-ID value from component
+        rid = component.getRecurrenceIDUTC()
+        
+        # Return the one that matches in the calendar
+        return calendar.overriddenComponent(rid)
     
-def getSyncInfo(name, calendar):
-    """
-    Get property value details needed to synchronize iTIP components.
+    def mergeComponents(self, newcal, oldcal):
+        """
+        Merge the overridden instance components in newcal into old cal replacing any
+        matching components there.
     
-    @param calendar: L{Component} for calendar to check.
-    @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
-    """
-    try:
-        # Extract components from primary component (note we allow multiple components to be present
-        # because CANCEL requests can have multiple components).
-        comp = calendar.mainComponent(allow_multiple=True)
-        uid, seq, dtstamp, rid = getComponentSyncInfo(comp)
+        @param newcal: the new overridden instances to use.
+        @type newcal: L{Component}
+        @param oldcal: the component to merge into.
+        @type oldcal: L{Component}
+        """
         
-    except ValueError:
-        return (name, None, None, None, None)
+        # FIXME: going to ignore VTIMEZONE - i.e. will assume that the component being added
+        # use a TZID that is already specified in the old component set.
     
-    return (name, uid, seq, dtstamp, rid)
-
-def getComponentSyncInfo(component):
-    """
-    Get property value details needed to synchronize iTIP components.
+        # We will update the SEQUENCE on the master to the highest value of the current one on the master
+        # or the ones in the components we are changing.
     
-    @param component: L{Component} to check.
-    @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
-    """
-    try:
-        # Extract items from component
-        uid = component.propertyValue("UID")
-        seq = component.propertyValue("SEQUENCE")
-        dtstamp = component.propertyValue("DTSTAMP")
-        rid = component.propertyValue("RECURRENCE-ID")
+        for component in newcal.subcomponents():
+            if component.name() == "VTIMEZONE":
+                continue
+            
+            rid = component.getRecurrenceIDUTC()
+            old_component = oldcal.overriddenComponent(rid)
+            if old_component:
+                oldcal.removeComponent(old_component)
+            oldcal.addComponent(component)
+    
+    def getAllInfo(self, collection, calendar, ignore):
+        """
+        Find each component in the calendar collection that has a matching UID with
+        the supplied component, and get useful synchronization details from it, ignoring
+        the one with the supplied resource name.
+    
+        @param collection: the L{CalDAVFile} for the calendar collection.
+        @param calendar: the L{Component} for the component being compared with.
+        @param ignore: the C{str} containing the name of a resource to ignore,
+            or C{None} if none to ignore.
+        @return: C{list} of synchronization information for each resource found.
+        """
+        names = []
+        try:
+            # Extract UID from primary component (note we allow multiple components to be present
+            # because CANCEL requests can have multiple components).
+            comp = calendar.mainComponent(allow_multiple=True)
+            uid = comp.propertyValue("UID")
+            
+            # Now use calendar collection index to find all other resources with the same UID
+            index = collection.index()
+            names = index.resourceNamesForUID(uid)
+            
+            # Remove the one we want to ignore
+            if ignore is not None:
+                names = [name for name in names if name != ignore.fp.basename()]
+        except ValueError:
+            return []
         
-    except ValueError:
-        return (None, None, None, None)
+        # Now get info for each name
+        result = []
+        for name in names:
+            cal = collection.iCalendar(name)
+            result.append(self.getSyncInfo(name, cal))
     
-    return (uid, seq, dtstamp, rid)
-
-def compareComponents(component1, component2):
-    """
-    Compare synchronization information for two components to see if they match according to iTIP.
-
-    @param component1: first component to check.
-    @type component1: L{Component}
-    @param component2: second component to check.
-    @type component2: L{Component}
+        return result
+        
+    def getSyncInfo(self, name, calendar):
+        """
+        Get property value details needed to synchronize iTIP components.
+        
+        @param calendar: L{Component} for calendar to check.
+        @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
+        """
+        try:
+            # Extract components from primary component (note we allow multiple components to be present
+            # because CANCEL requests can have multiple components).
+            comp = calendar.mainComponent(allow_multiple=True)
+            uid, seq, dtstamp, rid = self.getComponentSyncInfo(comp)
+            
+        except ValueError:
+            return (name, None, None, None, None)
+        
+        return (name, uid, seq, dtstamp, rid)
     
-    @return: 0, 1, -1 as per compareSyncInfo.
-    """
-    info1 = (None,) + getComponentSyncInfo(component1)
-    info2 = (None,) + getComponentSyncInfo(component2)
-    return compareSyncInfo(info1, info2)
-
-def compareSyncInfo(info1, info2):
-    """
-    Compare two synchronization information records.
+    def getComponentSyncInfo(self, component):
+        """
+        Get property value details needed to synchronize iTIP components.
+        
+        @param component: L{Component} to check.
+        @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
+        """
+        try:
+            # Extract items from component
+            uid = component.propertyValue("UID")
+            seq = component.propertyValue("SEQUENCE")
+            dtstamp = component.propertyValue("DTSTAMP")
+            rid = component.propertyValue("RECURRENCE-ID")
+            
+        except ValueError:
+            return (None, None, None, None)
+        
+        return (uid, seq, dtstamp, rid)
     
-    @param info1: a C{tuple} as returned by L{getSyncInfo}.
-    @param info2: a C{tuple} as returned by L{getSyncInfo}.
-    @return: 1 if info1 > info2, 0 if info1 == info2, -1 if info1 < info2
-    """
-    # UIDs MUST match
-    assert info1[1] == info2[1]
+    def compareComponents(self, component1, component2):
+        """
+        Compare synchronization information for two components to see if they match according to iTIP.
     
-    # Look for sequence
-    if (info1[2] is not None) and (info2[2] is not None):
-        if info1[2] > info2[2]:
+        @param component1: first component to check.
+        @type component1: L{Component}
+        @param component2: second component to check.
+        @type component2: L{Component}
+        
+        @return: 0, 1, -1 as per compareSyncInfo.
+        """
+        info1 = (None,) + self.getComponentSyncInfo(component1)
+        info2 = (None,) + self.getComponentSyncInfo(component2)
+        return self.compareSyncInfo(info1, info2)
+    
+    def compareSyncInfo(self, info1, info2):
+        """
+        Compare two synchronization information records.
+        
+        @param info1: a C{tuple} as returned by L{getSyncInfo}.
+        @param info2: a C{tuple} as returned by L{getSyncInfo}.
+        @return: 1 if info1 > info2, 0 if info1 == info2, -1 if info1 < info2
+        """
+        # UIDs MUST match
+        assert info1[1] == info2[1]
+        
+        # Look for sequence
+        if (info1[2] is not None) and (info2[2] is not None):
+            if info1[2] > info2[2]:
+                return 1
+            if info1[2] < info2[2]:
+                return -1
+        elif (info1[2] is not None) and (info2[2] is None):
             return 1
-        if info1[2] < info2[2]:
+        elif (info1[2] is None) and (info2[2] is not None):
             return -1
-    elif (info1[2] is not None) and (info2[2] is None):
-        return 1
-    elif (info1[2] is None) and (info2[2] is not None):
-        return -1
-
-    # Look for DTSTAMP
-    if (info1[3] is not None) and (info2[3] is not None):
-        if info1[3] > info2[3]:
+    
+        # Look for DTSTAMP
+        if (info1[3] is not None) and (info2[3] is not None):
+            if info1[3] > info2[3]:
+                return 1
+            if info1[3] < info2[3]:
+                return -1
+        elif (info1[3] is not None) and (info2[3] is None):
             return 1
-        if info1[3] < info2[3]:
+        elif (info1[3] is None) and (info2[3] is not None):
             return -1
-    elif (info1[3] is not None) and (info2[3] is None):
-        return 1
-    elif (info1[3] is None) and (info2[3] is not None):
-        return -1
-
-    return 0
+    
+        return 0

Modified: CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/schedule_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/schedule_common.py	2007-10-15 20:39:54 UTC (rev 1962)
+++ CalendarServer/branches/users/cdaboo/server2server-1941/twistedcaldav/schedule_common.py	2007-10-16 19:54:36 UTC (rev 1963)
@@ -40,11 +40,11 @@
 from twisted.web2.dav.util import joinURL
 
 from twistedcaldav import caldavxml
-from twistedcaldav import itip
 from twistedcaldav.caldavxml import caldav_namespace, TimeRange
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.ical import Component
+from twistedcaldav.itip import iTipProcessor
 from twistedcaldav.method import report_common
 from twistedcaldav.method.put_common import storeCalendarObjectResource
 from twistedcaldav.resource import isCalendarCollectionResource
@@ -360,12 +360,13 @@
         # Now we have to do auto-respond
         if len(autoresponses) != 0:
             # First check that we have a method that we can auto-respond to
-            if not itip.canAutoRespond(self.calendar):
+            if not iTipProcessor.canAutoRespond(self.calendar):
                 autoresponses = []
             
         # Now do the actual auto response
         for principal, inbox, child in autoresponses:
             # Add delayed reactor task to handle iTIP responses
+            itip = iTipProcessor()
             reactor.callLater(0.0, itip.handleRequest, *(self.request, principal, inbox, self.calendar.duplicate(), child)) #@UndefinedVariable
     
         # Return with final response if we are done

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20071016/9dc29f45/attachment-0001.html


More information about the calendarserver-changes mailing list