[CalendarServer-changes] [13633] CalendarServer/trunk/txdav
source_changes at macosforge.org
source_changes at macosforge.org
Thu Jun 12 03:06:36 PDT 2014
Revision: 13633
http://trac.calendarserver.org//changeset/13633
Author: gaya at apple.com
Date: 2014-06-12 03:06:36 -0700 (Thu, 12 Jun 2014)
Log Message:
-----------
Do not update past instance of event that spans past to future
Modified Paths:
--------------
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/who/groups.py
CalendarServer/trunk/txdav/who/test/test_group_attendees.py
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2014-06-11 18:38:06 UTC (rev 13632)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2014-06-12 10:06:36 UTC (rev 13633)
@@ -1961,7 +1961,7 @@
# sync group attendee members if inserting or group changed
changed = False
- if inserting or (yield self.updateGROUP_ATTENDEE(groupCUAToAttendeeMemberPropMap)):
+ if inserting or (yield self.updateEventGroupLink(groupCUAToAttendeeMemberPropMap)):
changed = component.reconcileGroupAttendees(groupCUAToAttendeeMemberPropMap)
# save for post processing when self._resourceID is non-zero
@@ -1971,7 +1971,7 @@
@inlineCallbacks
- def updateGROUP_ATTENDEE(self, groupCUAToAttendeeMemberPropMap=None):
+ def updateEventGroupLink(self, groupCUAToAttendeeMemberPropMap=None):
"""
update schema.GROUP_ATTENDEE
"""
@@ -2037,8 +2037,10 @@
@inlineCallbacks
- def deleteOldGROUP_ATTENDEE(self, component, instances, inserting, txn):
+ def removeOldEventGroupLink(self, component, instances, inserting, txn):
+ isOldEventWithGroupAttendees = False
+
# If this event is old, break possible tie to group update
if hasattr(self, "_groupCUAToAttendeeMemberPropMap"):
@@ -2056,7 +2058,7 @@
tr.END_DATE > cutoffDate_datatime
),
).on(txn)
- isOld = rows[0][0] == 0
+ isOldEventWithGroupAttendees = rows[0][0] == 0
else:
if instances and len(instances.instances):
@@ -2065,13 +2067,13 @@
Duration(seconds=config.GroupAttendees.UpdateOldEventLimitSeconds)
)
maxInstanceKey = sorted(instance for instance in instances)[-1]
- isOld = cutoffDate_DateTime > instances[maxInstanceKey].end
+ isOldEventWithGroupAttendees = cutoffDate_DateTime > instances[maxInstanceKey].end
else:
- isOld = True
+ isOldEventWithGroupAttendees = True
- if isOld:
+ if isOldEventWithGroupAttendees:
if inserting:
- # don't create GROUP_ATTENDEE rows in updateGROUP_ATTENDEE()
+ # don't create GROUP_ATTENDEE rows in updateEventGroupLink()
del self._groupCUAToAttendeeMemberPropMap
else:
# delete existing group rows
@@ -2082,7 +2084,9 @@
#Return=[ga.GROUP_ID]
).on(txn)
+ returnValue(isOldEventWithGroupAttendees)
+
def validCalendarDataCheck(self, component, inserting):
"""
Check that the calendar data is valid iCalendar.
@@ -2777,7 +2781,7 @@
# update GROUP_ATTENDEE table rows
if inserting:
- yield self.updateGROUP_ATTENDEE()
+ yield self.updateEventGroupLink()
# Post process managed attachments
if internal_state in (
@@ -2915,7 +2919,7 @@
# Now coerce indexing to off if needed
if not doInstanceIndexing:
- #instances = None # used by deleteOldGROUP_ATTENDEE() call at end
+ #instances = None # used by removeOldEventGroupLink() call at end
recurrenceLowerLimit = None
recurrenceLimit = DateTime(1900, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
@@ -3025,7 +3029,7 @@
if instanceIndexingRequired and doInstanceIndexing:
yield self._addInstances(component, instances, truncateLowerLimit, isInboxItem, txn)
- yield self.deleteOldGROUP_ATTENDEE(component, instances, inserting, txn)
+ yield self.removeOldEventGroupLink(component, instances, inserting, txn)
@inlineCallbacks
@@ -4048,7 +4052,7 @@
in L{CalendarObject.splitAt}).
@type rid: L{DateTime}
@param olderUID: sets the iCalendar UID to be used in the new resource created during the split.
- If set to L{None}, a UUID is generated and used.
+ If L{None} a UUID is generated and used.
@type olderUID: L{str}
"""
Modified: CalendarServer/trunk/txdav/who/groups.py
===================================================================
--- CalendarServer/trunk/txdav/who/groups.py 2014-06-11 18:38:06 UTC (rev 13632)
+++ CalendarServer/trunk/txdav/who/groups.py 2014-06-12 10:06:36 UTC (rev 13633)
@@ -19,13 +19,16 @@
Group membership caching
"""
+from pycalendar.datetime import DateTime
+from pycalendar.duration import Duration
from twext.enterprise.dal.record import fromTable
from twext.enterprise.dal.syntax import Delete, Select
from twext.enterprise.jobqueue import WorkItem, RegeneratingWorkItem
from twext.python.log import Logger
from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twistedcaldav.config import config
-from txdav.caldav.datastore.sql import CalendarStoreFeatures
+from txdav.caldav.datastore.scheduling.icalsplitter import iCalSplitter
+from txdav.caldav.datastore.sql import CalendarStoreFeatures, ComponentUpdateState
from txdav.common.datastore.sql_tables import schema
import datetime
import hashlib
@@ -135,7 +138,11 @@
).on(self.transaction)
# get db object
- calendarObject = (yield CalendarStoreFeatures(self.transaction._store).calendarObjectWithID(self.transaction, self.resourceID))
+ calendarObject = yield CalendarStoreFeatures(
+ self.transaction._store
+ ).calendarObjectWithID(
+ self.transaction, self.resourceID
+ )
component = yield calendarObject.componentForUser()
# Change a copy of the original, as we need the original cached on the resource
@@ -144,6 +151,66 @@
# sync group attendees
if (yield calendarObject.reconcileGroupAttendees(component)):
+
+ # group attendees in event have changed
+ if (component.masterComponent() is None or not component.isRecurring()):
+
+ # skip non-recurring old events, no instances
+ if (yield calendarObject.removeOldEventGroupLink(
+ component,
+ instances=None,
+ inserting=False,
+ txn=self.transaction
+ )):
+ returnValue(None)
+ else:
+ # skip recurring old events
+ expand = (DateTime.getToday() +
+ Duration(days=config.FreeBusyIndexExpandAheadDays))
+
+ if config.FreeBusyIndexLowerLimitDays:
+ truncateLowerLimit = DateTime.getToday()
+ truncateLowerLimit.offsetDay(-config.FreeBusyIndexLowerLimitDays)
+ else:
+ truncateLowerLimit = None
+
+ instances = component.expandTimeRanges(
+ expand,
+ lowerLimit=truncateLowerLimit,
+ ignoreInvalidInstances=True
+ )
+ if (yield calendarObject.removeOldEventGroupLink(
+ component,
+ instances=instances,
+ inserting=False,
+ txn=self.transaction
+ )):
+ returnValue(None)
+
+ # split spanning events and only update present-future split result
+ splitter = iCalSplitter(0, 1)
+ break_point = DateTime.getToday() - Duration(seconds=config.GroupAttendees.UpdateOldEventLimitSeconds)
+ rid = splitter.whereSplit(component, break_point=break_point)
+ if rid is not None:
+ yield calendarObject.split(onlyThis=True, rid=rid)
+
+ # remove group link to ensure update (update to unknown hash would work too)
+ ga = schema.GROUP_ATTENDEE
+ yield Delete(
+ From=ga,
+ Where=(ga.RESOURCE_ID == self.resourceID).And(
+ ga.GROUP_ID == self.groupID
+ )
+ ).on(self.transaction)
+
+ # update group attendee in remaining component
+ component = yield calendarObject.componentForUser()
+ component = component.duplicate()
+ change = yield calendarObject.reconcileGroupAttendees(component)
+ assert change
+ yield calendarObject._setComponentInternal(component, False, ComponentUpdateState.SPLIT_OWNER)
+ returnValue(None)
+
yield calendarObject.setComponent(component)
Modified: CalendarServer/trunk/txdav/who/test/test_group_attendees.py
===================================================================
--- CalendarServer/trunk/txdav/who/test/test_group_attendees.py 2014-06-11 18:38:06 UTC (rev 13632)
+++ CalendarServer/trunk/txdav/who/test/test_group_attendees.py 2014-06-12 10:06:36 UTC (rev 13633)
@@ -18,8 +18,7 @@
group attendee tests
"""
-import os
-
+from twext.enterprise.dal.syntax import Insert
from twext.enterprise.jobqueue import JobItem
from twext.python.filepath import CachingFilePath as FilePath
from twext.who.directory import DirectoryService
@@ -29,8 +28,10 @@
from twistedcaldav.config import config
from twistedcaldav.ical import Component, normalize_iCalStr, ignoredComponents
from txdav.caldav.datastore.test.util import populateCalendarsFrom, CommonCommonTests
+from txdav.common.datastore.sql_tables import schema
from txdav.who.directory import CalendarDirectoryRecordMixin
from txdav.who.groups import GroupCacher
+import os
class GroupAttendeeReconciliation(CommonCommonTests, unittest.TestCase):
@@ -508,6 +509,7 @@
groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
self.assertEqual(len(groupsToRefresh), 1)
+ self.assertEqual(list(groupsToRefresh)[0], "20000000-0000-0000-0000-000000000001")
wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
yield self.commit()
@@ -649,6 +651,7 @@
groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
self.assertEqual(len(groupsToRefresh), 1)
+ self.assertEqual(list(groupsToRefresh)[0], "20000000-0000-0000-0000-000000000001")
wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
yield self.commit()
@@ -824,6 +827,7 @@
yield None
returnValue(set())
+ unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
calendar = yield self.calendarUnderTest(name="calendar", home="10000000-0000-0000-0000-000000000002")
@@ -833,6 +837,7 @@
groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
self.assertEqual(len(groupsToRefresh), 1)
+ self.assertEqual(list(groupsToRefresh)[0], "20000000-0000-0000-0000-000000000001")
wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
yield self.commit()
@@ -856,12 +861,31 @@
self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
- if len(wps): # This is needed because the test currently fails and does actually create job items we have to wait for
- yield self.commit()
- yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
self.assertEqual(len(wps), 0)
+ yield self.commit()
+ yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+ #finally, simulate an event that has become old
+ self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
+ groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
+ ga = schema.GROUP_ATTENDEE
+ yield Insert({
+ ga.RESOURCE_ID: cobj._resourceID,
+ ga.GROUP_ID: groupID,
+ ga.MEMBERSHIP_HASH: (-1),
+ }
+ ).on(self.transactionUnderTest())
+ wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
+ self.assertEqual(len(wps), 1)
+ yield self.commit()
+ yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+ cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
+ vcalendar = yield cobj.component()
+ self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
+
+
@inlineCallbacks
def test_groupChangeOldNoMasterEvent(self):
"""
@@ -957,6 +981,7 @@
yield None
returnValue(set())
+ unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
calendar = yield self.calendarUnderTest(name="calendar", home="10000000-0000-0000-0000-000000000002")
@@ -966,6 +991,7 @@
groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
self.assertEqual(len(groupsToRefresh), 1)
+ self.assertEqual(list(groupsToRefresh)[0], "20000000-0000-0000-0000-000000000001")
wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
yield self.commit()
@@ -994,8 +1020,129 @@
yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
self.assertEqual(len(wps), 0)
+ #finally, simulate an event that has become old
+ self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
+ groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self.transactionUnderTest().groupByUID("20000000-0000-0000-0000-000000000001")
+ ga = schema.GROUP_ATTENDEE
+ yield Insert({
+ ga.RESOURCE_ID: cobj._resourceID,
+ ga.GROUP_ID: groupID,
+ ga.MEMBERSHIP_HASH: (-1),
+ }
+ ).on(self.transactionUnderTest())
+ wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
+ self.assertEqual(len(wps), 1)
+ yield self.commit()
+ yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+ cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
+ vcalendar = yield cobj.component()
+ self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
+
+
@inlineCallbacks
+ def test_groupChangeSpanningEvent(self):
+ """
+ Test that group attendee changes not applied to old recurring events
+ """
+
+ data_put_1 = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20120101T100000Z
+DURATION:PT1H
+RRULE:FREQ=DAILY;UNTIL=20240101T100000
+SUMMARY:event 1
+UID:event1 at ninevah.local
+ORGANIZER:MAILTO:user02 at example.com
+ATTENDEE:mailto:user02 at example.com
+ATTENDEE:MAILTO:group01 at example.com
+END:VEVENT
+END:VCALENDAR"""
+
+ data_get_1 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+BEGIN:VEVENT
+UID:event1 at ninevah.local
+DTSTART:20120101T100000Z
+DURATION:PT1H
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000001";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000001
+CREATED:20060101T150000Z
+ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
+RRULE:FREQ=DAILY;UNTIL=20240101T100000
+SUMMARY:event 1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ data_get_2 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+BEGIN:VEVENT
+UID:event1 at ninevah.local
+{start}DURATION:PT1H
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
+CREATED:20060101T150000Z
+ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
+{relatedTo}RRULE:FREQ=DAILY;UNTIL=20240101T100000
+SEQUENCE:2
+SUMMARY:event 1
+END:VEVENT
+END:VCALENDAR
+"""
+
+ @inlineCallbacks
+ def expandedMembers(self, records=None):
+ yield None
+ returnValue(set())
+
+ groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+
+ calendar = yield self.calendarUnderTest(name="calendar", home="10000000-0000-0000-0000-000000000002")
+ vcalendar = Component.fromString(data_put_1)
+ yield calendar.createCalendarObjectWithName("data1.ics", vcalendar)
+ yield self.commit()
+
+ cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
+ vcalendar = yield cobj.component()
+ self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1))
+
+ yield self._verifyObjectResourceCount("10000000-0000-0000-0000-000000000001", 1)
+
+ self.patch(CalendarDirectoryRecordMixin, "expandedMembers", expandedMembers)
+
+ wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
+ yield self.commit()
+ self.assertEqual(len(wps), 1)
+ yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+ cobj = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
+ vcalendar = yield cobj.component()
+
+ for component in vcalendar.subcomponents():
+ if component.name() in ignoredComponents:
+ continue
+ relatedTo = component.getProperty("RELATED-TO")
+ start = component.getProperty("DTSTART")
+ break
+
+ self.assertEqual(
+ normalize_iCalStr(vcalendar),
+ normalize_iCalStr(data_get_2.format(start=start, relatedTo=relatedTo)))
+
+
+ @inlineCallbacks
def test_groupRemovalFromDirectory(self):
"""
Test that removing a group from the directory also removes the expanded attendees.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140612/78382fe7/attachment-0001.html>
More information about the calendarserver-changes
mailing list