[CalendarServer-changes] [8412] CalendarServer/trunk/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Wed Dec 7 11:14:02 PST 2011


Revision: 8412
          http://trac.macosforge.org/projects/calendarserver/changeset/8412
Author:   sagen at apple.com
Date:     2011-12-07 11:14:01 -0800 (Wed, 07 Dec 2011)
Log Message:
-----------
Invalid recurrence-ids are made valid by automatically adding an RDATE

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/instance.py
    CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/trunk/twistedcaldav/test/test_icalendar.py

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2011-12-07 19:13:58 UTC (rev 8411)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2011-12-07 19:14:01 UTC (rev 8412)
@@ -979,7 +979,7 @@
 
         return newcomp
 
-    def cacheExpandedTimeRanges(self, limit):
+    def cacheExpandedTimeRanges(self, limit, ignoreInvalidInstances=False):
         """
         Expand instances up to the specified limit and cache the results in this object
         so we can return cached results in the future.
@@ -996,7 +996,8 @@
                 # so return cached instances
                 return self.cachedInstances
         
-        self.cachedInstances = self.expandTimeRanges(limit)
+        self.cachedInstances = self.expandTimeRanges(limit,
+            ignoreInvalidInstances=ignoreInvalidInstances)
         return self.cachedInstances
 
     def expandTimeRanges(self, limit, ignoreInvalidInstances=False):
@@ -1037,7 +1038,7 @@
         
         @return: a tuple of recurrence-ids
         """
-        
+
         # Extract appropriate sub-component if this is a VCALENDAR
         if self.name() == "VCALENDAR":
             result = ()
@@ -1174,7 +1175,7 @@
 
         return newcomp
         
-    def validInstances(self, rids):
+    def validInstances(self, rids, ignoreInvalidInstances=False):
         """
         Test whether the specified recurrence-ids are valid instances in this event.
 
@@ -1188,34 +1189,36 @@
         non_master_rids = [rid for rid in rids if rid is not None]
         if non_master_rids:
             highest_rid = max(non_master_rids)
-            self.cacheExpandedTimeRanges(highest_rid + PyCalendarDuration(days=1))
+            self.cacheExpandedTimeRanges(
+                highest_rid + PyCalendarDuration(days=1),
+                ignoreInvalidInstances=ignoreInvalidInstances
+            )
         for rid in rids:
-            if self.validInstance(rid, clear_cache=False):
+            if self.validInstance(rid, clear_cache=False, ignoreInvalidInstances=ignoreInvalidInstances):
                 valid.add(rid)
         return valid
 
-    def validInstance(self, rid, clear_cache=True):
+    def validInstance(self, rid, clear_cache=True, ignoreInvalidInstances=False):
         """
         Test whether the specified recurrence-id is a valid instance in this event.
 
         @param rid: recurrence-id value
         @type rid: L{PyCalendarDateTime}
-        
+
         @return: C{bool}
         """
-        
-        # First check overridden instances already in this component
-        if not hasattr(self, "cachedComponentInstances") or clear_cache:
-            self.cachedComponentInstances = set(self.getComponentInstances())
-        if rid in self.cachedComponentInstances:
-            return True
-            
-        # Must have a master component
+
         if self.masterComponent() is None:
-            return False
+            return rid in set(self.getComponentInstances())
 
+        if rid is None:
+            return True
+
         # Get expansion
-        instances = self.cacheExpandedTimeRanges(rid + PyCalendarDuration(days=1))
+        instances = self.cacheExpandedTimeRanges(
+            rid + PyCalendarDuration(days=1),
+            ignoreInvalidInstances=ignoreInvalidInstances
+        )
         new_rids = set([instances[key].rid for key in instances])
         return rid in new_rids
 
@@ -1296,8 +1299,38 @@
             log.debug("Unknown resource type: %s" % (self,))
             raise InvalidICalendarDataError("Unknown resource type")
 
+        fixed = []
+        unfixed = []
+
+        # Detect invalid occurrences and fix by adding RDATEs for them
+        master = self.masterComponent()
+        if master is not None:
+            # Get the set of all recurrence IDs
+            all_rids = set(self.getComponentInstances())
+            if None in all_rids:
+                all_rids.remove(None)
+            # Get the set of all valid recurrence IDs
+            valid_rids = self.validInstances(all_rids, ignoreInvalidInstances=True)
+            # Get the set of all RDATEs and add those to the valid set
+            rdates = []
+            for property in master.properties("RDATE"):
+                rdates.extend([_rdate.getValue() for _rdate in property.value()])
+            valid_rids.update(set(rdates))
+            # Determine the invalid recurrence IDs by set subtraction
+            invalid_rids = all_rids - valid_rids
+            # Add RDATEs for the invalid ones.
+            for invalid_rid in invalid_rids:
+                if doFix:
+                    master.addProperty(Property("RDATE", [invalid_rid,]))
+                    fixed.append("Added RDATE for invalid occurrence: %s" %
+                        (invalid_rid,))
+                else:
+                    unfixed.append("Invalid occurrence: %s" % (invalid_rid,))
+
         # Do underlying iCalendar library validation with data fix
-        fixed, unfixed = self._pycalendar.validate(doFix=doFix)
+        pyfixed, pyunfixed = self._pycalendar.validate(doFix=doFix)
+        fixed.extend(pyfixed)
+        unfixed.extend(pyunfixed)
         if unfixed:
             log.debug("Calendar data had unfixable problems:\n  %s" % ("\n  ".join(unfixed),))
             if doRaise:
@@ -1307,6 +1340,8 @@
         
         return fixed, unfixed
 
+        
+        
     def validCalendarForCalDAV(self, methodAllowed):
         """
         @param methodAllowed:     True if METHOD property is allowed, False otherwise.

Modified: CalendarServer/trunk/twistedcaldav/instance.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/instance.py	2011-12-07 19:13:58 UTC (rev 8411)
+++ CalendarServer/trunk/twistedcaldav/instance.py	2011-12-07 19:14:01 UTC (rev 8412)
@@ -365,7 +365,7 @@
                 if oldinstance.overridden:
                     continue
                 
-                # Determine the start/end of the new instance\
+                # Determine the start/end of the new instance
                 originalStart = oldinstance.rid
                 start = oldinstance.start
                 end = oldinstance.end

Modified: CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py	2011-12-07 19:13:58 UTC (rev 8411)
+++ CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py	2011-12-07 19:14:01 UTC (rev 8412)
@@ -596,7 +596,7 @@
         rdates = component.properties("RDATE")
         for rdate in rdates:
             for value in rdate.value():
-                if isinstance(PyCalendarDateTime()):
+                if isinstance(value, PyCalendarDateTime):
                     value = value.duplicate().adjustToUTC()
                 newrdates.add(value)
         

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2011-12-07 19:13:58 UTC (rev 8411)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2011-12-07 19:14:01 UTC (rev 8412)
@@ -377,7 +377,80 @@
         # Now it should pass without fixing
         calendar.validCalendarData(doFix=False)
 
+        # Test invalid occurrences
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 5.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/Los_Angeles
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20111206T203543Z
+UID:5F7FF5FB-2253-4895-8BF1-76E8ED868B4C
+DTEND;TZID=America/Los_Angeles:20111207T153000
+RRULE:FREQ=WEEKLY;COUNT=400
+TRANSP:OPAQUE
+SUMMARY:bogus instance
+DTSTART;TZID=America/Los_Angeles:20111207T143000
+DTSTAMP:20111206T203553Z
+SEQUENCE:3
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20111206T203543Z
+UID:5F7FF5FB-2253-4895-8BF1-76E8ED868B4C
+DTEND;TZID=America/Los_Angeles:20111221T124500
+TRANSP:OPAQUE
+SUMMARY:bogus instance
+DTSTART;TZID=America/Los_Angeles:20111221T114500
+DTSTAMP:20111206T203632Z
+SEQUENCE:5
+RECURRENCE-ID;TZID=America/Los_Angeles:20111221T143000
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20111206T203543Z
+UID:5F7FF5FB-2253-4895-8BF1-76E8ED868B4C
+DTEND;TZID=America/Los_Angeles:20111214T163000
+TRANSP:OPAQUE
+SUMMARY:bogus instance
+DTSTART;TZID=America/Los_Angeles:20111214T153000
+DTSTAMP:20111206T203606Z
+SEQUENCE:4
+RECURRENCE-ID;TZID=America/Los_Angeles:20111215T143000
+END:VEVENT
+END:VCALENDAR
+"""
+        # Ensure it starts off invalid
+        calendar = Component.fromString(data)
+        try:
+            calendar.validCalendarData(doFix=False)
+        except InvalidICalendarDataError:
+            pass
+        else:
+            self.fail("Shouldn't validate for CalDAV")
 
+        # Fix it
+        calendar.validCalendarData(doFix=True)
+        self.assertTrue("RDATE:20111215T223000Z\r\n" in str(calendar))
+
+        # Now it should pass without fixing
+        calendar.validCalendarData(doFix=False)
+
+
     def test_component_timeranges(self):
         """
         Component time range query.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20111207/02500484/attachment-0001.html>


More information about the calendarserver-changes mailing list