[CalendarServer-changes] [11679] CalendarServer/branches/users/gaya/sharedgroupfixes/txdav

source_changes at macosforge.org source_changes at macosforge.org
Thu Sep 12 14:24:21 PDT 2013


Revision: 11679
          http://trac.calendarserver.org//changeset/11679
Author:   gaya at apple.com
Date:     2013-09-12 14:24:21 -0700 (Thu, 12 Sep 2013)
Log Message:
-----------
checkpoint

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py
    CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql.py
    CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql

Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py	2013-09-12 21:17:25 UTC (rev 11678)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py	2013-09-12 21:24:21 UTC (rev 11679)
@@ -453,7 +453,88 @@
         return False
 
 
+    @classproperty
+    def _deleteBumpTokenQuery(cls): #@NoSelf
+        rev = cls._revisionsSchema
+        return Update({rev.REVISION: schema.REVISION_SEQ,
+                       rev.DELETED: True},
+                      Where=(rev.RESOURCE_ID == Parameter("resourceID")).And(
+                           rev.RESOURCE_NAME == Parameter("name")).And(
+                           rev.OBJECT_RESOURCE_ID == Parameter("id")),
+                      Return=rev.REVISION)
+
+
+    @classproperty
+    def _unshareBumpTokenQuery(cls): #@NoSelf
+        rev = cls._revisionsSchema
+        return Update({rev.REVISION: schema.REVISION_SEQ,
+                       rev.OBJECT_RESOURCE_ID: Parameter("id"),
+                       rev.UNSHARED: True,
+                       },
+                      Where=(rev.RESOURCE_ID == Parameter("resourceID")).And(
+                           rev.RESOURCE_NAME == Parameter("name")),
+                      Return=rev.REVISION)
+
+
     @inlineCallbacks
+    def _changeRevision(self, action, name, id=0):
+
+        # Need to handle the case where for some reason the revision entry is
+        # actually missing. For a "delete" we don't care, for an "update" we
+        # will turn it into an "insert".
+        if action == "delete":
+            rows = (
+                yield self._deleteBumpTokenQuery.on(
+                    self._txn, resourceID=self._resourceID, name=name, id=id))
+            if rows:
+                self._syncTokenRevision = rows[0][0]
+        elif action == "unshare":
+            rows = (
+                yield self._unshareBumpTokenQuery.on(
+                    self._txn, resourceID=self._resourceID, name=name, id=id))
+            if rows:
+                self._syncTokenRevision = rows[0][0]
+        elif action == "update":
+            rows = (
+                yield self._updateBumpTokenQuery.on(
+                    self._txn, resourceID=self._resourceID, name=name))
+            if rows:
+                self._syncTokenRevision = rows[0][0]
+            else:
+                action = "insert"
+
+        if action == "insert":
+            # Note that an "insert" may happen for a resource that previously
+            # existed and then was deleted. In that case an entry in the
+            # REVISIONS table still exists so we have to detect that and do db
+            # INSERT or UPDATE as appropriate
+
+            found = bool((
+                yield self._insertFindPreviouslyNamedQuery.on(
+                    self._txn, resourceID=self._resourceID, name=name)))
+            if found:
+                self._syncTokenRevision = (
+                    yield self._updatePreviouslyNamedQuery.on(
+                        self._txn, resourceID=self._resourceID, name=name)
+                )[0][0]
+            else:
+                self._syncTokenRevision = (
+                    yield self._completelyNewRevisionQuery.on(
+                        self._txn, homeID=self.ownerHome()._resourceID,
+                        resourceID=self._resourceID, name=name)
+                )[0][0]
+        self._maybeNotify()
+
+
+    def _deleteRevision(self, name, id):
+        return self._changeRevision("delete", name, id)
+
+
+    def _unshareRevision(self, name, id):
+        return self._changeRevision("unshare", name, id)
+
+
+    @inlineCallbacks
     def resourceNamesSinceRevision(self, revision):
         """
         Return the changed and deleted resources since a particular revision. This implementation takes
@@ -495,6 +576,11 @@
         record a revision for the sharee home and sharee collection name with the "deleted" flag set. That way
         the shared collection can be reported as removed.
 
+        For shared groups.  Find the items that have be added and removed since revision in the aboMembers
+        tables.  Then add in changes from the revision table.
+
+        TODO: Cover the case where the sharing changes. Thinking now that I need a unshared group tombstone.
+
         @param revision: the sync revision to compare to
         @type revision: C{str}
         @param depth: depth for determine what changed
@@ -507,147 +593,92 @@
         if self.fullyShared():
             returnValue((yield super(AddressBook, self).sharedChildResourceNamesSinceRevision(revision, depth)))
 
+        path = self.name()
         changed = set()
         deleted = set()
 
         groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
                 self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
         )
-        acceptedGroupIDs = [groupBindRow[2] for groupBindRow in groupBindRows]
+        acceptedGroupIDs = set([groupBindRow[2] for groupBindRow in groupBindRows])
         minRevision = min([groupBindRow[5] for groupBindRow in groupBindRows])
         sharerevision = 0 if revision < minRevision else revision
-        print("sharedChildResourceNamesSinceRevision:%s minRevision:%s, sharerevision:%s" % (self, minRevision, sharerevision))
+        print("sharedChildResourceNamesSinceRevision:%s acceptedGroupIDs:%s, minRevision:%s, sharerevision:%s" % (self, acceptedGroupIDs, minRevision, sharerevision))
 
         # get revision table changes
         rev = self._revisionsSchema
         results = [(
                 name,
+                id,
                 wasdeleted,
-            ) for name, wasdeleted in (
-                yield Select([rev.RESOURCE_NAME, rev.DELETED],
+                wasunshared,
+            ) for name, id, wasdeleted, wasunshared in (
+                yield Select([rev.RESOURCE_NAME, rev.OBJECT_RESOURCE_ID, rev.DELETED, rev.UNSHARED],
                              From=rev,
                             Where=(rev.REVISION > sharerevision).And(
                             rev.RESOURCE_ID == self._resourceID)).on(self._txn)
             ) if name
         ]
         print("sharedChildResourceNamesSinceRevision:%s results:%s" % (self, results))
+        allowedObjectIDs = set((yield self.expandGroupIDs(self._txn, acceptedGroupIDs)))
+        print("sharedChildResourceNamesSinceRevision:%s allowedObjectIDs=%s," % (self, allowedObjectIDs,))
 
-        # get members table changes
-        aboMembers = schema.ABO_MEMBERS
-        memberRows = yield Select(
-            [aboMembers.GROUP_ID, aboMembers.MEMBER_ID, aboMembers.REMOVED],
-            From=aboMembers,
-            Where=(aboMembers.REVISION > sharerevision).And(
-                aboMembers.ADDRESSBOOK_ID == self._resourceID)
-        ).on(self._txn)
-        groupIDToChangedMemberIDMap = {}
-        for groupID, memberID, removed in memberRows:
-            if groupID not in groupIDToChangedMemberIDMap:
-                groupIDToChangedMemberIDMap[groupID] = set()
-            groupIDToChangedMemberIDMap[groupID].add(memberID)
-        print("sharedChildResourceNamesSinceRevision:%s memberRows:%s groupIDToChangedMemberIDMap=%s" % (self, memberRows, groupIDToChangedMemberIDMap,))
+        # get unshared groups from revisions
+        unsharedGroupIDs = set([id for name, id, wasdeleted, wasunshared in results if wasunshared])
+        print("sharedChildResourceNamesSinceRevision:%s allowedObjectIDs=%s," % (self, unsharedGroupIDs,))
 
-        allowedObjectIDs = None
-        @inlineCallbacks
-        def allowedObjectIDs(acceptedGroupIDs):
-            if not hasattr(self, "__allowedObjectIDs"):
-                self.__allowedObjectIDs = yield self.expandGroupIDs(self._txn, acceptedGroupIDs)
-            returnValue(self.__allowedObjectIDs)
+        oldAllowedObjectIDs = set((yield self.expandGroupIDs(self._txn, acceptedGroupIDs | unsharedGroupIDs, revision)))
+        print("sharedChildResourceNamesSinceRevision:%s oldAllowedObjectIDs=%s," % (self, oldAllowedObjectIDs,))
 
-        @inlineCallbacks
-        def getMissingNames(ids, idToNameMap):
-            idsForMissingNames = list(set(ids) - set(idToNameMap.keys()))
-            if idsForMissingNames:
-                abo = schema.ADDRESSBOOK_OBJECT
-                memberIDNameRows = (
-                    yield AddressBookObject._columnsWithResourceIDsQuery(
-                        [abo.RESOURCE_ID, abo.RESOURCE_NAME],
-                        idsForMissingNames
-                    ).on(self._txn, resourceIDs=idsForMissingNames)
-                )
-                idToNameMap = dict(dict(idToNameMap), **dict(memberIDNameRows))
+        addedObjectIds = allowedObjectIDs - oldAllowedObjectIDs
+        removedObjectIds = oldAllowedObjectIDs - allowedObjectIDs
+        print("sharedChildResourceNamesSinceRevision:%s addedObjectIds=%s," % (self, addedObjectIds,))
+        print("sharedChildResourceNamesSinceRevision:%s removedObjectIds=%s," % (self, removedObjectIds,))
 
-            returnValue(idToNameMap)
+        idToNameMap = dict([(name, id) for name, id, wasdeleted, wasunshared in results if id != 0])
+        missingNameIds = (allowedObjectIDs | oldAllowedObjectIDs) - set(idToNameMap.keys())
+        if missingNameIds:
+            abo = schema.ADDRESSBOOK_OBJECT
+            memberIDNameRows = (
+                yield AddressBookObject._columnsWithResourceIDsQuery(
+                    [abo.RESOURCE_ID, abo.RESOURCE_NAME],
+                    missingNameIds
+                ).on(self._txn, resourceIDs=missingNameIds)
+            )
+            idToNameMap = dict(dict(idToNameMap), **dict(memberIDNameRows))
 
-        allIDs = set()
-        for groupID, memberID, removed in memberRows:
-            allIDs.add(groupID)
-            allIDs.add(memberID)
-
-        idToNameMap = yield getMissingNames(allIDs, {})
         print("sharedChildResourceNamesSinceRevision:%s idToNameMap=%s" % (self, idToNameMap,))
+        #nameToIDMap = dict([(v, k) for k, v in idToNameMap.iteritems()])
 
-        path = self.name()
-        removedMemberIDs = set()
-        changedMemberIDs = set()
+        # for changes, get object names all at once here
+        if sharerevision:
+            if depth == "1":
+                if removedObjectIds:
+                    changed.add("%s/" % (path,))
+            else:
+                for removedObjectId in removedObjectIds:
+                    deleted.add("%s/%s" % (path, idToNameMap[removedObjectId],))
 
-        # TODO: use for query
-        allowedObjectIDs = yield self.expandGroupIDs(self._txn, acceptedGroupIDs)
-        print("sharedChildResourceNamesSinceRevision:%s acceptedGroupIDs:%s allowedObjectIDs=%s," % (self, acceptedGroupIDs, allowedObjectIDs,))
-
-        for groupID, memberID, removed in memberRows:
-
-            if memberID not in allowedObjectIDs or groupID not in allowedObjectIDs:
-                print("sharedChildResourceNamesSinceRevision:%s SKIP groupID:%s memberID=%s, wasdeleted=%s" % (self, groupID, memberID, wasdeleted,))
-                continue
-            print("sharedChildResourceNamesSinceRevision:%s groupID:%s memberID=%s, wasdeleted=%s" % (self, groupID, memberID, wasdeleted,))
-
-            if removed:
-                if sharerevision:
-                    if depth == "1":
-                        changed.add("%s/" % (path,))
-                    else:
-                        removedMemberIDs.add(memberID)
-
-            # Always report collection as changed
-            changed.add("%s/" % (path,))
-
-            # Resource changed - for depth "infinity" report resource as changed
             if depth != "1":
-                changedMemberIDs.add(memberID)
-                changedMemberIDs.add(groupID)
+                for addedObjectID in addedObjectIds:
+                    changed.add("%s/%s" % (path, idToNameMap[addedObjectID],))
 
-            print("sharedChildResourceNamesSinceRevision:%s changed:%s deleted=%s, removedMemberIDs=%s, changedMemberIDs=%s" % (self, changed, deleted, removedMemberIDs, changedMemberIDs))
+        for name, id, wasdeleted, wasunshared in results:
+            if name in idToNameMap.values():
+                # Always report collection as changed
+                changed.add("%s/" % (path,))
 
-        '''
-        nameToIDMap = dict([(v, k) for k, v in idToNameMap.iteritems()])
+                # Resource changed - for depth "infinity" report resource as changed
+                if depth != "1":
+                    item = "%s/%s" % (path, name,)
+                    if item not in deleted:
+                        changed.add("%s/%s" % (path, name,))
 
-        for name, wasdeleted in results:
+        returnValue((changed, deleted))
 
-            id = nameToIDMap[name]
-            if id not in allowedObjectIDs:
-                print("sharedChildResourceNamesSinceRevision:%s SKIP name:%s id=%s, wasdeleted=%s" % (self, name, id, wasdeleted,))
-                continue
 
-            print("sharedChildResourceNamesSinceRevision:%s name:%s id=%s, wasdeleted=%s" % (self, name, id, wasdeleted,))
-            if wasdeleted:
-                if sharerevision:
-                    if depth == "1":
-                        changed.add("%s/" % (path,))
-                    else:
-                        deleted.add("%s/%s" % (path, name,))
 
-            # Always report collection as changed
-            changed.add("%s/" % (path,))
 
-            # Resource changed - for depth "infinity" report resource as changed
-            if depth != "1":
-                changed.add("%s/%s" % (path, name,))
-
-            print("sharedChildResourceNamesSinceRevision:%s changed:%s deleted=%s, removedMemberIDs=%s, changedMemberIDs=%s" % (self, changed, deleted, removedMemberIDs, changedMemberIDs))
-        '''
-
-        # for changes, get object names all at once here
-        idToNameMap = yield getMissingNames(removedMemberIDs | changedMemberIDs, idToNameMap)
-        for removedMemberID in removedMemberIDs:
-            deleted.add("%s/%s" % (path, idToNameMap[removedMemberID],))
-
-        for changedMemberID in changedMemberIDs:
-            changed.add("%s/%s" % (path, idToNameMap[changedMemberID],))
-
-        returnValue((changed, deleted))
-
-
     @inlineCallbacks
     def _loadPropertyStore(self, props=None):
         if props is None:
@@ -693,7 +724,7 @@
     @inlineCallbacks
     def removedObjectResource(self, child):
         """
-            just like CommonHomeChild.removedObjectResource() but does not call self._deleteRevision(child.name())
+            just like CommonHomeChild.removedObjectResource() but does not call self._deleteRevision()
         """
         self._objects.pop(child.name(), None)
         self._objects.pop(child.uid(), None)
@@ -1177,15 +1208,28 @@
         DAL query to load all object resource names for a home child.
         """
         aboMembers = schema.ABO_MEMBERS
-        return Select([aboMembers.MEMBER_ID], From=aboMembers,
+        return Select([aboMembers.MEMBER_ID, aboMembers.REMOVED, Max(aboMembers.REVISION)], From=aboMembers,
+                      Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs))),
+                      GroupBy=(aboMembers.MEMBER_ID, aboMembers.REMOVED)
+                     )
+
+
+    @classmethod
+    def _memberIDsWithGroupIDsAndRevisionQuery(cls, groupIDs):
+        """
+        DAL query to load all object resource names for a home child.
+        """
+        aboMembers = schema.ABO_MEMBERS
+        return Select([aboMembers.MEMBER_ID, aboMembers.REMOVED, Max(aboMembers.REVISION)], From=aboMembers,
                       Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))
-                            .And(aboMembers.REMOVED == False),
-                      )
+                            .And(aboMembers.REVISION <= Parameter("revision")),
+                      GroupBy=(aboMembers.MEMBER_ID, aboMembers.REMOVED)
+                     )
 
 
     @classmethod
     @inlineCallbacks
-    def expandGroupIDs(cls, txn, groupIDs, includeGroupIDs=True):
+    def expandGroupIDs(cls, txn, groupIDs, atRevision=0, includeGroupIDs=True):
         """
         Get all AddressBookObject resource IDs contains in the given shared groups with the given groupIDs
         """
@@ -1193,10 +1237,15 @@
         examinedIDs = set()
         remainingIDs = set(groupIDs)
         while remainingIDs:
-            memberRows = yield cls._memberIDsWithGroupIDsQuery(remainingIDs).on(
-                txn, groupIDs=remainingIDs
-            )
-            objectIDs |= set(memberRow[0] for memberRow in memberRows)
+            if atRevision == 0:
+                memberRows = yield cls._memberIDsWithGroupIDsQuery(remainingIDs).on(
+                    txn, groupIDs=remainingIDs
+                )
+            else:
+                memberRows = yield cls._memberIDsWithGroupIDsAndRevisionQuery(remainingIDs).on(
+                    txn, groupIDs=remainingIDs, revision=atRevision
+                )
+            objectIDs |= set(memberRow[0] for memberRow in memberRows if not memberRow[1]) # not removed
             examinedIDs |= remainingIDs
             remainingIDs = objectIDs - examinedIDs
 
@@ -1433,19 +1482,19 @@
 
         @return: a L{Deferred} which will fire with the previous shareUID
         """
-        sharedAddressBook = yield shareeHome.addressbookWithName(self.shareeName())
-        if sharedAddressBook:
+        shareeAddressBook = yield shareeHome.addressbookWithName(self.shareeName())
+        if shareeAddressBook:
 
-            acceptedBindCount = 1 if sharedAddressBook.fullyShared() else 0
+            acceptedBindCount = 1 if shareeAddressBook.fullyShared() else 0
             acceptedBindCount += len((yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
-                    self._txn, homeID=shareeHome._resourceID, addressbookID=sharedAddressBook._resourceID
+                    self._txn, homeID=shareeHome._resourceID, addressbookID=shareeAddressBook._resourceID
             )))
             if acceptedBindCount == 1:
-                yield sharedAddressBook._deletedSyncToken(sharedRemoval=True)
+                yield shareeAddressBook._deletedSyncToken(sharedRemoval=True)
                 shareeHome._children.pop(self.shareeName(), None)
-                shareeHome._children.pop(sharedAddressBook._resourceID, None)
-            elif not sharedAddressBook.fullyShared():
-                # FIXME: remove objects for this group only using self.removeObjectResource
+                shareeHome._children.pop(shareeAddressBook._resourceID, None)
+            elif not shareeAddressBook.fullyShared():
+                # FIXME: remove objects for this group only using self.removeObjectResource()
                 self._objectNames = None
 
             # Must send notification to ensure cache invalidation occurs
@@ -1540,55 +1589,24 @@
             if self._resourceID not in readWriteObjectIDs:
                 raise HTTPError(FORBIDDEN)
 
-            '''
-            # get sync token for delete now
-            yield self.addressbook()._deleteRevision(self.name())
-
-            # remove group memberships that make this UID visible
-            abo = schema.ADDRESSBOOK_OBJECT
-            groupIDRows = (
-                yield Select([abo.RESOURCE_ID],
-                    From=abo,
-                    Where=(abo.KIND == _ABO_KIND_GROUP)
-                        .And(abo.RESOURCE_ID.In(Parameter("readWriteObjectIDs", len(readWriteObjectIDs)))),
-                ).on(self._txn, readWriteObjectIDs=readWriteObjectIDs)
-            )
-            groupIDs = [groupIDRow[0] for groupIDRow in groupIDRows]
-            if groupIDs:
-                print("remove:%s _removeMemberIDFromGroupIDsQuery:groupIDs=%s memberID=%s" % (self, groupIDs, self._resourceID,))
-                yield self._removeMemberIDFromGroupIDsQuery(groupIDs).on(self._txn,
-                    groupIDs=groupIDs,
-                    addressbookID=self._ownerAddressBookResourceID,
-                    memberID=self._resourceID,
-                    revision=self._syncTokenRevision,
-               )
-
-        else:
-            # get sync token for delete now
-            yield self.addressbook()._deleteRevision(self.name())
-            '''
-
         # get sync token for delete now
-        yield self.addressbook()._deleteRevision(self.name())
+        yield self.addressbook()._deleteRevision(self.name(), self._resourceID)
 
         aboMembers = schema.ABO_MEMBERS
-        groupIDRevisions = yield Select(
-            [aboMembers.GROUP_ID, aboMembers.REVISION],
+        groupIDRows = yield Select(
+            [aboMembers.GROUP_ID],
             From=aboMembers,
             Where=(aboMembers.MEMBER_ID == self._resourceID)
                 .And(aboMembers.REMOVED == False),
         ).on(self._txn)
+        groupIDs = [groupIDRow[0] for groupIDRow in groupIDRows]
 
-        groupIDsToRemoveFrom = (
-            [groupIDRow[0] for groupIDRow in groupIDRevisions]
-            if self.owned() or self.addressbook().fullyShared()
-            else readWriteObjectIDs
-        )
+        groupIDsToRemoveFrom = groupIDs if self.owned() or self.addressbook().fullyShared() else readWriteObjectIDs
 
         if groupIDsToRemoveFrom:
             # remove memberships
-            print("remove:%s _removeMemberIDFromGroupIDsQuery:revision=%s groupIDs=%s memberID=%s" % (self, self._ownerAddressBookResourceID, groupIDsToRemoveFrom, self._resourceID,))
-            yield self._removeMemberIDFromGroupIDsQuery(groupIDsToRemoveFrom).on(self._txn,
+            print("remove:%s _removeMemberIDFromGroupsQuery:revision=%s groupIDs=%s memberID=%s" % (self, self._syncTokenRevision, groupIDsToRemoveFrom, self._resourceID,))
+            yield self._removeMemberIDFromGroupsQuery(groupIDsToRemoveFrom).on(self._txn,
                 groupIDs=groupIDsToRemoveFrom,
                 addressbookID=self._ownerAddressBookResourceID,
                 memberID=self._resourceID,
@@ -1598,15 +1616,14 @@
         # add to foreign member table row by UID (aboForeignMembers on address books)
         memberAddress = "urn:uuid:" + self._uid
         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
-        if groupIDRevisions:
-            print("updateDatabase:%s _insertForeignMemberAddrQuery:revision=%s foreignMemberAddrToAdd=%s" % (self, self._syncTokenRevision, groupIDRevisions,))
+        if groupIDs:
+            print("updateDatabase:%s foreignMemberAddrToAdd=%s" % (self, groupIDs,))
 
-        for groupID, revision in groupIDRevisions:#set(groupIDs) - set([self._ownerAddressBookResourceID]):
+        for groupID in groupIDs:#set(groupIDs) - set([self._ownerAddressBookResourceID]):
             yield Insert(
                 {aboForeignMembers.GROUP_ID: groupID,
                  aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                 aboForeignMembers.MEMBER_ADDRESS: memberAddress,
-                 aboForeignMembers.REVISION: revision, }
+                 aboForeignMembers.MEMBER_ADDRESS: memberAddress, }
             ).on(self._txn)
 
         if self.kind() == _ABO_KIND_GROUP:
@@ -1621,8 +1638,8 @@
             memberIDsToRemove = [memberIDRow[0] for memberIDRow in memberIDRows]
 
             if memberIDsToRemove:
-                print("remove:%s _removeMemberIDsFromGroupIDQuery:revision=%s groupID=%s memberIDsToRemove=%s" % (self, self._ownerAddressBookResourceID, self._resourceID, memberIDsToRemove,))
-                yield self._removeMemberIDsFromGroupIDQuery(memberIDsToRemove).on(
+                print("remove:%s _removeMemberIDsFromGroupQuery:revision=%s groupID=%s memberIDsToRemove=%s" % (self, self._ownerAddressBookResourceID, self._resourceID, memberIDsToRemove,))
+                yield self._removeMemberIDsFromGroupQuery(memberIDsToRemove).on(
                     self._txn,
                     groupID=self._resourceID,
                     addressbookID=self._ownerAddressBookResourceID,
@@ -2033,23 +2050,8 @@
 
 
     @classmethod
-    def _addRemovedMemberIDsToGroupIDQuery(cls, memberIDs): #@NoSelf
+    def _removeMemberIDsFromGroupQuery(cls, memberIDs): #@NoSelf
         """
-        DAL statement to mark a member table rows as not removed
-        """
-        aboMembers = schema.ABO_MEMBERS
-        return Update(
-            {aboMembers.REVISION: Parameter("revision"),
-             aboMembers.REMOVED: False, },
-            Where=(aboMembers.GROUP_ID == Parameter("groupID"))
-              .And(aboMembers.ADDRESSBOOK_ID == Parameter("addressbookID"))
-              .And(aboMembers.MEMBER_ID.In(Parameter("memberIDs", len(memberIDs)))),
-       )
-
-
-    @classmethod
-    def _removeMemberIDsFromGroupIDQuery(cls, memberIDs): #@NoSelf
-        """
         DAL statement to mark a member table row to as removed
         """
         aboMembers = schema.ABO_MEMBERS
@@ -2063,7 +2065,7 @@
 
 
     @classmethod
-    def _removeMemberIDFromGroupIDsQuery(cls, groupIDs): #@NoSelf
+    def _removeMemberIDFromGroupsQuery(cls, groupIDs): #@NoSelf
         """
         DAL statement update a group member
         """
@@ -2163,7 +2165,6 @@
 
         abo = schema.ADDRESSBOOK_OBJECT
         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
-        aboMembers = schema.ABO_MEMBERS
 
         if inserting:
             self._resourceID, self._created, self._modified = (
@@ -2182,26 +2183,24 @@
             groupIDRows = yield Delete(
                 aboForeignMembers,
                 Where=aboForeignMembers.MEMBER_ADDRESS == "urn:uuid:" + self._uid,
-                Return=[aboForeignMembers.GROUP_ID, aboForeignMembers.REVISION]
+                Return=aboForeignMembers.GROUP_ID
             ).on(self._txn)
-            groupIDToRevisionMap = dict(groupIDRows)
+            groupIDs = set([groupIDRow[0] for groupIDRow in groupIDRows])
 
-            # add vCard to writable groups
             if not self.owned() and not self.addressbook().fullyShared():
                 readWriteGroupIDs = yield self.addressbook().readWriteGroupIDs()
                 assert readWriteGroupIDs, "no access"
-                for id in readWriteGroupIDs:
-                    groupIDToRevisionMap[id] = self._syncTokenRevision
+                groupIDs |= set(readWriteGroupIDs)
 
             # add to member table rows
-            if groupIDToRevisionMap:
-                print("updateDatabase:%s _insertMemberIDQuery:revision=%s groupIDToRevisionMap=%s memberIDToAdd=%s" % (self, self._syncTokenRevision, groupIDToRevisionMap, self._resourceID,))
-            for groupID, revision in groupIDToRevisionMap.iteritems():
+            if groupIDs:
+                print("updateDatabase:%s _insertMemberIDQuery:revision=%s groupIDToRevisionMap=%s memberIDToAdd=%s" % (self, self._syncTokenRevision, groupIDs, self._resourceID,))
+            for groupID in groupIDs:
                 yield self._insertMemberIDQuery.on(self._txn,
                     groupID=groupID,
                     addressbookID=self._ownerAddressBookResourceID,
                     memberID=self._resourceID,
-                    revision=revision,
+                    revision=self._syncTokenRevision,
                 )
 
         else:
@@ -2219,15 +2218,13 @@
                 memberIDs.append(self._resourceID)
 
             # get current members
-            currentMemberRows = yield Select([aboMembers.MEMBER_ID, aboMembers.REMOVED],
-                 From=aboMembers,
-                 Where=aboMembers.GROUP_ID == self._resourceID,).on(self._txn)
+            currentMemberRows = yield AddressBook._memberIDsWithGroupIDsQuery([self._resourceID]).on(
+                    self._txn, groupIDs=[self._resourceID]
+            )
             currentMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows if not currentMemberRow[1]]
-            removedMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows if currentMemberRow[1]]
 
             memberIDsToRemove = set(currentMemberIDs) - set(memberIDs)
-            memberIDsToAdd = set(memberIDs) - set(currentMemberIDs) - set(removedMemberIDs)
-            memberIDsToReadd = set(memberIDs) & set(removedMemberIDs)
+            memberIDsToAdd = set(memberIDs) - set(currentMemberIDs)
 
             if memberIDsToAdd:
                 print("updateDatabase:%s _insertMemberIDQuery:revision=%s groupID=%s memberIDsToAdd=%s" % (self, self._syncTokenRevision, self._resourceID, memberIDsToAdd,))
@@ -2237,20 +2234,11 @@
                     addressbookID=self._ownerAddressBookResourceID,
                     memberID=memberIDToAdd,
                     revision=self._syncTokenRevision,
-               )
+                )
 
-            if memberIDsToReadd:
-                print("updateDatabase:%s _addRemovedMemberIDsToGroupIDQuery:revision=%s groupID=%s memberIDsToRemove=%s" % (self, self._syncTokenRevision, self._resourceID, memberIDsToReadd,))
-                yield self._addRemovedMemberIDsToGroupIDQuery(memberIDsToReadd).on(self._txn,
-                    groupID=self._resourceID,
-                    addressbookID=self._ownerAddressBookResourceID,
-                    memberIDs=memberIDsToReadd,
-                    revision=self._syncTokenRevision,
-               )
-
             if memberIDsToRemove:
-                print("updateDatabase:%s _removeMemberIDsFromGroupIDQuery:revision=%s groupID=%s memberIDsToRemove=%s" % (self, self._syncTokenRevision, self._resourceID, memberIDsToRemove,))
-                yield self._removeMemberIDsFromGroupIDQuery(memberIDsToRemove).on(
+                print("updateDatabase:%s _removeMemberIDsFromGroupQuery:revision=%s groupID=%s memberIDsToRemove=%s" % (self, self._syncTokenRevision, self._resourceID, memberIDsToRemove,))
+                yield self._removeMemberIDsFromGroupQuery(memberIDsToRemove).on(
                     self._txn,
                     groupID=self._resourceID,
                     addressbookID=self._ownerAddressBookResourceID,
@@ -2282,8 +2270,7 @@
                     yield Insert(
                         {aboForeignMembers.GROUP_ID: self._resourceID,
                          aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                         aboForeignMembers.MEMBER_ADDRESS: foreignMemberAddrToAdd,
-                         aboForeignMembers.REVISION: self._syncTokenRevision, }
+                         aboForeignMembers.MEMBER_ADDRESS: foreignMemberAddrToAdd, }
                     ).on(self._txn)
 
 
@@ -2326,14 +2313,10 @@
 
                     # generate "X-ADDRESSBOOKSERVER-MEMBER" properties
                     # first get member resource ids
-                    aboMembers = schema.ABO_MEMBERS
-                    memberRows = yield Select(
-                        [aboMembers.MEMBER_ID],
-                         From=aboMembers,
-                         Where=(aboMembers.GROUP_ID == self._resourceID)
-                            .And(aboMembers.REMOVED == False),
-                    ).on(self._txn)
-                    memberIDs = [memberRow[0] for memberRow in memberRows]
+                    memberRows = yield AddressBook._memberIDsWithGroupIDsQuery([self._resourceID]).on(
+                            self._txn, groupIDs=[self._resourceID]
+                    )
+                    memberIDs = [memberRow[0] for memberRow in memberRows if not memberRow[1]]
 
                     # then get member UIDs
                     abo = schema.ADDRESSBOOK_OBJECT
@@ -2522,21 +2505,23 @@
 
         @return: a L{Deferred} which will fire with the previously-used name.
         """
-        sharedAddressBook = yield shareeHome.addressbookWithName(self.addressbook().shareeName())
+        shareeAddressBook = yield shareeHome.addressbookWithName(self.addressbook().shareeName())
 
-        if sharedAddressBook:
+        if shareeAddressBook:
 
-            acceptedBindCount = 1 if sharedAddressBook.fullyShared() else 0
+            acceptedBindCount = 1 if shareeAddressBook.fullyShared() else 0
             acceptedBindCount += len((
                 yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
-                    self._txn, homeID=shareeHome._resourceID, addressbookID=sharedAddressBook._resourceID
+                    self._txn, homeID=shareeHome._resourceID, addressbookID=shareeAddressBook._resourceID
                 )
             ))
 
             if acceptedBindCount == 1:
-                yield sharedAddressBook._deletedSyncToken(sharedRemoval=True)
+                yield shareeAddressBook._deletedSyncToken(sharedRemoval=True)
                 shareeHome._children.pop(self.addressbook().shareeName(), None)
                 shareeHome._children.pop(self.addressbook()._resourceID, None)
+            else:
+                yield shareeAddressBook._unshareRevision(self.name(), self._resourceID)
 
             # Must send notification to ensure cache invalidation occurs
             yield self.notifyChanged()
@@ -2635,8 +2620,9 @@
 
 
     @inlineCallbacks
-    def syncToken(self):
-        returnValue((yield self.addressbook().syncToken()))
+    def _initBindRevision(self):
+        yield self.addressbook().syncToken() # init self.addressbook()._syncTokenRevision
+        yield super(AddressBookObject, self)._initBindRevision()
 
 
     @inlineCallbacks

Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql.py	2013-09-12 21:17:25 UTC (rev 11678)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql.py	2013-09-12 21:24:21 UTC (rev 11679)
@@ -3101,7 +3101,6 @@
 
     @inlineCallbacks
     def _initBindRevision(self):
-        yield self.syncToken()
         self._bindRevision = self._syncTokenRevision
 
         bind = self._bindSchema

Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql	2013-09-12 21:17:25 UTC (rev 11678)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql	2013-09-12 21:24:21 UTC (rev 11679)
@@ -465,7 +465,7 @@
   	REVISION              integer      default nextval('REVISION_SEQ') not null,
   	REMOVED               boolean      default false not null,
 
-    primary key (GROUP_ID, MEMBER_ID) -- implicit index
+    primary key (GROUP_ID, MEMBER_ID, REVISION) -- implicit index
 );
 
 create index ABO_MEMBERS_ADDRESSBOOK_ID on
@@ -481,7 +481,6 @@
     GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') RESOURCE_ID
  	ADDRESSBOOK_ID		  integer      not null references ADDRESSBOOK_HOME on delete cascade,
     MEMBER_ADDRESS  	  varchar(255) not null, 													-- member AddressBook Object's 'calendar' address
-  	REVISION              integer      default nextval('REVISION_SEQ') not null,
 
     primary key (GROUP_ID, MEMBER_ADDRESS) -- implicit index
 );
@@ -550,9 +549,11 @@
   ADDRESSBOOK_HOME_RESOURCE_ID 			integer			not null references ADDRESSBOOK_HOME,
   OWNER_ADDRESSBOOK_HOME_RESOURCE_ID    integer     	references ADDRESSBOOK_HOME,
   ADDRESSBOOK_NAME             			varchar(255) 	default null,
+  OBJECT_RESOURCE_ID					integer			default 0,
   RESOURCE_NAME                			varchar(255),
   REVISION                     			integer     	default nextval('REVISION_SEQ') not null,
-  DELETED                      			boolean      	not null
+  DELETED                      			boolean      	not null,
+  UNSHARED                      		boolean      	not null default false
 );
 
 create index ADDRESSBOOK_OBJECT_REVISIONS_HOME_RESOURCE_ID_OWNER_ADDRESSBOOK_HOME_RESOURCE_ID
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130912/e212f9cc/attachment-0001.html>


More information about the calendarserver-changes mailing list