[CalendarServer-changes] [8127] CalendarServer/trunk/twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Wed Sep 28 07:50:46 PDT 2011
Revision: 8127
http://trac.macosforge.org/projects/calendarserver/changeset/8127
Author: cdaboo at apple.com
Date: 2011-09-28 07:50:44 -0700 (Wed, 28 Sep 2011)
Log Message:
-----------
Do proper SEQUENCE processing to cope without of order iTIP we might get from external servers.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
CalendarServer/trunk/twistedcaldav/scheduling/itip.py
CalendarServer/trunk/twistedcaldav/scheduling/test/test_itip.py
CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2011-09-27 23:52:37 UTC (rev 8126)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2011-09-28 14:50:44 UTC (rev 8127)
@@ -526,6 +526,7 @@
def overriddenComponent(self, recurrence_id):
"""
Return the overridden iCal component in this calendar matching the supplied RECURRENCE-ID property.
+ This also returns the matching master component if recurrence_id is C{None}.
@param recurrence_id: The RECURRENCE-ID property value to match.
@type recurrence_id: L{PyCalendarDateTime}
@@ -729,7 +730,7 @@
"""
Return the trigger information for the specified alarm component.
@param component: the Component whose start should be returned.
- @return: ta tuple consisting of:
+ @return: a tuple consisting of:
trigger : the 'native' trigger value
related : either True (for START) or False (for END)
repeat : an integer for the REPEAT count
@@ -1890,7 +1891,7 @@
@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}
+ @type properties: C{tuple} or C{list}
"""
assert from_calendar.name() == "VCALENDAR", "Not a calendar: %r" % (self,)
@@ -1947,7 +1948,7 @@
if not found_all_attendees:
removed_master = True
- # Now remove the unwanted components - but we may need to exdate the master
+ # Now remove the unwanted components - but we may need to EXDATE the master
exdates = []
for component in remove_components:
rid = component.getRecurrenceIDUTC()
@@ -2085,6 +2086,129 @@
for param, value in paramvalues:
prop.removeParameterValue(param, value)
+ def getITIPInfo(self):
+ """
+ Get property value details needed to synchronize iTIP components.
+
+ @return: C{tuple} of (uid, seq, dtstamp, r-id) some of which may be C{None} if property does not exist
+ """
+ try:
+ # Extract items from component
+ uid = self.propertyValue("UID")
+ seq = self.propertyValue("SEQUENCE")
+ if seq:
+ seq = int(seq)
+ dtstamp = self.propertyValue("DTSTAMP")
+ rid = self.propertyValue("RECURRENCE-ID")
+
+ except ValueError:
+ return (None, None, None, None)
+
+ return (uid, seq, dtstamp, rid)
+
+ @staticmethod
+ def compareComponentsForITIP(component1, component2, use_dtstamp=True):
+ """
+ Compare synchronization information for two components to see if they match according to iTIP.
+
+ @param component1: first component to check.
+ @type component1: L{Component}
+ @param component2: second component to check.
+ @type component2: L{Component}
+ @param use_dtstamp: whether DTSTAMP is used in addition to SEQUENCE.
+ @type component2: C{bool}
+
+ @return: 0, 1, -1 as per compareSyncInfo.
+ """
+ info1 = (None,) + Component.getITIPInfo(component1)
+ info2 = (None,) + Component.getITIPInfo(component2)
+ return Component.compareITIPInfo(info1, info2, use_dtstamp)
+
+ @staticmethod
+ def compareITIPInfo(info1, info2, use_dtstamp=True):
+ """
+ Compare two synchronization information records.
+
+ @param info1: a C{tuple} as returned by L{getSyncInfo}.
+ @param info2: a C{tuple} as returned by L{getSyncInfo}.
+ @return: 1 if info1 > info2, 0 if info1 == info2, -1 if info1 < info2
+ """
+
+ _ignore_name1, uid1, seq1, dtstamp1, _ignore_rid1 = info1
+ _ignore_name2, uid2, seq2, dtstamp2, _ignore_rid2 = info2
+
+ # UIDs MUST match
+ assert uid1 == uid2
+
+ # Look for sequence
+ if (seq1 is not None) and (seq2 is not None):
+ if seq1 > seq2:
+ return 1
+ if seq1 < seq2:
+ return -1
+ elif (seq1 is not None) and (seq2 is None):
+ return 1
+ elif (seq1 is None) and (seq2 is not None):
+ return -1
+
+ # Look for DTSTAMP
+ if use_dtstamp:
+ if (dtstamp1 is not None) and (dtstamp2 is not None):
+ if dtstamp1 > dtstamp2:
+ return 1
+ if dtstamp1 < dtstamp2:
+ return -1
+ elif (dtstamp1 is not None) and (dtstamp2 is None):
+ return 1
+ elif (dtstamp1 is None) and (dtstamp2 is not None):
+ return -1
+
+ return 0
+
+ def needsiTIPSequenceChange(self, oldcalendar):
+ """
+ Compare this calendar with the old one and indicate whether the current one has SEQUENCE
+ that is always greater than the old.
+ """
+
+ for component in self.subcomponents():
+ if component.name() in ignoredComponents:
+ continue
+ oldcomponent = oldcalendar.overriddenComponent(component.getRecurrenceIDUTC())
+ if oldcomponent is None:
+ oldcomponent = oldcalendar.masterComponent()
+ if oldcomponent is None:
+ continue
+ newseq = component.propertyValue("SEQUENCE")
+ if newseq is None:
+ newseq = 0
+ oldseq = oldcomponent.propertyValue("SEQUENCE")
+ if oldseq is None:
+ oldseq = 0
+ if newseq <= oldseq:
+ return True
+
+ return False
+
+ def bumpiTIPInfo(self, oldcalendar=None, doSequence=False):
+ """
+ Change DTSTAMP and optionally SEQUENCE on all components.
+ """
+
+ if doSequence:
+
+ def maxSequence(calendar):
+ seqs = calendar.getAllPropertiesInAnyComponent("SEQUENCE", depth=1)
+ return max(seqs, key=lambda x:x.value()).value() if seqs else 0
+
+ # Determine value to bump to from old calendar (if exists) or self
+ newseq = maxSequence(oldcalendar if oldcalendar is not None else self) + 1
+
+ # Bump all components
+ self.replacePropertyInAllComponents(Property("SEQUENCE", newseq))
+
+ self.replacePropertyInAllComponents(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
+
def normalizeAll(self):
# Normalize all properties
Modified: CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py 2011-09-27 23:52:37 UTC (rev 8126)
+++ CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py 2011-09-28 14:50:44 UTC (rev 8127)
@@ -722,7 +722,6 @@
"DTSTAMP",
"CREATED",
"LAST-MODIFIED",
- "SEQUENCE",
"X-CALENDARSERVER-PRIVATE-COMMENT",
):
continue
Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2011-09-27 23:52:37 UTC (rev 8126)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2011-09-28 14:50:44 UTC (rev 8127)
@@ -475,6 +475,8 @@
self.cancelledAttendees = ()
self.reinvites = None
self.needs_action_rids = None
+
+ self.needs_sequence_change = False
# Check for a delete
if self.action == "remove":
@@ -484,6 +486,9 @@
# Cancel all attendees
self.cancelledAttendees = [(attendee, None) for attendee in self.attendees]
+
+ # CANCEL always bumps sequence
+ self.needs_sequence_change = True
# Check for a new resource or an update
elif self.action == "modify":
@@ -524,6 +529,10 @@
# Check for removed attendees
if not recurrence_reschedule:
self.findRemovedAttendees()
+
+ # For now we always bump the sequence number on modifications because we cannot track DTSTAMP on
+ # the Attendee side. But we check the old and the new and only bump if the client did not already do it.
+ self.needs_sequence_change = self.calendar.needsiTIPSequenceChange(self.oldcalendar)
elif self.action == "create":
log.debug("Implicit - organizer '%s' is creating UID: '%s'" % (self.organizer, self.uid))
@@ -534,6 +543,9 @@
if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION").upper() == "NEEDS-ACTION":
attendee.setParameter("RSVP", "TRUE")
+ if self.needs_sequence_change:
+ self.calendar.bumpiTIPInfo(oldcalendar=self.oldcalendar, doSequence=True)
+
yield self.scheduleWithAttendees()
# Always clear SCHEDULE-FORCE-SEND from all attendees after scheduling
Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2011-09-27 23:52:37 UTC (rev 8126)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2011-09-28 14:50:44 UTC (rev 8127)
@@ -15,7 +15,7 @@
##
"""
-iTIP (RFC2446) processing.
+iTIP (RFC5546) processing.
"""
#
@@ -33,7 +33,8 @@
from twext.python.log import Logger
from twistedcaldav.config import config
-from twistedcaldav.ical import Property, iCalendarProductID, Component
+from twistedcaldav.ical import Property, iCalendarProductID, Component,\
+ ignoredComponents
from pycalendar.datetime import PyCalendarDateTime
@@ -83,10 +84,14 @@
@return: a C{tuple} of:
calendar object ready to save, or C{None} (request should be ignored)
- a C{set} of iCalendar properties that changed, or C{None},
a C{set} of recurrences that changed, or C{None}
"""
+ # Check sequencing
+ if not iTipProcessing.sequenceComparison(itip_message, calendar):
+ # Ignore out of sequence message
+ return None, None
+
# Merge Organizer data with Attendee's own changes (VALARMs, Comment only for now).
from twistedcaldav.scheduling.icaldiff import iCalDiff
rids = iCalDiff(calendar, itip_message, False).whatIsDifferent()
@@ -189,6 +194,11 @@
assert itip_message.propertyValue("METHOD") == "CANCEL", "iTIP message must have METHOD:CANCEL"
assert itip_message.resourceUID() == calendar.resourceUID(), "UIDs must be the same to process iTIP message"
+ # Check sequencing
+ if not iTipProcessing.sequenceComparison(itip_message, calendar):
+ # Ignore out of sequence message
+ return False, False, None
+
# Check to see if this is a cancel of the entire event
if itip_message.masterComponent() is not None:
if autoprocessing:
@@ -263,6 +273,8 @@
Process a METHOD=REPLY.
TODO: Yes, I am going to ignore RANGE= on RECURRENCE-ID for now...
+ TODO: We have no way to track SEQUENCE/DTSTAMP on a per-attendee basis to correctly serialize out-of-order
+ replies.
@param itip_message: the iTIP message calendar object to process.
@type itip_message:
@@ -511,10 +523,93 @@
if component.name() == "VEVENT":
component.replaceProperty(Property("TRANSP", "TRANSPARENT"))
+ @staticmethod
+ def sequenceComparison(itip, calendar):
+ """
+ Do appropriate itip message sequencing based by comparison with existing calendar data.
+
+ @return: C{True} if the itip message is new and should be processed, C{False}
+ if no processing is needed
+ @rtype: C{bool}
+ """
+
+ # Master component comparison trumps all else
+ itip_master = itip.masterComponent()
+ cal_master = calendar.masterComponent()
+
+ # If master component exists, compare all in iTIP and update if any are new
+ if cal_master:
+ for itip_component in itip.subcomponents():
+ if itip_component.name() in ignoredComponents:
+ continue
+ cal_component = calendar.overriddenComponent(itip_component.getRecurrenceIDUTC())
+ if cal_component is None:
+ cal_component = cal_master
+
+ # TODO: No DTSTAMP comparison because we do not track DTSTAMPs
+ # Treat components the same as meaning so an update - in theory no harm in doing that
+ if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
+ return True
+
+ return False
+
+ elif itip_master:
+
+ # Do comparison of each appropriate component if any one is new, process the itip
+ for cal_component in calendar.subcomponents():
+ if cal_component.name() in ignoredComponents:
+ continue
+ itip_component = itip.overriddenComponent(cal_component.getRecurrenceIDUTC())
+ if itip_component is None:
+ itip_component = itip_master
+
+ # TODO: No DTSTAMP comparison because we do not track DTSTAMPs
+ # Treat components the same as meaning so an update - in theory no harm in doing that
+ if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
+ return True
+
+ return False
+
+ else:
+ # Do comparison of each matching component if any one is new, process the entire itip.
+ # There is a race condition here, similar to REPLY, where we could reinstate an instance
+ # that has been removed. Not much we can do about it without additional tracking.
+
+ cal_rids = set()
+ for cal_component in calendar.subcomponents():
+ if cal_component.name() in ignoredComponents:
+ continue
+ cal_rids.add(cal_component.getRecurrenceIDUTC())
+ itip_rids = set()
+ for itip_component in itip.subcomponents():
+ if itip_component.name() in ignoredComponents:
+ continue
+ itip_rids.add(itip_component.getRecurrenceIDUTC())
+
+ # Compare ones that match
+ for rid in cal_rids & itip_rids:
+ cal_component = calendar.overriddenComponent(rid)
+ itip_component = itip.overriddenComponent(rid)
+
+ # TODO: No DTSTAMP comparison because we do not track DTSTAMPs
+ # Treat components the same as meaning so an update - in theory no harm in doing that
+ if Component.compareComponentsForITIP(itip_component, cal_component, use_dtstamp=False) >= 0:
+ return True
+
+ # If there are others in one set and not the other - always process, else no process
+ return len(cal_rids ^ itip_rids) > 0
+
class iTipGenerator(object):
+ """
+ This assumes that DTSTAMP and SEQUENCE are already at their new values in the original calendar
+ data passed in to each generateXXX() call.
+ """
@staticmethod
def generateCancel(original, attendees, instances=None, full_cancel=False):
+ """
+ This assumes that SEQUENCE is already at its new value in the original calendar data.
+ """
itip = Component("VCALENDAR")
itip.addProperty(Property("VERSION", "2.0"))
@@ -540,18 +635,16 @@
assert instance is not None, "Need a master component"
# Add some required properties extracted from the original
- comp.addProperty(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
+ comp.addProperty(Property("DTSTAMP", instance.propertyValue("DTSTAMP")))
comp.addProperty(Property("UID", instance.propertyValue("UID")))
- seq = instance.propertyValue("SEQUENCE")
- seq = int(seq) + 1 if seq else 1
- comp.addProperty(Property("SEQUENCE", seq))
+ comp.addProperty(Property("SEQUENCE", instance.propertyValue("SEQUENCE") if instance.hasProperty("SEQUENCE") else 0))
comp.addProperty(instance.getOrganizerProperty())
if instance_rid:
comp.addProperty(Property("RECURRENCE-ID", instance_rid.duplicate().adjustToUTC()))
def addProperties(propname):
- for property in instance.properties(propname):
- comp.addProperty(property)
+ for icalproperty in instance.properties(propname):
+ comp.addProperty(icalproperty)
addProperties("SUMMARY")
addProperties("DTSTART")
@@ -589,24 +682,22 @@
@staticmethod
def generateAttendeeRequest(original, attendees, filter_rids):
-
+ """
+ This assumes that SEQUENCE is already at its new value in the original calendar data.
+ """
+
# Start with a copy of the original as we may have to modify bits of it
itip = original.duplicate()
itip.replaceProperty(Property("PRODID", iCalendarProductID))
itip.addProperty(Property("METHOD", "REQUEST"))
- # Force update to DTSTAMP everywhere
- itip.replacePropertyInAllComponents(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
-
# Now filter out components that do not contain every attendee
itip.attendeesView(attendees, onlyScheduleAgentServer=True)
# Now filter out components except the ones specified
if itip.filterComponents(filter_rids):
-
# Strip out unwanted bits
iTipGenerator.prepareSchedulingMessage(itip)
-
return itip
else:
@@ -623,7 +714,7 @@
# Now filter out components except the ones specified
itip.filterComponents(changedRids)
- # Force update to DTSTAMP everywhere
+ # Force update to DTSTAMP everywhere so reply sequencing will work
itip.replacePropertyInAllComponents(Property("DTSTAMP", PyCalendarDateTime.getNowUTC()))
# Remove all attendees except the one we want
Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_itip.py 2011-09-27 23:52:37 UTC (rev 8126)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_itip.py 2011-09-28 14:50:44 UTC (rev 8127)
@@ -18,8 +18,8 @@
from pycalendar.timezone import PyCalendarTimezone
from twistedcaldav.ical import Component
from twistedcaldav.scheduling.itip import iTipProcessing, iTipGenerator
+import twistedcaldav.test.util
import os
-import twistedcaldav.test.util
class iTIPProcessing (twistedcaldav.test.util.TestCase):
"""
@@ -899,6 +899,501 @@
msg=description
)
+ def test_sequenceComparison(self):
+ """
+ Test iTIPProcessing.sequenceComparison
+ """
+
+ data = (
+ (
+ "1.1 Simple Update - SEQUENCE change",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "1.2 Simple Update - DTSTAMP change",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T010000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "1.3 Simple Update - no change",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "2.1 Recurrence add changed SEQUENCE instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T010000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "2.2 Recurrence add changed DTSTAMP instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "2.3 Recurrence add unchanged instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071115T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "3.1 Recurrence master/no-master changed SEQUENCE instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:CANCEL
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "3.2 Recurrence master/no-master old SEQUENCE instance no prior instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:CANCEL
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "3.3 Recurrence master/no-master old SEQUENCE instance with prior instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+SEQUENCE:2
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:CANCEL
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "4.1 Recurrence no-master/master changed SEQUENCE master",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHID:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "4.2 Recurrence no-master/master changed DTSTAMP master",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T000000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHID:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T010000Z
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "4.3 Recurrence no-master/master old SEQUENCE instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHID:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "4.4 Recurrence no-master/master changed SEQUENCE instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHID:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+DTSTAMP:20071114T000000Z
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "5.1 Recurrence no-masters changed SEQUENCE same instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHID:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "5.2 Recurrence no-masters changed DTSTAMP same instance",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHID:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T020000Z
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "5.3 Recurrence no-masters changed SEQUENCE different instances",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071116T000000Z
+DTSTART:20071116T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHID:REQUEST
+BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20071117T000000Z
+DTSTART:20071117T000000Z
+DTSTAMP:20071114T010000Z
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ )
+
+ for title, calendar_txt, itip_txt, expected in data:
+ calendar = Component.fromString(calendar_txt)
+ itip = Component.fromString(itip_txt)
+
+ result = iTipProcessing.sequenceComparison(itip, calendar)
+ self.assertEqual(result, expected, msg="Result mismatch: %s" % (title,))
+
+
class iTIPGenerator (twistedcaldav.test.util.TestCase):
"""
iCalendar support tests
@@ -962,6 +1457,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
""",
@@ -974,6 +1470,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
""",
@@ -1229,6 +1726,7 @@
ATTENDEE:mailto:user2 at example.com
ATTENDEE:mailto:user3 at example.com
ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
""",
@@ -1260,7 +1758,7 @@
ATTENDEE:mailto:user2 at example.com
ATTENDEE:mailto:user3 at example.com
ORGANIZER:mailto:user1 at example.com
-SEQUENCE:1
+SEQUENCE:2
END:VEVENT
END:VCALENDAR
""",
@@ -1293,6 +1791,7 @@
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
""",
@@ -1325,6 +1824,7 @@
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
+SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
UID:12345-67890-4
@@ -1332,6 +1832,7 @@
DTSTART:20081114T010000Z
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
""",
@@ -1364,6 +1865,7 @@
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
+SEQUENCE:1
END:VEVENT
BEGIN:VEVENT
UID:12345-67890-5
@@ -1371,6 +1873,7 @@
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
+SEQUENCE:1
END:VEVENT
END:VCALENDAR
""",
Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2011-09-27 23:52:37 UTC (rev 8126)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2011-09-28 14:50:44 UTC (rev 8127)
@@ -4564,3 +4564,743 @@
calendar = Component.fromString(text)
for rid, result in results:
self.assertEqual(calendar.perUserTransparency(rid), result, "Failed comparison: %s %s" % (title, rid,))
+
+ def test_needsiTIPSequenceChange(self):
+
+ data = (
+ (
+ "Simple old < new",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:1
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Simple old == new",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:1
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:1
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Simple old > new",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:2
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:1
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Recurring same instances all old < new",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:2
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Recurring same instances some old == new",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:2
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:2
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Recurring derived instance all old < new",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SEQUENCE:1
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:2
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Recurring derived instance some old == new",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SEQUENCE:2
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SEQUENCE:2
+SUMMARY:Test
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SEQUENCE:2
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ )
+
+ for title, old_txt, new_txt, result in data:
+ ical_old = Component.fromString(old_txt)
+ ical_new = Component.fromString(new_txt)
+ self.assertEqual(ical_new.needsiTIPSequenceChange(ical_old), result, "Failed: %s" % (title,))
+
+ def test_bumpiTIPInfo(self):
+
+ data = (
+ (
+ "Simple no sequence, no sequence change",
+ None,
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Simple sequence, no sequence change",
+ None,
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Simple no sequence, sequence change",
+ None,
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Simple sequence, sequence change",
+ None,
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Simple sequence, sequence change, old calendar",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:3
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Recurring override no sequence, no sequence change",
+ None,
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Recurring override vary sequence, no sequence change",
+ None,
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ False,
+ ),
+ (
+ "Recurring override no sequence, sequence change",
+ None,
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Recurring override vary sequence, sequence change",
+ None,
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+SEQUENCE:3
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:3
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ (
+ "Recurring override vary sequence, sequence change, old calendar",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:3
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+SEQUENCE:4
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+SEQUENCE:4
+END:VEVENT
+END:VCALENDAR
+""",
+ True,
+ ),
+ )
+
+ for title, old_txt, ical_txt, result_txt, doSequence in data:
+ old = Component.fromString(old_txt) if old_txt else None
+ ical = Component.fromString(ical_txt)
+ result = Component.fromString(result_txt)
+ ical.bumpiTIPInfo(oldcalendar=old, doSequence=doSequence)
+
+ ical1 = str(ical).split("\n")
+ ical2 = str(result).split("\n")
+
+ # Check without DTSTAMPs which we expect to be different
+ ical1_withoutDTSTAMP = [item for item in ical1 if not item.startswith("DTSTAMP:")]
+ ical2_withoutDTSTAMP = [item for item in ical2 if not item.startswith("DTSTAMP:")]
+
+ diff = "\n".join(unified_diff(ical1_withoutDTSTAMP, ical2_withoutDTSTAMP))
+ self.assertEqual("\n".join(ical1_withoutDTSTAMP), "\n".join(ical2_withoutDTSTAMP), "Failed comparison: %s\n%s" % (title, diff,))
+
+ # Check that all DTSTAMPs changed
+ dtstamps1 = set([item for item in ical1 if item.startswith("DTSTAMP:")])
+ dtstamps2 = set([item for item in ical2 if item.startswith("DTSTAMP:")])
+
+ diff = "\n".join(unified_diff(ical1, ical2))
+ self.assertEqual(len(dtstamps1 & dtstamps2), 0, "Failed comparison: %s\n%s" % (title, diff,))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110928/6658175c/attachment-0001.html>
More information about the calendarserver-changes
mailing list