[CalendarServer-changes] [13787] CalendarServer/branches/release/CalendarServer-5.3-dev

source_changes at macosforge.org source_changes at macosforge.org
Thu Jul 24 08:57:13 PDT 2014


Revision: 13787
          http://trac.calendarserver.org//changeset/13787
Author:   cdaboo at apple.com
Date:     2014-07-24 08:57:13 -0700 (Thu, 24 Jul 2014)
Log Message:
-----------
Don't update the TIME_RANGE table when there are no changes to it.

Modified Paths:
--------------
    CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/stdconfig.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/icaldiff.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/sql.py
    CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/test/test_sql.py

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/stdconfig.py	2014-07-24 15:56:17 UTC (rev 13786)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/twistedcaldav/stdconfig.py	2014-07-24 15:57:13 UTC (rev 13787)
@@ -1012,6 +1012,7 @@
     "FreeBusyIndexExpandAheadDays": 365,
     "FreeBusyIndexExpandMaxDays": 5 * 365,
     "FreeBusyIndexDelayedExpand": True,
+    "FreeBusyIndexSmartUpdate": True,
 
     # The RootResource uses a twext property store. Specify the class here
     "RootResourcePropStoreClass": "twext.web2.dav.xattrprops.xattrPropertyStore",

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/icaldiff.py	2014-07-24 15:56:17 UTC (rev 13786)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/icaldiff.py	2014-07-24 15:57:13 UTC (rev 13787)
@@ -692,7 +692,7 @@
         return partstatChanged
 
 
-    def whatIsDifferent(self):
+    def whatIsDifferent(self, isiTip=True):
         """
         Compare the two calendar objects in their entirety and return a list of properties
         and PARTSTAT parameters that are different.
@@ -721,7 +721,7 @@
         for key in (oldset & newset):
             component1 = oldmap[key]
             component2 = newmap[key]
-            self._diffComponents(component1, component2, rids)
+            self._diffComponents(component1, component2, rids, isiTip)
 
         # Now verify that each additional component in oldset matches a derived component in newset
         for key in oldset - newset:
@@ -729,7 +729,7 @@
             newcomponent = self.newcalendar.deriveInstance(key[2])
             if newcomponent is None:
                 continue
-            self._diffComponents(oldcomponent, newcomponent, rids)
+            self._diffComponents(oldcomponent, newcomponent, rids, isiTip)
 
         # Now verify that each additional component in oldset matches a derived component in newset
         for key in newset - oldset:
@@ -737,24 +737,59 @@
             if oldcomponent is None:
                 continue
             newcomponent = newmap[key]
-            self._diffComponents(oldcomponent, newcomponent, rids)
+            self._diffComponents(oldcomponent, newcomponent, rids, isiTip)
 
         return rids
 
 
-    def _componentDuplicateAndNormalize(self, comp):
+    TRPROPS = frozenset((
+        "DTSTART",
+        "DTEND",
+        "DURATION",
+        "DUE",
+        "RECURRENCE-ID",
+        "RRULE",
+        "RDATE",
+        "EXDATE",
+        "STATUS",
+        "TRANSP",
+        "X-APPLE-TRAVEL-START",
+        "X-APPLE-TRAVEL-DURATION",
+        "X-APPLE-TRAVEL-RETURN",
+        "X-APPLE-TRAVEL-RETURN-DURATION",
+    ))
+
+    def timeRangeDifference(self):
+        """
+        Is there a difference between the two components that implies a change to the time or
+        transparency/status of any instance.
+
+        @return: L{True} if there is such a change, L{False} otherwise
+        @rtype: L{bool}
+        """
+
+        for props in self.whatIsDifferent(isiTip=False).values():
+            props = frozenset(props.keys())
+            if props & self.TRPROPS:
+                return True
+        else:
+            return False
+
+
+    def _componentDuplicateAndNormalize(self, comp, isiTip=True):
         comp = comp.duplicate()
         comp.normalizePropertyValueLists("EXDATE")
-        comp.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
-        comp.removePropertyParameters("ATTENDEE", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
         comp.removeAlarms()
         comp.normalizeAll()
         comp.normalizeAttachments()
-        iTipGenerator.prepareSchedulingMessage(comp, reply=True)
+        if isiTip:
+            comp.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
+            comp.removePropertyParameters("ATTENDEE", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
+            iTipGenerator.prepareSchedulingMessage(comp, reply=True)
         return comp
 
 
-    def _diffComponents(self, comp1, comp2, rids):
+    def _diffComponents(self, comp1, comp2, rids, isiTip=True):
 
         assert isinstance(comp1, Component) and isinstance(comp2, Component)
 
@@ -763,8 +798,8 @@
             return
 
         # Duplicate then normalize for comparison
-        comp1 = self._componentDuplicateAndNormalize(comp1)
-        comp2 = self._componentDuplicateAndNormalize(comp2)
+        comp1 = self._componentDuplicateAndNormalize(comp1, isiTip)
+        comp2 = self._componentDuplicateAndNormalize(comp2, isiTip)
 
         # Diff all the properties
         propdiff = set(comp1.properties()) ^ set(comp2.properties())
@@ -773,7 +808,6 @@
         propsChanged = {}
         for prop in propdiff:
             if prop.name() in (
-                "TRANSP",
                 "DTSTAMP",
                 "CREATED",
                 "LAST-MODIFIED",

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py	2014-07-24 15:56:17 UTC (rev 13786)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/scheduling/processing.py	2014-07-24 15:57:13 UTC (rev 13787)
@@ -162,7 +162,7 @@
         self.recipient_calendar_resource = None
         calendar_resource = (yield getCalendarObjectForRecord(self.txn, self.recipient.principal, self.uid))
         if calendar_resource:
-            self.recipient_calendar = (yield calendar_resource.componentForUser(self.recipient.principal.uid))
+            self.recipient_calendar = (yield calendar_resource.componentForUser(self.recipient.principal.uid)).duplicate()
             self.recipient_calendar_resource = calendar_resource
 
 

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/sql.py	2014-07-24 15:56:17 UTC (rev 13786)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/sql.py	2014-07-24 15:57:13 UTC (rev 13787)
@@ -58,6 +58,7 @@
 from twistedcaldav.memcacher import Memcacher
 
 from txdav.base.propertystore.base import PropertyName
+from txdav.caldav.datastore.scheduling.icaldiff import iCalDiff
 from txdav.caldav.datastore.scheduling.icalsplitter import iCalSplitter
 from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
 from txdav.caldav.datastore.util import AttachmentRetrievalTransport, \
@@ -1746,7 +1747,7 @@
 
 
     @inlineCallbacks
-    def preservePrivateComments(self, component, inserting):
+    def preservePrivateComments(self, component, inserting, internal_state):
         """
         Check for private comments on the old resource and the new resource and re-insert
         ones that are lost.
@@ -1765,7 +1766,7 @@
                 "X-CALENDARSERVER-ATTENDEE-COMMENT",
             ))
 
-            if old_has_private_comments and not new_has_private_comments:
+            if old_has_private_comments and not new_has_private_comments and internal_state == ComponentUpdateState.NORMAL:
                 # Transfer old comments to new calendar
                 log.debug("Organizer private comment properties were entirely removed by the client. Restoring existing properties.")
                 old_calendar = (yield self.componentForUser())
@@ -1779,7 +1780,7 @@
             # to raise an error to prevent that so the client bugs can be tracked down.
 
             # Look for properties with duplicate "X-CALENDARSERVER-ATTENDEE-REF" values in the same component
-            if component.hasDuplicatePrivateComments(doFix=config.RemoveDuplicatePrivateComments):
+            if component.hasDuplicatePrivateComments(doFix=config.RemoveDuplicatePrivateComments) and internal_state == ComponentUpdateState.NORMAL:
                 raise DuplicatePrivateCommentsError("Duplicate X-CALENDARSERVER-ATTENDEE-COMMENT properties present.")
 
 
@@ -2196,7 +2197,7 @@
                 self.validAccess(component, inserting, internal_state)
 
             # Preserve private comments
-            yield self.preservePrivateComments(component, inserting)
+            yield self.preservePrivateComments(component, inserting, internal_state)
 
             managed_copied, managed_removed = (yield self.resourceCheckAttachments(component, inserting))
 
@@ -2215,7 +2216,7 @@
             yield self._lockUID(component, inserting, internal_state)
 
             # Preserve private comments
-            yield self.preservePrivateComments(component, inserting)
+            yield self.preservePrivateComments(component, inserting, internal_state)
 
             # Fix broken VTODOs
             yield self.replaceMissingToDoProperties(component, inserting, internal_state)
@@ -2259,6 +2260,13 @@
                 if did_implicit_action:
                     self._componentChanged = True
 
+            if not hasattr(self, "tr_change"):
+                if inserting or hasattr(component, "noInstanceIndexing") or not config.FreeBusyIndexSmartUpdate:
+                    self.tr_change = None
+                else:
+                    oldcomponent = yield self.componentForUser()
+                    self.tr_change = iCalDiff(oldcomponent, component, False).timeRangeDifference()
+
             # Always do the per-user data merge right before we store
             component = (yield self.mergePerUserData(component, inserting))
 
@@ -2331,7 +2339,12 @@
         # In some cases there is no need to remove/rebuild the instance index because we know no time or
         # freebusy related properties have changed (e.g. an attendee reply and refresh). In those cases
         # the component will have a special attribute present to let us know to suppress the instance indexing.
-        instanceIndexingRequired = not getattr(component, "noInstanceIndexing", False) or inserting or reCreate
+        if inserting or reCreate:
+            instanceIndexingRequired = True
+        elif getattr(component, "noInstanceIndexing", False):
+            instanceIndexingRequired = False
+        else:
+            instanceIndexingRequired = getattr(self, "tr_change", True) in (None, True)
 
         if instanceIndexingRequired:
 

Modified: CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/test/test_sql.py	2014-07-24 15:56:17 UTC (rev 13786)
+++ CalendarServer/branches/release/CalendarServer-5.3-dev/txdav/caldav/datastore/test/test_sql.py	2014-07-24 15:57:13 UTC (rev 13787)
@@ -52,6 +52,7 @@
 from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
 from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
 from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
+from txdav.caldav.datastore.sql import CalendarObject
 from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests, \
     test_event_text, OTHER_HOME_UID
 from txdav.caldav.datastore.test.test_file import setUpCalendarStore
@@ -1456,6 +1457,7 @@
         # Re-add event with re-indexing
         calendar = yield self.calendarUnderTest()
         calendarObject = yield self.calendarObjectUnderTest(name="indexing.ics")
+        calendarObject.tr_change = True
         yield calendarObject.setComponent(component)
         instances2 = yield calendarObject.instances()
         self.assertNotEqual(
@@ -6321,3 +6323,572 @@
         cobjs = yield cal.calendarObjects()
         self.assertEqual(len(cobjs), 1)
         yield self.failUnlessFailure(cobjs[0].splitAt(PyCalendarDateTime.parseText("%(now_fwd25)s" % self.subs)), InvalidSplit)
+
+
+
+class TimeRangeUpdateOptimization(CommonCommonTests, unittest.TestCase):
+    """
+    CalendarObject splitting tests
+    """
+
+    EVENT1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event #2
+DTSTAMP:20100203T013909Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T130000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT4 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+TRANSP:TRANSPARENT
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT5 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+STATUS:CANCELLED
+DTSTAMP:20100203T013909Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT6 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+X-APPLE-TRAVEL-DURATION;VALUE=DURATION:PT1H
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT7 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+RRULE:FREQ=DAILY
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT8 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+RRULE:FREQ=DAILY;COUNT=10
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT9 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+RRULE:FREQ=DAILY
+EXDATE:{now}T120000Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+    EVENT10 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+RRULE:FREQ=DAILY
+RDATE:{now}T150000Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(TimeRangeUpdateOptimization, self).setUp()
+        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
+        yield self.populate()
+
+        self.now = PyCalendarDateTime.getNowUTC()
+        self.now.setDateOnly(True)
+
+        self.trcount = 0
+        base_addInstances = CalendarObject._addInstances
+        def __addInstances(*args):
+            self.trcount += 1
+            return base_addInstances(*args)
+        self.patch(CalendarObject, "_addInstances", __addInstances)
+
+        self.patch(config, "FreeBusyIndexDelayedExpand", False)
+        self.patch(config, "FreeBusyIndexSmartUpdate", True)
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+
+    def storeUnderTest(self):
+        """
+        Create and return a L{CalendarStore} for testing.
+        """
+        return self._sqlCalendarStore
+
+
+    @property
+    def requirements(self):
+        return {
+            "home1": {
+                "calendar_1": {},
+            },
+            "user01": {
+                "calendar": {},
+                "inbox": {},
+            },
+            "user02": {
+                "calendar": {},
+                "inbox": {},
+            },
+        }
+
+
+    @inlineCallbacks
+    def test_initalPUT(self):
+        """
+        Test that initial PUT causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withoutTRChange(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT does not cause T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT2.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withoutOptimization(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        self.patch(config, "FreeBusyIndexSmartUpdate", False)
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT does cause T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT2.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 2)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withTRChange(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT causes T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT3.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 2)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withTranspChange(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT causes T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT4.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 2)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withStatusChange(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT causes T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT5.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 2)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withTravelTimeChange(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT causes T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT6.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 2)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withRRULEChange(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT7.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT causes T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT8.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 2)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withEXDATEAdd(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT7.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT causes T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT9.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 2)
+
+
+    @inlineCallbacks
+    def test_updatePUT_withRDATEAdd(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest()
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.EVENT7.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 1)
+
+        # Second PUT causes T-R change
+        cobj = yield self.calendarObjectUnderTest()
+        yield cobj.setComponent(Component.fromString(self.EVENT10.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 2)
+
+
+    INVITE1 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+    INVITE2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event
+DTSTAMP:20100203T013909Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+    INVITE3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T120000Z
+DURATION:PT1H
+SUMMARY:New Event #2
+DTSTAMP:20100203T013909Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+    INVITE4 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100203T013849Z
+UID:uid1
+DTSTART:{now}T140000Z
+DURATION:PT1H
+SUMMARY:New Event #2
+DTSTAMP:20100203T013909Z
+ORGANIZER:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user02 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+    @inlineCallbacks
+    def test_schedulingPUT(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest(home="user01", name="calendar")
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.INVITE1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 3)
+
+        # Attendee reply does not cause T-R change (except for inbox item and attendee resource transp change)
+        cal = yield self.calendarUnderTest(home="user02", name="calendar")
+        cobjs = yield cal.calendarObjects()
+        self.assertEqual(len(cobjs), 1)
+        yield cobjs[0].setComponent(Component.fromString(self.INVITE2.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 5)
+
+        # Organizer summary change does not cause T-R change (except for inbox item)
+        cobj = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar")
+        yield cobj.setComponent(Component.fromString(self.INVITE3.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 6)
+
+        # Organizer dtstart change causes T-R change
+        cobj = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar")
+        yield cobj.setComponent(Component.fromString(self.INVITE4.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 9)
+
+
+    @inlineCallbacks
+    def test_schedulingPUT_withoutOptimization(self):
+        """
+        Test that second PUT withe time change causes a TIME_RANGE update
+        """
+
+        self.patch(config, "FreeBusyIndexSmartUpdate", False)
+
+        # First PUT causes T-R change
+        cal = yield self.calendarUnderTest(home="user01", name="calendar")
+        yield cal.createObjectResourceWithName("1.ics", Component.fromString(self.INVITE1.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 3)
+
+        # Attendee reply does cause T-R change (except for organizer update)
+        cal = yield self.calendarUnderTest(home="user02", name="calendar")
+        cobjs = yield cal.calendarObjects()
+        self.assertEqual(len(cobjs), 1)
+        yield cobjs[0].setComponent(Component.fromString(self.INVITE2.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 5)
+
+        # Organizer summary change causes T-R change
+        cobj = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar")
+        yield cobj.setComponent(Component.fromString(self.INVITE3.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 8)
+
+        # Organizer dtstart change causes T-R change
+        cobj = yield self.calendarObjectUnderTest(home="user01", calendar_name="calendar")
+        yield cobj.setComponent(Component.fromString(self.INVITE4.format(now=self.now.getText())))
+        yield self.commit()
+
+        self.assertEqual(self.trcount, 11)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140724/66618cdf/attachment-0001.html>


More information about the calendarserver-changes mailing list