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

source_changes at macosforge.org source_changes at macosforge.org
Sun Mar 22 15:06:06 PDT 2009


Revision: 3910
          http://trac.macosforge.org/projects/calendarserver/changeset/3910
Author:   cdaboo at apple.com
Date:     2009-03-22 15:06:06 -0700 (Sun, 22 Mar 2009)
Log Message:
-----------
Handled recurrence truncation when overridden instances are present. Also fix issue with TRANSP
not being transferred over properly.

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

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2009-03-22 22:01:15 UTC (rev 3909)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2009-03-22 22:06:06 UTC (rev 3910)
@@ -879,6 +879,26 @@
 
         return newcomp
 
+    def cacheExpandedTimeRanges(self, limit):
+        """
+        Expand instances up to the specified limit and cache the results in this object
+        so we can return cached results in the future.
+ 
+        @param limit: the max datetime to cache up to.
+        @type limit: L{datetime.datetime} or L{datetime.date}
+        """
+        
+        # Checked for cached values first
+        if hasattr(self, "cachedInstances"):
+            cachedLimit = self.cachedInstances.limit
+            if cachedLimit is None or cachedLimit >= limit:
+                # We have already fully expanded, or cached up to the requested time,
+                # so return cached instances
+                return self.cachedInstances
+        
+        self.cachedInstances = self.expandTimeRanges(limit)
+        return self.cachedInstances
+
     def expandTimeRanges(self, limit):
         """
         Expand the set of recurrence instances for the components
@@ -994,6 +1014,25 @@
                         # Cannot derive from an existing EXDATE
                         return None
         
+        # Check whether recurrence-id matches an RDATE - if so it is OK
+        rdates = set()
+        for rdate in master.properties("RDATE"):
+            rdates.update([normalizeToUTC(item) for item in rdate.value()])
+        if rid not in rdates:
+            # Check whether we have a truncated RRULE
+            rrules = master.properties("RRULE")
+            if len(tuple(rrules)):
+                limit = rid
+                limit += datetime.timedelta(days=365)
+                instances = self.cacheExpandedTimeRanges(limit)
+                rids = set([instances[key].rid for key in instances])
+                if rid not in rids:
+                    # No match to a valid RRULE instance
+                    return None
+            else:
+                # No RRULE and no match to an RDATE => error
+                return None
+        
         # Create the derived instance
         newcomp = master.duplicate()
 

Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py	2009-03-22 22:01:15 UTC (rev 3909)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py	2009-03-22 22:06:06 UTC (rev 3910)
@@ -98,9 +98,11 @@
         if current_master:
             master_valarms = [comp for comp in current_master.subcomponents() if comp.name() == "VALARM"]
             private_comments = current_master.properties("X-CALENDARSERVER-PRIVATE-COMMENT")
+            transps = current_master.properties("TRANSP")
         else:
             master_valarms = ()
             private_comments = ()
+            transps = ()
 
         if itip_message.masterComponent() is not None:
             
@@ -113,11 +115,13 @@
                 master_component.addComponent(alarm)
             for comment in private_comments:
                 master_component.addProperty(comment)
+            for transp in transps:
+                master_component.replaceProperty(transp)
                 
             # Now try to match recurrences
             for component in new_calendar.subcomponents():
                 if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
-                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, component)
+                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, component)
             
             # Now try to match recurrences
             for component in calendar.subcomponents():
@@ -126,8 +130,9 @@
                     if new_calendar.overriddenComponent(rid) is None:
                         allowCancelled = component.propertyValue("STATUS") == "CANCELLED"
                         new_component = new_calendar.deriveInstance(rid, allowCancelled=allowCancelled)
-                        new_calendar.addComponent(new_component)
-                        iTipProcessing.transferItems(calendar, master_valarms, private_comments, new_component)
+                        if new_component:
+                            new_calendar.addComponent(new_component)
+                            iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, new_component)
             
             # Replace the entire object
             return new_calendar, props_changed, rids
@@ -144,7 +149,7 @@
                         calendar.addComponent(component)
                 else:
                     component = component.duplicate()
-                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, component, remove_matched=True)
+                    iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, component, remove_matched=True)
                     calendar.addComponent(component)
                     if recipient and not autoprocessing:
                         iTipProcessing.fixForiCal3((component,), recipient, config.Scheduling.CalDAV.OldDraftCompatibility)
@@ -225,8 +230,9 @@
                 else:
                     # Derive a new component and cancel it.
                     overridden = calendar.deriveInstance(rid)
-                    overridden.replaceProperty(Property("STATUS", "CANCELLED"))
-                    calendar.addComponent(overridden)
+                    if overridden:
+                        overridden.replaceProperty(Property("STATUS", "CANCELLED"))
+                        calendar.addComponent(overridden)
 
         # If we have any EXDATEs lets add them to the existing calendar object.
         if exdates and calendar_master:
@@ -279,7 +285,8 @@
             if rids is not None and (partstat_changed or private_comment_changed):
                 rids.add("")
 
-        # Now do all overridden ones
+        # Now do all overridden ones (sort by RECURRENCE-ID)
+        sortedComponents = []
         for itip_component in itip_message.subcomponents():
             
             # Make sure we have an appropriate component
@@ -288,14 +295,19 @@
             rid = itip_component.getRecurrenceIDUTC()
             if rid is None:
                 continue
+            sortedComponents.append((rid, itip_component,))
             
+        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)
             if match_component is None:
                 # Attendee is overriding an instance themselves - we need to create a derived one
                 # for the Organizer
                 match_component = calendar.deriveInstance(rid)
-                calendar.addComponent(match_component)
+                if match_component:
+                    calendar.addComponent(match_component)
 
             attendee, partstat, private_comment = iTipProcessing.updateAttendeeData(itip_component, match_component)
             attendees.add(attendee)
@@ -423,7 +435,7 @@
         return attendee.value(), partstat_changed, private_comment_changed
 
     @staticmethod
-    def transferItems(from_calendar, master_valarms, private_comments, to_component, remove_matched=False):
+    def transferItems(from_calendar, master_valarms, private_comments, transps, to_component, remove_matched=False):
 
         rid = to_component.getRecurrenceIDUTC()
 
@@ -433,6 +445,7 @@
             # Copy over VALARMs from existing component
             [to_component.addComponent(comp) for comp in matched.subcomponents() if comp.name() == "VALARM"]
             [to_component.addProperty(prop) for prop in matched.properties("X-CALENDARSERVER-ATTENDEE-COMMENT")]
+            [to_component.replaceProperty(prop) for prop in matched.properties("TRANSP")]
 
             # Remove the old one
             if remove_matched:
@@ -443,6 +456,7 @@
             # 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]
     
     @staticmethod
     def fixForiCal3(components, recipient, compatibilityMode):

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2009-03-22 22:01:15 UTC (rev 3909)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2009-03-22 22:06:06 UTC (rev 3910)
@@ -18,6 +18,7 @@
 import datetime
 from dateutil.tz import tzutc
 from difflib import unified_diff
+import itertools
 
 from twisted.trial.unittest import SkipTest
 
@@ -2235,3 +2236,338 @@
             ical = Component.fromString(calendar)
             result = ical.isRecurringUnbounded()
             self.assertEqual(result, expected, "Failed recurring unbounded test: %s" % (title,))
+
+    def test_derive_instance(self):
+        
+        data = (
+            (
+                "1.1 - simple",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                datetime.datetime(2009, 1, 2, 8, 0, 0, tzinfo=tzutc()),
+                """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T080000Z
+DTSTART:20090102T080000Z
+DTEND:20090102T090000Z
+END:VEVENT
+""",
+            ),
+            (
+                "1.2 - simple rdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                datetime.datetime(2009, 1, 2, 18, 0, 0, tzinfo=tzutc()),
+                """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T180000Z
+DTSTART:20090102T180000Z
+DTEND:20090102T190000Z
+END:VEVENT
+""",
+            ),
+            (
+                "1.3 - multiple rdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z,20090103T180000Z
+RDATE:20090104T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                datetime.datetime(2009, 1, 3, 18, 0, 0, tzinfo=tzutc()),
+                """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T180000Z
+DTSTART:20090103T180000Z
+DTEND:20090103T190000Z
+END:VEVENT
+""",
+            ),
+            (
+                "2.1 - invalid simple",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                datetime.datetime(2009, 1, 2, 9, 0, 0, tzinfo=tzutc()),
+                None,
+            ),
+            (
+                "2.2 - invalid simple rdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                datetime.datetime(2009, 1, 2, 19, 0, 0, tzinfo=tzutc()),
+                None,
+            ),
+            (
+                "2.3 - invalid multiple rdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z,20090103T180000Z
+RDATE:20090104T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                datetime.datetime(2009, 1, 3, 19, 0, 0, tzinfo=tzutc()),
+                None,
+            ),
+        )
+        
+        for title, calendar, rid, result in data:
+            ical = Component.fromString(calendar)
+            derived = ical.deriveInstance(rid)
+            derived = str(derived).replace("\r", "") if derived else None
+            self.assertEqual(derived, result, "Failed derive instance test: %s" % (title,))
+
+    def test_derive_instance_multiple(self):
+        
+        data = (
+            (
+                "1.1 - simple",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    datetime.datetime(2009, 1, 2, 8, 0, 0, tzinfo=tzutc()),
+                    datetime.datetime(2009, 1, 4, 8, 0, 0, tzinfo=tzutc()),
+                ),
+                (
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T080000Z
+DTSTART:20090102T080000Z
+DTEND:20090102T090000Z
+END:VEVENT
+""",
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090104T080000Z
+DTSTART:20090104T080000Z
+DTEND:20090104T090000Z
+END:VEVENT
+""",
+                ),
+            ),
+            (
+                "1.2 - simple rdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    datetime.datetime(2009, 1, 2, 18, 0, 0, tzinfo=tzutc()),
+                    datetime.datetime(2009, 1, 4, 8, 0, 0, tzinfo=tzutc()),
+                ),
+                (
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T180000Z
+DTSTART:20090102T180000Z
+DTEND:20090102T190000Z
+END:VEVENT
+""",
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090104T080000Z
+DTSTART:20090104T080000Z
+DTEND:20090104T090000Z
+END:VEVENT
+""",
+                ),
+            ),
+            (
+                "1.3 - multiple rdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z,20090103T180000Z
+RDATE:20090104T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    datetime.datetime(2009, 1, 3, 18, 0, 0, tzinfo=tzutc()),
+                    datetime.datetime(2009, 1, 5, 8, 0, 0, tzinfo=tzutc()),
+                ),
+                (
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T180000Z
+DTSTART:20090103T180000Z
+DTEND:20090103T190000Z
+END:VEVENT
+""",
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090105T080000Z
+DTSTART:20090105T080000Z
+DTEND:20090105T090000Z
+END:VEVENT
+""",
+                ),
+            ),
+            (
+                "2.1 - invalid simple",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    datetime.datetime(2009, 1, 2, 9, 0, 0, tzinfo=tzutc()),
+                    datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+                ),
+                (
+                    None,
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T080000Z
+DTSTART:20090103T080000Z
+DTEND:20090103T090000Z
+END:VEVENT
+""",
+                ),
+            ),
+            (
+                "2.2 - invalid simple rdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    datetime.datetime(2009, 1, 2, 19, 0, 0, tzinfo=tzutc()),
+                    datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+                ),
+                (
+                    None,
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T080000Z
+DTSTART:20090103T080000Z
+DTEND:20090103T090000Z
+END:VEVENT
+""",
+                ),
+            ),
+            (
+                "2.3 - invalid multiple rdate",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z,20090103T180000Z
+RDATE:20090104T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                (
+                    datetime.datetime(2009, 1, 3, 19, 0, 0, tzinfo=tzutc()),
+                    datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+                ),
+                (
+                    None,
+                    """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T080000Z
+DTSTART:20090103T080000Z
+DTEND:20090103T090000Z
+END:VEVENT
+""",
+                ),
+            ),
+        )
+        
+        for title, calendar, rids, results in data:
+            ical = Component.fromString(calendar)
+            for rid, result in itertools.izip(rids, results):
+                derived = ical.deriveInstance(rid)
+                derived = str(derived).replace("\r", "") if derived else None
+                self.assertEqual(derived, result, "Failed derive instance test: %s" % (title,))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090322/08137f3b/attachment-0001.html>


More information about the calendarserver-changes mailing list