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

source_changes at macosforge.org source_changes at macosforge.org
Tue Oct 21 09:03:33 PDT 2008


Revision: 3197
          http://trac.macosforge.org/projects/calendarserver/changeset/3197
Author:   cdaboo at apple.com
Date:     2008-10-21 09:03:33 -0700 (Tue, 21 Oct 2008)
Log Message:
-----------
Ensure that private comments are restored when using clients that don't preserve X- properties.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/customxml.py
    CalendarServer/trunk/twistedcaldav/dateops.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
    CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
    CalendarServer/trunk/twistedcaldav/test/test_icalendar.py

Modified: CalendarServer/trunk/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/customxml.py	2008-10-21 15:44:52 UTC (rev 3196)
+++ CalendarServer/trunk/twistedcaldav/customxml.py	2008-10-21 16:03:33 UTC (rev 3197)
@@ -25,6 +25,7 @@
 
 from twisted.web2.dav.davxml import dav_namespace
 from twisted.web2.dav.davxml import twisted_dav_namespace
+from twisted.web2.dav.element.base import twisted_private_namespace
 from twisted.web2.dav import davxml
 
 from twistedcaldav.ical import Component as iComponent
@@ -81,6 +82,18 @@
     def getValue(self):
         return str(self)
 
+class TwistedCalendarHasPrivateCommentsProperty (davxml.WebDAVEmptyElement):
+    """
+    Indicates that a calendar resource has private comments.
+    
+    NB This MUST be a private property as we don't want to expose the presence of private comments
+    in private events.
+    
+    """
+    namespace = twisted_private_namespace
+    name = "calendar-has-private-comments"
+    hidden = True
+
 class CalendarProxyRead (davxml.WebDAVEmptyElement):
     """
     A read-only calendar user proxy principal resource.

Modified: CalendarServer/trunk/twistedcaldav/dateops.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/dateops.py	2008-10-21 15:44:52 UTC (rev 3196)
+++ CalendarServer/trunk/twistedcaldav/dateops.py	2008-10-21 16:03:33 UTC (rev 3197)
@@ -42,7 +42,7 @@
     @return: the normalized date or datetime
     """
     if not isinstance(dt, datetime.date):
-        raise TypeError("$r is not a datetime.date instance" % (dt,))
+        raise TypeError("%r is not a datetime.date instance" % (dt,))
     
     if isinstance(dt, datetime.datetime):
         if dt.tzinfo is not None:
@@ -61,7 +61,7 @@
     @return: the normalized date or datetime
     """
     if not isinstance(dt, datetime.date):
-        raise TypeError("$r is not a datetime.date instance" % (dt,))
+        raise TypeError("%r is not a datetime.date instance" % (dt,))
     
     if isinstance(dt, datetime.datetime):
         if dt.tzinfo is not None:
@@ -125,7 +125,7 @@
 
 def makeComparableDateTime(dt1, dt2, defaulttz = None):  
     """
-    Ensure that the two datetime objects passed in are of a comparable type for arithemtic
+    Ensure that the two datetime objects passed in are of a comparable type for arithmetic
     and comparison operations..
     
     @param start: a L{datetime.datetime} or L{datetime.date} specifying one time.
@@ -137,7 +137,7 @@
 
     for dt in (dt1, dt2):
         if not isinstance(dt, datetime.date):
-            raise TypeError("$r is not a datetime.date instance" % (dt,))
+            raise TypeError("%r is not a datetime.date instance" % (dt,))
 
     # Pick appropriate tzinfo
     tzi = [None]

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2008-10-21 15:44:52 UTC (rev 3196)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2008-10-21 16:03:33 UTC (rev 3197)
@@ -407,8 +407,10 @@
             if component.name() == "VTIMEZONE":
                 continue
             rid = component.getRecurrenceIDUTC()
-            if rid and compareDateTime(rid, recurrence_id) == 0:
+            if rid and recurrence_id and compareDateTime(rid, recurrence_id) == 0:
                 return component
+            elif rid is None and recurrence_id is None:
+                return component
         
         return None
     
@@ -1303,6 +1305,24 @@
                 if property.value() == propvalue:
                     property.params()[paramname] = [paramvalue]
     
+    def hasPropertyInAnyComponent(self, properties):
+        """
+        Test for the existence of one or more properties in any component.
+        
+        @param properties: property names to test for
+        @type properties: C{list} or C{tuple}
+        """
+
+        for property in properties:
+            if self.hasProperty(property):
+                return True
+
+        for component in self.subcomponents():
+            if component.hasPropertyInAnyComponent(properties):
+                return True
+
+        return False
+
     def addPropertyToAllComponents(self, property):
         """
         Add a property to all top-level components except VTIMEZONE.
@@ -1326,7 +1346,39 @@
             if component.name() == "VTIMEZONE":
                 continue
             component.replaceProperty(property)
+    
+    def transferProperties(self, from_calendar, properties):
+        """
+        Transfer specified properties from old calendar into all components
+        of this calendar, synthesizing any for new overridden instances.
+ 
+        @param from_calendar: the old calendar to copy from
+        @type from_calendar: L{Component}
+        @param properties: the property names to copy over
+        @type properties: C{typle} or C{list}
+        """
+
+        assert from_calendar.name() == "VCALENDAR", "Not a calendar: %r" % (self,)
         
+        if self.name() == "VCALENDAR":
+            for component in self.subcomponents():
+                if component.name() == "VTIMEZONE":
+                    continue
+                component.transferProperties(from_calendar, properties)
+        else:
+            # Is there a matching component
+            rid = self.getRecurrenceIDUTC()
+            matched = from_calendar.overriddenComponent(rid)
+            
+            # If no match found, we are processing a new overridden instance so copy from the original master
+            if not matched:
+                matched = from_calendar.masterComponent()
+
+            if matched:
+                for propname in properties:
+                    for prop in matched.properties(propname):
+                        self.addProperty(prop)
+            
     def attendeesView(self, attendees):
         """
         Filter out any components that all attendees are not present in. Use EXDATEs

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2008-10-21 15:44:52 UTC (rev 3196)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2008-10-21 16:03:33 UTC (rev 3197)
@@ -46,7 +46,8 @@
 from twistedcaldav.caldavxml import NoUIDConflict
 from twistedcaldav.caldavxml import NumberOfRecurrencesWithinLimits
 from twistedcaldav.caldavxml import caldav_namespace
-from twistedcaldav.customxml import calendarserver_namespace 
+from twistedcaldav.customxml import calendarserver_namespace ,\
+    TwistedCalendarHasPrivateCommentsProperty
 from twistedcaldav.customxml import TwistedCalendarAccessProperty
 from twistedcaldav.fileops import copyToWithXAttrs
 from twistedcaldav.fileops import putWithXAttrs
@@ -693,7 +694,29 @@
             
             # Get current quota state.
             yield self.checkQuota()
-
+    
+            # Check for private comments on the old resource and the new resource and re-insert
+            # ones that are lost.
+            #
+            # NB Do this before implicit scheduling as we don't want old clients to trigger scheduling when
+            # the X- property is missing.
+            if config.Scheduling["CalDAV"].get("EnablePrivateComments", True):
+                old_has_private_comments = self.destination.exists() and self.destinationcal and self.destination.hasDeadProperty(TwistedCalendarHasPrivateCommentsProperty)
+                new_has_private_comments = self.calendar.hasPropertyInAnyComponent((
+                    "X-CALENDARSERVER-PRIVATE-COMMENT",
+                    "X-CALENDARSERVER-ATTENDEE-COMMENT",
+                ))
+                
+                if old_has_private_comments and not new_has_private_comments:
+                    # Transfer old comments to new calendar
+                    log.debug("Private Comments properties were entirely removed by the client. Restoring existing properties.")
+                    old_calendar = self.destination.iCalendar()
+                    self.calendar.transferProperties(old_calendar, (
+                        "X-CALENDARSERVER-PRIVATE-COMMENT",
+                        "X-CALENDARSERVER-ATTENDEE-COMMENT",
+                    ))
+                    self.calendardata = None
+    
             # Do scheduling
             if not self.isiTIP and self.allowImplicitSchedule:
                 scheduler = ImplicitScheduler()
@@ -722,6 +745,14 @@
             # Do the actual put or copy
             response = (yield self.doStore())
             
+
+            # Check for existence of private comments and write property
+            if config.Scheduling["CalDAV"].get("EnablePrivateComments", True):
+                if new_has_private_comments:
+                    self.destination.writeDeadProperty(TwistedCalendarHasPrivateCommentsProperty())
+                elif not self.destinationcal:
+                    self.destination.removeDeadProperty(TwistedCalendarHasPrivateCommentsProperty)                
+
             # Delete the original source if needed.
             if self.deletesource:
                 yield self.doSourceDelete()

Modified: CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py	2008-10-21 15:44:52 UTC (rev 3196)
+++ CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py	2008-10-21 16:03:33 UTC (rev 3197)
@@ -88,13 +88,13 @@
         def duplicateAndNormalize(calendar):
             calendar = calendar.duplicate()
             calendar.normalizePropertyValueLists("EXDATE")
+            calendar.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
             calendar.removeXProperties(("X-CALENDARSERVER-PRIVATE-COMMENT",))
             iTipGenerator.prepareSchedulingMessage(calendar)
             return calendar
 
         # Do straight comparison without alarms
         self.calendar1 = duplicateAndNormalize(self.calendar1)
-        self.calendar1.attendeesView((attendee,))
         self.calendar2 = duplicateAndNormalize(self.calendar2)
 
         if self.calendar1 == self.calendar2:

Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2008-10-21 15:44:52 UTC (rev 3196)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py	2008-10-21 16:03:33 UTC (rev 3197)
@@ -459,7 +459,12 @@
             # Get the ORGANIZER's current copy of the calendar object
             yield self.getOrganizersCopy()
             if self.organizer_calendar:
-            
+
+                if self.resource.exists():
+                    self.oldcalendar = self.resource.iCalendar()
+                else:
+                    self.oldcalendar = None
+
                 # Determine whether the current change is allowed
                 if self.isAttendeeChangeInsignificant():
                     log.debug("Implicit - attendee '%s' is updating UID: '%s' but change is not significant" % (self.attendee, self.uid))
@@ -519,7 +524,11 @@
         instances. Raise an exception if it is not allowed.
         """
         
-        differ = iCalDiff(self.organizer_calendar, self.calendar)
+        oldcalendar = self.oldcalendar
+        if oldcalendar is None:
+            oldcalendar = self.organizer_calendar
+            oldcalendar.attendeesView((self.attendee,))
+        differ = iCalDiff(oldcalendar, self.calendar)
         change_allowed, no_itip = differ.attendeeMerge(self.attendee)
         if not change_allowed:
             log.error("Attendee '%s' is not allowed to make an unauthorized change to an organized event: UID:%s" % (self.attendeePrincipal, self.uid,))

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2008-10-21 15:44:52 UTC (rev 3196)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2008-10-21 16:03:33 UTC (rev 3197)
@@ -89,12 +89,11 @@
 
         year = 2004
 
-        instances = calendar.expandTimeRanges(datetime.date(2100, 0, 0))
+        instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
         for key in instances:
             instance = instances[key]
             start = instance.start
             end = instance.end
-            # FIXME: This logic is wrong
             self.assertEqual(start, datetime.datetime(year, 7, 4))
             self.assertEqual(end  , datetime.datetime(year, 7, 5))
             if year == 2050: break
@@ -106,17 +105,23 @@
         # This event is the Thanksgiving holiday (2 days)
         #
         calendar = Component.fromStream(file(os.path.join(self.data_dir, "Holidays", "C318ABFE-1ED0-11D9-A5E0-000A958A3252.ics")))
-
+        results = {
+            2004: (11, 25, 27),
+            2005: (11, 24, 26),
+            2006: (11, 23, 25),
+            2007: (11, 22, 24),
+            2008: (11, 27, 29),
+        }
         year = 2004
 
-        instances = calendar.expandTimeRanges(datetime.date(2100, 0, 0))
+        instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
         for key in instances:
             instance = instances[key]
             start = instance.start
             end = instance.end
-            # FIXME: This logic is wrong: we want the 3rd Thursday and Friday
-            self.assertEqual(start, datetime.datetime(year, 11, 25))
-            self.assertEqual(end  , datetime.datetime(year, 11, 27))
+            if year in results:
+                self.assertEqual(start, datetime.datetime(year, results[year][0], results[year][1]))
+                self.assertEqual(end  , datetime.datetime(year, results[year][0], results[year][2]))
             if year == 2050: break
             year += 1
 
@@ -126,7 +131,13 @@
         # This event is Father's Day
         #
         calendar = Component.fromStream(file(os.path.join(self.data_dir, "Holidays", "C3186426-1ED0-11D9-A5E0-000A958A3252.ics")))
-
+        results = {
+            2002: (6, 16, 17),
+            2003: (6, 15, 16),
+            2004: (6, 20, 21),
+            2005: (6, 19, 20),
+            2006: (6, 18, 19),
+        }
         year = 2002
 
         instances = calendar.expandTimeRanges(datetime.date(2100, 1, 1))
@@ -134,16 +145,14 @@
             instance = instances[key]
             start = instance.start
             end = instance.end
-            # FIXME: This logic is wrong: we want the 3rd Sunday of June
-            self.assertEqual(start, datetime.datetime(year, 6, 16))
-            self.assertEqual(end  , datetime.datetime(year, 6, 17))
+            if year in results:
+                self.assertEqual(start, datetime.datetime(year, results[year][0], results[year][1]))
+                self.assertEqual(end  , datetime.datetime(year, results[year][0], results[year][2]))
             if year == 2050: break
             year += 1
 
         self.assertEqual(year, 2050)
 
-    test_component_timeranges.todo = "recurrance expansion should give us annual date pairs here"
-
     def test_component_timerange(self):
         """
         Component summary time range query.
@@ -1528,3 +1537,423 @@
             for instance in instances:
                 self.assertTrue(instances[instance].start in results, "%s: %s missing" % (description, instance,))
        
+    def test_has_property_in_any_component(self):
+        
+        data = (
+            (
+                "Single component - True",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+END:VEVENT
+END:VCALENDAR
+""",
+                ("DTSTART",),
+                True,
+            ),
+            (
+                "Single component - False",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+END:VEVENT
+END:VCALENDAR
+""",
+                ("DTEND",),
+                False,
+            ),
+            (
+                "Multiple components - True in both",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+END:VEVENT
+END:VCALENDAR
+""",
+                ("DTSTART",),
+                True,
+            ),
+            (
+                "Multiple components - True in one",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+END:VEVENT
+END:VCALENDAR
+""",
+                ("RECURRENCE-ID",),
+                True,
+            ),
+            (
+                "Multiple components - False",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+END:VEVENT
+END:VCALENDAR
+""",
+                ("DTEND",),
+                False,
+            ),
+            (
+                "Multiple components/propnames - True in both",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+END:VEVENT
+END:VCALENDAR
+""",
+                ("DTSTART", "RECURRENCE-ID",),
+                True,
+            ),
+            (
+                "Multiple components - True in one",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+END:VEVENT
+END:VCALENDAR
+""",
+                ("STATUS", "RECURRENCE-ID",),
+                True,
+            ),
+            (
+                "Multiple components - False",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+END:VEVENT
+END:VCALENDAR
+""",
+                ("STATUS", "DTEND",),
+                False,
+            ),
+        )
+        
+        for description, caldata, propnames, result in data:
+            component = Component.fromString(caldata)
+            self.assertTrue(component.hasPropertyInAnyComponent(propnames) == result, "Property name match incorrect: %s" % (description,))
+       
+    def test_transfer_properties(self):
+        
+        data = (
+            (
+                "Non recurring - one property",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM1:True
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM2:True
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM1:True
+X-ITEM2:True
+END:VEVENT
+END:VCALENDAR
+""",
+            ("X-ITEM2",),
+            ),
+            (
+                "Non recurring - two properties",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM1:True
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM2:True
+X-ITEM3:True
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM1:True
+X-ITEM2:True
+X-ITEM3:True
+END:VEVENT
+END:VCALENDAR
+""",
+            ("X-ITEM2","X-ITEM3",),
+            ),
+            (
+                "Non recurring - two properties - one overlap",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM1:True
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM2:True
+X-ITEM1:False
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+X-ITEM1:True
+X-ITEM2:True
+X-ITEM1:False
+END:VEVENT
+END:VCALENDAR
+""",
+            ("X-ITEM2","X-ITEM1",),
+            ),
+            (
+                "Non recurring - one property",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+X-ITEM1:True
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+X-ITEM1:False
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+X-ITEM2:True
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+X-ITEM2:False
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+X-ITEM1:True
+X-ITEM2:True
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+X-ITEM1:False
+X-ITEM2:False
+END:VEVENT
+END:VCALENDAR
+""",
+            ("X-ITEM2",),
+            ),
+            (
+                "Non recurring - new override, one property",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+X-ITEM1:True
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+X-ITEM1:False
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+X-ITEM2:True
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DURATION:P1H
+RRULE:FREQ=DAILY
+X-ITEM1:True
+X-ITEM2:True
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071115T000000Z
+DTSTART:20071115T010000Z
+DURATION:P1H
+X-ITEM1:False
+X-ITEM2:True
+END:VEVENT
+END:VCALENDAR
+""",
+            ("X-ITEM2",),
+            ),
+        )
+        
+        for description, transfer_to, transfer_from, result, propnames in data:
+            component_to = Component.fromString(transfer_to)
+            component_from = Component.fromString(transfer_from)
+            component_result = Component.fromString(result)
+            component_to.transferProperties(component_from, propnames)
+            self.assertEqual(str(component_to), str(component_result), "%s: mismatch" % (description,))
+       
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20081021/0082b3ba/attachment-0001.html 


More information about the calendarserver-changes mailing list