[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