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

source_changes at macosforge.org source_changes at macosforge.org
Thu Jun 7 18:11:47 PDT 2012


Revision: 9342
          http://trac.macosforge.org/projects/calendarserver/changeset/9342
Author:   cdaboo at apple.com
Date:     2012-06-07 18:11:47 -0700 (Thu, 07 Jun 2012)
Log Message:
-----------
Fixes to improve performance by doing less work during per user data filtering.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
    CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/test/test_icalendar.py

Modified: CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py	2012-06-08 01:10:12 UTC (rev 9341)
+++ CalendarServer/trunk/twistedcaldav/datafilters/peruserdata.py	2012-06-08 01:11:47 UTC (rev 9342)
@@ -210,6 +210,14 @@
             ical.addProperty(property)
 
     def _splitPerUserData(self, ical):
+        """
+        Split the per-user data out of the "normal" iCalendar components into separate per-user
+        components. Along the way keep the iCalendar representation in a "minimal" state by eliminating
+        any components that are the same as the master derived component.
+
+        @param ical: calendar data to process
+        @type ical: L{Component}
+        """
         
         def init_peruser_component():
             peruser = Component(PerUserDataFilter.PERUSER_COMPONENT)
@@ -229,8 +237,6 @@
             def init_perinstance_component():
                 peruser = Component(PerUserDataFilter.PERINSTANCE_COMPONENT)
                 rid = component.getRecurrenceIDUTC()
-                if rid:
-                    peruser.addProperty(Property("RECURRENCE-ID", rid))
                 perinstance_components[rid] = peruser
                 return peruser
 
@@ -253,15 +259,13 @@
         if self.uid:
             # Add unique per-instance components into the per-user component
             master_perinstance = perinstance_components.get(None)
-            master_perinstance_txt = str(master_perinstance)
             if master_perinstance:
                 peruser_component.addComponent(master_perinstance)
             for rid, perinstance in perinstance_components.iteritems():
                 if rid is None:
                     continue
-                perinstance_txt = str(perinstance)
-                perinstance_txt = "".join([line for line in perinstance_txt.splitlines(True) if not line.startswith("RECURRENCE-ID:")])
-                if master_perinstance is None or perinstance_txt != master_perinstance_txt:
+                if master_perinstance is None or perinstance != master_perinstance:
+                    perinstance.addProperty(Property("RECURRENCE-ID", rid))
                     peruser_component.addComponent(perinstance)
     
             self._compactInstances(ical)
@@ -280,16 +284,17 @@
         if master is None:
             return
 
+        masterDerived = ical.masterDerived()
+
         for subcomponent in tuple(ical.subcomponents()):
             if subcomponent.name() == "VTIMEZONE" or subcomponent.name().startswith("X-"):
                 continue
             rid = subcomponent.getRecurrenceIDUTC()
             if rid is None:
                 continue
-            derived = ical.deriveInstance(rid)
-            if derived:
-                if str(derived) == str(subcomponent):
-                    ical.removeComponent(subcomponent)
+            derived = ical.deriveInstance(rid, newcomp=masterDerived)
+            if derived and derived == subcomponent:
+                ical.removeComponent(subcomponent)
 
     def _mergeRepresentations(self, icalnew, icalold):
         

Modified: CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py	2012-06-08 01:10:12 UTC (rev 9341)
+++ CalendarServer/trunk/twistedcaldav/datafilters/test/test_peruserdata.py	2012-06-08 01:11:47 UTC (rev 9342)
@@ -5974,3 +5974,431 @@
             for newitem in (newdata, Component.fromString(newdata),):
                 self.assertEqual(str(PerUserDataFilter("user01").merge(newitem, olditem)), result01)
 
+
+class PerUserDataMergeTestCompact (twistedcaldav.test.util.TestCase):
+
+    def test_merge_vevent_compact(self):
+        
+        newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T110000Z
+DTEND:20080602T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID:20080602T110000Z
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        filtered = PerUserDataFilter("user01").merge(newdata, None)
+        self.assertEqual(str(filtered), result)
+        unfiltered = PerUserDataFilter("user01").filter(filtered)
+        self.assertEqual(str(unfiltered), newdata)
+
+
+    def test_merge_vevent_all_day_compact(self):
+        
+        newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;VALUE=DATE:20080602
+DTSTART;VALUE=DATE:20080602
+DTEND;VALUE=DATE:20080603
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+RECURRENCE-ID;VALUE=DATE:20080602
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        filtered = PerUserDataFilter("user01").merge(newdata, None)
+        self.assertEqual(str(filtered), result)
+        unfiltered = PerUserDataFilter("user01").filter(filtered)
+        self.assertEqual(str(unfiltered), newdata)
+
+
+    def test_merge_peruser_compact(self):
+        
+        newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        filtered = PerUserDataFilter("user01").merge(newdata, None)
+        self.assertEqual(str(filtered), result)
+        unfiltered = PerUserDataFilter("user01").filter(filtered)
+        self.assertEqual(str(unfiltered), newdata)
+
+
+    def test_merge_peruser_all_day_compact(self):
+        
+        newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;VALUE=DATE:20080602
+DTSTART;VALUE=DATE:20080602
+DTEND;VALUE=DATE:20080603
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+SUMMARY:Test
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:20080601
+DTEND;VALUE=DATE:20080602
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;VALUE=DATE:20080602
+DTSTART;VALUE=DATE:20080602
+DTEND;VALUE=DATE:20080603
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+SUMMARY:Test
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        filtered = PerUserDataFilter("user01").merge(newdata, None)
+        self.assertEqual(str(filtered), result)
+        unfiltered = PerUserDataFilter("user01").filter(filtered)
+        self.assertEqual(str(unfiltered), newdata)
+
+
+    def test_merge_both_compact(self):
+        
+        newdata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T110000Z
+DTSTART:20080602T110000Z
+DTEND:20080602T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        unfiltered_result = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T110000Z
+DTEND:20080601T120000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY;COUNT=5
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test-1mod
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        filtered = PerUserDataFilter("user01").merge(newdata, None)
+        self.assertEqual(str(filtered), result)
+        unfiltered = PerUserDataFilter("user01").filter(filtered)
+        self.assertEqual(str(unfiltered), unfiltered_result)

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2012-06-08 01:10:12 UTC (rev 9341)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2012-06-08 01:11:47 UTC (rev 9342)
@@ -1139,7 +1139,7 @@
                     return True
         return False
         
-    def deriveInstance(self, rid, allowCancelled=False):
+    def deriveInstance(self, rid, allowCancelled=False, newcomp=None):
         """
         Derive an instance from the master component that has the provided RECURRENCE-ID, but
         with all other properties, components etc from the master. If the requested override is
@@ -1154,6 +1154,9 @@
         @return: L{Component} for newly derived instance, or None if not valid override
         """
         
+        if allowCancelled and newcomp is not None:
+            raise ValueError("Cannot re-use master component with allowCancelled")
+
         # Must have a master component
         master = self.masterComponent()
         if master is None:
@@ -1188,22 +1191,17 @@
             rrules = master.properties("RRULE")
             if len(tuple(rrules)):
                 instances = self.cacheExpandedTimeRanges(rid)
-                rids = set([instances[key].rid for key in instances])
                 instance_rid = normalizeForIndex(rid)
-                if instance_rid not in rids:
+                if str(instance_rid) not in instances.instances:
                     # 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()
-
-        # Strip out unwanted recurrence properties
-        for property in tuple(newcomp.properties()):
-            if property.name() in ("RRULE", "RDATE", "EXRULE", "EXDATE", "RECURRENCE-ID",):
-                newcomp.removeProperty(property)
+        # If we were fed an already derived component, use that, otherwise make a new one
+        if newcomp is None:
+            newcomp = self.masterDerived()
         
         # New DTSTART is the RECURRENCE-ID we are deriving but adjusted to the
         # original DTSTART's localtime
@@ -1223,7 +1221,7 @@
         if newcomp.hasProperty("DTEND"):
             dtend.setValue(newdtstartValue + oldduration)
 
-        newcomp.addProperty(Property("RECURRENCE-ID", dtstart.value(), params={}))
+        newcomp.replaceProperty(Property("RECURRENCE-ID", dtstart.value(), params={}))
         
         if didCancel:
             newcomp.replaceProperty(Property("STATUS", "CANCELLED"))
@@ -1232,7 +1230,30 @@
         newcomp._pycalendar.finalise()
 
         return newcomp
+    
+    def masterDerived(self):
+        """
+        Generate a component from the master instance that can be fed repeatedly to
+        deriveInstance in the case where the result of deriveInstance is not going
+        to be inserted into the component. This provides an optimization for avoiding
+        unnecessary .duplicate() calls on the master for each deriveInstance. 
+        """
+
+        # Must have a master component
+        master = self.masterComponent()
+        if master is None:
+            return None
+
+        # Create the derived instance
+        newcomp = master.duplicate()
+
+        # Strip out unwanted recurrence properties
+        for property in tuple(newcomp.properties()):
+            if property.name() in ("RRULE", "RDATE", "EXRULE", "EXDATE", "RECURRENCE-ID",):
+                newcomp.removeProperty(property)
         
+        return newcomp
+
     def validInstances(self, rids, ignoreInvalidInstances=False):
         """
         Test whether the specified recurrence-ids are valid instances in this event.

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2012-06-08 01:10:12 UTC (rev 9341)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2012-06-08 01:11:47 UTC (rev 9342)
@@ -4200,6 +4200,59 @@
         self.assertEqual(ical.cachedInstances.limit, oldLimit)
 
 
+    def test_derive_instance_with_master_passed_in(self):
+        """
+        Test that derivation of instances only triggers an instance cache re-expansion when it
+        goes past the end of the last cache.
+        """
+        
+        event = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+        derived1 = """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T080000Z
+DTSTART:20090102T080000Z
+DTEND:20090102T090000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+""".replace("\n", "\r\n")
+
+        derived2 = """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090203T080000Z
+DTSTART:20090203T080000Z
+DTEND:20090203T090000Z
+DTSTAMP:20080601T120000Z
+END:VEVENT
+""".replace("\n", "\r\n")
+
+        ical = Component.fromString(event)
+        masterDerived = ical.masterDerived()
+        
+        # Derive one day apart - no re-cache
+        result = ical.deriveInstance(PyCalendarDateTime(2009, 1, 2, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)), newcomp=masterDerived)
+        self.assertEqual(str(result), derived1)
+
+        result = ical.deriveInstance(PyCalendarDateTime(2009, 2, 3, 8, 0, 0, tzid=PyCalendarTimezone(utc=True)), newcomp=masterDerived)
+        self.assertEqual(str(result), derived2)
+
+        result = ical.deriveInstance(PyCalendarDateTime(2009, 3, 3, 9, 0, 0, tzid=PyCalendarTimezone(utc=True)), newcomp=masterDerived)
+        self.assertEqual(result, None)
+
+        self.assertEqual(str(ical), event)
+
+
     def test_truncate_recurrence(self):
         
         data = (
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120607/d2930eb7/attachment-0001.html>


More information about the calendarserver-changes mailing list