[CalendarServer-changes] [3910] CalendarServer/trunk/twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Sun Mar 22 15:06:06 PDT 2009
Revision: 3910
http://trac.macosforge.org/projects/calendarserver/changeset/3910
Author: cdaboo at apple.com
Date: 2009-03-22 15:06:06 -0700 (Sun, 22 Mar 2009)
Log Message:
-----------
Handled recurrence truncation when overridden instances are present. Also fix issue with TRANSP
not being transferred over properly.
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/scheduling/itip.py
CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2009-03-22 22:01:15 UTC (rev 3909)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2009-03-22 22:06:06 UTC (rev 3910)
@@ -879,6 +879,26 @@
return newcomp
+ def cacheExpandedTimeRanges(self, limit):
+ """
+ Expand instances up to the specified limit and cache the results in this object
+ so we can return cached results in the future.
+
+ @param limit: the max datetime to cache up to.
+ @type limit: L{datetime.datetime} or L{datetime.date}
+ """
+
+ # Checked for cached values first
+ if hasattr(self, "cachedInstances"):
+ cachedLimit = self.cachedInstances.limit
+ if cachedLimit is None or cachedLimit >= limit:
+ # We have already fully expanded, or cached up to the requested time,
+ # so return cached instances
+ return self.cachedInstances
+
+ self.cachedInstances = self.expandTimeRanges(limit)
+ return self.cachedInstances
+
def expandTimeRanges(self, limit):
"""
Expand the set of recurrence instances for the components
@@ -994,6 +1014,25 @@
# Cannot derive from an existing EXDATE
return None
+ # Check whether recurrence-id matches an RDATE - if so it is OK
+ rdates = set()
+ for rdate in master.properties("RDATE"):
+ rdates.update([normalizeToUTC(item) for item in rdate.value()])
+ if rid not in rdates:
+ # Check whether we have a truncated RRULE
+ rrules = master.properties("RRULE")
+ if len(tuple(rrules)):
+ limit = rid
+ limit += datetime.timedelta(days=365)
+ instances = self.cacheExpandedTimeRanges(limit)
+ rids = set([instances[key].rid for key in instances])
+ if rid not in rids:
+ # No match to a valid RRULE instance
+ return None
+ else:
+ # No RRULE and no match to an RDATE => error
+ return None
+
# Create the derived instance
newcomp = master.duplicate()
Modified: CalendarServer/trunk/twistedcaldav/scheduling/itip.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2009-03-22 22:01:15 UTC (rev 3909)
+++ CalendarServer/trunk/twistedcaldav/scheduling/itip.py 2009-03-22 22:06:06 UTC (rev 3910)
@@ -98,9 +98,11 @@
if current_master:
master_valarms = [comp for comp in current_master.subcomponents() if comp.name() == "VALARM"]
private_comments = current_master.properties("X-CALENDARSERVER-PRIVATE-COMMENT")
+ transps = current_master.properties("TRANSP")
else:
master_valarms = ()
private_comments = ()
+ transps = ()
if itip_message.masterComponent() is not None:
@@ -113,11 +115,13 @@
master_component.addComponent(alarm)
for comment in private_comments:
master_component.addProperty(comment)
+ for transp in transps:
+ master_component.replaceProperty(transp)
# Now try to match recurrences
for component in new_calendar.subcomponents():
if component.name() != "VTIMEZONE" and component.getRecurrenceIDUTC() is not None:
- iTipProcessing.transferItems(calendar, master_valarms, private_comments, component)
+ iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, component)
# Now try to match recurrences
for component in calendar.subcomponents():
@@ -126,8 +130,9 @@
if new_calendar.overriddenComponent(rid) is None:
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 new_component:
+ new_calendar.addComponent(new_component)
+ iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, new_component)
# Replace the entire object
return new_calendar, props_changed, rids
@@ -144,7 +149,7 @@
calendar.addComponent(component)
else:
component = component.duplicate()
- iTipProcessing.transferItems(calendar, master_valarms, private_comments, component, remove_matched=True)
+ iTipProcessing.transferItems(calendar, master_valarms, private_comments, transps, component, remove_matched=True)
calendar.addComponent(component)
if recipient and not autoprocessing:
iTipProcessing.fixForiCal3((component,), recipient, config.Scheduling.CalDAV.OldDraftCompatibility)
@@ -225,8 +230,9 @@
else:
# Derive a new component and cancel it.
overridden = calendar.deriveInstance(rid)
- overridden.replaceProperty(Property("STATUS", "CANCELLED"))
- calendar.addComponent(overridden)
+ if overridden:
+ overridden.replaceProperty(Property("STATUS", "CANCELLED"))
+ calendar.addComponent(overridden)
# If we have any EXDATEs lets add them to the existing calendar object.
if exdates and calendar_master:
@@ -279,7 +285,8 @@
if rids is not None and (partstat_changed or private_comment_changed):
rids.add("")
- # Now do all overridden ones
+ # Now do all overridden ones (sort by RECURRENCE-ID)
+ sortedComponents = []
for itip_component in itip_message.subcomponents():
# Make sure we have an appropriate component
@@ -288,14 +295,19 @@
rid = itip_component.getRecurrenceIDUTC()
if rid is None:
continue
+ sortedComponents.append((rid, itip_component,))
+ sortedComponents.sort(key=lambda x:x[0])
+
+ for rid, itip_component in sortedComponents:
# Find matching component in organizer's copy
match_component = calendar.overriddenComponent(rid)
if match_component is None:
# Attendee is overriding an instance themselves - we need to create a derived one
# for the Organizer
match_component = calendar.deriveInstance(rid)
- calendar.addComponent(match_component)
+ if match_component:
+ calendar.addComponent(match_component)
attendee, partstat, private_comment = iTipProcessing.updateAttendeeData(itip_component, match_component)
attendees.add(attendee)
@@ -423,7 +435,7 @@
return attendee.value(), partstat_changed, private_comment_changed
@staticmethod
- def transferItems(from_calendar, master_valarms, private_comments, to_component, remove_matched=False):
+ def transferItems(from_calendar, master_valarms, private_comments, transps, to_component, remove_matched=False):
rid = to_component.getRecurrenceIDUTC()
@@ -433,6 +445,7 @@
# Copy over VALARMs from existing component
[to_component.addComponent(comp) for comp in matched.subcomponents() if comp.name() == "VALARM"]
[to_component.addProperty(prop) for prop in matched.properties("X-CALENDARSERVER-ATTENDEE-COMMENT")]
+ [to_component.replaceProperty(prop) for prop in matched.properties("TRANSP")]
# Remove the old one
if remove_matched:
@@ -443,6 +456,7 @@
# into the new one.
[to_component.addComponent(alarm) for alarm in master_valarms]
[to_component.addProperty(comment) for comment in private_comments]
+ [to_component.replaceProperty(transp) for transp in transps]
@staticmethod
def fixForiCal3(components, recipient, compatibilityMode):
Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2009-03-22 22:01:15 UTC (rev 3909)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2009-03-22 22:06:06 UTC (rev 3910)
@@ -18,6 +18,7 @@
import datetime
from dateutil.tz import tzutc
from difflib import unified_diff
+import itertools
from twisted.trial.unittest import SkipTest
@@ -2235,3 +2236,338 @@
ical = Component.fromString(calendar)
result = ical.isRecurringUnbounded()
self.assertEqual(result, expected, "Failed recurring unbounded test: %s" % (title,))
+
+ def test_derive_instance(self):
+
+ data = (
+ (
+ "1.1 - simple",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ datetime.datetime(2009, 1, 2, 8, 0, 0, tzinfo=tzutc()),
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T080000Z
+DTSTART:20090102T080000Z
+DTEND:20090102T090000Z
+END:VEVENT
+""",
+ ),
+ (
+ "1.2 - simple rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ datetime.datetime(2009, 1, 2, 18, 0, 0, tzinfo=tzutc()),
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T180000Z
+DTSTART:20090102T180000Z
+DTEND:20090102T190000Z
+END:VEVENT
+""",
+ ),
+ (
+ "1.3 - multiple rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z,20090103T180000Z
+RDATE:20090104T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ datetime.datetime(2009, 1, 3, 18, 0, 0, tzinfo=tzutc()),
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T180000Z
+DTSTART:20090103T180000Z
+DTEND:20090103T190000Z
+END:VEVENT
+""",
+ ),
+ (
+ "2.1 - invalid simple",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ datetime.datetime(2009, 1, 2, 9, 0, 0, tzinfo=tzutc()),
+ None,
+ ),
+ (
+ "2.2 - invalid simple rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ datetime.datetime(2009, 1, 2, 19, 0, 0, tzinfo=tzutc()),
+ None,
+ ),
+ (
+ "2.3 - invalid multiple rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z,20090103T180000Z
+RDATE:20090104T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ datetime.datetime(2009, 1, 3, 19, 0, 0, tzinfo=tzutc()),
+ None,
+ ),
+ )
+
+ for title, calendar, rid, result in data:
+ ical = Component.fromString(calendar)
+ derived = ical.deriveInstance(rid)
+ derived = str(derived).replace("\r", "") if derived else None
+ self.assertEqual(derived, result, "Failed derive instance test: %s" % (title,))
+
+ def test_derive_instance_multiple(self):
+
+ data = (
+ (
+ "1.1 - simple",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ datetime.datetime(2009, 1, 2, 8, 0, 0, tzinfo=tzutc()),
+ datetime.datetime(2009, 1, 4, 8, 0, 0, tzinfo=tzutc()),
+ ),
+ (
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T080000Z
+DTSTART:20090102T080000Z
+DTEND:20090102T090000Z
+END:VEVENT
+""",
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090104T080000Z
+DTSTART:20090104T080000Z
+DTEND:20090104T090000Z
+END:VEVENT
+""",
+ ),
+ ),
+ (
+ "1.2 - simple rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ datetime.datetime(2009, 1, 2, 18, 0, 0, tzinfo=tzutc()),
+ datetime.datetime(2009, 1, 4, 8, 0, 0, tzinfo=tzutc()),
+ ),
+ (
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090102T180000Z
+DTSTART:20090102T180000Z
+DTEND:20090102T190000Z
+END:VEVENT
+""",
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090104T080000Z
+DTSTART:20090104T080000Z
+DTEND:20090104T090000Z
+END:VEVENT
+""",
+ ),
+ ),
+ (
+ "1.3 - multiple rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z,20090103T180000Z
+RDATE:20090104T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ datetime.datetime(2009, 1, 3, 18, 0, 0, tzinfo=tzutc()),
+ datetime.datetime(2009, 1, 5, 8, 0, 0, tzinfo=tzutc()),
+ ),
+ (
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T180000Z
+DTSTART:20090103T180000Z
+DTEND:20090103T190000Z
+END:VEVENT
+""",
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090105T080000Z
+DTSTART:20090105T080000Z
+DTEND:20090105T090000Z
+END:VEVENT
+""",
+ ),
+ ),
+ (
+ "2.1 - invalid simple",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ datetime.datetime(2009, 1, 2, 9, 0, 0, tzinfo=tzutc()),
+ datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+ ),
+ (
+ None,
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T080000Z
+DTSTART:20090103T080000Z
+DTEND:20090103T090000Z
+END:VEVENT
+""",
+ ),
+ ),
+ (
+ "2.2 - invalid simple rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ datetime.datetime(2009, 1, 2, 19, 0, 0, tzinfo=tzutc()),
+ datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+ ),
+ (
+ None,
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T080000Z
+DTSTART:20090103T080000Z
+DTEND:20090103T090000Z
+END:VEVENT
+""",
+ ),
+ ),
+ (
+ "2.3 - invalid multiple rdate",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20090101T080000Z
+DTEND:20090101T090000Z
+RRULE:FREQ=DAILY
+RDATE:20090102T180000Z,20090103T180000Z
+RDATE:20090104T180000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ (
+ datetime.datetime(2009, 1, 3, 19, 0, 0, tzinfo=tzutc()),
+ datetime.datetime(2009, 1, 3, 8, 0, 0, tzinfo=tzutc()),
+ ),
+ (
+ None,
+ """BEGIN:VEVENT
+UID:12345-67890-1
+RECURRENCE-ID:20090103T080000Z
+DTSTART:20090103T080000Z
+DTEND:20090103T090000Z
+END:VEVENT
+""",
+ ),
+ ),
+ )
+
+ for title, calendar, rids, results in data:
+ ical = Component.fromString(calendar)
+ for rid, result in itertools.izip(rids, results):
+ derived = ical.deriveInstance(rid)
+ derived = str(derived).replace("\r", "") if derived else None
+ self.assertEqual(derived, result, "Failed derive instance test: %s" % (title,))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090322/08137f3b/attachment-0001.html>
More information about the calendarserver-changes
mailing list