[CalendarServer-changes] [9931] CalendarServer/branches/release/CalendarServer-4.2-dev

source_changes at macosforge.org source_changes at macosforge.org
Thu Oct 11 14:14:52 PDT 2012


Revision: 9931
          http://trac.calendarserver.org//changeset/9931
Author:   cdaboo at apple.com
Date:     2012-10-11 14:14:51 -0700 (Thu, 11 Oct 2012)
Log Message:
-----------
Pull-up r9925 from trunk. Also switch to dedicated CDT branch.

Revision Links:
--------------
    http://trac.calendarserver.org//changeset/9925

Modified Paths:
--------------
    CalendarServer/branches/release/CalendarServer-4.2-dev/support/build.sh
    CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/itip.py

Property Changed:
----------------
    CalendarServer/branches/release/CalendarServer-4.2-dev/


Property changes on: CalendarServer/branches/release/CalendarServer-4.2-dev
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:9867,9870,9876,9895,9899,9901,9904,9928
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:9867,9870,9876,9895,9899,9901,9904,9925,9928

Modified: CalendarServer/branches/release/CalendarServer-4.2-dev/support/build.sh
===================================================================
--- CalendarServer/branches/release/CalendarServer-4.2-dev/support/build.sh	2012-10-11 21:11:09 UTC (rev 9930)
+++ CalendarServer/branches/release/CalendarServer-4.2-dev/support/build.sh	2012-10-11 21:14:51 UTC (rev 9931)
@@ -803,7 +803,7 @@
     "${pypi}/s/${n}/${p}.tar.gz";
 
   svn_get "CalDAVTester" "${top}/CalDAVTester" \
-      "${svn_uri_base}/CalDAVTester/trunk" 9841;
+      "${svn_uri_base}/CalDAVTester/branches/release/CalDAVTester-4.2-dev" HEAD;
 
   local v="0.3";
   local n="pydoctor";

Modified: CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/icaldiff.py	2012-10-11 21:11:09 UTC (rev 9930)
+++ CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/icaldiff.py	2012-10-11 21:14:51 UTC (rev 9931)
@@ -36,26 +36,27 @@
 log = Logger()
 
 class iCalDiff(object):
-    
+
     def __init__(self, oldcalendar, newcalendar, smart_merge):
         """
-        
+
         @param oldcalendar:
         @type oldcalendar:
         @param newcalendar:
         @type newcalendar:
         """
-        
+
         self.oldcalendar = oldcalendar
         self.newcalendar = newcalendar
         self.smart_merge = smart_merge
-    
+
+
     def organizerDiff(self):
         """
         Diff the two calendars looking for changes that should trigger implicit scheduling if
         changed by an organizer. Basically any change except for anything related to a VALARM.
         """
-        
+
         # If smart merge is needed we have to do this before trying the diff
         if self.smart_merge:
             log.debug("organizerDiff: doing smart Organizer diff/merge")
@@ -76,7 +77,7 @@
             calendar.removePropertyParameters("ATTENDEE", ("RSVP", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
             calendar.normalizeAll()
             return calendar
-        
+
         # Normalize components for comparison
         oldcalendar_norm = duplicateAndNormalize(self.oldcalendar)
         newcalendar_norm = duplicateAndNormalize(self.newcalendar)
@@ -84,6 +85,7 @@
         result = oldcalendar_norm == newcalendar_norm
         return result
 
+
     def _organizerMerge(self):
         """
         Merge changes to ATTENDEE properties in oldcalendar into newcalendar.
@@ -91,20 +93,21 @@
         organizer = normalizeCUAddr(self.newcalendar.masterComponent().propertyValue("ORGANIZER"))
         self._doSmartMerge(organizer, True)
 
+
     def _doSmartMerge(self, ignore_attendee, is_organizer):
         """
         Merge changes to ATTENDEE properties in oldcalendar into newcalendar.
         """
-        
+
         old_master = self.oldcalendar.masterComponent()
         new_master = self.newcalendar.masterComponent()
-        
+
         # Do master merge first
         self._tryComponentMerge(old_master, new_master, ignore_attendee, is_organizer)
 
         # New check the matching components
         for old_component in self.oldcalendar.subcomponents():
-            
+
             # Make sure we have an appropriate component
             if old_component.name() == "VTIMEZONE":
                 continue
@@ -118,7 +121,7 @@
                 # If the old component was cancelled ignore when an attendee
                 if not is_organizer and old_component.propertyValue("STATUS") == "CANCELLED":
                     continue
-                
+
                 # Determine whether the instance is still valid in the new calendar
                 new_component = self.newcalendar.deriveInstance(rid)
                 if new_component:
@@ -133,7 +136,7 @@
 
         # Check the new instances not in the old calendar
         for new_component in self.newcalendar.subcomponents():
-            
+
             # Make sure we have an appropriate component
             if new_component.name() == "VTIMEZONE":
                 continue
@@ -147,7 +150,7 @@
                 # If the new component is cancelled ignore when an attendee
                 if not is_organizer and new_component.propertyValue("STATUS") == "CANCELLED":
                     continue
-                
+
                 # Try to derive a new instance in the client and transfer attendee status
                 old_component = self.oldcalendar.deriveInstance(rid)
                 if old_component:
@@ -156,11 +159,13 @@
                 else:
                     # Ignore as we have no state for the new instance
                     pass
-    
+
+
     def _tryComponentMerge(self, old_comp, new_comp, ignore_attendee_value, is_organizer):
         if not is_organizer or not self._organizerChangePreventsMerge(old_comp, new_comp):
             self._transferAttendees(old_comp, new_comp, ignore_attendee_value)
 
+
     def _organizerChangePreventsMerge(self, old_comp, new_comp):
         """
         Check whether a change from an Organizer needs a re-schedule which means that any
@@ -175,7 +180,7 @@
         """
 
         props_to_test = ("DTSTART", "DTEND", "DURATION", "RRULE", "RDATE", "EXDATE", "RECURRENCE-ID",)
-        
+
         for prop in props_to_test:
             # Change => no merge
             old_props = set(old_comp.properties(prop))
@@ -184,7 +189,8 @@
                 return True
 
         return False
-    
+
+
     def _transferAttendees(self, old_comp, new_comp, ignore_attendee_value):
         """
         Transfer Attendee PARTSTAT from old component to new component.
@@ -206,7 +212,7 @@
             old_attendees[value] = attendee
 
         for new_attendee in new_comp.properties("ATTENDEE"):
-            
+
             # Whenever SCHEDULE-FORCE-SEND is explicitly set by the Organizer we assume the Organizer
             # is deliberately overwriting PARTSTAT
             if new_attendee.parameterValue("SCHEDULE-FORCE-SEND", "") == "REQUEST":
@@ -219,7 +225,8 @@
                 self._transferParameter(old_attendee, new_attendee, "PARTSTAT")
                 self._transferParameter(old_attendee, new_attendee, "RSVP")
                 self._transferParameter(old_attendee, new_attendee, "SCHEDULE-STATUS")
-    
+
+
     def _transferParameter(self, old_property, new_property, parameter):
         paramvalue = old_property.parameterValue(parameter)
         if paramvalue is None:
@@ -230,21 +237,22 @@
         else:
             new_property.setParameter(parameter, paramvalue)
 
+
     def attendeeMerge(self, attendee):
         """
         Merge the ATTENDEE specific changes with the organizer's view of the attendee's event.
         This will remove any attempt by the attendee to change things like the time or location.
-       
+
         @param attendee: the value of the ATTENDEE property corresponding to the attendee making the change
         @type attendee: C{str}
-        
+
         @return: C{tuple} of:
             C{bool} - change is allowed
             C{bool} - iTIP reply needs to be sent
             C{list} - list of RECURRENCE-IDs changed
             L{Component} - new calendar object to store
         """
-        
+
         self.attendee = normalizeCUAddr(attendee)
 
         returnCalendar = self.oldcalendar.duplicate()
@@ -252,7 +260,7 @@
 
         changeCausesReply = False
         changedRids = []
-        
+
         # First get uid/rid map of components
         def mapComponents(calendar):
             map = {}
@@ -269,7 +277,7 @@
                     cancelledRids.add(rid)
                 if rid is None:
                     master = component
-            
+
             # Normalize each master by adding any STATUS:CANCELLED components as EXDATEs
             exdates = None
             if master:
@@ -277,9 +285,9 @@
                 exdates = set()
                 for exdate in master.properties("EXDATE"):
                     exdates.update([value.getValue().duplicate().adjustToUTC() for value in exdate.value()])
-               
+
             return exdates, map, master
-        
+
         exdatesold, mapold, masterold = mapComponents(self.oldcalendar)
         setold = set(mapold.keys())
         exdatesnew, mapnew, masternew = mapComponents(self.newcalendar)
@@ -303,18 +311,18 @@
                     return False, False, (), None
             else:
                 componentold = self.oldcalendar.overriddenComponent(masternewStart)
-            
+
             # Take the recurrence ID from component1 and fix map2/set2
             keynew = (masternew.name(), masternew.propertyValue("UID"), None)
             componentnew = mapnew[keynew]
             del mapnew[keynew]
-            
+
             ridold = componentold.getRecurrenceIDUTC()
             newkeynew = (masternew.name(), masternew.propertyValue("UID"), ridold)
             mapnew[newkeynew] = componentnew
             setnew.remove(keynew)
             setnew.add(newkeynew)
-    
+
         # All the components in oldcalendar must be in newcalendar unless they are CANCELLED
         for key in setold - setnew:
             _ignore_name, _ignore_uid, rid = key
@@ -327,7 +335,7 @@
                     if self._attendeeDecline(overridden):
                         changeCausesReply = True
                         changedRids.append(rid.getText() if rid else "")
-                        
+
                     # When a master component is present we keep the missing override in place but mark it as hidden.
                     # When no master is present we remove the override,
                     if exdatesnew is not None:
@@ -338,7 +346,7 @@
                     # We used to generate a 403 here - but instead we now ignore this error and let the server data
                     # override the client
                     self._logDiffError("attendeeMerge: Missing uncancelled component from first calendar: %s" % (key,))
-            else: 
+            else:
                 if exdatesnew is not None and rid not in exdatesnew:
                     # We used to generate a 403 here - but instead we now ignore this error and let the server data
                     # override the client
@@ -350,11 +358,11 @@
                     if returnMaster:
                         # Use the original R-ID value so we preserve the timezone
                         original_rid = component.propertyValue("RECURRENCE-ID")
-                        returnMaster.addProperty(Property("EXDATE", [original_rid,]))
-        
+                        returnMaster.addProperty(Property("EXDATE", [original_rid, ]))
+
         # Derive a new component in the new calendar for each new one in setnew
         for key in setnew - setold:
-            
+
             # First check if the attendee's copy is cancelled and properly EXDATE'd
             # and skip it if so.
             _ignore_name, _ignore_uid, rid = key
@@ -387,7 +395,7 @@
                     returnCalendar.addComponent(newOverride)
 
         # So now returnCalendar has all the same components as set2. Check changes and do transfers.
-        
+
         # Make sure the same VCALENDAR properties match
         if not self._checkVCALENDARProperties(returnCalendar, self.newcalendar):
             # We used to generate a 403 here - but instead we now ignore this error and let the server data
@@ -402,7 +410,7 @@
             _ignore_name, _ignore_uid, rid = key
             serverData = returnCalendar.overriddenComponent(rid)
             clientData = mapnew[key]
-            
+
             allowed, reply = self._transferAttendeeData(serverData, clientData, declines)
             if not allowed:
                 # We used to generate a 403 here - but instead we now ignore this error and let the server data
@@ -422,7 +430,7 @@
                     if self._attendeeDecline(overridden):
                         changeCausesReply = True
                         changedRids.append(decline.getText() if decline else "")
-                        
+
                     # When a master component is present we keep the missing override in place but mark it as hidden.
                     # When no master is present we remove the override,
                     if exdatesnew is not None:
@@ -434,43 +442,45 @@
 
         return True, changeCausesReply, changedRids, returnCalendar
 
+
     def _checkVCALENDARProperties(self, serverData, clientData):
 
         self._transferProperty("X-CALENDARSERVER-ACCESS", serverData, clientData)
 
         # Get property differences in the VCALENDAR objects
         propdiff = set(serverData.properties()) ^ set(clientData.properties())
-        
+
         # Ignore certain properties
         ignored = ("PRODID", "CALSCALE",)
         propdiff = set([prop for prop in propdiff if prop.name() not in ignored])
-        
+
         result = len(propdiff) == 0
         if not result:
             log.debug("VCALENDAR properties differ: %s" % (propdiff,))
         return result
 
+
     def _transferAttendeeData(self, serverComponent, clientComponent, declines):
-        
+
         # We are skipping this check now - instead we let the server data override the broken client data
         # First check validity of date-time related properties and get removed components which are declines
         self._checkInvalidChanges(serverComponent, clientComponent, declines)
-        
+
         # Now look for items to transfer from one to the other.
         # We care about the ATTENDEE's PARTSTAT, TRANSP, VALARMS, X-APPLE-NEEDS-REPLY,
         # DTSTAMP, LAST-MODIFIED, COMPLETED, and ATTACH's referring to a dropbox
-        
+
         replyNeeded = False
 
         # ATTENDEE/PARTSTAT/RSVP
         serverAttendee = serverComponent.getAttendeeProperty((self.attendee,))
         clientAttendee = clientComponent.getAttendeeProperty((self.attendee,))
-        
+
         # Possible case where one ATTENDEE prop is missing - this happens with a "fake" master sometimes
         if serverAttendee is None or clientAttendee is None:
             log.err("ATTENDEE for user making an attendee change is missing: %s" % (self.attendee,))
             return False, False
-    
+
         if serverAttendee.parameterValue("PARTSTAT", "NEEDS-ACTION") != clientAttendee.parameterValue("PARTSTAT", "NEEDS-ACTION"):
             serverAttendee.setParameter("PARTSTAT", clientAttendee.parameterValue("PARTSTAT", "NEEDS-ACTION"))
             replyNeeded = True
@@ -490,7 +500,7 @@
         self._transferProperty("LAST-MODIFIED", serverComponent, clientComponent)
         self._transferProperty("X-APPLE-NEEDS-REPLY", serverComponent, clientComponent)
         self._transferProperty("COMPLETED", serverComponent, clientComponent)
-        
+
         # Dropbox - this now never returns false
         self._transferDropBoxData(serverComponent, clientComponent)
 
@@ -499,14 +509,15 @@
         for comp in clientComponent.subcomponents():
             if comp.name() == "VALARM":
                 serverComponent.addComponent(comp)
-        
+
         return True, replyNeeded
-    
+
+
     def _transferDropBoxData(self, serverComponent, clientComponent):
-        
+
         serverDropbox = serverComponent.propertyValue("X-APPLE-DROPBOX")
         clientDropbox = clientComponent.propertyValue("X-APPLE-DROPBOX")
-        
+
         # Handle four cases
         if not clientDropbox:
             return True
@@ -527,7 +538,7 @@
                     dataValue = attachment.value()
                     if dataValue.find(serverDropbox) != -1:
                         serverComponent.removeProperty(attachment)
-        
+
             # Copy new ATTACH's to server
             for attachment in tuple(clientComponent.properties("ATTACH")):
                 valueType = attachment.parameterValue("VALUE")
@@ -535,16 +546,17 @@
                     dataValue = attachment.value()
                     if dataValue.find(serverDropbox) != -1:
                         serverComponent.addProperty(attachment)
-                        
+
             return True
-        
+
+
     def _checkInvalidChanges(self, serverComponent, clientComponent, declines):
-        
+
         # Properties we care about: DTSTART, DTEND, DURATION, RRULE, RDATE, EXDATE
-        
+
         serverProps = self._getNormalizedDateTimeProperties(serverComponent)
         clientProps = self._getNormalizedDateTimeProperties(clientComponent)
-        
+
         # Need to special case EXDATEs as an Attendee can effectively DECLINE by adding an EXDATE
         if serverProps[:-1] != clientProps[:-1]:
             invalidChanges = []
@@ -562,30 +574,31 @@
             return True
         else:
             return True
-        
+
+
     def _getNormalizedDateTimeProperties(self, component):
-        
+
         # Basic time properties
         if component.name() in ("VEVENT", "VJOURNAL",):
             dtstart = component.getProperty("DTSTART")
             dtend = component.getProperty("DTEND")
             duration = component.getProperty("DURATION")
-            
+
             timeRange = PyCalendarPeriod(
-                start    = dtstart.value()  if dtstart  is not None else None,
-                end      = dtend.value()    if dtend    is not None else None,
-                duration = duration.value() if duration is not None else None,
+                start=dtstart.value()  if dtstart  is not None else None,
+                end=dtend.value()    if dtend    is not None else None,
+                duration=duration.value() if duration is not None else None,
             )
             newdue = None
-            
+
         elif component.name() == "VTODO":
             dtstart = component.getProperty("DTSTART")
             duration = component.getProperty("DURATION")
-            
+
             if dtstart or duration:
                 timeRange = PyCalendarPeriod(
-                    start    = dtstart.value()  if dtstart  is not None else None,
-                    duration = duration.value() if duration is not None else None,
+                    start=dtstart.value()  if dtstart  is not None else None,
+                    duration=duration.value() if duration is not None else None,
                 )
             else:
                 timeRange = PyCalendarPeriod()
@@ -593,7 +606,7 @@
             newdue = component.getProperty("DUE")
             if newdue is not None:
                 newdue = newdue.value().duplicate().adjustToUTC()
-            
+
         # Recurrence rules - we need to normalize the order of the value parts
         newrrules = set()
         rrules = component.properties("RRULE")
@@ -602,7 +615,7 @@
             indexedTokens.update([valuePart.split("=") for valuePart in rrule.value().getText().split(";")])
             sortedValue = ";".join(["%s=%s" % (key, value,) for key, value in sorted(indexedTokens.iteritems(), key=lambda x:x[0])])
             newrrules.add(sortedValue)
-        
+
         # RDATEs
         newrdates = set()
         rdates = component.properties("RDATE")
@@ -611,7 +624,7 @@
                 if isinstance(value, PyCalendarDateTime):
                     value = value.duplicate().adjustToUTC()
                 newrdates.add(value)
-        
+
         # EXDATEs
         newexdates = set()
         exdates = component.properties("EXDATE")
@@ -620,6 +633,7 @@
 
         return timeRange.getStart(), timeRange.getEnd(), newdue, newrrules, newrdates, newexdates
 
+
     def _transferProperty(self, propName, serverComponent, clientComponent):
 
         changed = False
@@ -640,7 +654,7 @@
 
         @param component:
         @type component:
-        
+
         @return: C{bool} indicating whether the PARTSTAT value was in fact changed
         """
         attendee = component.getAttendeeProperty((self.attendee,))
@@ -649,7 +663,7 @@
         if attendee is None:
             log.err("ATTENDEE for user making an attendee change is missing: %s" % (self.attendee,))
             return False
-    
+
         partstatChanged = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") != "DECLINED"
         attendee.setParameter("PARTSTAT", "DECLINED")
         prop = component.getProperty("X-APPLE-NEEDS-REPLY")
@@ -657,6 +671,7 @@
             component.removeProperty(prop)
         return partstatChanged
 
+
     def whatIsDifferent(self):
         """
         Compare the two calendar objects in their entirety and return a list of properties
@@ -674,7 +689,7 @@
                 rid = component.getRecurrenceIDUTC()
                 map[(name, uid, rid,)] = component
             return map
-        
+
         rids = {}
 
         oldmap = mapComponents(self.oldcalendar)
@@ -687,7 +702,7 @@
             component1 = oldmap[key]
             component2 = newmap[key]
             self._diffComponents(component1, component2, rids)
-        
+
         # Now verify that each additional component in oldset matches a derived component in newset
         for key in oldset - newset:
             oldcomponent = oldmap[key]
@@ -695,7 +710,7 @@
             if newcomponent is None:
                 continue
             self._diffComponents(oldcomponent, newcomponent, rids)
-        
+
         # Now verify that each additional component in oldset matches a derived component in newset
         for key in newset - oldset:
             oldcomponent = self.oldcalendar.deriveInstance(key[2])
@@ -703,9 +718,10 @@
                 continue
             newcomponent = newmap[key]
             self._diffComponents(oldcomponent, newcomponent, rids)
-        
+
         return rids
 
+
     def _componentDuplicateAndNormalize(self, comp):
         comp = comp.duplicate()
         comp.normalizePropertyValueLists("EXDATE")
@@ -717,14 +733,15 @@
         iTipGenerator.prepareSchedulingMessage(comp, reply=True)
         return comp
 
+
     def _diffComponents(self, comp1, comp2, rids):
-        
+
         assert isinstance(comp1, Component) and isinstance(comp2, Component)
-        
+
         if comp1.name() != comp2.name():
             log.debug("Component names are different: '%s' and '%s'" % (comp1.name(), comp2.name()))
             return
-        
+
         # Duplicate then normalize for comparison
         comp1 = self._componentDuplicateAndNormalize(comp1)
         comp2 = self._componentDuplicateAndNormalize(comp2)
@@ -732,7 +749,7 @@
         # Diff all the properties
         propdiff = set(comp1.properties()) ^ set(comp2.properties())
         addedChanges = False
-        
+
         propsChanged = {}
         for prop in propdiff:
             if prop.name() in (
@@ -755,11 +772,12 @@
             if "_TZID" in propsChanged[prop.name()]:
                 propsChanged[prop.name()].remove("_TZID")
                 propsChanged[prop.name()].add("TZID")
-        
+
         if addedChanges:
             rid = comp1.getRecurrenceIDUTC()
             rids[rid.getText() if rid is not None else ""] = propsChanged
 
+
     def _logDiffError(self, title):
 
         strcal1 = str(self.oldcalendar)
@@ -770,7 +788,7 @@
             fromfile='Existing Calendar Object',
             tofile='New Calendar Object',
         ))
-        
+
         logstr = """%s
 
 ------ Existing Calendar Data ------

Modified: CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/implicit.py	2012-10-11 21:11:09 UTC (rev 9930)
+++ CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/implicit.py	2012-10-11 21:14:51 UTC (rev 9931)
@@ -26,8 +26,8 @@
 from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
 from twistedcaldav.ical import Property
 from twistedcaldav.scheduling import addressmapping
-from twistedcaldav.scheduling.cuaddress import InvalidCalendarUser,\
-    LocalCalendarUser, PartitionedCalendarUser, OtherServerCalendarUser,\
+from twistedcaldav.scheduling.cuaddress import InvalidCalendarUser, \
+    LocalCalendarUser, PartitionedCalendarUser, OtherServerCalendarUser, \
     normalizeCUAddr
 from twistedcaldav.scheduling.icaldiff import iCalDiff
 from twistedcaldav.scheduling.itip import iTipGenerator, iTIPRequestStatus
@@ -48,19 +48,20 @@
 #
 
 class ImplicitScheduler(object):
-    
+
     # Return Status codes
-    STATUS_OK                       = 0
+    STATUS_OK = 0
     STATUS_ORPHANED_CANCELLED_EVENT = 1
-    STATUS_ORPHANED_EVENT           = 2
+    STATUS_ORPHANED_EVENT = 2
 
     def __init__(self):
-        
+
         self.return_status = ImplicitScheduler.STATUS_OK
 
+
     @inlineCallbacks
     def testImplicitSchedulingPUT(self, request, resource, resource_uri, calendar, internal_request=False):
-        
+
         self.request = request
         self.resource = resource
         self.calendar = calendar
@@ -78,12 +79,12 @@
             resource.isScheduleObject = None
             is_scheduling_object = (yield self.checkSchedulingObjectResource(resource))
             existing_type = "schedule" if is_scheduling_object else "calendar"
-            
+
         if existing_type == "calendar":
             self.action = "create" if new_type == "schedule" else "none"
         else:
             self.action = "modify" if new_type == "schedule" else "remove"
-                
+
         # Cannot create new resource with existing UID
         if not existing_resource or self.action == "create":
             yield self.hasCalendarResourceUIDSomewhereElse(resource, resource_uri, new_type)
@@ -95,7 +96,7 @@
             self.return_calendar = calendar
             self.calendar = (yield resource.iCalendarForUser(request))
             yield self.checkImplicitState()
-        
+
         # Once we have collected sufficient information from the calendar data, check validity of organizer and attendees
         self.checkValidOrganizer()
 
@@ -119,13 +120,13 @@
                 (caldav_namespace, "organizer-allowed"),
                 "Organizer cannot schedule without a master component.",
             ))
-            
 
         returnValue((self.action != "none", new_type == "schedule",))
 
+
     @inlineCallbacks
     def testImplicitSchedulingMOVE(self, request, srcresource, srccal, src_uri, destresource, destcal, dest_uri, calendar, internal_request=False):
-        
+
         self.request = request
         self.resource = destresource
         self.calendar = calendar
@@ -158,9 +159,10 @@
 
         returnValue((self.action != "none", new_type == "schedule",))
 
+
     @inlineCallbacks
     def testImplicitSchedulingCOPY(self, request, srcresource, srccal, src_uri, destresource, destcal, dest_uri, calendar, internal_request=False):
-        
+
         self.request = request
         self.resource = destresource
         self.calendar = calendar
@@ -191,9 +193,10 @@
 
         returnValue((self.action != "none", src_is_implicit,))
 
+
     @inlineCallbacks
     def testImplicitSchedulingDELETE(self, request, resource, calendar, internal_request=False):
-        
+
         self.request = request
         self.resource = resource
         self.calendar = calendar
@@ -207,11 +210,12 @@
 
         returnValue((self.action != "none", False,))
 
+
     def checkValidOrganizer(self):
         """
         Make sure the ORGANIZER is allowed to do certain scheduling operations.
         """
-        
+
         # Check to see whether the organizer principal is enabled for scheduling. If not, do not allow them
         # to create new scheduling resources.
         if self.action == "create":
@@ -223,9 +227,10 @@
                     "Organizer cannot schedule",
                 ))
 
+
     @inlineCallbacks
     def checkSchedulingObjectResource(self, resource):
-        
+
         if resource and resource.exists():
             implicit = resource.isScheduleObject
             if implicit is not None:
@@ -238,12 +243,13 @@
                 except ValueError:
                     # We have different ORGANIZERs in the same iCalendar object - this is an error
                     returnValue(False)
-                    
+
                 # Any ORGANIZER => a scheduling object resource
                 returnValue(organizer is not None)
 
         returnValue(False)
-        
+
+
     @inlineCallbacks
     def checkImplicitState(self):
         # Get some useful information from the calendar
@@ -265,6 +271,7 @@
 
         returnValue(self.state is not None)
 
+
     @inlineCallbacks
     def doImplicitScheduling(self, do_smart_merge=False):
         """
@@ -275,7 +282,7 @@
         @return: a new calendar object modified with scheduling information,
             or C{None} if nothing happened or C{int} if some other state occurs
         """
-        
+
         # Setup some parameters
         self.do_smart_merge = do_smart_merge
         self.except_attendees = ()
@@ -296,6 +303,7 @@
         else:
             returnValue(self.return_calendar if hasattr(self, "return_calendar") else self.calendar)
 
+
     @inlineCallbacks
     def refreshAllAttendeesExceptSome(self, request, resource, except_attendees=(), only_attendees=None):
         """
@@ -315,7 +323,6 @@
         self.changed_rids = None
         self.reinvites = None
 
-        
         # Get some useful information from the calendar
         yield self.extractCalendarData()
         self.organizerPrincipal = self.resource.principalForCalendarUserAddress(self.organizer)
@@ -324,14 +331,14 @@
         # Originator is the organizer in this case
         self.originatorPrincipal = self.organizerPrincipal
         self.originator = self.organizer
-        
+
         # We want to suppress chatty iMIP messages when other attendees reply
         self.request.suppressRefresh = False
 
         for attendee in self.calendar.getAllAttendeeProperties():
             if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION").upper() == "NEEDS-ACTION":
                 self.request.suppressRefresh = True
-        
+
         if hasattr(self.request, "doing_attendee_refresh"):
             self.request.doing_attendee_refresh += 1
         else:
@@ -348,9 +355,10 @@
                 self.request.extendedLogItems = {}
             self.request.extendedLogItems["itip.refreshes"] = refreshCount
 
+
     @inlineCallbacks
     def sendAttendeeReply(self, request, resource, calendar, attendee):
-        
+
         self.request = request
         self.resource = resource
         self.calendar = calendar
@@ -360,20 +368,21 @@
         self.calendar_owner = None
         self.internal_request = True
         self.changed_rids = None
-        
+
         # Get some useful information from the calendar
-        yield self.extractCalendarData()        
+        yield self.extractCalendarData()
 
         self.originator = self.attendee = attendee.principal.canonicalCalendarUserAddress()
         self.attendeePrincipal = attendee.principal
-        
+
         result = (yield self.scheduleWithOrganizer())
 
         returnValue(result)
 
+
     @inlineCallbacks
     def extractCalendarData(self):
-        
+
         # Get the originator who is the owner of the calendar resource being modified
         self.originatorPrincipal = None
         self.originator = ""
@@ -401,17 +410,18 @@
                 (caldav_namespace, "single-organizer"),
                 "Only one organizer allowed in scheduling object resource",
             ))
-        
+
         # Get the ATTENDEEs
         self.attendeesByInstance = self.calendar.getAttendeesByInstance(True, onlyScheduleAgentServer=True)
         self.instances = set(self.calendar.getComponentInstances())
         self.attendees = set()
         for attendee, _ignore in self.attendeesByInstance:
             self.attendees.add(attendee)
-            
+
         # Some other useful things
         self.uid = self.calendar.resourceUID()
-    
+
+
     @inlineCallbacks
     def hasCalendarResourceUIDSomewhereElse(self, check_resource, check_uri, type):
         """
@@ -437,34 +447,36 @@
                 "Cannot duplicate scheduling object resource",
             ))
 
+
     @inlineCallbacks
     def isOrganizerScheduling(self):
         """
         Test whether this is a scheduling operation by an organizer
         """
-        
+
         # First must have organizer property
         if not self.organizer:
             returnValue(False)
-        
+
         # Organizer must map to a valid principal
         self.organizerPrincipal = self.resource.principalForCalendarUserAddress(self.organizer)
         self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
         if not self.organizerPrincipal:
             returnValue(False)
-        
+
         # Organizer must be the owner of the calendar resource
         if str(self.calendar_owner) != self.organizerPrincipal.principalURL():
             returnValue(False)
 
         returnValue(True)
 
+
     def isAttendeeScheduling(self):
-        
+
         # First must have organizer property
         if not self.organizer:
             return False
-        
+
         # Check to see whether any attendee is the owner
         for attendee in self.attendees:
             attendeePrincipal = self.resource.principalForCalendarUserAddress(attendee)
@@ -472,9 +484,10 @@
                 self.attendee = attendee
                 self.attendeePrincipal = attendeePrincipal
                 return True
-        
+
         return False
 
+
     @inlineCallbacks
     def doAccessControl(self, principal, is_organizer):
         """
@@ -486,21 +499,23 @@
         @param is_organizer:
         @type is_organizer:
         """
-        
+
         # Find outbox
         outboxURL = principal.scheduleOutboxURL()
         outbox = (yield self.request.locateResource(outboxURL))
         yield outbox.authorize(self.request, (caldavxml.ScheduleSend(),))
 
+
     def makeScheduler(self):
         """
         Convenience method which we can override in unit tests to make testing easier.
         """
         return CalDAVScheduler(self.request, self.resource)
 
+
     @inlineCallbacks
     def doImplicitOrganizer(self):
-        
+
         # Do access control
         if not self.internal_request:
             yield self.doAccessControl(self.organizerPrincipal, True)
@@ -510,7 +525,7 @@
         self.cancelledAttendees = ()
         self.reinvites = None
         self.needs_action_rids = None
-        
+
         self.needs_sequence_change = False
 
         # Check for a delete
@@ -521,7 +536,7 @@
 
             # Cancel all attendees
             self.cancelledAttendees = [(attendee, None) for attendee in self.attendees]
-            
+
             # CANCEL always bumps sequence
             self.needs_sequence_change = True
 
@@ -533,7 +548,7 @@
             self.oldAttendeesByInstance = self.oldcalendar.getAttendeesByInstance(True, onlyScheduleAgentServer=True)
             self.oldInstances = set(self.oldcalendar.getComponentInstances())
             self.coerceAttendeesPartstatOnModify()
-            
+
             # Don't allow any SEQUENCE to decrease
             if self.oldcalendar:
                 self.calendar.sequenceInSync(self.oldcalendar)
@@ -550,14 +565,14 @@
                     returnValue(None)
             else:
                 log.debug("Implicit - organizer '%s' is modifying UID: '%s'" % (self.organizer, self.uid))
-    
+
                 for rid in self.needs_action_rids:
                     comp = self.calendar.overriddenComponent(rid)
-            
+
                     for attendee in comp.getAllAttendeeProperties():
                         if attendee.hasParameter("PARTSTAT"):
                             cuaddr = attendee.value()
-                            
+
                             if cuaddr in self.organizerPrincipal.calendarUserAddresses():
                                 # If the attendee is the organizer then do not update
                                 # the PARTSTAT to NEEDS-ACTION.
@@ -569,7 +584,7 @@
                 # Check for removed attendees
                 if not recurrence_reschedule:
                     self.findRemovedAttendees()
-                    
+
                 # For now we always bump the sequence number on modifications because we cannot track DTSTAMP on
                 # the Attendee side. But we check the old and the new and only bump if the client did not already do it.
                 self.needs_sequence_change = self.calendar.needsiTIPSequenceChange(self.oldcalendar)
@@ -577,7 +592,7 @@
         elif self.action == "create":
             log.debug("Implicit - organizer '%s' is creating UID: '%s'" % (self.organizer, self.uid))
             self.coerceAttendeesPartstatOnCreate()
-            
+
         # Always set RSVP=TRUE for any NEEDS-ACTION
         for attendee in self.calendar.getAllAttendeeProperties():
             if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION").upper() == "NEEDS-ACTION":
@@ -587,7 +602,7 @@
             self.calendar.bumpiTIPInfo(oldcalendar=self.oldcalendar, doSequence=True)
 
         yield self.scheduleWithAttendees()
-        
+
         # Always clear SCHEDULE-FORCE-SEND from all attendees after scheduling
         for attendee in self.calendar.getAllAttendeeProperties():
             try:
@@ -595,8 +610,9 @@
             except KeyError:
                 pass
 
+
     def isOrganizerChangeInsignificant(self):
-        
+
         rids = None
         date_changed_rids = None
         reinvites = None
@@ -613,7 +629,7 @@
                 if "ORGANIZER" in props:
                     checkOrganizerValue = True
                 rids.add(rid)
-                
+
                 if any([testprop in props for testprop in (
                     "DTSTART",
                     "DTEND",
@@ -631,7 +647,7 @@
                         recurrence_reschedule = True
 
                     elif "RRULE" in props:
-                        
+
                         # Need to see if the RRULE change is a simple truncation or expansion - i.e. a change to
                         # COUNT or UNTIL only. If so we don't need to treat this as a complete re-schedule.
 
@@ -643,23 +659,23 @@
                         oldrrule = oldrrule[0].value() if len(oldrrule) else None
                         newrrule = tuple(self.calendar.masterComponent().properties("RRULE"))
                         newrrule = newrrule[0].value() if len(newrrule) else None
-                        
+
                         if newrrule is not None and oldrrule is not None:
-                            
+
                             # Normalize the rrules by removing COUNT/UNTIL and then compare
                             oldrrule = oldrrule.duplicate()
                             newrrule = newrrule.duplicate()
-                            
+
                             oldrrule.setUseUntil(False)
                             oldrrule.setUntil(None)
                             oldrrule.setUseCount(False)
                             oldrrule.setCount(0)
- 
+
                             newrrule.setUseUntil(False)
                             newrrule.setUntil(None)
                             newrrule.setUseCount(False)
                             newrrule.setCount(0)
-                            
+
                             # If they are equal we have a simple change - no overall reschedule
                             if newrrule == oldrrule:
                                 recurrence_reschedule = False
@@ -685,13 +701,14 @@
                     pass
 
         return no_change, rids, date_changed_rids, reinvites, recurrence_reschedule
-    
+
+
     def findRemovedAttendees(self):
         """
         Look for attendees that have been removed from any instances. Save those off
         as users that need to be sent a cancel.
         """
-        
+
         # Several possibilities for when CANCELs need to be sent:
         #
         # Remove ATTENDEE property
@@ -700,12 +717,12 @@
         # Remove RDATE
         # Truncate RRULE
         # Change RRULE
-        
+
         # TODO: the later three will be ignored for now.
 
         mappedOld = set(self.oldAttendeesByInstance)
         mappedNew = set(self.attendeesByInstance)
-        
+
         # Get missing instances
         removedInstances = self.oldInstances - self.instances
         addedInstances = self.instances - self.oldInstances
@@ -722,18 +739,18 @@
 
         # 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)
@@ -757,6 +774,7 @@
                 if (attendee, rid) not in mappedNew and rid not in oldexdates:
                     self.cancelledAttendees.add((attendee, rid))
 
+
     def coerceAttendeesPartstatOnCreate(self):
         """
         Make sure any attendees handled by the server start off with PARTSTAT=NEEDS-ACTION as
@@ -768,21 +786,22 @@
                 continue
             if attendee.parameterValue("SCHEDULE-AGENT", "SERVER").upper() == "SERVER" and attendee.hasParameter("PARTSTAT"):
                 attendee.setParameter("PARTSTAT", "NEEDS-ACTION")
-    
+
+
     def coerceAttendeesPartstatOnModify(self):
         """
         Make sure that the organizer does not change attendees' PARTSTAT to anything
         other than NEEDS-ACTION for those attendees handled by the server.
         """
-        
+
         # Get the set of Rids in each calendar
         newRids = set(self.calendar.getComponentInstances())
         oldRids = set(self.oldcalendar.getComponentInstances())
-        
+
         # Test/fix ones that are the same
         for rid in (newRids & oldRids):
             self.compareAttendeePartstats(self.oldcalendar.overriddenComponent(rid), self.calendar.overriddenComponent(rid))
-        
+
         # Test/fix ones added
         for rid in (newRids - oldRids):
             # Compare the new one to the old master
@@ -790,8 +809,8 @@
 
         # For removals, we ignore ones that are no longer valid
         valid_old_rids = self.calendar.validInstances(oldRids - newRids)
-    
-        # Test/fix ones removed         
+
+        # Test/fix ones removed
         for rid in valid_old_rids:
             # Compare the old one to the new master
             # Note it is hard to recover from this state so raise instead
@@ -800,16 +819,17 @@
                 self.calendar.overriddenComponent(None),
                 raiseOnMisMatch=True
             )
-        
+
+
     def compareAttendeePartstats(self, old_component, new_component, raiseOnMisMatch=False):
         """
         Compare two components, old and new, and make sure the Organizer has not changed the PARTSTATs
         in the new one to anything other than NEEDS-ACTION. If there is a change, undo it.
         """
-        
+
         old_attendees = dict([(normalizeCUAddr(attendee.value()), attendee) for attendee in old_component.getAllAttendeeProperties()])
         new_attendees = dict([(normalizeCUAddr(attendee.value()), attendee) for attendee in new_component.getAllAttendeeProperties()])
-        
+
         for cuaddr, newattendee in new_attendees.items():
             # Don't adjust ORGANIZER's ATTENDEE
             if newattendee.value() in self.organizerPrincipal.calendarUserAddresses():
@@ -828,12 +848,13 @@
                     else:
                         newattendee.setParameter("PARTSTAT", old_partstat)
 
+
     @inlineCallbacks
     def scheduleWithAttendees(self):
-        
+
         # First process cancelled attendees
         total = (yield self.processCancels())
-        
+
         # Process regular requests next
         if self.action in ("create", "modify",):
             total += (yield self.processRequests())
@@ -842,9 +863,10 @@
             self.request.extendedLogItems = {}
         self.request.extendedLogItems["itip.requests"] = total
 
+
     @inlineCallbacks
     def processCancels(self):
-        
+
         # TODO: a better policy here is to aggregate by attendees with the same set of instances
         # being cancelled, but for now we will do one scheduling message per attendee.
 
@@ -852,17 +874,17 @@
         aggregated = {}
         for attendee, rid in self.cancelledAttendees:
             aggregated.setdefault(attendee, []).append(rid)
-        
+
         count = 0
         for attendee, rids in aggregated.iteritems():
-            
+
             # Don't send message back to the ORGANIZER
             if attendee in self.organizerPrincipal.calendarUserAddresses():
                 continue
 
             # Generate an iTIP CANCEL message for this attendee, cancelling
             # each instance or the whole
-            
+
             if None in rids:
                 # One big CANCEL will do
                 itipmsg = iTipGenerator.generateCancel(self.oldcalendar, (attendee,), None, self.action == "remove")
@@ -874,19 +896,20 @@
             if itipmsg:
                 # This is a local CALDAV scheduling operation.
                 scheduler = self.makeScheduler()
-        
+
                 # 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.originator, (attendee,), itipmsg, internal_request=True))
                 self.handleSchedulingResponse(response, True)
-                
+
                 count += 1
-            
+
         returnValue(count)
-            
+
+
     @inlineCallbacks
     def processRequests(self):
-        
+
         # TODO: a better policy here is to aggregate by attendees with the same set of instances
         # being requested, but for now we will do one scheduling message per attendee.
 
@@ -916,18 +939,19 @@
             if itipmsg is not None:
                 # This is a local CALDAV scheduling operation.
                 scheduler = self.makeScheduler()
-        
+
                 # 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.originator, (attendee,), itipmsg, internal_request=True))
                 self.handleSchedulingResponse(response, True)
-                
+
                 count += 1
-                
+
         returnValue(count)
 
+
     def handleSchedulingResponse(self, response, is_organizer):
-        
+
         # Map each recipient in the response to a status code
         responses = {}
         for item in response.responses:
@@ -935,7 +959,7 @@
             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",
@@ -943,6 +967,7 @@
                 "ATTENDEE" if is_organizer else "ORGANIZER",
                 recipient)
 
+
     @inlineCallbacks
     def doImplicitAttendee(self):
 
@@ -986,7 +1011,7 @@
                     ))
             else:
                 self.oldcalendar = None
-            
+
             # Get the ORGANIZER's current copy of the calendar object
             yield self.getOrganizersCopy()
             if self.organizer_calendar:
@@ -1008,7 +1033,7 @@
                 if not changeAllowed:
                     if self.calendar.hasPropertyValueInAllComponents(Property("STATUS", "CANCELLED")):
                         log.debug("Attendee '%s' is creating CANCELLED event for mismatched UID: '%s' - removing entire event" % (self.attendee, self.uid,))
-                        self.return_status = ImplicitScheduler.STATUS_ORPHANED_CANCELLED_EVENT
+                        self.return_status = ImplicitScheduler.STATUS_ORPHANED_EVENT
                         returnValue(None)
                     else:
                         log.error("Attendee '%s' is not allowed to make an unauthorized change to an organized event: UID:%s" % (self.attendeePrincipal, self.uid,))
@@ -1018,6 +1043,13 @@
                             "Attendee changes are not allowed",
                         ))
 
+                # Check that the return calendar actually has any components left - this can happen if a cancelled
+                # component is removed and replaced by another cancelled or invalid one
+                if self.calendar.mainType() is None:
+                    log.debug("Attendee '%s' is replacing CANCELLED event: '%s' - removing entire event" % (self.attendee, self.uid,))
+                    self.return_status = ImplicitScheduler.STATUS_ORPHANED_EVENT
+                    returnValue(None)
+
                 if not doITipReply:
                     log.debug("Implicit - attendee '%s' is updating UID: '%s' but change is not significant" % (self.attendee, self.uid))
                     returnValue(None)
@@ -1078,6 +1110,7 @@
                 log.debug("Implicit - attendee '%s' is updating UID without server scheduling: '%s'" % (self.attendee, self.uid))
                 # Nothing else to do
 
+
     @inlineCallbacks
     def doImplicitMissingAttendee(self):
 
@@ -1089,7 +1122,7 @@
             # We will allow the attendee to do anything in this case, but we will mark the organizer
             # with an schedule-status error and schedule-agent none
             log.debug("Missing attendee is allowed to update UID: '%s' with invalid organizer '%s'" % (self.uid, self.organizer))
-            
+
             # Make sure ORGANIZER is not changed if originally SCHEDULE-AGENT=SERVER
             if self.resource.exists():
                 self.oldcalendar = (yield self.resource.iCalendarForUser(self.request))
@@ -1108,6 +1141,7 @@
                 self.calendar.setParameterToValueForPropertyWithValue("SCHEDULE-AGENT", "NONE", "ORGANIZER", None)
                 self.calendar.setParameterToValueForPropertyWithValue("SCHEDULE-STATUS", iTIPRequestStatus.NO_USER_SUPPORT_CODE, "ORGANIZER", None)
 
+
     def checkOrganizerScheduleAgent(self):
 
         is_server = self.calendar.getOrganizerScheduleAgent()
@@ -1122,19 +1156,20 @@
             self.calendar.setParameterToValueForPropertyWithValue("SCHEDULE-AGENT", "NONE", "ORGANIZER", None)
             self.calendar.setParameterToValueForPropertyWithValue("SCHEDULE-STATUS", iTIPRequestStatus.NO_USER_SUPPORT_CODE, "ORGANIZER", None)
             is_server = False
-            
+
         return is_server
 
+
     @inlineCallbacks
     def getOrganizersCopy(self):
         """
         Get the Organizer's copy of the event being processed.
-        
+
         NB it is possible that the Organizer is not hosted on this server
         so the result here will be None. In that case we have to trust that
         the attendee does the right thing about changing the details in the event.
         """
-        
+
         self.organizer_calendar = None
         calendar_resource, _ignore_name, _ignore_collection, _ignore_uri = (yield getCalendarObjectForPrincipals(self.request, self.organizerPrincipal, self.uid))
         if calendar_resource:
@@ -1143,14 +1178,15 @@
             # For partitioning where the organizer is on a different node, we will assume that the attendee's copy
             # of the event is up to date and "authoritative". So we pretend that is the organizer copy
             self.organizer_calendar = self.oldcalendar
-        
+
+
     def isAttendeeChangeInsignificant(self):
         """
         Check whether the change is significant (PARTSTAT) or allowed
         (attendee can only change their property, alarms, TRANSP, and
         instances. Raise an exception if it is not allowed.
         """
-        
+
         oldcalendar = self.oldcalendar
         if oldcalendar is None:
             oldcalendar = self.organizer_calendar
@@ -1158,28 +1194,31 @@
         differ = iCalDiff(oldcalendar, self.calendar, self.do_smart_merge)
         return differ.attendeeMerge(self.attendee)
 
+
     def scheduleWithOrganizer(self, changedRids=None):
 
         if not hasattr(self.request, "extendedLogItems"):
             self.request.extendedLogItems = {}
         self.request.extendedLogItems["itip.reply"] = "reply"
-    
+
         itipmsg = iTipGenerator.generateAttendeeReply(self.calendar, self.attendee, changedRids=changedRids)
 
         # Send scheduling message
         return self.sendToOrganizer("REPLY", itipmsg)
 
+
     def scheduleCancelWithOrganizer(self):
 
         if not hasattr(self.request, "extendedLogItems"):
             self.request.extendedLogItems = {}
         self.request.extendedLogItems["itip.reply"] = "cancel"
-    
+
         itipmsg = iTipGenerator.generateAttendeeReply(self.calendar, self.attendee, force_decline=True)
 
         # Send scheduling message
         return self.sendToOrganizer("CANCEL", itipmsg)
 
+
     def sendToOrganizer(self, action, itipmsg):
 
         # Send scheduling message
@@ -1190,7 +1229,7 @@
         # Do the PUT processing
         def _gotResponse(response):
             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.originator, (self.organizer,), itipmsg, internal_request=True)
         d.addCallback(_gotResponse)

Modified: CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/itip.py	2012-10-11 21:11:09 UTC (rev 9930)
+++ CalendarServer/branches/release/CalendarServer-4.2-dev/twistedcaldav/scheduling/itip.py	2012-10-11 21:14:51 UTC (rev 9931)
@@ -30,7 +30,7 @@
 from twext.python.log import Logger
 
 from twistedcaldav.config import config
-from twistedcaldav.ical import Property, iCalendarProductID, Component,\
+from twistedcaldav.ical import Property, iCalendarProductID, Component, \
     ignoredComponents
 
 from pycalendar.datetime import PyCalendarDateTime
@@ -50,10 +50,10 @@
     def processNewRequest(itip_message, recipient=None, creating=False):
         """
         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"
@@ -65,7 +65,7 @@
 
         if recipient:
             iTipProcessing.addTranspForNeedsAction(calendar.subcomponents(), recipient)
-                
+
             # Check for incoming DECLINED
             if creating:
                 master = calendar.masterComponent()
@@ -81,23 +81,24 @@
                             calendar.removeComponent(component)
 
         return calendar
-        
+
+
     @staticmethod
     def processRequest(itip_message, calendar, recipient):
         """
         Process a METHOD=REQUEST. We need to merge per-attendee properties such as TRANPS, COMPLETED etc
         with the data coming from the organizer.
-        
+
         @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: a C{tuple} of:
             calendar object ready to save, or C{None} (request should be ignored)
             a C{set} of recurrences that changed, or C{None}
         """
-        
+
         # Check sequencing
         if not iTipProcessing.sequenceComparison(itip_message, calendar):
             # Ignore out of sequence message
@@ -126,10 +127,10 @@
             organizer_schedule_status = None
 
         if itip_message.masterComponent() is not None:
-            
+
             # Get a new calendar object first
             new_calendar = iTipProcessing.processNewRequest(itip_message, recipient)
-            
+
             # Copy over master alarms, comments
             master_component = new_calendar.masterComponent()
             for alarm in master_valarms:
@@ -140,16 +141,16 @@
                 master_component.replaceProperty(transp)
             for completed in completeds:
                 master_component.replaceProperty(completed)
-            if organizer_schedule_status: 
+            if organizer_schedule_status:
                 organizer = master_component.getProperty("ORGANIZER")
                 if organizer:
                     organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
-                
+
             # Now try to match recurrences in the new calendar
             for component in tuple(new_calendar.subcomponents()):
                 if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
                     iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, component, recipient)
-            
+
             # Now try to match recurrences from the old calendar
             for component in calendar.subcomponents():
                 if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
@@ -160,7 +161,7 @@
                         if new_component:
                             new_calendar.addComponent(new_component)
                             iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, new_component, recipient)
-            
+
             # Replace the entire object
             return new_calendar, rids
 
@@ -185,24 +186,25 @@
             # Write back the modified object
             return calendar, rids
 
+
     @staticmethod
     def processCancel(itip_message, calendar, autoprocessing=False):
         """
         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
             C{set}  : set of Recurrence-IDs for cancelled instances, or C{None} if all cancelled
         """
-        
+
         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"
 
@@ -235,32 +237,32 @@
         for component in itip_message.subcomponents():
             if component.name() == "VTIMEZONE":
                 continue
-        
+
             # Extract RECURRENCE-ID value from component
             rid = component.getRecurrenceIDUTC()
             rids.add(rid)
-            
+
             # Get the one that matches in the calendar
             overridden = calendar.overriddenComponent(rid)
-            
+
             if overridden:
                 # We are cancelling an overridden component.
 
                 if autoprocessing:
                     # Exclude the cancelled instance
                     exdates.append(component.getRecurrenceIDUTC())
-                
+
                     # Remove the existing component.
                     calendar.removeComponent(overridden)
                 else:
                     # Existing component is cancelled.
                     overridden.replaceProperty(Property("STATUS", "CANCELLED"))
                     newseq = component.propertyValue("SEQUENCE")
-                    overridden.replacePropertyInAllComponents(Property("SEQUENCE", newseq))
+                    overridden.replaceProperty(Property("SEQUENCE", newseq))
 
             elif calendar_master:
                 # We are trying to CANCEL a non-overridden instance.
-                
+
                 if autoprocessing:
                     # Exclude the cancelled instance
                     exdates.append(component.getRecurrenceIDUTC())
@@ -284,26 +286,27 @@
             return True, True, None
         else:
             return True, False, rids
-    
+
+
     @staticmethod
     def processReply(itip_message, calendar):
         """
         Process a METHOD=REPLY.
-        
+
         TODO: Yes, I am going to ignore RANGE= on RECURRENCE-ID for now...
         TODO: We have no way to track SEQUENCE/DTSTAMP on a per-attendee basis to correctly serialize out-of-order
               replies.
-        
+
         @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: a C{tuple} of:
             C{True} if processed, C{False} if scheduling message should be ignored
             C{tuple} of change info
         """
-        
+
         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"
 
@@ -326,7 +329,7 @@
         # Now do all overridden ones (sort by RECURRENCE-ID)
         sortedComponents = []
         for itip_component in itip_message.subcomponents():
-            
+
             # Make sure we have an appropriate component
             if itip_component.name() == "VTIMEZONE":
                 continue
@@ -334,9 +337,9 @@
             if rid is None:
                 continue
             sortedComponents.append((rid, itip_component,))
-            
-        sortedComponents.sort(key=lambda x:x[0])
-        
+
+        sortedComponents.sort(key=lambda x: x[0])
+
         for rid, itip_component in sortedComponents:
             # Find matching component in organizer's copy
             match_component = calendar.overriddenComponent(rid)
@@ -366,6 +369,7 @@
             log.error("ATTENDEE property in a REPLY must be the same in all components\n%s" % (str(itip_message),))
             return False, None
 
+
     @staticmethod
     def updateAttendeeData(from_component, to_component):
         """
@@ -377,7 +381,7 @@
         @param to_component:
         @type to_component:
         """
-        
+
         # Track what changed
         partstat_changed = False
         private_comment_changed = False
@@ -397,7 +401,7 @@
 
         attendee = attendees[0]
         partstat = attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
-        
+
         # Now find matching ATTENDEE in to_component
         existing_attendee = to_component.getAttendeeProperty((attendee.value(),))
         if existing_attendee:
@@ -405,7 +409,7 @@
             existing_attendee.setParameter("PARTSTAT", partstat)
             existing_attendee.setParameter("SCHEDULE-STATUS", reqstatus)
             partstat_changed = (oldpartstat != partstat)
-            
+
             # Always delete RSVP on PARTSTAT change
             if partstat_changed:
                 try:
@@ -418,7 +422,7 @@
                 # Look for X-CALENDARSERVER-PRIVATE-COMMENT property in iTIP component (State 1 in spec)
                 attendee_comment = tuple(from_component.properties("X-CALENDARSERVER-PRIVATE-COMMENT"))
                 attendee_comment = attendee_comment[0] if len(attendee_comment) else None
-                
+
                 # Look for matching X-CALENDARSERVER-ATTENDEE-COMMENT property in existing data (State 2 in spec)
                 private_comments = tuple(to_component.properties("X-CALENDARSERVER-ATTENDEE-COMMENT"))
                 for comment in private_comments:
@@ -431,64 +435,65 @@
             else:
                 attendee_comment = None
                 private_comment = None
-                
+
             # Now do update logic
             if attendee_comment is None and private_comment is None:
                 # Nothing to do
                 pass
- 
+
             elif attendee_comment is None and private_comment is not None:
                 # Remove all property parameters
                 private_comment.removeAllParameters()
-                
+
                 # Add default parameters
                 private_comment.setParameter("X-CALENDARSERVER-ATTENDEE-REF", attendee.value())
                 private_comment.setParameter("X-CALENDARSERVER-DTSTAMP", PyCalendarDateTime.getNowUTC().getText())
-                
+
                 # Set value empty
                 private_comment.setValue("")
-                
+
                 private_comment_changed = True
-                
+
             elif attendee_comment is not None and private_comment is None:
-                
+
                 # Add new property
                 private_comment = Property(
                     "X-CALENDARSERVER-ATTENDEE-COMMENT",
                     attendee_comment.value(),
-                    params = {
+                    params={
                         "X-CALENDARSERVER-ATTENDEE-REF": attendee.value(),
-                        "X-CALENDARSERVER-DTSTAMP":      PyCalendarDateTime.getNowUTC().getText(),
+                        "X-CALENDARSERVER-DTSTAMP": PyCalendarDateTime.getNowUTC().getText(),
                     }
                 )
                 to_component.addProperty(private_comment)
-                
+
                 private_comment_changed = True
-            
+
             else:
                 # Only change if different
                 if private_comment.value() != attendee_comment.value():
                     # Remove all property parameters
                     private_comment.removeAllParameters()
-                    
+
                     # Add default parameters
                     private_comment.setParameter("X-CALENDARSERVER-ATTENDEE-REF", attendee.value())
                     private_comment.setParameter("X-CALENDARSERVER-DTSTAMP", PyCalendarDateTime.getNowUTC().getText())
-                    
+
                     # Set new value
                     private_comment.setValue(attendee_comment.value())
-    
+
                     private_comment_changed = True
 
         return attendee.value(), partstat_changed, private_comment_changed
 
+
     @staticmethod
     def transferItems(from_calendar, master_valarms, private_comments, transps, completeds, organizer_schedule_status, to_component, recipient, remove_matched=False):
         """
         Transfer properties from a calendar to a component by first trying to match the component in the original calendar and
         use the properties from that, or use the values provided as arguments (which have been derived from the original calendar's
         master component).
-        
+
         @return: C{True} if an EXDATE match occurred requiring the incoming component to be removed.
         """
 
@@ -505,7 +510,7 @@
 
             organizer = matched.getProperty("ORGANIZER")
             organizer_schedule_status = organizer.parameterValue("SCHEDULE-STATUS", None) if organizer else None
-            if organizer_schedule_status: 
+            if organizer_schedule_status:
                 organizer = to_component.getProperty("ORGANIZER")
                 if organizer:
                     organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
@@ -513,7 +518,7 @@
             # Remove the old one
             if remove_matched:
                 from_calendar.removeComponent(matched)
-                
+
             # Check for incoming DECLINED
             attendee = to_component.getAttendeeProperty((recipient,))
             if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
@@ -526,20 +531,21 @@
             attendee = to_component.getAttendeeProperty((recipient,))
             if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "DECLINED":
                 return True
-                    
+
             # It is a new override - copy any valarms on the existing master component
             # into the new one.
             [to_component.addComponent(alarm) for alarm in master_valarms]
             [to_component.addProperty(comment) for comment in private_comments]
             [to_component.replaceProperty(transp) for transp in transps]
             [to_component.replaceProperty(completed) for completed in completeds]
-            if organizer_schedule_status: 
+            if organizer_schedule_status:
                 organizer = to_component.getProperty("ORGANIZER")
                 if organizer:
                     organizer.setParameter("SCHEDULE-STATUS", organizer_schedule_status)
-                
+
         return False
-    
+
+
     @staticmethod
     def addTranspForNeedsAction(components, recipient):
         # For each component where the ATTENDEE property of the recipient has PARTSTAT
@@ -551,20 +557,21 @@
             if attendee and attendee.parameterValue("PARTSTAT", "NEEDS-ACTION") == "NEEDS-ACTION":
                 component.replaceProperty(Property("TRANSP", "TRANSPARENT"))
 
+
     @staticmethod
     def sequenceComparison(itip, calendar):
         """
         Do appropriate itip message sequencing based by comparison with existing calendar data.
-        
+
         @return: C{True} if the itip message is new and should be processed, C{False}
             if no processing is needed
         @rtype: C{bool}
         """
-        
+
         # Master component comparison trumps all else
         itip_master = itip.masterComponent()
         cal_master = calendar.masterComponent()
-        
+
         # If master component exists, compare all in iTIP and update if any are new
         if cal_master:
             for itip_component in itip.subcomponents():
@@ -573,7 +580,7 @@
                 cal_component = calendar.overriddenComponent(itip_component.getRecurrenceIDUTC())
                 if cal_component is None:
                     cal_component = cal_master
-                    
+
                 # TODO: No DTSTAMP comparison because we do not track DTSTAMPs
                 # Treat components the same as meaning so an update - in theory no harm in doing that
                 if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
@@ -582,7 +589,7 @@
             return False
 
         elif itip_master:
-            
+
             # Do comparison of each appropriate component if any one is new, process the itip
             for cal_component in calendar.subcomponents():
                 if cal_component.name() in ignoredComponents:
@@ -595,14 +602,14 @@
                 # Treat components the same as meaning so an update - in theory no harm in doing that
                 if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
                     return True
-            
+
             return False
-    
+
         else:
             # Do comparison of each matching component if any one is new, process the entire itip.
             # There is a race condition here, similar to REPLY, where we could reinstate an instance
             # that has been removed. Not much we can do about it without additional tracking.
-            
+
             cal_rids = set()
             for cal_component in calendar.subcomponents():
                 if cal_component.name() in ignoredComponents:
@@ -613,7 +620,7 @@
                 if itip_component.name() in ignoredComponents:
                     continue
                 itip_rids.add(itip_component.getRecurrenceIDUTC())
-            
+
             # Compare ones that match
             for rid in cal_rids & itip_rids:
                 cal_component = calendar.overriddenComponent(rid)
@@ -623,24 +630,26 @@
                 # Treat components the same as meaning so an update - in theory no harm in doing that
                 if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
                     return True
-            
+
             # If there are others in one set and not the other - always process, else no process
             return len(cal_rids ^ itip_rids) > 0
-            
+
+
+
 class iTipGenerator(object):
     """
     This assumes that DTSTAMP and SEQUENCE are already at their new values in the original calendar
     data passed in to each generateXXX() call.
     """
-    
+
     @staticmethod
     def generateCancel(original, attendees, instances=None, full_cancel=False):
         """
         This assumes that SEQUENCE is not already at its new value in the original calendar data. This
         is because the component passed in is the one that originally contained the attendee that is
-        being removed. 
+        being removed.
         """
-        
+
         itip = Component("VCALENDAR")
         itip.addProperty(Property("VERSION", "2.0"))
         itip.addProperty(Property("PRODID", iCalendarProductID))
@@ -652,7 +661,7 @@
         tzids = set()
         added = False
         for instance_rid in instances:
-            
+
             # Create a new component matching the type of the original
             comp = Component(original.mainType())
 
@@ -664,7 +673,7 @@
                 instance = original.overriddenComponent(instance_rid)
                 if instance is None:
                     instance = original.deriveInstance(instance_rid)
-                    
+
                 # If the instance to be cancelled did not exist in the original, then
                 # do nothing
                 if instance is None:
@@ -679,11 +688,11 @@
             comp.addProperty(instance.getOrganizerProperty())
             if instance_rid:
                 comp.addProperty(Property("RECURRENCE-ID", instance_rid.duplicate().adjustToUTC()))
-            
+
             def addProperties(propname):
                 for icalproperty in instance.properties(propname):
                     comp.addProperty(icalproperty)
-                    
+
             addProperties("SUMMARY")
             addProperties("DTSTART")
             addProperties("DTEND")
@@ -706,7 +715,7 @@
 
             itip.addComponent(comp)
             added = True
-        
+
         if added:
             # Now include any referenced tzids
             for comp in original.subcomponents():
@@ -714,37 +723,39 @@
                     tzid = comp.propertyValue("TZID")
                     if tzid in tzids:
                         itip.addComponent(comp)
-    
+
             # Strip out unwanted bits
             iTipGenerator.prepareSchedulingMessage(itip)
-    
+
             return itip
         else:
             return None
 
+
     @staticmethod
     def generateAttendeeRequest(original, attendees, filter_rids):
         """
         This assumes that SEQUENCE is already at its new value in the original calendar data.
         """
-        
+
         # Start with a copy of the original as we may have to modify bits of it
         itip = original.duplicate()
         itip.replaceProperty(Property("PRODID", iCalendarProductID))
         itip.addProperty(Property("METHOD", "REQUEST"))
-        
+
         # Now filter out components that do not contain every attendee
         itip.attendeesView(attendees, onlyScheduleAgentServer=True)
-        
+
         # Now filter out components except the ones specified
         if itip.filterComponents(filter_rids):
             # Strip out unwanted bits
             iTipGenerator.prepareSchedulingMessage(itip)
             return itip
-        
+
         else:
             return None
 
+
     @staticmethod
     def generateAttendeeReply(original, attendee, changedRids=None, force_decline=False):
 
@@ -792,22 +803,23 @@
             "LOCATION",
             "DESCRIPTION",
         ))
-        
+
         # 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:
                 attendeeProp.setParameter("PARTSTAT", "DECLINED")
-        
+
         # Add REQUEST-STATUS to each top-level component
-        itip.addPropertyToAllComponents(Property("REQUEST-STATUS", ["2.0", "Success",]))
+        itip.addPropertyToAllComponents(Property("REQUEST-STATUS", ["2.0", "Success", ]))
 
         # Strip out unwanted bits
         iTipGenerator.prepareSchedulingMessage(itip, reply=True)
 
         return itip
 
+
     @staticmethod
     def prepareSchedulingMessage(itip, reply=False):
         """
@@ -831,40 +843,42 @@
             # Attendee properties that need to go to the Organizer
             keep_properties = ("X-CALENDARSERVER-PRIVATE-COMMENT",)
         itip.removeXProperties(keep_properties=keep_properties)
-        
+
         # Property Parameters
         itip.removePropertyParameters("ATTENDEE", ("SCHEDULE-AGENT", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
         itip.removePropertyParameters("ORGANIZER", ("SCHEDULE-AGENT", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
 
+
+
 class iTIPRequestStatus(object):
     """
     String constants for various iTIP status codes we use.
     """
-    
-    MESSAGE_PENDING_CODE        = "1.0"
-    MESSAGE_SENT_CODE           = "1.1"
-    MESSAGE_DELIVERED_CODE      = "1.2"
 
-    SUCCESS_CODE                = "2.0"
+    MESSAGE_PENDING_CODE = "1.0"
+    MESSAGE_SENT_CODE = "1.1"
+    MESSAGE_DELIVERED_CODE = "1.2"
 
-    INVALID_CALENDAR_USER_CODE  = "3.7"
-    NO_AUTHORITY_CODE           = "3.8"
+    SUCCESS_CODE = "2.0"
 
-    BAD_REQUEST_CODE            = "5.0"
-    SERVICE_UNAVAILABLE_CODE    = "5.1"
-    INVALID_SERVICE_CODE        = "5.2"
-    NO_USER_SUPPORT_CODE        = "5.3"
+    INVALID_CALENDAR_USER_CODE = "3.7"
+    NO_AUTHORITY_CODE = "3.8"
 
-    MESSAGE_PENDING         = MESSAGE_PENDING_CODE + ";Scheduling message send is pending"
-    MESSAGE_SENT            = MESSAGE_SENT_CODE + ";Scheduling message has been sent"
-    MESSAGE_DELIVERED       = MESSAGE_DELIVERED_CODE + ";Scheduling message has been delivered"
-    
-    SUCCESS                 = SUCCESS_CODE + ";Success"
+    BAD_REQUEST_CODE = "5.0"
+    SERVICE_UNAVAILABLE_CODE = "5.1"
+    INVALID_SERVICE_CODE = "5.2"
+    NO_USER_SUPPORT_CODE = "5.3"
 
-    INVALID_CALENDAR_USER   = INVALID_CALENDAR_USER_CODE + ";Invalid Calendar User"
-    NO_AUTHORITY            = NO_AUTHORITY_CODE + ";No authority"
+    MESSAGE_PENDING = MESSAGE_PENDING_CODE + ";Scheduling message send is pending"
+    MESSAGE_SENT = MESSAGE_SENT_CODE + ";Scheduling message has been sent"
+    MESSAGE_DELIVERED = MESSAGE_DELIVERED_CODE + ";Scheduling message has been delivered"
 
-    BAD_REQUEST             = BAD_REQUEST_CODE + ";Service cannot handle request"
-    SERVICE_UNAVAILABLE     = SERVICE_UNAVAILABLE_CODE + ";Service unavailable"
-    INVALID_SERVICE         = INVALID_SERVICE_CODE + ";Invalid calendar service"
-    NO_USER_SUPPORT         = NO_USER_SUPPORT_CODE + ";No scheduling support for user"
+    SUCCESS = SUCCESS_CODE + ";Success"
+
+    INVALID_CALENDAR_USER = INVALID_CALENDAR_USER_CODE + ";Invalid Calendar User"
+    NO_AUTHORITY = NO_AUTHORITY_CODE + ";No authority"
+
+    BAD_REQUEST = BAD_REQUEST_CODE + ";Service cannot handle request"
+    SERVICE_UNAVAILABLE = SERVICE_UNAVAILABLE_CODE + ";Service unavailable"
+    INVALID_SERVICE = INVALID_SERVICE_CODE + ";Invalid calendar service"
+    NO_USER_SUPPORT = NO_USER_SUPPORT_CODE + ";No scheduling support for user"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121011/867d8fd6/attachment-0001.html>


More information about the calendarserver-changes mailing list