[CalendarServer-changes] [4213] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Fri May 8 08:27:58 PDT 2009
Revision: 4213
http://trac.macosforge.org/projects/calendarserver/changeset/4213
Author: cdaboo at apple.com
Date: 2009-05-08 08:27:57 -0700 (Fri, 08 May 2009)
Log Message:
-----------
Server now truncates RRULEs to keep number of instances within reasonable limits.
Modified Paths:
--------------
CalendarServer/trunk/conf/caldavd-apple.plist
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/conf/caldavd.plist
CalendarServer/trunk/run
CalendarServer/trunk/twistedcaldav/config.py
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/method/put_common.py
CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
Modified: CalendarServer/trunk/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-apple.plist 2009-05-08 15:26:23 UTC (rev 4212)
+++ CalendarServer/trunk/conf/caldavd-apple.plist 2009-05-08 15:27:57 UTC (rev 4213)
@@ -109,7 +109,12 @@
<key>MaxAttendeesPerInstance</key>
<integer>100</integer>
+ <!-- Maximum number of instances allowed for a single RRULE -->
+ <!-- 0 for no limit -->
+ <key>MaxInstancesForRRULE</key>
+ <integer>400</integer>
+
<!--
Directory service
Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2009-05-08 15:26:23 UTC (rev 4212)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2009-05-08 15:27:57 UTC (rev 4213)
@@ -106,7 +106,12 @@
<key>MaxAttendeesPerInstance</key>
<integer>100</integer>
+ <!-- Maximum number of instances allowed for a single RRULE -->
+ <!-- 0 for no limit -->
+ <key>MaxInstancesForRRULE</key>
+ <integer>400</integer>
+
<!--
Directory service
Modified: CalendarServer/trunk/conf/caldavd.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd.plist 2009-05-08 15:26:23 UTC (rev 4212)
+++ CalendarServer/trunk/conf/caldavd.plist 2009-05-08 15:27:57 UTC (rev 4213)
@@ -109,7 +109,12 @@
<key>MaxAttendeesPerInstance</key>
<integer>100</integer>
+ <!-- Maximum number of instances allowed for a single RRULE -->
+ <!-- 0 for no limit -->
+ <key>MaxInstancesForRRULE</key>
+ <integer>400</integer>
+
<!--
Directory service
Modified: CalendarServer/trunk/run
===================================================================
--- CalendarServer/trunk/run 2009-05-08 15:26:23 UTC (rev 4212)
+++ CalendarServer/trunk/run 2009-05-08 15:27:57 UTC (rev 4213)
@@ -727,7 +727,7 @@
caldavtester="${top}/CalDAVTester";
-svn_get "CalDAVTester" "${caldavtester}" "${svn_uri_base}/CalDAVTester/trunk" 4196;
+svn_get "CalDAVTester" "${caldavtester}" "${svn_uri_base}/CalDAVTester/trunk" 4212;
#
# PyFlakes
Modified: CalendarServer/trunk/twistedcaldav/config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/config.py 2009-05-08 15:26:23 UTC (rev 4212)
+++ CalendarServer/trunk/twistedcaldav/config.py 2009-05-08 15:27:57 UTC (rev 4213)
@@ -116,6 +116,7 @@
"UserQuota" : 104857600, # User quota (in bytes)
"MaximumAttachmentSize" : 1048576, # Attachment size limit (in bytes)
"MaxAttendeesPerInstance" : 100, # Maximum number of unique attendees
+ "MaxInstancesForRRULE" : 400, # Maximum number of instances for an RRULE
"WebCalendarRoot" : "/usr/share/collaboration",
"Aliases": {},
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2009-05-08 15:26:23 UTC (rev 4212)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2009-05-08 15:27:57 UTC (rev 4213)
@@ -36,7 +36,7 @@
from twisted.web2.stream import IStream
from twistedcaldav.dateops import compareDateTime, normalizeToUTC, timeRangesOverlap,\
- normalizeStartEndDuration, toString, normalizeForIndex
+ normalizeStartEndDuration, toString, normalizeForIndex, differenceDateTime
from twistedcaldav.instance import InstanceList
from twistedcaldav.log import Logger
from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
@@ -728,6 +728,10 @@
self.transformAllToNative()
return self._vobject.getrruleset(addRDate)
+ def setRRuleSet(self, rruleset):
+ #self.transformAllToNative()
+ return self._vobject.setrruleset(rruleset)
+
def getEffectiveStartEnd(self):
# Get the start/end range needed for instance comparisons
@@ -810,6 +814,59 @@
return results
+ def truncateRecurrence(self, maximumCount):
+ """
+ Truncate RRULEs etc to make sure there are no more than the given number
+ of instances.
+
+ @param maximumCount: the maximum number of instances to allow
+ @type maximumCount: C{int}
+ @return: a C{bool} indicating whether a change was made or not
+ """
+
+ changed = False
+ master = self.masterComponent()
+ if master and master.isRecurring():
+ rrules = master.getRRuleSet()
+ if rrules:
+ for rrule in rrules._rrule:
+ if rrule._count is not None:
+ # Make sure COUNT is less than the limit
+ if rrule._count > maximumCount:
+ rrule._count = maximumCount
+ changed = True
+ elif rrule._until is not None:
+ # Need to figure out how to determine number of instances
+ # with this UNTIL and truncate if needed
+ start = master.getStartDateUTC()
+ diff = differenceDateTime(start, rrule._until)
+ diff = diff.days * 24 * 60 * 60 + diff.seconds
+
+ period = {
+ 0: 365 * 24 * 60 * 60,
+ 1: 30 * 24 * 60 * 60,
+ 2: 7 * 24 * 60 * 60,
+ 3: 1 * 24 * 60 * 60,
+ 4: 60 * 60,
+ 5: 60,
+ 6: 1
+ }[rrule._freq] * rrule._interval
+
+ if diff / period > maximumCount:
+ rrule._until = None
+ rrule._count = maximumCount
+ changed = True
+ else:
+ # For frequencies other than yearly we will truncate at our limit
+ if rrule._freq != 0:
+ rrule._count = maximumCount
+ changed = True
+
+ if changed:
+ master.setRRuleSet(rrules)
+
+ return changed
+
def expand(self, start, end, timezone=None):
"""
Expand the components into a set of new components, one for each
Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py 2009-05-08 15:26:23 UTC (rev 4212)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py 2009-05-08 15:27:57 UTC (rev 4213)
@@ -668,6 +668,20 @@
copyToWithXAttrs(self.source.fp, self.rollback.source_copy)
log.debug("Rollback: backing up source %s to %s" % (self.source.fp.path, self.rollback.source_copy.path))
+ def truncateRecurrence(self):
+
+ if config.MaxInstancesForRRULE != 0:
+ try:
+ result = self.calendar.truncateRecurrence(config.MaxInstancesForRRULE)
+ except (ValueError, TypeError), ex:
+ log.err("Cannot truncate calendar resource: %s" % (ex,))
+ raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data")))
+ if result:
+ self.calendardata = str(self.calendar)
+ return result
+ else:
+ return False
+
def preservePrivateComments(self):
# Check for private comments on the old resource and the new resource and re-insert
# ones that are lost.
@@ -940,6 +954,9 @@
# Get current quota state.
yield self.checkQuota()
+ # Handle RRULE truncation
+ rruleChanged = self.truncateRecurrence()
+
# Preserve private comments
new_has_private_comments = self.preservePrivateComments()
@@ -978,7 +995,7 @@
response = (yield self.doStore(data_changed))
# Must not set ETag in response if data changed
- if did_implicit_action:
+ if did_implicit_action or rruleChanged:
def _removeEtag(request, response):
response.headers.removeHeader('etag')
return response
Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2009-05-08 15:26:23 UTC (rev 4212)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py 2009-05-08 15:27:57 UTC (rev 4213)
@@ -2744,3 +2744,149 @@
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_truncate_recurrence(self):
+
+ data = (
+ (
+ "1.1 - no recurrence",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ None,
+ ),
+ (
+ "1.2 - no truncation - count",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=WEEKLY;COUNT=2
+END:VEVENT
+END:VCALENDAR
+""",
+ None,
+ ),
+ (
+ "1.3 - no truncation - until",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=WEEKLY;UNTIL=20071128T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ None,
+ ),
+ (
+ "1.4 - truncation - count",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=WEEKLY;COUNT=2000
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:COUNT=400;FREQ=WEEKLY
+END:VEVENT
+END:VCALENDAR
+""",
+ ),
+ (
+ "1.5 - truncation - until",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=DAILY;UNTIL=20471128T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:COUNT=400;FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ ),
+ (
+ "1.6 - no truncation - unbounded yearly",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=WEEKLY;UNTIL=20071128T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+ None,
+ ),
+ (
+ "1.7 - truncation - unbounded daily",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+RRULE:COUNT=400;FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+""",
+ ),
+ )
+
+ for title, original, result in data:
+ ical1 = Component.fromString(original)
+ changed = ical1.truncateRecurrence(400)
+ ical1.normalizeAll()
+ ical1 = str(ical1)
+ if result is not None:
+ if not changed:
+ self.fail("Truncation did not happen when expected: %s" % (title,))
+ else:
+ ical2 = Component.fromString(result)
+ ical2 = str(ical2)
+
+ diff = "\n".join(unified_diff(ical1.split("\n"), ical2.split("\n")))
+ self.assertEqual(str(ical1), str(ical2), "Failed comparison: %s\n%s" % (title, diff,))
+ elif changed:
+ self.fail("Truncation happened when not expected: %s" % (title,))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090508/5774e2fc/attachment-0001.html>
More information about the calendarserver-changes
mailing list