[CalendarServer-changes] [3811] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed Mar 11 07:59:16 PDT 2009
Revision: 3811
http://trac.macosforge.org/projects/calendarserver/changeset/3811
Author: cdaboo at apple.com
Date: 2009-03-11 07:59:15 -0700 (Wed, 11 Mar 2009)
Log Message:
-----------
Improved attendee PUT merging behavior to treat the server data as the primary state and only merge
per-Attendee properties from the PUT data into that (whilst checking for invalid changes to only
date-time related properties). This should eliminate bogus valid-attendee-change errors.
Modified Paths:
--------------
CalendarServer/trunk/run
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_icaldiff.py
Modified: CalendarServer/trunk/run
===================================================================
--- CalendarServer/trunk/run 2009-03-11 14:52:12 UTC (rev 3810)
+++ CalendarServer/trunk/run 2009-03-11 14:59:15 UTC (rev 3811)
@@ -707,7 +707,7 @@
caldavtester="${top}/CalDAVTester";
-svn_get "CalDAVTester" "${caldavtester}" "${svn_uri_base}/CalDAVTester/trunk" 3795;
+svn_get "CalDAVTester" "${caldavtester}" "${svn_uri_base}/CalDAVTester/trunk" 3810;
#
# PyFlakes
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2009-03-11 14:52:12 UTC (rev 3810)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2009-03-11 14:59:15 UTC (rev 3811)
@@ -926,13 +926,19 @@
return True
return False
- def deriveInstance(self, rid):
+ def deriveInstance(self, rid, allowCancelled=False):
"""
Derive an instance from the master component that has the provided RECURRENCE-ID, but
- with all other properties, components etc from the master.
+ with all other properties, components etc from the master. If the requested override is
+ currently marked as an EXDATE in the existing master, allow an option whereby the override
+ is added as STATUS:CANCELLED and the EXDATE removed.
@param rid: recurrence-id value
@type rid: L{datetime.datetime}
+ @param allowCancelled: whether to allow a STATUS:CANCELLED override
+ @type allowCancelled: C{bool}
+
+ @return: L{Component} for newly derived instance, or None if not valid override
"""
# Must have a master component
@@ -942,16 +948,26 @@
# TODO: Check that the recurrence-id is a valid instance
# For now we just check that there is no matching EXDATE
- for exdate in self.properties("EXDATE"):
- if exdate == rid:
- return None
+ didCancel = False
+ for exdate in tuple(master.properties("EXDATE")):
+ for exdateValue in exdate.value():
+ if exdateValue == rid:
+ if allowCancelled:
+ exdate.value().remove(exdateValue)
+ if len(exdate.value()) == 0:
+ master.removeProperty(exdate)
+ didCancel = True
+ break
+ else:
+ # Cannot derive from an existing EXDATE
+ 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"]:
+ if property.name() in ("RRULE", "RDATE", "EXRULE", "EXDATE", "RECURRENCE-ID",):
newcomp.removeProperty(property)
# New DTSTART is the RECURRENCE-ID we are deriving but adjusted to the
@@ -974,7 +990,10 @@
except KeyError:
rid_params = {}
newcomp.addProperty(Property("RECURRENCE-ID", dtstart.value(), params=rid_params))
-
+
+ if didCancel:
+ newcomp.addProperty(Property("STATUS", "CANCELLED"))
+
return newcomp
def resourceUID(self):
Modified: CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py 2009-03-11 14:52:12 UTC (rev 3810)
+++ CalendarServer/trunk/twistedcaldav/scheduling/icaldiff.py 2009-03-11 14:59:15 UTC (rev 3811)
@@ -14,7 +14,8 @@
# limitations under the License.
##
-from twistedcaldav.dateops import normalizeToUTC, toString
+from twistedcaldav.dateops import normalizeToUTC, toString,\
+ normalizeStartEndDuration
from twistedcaldav.ical import Component, Property
from twistedcaldav.log import Logger
from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
@@ -220,80 +221,305 @@
else:
new_property.params()[parameter] = paramvalue
- def attendeeDiff(self, attendee):
+ def attendeeMerge(self, attendee):
"""
Merge the ATTENDEE specific changes with the organizer's view of the attendee's event.
This will remove any attempt by the attendee to change things like the time or location.
@param attendee: the value of the ATTENDEE property corresponding to the attendee making the change
@type attendee: C{str}
+
+ @return: C{tuple} of:
+ C{bool} - change is allowed
+ C{bool} - iTIP reply needs to be sent
+ C{list} - list of RECURRENCE-IDs changed
+ L{Component} - new calendar object to store
"""
self.attendee = normalizeCUAddr(attendee)
- # If smart merge is needed we have to do this before trying the diff
- if self.smart_merge:
- log.debug("attendeeDiff: doing smart Attendee diff/merge")
- self._attendeeMerge()
+ self.newCalendar = self.calendar1.duplicate()
+ self.newMaster = self.newCalendar.masterComponent()
- # Do straight comparison without alarms
- self.originalCalendar1 = self.calendar1
- self.originalCalendar2 = self.calendar2
- self.calendar1 = self._attendeeDuplicateAndNormalize(self.calendar1)
- self.calendar2 = self._attendeeDuplicateAndNormalize(self.calendar2)
+ changeCausesReply = False
+ changedRids = []
+
+ # First get uid/rid map of components
+ def mapComponents(calendar):
+ map = {}
+ cancelledRids = set()
+ master = None
+ for component in calendar.subcomponents():
+ if component.name() == "VTIMEZONE":
+ continue
+ name = component.name()
+ uid = component.propertyValue("UID")
+ rid = component.getRecurrenceIDUTC()
+ map[(name, uid, rid,)] = component
+ if component.propertyValue("STATUS") == "CANCELLED" and rid is not None:
+ cancelledRids.add(rid)
+ if rid is None:
+ master = component
+
+ # Normalize each master by adding any STATUS:CANCELLED components as EXDATEs
+ exdates = set()
+ if master:
+ # Get all EXDATEs in UTC
+ for exdate in master.properties("EXDATE"):
+ exdates.update([normalizeToUTC(value) for value in exdate.value()])
+
+ return exdates, map
+
+ exdates1, map1 = mapComponents(self.calendar1)
+ set1 = set(map1.keys())
+ exdates2, map2 = mapComponents(self.calendar2)
+ set2 = set(map2.keys())
- if self.calendar1 == self.calendar2:
- return True, True
+ # All the components in calendar1 must be in calendar2 unless they are CANCELLED
+ result = set1 - set2
+ for key in result:
+ _ignore_name, _ignore_uid, rid = key
+ component = map1[key]
+ if component.propertyValue("STATUS") != "CANCELLED":
+ # Attendee may decline by EXDATE'ing an instance - we need to handle that
+ if rid in exdates2:
+ # Mark Attendee as DECLINED in the server instance
+ if self._attendeeDecline(self.newCalendar.overriddenComponent(rid)):
+ changeCausesReply = True
+ changedRids.append(rid)
+ else:
+ log.debug("attendeeMerge: Missing uncancelled component from first calendar: %s" % (key,))
+ return False, False, (), None
+ else:
+ if rid not in exdates2:
+ log.debug("attendeeMerge: Missing EXDATE for cancelled components from first calendar: %s" % (key,))
+ return False, False, (), None
+ else:
+ # Remove the CANCELLED component from the new calendar and add an EXDATE
+ overridden = self.newCalendar.overriddenComponent(rid)
+ self.newCalendar.removeComponent(overridden)
+ if self.newMaster:
+ self.newMaster.addProperty(Property("EXDATE", (rid,)))
+
+ # Derive a new component in the new calendar for each new one in set2
+ for key in set2 - set1:
+
+ # First check if the attendee's copy is cancelled and properly EXDATE'd
+ # and skip it if so.
+ _ignore_name, _ignore_uid, rid = key
+ component2 = map2[key]
+ if component2.propertyValue("STATUS") == "CANCELLED":
+ if rid not in exdates1:
+ log.debug("attendeeMerge: Cancelled component not found in first calendar (or no EXDATE): %s" % (key,))
+ return False, False, (), None
+ else:
+ # Derive new component with STATUS:CANCELLED and remove EXDATE
+ newOverride = self.newCalendar.deriveInstance(rid, allowCancelled=True)
+ if newOverride is None:
+ log.debug("attendeeMerge: Could not derive instance for cancelled component: %s" % (key,))
+ return False, False, (), None
+ self.newCalendar.addComponent(newOverride)
+ else:
+ # Derive new component
+ newOverride = self.newCalendar.deriveInstance(rid)
+ if newOverride is None:
+ log.debug("attendeeMerge: Could not derive instance for uncancelled component: %s" % (key,))
+ return False, False, (), None
+ self.newCalendar.addComponent(newOverride)
- # Need to look at each component and do special comparisons
+ # So now newCalendar has all the same components as set2. Check changes and do transfers.
# Make sure the same VCALENDAR properties match
- if not self._checkVCALENDARProperties():
- self._logDiffError("attendeeDiff: VCALENDAR properties do not match")
+ if not self._checkVCALENDARProperties(self.newCalendar, self.calendar2):
+ self._logDiffError("attendeeMerge: VCALENDAR properties do not match")
+ return False, False, (), None
+
+ # Now we transfer per-Attendee
+ # data from calendar2 into newCalendar to sync up changes, whilst verifying that other
+ # key properties are unchanged
+ declines = []
+ for key in set2:
+ _ignore_name, _ignore_uid, rid = key
+ serverData = self.newCalendar.overriddenComponent(rid)
+ clientData = map2[key]
+
+ allowed, reply = self._transferAttendeeData(serverData, clientData, declines)
+ if not allowed:
+ self._logDiffError("attendeeMerge: Mismatched calendar objects")
+ return False, False, (), None
+ changeCausesReply |= reply
+ if reply:
+ changedRids.append(rid)
+
+ # We need to derive instances for any declined using an EXDATE
+ for decline in sorted(declines):
+ overridden = self.newCalendar.overriddenComponent(decline)
+ if not overridden:
+ overridden = self.newCalendar.deriveInstance(decline)
+ if overridden:
+ self.newCalendar.addComponent(overridden)
+ if self._attendeeDecline(overridden):
+ changeCausesReply = True
+ changedRids.append(decline)
+ else:
+ log.debug("Unable to override and instance to mark as DECLINED: %s" % (decline,))
+ return False, False, (), None
+
+ return True, changeCausesReply, changedRids, self.newCalendar
+
+ def _checkVCALENDARProperties(self, serverData, clientData):
+
+ self._transferProperty("X-CALENDARSERVER-ACCESS", serverData, clientData)
+
+ # Get property differences in the VCALENDAR objects
+ propdiff = set(serverData.properties()) ^ set(clientData.properties())
+
+ # Ignore certain properties
+ ignored = ("PRODID", "CALSCALE",)
+ propdiff = set([prop for prop in propdiff if prop.name() not in ignored])
+
+ result = len(propdiff) == 0
+ if not result:
+ log.debug("VCALENDAR properties differ: %s" % (propdiff,))
+ return result
+
+ def _transferAttendeeData(self, serverComponent, clientComponent, declines):
+
+ # First check validity of date-time related properties
+ if not self._checkInvalidChanges(serverComponent, clientComponent, declines):
return False, False
- # Make sure the same VTIMEZONE components appear
- tzidRemapping = False
- if not self._compareVTIMEZONEs():
- # Not an error any more. Instead we need to merge back the original TZIDs
- # into the event being written.
- tzidRemapping = True
+ # Now look for items to transfer from one to the other.
+ # We care about the ATTENDEE's PARTSTAT, TRANSP, VALARMS, X-APPLE-NEEDS-REPLY,
+ # DTSTAMP, LAST-MODIFIED
- # Compare each component instance from the new calendar with each derived
- # component instance from the old one
- result = self._compareComponents()
- if not result[0]:
- self._logDiffError("attendeeDiff: Mismatched calendar objects")
- else:
- # May need to do some rewriting
- if tzidRemapping:
+ replyNeeded = False
+
+ # ATTENDEE/PARTSTAT/RSVP
+ serverAttendee = serverComponent.getAttendeeProperty((self.attendee,))
+ clientAttendee = clientComponent.getAttendeeProperty((self.attendee,))
+ if serverAttendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0] != clientAttendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0]:
+ serverAttendee.params()["PARTSTAT"] = clientAttendee.params().get("PARTSTAT", "NEEDS-ACTION")
+ replyNeeded = True
+ if serverAttendee.params().get("RSVP", ("FALSE",))[0] != clientAttendee.params().get("RSVP", ("FALSE",))[0]:
+ if clientAttendee.params().get("RSVP", ("FALSE",))[0] == "FALSE":
try:
- self._remapTZIDs()
- self._logDiffError("attendeeDiff: VTIMEZONEs re-mapped")
- except ValueError, e:
- self._logDiffError("attendeeDiff: VTIMEZONE re-mapping failed: %s" % (str(e),))
- return False, False
+ del serverAttendee.params()["RSVP"]
+ except KeyError:
+ pass
+ else:
+ serverAttendee.params()["RSVP"] = ["TRUE",]
- return result
-
- def _attendeeDuplicateAndNormalize(self, calendar):
- calendar = calendar.duplicate()
- calendar.normalizePropertyValueLists("EXDATE")
- calendar.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
- calendar.normalizeAll()
- calendar.normalizeAttachments()
- iTipGenerator.prepareSchedulingMessage(calendar, reply=True)
- return calendar
+ # Transfer these properties from the client data
+ replyNeeded |= self._transferProperty("X-CALENDARSERVER-PRIVATE-COMMENT", serverComponent, clientComponent)
+ self._transferProperty("TRANSP", serverComponent, clientComponent)
+ self._transferProperty("DTSTAMP", serverComponent, clientComponent)
+ self._transferProperty("LAST-MODIFIED", serverComponent, clientComponent)
+ self._transferProperty("X-APPLE-NEEDS-REPLY", serverComponent, clientComponent)
+
+ # Handle VALARMs
+ serverComponent.removeAlarms()
+ for comp in clientComponent.subcomponents():
+ if comp.name() == "VALARM":
+ serverComponent.addComponent(comp)
+
+ return True, replyNeeded
+
+ def _checkInvalidChanges(self, serverComponent, clientComponent, declines):
+
+ # Properties we care about: DTSTART, DTEND, DURATION, RRULE, RDATE, EXDATE
+
+ serverProps = self._getNormalizedDateTimeProperties(serverComponent)
+ clientProps = self._getNormalizedDateTimeProperties(clientComponent)
+
+ # Need to special case EXDATEs as an Attendee can effectively DECLINE by adding an EXDATE
+ if serverProps[:-1] != clientProps[:-1]:
+ invalidChanges = []
+ propNames = ("DTSTART", "DTEND", "RRULE", "RDATE", "EXDATE")
+ invalidChanges = [propName for ctr, propName in enumerate(propNames) if serverProps[ctr] != clientProps[ctr]]
+ log.debug("Critical properties do not match: %s" % (", ".join(invalidChanges),))
+ return False
+ elif serverProps[-1] != clientProps[-1]:
+ # Bad if EXDATEs have been removed
+ missing = serverProps[-1] - clientProps[-1]
+ if missing:
+ log.debug("EXDATEs missing: %s" % (", ".join([toString(exdate) for exdate in missing]),))
+ return False
+ declines.extend(clientProps[-1] - serverProps[-1])
+ return True
+ else:
+ return True
+
+ def _getNormalizedDateTimeProperties(self, component):
+
+ # Basic time properties
+ dtstart = component.getProperty("DTSTART")
+ dtend = component.getProperty("DTEND")
+ duration = component.getProperty("DURATION")
+
+ newdtstart, newdtend = normalizeStartEndDuration(
+ dtstart.value(),
+ dtend.value() if dtend is not None else None,
+ duration.value() if duration is not None else None,
+ )
+
+ # Recurrence rules - we need to normalize the order of the value parts
+ newrrules = set()
+ rrules = component.properties("RRULE")
+ for rrule in rrules:
+ indexedTokens = {}
+ indexedTokens.update([valuePart.split("=") for valuePart in rrule.value().split(";")])
+ sortedValue = ";".join(["%s=%s" % (key, value,) for key, value in sorted(indexedTokens.iteritems(), key=lambda x:x[0])])
+ newrrules.add(sortedValue)
+
+ # RDATEs
+ newrdates = set()
+ rdates = component.properties("RDATE")
+ for rdate in rdates:
+ newrdates.update([normalizeToUTC(value) for value in rdate.value()])
+
+ # EXDATEs
+ newexdates = set()
+ exdates = component.properties("EXDATE")
+ for exdate in exdates:
+ newexdates.update([normalizeToUTC(value) for value in exdate.value()])
- def _attendeeMerge(self):
+ return newdtstart, newdtend, newrrules, newrdates, newexdates
+
+ def _transferProperty(self, propName, serverComponent, clientComponent):
+
+ changed = False
+ serverProp = serverComponent.getProperty(propName)
+ clientProp = clientComponent.getProperty(propName)
+ if serverProp != clientProp:
+ if clientProp:
+ serverComponent.replaceProperty(Property(propName, clientProp.value()))
+ else:
+ serverComponent.removeProperty(serverProp)
+ changed = True
+ return changed
+
+
+ def _attendeeDecline(self, component):
"""
- Merge changes to ATTENDEE properties in calendar1 into calendar2.
+ Marke attendee as DECLINED in the component.
+
+ @param component:
+ @type component:
- NB At this point we are going to assume that the changes in calendar1 are only
- other ATTENDEE PARTSTAT changes as this method should only get called when
- If-Schedule-Tag-Match is present and does not generate an error for an Attendee.
+ @return: C{bool} indicating whether the PARTSTAT value was in fact changed
"""
- self._doSmartMerge(self.attendee, False)
+ attendee = component.getAttendeeProperty((self.attendee,))
+ partstatChanged = attendee.params().get("PARTSTAT", ("NEEDS-ACTION",))[0] != "DECLINED"
+ attendee.params()["PARTSTAT"] = ["DECLINED",]
+ try:
+ del attendee.params()["RSVP"]
+ except KeyError:
+ pass
+ prop = component.getProperty("X-APPLE-NEEDS-REPLY")
+ if prop:
+ component.removeProperty(prop)
+ return partstatChanged
def whatIsDifferent(self):
"""
@@ -351,281 +577,15 @@
rids = None
return props_changed, rids
- def _checkVCALENDARProperties(self):
+ def _attendeeDuplicateAndNormalize(self, calendar):
+ calendar = calendar.duplicate()
+ calendar.normalizePropertyValueLists("EXDATE")
+ calendar.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
+ calendar.normalizeAll()
+ calendar.normalizeAttachments()
+ iTipGenerator.prepareSchedulingMessage(calendar, reply=True)
+ return calendar
- # Get property differences in the VCALENDAR objects
- propdiff = set(self.calendar1.properties()) ^ set(self.calendar2.properties())
-
- # Ignore certain properties
- ignored = ("PRODID", "CALSCALE",)
- propdiff = set([prop for prop in propdiff if prop.name() not in ignored])
-
- result = len(propdiff) == 0
- if not result:
- log.debug("VCALENDAR properties differ: %s" % (propdiff,))
- return result
-
- @staticmethod
- def _extractTZIDs(calendar):
-
- tzids = set()
- for component in calendar.subcomponents():
- if component.name() == "VTIMEZONE":
- tzids.add(component.propertyValue("TZID"))
- return tzids
-
- def _compareVTIMEZONEs(self):
-
- # FIXME: clients may re-write timezones so the best we can do is
- # compare TZIDs. That is not ideal as a client could have an old version
- # of a VTIMEZONE and thus could show events at different times than the
- # organizer.
-
- tzids1 = self._extractTZIDs(self.calendar1)
- tzids2 = self._extractTZIDs(self.calendar2)
- result = tzids1 == tzids2
- if not result:
- log.debug("Different VTIMEZONES: %s %s" % (tzids1, tzids2))
- return result
-
- def _remapTZIDs(self):
- """
- Re-map TZIDs that changed between the existing calendar data and the new data
- being written for the attendee.
- """
-
- # Do master component re-map first
- old_master = self.originalCalendar1.masterComponent()
- new_master = self.originalCalendar2.masterComponent()
- self._remapTZIDsOnComponent(old_master, new_master)
-
- # Now do each corresponding overridden component
- for newComponent in self.originalCalendar2.subcomponents():
-
- # Make sure we have an appropriate component
- if newComponent.name() == "VTIMEZONE":
- continue
- rid = newComponent.getRecurrenceIDUTC()
- if rid is None:
- continue
-
- # Find matching component in new calendar
- oldComponent = self.originalCalendar1.overriddenComponent(rid)
- if oldComponent is None:
- # Derive a new instance from the new calendar and transfer attendee status
- oldComponent = self.originalCalendar2.deriveInstance(rid)
-
- if oldComponent:
- self._remapTZIDsOnComponent(oldComponent, newComponent)
-
-
- # Now manipulate the VTIMEZONE components in the calendar data
- for newComponent in tuple(self.originalCalendar2.subcomponents()):
- # Make sure we have an appropriate component
- if newComponent.name() == "VTIMEZONE":
- self.originalCalendar2.removeComponent(newComponent)
-
- # The following statement is required to force vobject to serialize the
- # calendar data and in the process add any missing VTIMEZONEs as needed.
- _ignore = str(self.originalCalendar2)
- log.debug(_ignore)
-
- def _remapTZIDsOnComponent(self, oldComponent, newComponent):
- """
- Re-map TZIDs that changed between the existing calendar data and the new data
- being written for the attendee.
- """
-
- # Look at each property that culd contain a TZID:
- # DTSTART, DTEND, RDATE, EXDATE, RECURRENCE-ID, DUE.
- # NB EXDATE/RDATE can occur multiple times - special case
- checkPropertiesOneOff = (
- "DTSTART",
- "DTEND",
- "RECURRENCE-ID",
- "DUE",
- )
- checkPropertiesMultiple = (
- "RDATE",
- "EXDATE",
- )
-
- for propName in checkPropertiesOneOff:
- oldProp = oldComponent.getProperty(propName)
- newProp = newComponent.getProperty(propName)
-
- # Special case behavior where DURATIOn is mapped to DTEND
- if propName == "DTEND" and oldProp is None and newProp is not None:
- oldProp = oldComponent.getProperty("DTSTART")
-
- # Transfer tzinfo from old property value to the new one
- if oldProp is not None and newProp is not None:
- if "X-VOBJ-ORIGINAL-TZID" in oldProp.params():
- oldTZID = oldProp.paramValue("X-VOBJ-ORIGINAL-TZID")
- if "X-VOBJ-ORIGINAL-TZID" in newProp.params():
- newTZID = newProp.paramValue("X-VOBJ-ORIGINAL-TZID")
-
- if oldTZID != newTZID:
- newProp.params()["X-VOBJ-ORIGINAL-TZID"][0] = oldTZID
- newProp.setValue(newProp.value().replace(tzinfo=oldProp.value().tzinfo))
- else:
- raise ValueError("Cannot handle mismatched TZIDs on %s" % (propName,))
-
- for propName in checkPropertiesMultiple:
- oldProps = oldComponent.properties(propName)
- newProps = newComponent.properties(propName)
- oldTZID = None
- oldTzinfo = None
- for prop in oldProps:
- if "X-VOBJ-ORIGINAL-TZID" in prop.params():
- if oldTZID and oldTZID != prop.paramValue("X-VOBJ-ORIGINAL-TZID"):
- raise ValueError("Cannot handle different TZIDs on multiple %s" % (propName,))
- else:
- oldTZID = prop.paramValue("X-VOBJ-ORIGINAL-TZID")
- oldTzinfo = prop.value()[0].tzinfo
- for prop in newProps:
- if "X-VOBJ-ORIGINAL-TZID" in prop.params():
- if oldTZID:
- prop.params()["X-VOBJ-ORIGINAL-TZID"][0] = oldTZID
- prop.setValue([item.replace(tzinfo=oldTzinfo) for item in prop.value()])
- else:
- raise ValueError("Cannot handle mismatched TZIDs on %s" % (propName,))
- elif oldTZID:
- raise ValueError("Cannot handle mismatched TZIDs on %s" % (propName,))
-
- def _compareComponents(self):
-
- # First get uid/rid map of components
- def mapComponents(calendar):
- map = {}
- cancelledRids = set()
- master = None
- for component in calendar.subcomponents():
- if component.name() == "VTIMEZONE":
- continue
- name = component.name()
- uid = component.propertyValue("UID")
- rid = component.getRecurrenceIDUTC()
- map[(name, uid, rid,)] = component
- if component.propertyValue("STATUS") == "CANCELLED" and rid is not None:
- cancelledRids.add(rid)
- if rid is None:
- master = component
-
- # Normalize each master by adding any STATUS:CANCELLED components as EXDATEs
- exdates = set()
- if master:
- for rid in sorted(cancelledRids):
- master.addProperty(Property("EXDATE", [rid,]))
-
- # Get all EXDATEs in UTC
- for exdate in master.properties("EXDATE"):
- exdates.update([normalizeToUTC(value) for value in exdate.value()])
-
- return exdates, map
-
- exdates1, map1 = mapComponents(self.calendar1)
- set1 = set(map1.keys())
- exdates2, map2 = mapComponents(self.calendar2)
- set2 = set(map2.keys())
-
- # All the components in calendar1 must be in calendar2 unless they are CANCELLED
- result = set1 - set2
- for key in result:
- component = map1[key]
- if component.propertyValue("STATUS") != "CANCELLED":
- log.debug("Missing uncancelled component from first calendar: %s" % (key,))
- return False, False
- else:
- _ignore_name, _ignore_uid, rid = key
- if rid not in exdates2:
- log.debug("Missing EXDATE for cancelled components from first calendar: %s" % (key,))
- return False, False
-
-
- # Now verify that each component in set1 matches what is in set2
- attendee_unchanged = True
- for key, value in map1.iteritems():
- component1 = value
- component2 = map2.get(key)
- if component2 is None:
- continue
-
- nomismatch, no_attendee_change = self._testComponents(component1, component2)
- if not nomismatch:
- return False, False
- attendee_unchanged &= no_attendee_change
-
- # Now verify that each additional component in set2 matches a derived component in set1
- for key in set2 - set1:
-
- # First check if the attendee's copy is cancelled and properly EXDATE'd
- # and skip it if so.
- component2 = map2[key]
- if component2.propertyValue("STATUS") == "CANCELLED":
- _ignore_name, _ignore_uid, rid = key
- if rid not in exdates1:
- log.debug("Cancelled component not found in first calendar (or no EXDATE): %s" % (key,))
- return False, False
- continue
-
- # Now derive the organizer's expected instance and compare
- component1 = self.calendar1.deriveInstance(key[2])
- if component1 is None:
- log.debug("_compareComponents: Could not derive instance: %s" % (key[2],))
- return False, False
-
- nomismatch, no_attendee_change = self._testComponents(component1, component2)
- if not nomismatch:
- return False, False
- attendee_unchanged &= no_attendee_change
-
- return True, attendee_unchanged
-
- def _testComponents(self, comp1, comp2):
-
- assert isinstance(comp1, Component) and isinstance(comp2, Component)
-
- if comp1.name() != comp2.name():
- log.debug("Component names are different: '%s' and '%s'" % (comp1.name(), comp2.name()))
- return False, False
-
- # Only accept a change to this attendee's own ATTENDEE property
- comp1.transformAllFromNative()
- comp2.transformAllFromNative()
- propdiff = set(comp1.properties()) ^ set(comp2.properties())
- comp1.transformAllToNative()
- comp2.transformAllToNative()
- for prop in tuple(propdiff):
- # These ones are OK to change
- if prop.name() in (
- "TRANSP",
- "DTSTAMP",
- "CREATED",
- "LAST-MODIFIED",
- "SEQUENCE",
- ):
- propdiff.remove(prop)
- continue
-
- # These ones can change and trigger a reschedule
- if ((prop.name() == "ATTENDEE" and prop.value() == self.attendee) or
- prop.name() == "X-CALENDARSERVER-PRIVATE-COMMENT"):
- continue
-
- # Change that is not allowed
- log.debug("Component properties are different (trigger is '%s'): %s" % (prop.name(), propdiff,))
- return False, False
-
- # Compare subcomponents.
- # NB at this point we assume VALARMS have been removed.
- result = set(comp1.subcomponents()) ^ set(comp2.subcomponents())
- if result:
- log.debug("Sub-components are different: %s" % (result,))
- return False, False
-
- return True, len(propdiff) == 0
-
def _diffComponents(self, comp1, comp2, changed, rids):
assert isinstance(comp1, Component) and isinstance(comp2, Component)
Modified: CalendarServer/trunk/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2009-03-11 14:52:12 UTC (rev 3810)
+++ CalendarServer/trunk/twistedcaldav/scheduling/implicit.py 2009-03-11 14:59:15 UTC (rev 3811)
@@ -666,9 +666,11 @@
self.oldcalendar = None
# Determine whether the current change is allowed
- change_allowed, no_itip = self.isAttendeeChangeInsignificant()
+ changeAllowed, doITipReply, changedRids, newCalendar = self.isAttendeeChangeInsignificant()
+ if changeAllowed:
+ self.return_calendar = self.calendar = newCalendar
- if not change_allowed:
+ if not changeAllowed:
if self.calendar.hasPropertyValueInAllComponents(Property("STATUS", "CANCELLED")):
log.debug("Attendee '%s' is creating CANCELLED event for mismatched UID: '%s' - removing entire event" % (self.attendee, self.uid,))
self.return_status = ImplicitScheduler.STATUS_ORPHANED_CANCELLED_EVENT
@@ -677,7 +679,7 @@
log.error("Attendee '%s' is not allowed to make an unauthorized change to an organized event: UID:%s" % (self.attendeePrincipal, self.uid,))
raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-attendee-change")))
- if no_itip:
+ if not doITipReply:
log.debug("Implicit - attendee '%s' is updating UID: '%s' but change is not significant" % (self.attendee, self.uid))
returnValue(None)
elif isinstance(self.organizerAddress, LocalCalendarUser):
@@ -747,7 +749,7 @@
oldcalendar = self.organizer_calendar
oldcalendar.attendeesView((self.attendee,))
differ = iCalDiff(oldcalendar, self.calendar, self.do_smart_merge)
- return differ.attendeeDiff(self.attendee)
+ return differ.attendeeMerge(self.attendee)
def scheduleWithOrganizer(self):
Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2009-03-11 14:52:12 UTC (rev 3810)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2009-03-11 14:59:15 UTC (rev 3811)
@@ -124,18 +124,10 @@
if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
rid = component.getRecurrenceIDUTC()
if new_calendar.overriddenComponent(rid) is None:
- new_component = new_calendar.deriveInstance(rid)
+ allowCancelled = component.propertyValue("STATUS") == "CANCELLED"
+ new_component = new_calendar.deriveInstance(rid, allowCancelled=allowCancelled)
new_calendar.addComponent(new_component)
iTipProcessing.transferItems(calendar, master_valarms, private_comments, new_component)
- if component.propertyValue("STATUS") == "CANCELLED":
- new_component.replaceProperty(Property("STATUS", "CANCELLED"))
- for exdate in master_component.properties("EXDATE"):
- for value in exdate.value():
- if value == rid:
- exdate.value().remove(value)
- if len(exdate.value()) == 0:
- master_component.removeProperty(exdate)
- break
# Replace the entire object
return new_calendar, props_changed, rids
Modified: CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py 2009-03-11 14:52:12 UTC (rev 3810)
+++ CalendarServer/trunk/twistedcaldav/scheduling/test/test_icaldiff.py 2009-03-11 14:59:15 UTC (rev 3811)
@@ -18,6 +18,7 @@
from twistedcaldav.scheduling.icaldiff import iCalDiff
import twistedcaldav.test.util
from difflib import unified_diff
+from twistedcaldav.dateops import toString
import itertools
@@ -474,7 +475,9 @@
differ = iCalDiff(Component.fromString(calendar1), Component.fromString(calendar2), False)
self.assertEqual(differ.organizerDiff(), result, msg=description)
- def test_attendee_diff_simple(self):
+
+ def test_attendee_merge_simple(self):
+
data = (
(
@@ -506,7 +509,19 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.2 Simple component, PARTSTAT change",
@@ -537,7 +552,19 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, False,)
+ (True, True, (None,), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.3 Simple component, bad change",
@@ -568,7 +595,7 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (False, False,)
+ (False, False, (), None)
),
(
"#1.4 Simple component, valarm change",
@@ -609,7 +636,24 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test for Attendee
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.5 Simple component, vcalendar props change ok",
@@ -651,7 +695,24 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test for Attendee
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.6 Simple component, vcalendar props change bad",
@@ -693,7 +754,7 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (False, False, (), None)
),
(
"#1.7 Simple component, vtimezone no change",
@@ -760,7 +821,37 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US-Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:STANDARD
+DTSTART:19901026T060000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19900404T010000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=US-Eastern:20080601T120000
+DTEND;TZID=US-Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.8 Simple component, vtimezone bad change",
@@ -827,7 +918,7 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (False, False,)
+ (False, False, (), None)
),
(
"#1.9 Simple component, vtimezone substitute",
@@ -894,7 +985,37 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US-Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:STANDARD
+DTSTART:19901026T060000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19900404T010000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=US-Eastern:20080601T120000
+DTEND;TZID=US-Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.10 Simple component, vtimezone substitute",
@@ -961,15 +1082,52 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTIMEZONE
+TZID:US-Eastern
+LAST-MODIFIED:20040110T032845Z
+BEGIN:STANDARD
+DTSTART:19901026T060000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19900404T010000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;TZID=US-Eastern:20080601T120000
+DTEND;TZID=US-Eastern:20080601T130000
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
)
for description, calendar1, calendar2, attendee, result in data:
differ = iCalDiff(Component.fromString(calendar1), Component.fromString(calendar2), False)
- self.assertEqual(differ.attendeeDiff(attendee), result, msg=description)
+ diffResult = differ.attendeeMerge(attendee)
+ diffResult = (
+ diffResult[0],
+ diffResult[1],
+ tuple([toString(i) if i else None for i in diffResult[2]]),
+ str(diffResult[3]).replace("\r", "") if diffResult[3] else None,
+ )
+ self.assertEqual(diffResult, result, msg="%s: actual result: (%s)" % (description, ", ".join([str(i).replace("\r", "") for i in diffResult]),))
- def test_attendee_diff_complex(self):
+ def test_attendee_merge_complex(self):
data = (
(
@@ -1019,7 +1177,28 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080602T130000Z
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.2 Complex component, alarm change",
@@ -1078,7 +1257,33 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test for Attendee
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080602T130000Z
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.3 Complex component, missing override",
@@ -1119,7 +1324,7 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (False, False,)
+ (False, False, (), None)
),
(
"#1.4 Complex component, additional override no change ok",
@@ -1177,7 +1382,37 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, True,)
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080602T130000Z
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.5 Complex component, additional override change ok",
@@ -1235,7 +1470,37 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (True, False,)
+ (True, True, ("20080602T120000Z", "20080604T120000Z",), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T123000Z
+DTEND:20080602T130000Z
+ATTENDEE;PARTSTAT=ACCEPTED;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
),
(
"#1.6 Complex component, additional override bad",
@@ -1293,14 +1558,440 @@
END:VCALENDAR
""",
"mailto:user2 at example.com",
- (False, False,)
+ (False, False, (), None)
),
)
for description, calendar1, calendar2, attendee, result in data:
differ = iCalDiff(Component.fromString(calendar1), Component.fromString(calendar2), False)
- self.assertEqual(differ.attendeeDiff(attendee), result, msg=description)
+ diffResult = differ.attendeeMerge(attendee)
+ diffResult = (
+ diffResult[0],
+ diffResult[1],
+ tuple([toString(i) if i else None for i in diffResult[2]]),
+ str(diffResult[3]).replace("\r", "") if diffResult[3] else None,
+ )
+ self.assertEqual(diffResult, result, msg="%s: actual result: (%s)" % (description, ", ".join([str(i).replace("\r", "") for i in diffResult]),))
+
+ def test_attendee_merge_exdate(self):
+
+ data = (
+ (
+ "#1.1 Single component, one EXDATE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+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
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+EXDATE:20080604T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2 at example.com",
+ (True, True, ("20080604T120000Z",), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.2 Single component, two EXDATEs",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+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
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+EXDATE:20080604T120000Z,20080605T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2 at example.com",
+ (True, True, ("20080604T120000Z", "20080605T120000Z",), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080605T120000Z
+DTSTART:20080605T120000Z
+DTEND:20080605T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.3 Two components, one EXDATE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T130000Z
+DTEND:20080604T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+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
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+EXDATE:20080604T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2 at example.com",
+ (True, True, ("20080604T120000Z",), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T130000Z
+DTEND:20080604T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.4 Two components, two EXDATEs",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T130000Z
+DTEND:20080604T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+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
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+EXDATE:20080604T120000Z
+EXDATE:20080606T120000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2 at example.com",
+ (True, True, ("20080604T120000Z", "20080606T120000Z",), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T130000Z
+DTEND:20080604T140000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080606T120000Z
+DTSTART:20080606T120000Z
+DTEND:20080606T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ )
+
+ for description, calendar1, calendar2, attendee, result in data:
+ differ = iCalDiff(Component.fromString(calendar1), Component.fromString(calendar2), False)
+ diffResult = differ.attendeeMerge(attendee)
+ diffResult = (
+ diffResult[0],
+ diffResult[1],
+ tuple([toString(i) if i else None for i in diffResult[2]]),
+ str(diffResult[3]).replace("\r", "") if diffResult[3] else None,
+ )
+ self.assertEqual(diffResult, result, msg="%s: actual result: (%s)" % (description, ", ".join([str(i).replace("\r", "") for i in diffResult]),))
+
+ def test_attendee_merge_cancelled(self):
+
+ data = (
+ (
+ "#1.1 Remove EXDATE add CANCELLED",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+EXDATE:20080604T120000Z
+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
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+RECURRENCE-ID:20080604T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+STATUS:CANCELLED
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2 at example.com",
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080604T120000Z
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+STATUS:CANCELLED
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ (
+ "#1.2 Removed CANCELLED add EXDATE",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080604T120000Z
+DTEND:20080604T130000Z
+RECURRENCE-ID:20080604T120000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+STATUS:CANCELLED
+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
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+RRULE:FREQ=DAILY
+EXDATE:20080604T120000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ "mailto:user2 at example.com",
+ (True, False, (), """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
+EXDATE:20080604T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""")
+ ),
+ )
+
+ for description, calendar1, calendar2, attendee, result in data:
+ differ = iCalDiff(Component.fromString(calendar1), Component.fromString(calendar2), False)
+ diffResult = differ.attendeeMerge(attendee)
+ diffResult = (
+ diffResult[0],
+ diffResult[1],
+ tuple([toString(i) if i else None for i in diffResult[2]]),
+ str(diffResult[3]).replace("\r", "") if diffResult[3] else None,
+ )
+ self.assertEqual(diffResult, result, msg="%s: actual result: (%s)" % (description, ", ".join([str(i).replace("\r", "") for i in diffResult]),))
+
def test_what_is_different(self):
data1 = (
@@ -3050,638 +3741,3 @@
strcal2 = str(cal2)
strchanged = str(Component.fromString(changed_calendar))
self.assertEqual(strchanged, strcal2, msg="%s mismatch:\n%s" % (description, "\n".join(unified_diff(strchanged.split("\n"), strcal2.split("\n")))))
-
- def test_attendee_smart_merge(self):
-
- data1 = (
- (
- "#1.1 Simple component, no change",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- ),
- (
- "#1.2 Simple component, client change only",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- ),
- (
- "#1.3 Simple component, server change only",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- ),
- (
- "#1.4 Simple component, both change",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-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
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- ),
- )
-
- data2 = (
- (
- "#2.1 Simple recurring component, no change",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- ),
- (
- "#2.2 Simple recurring component, client instance change",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080603T120000Z
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080603T120000Z
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- ),
- (
- "#2.3 Simple recurring component, server instance change",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080603T120000Z
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=DECLINED:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080603T120000Z
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=DECLINED:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- ),
- (
- "#2.4 Simple recurring component, both instance change",
- """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080603T120000Z
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=DECLINED:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080604T120000Z
-DTSTART:20080604T120000Z
-DTEND:20080604T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
-ATTENDEE:mailto:user3 at example.com
-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
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-RRULE:FREQ=DAILY
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080602T120000Z
-DTSTART:20080602T120000Z
-DTEND:20080602T130000Z
-SUMMARY:Test - 2
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080604T120000Z
-DTSTART:20080604T120000Z
-DTEND:20080604T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=DECLINED:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user3 at example.com
-END:VEVENT
-BEGIN:VEVENT
-UID:12345-67890
-RECURRENCE-ID:20080603T120000Z
-DTSTART:20080603T120000Z
-DTEND:20080603T130000Z
-SUMMARY:Test
-ORGANIZER;CN="User 01":mailto:user1 at example.com
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE;PARTSTAT=ACCEPTED:mailto:user2 at example.com
-ATTENDEE;PARTSTAT=DECLINED:mailto:user3 at example.com
-END:VEVENT
-END:VCALENDAR
-""",
- ),
- )
-
- for description, calendar1, calendar2, changed_calendar in itertools.chain(data1, data2,):
- cal1 = Component.fromString(calendar1)
- cal2 = Component.fromString(calendar2)
-
- differ = iCalDiff(cal1, cal2, True)
- differ.attendeeDiff("mailto:user2 at example.com")
-
- strcal2 = str(cal2)
- strchanged = str(Component.fromString(changed_calendar))
- self.assertEqual(strchanged, strcal2, msg="%s mismatch:\n%s" % (description, "\n".join(unified_diff(strchanged.split("\n"), strcal2.split("\n")))))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090311/73b4efad/attachment-0001.html>
More information about the calendarserver-changes
mailing list