[CalendarServer-changes] [2869] CalendarServer/branches/users/cdaboo/implicit-2799

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 25 21:00:57 PDT 2008


Revision: 2869
          http://trac.macosforge.org/projects/calendarserver/changeset/2869
Author:   cdaboo at apple.com
Date:     2008-08-25 21:00:56 -0700 (Mon, 25 Aug 2008)
Log Message:
-----------
Trying to do a better job at merging.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/implicit-2799/conf/caldavd-test.plist
    CalendarServer/branches/users/cdaboo/implicit-2799/conf/caldavd.plist
    CalendarServer/branches/users/cdaboo/implicit-2799/support/patchmaker
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/config.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/directory/calendar.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/ical.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/itip.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/method/delete.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/resource.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/schedule.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/caldav.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/scheduler.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/test/test_icalendar.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/implicit-2799/.project
    CalendarServer/branches/users/cdaboo/implicit-2799/.pydevproject
    CalendarServer/branches/users/cdaboo/implicit-2799/lib-patches/vobject/src.vobject.base.patch
    CalendarServer/branches/users/cdaboo/implicit-2799/twisted/plugins/dropin.cache
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/itip.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/processing.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/test/test_implicit.py
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/test/test_itip.py

Removed Paths:
-------------
    CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/test/test_itip.py

Property Changed:
----------------
    CalendarServer/branches/users/cdaboo/implicit-2799/


Property changes on: CalendarServer/branches/users/cdaboo/implicit-2799
___________________________________________________________________
Name: svn:ignore
   - *.tgz
data
logs
build

   + *.tgz
data
logs
build
_run
telnets
tools


Added: CalendarServer/branches/users/cdaboo/implicit-2799/.project
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/.project	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/.project	2008-08-26 04:00:56 UTC (rev 2869)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>CalendarServer</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.python.pydev.PyDevBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.python.pydev.pythonNature</nature>
+	</natures>
+</projectDescription>

Added: CalendarServer/branches/users/cdaboo/implicit-2799/.pydevproject
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/.pydevproject	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/.pydevproject	2008-08-26 04:00:56 UTC (rev 2869)
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse-pydev version="1.0"?>
+
+<pydev_project>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
+<path>/CalendarServer</path>
+</pydev_pathproperty>
+<pydev_pathproperty name="org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH">
+<path>/Users/cyrusdaboo/Documents/Development/Apple/eclipse/Twisted</path>
+<path>/Users/cyrusdaboo/Documents/Development/Apple/eclipse/vobject/src</path>
+<path>/Users/cyrusdaboo/Documents/Development/Apple/eclipse/pydirector-1.0.0/build/lib.macosx-10.3-fat-2.5</path>
+<path>/Users/cyrusdaboo/Documents/Development/Apple/eclipse/pyOpenSSL-0.6/build/lib.macosx-10.3-fat-2.5</path>
+<path>/Users/cyrusdaboo/Documents/Development/Apple/eclipse/PyXML-0.8.4/build/lib.macosx-10.3-fat-2.5</path>
+<path>/Volumes/Data/Users/cyrusdaboo/Documents/Development/Apple/eclipse/PyOpenDirectory/build/lib.macosx-10.5-i386-2.5</path>
+<path>/Volumes/Data/Users/cyrusdaboo/Documents/Development/Apple/eclipse/PyKerberos/build/lib.macosx-10.5-i386-2.5</path>
+</pydev_pathproperty>
+</pydev_project>

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/conf/caldavd-test.plist	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/conf/caldavd-test.plist	2008-08-26 04:00:56 UTC (rev 2869)
@@ -394,6 +394,8 @@
   	  </array>
   	  <key>OldDraftCompatability</key>
   	  <true/>
+  	  <key>DefaultCalendarProvisioned</key>
+  	  <true/>
   	</dict>
     <!--  iSchedule protocol options -->
   	<key>iSchedule</key>

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/conf/caldavd.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/conf/caldavd.plist	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/conf/caldavd.plist	2008-08-26 04:00:56 UTC (rev 2869)
@@ -363,6 +363,8 @@
   	  </array>
   	  <key>OldDraftCompatability</key>
   	  <true/>
+  	  <key>DefaultCalendarProvisioned</key>
+  	  <true/>
   	</dict>
     <!--  iSchedule protocol options -->
   	<key>iSchedule</key>

Copied: CalendarServer/branches/users/cdaboo/implicit-2799/lib-patches/vobject/src.vobject.base.patch (from rev 2868, CalendarServer/branches/users/cdaboo/implicit-2805/lib-patches/vobject/src.vobject.base.patch)
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/lib-patches/vobject/src.vobject.base.patch	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/lib-patches/vobject/src.vobject.base.patch	2008-08-26 04:00:56 UTC (rev 2869)
@@ -0,0 +1,13 @@
+Index: src/vobject/base.py
+===================================================================
+--- src/vobject/base.py	(revision 192)
++++ src/vobject/base.py	(working copy)
+@@ -273,6 +273,8 @@
+         self.value = copy.copy(copyit.value)
+         self.encoded = self.encoded
+         self.params = copy.copy(copyit.params)
++        for k,v in self.params.items():
++            self.params[k] = copy.copy(v)
+         self.singletonparams = copy.copy(copyit.singletonparams)
+         self.lineNumber = copyit.lineNumber
+         

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/support/patchmaker
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/support/patchmaker	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/support/patchmaker	2008-08-26 04:00:56 UTC (rev 2869)
@@ -26,11 +26,11 @@
 #
 
 #projects = ("Twisted", "vobject", "dateutil", "xattr")
-projects = ("Twisted", "vobject",)
+projects = ("Twisted", vobject", )
 cwd = os.getcwd()
 libpatches = os.path.join(cwd, "lib-patches")
 
-svn = "/usr/bin/svn"
+svn = "/usr/local/bin/svn"
 
 # Stuff we have to manually ignore because our ignore logic cannot cope
 ignores = set((

Added: CalendarServer/branches/users/cdaboo/implicit-2799/twisted/plugins/dropin.cache
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twisted/plugins/dropin.cache	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twisted/plugins/dropin.cache	2008-08-26 04:00:56 UTC (rev 2869)
@@ -0,0 +1,95 @@
+(dp1
+S'caldav'
+p2
+ccopy_reg
+_reconstructor
+p3
+(ctwisted.plugin
+CachedDropin
+p4
+c__builtin__
+object
+p5
+NtRp6
+(dp7
+S'moduleName'
+p8
+S'twisted.plugins.caldav'
+p9
+sS'description'
+p10
+NsS'plugins'
+p11
+(lp12
+g3
+(ctwisted.plugin
+CachedPlugin
+p13
+g5
+NtRp14
+(dp15
+S'provided'
+p16
+(lp17
+ctwisted.plugin
+IPlugin
+p18
+actwisted.application.service
+IServiceMaker
+p19
+asS'dropin'
+p20
+g6
+sS'name'
+p21
+S'CalDAVNotifier'
+p22
+sg10
+Nsbag3
+(g13
+g5
+NtRp23
+(dp24
+g16
+(lp25
+g18
+ag19
+asg20
+g6
+sg21
+S'TwistedCalDAV'
+p26
+sg10
+NsbasbsS'kqueuereactor'
+p27
+g3
+(g4
+g5
+NtRp28
+(dp29
+g8
+S'twisted.plugins.kqueuereactor'
+p30
+sg10
+Nsg11
+(lp31
+g3
+(g13
+g5
+NtRp32
+(dp33
+g16
+(lp34
+g18
+actwisted.application.reactors
+IReactorInstaller
+p35
+asg20
+g28
+sg21
+S'caldav_kqueue'
+p36
+sg10
+S'\n    @ivar moduleName: The fully-qualified Python name of the module of which\n    the install callable is an attribute.\n    '
+p37
+sbasbs.
\ No newline at end of file

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/config.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/config.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -183,10 +183,11 @@
     "Scheduling": {
         
         "CalDAV": {
-            "EmailDomain"           : "",    # Domain for mailto calendar user addresses on this server
-            "HTTPDomain"            : "",    # Domain for http calendar user addresses on this server
-            "AddressPatterns"       : [],    # Reg-ex patterns to match local calendar user addresses
-            "OldDraftCompatability" : True   # Whether to maintain compatibility with non-implicit mode
+            "EmailDomain"                : "",    # Domain for mailto calendar user addresses on this server
+            "HTTPDomain"                 : "",    # Domain for http calendar user addresses on this server
+            "AddressPatterns"            : [],    # Reg-ex patterns to match local calendar user addresses
+            "OldDraftCompatability"      : True,  # Whether to maintain compatibility with non-implicit mode
+            "DefaultCalendarProvisioned" : True, # Whether the provisioned default calendar is marked as the scheduling default
         },
 
         "iSchedule": {

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/directory/calendar.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/directory/calendar.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -298,8 +298,12 @@
             # Set calendar-free-busy-set on inbox
             inbox = self.getChild("inbox")
             inbox.provision()
-            inbox.writeDeadProperty(caldavxml.CalendarFreeBusySet(davxml.HRef(childURL)))
+            inbox.processFreeBusyCalendar(childURL, True)
 
+            # Default calendar may need to be marked as the default for scheduling
+            if config.Scheduling["CalDAV"]["DefaultCalendarProvisioned"]:
+                inbox.writeDeadProperty(caldavxml.ScheduleDefaultCalendarURL(davxml.HRef(childURL)))
+
             return self
 
         d = child.createCalendarCollection()

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/ical.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/ical.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -34,6 +34,7 @@
 from twistedcaldav.dateops import compareDateTime, normalizeToUTC, timeRangesOverlap
 from twistedcaldav.instance import InstanceList
 from twistedcaldav.log import Logger
+from types import ListType
 from vobject import newFromBehavior, readComponents
 from vobject.base import Component as vComponent, ContentLine as vContentLine, ParseError as vParseError
 from vobject.icalendar import TimezoneComponent, dateTimeToString, deltaToOffset, getTransition, stringToDate, stringToDateTime, stringToDurations, utc
@@ -83,7 +84,11 @@
     def __str__ (self): return self._vobject.serialize()
     def __repr__(self): return "<%s: %r: %r>" % (self.__class__.__name__, self.name(), self.value())
 
-    def __hash__(self): return hash((self.name(), self.value()))
+    def __hash__(self):
+        if type(self.value()) is ListType:
+            return hash((self.name(), tuple(self.value())))
+        else:
+            return hash((self.name(), self.value()))
 
     def __ne__(self, other): return not self.__eq__(other)
     def __eq__(self, other):
@@ -667,6 +672,22 @@
         
         return result
     
+    def timezones(self):
+        """
+        Returns the set of TZID's for each VTIMEZONE component.
+
+        @return: a set of strings, one for each unique TZID value.
+        """
+        
+        assert self.name() == "VCALENDAR", "Not a calendar: %r" % (self,)
+
+        results = set()
+        for component in self.subcomponents():
+            if component.name() == "VTIMEZONE":
+                results.add(component.propertyValue("TZID"))
+        
+        return results
+    
     def expand(self, start, end):
         """
         Expand the components into a set of new components, one for each
@@ -763,6 +784,24 @@
         instances.expandTimeRanges(componentSet, limit)
         return instances
 
+    def getComponentInstances(self):
+        """
+        Get the R-ID value for each component.
+        
+        @return: a tuple of recurrence-ids
+        """
+        
+        # Extract appropriate sub-component if this is a VCALENDAR
+        if self.name() == "VCALENDAR":
+            result = ()
+            for component in self.subcomponents():
+                if component.name() != "VTIMEZONE":
+                    result += component.getComponentInstances()
+            return result
+        else:
+            rid = self.getRecurrenceIDUTC()
+            return (rid,)
+
     def deriveInstance(self, rid):
         """
         Derive an instance from the master component that has the provided RECURRENCE-ID, but
@@ -1187,6 +1226,40 @@
 
         return None
 
+    def setParameterToValueForPropertyWithValue(self, paramname, paramvalue, propname, propvalue):
+        """
+        Add or change the parameter to the specified value on the property having the specified value.
+        
+        @param paramname: the parameter name
+        @type paramname: C{str}
+        @param paramvalue: the parameter value to set
+        @type paramvalue: C{str}
+        @param propname: the property name
+        @type propname: C{str}
+        @param propvalue: the property value to test
+        @type propvalue: C{str}
+        """
+        
+        for component in self.subcomponents():
+            if component.name() == "VTIMEZONE":
+                continue
+            for property in component.properties(propname):
+                if property.value() == propvalue:
+                    property.params()[paramname] = [paramvalue]
+    
+    def addPropertyToAllComponents(self, property):
+        """
+        Add a property to all top-level components except VTIMEZONE.
+
+        @param property: the property to add
+        @type property: L{Property}
+        """
+        
+        for component in self.subcomponents():
+            if component.name() == "VTIMEZONE":
+                continue
+            component.addProperty(property)
+        
     def attendeesView(self, attendees):
         """
         Filter out any components that all attendees are not present in. Use EXDATEs

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/itip.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/itip.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -45,12 +45,10 @@
 from twistedcaldav import caldavxml
 from twistedcaldav.accounting import accountingEnabled, emitAccounting
 from twistedcaldav.log import Logger
-from twistedcaldav.ical import Property, iCalendarProductID, Component
+from twistedcaldav.ical import Property, iCalendarProductID
 from twistedcaldav.method import report_common
 from twistedcaldav.resource import isCalendarCollectionResource
 
-from vobject.icalendar import utc
-
 log = Logger()
 
 __version__ = "0.0"
@@ -899,99 +897,3 @@
             return -1
     
         return 0
-
-class iTipGenerator(object):
-    
-    @staticmethod
-    def generateCancel(original, attendees, instances=None):
-        
-        itip = Component("VCALENDAR")
-        itip.addProperty(Property("VERSION", "2.0"))
-        itip.addProperty(Property("PRODID", iCalendarProductID))
-        itip.addProperty(Property("METHOD", "CANCEL"))
-
-        if instances is None:
-            instances = (None,)
-
-        for instance_rid in instances:
-            
-            # Create a new component matching the type of the original
-            comp = Component(original.mainType())
-            itip.addComponent(comp)
-
-            # Use the master component when the instance is None
-            if not instance_rid:
-                instance = original.masterComponent()
-            else:
-                instance = original.overriddenComponent(instance_rid)
-                if instance is None:
-                    instance = original.masterComponent()
-            assert instance is not None
-
-            # Add some required properties extracted from the original
-            comp.addProperty(Property("DTSTAMP", datetime.datetime.now(tz=utc)))
-            comp.addProperty(Property("UID", instance.propertyValue("UID")))
-            seq = instance.propertyValue("SEQUENCE")
-            seq = str(int(seq) + 1) if seq else "1"
-            comp.addProperty(Property("SEQUENCE", seq))
-            comp.addProperty(instance.getOrganizerProperty())
-            if instance_rid:
-                comp.addProperty(Property("RECURRENCE-ID", instance_rid))
-            
-            # Extract the matching attendee property
-            for attendee in attendees:
-                attendeeProp = instance.getAttendeeProperty((attendee,))
-                assert attendeeProp is not None
-                comp.addProperty(attendeeProp)
-
-        return itip
-
-    @staticmethod
-    def generateAttendeeRequest(original, attendees):
-
-        # Start with a copy of the original as we may have to modify bits of it
-        itip = original.duplicate()
-        itip.addProperty(Property("METHOD", "REQUEST"))
-        
-        # Now filter out components that do not contain every attendee
-        itip.attendeesView(attendees)
-        
-        # No alarms
-        itip.removeAlarms()
-
-        return itip
-
-    @staticmethod
-    def generateAttendeeReply(original, attendee, force_decline=False):
-
-        # Start with a copy of the original as we may have to modify bits of it
-        itip = original.duplicate()
-        itip.addProperty(Property("METHOD", "REPLY"))
-        
-        # Remove all attendees except the one we want
-        itip.removeAllButOneAttendee(attendee)
-        
-        # No alarms
-        itip.removeAlarms()
-
-        # Remove all but essential properties
-        itip.removeUnwantedProperties((
-            "UID",
-            "RECURRENCE-ID",
-            "SEQUENCE",
-            "DTSTAMP",
-            "ORGANIZER",
-            "ATTENDEE",
-        ))
-        
-        # Now set each ATTENDEE's PARTSTAT to DECLINED
-        if force_decline:
-            attendeeProps = itip.getAttendeeProperties((attendee,))
-            assert attendeeProps, "Must have some matching ATTENDEEs"
-            for attendeeProp in attendeeProps:
-                if "PARTSTAT" in attendeeProp.params():
-                    attendeeProp.params()["PARTSTAT"][0] = "DECLINED"
-                else:
-                    attendeeProp.params()["PARTSTAT"] = ["DECLINED"]
-        
-        return itip

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/method/delete.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/method/delete.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/method/delete.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -24,8 +24,7 @@
 from twisted.web2 import responsecode
 from twisted.web2.dav.util import parentForURL
 
-from twistedcaldav.resource import isPseudoCalendarCollectionResource,\
-    isCalendarCollectionResource
+from twistedcaldav.resource import isCalendarCollectionResource
 from twistedcaldav.scheduling.implicit import ImplicitScheduler
 
 @inlineCallbacks

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/method/put_common.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/method/put_common.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -181,7 +181,8 @@
         source=None, source_uri=None, sourceparent=None, sourcecal=False, deletesource=False,
         destination=None, destination_uri=None, destinationparent=None, destinationcal=True,
         calendar=None,
-        isiTIP=False
+        isiTIP=False,
+        allowImplicitSchedule=True,
     ):
         """
         Function that does common PUT/COPY/MOVE behavior.
@@ -198,7 +199,8 @@
         @param sourceparent:      the L{CalDAVFile} for the source resource's parent collection, or None if source is None.
         @param destinationparent: the L{CalDAVFile} for the destination resource's parent collection.
         @param deletesource:      True if the source resource is to be deleted on successful completion, False otherwise.
-        @param isiTIP:            True if relaxed calendar data validation is to be done, False otherwise.
+        @param isiTIP:                True if relaxed calendar data validation is to be done, False otherwise.
+        @param allowImplicitSchedule: True if implicit scheduling should be attempted, False otherwise.
         """
         
         # Check that all arguments are valid
@@ -236,6 +238,7 @@
         self.calendardata = None
         self.deletesource = deletesource
         self.isiTIP = isiTIP
+        self.allowImplicitSchedule = allowImplicitSchedule
         
         self.rollback = None
         self.access = None
@@ -685,9 +688,12 @@
             yield self.checkQuota()
 
             # Do scheduling
-            if not self.isiTIP:
+            if not self.isiTIP and self.allowImplicitSchedule:
                 scheduler = ImplicitScheduler()
-                self.calendar = (yield scheduler.doImplicitScheduling(self.request, self.destination, self.calendar, False))
+                new_calendar = (yield scheduler.doImplicitScheduling(self.request, self.destination, self.calendar, False))
+                if new_calendar:
+                    self.calendar = new_calendar
+                    self.calendardata = str(self.calendar)
 
             # Initialize the rollback system
             self.setupRollback()

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/resource.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/resource.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -39,7 +39,8 @@
 from twisted.web2.dav.davxml import dav_namespace
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.resource import TwistedACLInheritable
-from twisted.web2.dav.util import joinURL, parentForURL, unimplemented
+from twisted.web2.dav.util import joinURL, parentForURL, unimplemented,\
+    normalizeURL
 from twisted.web2.http import HTTPError, RedirectResponse, StatusResponse, Response
 from twisted.web2.http_headers import MimeType
 from twisted.web2.iweb import IResponse
@@ -59,6 +60,8 @@
 from twistedcaldav.ical import Component as iComponent
 from twistedcaldav.log import LoggingMixIn
 
+from urlparse import urlsplit
+
 if twistedcaldav.__version__:
     serverVersion = twisted.web2.server.VERSION + " TwistedCalDAV/" + twistedcaldav.__version__
 else:
@@ -489,11 +492,18 @@
         if inboxURL:
             inbox = (yield request.locateResource(inboxURL))
             inbox.processFreeBusyCalendar(request.path, False)
+            
+            # Also check the default calendar setting and remove it if the default is deleted
+            default = (yield inbox.readProperty((caldav_namespace, "schedule-default-calendar-URL"), request))
+            if default and len(default.children) == 1:
+                defaultURL = normalizeURL(str(default.children[0]))
+                if normalizeURL(request.path) == defaultURL:
+                    yield inbox.writeProperty(caldavxml.ScheduleDefaultCalendarURL())               
 
     @inlineCallbacks
     def movedCalendar(self, request, destination, destination_uri):
         """
-        Calendar has been deleted. Need to do some extra clean-up.
+        Calendar has been moved. Need to do some extra clean-up.
 
         @param request:
         @type request:
@@ -503,9 +513,18 @@
         principal = (yield self.ownerPrincipal(request))
         inboxURL = principal.scheduleInboxURL()
         if inboxURL:
+            (_ignore_scheme, _ignore_host, destination_path, _ignore_query, _ignore_fragment) = urlsplit(normalizeURL(destination_uri))
+
             inbox = (yield request.locateResource(inboxURL))
             inbox.processFreeBusyCalendar(request.path, False)
             inbox.processFreeBusyCalendar(destination_uri, destination.isCalendarOpaque())
+            
+            # Also check the default calendar setting and remove it if the default is deleted
+            default = (yield inbox.readProperty((caldav_namespace, "schedule-default-calendar-URL"), request))
+            if default and len(default.children) == 1:
+                defaultURL = normalizeURL(str(default.children[0]))
+                if normalizeURL(request.path) == defaultURL:
+                    yield inbox.writeProperty(caldavxml.ScheduleDefaultCalendarURL(davxml.HRef(destination_path)))               
 
     def isCalendarOpaque(self):
         

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/schedule.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/schedule.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -30,7 +30,7 @@
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.dav.idav import IDAVResource
 from twisted.web2.dav.resource import AccessDeniedError
-from twisted.web2.dav.util import joinURL
+from twisted.web2.dav.util import joinURL, normalizeURL
 from twisted.web2.http import HTTPError
 from twisted.web2.http import Response
 from twisted.web2.http_headers import MimeType
@@ -137,6 +137,7 @@
         elif property.qname() == (caldav_namespace, "calendar-free-busy-set"):
             # Verify that the calendars added in the PROPPATCH are valid. We do not check
             # whether existing items in the property are still valid - only new ones.
+            property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children]
             new_calendars = set([str(href) for href in property.children])
             if not self.hasDeadProperty(property):
                 old_calendars = set()
@@ -154,6 +155,7 @@
 
         elif property.qname() == (caldav_namespace, "schedule-default-calendar-URL"):
             # Verify that the calendar added in the PROPPATCH is valid.
+            property.children = [davxml.HRef(normalizeURL(str(href))) for href in property.children]
             new_calendar = [str(href) for href in property.children]
             if len(new_calendar) == 1:
                 cal = (yield request.locateResource(str(new_calendar[0])))
@@ -167,10 +169,12 @@
         yield super(ScheduleInboxResource, self).writeProperty(property, request)
 
     def processFreeBusyCalendar(self, uri, addit):
+        uri = normalizeURL(uri)
+
         if not self.hasDeadProperty((caldav_namespace, "calendar-free-busy-set")):
             fbset = set()
         else:
-            fbset = set([str(href) for href in self.readDeadProperty((caldav_namespace, "calendar-free-busy-set")).children])
+            fbset = set([normalizeURL(str(href)) for href in self.readDeadProperty((caldav_namespace, "calendar-free-busy-set")).children])
         if addit:
             if uri not in fbset:
                 fbset.add(uri)
@@ -280,6 +284,7 @@
     def resourceType(self):
         return davxml.ResourceType.scheduleOutbox
 
+    @inlineCallbacks
     def http_POST(self, request):
         """
         The CalDAV POST method.
@@ -290,18 +295,15 @@
         issues which the other approach would have with large numbers of recipients.
         """
         # Check authentication and access controls
-        def _gotResult(_):
-    
-            # This is a local CALDAV scheduling operation.
-            scheduler = CalDAVScheduler(request, self)
-    
-            # Do the POST processing treating
-            return scheduler.doSchedulingViaPOST()
-            
-        d = self.authorize(request, (caldavxml.Schedule(),))
-        d.addCallback(_gotResult)
-        return d
+        yield self.authorize(request, (caldavxml.Schedule(),))
 
+        # This is a local CALDAV scheduling operation.
+        scheduler = CalDAVScheduler(request, self)
+
+        # Do the POST processing treating
+        result = (yield scheduler.doSchedulingViaPOST())
+        returnValue(result.response())
+
 class IScheduleInboxResource (CalDAVResource):
     """
     iSchedule Inbox resource.
@@ -358,21 +360,18 @@
         response.headers.setHeader("content-type", MimeType("text", "html"))
         return response
 
+    @inlineCallbacks
     def http_POST(self, request):
         """
         The server-to-server POST method.
         """
 
         # Check authentication and access controls
-        def _gotResult(_):
-    
-            # This is a server-to-server scheduling operation.
-            scheduler = IScheduleScheduler(request, self)
-    
-            # Do the POST processing treating this as a non-local schedule
-            return scheduler.doSchedulingViaPOST()
+        yield self.authorize(request, (caldavxml.Schedule(),))
 
-        d = self.authorize(request, (caldavxml.Schedule(),))
-        d.addCallback(_gotResult)
-        return d
+        # This is a server-to-server scheduling operation.
+        scheduler = IScheduleScheduler(request, self)
 
+        # Do the POST processing treating this as a non-local schedule
+        result = (yield scheduler.doSchedulingViaPOST())
+        returnValue(result.response())

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/caldav.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/caldav.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/caldav.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -37,6 +37,8 @@
 from twistedcaldav.scheduling.cuaddress import LocalCalendarUser,\
     RemoteCalendarUser
 from twistedcaldav.scheduling.delivery import DeliveryService
+from twistedcaldav.scheduling.processing import ImplicitProcessor,\
+    ImplicitProcessorException
 
 import md5
 import time
@@ -145,42 +147,62 @@
         childURL = joinURL(recipient.inboxURL, name)
         child = (yield self.scheduler.request.locateResource(childURL))
 
-        # Copy calendar to inbox (doing fan-out)
+        # Do implicit scheduling message processing
         try:
-            from twistedcaldav.method.put_common import StoreCalendarObjectResource
-            yield StoreCalendarObjectResource(
-                         request=self.scheduler.request,
-                         destination = child,
-                         destination_uri = childURL,
-                         destinationparent = recipient.inbox,
-                         destinationcal = True,
-                         calendar = self.scheduler.calendar,
-                         isiTIP = True
-                     ).run()
-        except:
-            # FIXME: Bare except
+            processor = ImplicitProcessor()
+            processed, autoprocessed = (yield processor.doImplicitProcessing(
+                self.scheduler.request,
+                self.scheduler.calendar,
+                self.scheduler.originator,
+                recipient
+            ))
+        except ImplicitProcessorException, e:
             log.err("Could not store data in Inbox : %s" % (recipient.inbox,))
             err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
-            responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.8;No authority")
+            responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=e.msg)
             returnValue(False)
-        else:
+
+        if autoprocessed:
+            # No need to write the inbox item as it has already been auto-processed
             responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success")
-
-            # Store CALDAV:originator property
-            child.writeDeadProperty(caldavxml.Originator(davxml.HRef(self.scheduler.originator.cuaddr)))
-        
-            # Store CALDAV:recipient property
-            child.writeDeadProperty(caldavxml.Recipient(davxml.HRef(recipient.cuaddr)))
-        
-            # Store CALDAV:schedule-state property
-            child.writeDeadProperty(caldavxml.ScheduleState(caldavxml.ScheduleUnprocessed()))
-        
-            # Look for auto-schedule option
-            if recipient.principal.autoSchedule():
-                autoresponses.append((recipient.principal, recipient.inbox, child))
-                
             returnValue(True)
+        else:
+            # Copy calendar to inbox 
+            try:
+                from twistedcaldav.method.put_common import StoreCalendarObjectResource
+                yield StoreCalendarObjectResource(
+                             request=self.scheduler.request,
+                             destination = child,
+                             destination_uri = childURL,
+                             destinationparent = recipient.inbox,
+                             destinationcal = True,
+                             calendar = self.scheduler.calendar,
+                             isiTIP = True
+                         ).run()
+            except:
+                # FIXME: Bare except
+                log.err("Could not store data in Inbox : %s" % (recipient.inbox,))
+                err = HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "recipient-permissions")))
+                responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus="3.8;No authority")
+                returnValue(False)
+            else:
+                responses.add(recipient.cuaddr, responsecode.OK, reqstatus="2.0;Success")
     
+                # Store CALDAV:originator property
+                child.writeDeadProperty(caldavxml.Originator(davxml.HRef(self.scheduler.originator.cuaddr)))
+            
+                # Store CALDAV:recipient property
+                child.writeDeadProperty(caldavxml.Recipient(davxml.HRef(recipient.cuaddr)))
+            
+                # Store CALDAV:schedule-state property
+                child.writeDeadProperty(caldavxml.ScheduleState(caldavxml.ScheduleProcessed() if processed else caldavxml.ScheduleUnprocessed()))
+            
+                # Look for auto-schedule option
+                if not processed and recipient.principal.autoSchedule():
+                    autoresponses.append((recipient.principal, recipient.inbox, child))
+                    
+                returnValue(True)
+    
     @inlineCallbacks
     def generateFreeBusyResponse(self, recipient, responses, organizerProp, uid):
 

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/icaldiff.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/icaldiff.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -16,6 +16,7 @@
 
 from twistedcaldav.ical import Component
 from twistedcaldav.log import Logger
+from twistedcaldav.scheduling.itip import iTipGenerator
 
 """
 Class that handles diff'ing two calendar objects.
@@ -68,13 +69,13 @@
 
         # Do straight comparison without alarms
         self.calendar1 = self.calendar1.duplicate()
-        self.calendar1.removeAlarms()
         self.calendar1.removeXProperties()
         self.calendar1.attendeesView((attendee,))
+        iTipGenerator.prepareSchedulingMessage(self.calendar1)
 
         self.calendar2 = self.calendar2.duplicate()
-        self.calendar2.removeAlarms()
         self.calendar2.removeXProperties()
+        iTipGenerator.prepareSchedulingMessage(self.calendar2)
 
         if self.calendar1 == self.calendar2:
             return True, True
@@ -152,7 +153,6 @@
         result = set1 - set2
         if result:
             log.debug("Missing components from first calendar: %s" % (result,))
-        if result:
             return False, False
 
         # Now verify that each component in set1 matches what is in set2
@@ -192,7 +192,13 @@
         propdiff = set(comp1.properties()) ^ set(comp2.properties())
         for prop in tuple(propdiff):
             # These ones are OK to change
-            if prop.name() in ("TRANSP", "DTSTAMP", "CREATED", "LAST-MODIFIED",):
+            if prop.name() in (
+                "TRANSP",
+                "DTSTAMP",
+                "CREATED",
+                "LAST-MODIFIED",
+                "SEQUENCE",
+            ):
                 propdiff.remove(prop)
                 continue
             if prop.name() != "ATTENDEE" or prop.value() != self.attendee:

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/implicit.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/implicit.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -19,11 +19,12 @@
 from twisted.web2.dav.http import ErrorResponse
 from twisted.web2.http import HTTPError
 from twistedcaldav.caldavxml import caldav_namespace
-from twistedcaldav.itip import iTipGenerator
+from twistedcaldav.scheduling.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
+from twistedcaldav import caldavxml
 
 __all__ = [
     "ImplicitScheduler",
@@ -34,8 +35,11 @@
 # TODO:
 #
 # Handle the case where a PUT removes the ORGANIZER property. That should be equivalent to cancelling the entire meeting.
+# Support SCHEDULE-AGENT property
+# Support SCHEDULE-STATUS property
+# Support live calendars
+# Support Schedule-Reply header
 #
-# Figure out proper behavior for what happens when an attendee deletes their copy of an event. For now disallow.
 
 class ImplicitScheduler(object):
     
@@ -56,7 +60,8 @@
         @param deleting: C{True} if the resource is being deleting
         @type deleting: bool
 
-        @return: a new calendar object modified with scheduling information
+        @return: a new calendar object modified with scheduling information,
+            or C{None} if nothing happened
         """
         
         self.request = request
@@ -64,6 +69,8 @@
         self.calendar = calendar
         self.calendar_owner = (yield self.resource.owner(self.request))
         self.deleting = deleting
+        self.internal_request = False
+        self.except_attendees = ()
 
         # When deleting we MUST have the calendar as the actual resource
         # will have been deleted by now
@@ -77,9 +84,36 @@
             yield self.doImplicitOrganizer()
         elif self.isAttendeeScheduling():
             yield self.doImplicitAttendee()
+        else:
+            returnValue(None)
 
         returnValue(self.calendar)
 
+    def refreshAllAttendeesExceptSome(self, request, resource, calendar, attendees):
+        """
+        
+        @param request:
+        @type request:
+        @param attendee:
+        @type attendee:
+        @param calendar:
+        @type calendar:
+        """
+
+        self.request = request
+        self.resource = resource
+        self.calendar = calendar
+        self.calendar_owner = None
+        self.deleting = False
+        self.internal_request = True
+        self.except_attendees = attendees
+        
+        # Get some useful information from the calendar
+        self.extractCalendarData()
+        self.organizerPrincipal = self.resource.principalForCalendarUserAddress(self.organizer)
+        
+        return self.processRequests()
+
     def extractCalendarData(self):
         
         # Get the ORGANIZER and verify it is the same for all components
@@ -184,13 +218,68 @@
         as users that need to be sent a cancel.
         """
         
+        # Several possibilities for when CANCELs need to be sent:
+        #
+        # Remove ATTENDEE property
+        # Add EXDATE
+        # Remove overridden component
+        # Remove RDATE
+        # Truncate RRULE
+        # Change RRULE
+        
+        # TODO: the later three will be ignored for now.
+
         oldAttendeesByInstance = self.oldcalendar.getAttendeesByInstance()
         
         mappedOld = set(oldAttendeesByInstance)
         mappedNew = set(self.attendeesByInstance)
         
-        self.cancelledAttendees = mappedOld.difference(mappedNew)
+        # Get missing instances
+        oldInstances = set(self.oldcalendar.getComponentInstances())
+        newInstances = set(self.calendar.getComponentInstances())
+        removedInstances = oldInstances - newInstances
 
+        # Also look for new EXDATEs
+        oldexdates = set()
+        for property in self.oldcalendar.masterComponent().properties("EXDATE"):
+            oldexdates.update(property.value())
+        newexdates = set()
+        for property in self.calendar.masterComponent().properties("EXDATE"):
+            newexdates.update(property.value())
+
+        addedexdates = newexdates - oldexdates
+
+        # Now figure out the attendees that need to be sent CANCELs
+        self.cancelledAttendees = set()
+        
+        for item in mappedOld:
+            if item not in mappedNew:
+                
+                # Several possibilities:
+                #
+                # 1. removed from master component - always a CANCEL
+                # 2. removed from overridden component - always a CANCEL
+                # 3. removed overridden component - only CANCEL if not in master or exdate added
+                 
+                new_attendee, rid = item
+                
+                # 1. & 2.
+                if rid is None or rid not in removedInstances:
+                    self.cancelledAttendees.add(item)
+                else:
+                    # 3.
+                    if (new_attendee, None) not in mappedNew or rid in addedexdates:
+                        self.cancelledAttendees.add(item)
+
+        master_attendees = self.oldcalendar.masterComponent().getAttendeesByInstance()
+        for attendee, _ignore in master_attendees:
+            for exdate in addedexdates:
+                # Don't remove the master attendee's when an EXDATE is added for a removed overridden component
+                # as the set of attendees in the override may be different from the master set, but the override
+                # will have been accounted for by the previous attendee/instance logic.
+                if exdate not in removedInstances:
+                    self.cancelledAttendees.add((attendee, exdate))
+
     @inlineCallbacks
     def scheduleWithAttendees(self):
         
@@ -235,7 +324,7 @@
     
             # 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))
+            response = (yield scheduler.doSchedulingViaPUT(self.organizer, (attendee,), itipmsg, self.internal_request))
             self.handleSchedulingResponse(response, True)
             
     @inlineCallbacks
@@ -251,6 +340,10 @@
             if attendee in self.organizerPrincipal.calendarUserAddresses():
                 continue
 
+            # Don't send message to specified attendees
+            if attendee in self.except_attendees:
+                continue
+
             itipmsg = iTipGenerator.generateAttendeeRequest(self.calendar, (attendee,))
 
             # Send scheduling message
@@ -260,13 +353,25 @@
     
             # 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))
+            response = (yield scheduler.doSchedulingViaPUT(self.organizer, (attendee,), itipmsg, self.internal_request))
             self.handleSchedulingResponse(response, True)
 
     def handleSchedulingResponse(self, response, is_organizer):
         
-        # TODO: need to figure out how to process the response
-        pass
+        # Map each recipient in the response to a status code
+        responses = {}
+        for item in response.responses:
+            assert isinstance(item, caldavxml.Response), "Wrong element in response"
+            recipient = str(item.children[0].children[0])
+            status = str(item.children[1])
+            responses[recipient] = status
+            
+        # Now apply to each ATTENDEE/ORGANIZER in the original data
+        self.calendar.setParameterToValueForPropertyWithValue(
+            "SCHEDULE-STATUS",
+            status,
+            "ATTENDEE" if is_organizer else "ORGANIZER",
+            recipient)
 
     @inlineCallbacks
     def doImplicitAttendee(self):
@@ -364,7 +469,6 @@
             self.handleSchedulingResponse(response, False)
             
         log.info("Implicit %s - attendee: '%s' to organizer: '%s', UID: '%s'" % (action, self.attendee, self.organizer, self.uid,))
-        d = scheduler.doSchedulingViaPUT(self.attendee, (self.organizer,), itipmsg)
+        d = scheduler.doSchedulingViaPUT(self.attendee, (self.organizer,), itipmsg, self.internal_request)
         d.addCallback(_gotResponse)
         return d
-

Copied: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/itip.py (from rev 2868, CalendarServer/branches/users/cdaboo/implicit-2805/twistedcaldav/scheduling/itip.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/itip.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/itip.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -0,0 +1,448 @@
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+iTIP (RFC2446) processing.
+"""
+
+#
+# This is currently used for handling auto-replies to schedule requests arriving
+# in an inbox. It is called in a delayed fashion via reactor.callLater.
+#
+# We assume that all the components/calendars we deal with have been determined
+# as being 'valid for CalDAV/iTIP', i.e. they contain UIDs, single component
+# types, etc.
+#
+# The logic for component matching needs a lot more work as it currently does not
+# know how to deal with overridden instances.
+#
+
+import datetime
+
+from twistedcaldav.log import Logger
+from twistedcaldav.ical import Property, iCalendarProductID, Component
+
+from vobject.icalendar import utc
+
+log = Logger()
+
+__version__ = "0.0"
+
+__all__ = [
+    "iTipProcessor",
+    "iTipGenerator",
+]
+
+class iTipProcessing(object):
+
+    @staticmethod
+    def processNewRequest(itip_message):
+        """
+        Process a METHOD=REQUEST for a brand new calendar object.
+        
+        @param itip_message: the iTIP message calendar object to process.
+        @type itip_message:
+        
+        @return: calendar object ready to save
+        """
+        assert itip_message.propertyValue("METHOD") == "REQUEST", "iTIP message must have METHOD:REQUEST"
+
+        calendar = itip_message.duplicate()
+        method = calendar.getProperty("METHOD")
+        if method:
+            calendar.removeProperty(method)
+            
+        return calendar
+        
+    @staticmethod
+    def processRequest(itip_message, calendar):
+        """
+        Process a METHOD=REQUEST.
+        
+        @param itip_message: the iTIP message calendar object to process.
+        @type itip_message:
+        @param calendar: the calendar object to apply the REQUEST to
+        @type calendar:
+        
+        @return: calendar object ready to save, or C{None} (request should be ignored)
+        """
+        
+        # Merge Organizer data with Attendee's own changes (VALARMs only for now).
+        
+        # Different behavior depending on whether a master component is present or not
+        current_master = calendar.masterComponent()
+        if current_master:
+            master_valarms = [comp for comp in current_master.subcomponents() if comp.name() == "VALARM"]
+        else:
+            master_valarms = ()
+
+        if itip_message.masterComponent() is not None:
+            
+            # Get a new calendar object first
+            new_calendar = iTipProcessing.processNewRequest(itip_message)
+            
+            # Copy over master alarms
+            master_component = new_calendar.masterComponent()
+            for alarm in master_valarms:
+                master_component.addComponent(alarm)
+                
+            # Now try to match recurrences
+            for component in new_calendar.subcomponents():
+                if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
+                    iTipProcessing.transferAlarms(calendar, master_valarms, component)
+            
+            # Replace the entire object
+            return new_calendar
+
+        else:
+            # Need existing tzids
+            tzids = calendar.timezones()
+
+            # Update existing instances
+            for component in itip_message.subcomponents():
+                if component.name() == "VTIMEZONE":
+                    # May need to add a new VTIMEZONE
+                    if component.propertyValue("TZID") not in tzids:
+                        calendar.addComponent(component)
+                else:
+                    iTipProcessing.transferAlarms(calendar, master_valarms, component, remove_matched=True)
+                    calendar.addComponent(component)
+
+            # Write back the modified object
+            return calendar
+
+    @staticmethod
+    def processCancel(itip_message, calendar):
+        """
+        Process a METHOD=CANCEL.
+        
+        TODO: Yes, I am going to ignore RANGE= on RECURRENCE-ID for now...
+        
+        @param itip_message: the iTIP message calendar object to process.
+        @type itip_message:
+        @param calendar: the calendar object to apply the CANCEL to
+        @type calendar:
+        
+        @return: C{tuple} of:
+            C{bool} : C{True} if processed, C{False} if scheduling message should be ignored
+            C{bool} : C{True} if calendar object should be deleted, C{False} otherwise
+        """
+        
+        assert itip_message.propertyValue("METHOD") == "CANCEL", "iTIP message must have METHOD:CANCEL"
+        assert itip_message.resourceUID() == calendar.resourceUID(), "UIDs must be the same to process iTIP message"
+
+        # Check to see if this is a cancel of the entire event
+        if itip_message.masterComponent() is not None:
+            return True, True
+
+        # 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 master object if it exists
+        calendar_master = calendar.masterComponent()
+        exdates = []
+
+        # Look at each component in the iTIP message
+        for component in itip_message.subcomponents():
+            if component.name() == "VTIMEZONE":
+                continue
+        
+            # Extract RECURRENCE-ID value from component
+            rid = component.getRecurrenceIDUTC()
+            
+            # Get the one that matches in the calendar
+            overridden = calendar.overriddenComponent(rid)
+            
+            if overridden:
+                # We are cancelling an overridden component.
+
+                # Exclude the cancelled instance
+                exdates.append(component.getRecurrenceIDUTC())
+                
+                # Remove the existing component.
+                calendar.removeComponent(overridden)
+            elif calendar_master:
+                # We are trying to CANCEL a non-overridden instance.
+
+                # Exclude the cancelled instance
+                exdates.append(component.getRecurrenceIDUTC())
+
+        # If we have any EXDATEs lets add them to the existing calendar object.
+        if exdates and calendar_master:
+            calendar_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 calendar.mainType() is None:
+            # Delete the now empty calendar object
+            return True, True
+        else:
+            return True, False
+    
+    @staticmethod
+    def processReply(itip_message, calendar):
+        """
+        Process a METHOD=REPLY.
+        
+        TODO: Yes, I am going to ignore RANGE= on RECURRENCE-ID for now...
+        
+        @param itip_message: the iTIP message calendar object to process.
+        @type itip_message:
+        @param calendar: the calendar object to apply the REPLY to
+        @type calendar:
+        
+        @return: C{True} if processed, C{False} if scheduling message should be ignored
+        """
+        
+        assert itip_message.propertyValue("METHOD") == "REPLY", "iTIP message must have METHOD:REPLY"
+        assert itip_message.resourceUID() == calendar.resourceUID(), "UIDs must be the same to process iTIP message"
+
+        # Take each component in the reply and update the corresponding component
+        # in the organizer's copy (possibly generating new ones) so that the ATTENDEE
+        # PARTSTATs match up.
+
+        # Do the master first
+        old_master = calendar.masterComponent()
+        new_master = itip_message.masterComponent()
+        attendees = set()
+        if new_master:
+            attendees.add(iTipProcessing.updateAttendeePartStat(new_master, old_master))
+
+        # Now do all overridden ones
+        for itip_component in itip_message.subcomponents():
+            
+            # Make sure we have an appropriate component
+            if itip_component.name() == "VTIMEZONE":
+                continue
+            rid = itip_component.getRecurrenceIDUTC()
+            if rid is None:
+                continue
+            
+            # Find matching component in organizer's copy
+            match_component = calendar.overriddenComponent(rid)
+            if match_component is None:
+                # Attendee is overriding an instance themselves - we need to create a derived one
+                # for the Organizer
+                match_component = calendar.deriveInstance(rid)
+                calendar.addComponent(match_component)
+
+            attendees.add(iTipProcessing.updateAttendeePartStat(itip_component, match_component))
+                
+        return True, attendees
+
+    @staticmethod
+    def updateAttendeePartStat(from_component, to_component):
+        """
+        Copy the PARTSTAT of the Attendee in the from_component to the matching ATTENDEE
+        in the to_component. Ignore if no match found.
+
+        @param from_component:
+        @type from_component:
+        @param to_component:
+        @type to_component:
+        """
+        
+        # Get attendee in from_component - there MUST be only one
+        attendees = tuple(from_component.properties("ATTENDEE"))
+        assert len(attendees) == 1, "There must be one and only one ATTENDEE property in a REPLY"
+        attendee = attendees[0]
+        partstat = attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0]
+        
+        # Now find matching ATTENDEE in to_component
+        existing_attendee = to_component.getAttendeeProperty((attendee.value(),))
+        if existing_attendee:
+            existing_attendee.params().setdefault("PARTSTAT", [partstat])[0] = partstat
+            
+        return attendee.value()
+
+    @staticmethod
+    def transferAlarms(from_calendar, master_valarms, to_component, remove_matched=False):
+
+        rid = to_component.getRecurrenceIDUTC()
+
+        # Is there a matching component
+        matched = from_calendar.overriddenComponent(rid)
+        if matched:
+            # Copy over VALARMs from existing component
+            [to_component.addComponent(comp) for comp in matched.subcomponents() if comp.name() == "VALARM"]
+
+            # Remove the old one
+            if remove_matched:
+                from_calendar.removeComponent(matched)
+                
+        else:
+            # It is a new override - copy any valarms on the existing master component
+            # into the new one.
+            for alarm in master_valarms:
+                # Just copy in the new override
+                to_component.addComponent(alarm)
+    
+class iTipGenerator(object):
+    
+    @staticmethod
+    def generateCancel(original, attendees, instances=None):
+        
+        itip = Component("VCALENDAR")
+        itip.addProperty(Property("VERSION", "2.0"))
+        itip.addProperty(Property("PRODID", iCalendarProductID))
+        itip.addProperty(Property("METHOD", "CANCEL"))
+
+        if instances is None:
+            instances = (None,)
+
+        tzids = set()
+        for instance_rid in instances:
+            
+            # Create a new component matching the type of the original
+            comp = Component(original.mainType())
+            itip.addComponent(comp)
+
+            # Use the master component when the instance is None
+            if not instance_rid:
+                instance = original.masterComponent()
+            else:
+                instance = original.overriddenComponent(instance_rid)
+                if instance is None:
+                    instance = original.masterComponent()
+            assert instance is not None
+
+            # Add some required properties extracted from the original
+            comp.addProperty(Property("DTSTAMP", datetime.datetime.now(tz=utc)))
+            comp.addProperty(Property("UID", instance.propertyValue("UID")))
+            seq = instance.propertyValue("SEQUENCE")
+            seq = str(int(seq) + 1) if seq else "1"
+            comp.addProperty(Property("SEQUENCE", seq))
+            comp.addProperty(instance.getOrganizerProperty())
+            if instance_rid:
+                comp.addProperty(Property("RECURRENCE-ID", instance_rid))
+            
+            # Extract the matching attendee property
+            for attendee in attendees:
+                attendeeProp = instance.getAttendeeProperty((attendee,))
+                assert attendeeProp is not None
+                comp.addProperty(attendeeProp)
+
+            tzids.update(comp.timezoneIDs())
+            
+        # Now include any referenced tzids
+        for comp in original.subcomponents():
+            if comp.name() == "VTIMEZONE":
+                tzid = comp.propertyValue("TZID")
+                if tzid in tzids:
+                    itip.addComponent(comp)
+
+        return itip
+
+    @staticmethod
+    def generateAttendeeRequest(original, attendees):
+
+        # Start with a copy of the original as we may have to modify bits of it
+        itip = original.duplicate()
+        itip.addProperty(Property("METHOD", "REQUEST"))
+        
+        # Now filter out components that do not contain every attendee
+        itip.attendeesView(attendees)
+        
+        # Strip out unwanted bits
+        iTipGenerator.prepareSchedulingMessage(itip)
+
+        return itip
+        
+    @staticmethod
+    def generateAttendeeReply(original, attendee, force_decline=False):
+
+        # Start with a copy of the original as we may have to modify bits of it
+        itip = original.duplicate()
+        itip.addProperty(Property("METHOD", "REPLY"))
+        
+        # Remove all attendees except the one we want
+        itip.removeAllButOneAttendee(attendee)
+        
+        # No alarms
+        itip.removeAlarms()
+
+        # Remove all but essential properties
+        itip.removeUnwantedProperties((
+            "UID",
+            "RECURRENCE-ID",
+            "SEQUENCE",
+            "DTSTAMP",
+            "ORGANIZER",
+            "ATTENDEE",
+            "X-CALENDARSERVER-PRIVATE-COMMENT",
+        ))
+        
+        # Now set each ATTENDEE's PARTSTAT to DECLINED
+        if force_decline:
+            attendeeProps = itip.getAttendeeProperties((attendee,))
+            assert attendeeProps, "Must have some matching ATTENDEEs"
+            for attendeeProp in attendeeProps:
+                if "PARTSTAT" in attendeeProp.params():
+                    attendeeProp.params()["PARTSTAT"][0] = "DECLINED"
+                else:
+                    attendeeProp.params()["PARTSTAT"] = ["DECLINED"]
+        
+        # Add REQUEST-STATUS to each top-level component
+        itip.addPropertyToAllComponents(Property("REQUEST-STATUS", "2.0;Success"))
+        return itip
+
+    @staticmethod
+    def prepareSchedulingMessage(itip):
+        """
+        Remove properties and parameters that should not be sent in an iTIP message
+        """
+
+        # Component properties
+        def stripSubComponents(component, strip):
+            
+            for subcomponent in tuple(component.subcomponents()):
+                if subcomponent.name() in strip:
+                    component.removeComponent(subcomponent)
+
+        # Component properties
+        def stripComponentProperties(component, properties):
+            
+            for property in tuple(component.properties()):
+                if property.name() in properties:
+                    component.removeProperty(property)
+
+        # Property parameters
+        def stripPropertyParameters(properties, parameters):
+            
+            for property in properties:
+                for parameter in parameters:
+                    try:
+                        del property.params()[parameter]
+                    except KeyError:
+                        pass
+
+        # Top-level properties
+        stripComponentProperties(itip, ("X-CALENDARSERVER-ACCESS",))
+                
+        # Component properties
+        for component in itip.subcomponents():
+            stripSubComponents(component, ("VALARM",))
+            stripComponentProperties(component, (
+                "X-CALENDARSERVER-PRIVATE-COMMENT",
+                "X-CALENDARSERVER-ATTENDEE-COMMENT",
+            ))
+            stripPropertyParameters(component.properties("ATTENDEE"), (
+                "SCHEDULE-AGENT",
+                "SCHEDULE-STATUS",
+            ))
+            stripPropertyParameters(component.properties("ORGANIZER"), (
+                "SCHEDULE-STATUS",
+            ))

Copied: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/processing.py (from rev 2868, CalendarServer/branches/users/cdaboo/implicit-2805/twistedcaldav/scheduling/processing.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/processing.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/processing.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -0,0 +1,332 @@
+#
+# Copyright (c) 2005-2008 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twistedcaldav.log import Logger
+from twistedcaldav.method import report_common
+from twisted.web2.dav.fileop import delete
+from twistedcaldav.scheduling.itip import iTipProcessing
+from hashlib import md5
+from twisted.web2.dav.util import joinURL
+from twistedcaldav.caldavxml import caldav_namespace
+import time
+
+__all__ = [
+    "ImplicitProcessor",
+]
+
+log = Logger()
+
+class ImplicitProcessorException(Exception):
+    
+    def __init__(self, msg):
+        self.msg = msg
+
+class ImplicitProcessor(object):
+    
+    def __init__(self):
+        pass
+
+    @inlineCallbacks
+    def doImplicitProcessing(self, request, message, originator, recipient):
+        """
+        Do implicit processing of a scheduling message, and possibly also auto-process it
+        if the recipient has auto-accept on.
+
+        @param message:
+        @type message:
+        @param originator:
+        @type originator:
+        @param recipient:
+        @type recipient:
+        
+        @return: a C{tuple} of (C{bool}, C{bool}) indicating whether the message was processed, and if it was whether
+            auto-processing has taken place.
+        """
+
+        self.request = request
+        self.message = message
+        self.originator = originator
+        self.recipient = recipient
+        
+        # TODO: for now going to assume that the originator is local - i.e. the scheduling message sent
+        # represents the actual organizer's view.
+        
+        # First see whether this is the organizer or attendee sending the message
+        self.extractCalendarData()
+
+        if self.isOrganizerReceivingMessage():
+            result = (yield self.doImplicitOrganizer())
+        elif self.isAttendeeReceivingMessage():
+            result = (yield self.doImplicitAttendee())
+        else:
+            log.error("METHOD:%s not supported for implicit scheduling." % (self.method,))
+            raise ImplicitProcessorException("3.14;Unsupported capability")
+
+        returnValue(result)
+
+    def extractCalendarData(self):
+        
+        # Some other useful things
+        self.method = self.message.propertyValue("METHOD")
+        self.uid = self.message.resourceUID()
+    
+    def isOrganizerReceivingMessage(self):
+        return self.method in ("REPLY", "REFRESH")
+
+    def isAttendeeReceivingMessage(self):
+        return self.method in ("REQUEST", "ADD", "CANCEL")
+
+    @inlineCallbacks
+    def getRecipientsCopy(self):
+        """
+        Get the Recipient's copy of the event being processed.
+        """
+        
+        self.recipient_calendar = None
+        self.recipient_calendar_collection = None
+        self.recipient_calendar_name = None
+        if self.recipient.principal:
+            # Get Recipient's calendar-home
+            calendar_home = self.recipient.principal.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
+
+            def queryCalendarCollection(collection, uri):
+                rname = collection.index().resourceNameForUID(self.uid)
+                if rname:
+                    self.recipient_calendar = collection.iCalendar(rname)
+                    self.recipient_calendar_name = rname
+                    self.recipient_calendar_collection = collection
+                    self.recipient_calendar_collection_uri = uri
+                    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)
+    
+    @inlineCallbacks
+    def doImplicitOrganizer(self):
+
+        # Locate the organizer's copy of the event.
+        yield self.getRecipientsCopy()
+        if self.recipient_calendar is None:
+            log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring UID: '%s' - organizer has no copy" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+            returnValue((True, True,))
+
+        # Handle new items differently than existing ones.
+        if self.method == "REPLY":
+            result = (yield self.doImplicitOrganizerUpdate())
+        elif self.method == "REFRESH":
+            # With implicit we ignore refreshes.
+            # TODO: for iMIP etc we do need to handle them 
+            result = (True, True,)
+
+        returnValue(result)
+
+    @inlineCallbacks
+    def doImplicitOrganizerUpdate(self):
+        
+        # Check to see if this is a valid reply
+        result, processed_attendees = iTipProcessing.processReply(self.message, self.recipient_calendar)
+        if result:
+ 
+            # Update the attendee's copy of the event
+            log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REPLY, UID: '%s' - updating event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+            recipient_calendar_resource = (yield self.writeCalendarResource(self.recipient_calendar_collection_uri, self.recipient_calendar_collection, self.recipient_calendar_name, self.recipient_calendar))
+            result = (True, False,)
+            
+            self.updateAllAttendeesExceptSome(recipient_calendar_resource, processed_attendees)
+
+        else:
+            # Ignore scheduling message
+            result = (True, True,)
+
+        returnValue(result)
+
+    def updateAllAttendeesExceptSome(self, resource, attendees):
+        """
+        Send an update out to all attendees except the specified ones, to refresh the others due to a change
+        by that one.
+        
+        @param attendee: cu-addresses of attendees not to send to
+        @type attendee: C{set}
+        """
+        
+        from twistedcaldav.scheduling.implicit import ImplicitScheduler
+        scheduler = ImplicitScheduler()
+        scheduler.refreshAllAttendeesExceptSome(self.request, resource, self.recipient_calendar, attendees)
+
+    @inlineCallbacks
+    def doImplicitAttendee(self):
+
+        # Locate the attendee's copy of the event if it exists.
+        yield self.getRecipientsCopy()
+        self.new_resource = self.recipient_calendar is None
+
+        # Handle new items differently than existing ones.
+        if self.new_resource and self.method == "CANCEL":
+            result = (True, True,)
+        else:
+            result = (yield self.doImplicitAttendeeUpdate())
+        
+        returnValue(result)
+
+    @inlineCallbacks
+    def doImplicitAttendeeUpdate(self):
+        
+        # Different based on method
+        if self.method == "REQUEST":
+            result = (yield self.doImplicitAttendeRequest())
+        elif self.method == "CANCEL":
+            result = (yield self.doImplicitAttendeCancel())
+        elif self.method == "ADD":
+            # TODO: implement ADD
+            result = (False, False,)
+            
+        returnValue(result)
+
+    @inlineCallbacks
+    def doImplicitAttendeRequest(self):
+
+        # If there is no existing copy, then look for default calendar and copy it here
+        if self.new_resource:
+            
+            # Check for default calendar
+            default = (yield self.recipient.inbox.readProperty((caldav_namespace, "schedule-default-calendar-URL"), self.request))
+            if len(default.children) == 1:
+                defaultURL = str(default.children[0])
+                default = (yield self.request.locateResource(defaultURL))
+            else:
+                default = None
+
+            if default:
+                log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring METHOD:REQUEST, UID: '%s' - new processed" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                new_calendar = iTipProcessing.processNewRequest(self.message)
+                name =  md5(str(new_calendar) + str(time.time()) + default.fp.path).hexdigest() + ".ics"
+                yield self.writeCalendarResource(defaultURL, default, name, new_calendar)
+                result = (True, False,)
+            else:
+                log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring METHOD:REQUEST, UID: '%s' - new not processed" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                result = (False, False,)
+        else:
+            # Processing update to existing event
+            new_calendar = iTipProcessing.processRequest(self.message, self.recipient_calendar)
+            if new_calendar:
+     
+                # Update the attendee's copy of the event
+                log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - updating event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                yield self.writeCalendarResource(self.recipient_calendar_collection_uri, self.recipient_calendar_collection, self.recipient_calendar_name, new_calendar)
+                result = (True, False,)
+                
+            else:
+                # Request needs to be ignored
+                log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - ignoring" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                result = (True, True,)
+
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def doImplicitAttendeCancel(self):
+
+        # If there is no existing copy, then ignore
+        if self.recipient_calendar is None:
+            log.debug("ImplicitProcessing - originator '%s' to recipient '%s' ignoring METHOD:CANCEL, UID: '%s' - attendee has no copy" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+            result = (True, True,)
+        else:
+            # Check to see if this is a cancel of the entire event
+            processed_message, delete_original = iTipProcessing.processCancel(self.message, self.recipient_calendar)
+            if processed_message:
+                if delete_original:
+                    
+                    # Delete the attendee's copy of the event
+                    log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:CANCEL, UID: '%s' - deleting entire event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                    yield self.deleteCalendarResource(self.recipient_calendar_collection, self.recipient_calendar_name)
+                    result = (True, False,)
+                    
+                else:
+         
+                    # Update the attendee's copy of the event
+                    log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:CANCEL, UID: '%s' - updating event" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                    yield self.writeCalendarResource(self.recipient_calendar_collection_uri, self.recipient_calendar_collection, self.recipient_calendar_name, self.recipient_calendar)
+                    result = (True, False,)
+            else:
+                log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:CANCEL, UID: '%s' - ignoring" % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+                result = (True, True,)
+
+        returnValue(result)
+
+    @inlineCallbacks
+    def writeCalendarResource(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: L{Deferred} -> L{CalDAVFile}
+        """
+        
+        # Create a new name if one was not provided
+        if name is None:
+            name =  md5(str(calendar) + str(time.time()) + collection.fp.path).hexdigest() + ".ics"
+    
+        # Get a resource for the new item
+        newchildURL = joinURL(collURL, name)
+        newchild = yield self.request.locateResource(newchildURL)
+        
+        # Now write it to the resource
+        from twistedcaldav.method.put_common import StoreCalendarObjectResource
+        yield StoreCalendarObjectResource(
+                     request=self.request,
+                     destination = newchild,
+                     destination_uri = newchildURL,
+                     destinationparent = collection,
+                     destinationcal = True,
+                     calendar = calendar,
+                     isiTIP = False,
+                     allowImplicitSchedule = False
+                 ).run()
+    
+        returnValue(newchild)
+
+    @inlineCallbacks
+    def deleteCalendarResource(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())
+        
+        yield delete("", delchild.fp, "0")
+        
+        # Change CTag on the parent calendar collection
+        yield collection.updateCTag()

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/scheduler.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/scheduler.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -69,6 +69,7 @@
         self.excludeUID = None
         self.fakeTheResult = False
         self.method = "Unknown"
+        self.internal_request = False
     
     @inlineCallbacks
     def doSchedulingViaPOST(self):
@@ -86,10 +87,10 @@
         self.loadRecipientsFromRequestHeaders()
         yield self.loadCalendarFromRequest()
 
-        response = (yield self.doScheduling())
-        returnValue(response)
+        result = (yield self.doScheduling())
+        returnValue(result)
 
-    def doSchedulingViaPUT(self, originator, recipients, calendar):
+    def doSchedulingViaPUT(self, originator, recipients, calendar, internal_request=False):
         """
         The implicit scheduling PUT operation.
         """
@@ -103,6 +104,7 @@
         self.originator = originator
         self.recipients = recipients
         self.calendar = calendar
+        self.internal_request = internal_request
 
         return self.doScheduling()
 
@@ -130,9 +132,9 @@
         self.finalChecks()
 
         # Do scheduling tasks
-        response = (yield self.generateSchedulingResponse())
+        result = (yield self.generateSchedulingResponse())
 
-        returnValue(response)
+        returnValue(result)
 
     def loadOriginatorFromRequestHeaders(self):
         # Must have Originator header
@@ -319,7 +321,7 @@
             yield self.generateIMIPSchedulingResponses(imip_recipients, responses, freebusy)
     
         # Return with final response if we are done
-        returnValue(responses.response())
+        returnValue(responses)
     
     def generateLocalSchedulingResponses(self, recipients, responses, freebusy):
         """
@@ -363,7 +365,7 @@
 
     def checkAuthorization(self):
         # Must have an authenticated user
-        if self.resource.currentPrincipal(self.request) == davxml.Principal(davxml.Unauthenticated()):
+        if not self.internal_request and self.resource.currentPrincipal(self.request) == davxml.Principal(davxml.Unauthenticated()):
             log.err("Unauthenticated originators not allowed: %s" % (self.originator,))
             raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
 
@@ -385,11 +387,13 @@
                 log.err("Could not find inbox for originator: %s" % (self.originator,))
                 raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
         
-            # Verify that Originator matches the authenticated user.
-            authn_principal = self.resource.currentPrincipal(self.request)
-            if davxml.Principal(davxml.HRef(originatorPrincipal.principalURL())) != authn_principal:
-                log.err("Originator: %s does not match authorized user: %s" % (self.originator, authn_principal.children[0],))
-                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
+            # Verify that Originator matches the authenticated user, but not if this is a server
+            # generated request
+            if not self.internal_request:
+                authn_principal = self.resource.currentPrincipal(self.request)
+                if davxml.Principal(davxml.HRef(originatorPrincipal.principalURL())) != authn_principal:
+                    log.err("Originator: %s does not match authorized user: %s" % (self.originator, authn_principal.children[0],))
+                    raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "originator-allowed")))
 
             self.originator = LocalCalendarUser(self.originator, originatorPrincipal)
 

Copied: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/test/test_implicit.py (from rev 2868, CalendarServer/branches/users/cdaboo/implicit-2805/twistedcaldav/scheduling/test/test_implicit.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/test/test_implicit.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/test/test_implicit.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -0,0 +1,762 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from twistedcaldav.scheduling.icaldiff import iCalDiff
+
+from twistedcaldav.ical import Component
+import twistedcaldav.test.util
+from twistedcaldav.scheduling.implicit import ImplicitScheduler
+from dateutil.tz import tzutc
+import datetime
+
+class Implicit (twistedcaldav.test.util.TestCase):
+    """
+    iCalendar support tests
+    """
+
+    def test_removed_attendees(self):
+        
+        data = (
+            (
+                "#1.1 Simple component, no change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (),
+            ),
+            (
+                "#1.2 Simple component, one removal",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (("mailto:user2 at example.com", None),),
+            ),
+            (
+                "#1.3 Simple component, two removals",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user2 at example.com", None),
+                    ("mailto:user3 at example.com", None),
+                ),
+            ),
+            (
+                "#2.1 Simple recurring component, two removals",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user2 at example.com", None),
+                    ("mailto:user3 at example.com", None),
+                ),
+            ),
+            (
+                "#2.2 Simple recurring component, add exdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+EXDATE:20080801T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+            (
+                "#2.3 Simple recurring component, add multiple comma exdates",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+EXDATE:20080801T120000Z,20080901T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user1 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user2 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+            (
+                "#2.3 Simple recurring component, add multiple comma/property exdates",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+EXDATE:20080801T120000Z,20080901T120000Z
+EXDATE:20081201T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user1 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user2 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 9, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user1 at example.com", datetime.datetime(2008, 12, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user2 at example.com", datetime.datetime(2008, 12, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 12, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+            (
+                "#3.1 Complex recurring component with same attendees, no change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (),
+            ),
+            (
+                "#3.2 Complex recurring component with same attendees, change master/override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user3 at example.com", None),
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+            (
+                "#3.3 Complex recurring component with same attendees, change override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+            (
+                "#3.4 Complex recurring component with same attendees, change master",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user3 at example.com", None),
+                ),
+            ),
+            (
+                "#3.5 Complex recurring component with same attendees, remove override - no exdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+END:VCALENDAR
+""",
+                (),
+            ),
+            (
+                "#3.6 Complex recurring component with same attendees, remove override - exdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+EXDATE:20080801T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user3 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+            (
+                "#4.1 Complex recurring component with different attendees, change master/override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user4 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user3 at example.com", None),
+                    ("mailto:user4 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+            (
+                "#4.2 Complex recurring component with different attendees, remove override - no exdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user4 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user4 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+            (
+                "#4.3 Complex recurring component with different attendees, remove override - exdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user4 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+RRULE:FREQ=MONTHLY
+EXDATE:20080801T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    ("mailto:user1 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user2 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                    ("mailto:user4 at example.com", datetime.datetime(2008, 8, 1, 12, 0, 0, tzinfo=tzutc())),
+                ),
+            ),
+        )
+
+        for description, calendar1, calendar2, result in data:
+            scheduler = ImplicitScheduler()
+            scheduler.oldcalendar = Component.fromString(calendar1)
+            scheduler.calendar = Component.fromString(calendar2)
+            scheduler.extractCalendarData()
+            scheduler.findRemovedAttendees()
+#            if not description.startswith("#4.3"):
+#                continue
+#            print description
+#            print scheduler.cancelledAttendees
+#            print set(result)
+            self.assertEqual(scheduler.cancelledAttendees, set(result), msg=description)
+

Copied: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/test/test_itip.py (from rev 2868, CalendarServer/branches/users/cdaboo/implicit-2805/twistedcaldav/scheduling/test/test_itip.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/test/test_itip.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/scheduling/test/test_itip.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -0,0 +1,1075 @@
+##
+# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from dateutil.tz import tzutc
+from twistedcaldav.ical import Component
+from twistedcaldav.scheduling.itip import iTipProcessing, iTipGenerator
+import datetime
+import os
+import twistedcaldav.test.util
+
+class iTIPProcessing (twistedcaldav.test.util.TestCase):
+    """
+    iCalendar support tests
+    """
+
+    def test_update_attendee_partstat(self):
+        
+        data = (
+            (
+                "#1.1 Simple component, accepted",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "#1.2 Simple component, accepted",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "#1.3 Simple component, no change",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "#2.1 Recurring component, change master/override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ATTENDEE;PARTSTAT=DECLINED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "#2.2 Recurring component, change master only",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "#2.3 Recurring component, change override only",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ATTENDEE;PARTSTAT=DECLINED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "#3.1 Recurring component, change master/override, new override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080901T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ATTENDEE;PARTSTAT=DECLINED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080901T120000Z
+DTSTART:20080901T120000Z
+DTEND:20080901T130000Z
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "#3.2 Recurring component, change master, new override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080901T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080901T120000Z
+DTSTART:20080901T120000Z
+DTEND:20080901T130000Z
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            (
+                "#3.3 Recurring component, change override, new override",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080901T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=MONTHLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080801T120000Z
+DTSTART:20080801T123000Z
+DTEND:20080801T133000Z
+ATTENDEE;PARTSTAT=DECLINED:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080901T120000Z
+DTSTART:20080901T120000Z
+DTEND:20080901T130000Z
+ATTENDEE;PARTSTAT=TENTATIVE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+        )
+
+        for description, calendar_txt, itipmsg_txt, result in data:
+            calendar = Component.fromString(calendar_txt)
+            itipmsg = Component.fromString(itipmsg_txt)
+            iTipProcessing.processReply(itipmsg, calendar)
+#            if not description.startswith("#3.1"):
+#                continue
+#            print description
+#            print str(calendar)
+#            print str(result)
+            self.assertEqual(str(calendar).replace("\r", ""), str(result), msg=description)
+
+class iTIPGenerator (twistedcaldav.test.util.TestCase):
+    """
+    iCalendar support tests
+    """
+    data_dir = os.path.join(os.path.dirname(__file__), "data")
+
+    def test_request(self):
+        
+        data = (
+            # Simple component, no Attendees - no filtering
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                ()
+            ),
+
+            # Simple component, no Attendees - filtering
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+END:VCALENDAR
+""",
+                ("mailto:user01 at example.com",)
+            ),
+
+            # Simple component, with one attendee - filtering match
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user2 at example.com",)
+            ),
+
+            # Simple component, with one attendee - no filtering match
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-4
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+END:VCALENDAR
+""",
+                ("mailto:user3 at example.com",)
+            ),
+
+            # Recurring component with one instance, each with one attendee - filtering match
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user2 at example.com",)
+            ),
+
+            # Recurring component with one instance, each with one attendee - no filtering match
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-4
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+END:VCALENDAR
+""",
+                ("mailto:user3 at example.com",)
+            ),        
+
+            # Recurring component with one instance, master with one attendee, instance without attendee - filtering match
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+EXDATE:20081114T000000Z
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user2 at example.com",)
+            ),
+
+            # Recurring component with one instance, master with one attendee, instance without attendee - no filtering match
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-4
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+END:VCALENDAR
+""",
+                ("mailto:user3 at example.com",)
+            ),
+
+            # Recurring component with one instance, master without attendee, instance with attendee - filtering match
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user2 at example.com",)
+            ),
+
+            # Recurring component with one instance, master without attendee, instance with attendee - no filtering match
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-4
+DTSTART:20071114T000000Z
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+END:VCALENDAR
+""",
+                ("mailto:user3 at example.com",)
+            ),
+        )
+        
+        for original, filtered, attendees in data:
+            component = Component.fromString(original)
+            itipped = iTipGenerator.generateAttendeeRequest(component, attendees)
+            self.assertEqual(filtered, str(itipped).replace("\r", ""))
+
+    def test_cancel(self):
+        
+        data = (
+            # Simple component, with two attendees - cancel one
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:CANCEL
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user2 at example.com",),
+                (None,),
+            ),
+
+            # Simple component, with two attendees - cancel two
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:CANCEL
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2
+ATTENDEE:mailto:user3 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user3 at example.com", "mailto:user2 at example.com",),
+                (None,)
+            ),
+
+            # Recurring component with no instance, one attendee - cancel instance
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:CANCEL
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+RECURRENCE-ID:20081114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user2 at example.com",),
+                (datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc()), ),
+            ),
+
+            # Recurring component with one instance, each with one attendee - cancel instance
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-4
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-4
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:CANCEL
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-4
+RECURRENCE-ID:20081114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user2 at example.com",),
+                (datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc()), ),
+            ),
+
+            # Recurring component with one instance, each with one attendee - cancel master
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-5
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-5
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:CANCEL
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-5
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+                ("mailto:user2 at example.com",),
+                (None, ),
+            ),
+        )
+        
+        for original, filtered, attendees, instances in data:
+            component = Component.fromString(original)
+            itipped = iTipGenerator.generateCancel(component, attendees, instances)
+            itipped = str(itipped).replace("\r", "")
+            itipped = "".join([line for line in itipped.splitlines(True) if not line.startswith("DTSTAMP:")])
+            self.assertEqual(filtered, itipped)

Modified: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/test/test_icalendar.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/test/test_icalendar.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -21,7 +21,7 @@
 from twisted.trial.unittest import SkipTest
 
 from twistedcaldav.ical import Component, parse_date, parse_datetime,\
-    parse_date_or_datetime, parse_duration
+    parse_date_or_datetime, parse_duration, Property
 import twistedcaldav.test.util
 
 from vobject.icalendar import utc
@@ -440,6 +440,213 @@
             component = Component.fromString(caldata)
             self.assertEqual(component.getAttendeesByInstance(), result)
 
+    def test_set_parameter_value(self):
+        data = (
+            # ATTENDEE - no existing parameter
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;SCHEDULE-STATUS="2.0;OK":mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    "SCHEDULE-STATUS",
+                    "2.0;OK",
+                    "ATTENDEE",
+                    "mailto:user02 at example.com",
+                ),
+            ),
+            # ATTENDEE - existing parameter
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;SCHEDULE-STATUS="5.0;BAD":mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;SCHEDULE-STATUS="2.0;OK":mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    "SCHEDULE-STATUS",
+                    "2.0;OK",
+                    "ATTENDEE",
+                    "mailto:user02 at example.com",
+                ),
+            ),
+            # ORGANIZER - no existing parameter
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+ORGANIZER;SCHEDULE-STATUS="2.0;OK":mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    "SCHEDULE-STATUS",
+                    "2.0;OK",
+                    "ORGANIZER",
+                    "mailto:user01 at example.com",
+                ),
+            ),
+            # ORGANIZER - existing parameter
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+ORGANIZER;SCHEDULE-STATUS="5.0;BAD":mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE:mailto:user02 at example.com
+ORGANIZER;SCHEDULE-STATUS="2.0;OK":mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    "SCHEDULE-STATUS",
+                    "2.0;OK",
+                    "ORGANIZER",
+                    "mailto:user01 at example.com",
+                ),
+            ),
+        )
+
+        for original, result, args in data:
+            component = Component.fromString(original)
+            component.setParameterToValueForPropertyWithValue(*args)
+            self.assertEqual(result, str(component).replace("\r", ""))        
+
+    def test_add_property(self):
+        data = (
+            # Simple component
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+REQUEST-STATUS:2.0\;Success
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+            # Complex component
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T020000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+REQUEST-STATUS:2.0\;Success
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T020000Z
+REQUEST-STATUS:2.0\;Success
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+        )
+
+        for original, result in data:
+            component = Component.fromString(original)
+            component.addPropertyToAllComponents(Property("REQUEST-STATUS", "2.0;Success"))
+            self.assertEqual(result, str(component).replace("\r", ""))        
+
     def test_attendees_views(self):
         
         data = (

Deleted: CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/test/test_itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/test/test_itip.py	2008-08-26 03:18:47 UTC (rev 2868)
+++ CalendarServer/branches/users/cdaboo/implicit-2799/twistedcaldav/test/test_itip.py	2008-08-26 04:00:56 UTC (rev 2869)
@@ -1,515 +0,0 @@
-##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twistedcaldav.ical import Component
-from twistedcaldav.itip import iTipGenerator
-import twistedcaldav.test.util
-from dateutil.tz import tzutc
-import datetime
-import os
-
-class iTIPGenerator (twistedcaldav.test.util.TestCase):
-    """
-    iCalendar support tests
-    """
-    data_dir = os.path.join(os.path.dirname(__file__), "data")
-
-    def test_request(self):
-        
-        data = (
-            # Simple component, no Attendees - no filtering
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1
-DTSTART:20071114T000000Z
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1
-DTSTART:20071114T000000Z
-END:VEVENT
-END:VCALENDAR
-""",
-                ()
-            ),
-
-            # Simple component, no Attendees - filtering
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2
-DTSTART:20071114T000000Z
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-END:VCALENDAR
-""",
-                ("mailto:user01 at example.com",)
-            ),
-
-            # Simple component, with one attendee - filtering match
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user2 at example.com",)
-            ),
-
-            # Simple component, with one attendee - no filtering match
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-4
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-END:VCALENDAR
-""",
-                ("mailto:user3 at example.com",)
-            ),
-
-            # Recurring component with one instance, each with one attendee - filtering match
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user2 at example.com",)
-            ),
-
-            # Recurring component with one instance, each with one attendee - no filtering match
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-4
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-END:VCALENDAR
-""",
-                ("mailto:user3 at example.com",)
-            ),        
-
-            # Recurring component with one instance, master with one attendee, instance without attendee - filtering match
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-EXDATE:20081114T000000Z
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user2 at example.com",)
-            ),
-
-            # Recurring component with one instance, master with one attendee, instance without attendee - no filtering match
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-4
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-END:VCALENDAR
-""",
-                ("mailto:user3 at example.com",)
-            ),
-
-            # Recurring component with one instance, master without attendee, instance with attendee - filtering match
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-DTSTART:20071114T000000Z
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user2 at example.com",)
-            ),
-
-            # Recurring component with one instance, master without attendee, instance with attendee - no filtering match
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-4
-DTSTART:20071114T000000Z
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-END:VCALENDAR
-""",
-                ("mailto:user3 at example.com",)
-            ),
-        )
-        
-        for original, filtered, attendees in data:
-            component = Component.fromString(original)
-            itipped = iTipGenerator.generateAttendeeRequest(component, attendees)
-            self.assertEqual(filtered, str(itipped).replace("\r", ""))
-
-    def test_cancel(self):
-        
-        data = (
-            # Simple component, with two attendees - cancel one
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:CANCEL
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-1
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-SEQUENCE:1
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user2 at example.com",),
-                (None,),
-            ),
-
-            # Simple component, with two attendees - cancel two
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-ORGANIZER:mailto:user1 at example.com
-SEQUENCE:1
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:CANCEL
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-2
-ATTENDEE:mailto:user3 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-SEQUENCE:2
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user3 at example.com", "mailto:user2 at example.com",),
-                (None,)
-            ),
-
-            # Recurring component with no instance, one attendee - cancel instance
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:CANCEL
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-3
-RECURRENCE-ID:20081114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-SEQUENCE:1
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user2 at example.com",),
-                (datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc()), ),
-            ),
-
-            # Recurring component with one instance, each with one attendee - cancel instance
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-4
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-4
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:CANCEL
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-4
-RECURRENCE-ID:20081114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-SEQUENCE:1
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user2 at example.com",),
-                (datetime.datetime(2008, 11, 14, 0, 0, tzinfo=tzutc()), ),
-            ),
-
-            # Recurring component with one instance, each with one attendee - cancel master
-            (
-                """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-5
-DTSTART:20071114T000000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-RRULE:FREQ=YEARLY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890-5
-RECURRENCE-ID:20081114T000000Z
-DTSTART:20071114T010000Z
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
-                """BEGIN:VCALENDAR
-VERSION:2.0
-METHOD:CANCEL
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890-5
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER:mailto:user1 at example.com
-SEQUENCE:1
-END:VEVENT
-END:VCALENDAR
-""",
-                ("mailto:user2 at example.com",),
-                (None, ),
-            ),
-        )
-        
-        for original, filtered, attendees, instances in data:
-            component = Component.fromString(original)
-            itipped = iTipGenerator.generateCancel(component, attendees, instances)
-            itipped = str(itipped).replace("\r", "")
-            itipped = "".join([line for line in itipped.splitlines(True) if not line.startswith("DTSTAMP:")])
-            self.assertEqual(filtered, itipped)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080825/6ac4a101/attachment-0001.html 


More information about the calendarserver-changes mailing list