[CalendarServer-changes] [4818] CalendarServer/branches/users/cdaboo/implicit-improvements-4804/ twistedcaldav/scheduling/icaldiff.py

source_changes at macosforge.org source_changes at macosforge.org
Thu Dec 3 14:08:41 PST 2009


Revision: 4818
          http://trac.macosforge.org/projects/calendarserver/changeset/4818
Author:   cdaboo at apple.com
Date:     2009-12-03 14:08:41 -0800 (Thu, 03 Dec 2009)
Log Message:
-----------
Don't error out on some client bugs. Add accounting logging for implicit errors. Clarify code by renaming
some vars. Fix issue with invalid derived instances for some spanning a DSt transition due to conversion
of datetimes into UTC before deriveInstance is used.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/implicit-improvements-4804/twistedcaldav/scheduling/icaldiff.py

Modified: CalendarServer/branches/users/cdaboo/implicit-improvements-4804/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-improvements-4804/twistedcaldav/scheduling/icaldiff.py	2009-12-03 21:24:24 UTC (rev 4817)
+++ CalendarServer/branches/users/cdaboo/implicit-improvements-4804/twistedcaldav/scheduling/icaldiff.py	2009-12-03 22:08:41 UTC (rev 4818)
@@ -21,6 +21,7 @@
 from twistedcaldav.log import Logger
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 from twistedcaldav.scheduling.itip import iTipGenerator
+from twistedcaldav import accounting
 
 from difflib import unified_diff
 
@@ -36,17 +37,17 @@
 
 class iCalDiff(object):
     
-    def __init__(self, calendar1, calendar2, smart_merge):
+    def __init__(self, oldcalendar, newcalendar, smart_merge):
         """
         
-        @param calendar1:
-        @type calendar1:
-        @param calendar2:
-        @type calendar2:
+        @param oldcalendar:
+        @type oldcalendar:
+        @param newcalendar:
+        @type newcalendar:
         """
         
-        self.calendar1 = calendar1
-        self.calendar2 = calendar2
+        self.oldcalendar = oldcalendar
+        self.newcalendar = newcalendar
         self.smart_merge = smart_merge
     
     def organizerDiff(self):
@@ -75,34 +76,32 @@
             return calendar
         
         # Normalize components for comparison
-        self.calendar1 = duplicateAndNormalize(self.calendar1)
-        self.calendar2 = duplicateAndNormalize(self.calendar2)
+        oldcalendar_norm = duplicateAndNormalize(self.oldcalendar)
+        newcalendar_norm = duplicateAndNormalize(self.newcalendar)
 
-        result = self.calendar1 == self.calendar2
-        if not result:
-            self._logDiffError("organizerDiff: Mismatched calendar objects")
+        result = oldcalendar_norm == newcalendar_norm
         return result
 
     def _organizerMerge(self):
         """
-        Merge changes to ATTENDEE properties in calendar1 into calendar2.
+        Merge changes to ATTENDEE properties in oldcalendar into newcalendar.
         """
-        organizer = normalizeCUAddr(self.calendar2.masterComponent().propertyValue("ORGANIZER"))
+        organizer = normalizeCUAddr(self.newcalendar.masterComponent().propertyValue("ORGANIZER"))
         self._doSmartMerge(organizer, True)
 
     def _doSmartMerge(self, ignore_attendee, is_organizer):
         """
-        Merge changes to ATTENDEE properties in calendar1 into calendar2.
+        Merge changes to ATTENDEE properties in oldcalendar into newcalendar.
         """
         
-        old_master = self.calendar1.masterComponent()
-        new_master = self.calendar2.masterComponent()
+        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.calendar1.subcomponents():
+        for old_component in self.oldcalendar.subcomponents():
             
             # Make sure we have an appropriate component
             if old_component.name() == "VTIMEZONE":
@@ -112,17 +111,17 @@
                 continue
 
             # Find matching component in new calendar
-            new_component = self.calendar2.overriddenComponent(rid)
+            new_component = self.newcalendar.overriddenComponent(rid)
             if new_component is None:
                 # 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.calendar2.deriveInstance(rid)
+                new_component = self.newcalendar.deriveInstance(rid)
                 if new_component:
                     # Derive a new instance from the new calendar and transfer attendee status
-                    self.calendar2.addComponent(new_component)
+                    self.newcalendar.addComponent(new_component)
                     self._tryComponentMerge(old_component, new_component, ignore_attendee, is_organizer)
                 else:
                     # Ignore the old instance as it no longer exists
@@ -131,7 +130,7 @@
                 self._tryComponentMerge(old_component, new_component, ignore_attendee, is_organizer)
 
         # Check the new instances not in the old calendar
-        for new_component in self.calendar2.subcomponents():
+        for new_component in self.newcalendar.subcomponents():
             
             # Make sure we have an appropriate component
             if new_component.name() == "VTIMEZONE":
@@ -141,16 +140,16 @@
                 continue
 
             # Find matching component in old calendar
-            old_component = self.calendar1.overriddenComponent(rid)
+            old_component = self.oldcalendar.overriddenComponent(rid)
             if old_component is None:
                 # 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.calendar1.deriveInstance(rid)
+                old_component = self.oldcalendar.deriveInstance(rid)
                 if old_component:
-                    self.calendar1.addComponent(old_component)
+                    self.oldcalendar.addComponent(old_component)
                     self._tryComponentMerge(old_component, new_component, ignore_attendee, is_organizer)
                 else:
                     # Ignore as we have no state for the new instance
@@ -248,11 +247,11 @@
 
         if config.MaxInstancesForRRULE != 0:
             try:
-                self.calendar1.truncateRecurrence(config.MaxInstancesForRRULE)
+                self.oldcalendar.truncateRecurrence(config.MaxInstancesForRRULE)
             except (ValueError, TypeError), ex:
                 log.err("Cannot truncate calendar resource: %s" % (ex,))
 
-        self.newCalendar = self.calendar1.duplicate()
+        self.newCalendar = self.oldcalendar.duplicate()
         self.newMaster = self.newCalendar.masterComponent()
 
         changeCausesReply = False
@@ -285,60 +284,61 @@
                
             return exdates, map, master
         
-        exdates1, map1, master1 = mapComponents(self.calendar1)
-        set1 = set(map1.keys())
-        exdates2, map2, master2 = mapComponents(self.calendar2)
-        set2 = set(map2.keys())
+        exdatesold, mapold, masterold = mapComponents(self.oldcalendar)
+        setold = set(mapold.keys())
+        exdatesnew, mapnew, masternew = mapComponents(self.newcalendar)
+        setnew = set(mapnew.keys())
 
         # Handle case where iCal breaks events without a master component
-        if master2 is not None and master1 is None:
-            master2Start = master2.getStartDateUTC()
-            key2 = (master2.name(), master2.propertyValue("UID"), master2Start)
-            if key2 not in set1:
+        if masternew is not None and masterold is None:
+            masternewStart = masternew.getStartDateUTC()
+            keynew = (masternew.name(), masternew.propertyValue("UID"), masternewStart)
+            if keynew not in setold:
                 # The DTSTART in the fake master does not match a RECURRENCE-ID in the real data.
                 # We have to do a brute force search for the component that matches based on DTSTART
-                for component1 in self.calendar1.subcomponents():
-                    if component1.name() == "VTIMEZONE":
+                for componentold in self.oldcalendar.subcomponents():
+                    if componentold.name() == "VTIMEZONE":
                         continue
-                    if master2Start == component1.getStartDateUTC():
+                    if masternewStart == componentold.getStartDateUTC():
                         break
                 else:
                     # Nothing matches - this has to be treated as an error
-                    log.debug("attendeeMerge: Unable to match fake master component: %s" % (key2,))
+                    log.debug("attendeeMerge: Unable to match fake master component: %s" % (keynew,))
                     return False, False, (), None
             else:
-                component1 = self.calendar1.overriddenComponent(master2Start)
+                componentold = self.oldcalendar.overriddenComponent(masternewStart)
             
             # Take the recurrence ID from component1 and fix map2/set2
-            key2 = (master2.name(), master2.propertyValue("UID"), None)
-            component2 = map2[key2]
-            del map2[key2]
+            keynew = (masternew.name(), masternew.propertyValue("UID"), None)
+            componentnew = mapnew[keynew]
+            del mapnew[keynew]
             
-            rid1 = component1.getRecurrenceIDUTC()
-            newkey2 = (master2.name(), master2.propertyValue("UID"), rid1)
-            map2[newkey2] = component2
-            set2.remove(key2)
-            set2.add(newkey2)
+            ridold = componentold.getRecurrenceIDUTC()
+            newkeynew = (masternew.name(), masternew.propertyValue("UID"), ridold)
+            mapnew[newkeynew] = componentnew
+            setnew.remove(keynew)
+            setnew.add(newkeynew)
     
-        # All the components in calendar1 must be in calendar2 unless they are CANCELLED
-        result = set1 - set2
-        for key in result:
+        # All the components in oldcalendar must be in newcalendar unless they are CANCELLED
+        for key in setold - setnew:
             _ignore_name, _ignore_uid, rid = key
-            component = map1[key]
+            component = mapold[key]
             if component.propertyValue("STATUS") != "CANCELLED":
                 # Attendee may decline by EXDATE'ing an instance - we need to handle that
-                if exdates2 is None or rid in exdates2:
+                if exdatesnew is None or rid in exdatesnew:
                     # Mark Attendee as DECLINED in the server instance
                     if self._attendeeDecline(self.newCalendar.overriddenComponent(rid)):
                         changeCausesReply = True
                         changedRids.append(toString(rid) if rid else "")
                 else:
-                    log.debug("attendeeMerge: Missing uncancelled component from first calendar: %s" % (key,))
-                    return False, False, (), None
+                    # 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: 
-                if exdates2 is not None and rid not in exdates2:
-                    log.debug("attendeeMerge: Missing EXDATE for cancelled components from first calendar: %s" % (key,))
-                    return False, False, (), None
+                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
+                    self._logDiffError("attendeeMerge: Missing EXDATE for cancelled components from first calendar: %s" % (key,))
                 else:
                     # Remove the CANCELLED component from the new calendar and add an EXDATE
                     overridden = self.newCalendar.overriddenComponent(rid)
@@ -346,15 +346,15 @@
                     if self.newMaster:
                         self.newMaster.addProperty(Property("EXDATE", [rid,]))
         
-        # Derive a new component in the new calendar for each new one in set2
-        for key in set2 - set1:
+        # 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
-            component2 = map2[key]
-            if component2.propertyValue("STATUS") == "CANCELLED":
-                if exdates1 is None or rid not in exdates1:
+            componentnew = mapnew[key]
+            if componentnew.propertyValue("STATUS") == "CANCELLED":
+                if exdatesold is None or rid not in exdatesold:
                     log.debug("attendeeMerge: Cancelled component not found in first calendar (or no EXDATE): %s" % (key,))
                     return False, False, (), None
                 else:
@@ -375,18 +375,18 @@
         # So now newCalendar has all the same components as set2. Check changes and do transfers.
         
         # Make sure the same VCALENDAR properties match
-        if not self._checkVCALENDARProperties(self.newCalendar, self.calendar2):
+        if not self._checkVCALENDARProperties(self.newCalendar, self.newcalendar):
             self._logDiffError("attendeeMerge: VCALENDAR properties do not match")
             return False, False, (), None
 
         # Now we transfer per-Attendee
-        # data from calendar2 into newCalendar to sync up changes, whilst verifying that other
+        # data from newcalendar into newCalendar to sync up changes, whilst verifying that other
         # key properties are unchanged
         declines = []
-        for key in set2:
+        for key in setnew:
             _ignore_name, _ignore_uid, rid = key
             serverData = self.newCalendar.overriddenComponent(rid)
-            clientData = map2[key]
+            clientData = mapnew[key]
             
             allowed, reply = self._transferAttendeeData(serverData, clientData, declines)
             if not allowed:
@@ -622,39 +622,41 @@
         
         rids = {}
 
-        map1 = mapComponents(self.calendar1)
-        set1 = set(map1.keys())
-        map2 = mapComponents(self.calendar2)
-        set2 = set(map2.keys())
+        oldmap = mapComponents(self.oldcalendar)
+        oldset = set(oldmap.keys())
+        newmap = mapComponents(self.newcalendar)
+        newset = set(newmap.keys())
 
-        # Now verify that each component in set1 matches what is in set2
-        for key in (set1 & set2):
-            component1 = map1[key]
-            component2 = map2[key]
+        # Now verify that each component in oldset matches what is in newset
+        for key in (oldset & newset):
+            component1 = oldmap[key]
+            component2 = newmap[key]
             self._diffComponents(component1, component2, rids)
         
-        # Now verify that each additional component in set1 matches a derived component in set2
-        for key in set1 - set2:
-            component1 = map1[key]
-            component2 = self.calendar2.deriveInstance(key[2])
-            if component2 is None:
+        # Now verify that each additional component in oldset matches a derived component in newset
+        for key in oldset - newset:
+            oldcomponent = oldmap[key]
+            newcomponent = self.newcalendar.deriveInstance(key[2])
+            if newcomponent is None:
                 continue
-            self._diffComponents(component1, component2, rids)
+            self._diffComponents(oldcomponent, newcomponent, rids)
         
-        # Now verify that each additional component in set1 matches a derived component in set2
-        for key in set2 - set1:
-            component1 = self.calendar1.deriveInstance(key[2])
-            if component1 is None:
+        # 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])
+            if oldcomponent is None:
                 continue
-            component2 = map2[key]
-            self._diffComponents(component1, component2, rids)
+            newcomponent = newmap[key]
+            self._diffComponents(oldcomponent, newcomponent, rids)
         
         return rids
 
-    def _attendeeDuplicateAndNormalize(self, comp):
+    def _componentDuplicateAndNormalize(self, comp):
         comp = comp.duplicate()
         comp.normalizePropertyValueLists("EXDATE")
         comp.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
+        comp.removePropertyParameters("ATTENDEE", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
+        comp.removeAlarms()
         comp.normalizeAll()
         comp.normalizeAttachments()
         iTipGenerator.prepareSchedulingMessage(comp, reply=True)
@@ -669,8 +671,8 @@
             return
         
         # Duplicate then normalize for comparison
-        comp1 = self._attendeeDuplicateAndNormalize(comp1)
-        comp2 = self._attendeeDuplicateAndNormalize(comp2)
+        comp1 = self._componentDuplicateAndNormalize(comp1)
+        comp2 = self._componentDuplicateAndNormalize(comp2)
 
         # Diff all the properties
         comp1.transformAllFromNative()
@@ -710,10 +712,25 @@
 
     def _logDiffError(self, title):
 
-        diff = "\n".join(unified_diff(
-            str(self.calendar1).split("\n"),
-            str(self.calendar2).split("\n"),
+        strcal1 = str(self.oldcalendar)
+        strcal2 = str(self.newcalendar)
+        strdiff = "\n".join(unified_diff(
+            strcal1.split("\n"),
+            strcal2.split("\n"),
             fromfile='Existing Calendar Object',
             tofile='New Calendar Object',
         ))
-        log.debug("%s:\n%s" % (title, diff,))
+        
+        logstr = """%s
+
+------ Existing Calendar Data ------
+%s
+------ New Calendar Data ------
+%s
+------ Diff ------
+%s
+""" % (title, strcal1, strcal2, strdiff,)
+
+        loggedName = accounting.emitAccounting("Implicit Errors", self.oldcalendar.resourceUID().encode("base64")[:-1], logstr)
+        if loggedName:
+            log.err("Generating Implicit Error accounting at path: %s" % (loggedName,))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20091203/c92e3f35/attachment-0001.html>


More information about the calendarserver-changes mailing list