[CalendarServer-changes] [13304] CalendarServer/trunk/txdav

source_changes at macosforge.org source_changes at macosforge.org
Wed Apr 16 17:26:27 PDT 2014


Revision: 13304
          http://trac.calendarserver.org//changeset/13304
Author:   gaya at apple.com
Date:     2014-04-16 17:26:27 -0700 (Wed, 16 Apr 2014)
Log Message:
-----------
update group attendees after group change

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-04-16 20:47:22 UTC (rev 13303)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2014-04-17 00:26:27 UTC (rev 13304)
@@ -1915,7 +1915,7 @@
             yield component.normalizeCalendarUserAddresses(normalizationLookup, self.directoryService().recordWithCalendarUserAddress)
 
             # Expand groups
-            yield self.expandGroupAttendees(component)
+            yield self.expandGroupAttendees(component, inserting)
 
             # Valid attendee list size check
             yield self.validAttendeeListSizeCheck(component, inserting)
@@ -1933,12 +1933,12 @@
 
 
     @inlineCallbacks
-    def expandGroupAttendees(self, component):
+    def expandGroupAttendees(self, component, inserting):
         """
         Expand group attendees
         """
         if not config.Scheduling.Options.AllowGroupAsAttendee:
-            return
+            returnValue(None)
 
         attendeeProps = component.getAllAttendeeProperties()
         groupCUAs = set([
@@ -1951,33 +1951,35 @@
             if groupRecord:
                 members = yield groupRecord.expandedMembers()
 
-                # calculate hash
-                memberUIDs = sorted([member.uid for member in members])
-                membershipHashContent = hashlib.md5()
-                for memberUID in memberUIDs:
-                    membershipHashContent.update(memberUID)
-                membershipHash = membershipHashContent.hexdigest()
-
-                # 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,
-                    })
-                except AllRetriesFailed:
-                    pass
-
-                # get members
+                # add group attendees
                 yield component.expandGroupAttendee(
                     groupRecord.canonicalCalendarUserAddress(),
                     set([member.canonicalCalendarUserAddress() for member in members]),
                     self.directoryService().recordWithCalendarUserAddress
                 )
 
+                # 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()
 
+                    # 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
+
+
     def validCalendarDataCheck(self, component, inserting):
         """
         Check that the calendar data is valid iCalendar.
@@ -2632,6 +2634,10 @@
 
         yield self.updateDatabase(component, inserting=inserting)
 
+        # add GROUP_ATTENNDEE rows using just created _resourceID
+        if inserting:
+            yield self.expandGroupAttendees(component, False)
+
         # Post process managed attachments
         if internal_state in (
             ComponentUpdateState.NORMAL,

Modified: CalendarServer/trunk/txdav/who/groups.py
===================================================================
--- CalendarServer/trunk/txdav/who/groups.py	2014-04-16 20:47:22 UTC (rev 13303)
+++ CalendarServer/trunk/txdav/who/groups.py	2014-04-17 00:26:27 UTC (rev 13304)
@@ -24,6 +24,7 @@
 from twext.enterprise.jobqueue import WorkItem, PeerConnectionPool
 from twisted.internet.defer import inlineCallbacks, returnValue
 from txdav.common.datastore.sql_tables import schema
+from txdav.caldav.datastore.sql import Calendar
 import datetime
 import hashlib
 
@@ -160,7 +161,7 @@
 ):
 
     group = property(
-        lambda self: "{0}, {1}".format(self.groupID, self.eventID)
+        lambda self: "{0}, {1}".format(self.groupID, self.resourceID)
     )
 
     @inlineCallbacks
@@ -169,39 +170,46 @@
         # Delete all other work items for this group
         yield Delete(
             From=self.table,
-            Where=((self.table.GROUP_ID == self.self.groupID).And(
-                self.table.RESOURCE_ID == self.self.eventID)
+            Where=((self.table.GROUP_ID == self.groupID).And(
+                self.table.RESOURCE_ID == self.resourceID)
             )
         ).on(self.transaction)
 
-        # get calendar Object
-        calObject = schema.CALENDAR_OBJECT
+        # get calendar id
+        co = schema.CALENDAR_OBJECT
         rows = yield Select(
-                [calObject.CALENDAR_RESOURCE_ID, ],
-                From=calObject,
-                Where=calObject.RESOURCE_ID == self.eventID,
+                [co.CALENDAR_RESOURCE_ID, ],
+                From=co,
+                Where=co.RESOURCE_ID == self.resourceID,
         ).on(self.transaction)
+        calendarID = rows[0][0]
 
-        calendarID = rows[0][0]
-        calendarHome = (yield self.Calendar._ownerHomeWithResourceID.on(
+        # get home id
+        calendarHomeID = (yield Calendar._ownerHomeWithResourceID.on(
             self.transaction, resourceID=calendarID)
         )[0][0]
 
+        # get db objects
+        calendarHome = yield self.transaction.calendarHomeWithResourceID(calendarHomeID)
         calendar = yield calendarHome.childWithID(calendarID)
-        calendarObject = yield calendar.objectResourceWithID(self.eventID)
+        calendarObject = yield calendar.objectResourceWithID(self.resourceID)
+        component = yield calendarObject.component()
 
-        # get group individual UIDs
-        groupMemember = schema.GROUP_MEMBERSHIP
-        rows = yield Select(
-                [groupMemember.MEMBER_GUID, ],
-                From=groupMemember,
-                Where=groupMemember.GROUP_ID == self.groupID,
-        ).on(self.transaction)
-        memberGUIDs = [row[0] for row in rows]
+        # 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
 
-        component = yield calendarObject.component()
-        changed = component.expandGroupAttendee(self.groupGUID, memberGUIDs, self.directoryService().recordWithCalendarUserAddress)
+        # 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:
             yield calendarObject.setComponent(component)
 
@@ -319,9 +327,12 @@
 
     @inlineCallbacks
     def refreshGroup(self, txn, groupUID):
-        # 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
+        """
+            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
+        """
         self.log.debug("Faulting in group: {g}", g=groupUID)
         record = (yield self.directory.recordWithUID(groupUID))
         if record is None:
@@ -357,9 +368,11 @@
                     newMemberUIDs.add(member.uid)
                 yield self.synchronizeMembers(txn, groupID, newMemberUIDs)
 
-            yield self.scheduleEventReconciliations(txn, groupID, groupUID)
+            wps = yield self.scheduleEventReconciliations(txn, groupID)
 
+        returnValue(wps)
 
+
     @inlineCallbacks
     def synchronizeMembers(self, txn, groupID, newMemberUIDs):
         numRemoved = numAdded = 0
@@ -400,10 +413,11 @@
 
 
     @inlineCallbacks
-    def scheduleEventReconciliations(self, txn, groupID, groupUID):
+    def scheduleEventReconciliations(self, txn, groupID):
         """
         Find all events who have this groupID as an attendee and create
         work items for them.
+        returns: WorkProposal list
         """
         groupAttendee = schema.GROUP_ATTENDEE
         rows = yield Select(
@@ -413,6 +427,7 @@
         ).on(txn)
         eventIDs = [row[0] for row in rows]
 
+        wps = []
         for eventID in eventIDs:
 
             notBefore = (
@@ -421,21 +436,23 @@
             )
             log.debug(
                 "scheduling group reconciliation for "
-                "({eventID}, {groupID}, {groupUID}): {when}",
-                eventID=eventID,
+                "({resourceID}, {groupID},): {when}",
+                resourceID=eventID,
                 groupID=groupID,
-                groupUID=groupUID,
-                when=notBefore)
+                when=notBefore
+            )
 
-            yield txn.enqueue(
+            wp = yield txn.enqueue(
                 GroupAttendeeReconciliationWork,
-                eventID=eventID,
+                resourceID=eventID,
                 groupID=groupID,
-                groupUID=groupUID,
                 notBefore=notBefore
             )
+            wps.append(wp)
 
+        returnValue(tuple(wps))
 
+
     @inlineCallbacks
     def groupsToRefresh(self, txn):
         delegatedUIDs = set((yield txn.allGroupDelegates()))

Modified: CalendarServer/trunk/txdav/who/test/test_group_attendees.py
===================================================================
--- CalendarServer/trunk/txdav/who/test/test_group_attendees.py	2014-04-16 20:47:22 UTC (rev 13303)
+++ CalendarServer/trunk/txdav/who/test/test_group_attendees.py	2014-04-17 00:26:27 UTC (rev 13304)
@@ -19,11 +19,13 @@
 """
 
 from twext.who.test.test_xml import xmlService
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.trial import unittest
 from twistedcaldav.config import config
 from twistedcaldav.ical import Component, normalize_iCalStr
 from txdav.caldav.datastore.test.util import buildCalendarStore, populateCalendarsFrom, CommonCommonTests
+from txdav.who.directory import CalendarDirectoryRecordMixin
+from txdav.who.groups import GroupCacher
 from txdav.who.util import directoryFromConfig
 import os
 
@@ -71,6 +73,9 @@
         "10000000-0000-0000-0000-000000000001" : {
             "calendar" : {}
         },
+        "10000000-0000-0000-0000-000000000002" : {
+            "calendar" : {}
+        },
     }
 
     @inlineCallbacks
@@ -337,3 +342,115 @@
         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))
+
+
+    @inlineCallbacks
+    def test_groupChange(self):
+        """
+        Test that group attendee is expanded on PUT
+        """
+        data_put_1 = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART;TZID=US/Eastern:20140101T100000
+DURATION:PT1H
+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;TZID=US/Eastern:20140101T100000
+DURATION:PT1H
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:uuid:10000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 02;CUTYPE=GROUP;EMAIL=group02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:uuid:20000000-0000-0000-0000-000000000002
+CREATED:20060101T150000Z
+ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:uuid:10000000-0000-0000-0000-000000000001
+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
+DTSTART;TZID=US/Eastern:20140101T100000
+DURATION:PT1H
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:10000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:uuid:20000000-0000-0000-0000-000000000001
+CREATED:20060101T150000Z
+ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:uuid:10000000-0000-0000-0000-000000000002
+SUMMARY:event 1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        data_get_3 = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+BEGIN:VEVENT
+UID:event1 at ninevah.local
+DTSTART;TZID=US/Eastern:20140101T100000
+DURATION:PT1H
+ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:uuid:10000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:uuid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=User 01;EMAIL=user01 at example.com;MEMBER="urn:uuid:20000000-0000-0000-0000-000000000001";PARTSTAT=NEEDS-ACTION:urn:uuid:10000000-0000-0000-0000-000000000001
+CREATED:20060101T150000Z
+ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:uuid:10000000-0000-0000-0000-000000000002
+SUMMARY:event 1
+END:VEVENT
+END:VCALENDAR
+"""
+
+        #TODO: should User 01 have SCHEDULE-STATUS=3.7 ?
+
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None):
+            yield None
+            returnValue(set())
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+        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())
+
+        calendar = yield self.calendarUnderTest(name="calendar", home="10000000-0000-0000-0000-000000000002")
+        vcalendar1 = Component.fromString(data_put_1)
+        yield calendar.createCalendarObjectWithName("data1.ics", vcalendar1)
+        yield self.commit()
+
+        cobj1 = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
+        vcalendar2 = yield cobj1.component()
+        self.assertEqual(normalize_iCalStr(vcalendar2), normalize_iCalStr(data_get_2))
+
+        self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
+
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        wps = 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()
+
+        cobj1 = yield self.calendarObjectUnderTest(name="data1.ics", calendar_name="calendar", home="10000000-0000-0000-0000-000000000002")
+        vcalendar3 = yield cobj1.component()
+        self.assertEqual(normalize_iCalStr(vcalendar3), normalize_iCalStr(data_get_3))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140416/1e454591/attachment-0001.html>


More information about the calendarserver-changes mailing list