[CalendarServer-changes] [9761] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Aug 31 12:13:52 PDT 2012


Revision: 9761
          http://trac.macosforge.org/projects/calendarserver/changeset/9761
Author:   cdaboo at apple.com
Date:     2012-08-31 12:13:51 -0700 (Fri, 31 Aug 2012)
Log Message:
-----------
Add optional duplicate alarm removal.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/calverify.py
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/twistedcaldav/method/put_common.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/test/test_icalendar.py

Modified: CalendarServer/trunk/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/calverify.py	2012-08-31 18:55:58 UTC (rev 9760)
+++ CalendarServer/trunk/calendarserver/tools/calverify.py	2012-08-31 19:13:51 UTC (rev 9761)
@@ -164,9 +164,34 @@
 
     return fixed, unfixed
 
+def new_hasDuplicateAlarms(self, doFix=False):
+    """
+    test and optionally remove alarms that have the same ACTION and TRIGGER values in the same component.
+    """
+    changed = False
+    if self.name() in ("VCALENDAR", "X-CALENDARSERVER-PERUSER",):
+        for component in self.subcomponents():
+            if component.name() in ("VTIMEZONE",):
+                continue
+            changed = component.hasDuplicateAlarms(doFix) or changed
+    else:
+        action_trigger = set()
+        for component in tuple(self.subcomponents()):
+            if component.name() == "VALARM":
+                item = (component.propertyValue("ACTION"), component.propertyValue("TRIGGER"),)
+                if item in action_trigger:
+                    if doFix:
+                        self.removeComponent(component)
+                    changed = True
+                else:
+                    action_trigger.add(item)
+    return changed
+
 Component.validRecurrenceIDs = new_validRecurrenceIDs
+if not hasattr(Component, "maxAlarmCounts"):
+    Component.hasDuplicateAlarms = new_hasDuplicateAlarms
 
-VERSION = "6"
+VERSION = "7"
 
 def printusage(e=None):
     if e:
@@ -934,6 +959,8 @@
                 component.validCalendarData(doFix=False, validateRecurrences=True)
                 component.validCalendarForCalDAV(methodAllowed=isinbox)
                 component.validOrganizerForScheduling(doFix=False)
+                if component.hasDuplicateAlarms(doFix=False):
+                    raise InvalidICalendarDataError("Duplicate VALARMS")
             self.noPrincipalPathCUAddresses(component, doFix=False)
         except ValueError, e:
             result = False
@@ -1058,6 +1085,7 @@
                 component.validCalendarData(doFix=True, validateRecurrences=True)
                 component.validCalendarForCalDAV(methodAllowed=isinbox)
                 component.validOrganizerForScheduling(doFix=True)
+                component.hasDuplicateAlarms(doFix=True)
             self.noPrincipalPathCUAddresses(component, doFix=True)
         except ValueError:
             result = False

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2012-08-31 18:55:58 UTC (rev 9760)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2012-08-31 19:13:51 UTC (rev 9761)
@@ -2301,7 +2301,30 @@
             for component in tuple(self.subcomponents()):
                 if component.name() == "VALARM":
                     self.removeComponent(component)
-                
+
+    def hasDuplicateAlarms(self, doFix=False):
+        """
+        Test and optionally remove alarms that have the same ACTION and TRIGGER values in the same component.
+        """
+        changed = False
+        if self.name() in ("VCALENDAR", "X-CALENDARSERVER-PERUSER",):
+            for component in self.subcomponents():
+                if component.name() in ("VTIMEZONE",):
+                    continue
+                changed = component.hasDuplicateAlarms(doFix) or changed
+        else:
+            action_trigger = set()
+            for component in tuple(self.subcomponents()):
+                if component.name() == "VALARM":
+                    item = (component.propertyValue("ACTION"), component.propertyValue("TRIGGER"),)
+                    if item in action_trigger:
+                        if doFix:
+                            self.removeComponent(component)
+                        changed = True
+                    else:
+                        action_trigger.add(item)
+        return changed
+
     def filterProperties(self, remove=None, keep=None, do_subcomponents=True):
         """
         Remove all properties that do not match the provided set.

Modified: CalendarServer/trunk/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/put_common.py	2012-08-31 18:55:58 UTC (rev 9760)
+++ CalendarServer/trunk/twistedcaldav/method/put_common.py	2012-08-31 19:13:51 UTC (rev 9761)
@@ -805,41 +805,45 @@
         
         returnValue(changed)
 
-    def addDefaultAlarm(self):
+    def processAlarms(self):
         """
-        Add a default alarm if required.
+        Remove duplicate alarms. Add a default alarm if required.
         
         @return: indicate whether a change was made
         @rtype: C{bool}
         """
 
+        # Remove duplicate alarms
+        changed = False
+        if config.RemoveDuplicateAlarms:
+            changed = self.calendar.hasDuplicateAlarms(doFix=True)
+
         # Only if feature enabled
         if not config.EnableDefaultAlarms:
-            return False
+            return changed
 
         # Check that we are creating and this is not the inbox
         if not self.destinationcal or self.destination.exists() or self.isiTIP:
-            return False
+            return changed
         
         # Never add default alarms to calendar data in shared calendars
         if self.destinationparent.isVirtualShare():
-            return False
+            return changed
 
         # Add default alarm for VEVENT and VTODO only
         mtype = self.calendar.mainType().upper()
         if self.calendar.mainType().upper() not in ("VEVENT", "VTODO"):
-            return False
+            return changed
         vevent = mtype == "VEVENT"
         
         # Check timed or all-day
         start, _ignore_end = self.calendar.mainComponent(allow_multiple=True).getEffectiveStartEnd()
         if start is None:
             # Yes VTODOs might have no DTSTART or DUE - in this case we do not add a default
-            return False
+            return changed
         timed = not start.isDateOnly()
         
         # See if default exists and add using appropriate logic
-        changed = False
         alarm = self.destinationparent.getDefaultAlarm(vevent, timed)
         if alarm:
             changed = self.calendar.addAlarms(alarm)
@@ -1167,8 +1171,8 @@
             # Handle sharing dropbox normalization
             dropboxChanged = (yield self.dropboxPathNormalization())
 
-            # Default alarms
-            alarmChanged = self.addDefaultAlarm()
+            # Default/duplicate alarms
+            alarmChanged = self.processAlarms()
 
             # Do scheduling
             implicit_result = (yield self.doImplicitScheduling())

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2012-08-31 18:55:58 UTC (rev 9760)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2012-08-31 19:13:51 UTC (rev 9761)
@@ -561,7 +561,8 @@
                             # the database, merge the data from the filesystem
                             # into the database homes.
 
-    "EnableDefaultAlarms" : True, # Support for default alarms generated by the server
+    "EnableDefaultAlarms" :  True, # Support for default alarms generated by the server
+    "RemoveDuplicateAlarms": True,  # Remove duplicate alarms on PUT
 
     # CardDAV Features
     "DirectoryAddressBook": {

Modified: CalendarServer/trunk/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2012-08-31 18:55:58 UTC (rev 9760)
+++ CalendarServer/trunk/twistedcaldav/test/test_icalendar.py	2012-08-31 19:13:51 UTC (rev 9761)
@@ -7441,3 +7441,613 @@
         component.removeComponent(subComponent)
         self.assertEquals(subComponent._parent, None)
         self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+    def test_hasDuplicateAlarms(self):
+        """
+        Test that L{Component.hasDuplicateAlarms} correctly detects, but does not fix, duplicate alarms.
+        """
+
+        data = (
+            # No Alarms
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            
+            # One alarm
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            
+            # Two different alarms
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            
+            # Two duplicates, one different
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+            
+            # Two duplicates in one component, three different in another
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:PT0S
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+            
+            # Four duplicates in X-CALENDARSERVER-PERINSTANCE
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                True,
+            ),
+        )
+
+        for txt, result in data: 
+            component = Component.fromString(txt)
+            self.assertEqual(component.hasDuplicateAlarms(doFix=False), result)
+            self.assertEqual(str(component), txt.replace("\n", "\r\n"))
+
+    def test_hasDuplicateAlarms_withFix(self):
+        """
+        Test that L{Component.hasDuplicateAlarms} correctly removes duplicate alarms.
+        """
+
+        data = (
+            # No Alarms
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            
+            # One alarm
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            
+            # Two different alarms
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                False,
+            ),
+            
+            # Two duplicates, one different
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+            
+            # Two duplicates in one component, three different in another
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT0M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20080602T120000Z
+DTSTART:20080602T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT5M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:PT0S
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                True,
+            ),
+            
+            # Four duplicates in X-CALENDARSERVER-PERINSTANCE
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+SUMMARY:Test
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DURATION:PT1H
+DTSTAMP:20080601T120000Z
+RRULE:FREQ=DAILY
+SUMMARY:Test
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""",
+                True,
+            ),
+        )
+
+        for txt, result, result_changed in data: 
+            component = Component.fromString(txt)
+            changed = component.hasDuplicateAlarms(doFix=True)
+            self.assertEqual(str(component), result.replace("\n", "\r\n"))
+            self.assertEqual(changed, result_changed)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120831/9d93a737/attachment-0001.html>


More information about the calendarserver-changes mailing list