[CalendarServer-changes] [13323] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Fri Apr 18 11:05:54 PDT 2014
Revision: 13323
http://trac.calendarserver.org//changeset/13323
Author: gaya at apple.com
Date: 2014-04-18 11:05:54 -0700 (Fri, 18 Apr 2014)
Log Message:
-----------
fix group attendees
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/txdav/caldav/datastore/sql.py
CalendarServer/trunk/txdav/who/directory.py
CalendarServer/trunk/txdav/who/groups.py
CalendarServer/trunk/txdav/who/test/test_group_attendees.py
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2014-04-18 16:32:45 UTC (rev 13322)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2014-04-18 18:05:54 UTC (rev 13323)
@@ -3391,10 +3391,8 @@
yield component.normalizeCalendarUserAddresses(lookupFunction, recordFunction, toUUID)
- @inlineCallbacks
- def expandGroupAttendee(self, groupCUA, memberCUAs, recordFunction):
+ def _reconcileGroupAttendee(self, groupCUA, memberAtttendeeProps):
- changed = False
for component in self.subcomponents():
if component.name() in ignoredComponents:
continue
@@ -3403,11 +3401,12 @@
oldAttendeeCUAs = set([attendeeProp.value() for attendeeProp in oldAttendeeProps])
# add new member attendees
- for memberCUA in sorted(set(memberCUAs) - oldAttendeeCUAs):
- directoryRecord = yield recordFunction(memberCUA)
- newAttendeeProp = directoryRecord.attendee(params={"MEMBER": groupCUA})
- component.addProperty(newAttendeeProp)
- changed = True
+ memberCUAs = set()
+ for newAttendeeProp in memberAtttendeeProps:
+ memberCUA = newAttendeeProp.value()
+ if newAttendeeProp.value() not in oldAttendeeCUAs:
+ component.addProperty(newAttendeeProp)
+ memberCUAs.add(memberCUA)
# remove attendee or update MEMBER attribute for non-primary attendees in this group,
for attendeeProp in oldAttendeeProps:
@@ -3418,15 +3417,30 @@
attendeeProp.removeParameterValue("MEMBER", groupCUA)
if not attendeeProp.parameterValues("MEMBER"):
component.removeProperty(attendeeProp)
- changed = True
else:
if attendeeProp.value() in memberCUAs:
attendeeProp.setParameter("MEMBER", parameterValues + (groupCUA,))
- changed = True
- returnValue(changed)
+ def reconcileGroupAttendees(self, groupCUAToAttendeeMemberPropMap):
+ allMemberCUAs = set()
+ for groupCUA, memberAttendeeProps in groupCUAToAttendeeMemberPropMap.iteritems():
+ self._reconcileGroupAttendee(groupCUA, memberAttendeeProps)
+ allMemberCUAs |= set([memberAttendeeProp.value() for memberAttendeeProp in memberAttendeeProps])
+
+ # remove orphans
+ for component in self.subcomponents():
+ if component.name() in ignoredComponents:
+ continue
+
+ for attendeeMemberProp in component.properties("ATTENDEE"):
+ if attendeeMemberProp.hasParameter("MEMBER"):
+ attendeeCUA = attendeeMemberProp.value()
+ if attendeeCUA not in allMemberCUAs:
+ component.removeProperty(attendeeMemberProp)
+
+
def allPerUserUIDs(self):
results = set()
@@ -3723,7 +3737,7 @@
-def normalize_iCalStr(icalstr):
+def normalize_iCalStr(icalstr, sort=False):
"""
Normalize a string representation of ical data for easy test comparison.
"""
@@ -3735,6 +3749,8 @@
pos = line.find(";X-CALENDARSERVER-DTSTAMP=")
if pos != -1:
lines[ctr] = line[:pos] + line[pos + len(";X-CALENDARSERVER-DTSTAMP=") + 16:]
+ if sort:
+ lines.sort()
icalstr = "\r\n".join(lines)
return icalstr + "\r\n"
Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py 2014-04-18 16:32:45 UTC (rev 13322)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py 2014-04-18 18:05:54 UTC (rev 13323)
@@ -92,7 +92,7 @@
ObjectResourceNameNotAllowedError, TooManyObjectResourcesError, \
InvalidUIDError, UIDExistsError, UIDExistsElsewhereError, \
InvalidResourceMove, InvalidComponentForStoreError, \
- NoSuchObjectResourceError, AllRetriesFailed
+ NoSuchObjectResourceError
from txdav.xml import element
from txdav.idav import ChangeCategory
@@ -1915,9 +1915,6 @@
# calendar data
yield component.normalizeCalendarUserAddresses(normalizationLookup, self.directoryService().recordWithCalendarUserAddress)
- # Expand groups
- yield self.expandGroupAttendees(component, inserting)
-
# Valid attendee list size check
yield self.validAttendeeListSizeCheck(component, inserting)
@@ -1934,9 +1931,9 @@
@inlineCallbacks
- def expandGroupAttendees(self, component, inserting):
+ def reconcileGroupAttendees(self, component):
"""
- Expand group attendees
+ reconcile group attendees
"""
if not config.Scheduling.Options.AllowGroupAsAttendee:
returnValue(None)
@@ -1946,41 +1943,84 @@
attendeeProp.value() for attendeeProp in attendeeProps
if attendeeProp.parameterValue("CUTYPE") == "GROUP"
])
+
+ groupCUAToAttendeeMemberPropMap = {}
for groupCUA in groupCUAs:
groupRecord = yield self.directoryService().recordWithCalendarUserAddress(groupCUA)
if groupRecord:
members = yield groupRecord.expandedMembers()
-
- # add group attendees
- yield component.expandGroupAttendee(
- groupRecord.canonicalCalendarUserAddress(),
- set([member.canonicalCalendarUserAddress() for member in members]),
- self.directoryService().recordWithCalendarUserAddress
+ groupCUAToAttendeeMemberPropMap[groupRecord.canonicalCalendarUserAddress()] = set(
+ [member.attendeeProperty(params={"MEMBER": groupCUA}) for member in members]
)
- # tie event to group cacher
- if not inserting:
- # calculate hash
- memberUIDs = sorted([member.uid for member in members])
- membershipHashContent = hashlib.md5()
- for memberUID in memberUIDs:
- membershipHashContent.update(memberUID)
- membershipHash = membershipHashContent.hexdigest()
+ # sync group attendees
+ component.reconcileGroupAttendees(groupCUAToAttendeeMemberPropMap)
- # associate group ID with self
- groupID, _ignore_name, membershipHash, _ignore_modDate = yield self._txn.groupByUID(groupRecord.uid)
- try:
- groupAttendee = schema.GROUP_ATTENDEE
- yield Insert({
- groupAttendee.RESOURCE_ID: self._resourceID,
- groupAttendee.GROUP_ID: groupID,
- groupAttendee.MEMBERSHIP_HASH: membershipHash,
- }).on(self._txn)
- except AllRetriesFailed:
- pass
+ # save for post processing
+ self._groupCUAToAttendeeMemberPropMap = groupCUAToAttendeeMemberPropMap
+ @inlineCallbacks
+ def updateGROUP_ATTENDEE(self):
+ """
+ update schema.GROUP_ATTENDEE
+ """
+ if not hasattr(self, "_groupCUAToAttendeeMemberPropMap"):
+ returnValue(None)
+
+ ga = schema.GROUP_ATTENDEE
+ rows = yield Select(
+ [ga.GROUP_ID, ga.MEMBERSHIP_HASH],
+ From=ga,
+ Where=ga.RESOURCE_ID == self._resourceID,
+ ).on(self._txn)
+ oldGAs = dict(rows)
+
+ for groupCUA, memberAttendeeProps in self._groupCUAToAttendeeMemberPropMap.iteritems():
+ groupRecord = yield self.directoryService().recordWithCalendarUserAddress(groupCUA)
+ if groupRecord:
+ members = set([(
+ yield self.directoryService().recordWithCalendarUserAddress(
+ memberAttendeeProp.value()
+ )
+ ) for memberAttendeeProp in memberAttendeeProps
+ ])
+
+ membershipHashContent = hashlib.md5()
+ for memberUID in sorted([member.uid for member in members]):
+ membershipHashContent.update(memberUID)
+ membershipHash = membershipHashContent.hexdigest()
+
+ groupID, _ignore_name, _ignoreMembershipHash, _ignore_modDate = yield self._txn.groupByUID(groupRecord.uid)
+
+ if groupID in oldGAs:
+ if oldGAs[groupID] != membershipHash:
+ yield Update({
+ ga.MEMBERSHIP_HASH: membershipHash,
+ },
+ Where=(ga.RESOURCE_ID == self._resourceID).And(
+ ga.GROUP_ID == groupID
+ )
+ ).on(self._txn)
+ del oldGAs[groupID]
+ else:
+ yield Insert({
+ ga.RESOURCE_ID: self._resourceID,
+ ga.GROUP_ID: groupID,
+ ga.MEMBERSHIP_HASH: membershipHash,
+ }
+ ).on(self._txn)
+
+ for groupID in oldGAs:
+ yield Delete(
+ From=ga,
+ Where=(ga.RESOURCE_ID == self._resourceID).And(
+ ga.GROUP_ID == groupID
+ )
+ ).on(self._txn)
+
+
def validCalendarDataCheck(self, component, inserting):
"""
Check that the calendar data is valid iCalendar.
@@ -2391,6 +2431,10 @@
internal_request=is_internal,
))
+ # group attendees
+ if scheduler.state == "organizer":
+ yield self.reconcileGroupAttendees(component)
+
# Set an attribute on this object to indicate that it is valid to check for an event split. We need to do this here so that if a timeout
# occurs whilst doing implicit processing (most likely because the event is too big) we are able to subsequently detect that it is OK
# to split and then try that.
@@ -2635,9 +2679,8 @@
yield self.updateDatabase(component, inserting=inserting)
- # add GROUP_ATTENNDEE rows using just created _resourceID
- if inserting:
- yield self.expandGroupAttendees(component, False)
+ # update GROUP_ATTENNDEE rows using
+ yield self.updateGROUP_ATTENDEE()
# Post process managed attachments
if internal_state in (
Modified: CalendarServer/trunk/txdav/who/directory.py
===================================================================
--- CalendarServer/trunk/txdav/who/directory.py 2014-04-18 16:32:45 UTC (rev 13322)
+++ CalendarServer/trunk/txdav/who/directory.py 2014-04-18 18:05:54 UTC (rev 13323)
@@ -505,7 +505,7 @@
returnValue(True)
- def attendee(self, params={}):
+ def attendeeProperty(self, params={}):
"""
Returns a pycalendar ATTENDEE property for this record.
Modified: CalendarServer/trunk/txdav/who/groups.py
===================================================================
--- CalendarServer/trunk/txdav/who/groups.py 2014-04-18 16:32:45 UTC (rev 13322)
+++ CalendarServer/trunk/txdav/who/groups.py 2014-04-18 18:05:54 UTC (rev 13323)
@@ -23,8 +23,9 @@
from twext.enterprise.dal.syntax import Delete, Select
from twext.enterprise.jobqueue import WorkItem, PeerConnectionPool
from twisted.internet.defer import inlineCallbacks, returnValue
-from txdav.common.datastore.sql_tables import schema
+from twistedcaldav.ical import normalize_iCalStr
from txdav.caldav.datastore.sql import CalendarStoreFeatures
+from txdav.common.datastore.sql_tables import schema
import datetime
import hashlib
@@ -177,28 +178,15 @@
# get db object
calendarObject = (yield CalendarStoreFeatures(self.transaction._store).calendarObjectWithID(self.transaction, self.resourceID))
- component = yield calendarObject.componentForUser()
+ oldComponent = yield calendarObject.componentForUser()
# Change a copy of the original, as we need the original cached on the resource
# so we can do a diff to test implicit scheduling changes
- component = component.duplicate()
+ component = oldComponent.duplicate()
- # TODO: Check performance because:
- # 1) if the component is changed then expandGroupAttendee() will be called again to validate
- # 2) The group and members are in the group cache so could use them here
-
- # get group record and members
- groupUID, _ignore_name, _ignore_membershipHash = yield self.transaction.groupByID(self.groupID)
- groupRecord = yield self.transaction.directoryService().recordWithUID(groupUID)
- members = yield groupRecord.expandedMembers() if groupRecord else set()
-
- # expand
- changed = yield component.expandGroupAttendee(
- groupRecord.canonicalCalendarUserAddress(),
- set([member.canonicalCalendarUserAddress() for member in members]),
- self.transaction.directoryService().recordWithCalendarUserAddress
- )
- if changed:
+ # sync group attendees
+ calendarObject.reconcileGroupAttendees(component)
+ if normalize_iCalStr(oldComponent, sort=True) != normalize_iCalStr(component, sort=True):
yield calendarObject.setComponent(component)
@@ -319,9 +307,10 @@
Does the work of a per-group refresh work item
Faults in the flattened membership of a group, as UIDs
and updates the GROUP_MEMBERSHIP table
- WorkProposals are returned for tests
+ WorkProposal is returned for tests
"""
self.log.debug("Faulting in group: {g}", g=groupUID)
+ wp = None
record = (yield self.directory.recordWithUID(groupUID))
if record is None:
# FIXME: the group has disappeared from the directory.
@@ -356,9 +345,9 @@
newMemberUIDs.add(member.uid)
yield self.synchronizeMembers(txn, groupID, newMemberUIDs)
- wps = yield self.scheduleEventReconciliations(txn, groupID)
+ wp = yield self.scheduleEventReconciliations(txn, groupID)
- returnValue(wps)
+ returnValue(wp)
@inlineCallbacks
@@ -405,18 +394,18 @@
"""
Find all events who have this groupID as an attendee and create
work items for them.
- returns: WorkProposal list
+ returns: WorkProposal
"""
- groupAttendee = schema.GROUP_ATTENDEE
+ ga = schema.GROUP_ATTENDEE
rows = yield Select(
- [groupAttendee.RESOURCE_ID, ],
- From=groupAttendee,
- Where=groupAttendee.GROUP_ID == groupID,
+ [ga.RESOURCE_ID, ],
+ From=ga,
+ Where=ga.GROUP_ID == groupID,
).on(txn)
- eventIDs = [row[0] for row in rows]
- wps = []
- for eventID in eventIDs:
+ wp = None
+ if rows:
+ eventID = rows[0][0]
notBefore = (
datetime.datetime.utcnow() +
@@ -436,9 +425,8 @@
groupID=groupID,
notBefore=notBefore
)
- wps.append(wp)
- returnValue(tuple(wps))
+ returnValue(wp)
@inlineCallbacks
Modified: CalendarServer/trunk/txdav/who/test/test_group_attendees.py
===================================================================
--- CalendarServer/trunk/txdav/who/test/test_group_attendees.py 2014-04-18 16:32:45 UTC (rev 13322)
+++ CalendarServer/trunk/txdav/who/test/test_group_attendees.py 2014-04-18 18:05:54 UTC (rev 13323)
@@ -138,7 +138,7 @@
cobj1 = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000001")
vcalendar2 = yield cobj1.component()
- self.assertEqual(normalize_iCalStr(vcalendar2), normalize_iCalStr(data_get_1))
+ self.assertEqual(normalize_iCalStr(vcalendar2, sort=True), normalize_iCalStr(data_get_1, sort=True))
yield self._verifyObjectResourceCount("10000000-0000-0000-0000-000000000006", 1)
yield self._verifyObjectResourceCount("10000000-0000-0000-0000-000000000007", 1)
@@ -303,7 +303,7 @@
cobj1 = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000001")
vcalendar2 = yield cobj1.component()
- self.assertEqual(normalize_iCalStr(vcalendar2), normalize_iCalStr(data_get_1))
+ self.assertEqual(normalize_iCalStr(vcalendar2, sort=True), normalize_iCalStr(data_get_1, sort=True))
yield self._verifyObjectResourceCount("10000000-0000-0000-0000-000000000006", 1)
yield self._verifyObjectResourceCount("10000000-0000-0000-0000-000000000007", 1)
@@ -372,7 +372,7 @@
cobj1 = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000001")
vcalendar2 = yield cobj1.component()
- self.assertEqual(normalize_iCalStr(vcalendar2), normalize_iCalStr(data_get_1))
+ self.assertEqual(normalize_iCalStr(vcalendar2, sort=True), normalize_iCalStr(data_get_1, sort=True))
yield self._verifyObjectResourceCount("10000000-0000-0000-0000-000000000006", 1)
yield self._verifyObjectResourceCount("10000000-0000-0000-0000-000000000007", 1)
@@ -447,8 +447,8 @@
self.patch(CalendarDirectoryRecordMixin, "expandedMembers", expandedMembers)
groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
- wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
- self.assertEqual(set(wps), set())
+ wp = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
+ self.assertEqual(wp, None)
calendar = yield self.calendarUnderTest(name="calendar", home="10000000-0000-0000-0000-000000000002")
vcalendar1 = Component.fromString(data_put_1)
@@ -465,11 +465,9 @@
self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
- wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
+ wp = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
yield self.commit()
- self.assertEqual(len(wps), 1)
- for wp in wps:
- yield wp.whenExecuted()
+ yield wp.whenExecuted()
cobj1 = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
vcalendar3 = yield cobj1.component()
@@ -480,11 +478,9 @@
self.patch(CalendarDirectoryRecordMixin, "expandedMembers", expandedMembers)
groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
- wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
+ wp = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
yield self.commit()
- self.assertEqual(len(wps), 1)
- for wp in wps:
- yield wp.whenExecuted()
+ yield wp.whenExecuted()
cobj1 = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
vcalendar3 = yield cobj1.component()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140418/5250d7d4/attachment-0001.html>
More information about the calendarserver-changes
mailing list