[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