[CalendarServer-changes] [11645] CalendarServer/branches/users/gaya/sharedgroupfixes

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 26 16:00:00 PDT 2013


Revision: 11645
          http://trac.calendarserver.org//changeset/11645
Author:   gaya at apple.com
Date:     2013-08-26 16:00:00 -0700 (Mon, 26 Aug 2013)
Log Message:
-----------
Add revision info to group schema

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

Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py	2013-08-26 17:42:59 UTC (rev 11644)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py	2013-08-26 23:00:00 UTC (rev 11645)
@@ -2607,7 +2607,7 @@
         # Content-type check
         content_type = request.headers.getHeader("content-type")
         if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
-            log.error("MIME type %s not allowed in calendar collection" % (content_type,))
+            log.error("MIME type {content_type} not allowed in calendar collection", content_type=content_type)
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (caldav_namespace, "supported-calendar-data"),
@@ -3245,7 +3245,7 @@
         # Content-type check
         content_type = request.headers.getHeader("content-type")
         if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "vcard"):
-            log.error("MIME type %s not allowed in vcard collection" % (content_type,))
+            log.error("MIME type {content_type} not allowed in vcard collection", content_type=content_type)
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (carddav_namespace, "supported-address-data"),

Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py	2013-08-26 17:42:59 UTC (rev 11644)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py	2013-08-26 23:00:00 UTC (rev 11645)
@@ -493,9 +493,26 @@
 
 
     @inlineCallbacks
+    def removedObjectResource(self, child):
+        """
+            just like CommonHomeChild.removedObjectResource() but does not call self._deleteRevision(child.name())
+        """
+        self._objects.pop(child.name(), None)
+        self._objects.pop(child.uid(), None)
+        if self._objectNames and child.name() in self._objectNames:
+            self._objectNames.remove(child.name())
+        #yield self._deleteRevision(child.name())
+        yield self.notifyChanged()
+
+
+    @inlineCallbacks
     def remove(self):
 
         if self._resourceID == self._home._resourceID:
+
+            # Note that revision table is NOT queried for removes
+            yield self._updateRevision(self.name())
+
             # Allow remove, as a way to reset the address book to an empty state
             for abo in (yield self.objectResources()):
                 yield abo.remove()
@@ -503,9 +520,6 @@
 
             yield self.unshare()  # storebridge should already have done this
 
-            # Note that revision table is NOT queried for removes
-            yield self._updateRevision(self.name())
-
             yield self.properties()._removeResource()
             yield self._loadPropertyStore()
 
@@ -966,7 +980,8 @@
         """
         aboMembers = schema.ABO_MEMBERS
         return Select([aboMembers.MEMBER_ID], From=aboMembers,
-                      Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs))),
+                      Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))
+                            .And(aboMembers.REMOVED == False),
                       )
 
 
@@ -1302,15 +1317,6 @@
         return self._resourceID == self.addressbook()._resourceID
 
 
-    @classmethod
-    def _deleteMembersWithMemberIDAndGroupIDsQuery(cls, memberID, groupIDs):
-        aboMembers = schema.ABO_MEMBERS
-        return Delete(
-            aboMembers,
-            Where=(aboMembers.MEMBER_ID == memberID).And(
-                    aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))))
-
-
     @inlineCallbacks
     def remove(self):
 
@@ -1327,29 +1333,61 @@
             if readWriteGroupIDs:
                 readWriteObjectIDs = yield self.addressbook().expandGroupIDs(self._txn, readWriteGroupIDs)
 
-            # can't delete item in shared group, even if user has addressbook unbind
+            # can't delete item in read-only shared group, even if user has addressbook unbind
             if self._resourceID not in readWriteObjectIDs:
                 raise HTTPError(FORBIDDEN)
 
-            # convert delete in sharee shared group address book to remove of memberships
-            # that make this object visible to the sharee
-            if readWriteObjectIDs:
-                yield self._deleteMembersWithMemberIDAndGroupIDsQuery(self._resourceID, readWriteObjectIDs).on(
-                    self._txn, groupIDs=readWriteObjectIDs
-                )
+            # 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]
+            for groupID in groupIDs:
+                yield self._updateMember.on(self._txn,
+                    groupID=groupID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=self._resourceID,
+                    resourceName=self.name(),
+                    revision=self.addressbook()._syncTokenRevision,
+                    removed=True
+               )
+        else:
+            # get sync token for delete now
+            yield self.addressbook()._deleteRevision(self.name())
+
         aboMembers = schema.ABO_MEMBERS
-        aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
-
-        groupIDRows = yield Delete(
-            aboMembers,
-            Where=aboMembers.MEMBER_ID == self._resourceID,
-            Return=aboMembers.GROUP_ID
+        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]
 
+        if self.owned() or self.addressbook().fullyShared():
+            # remove memberships
+            for groupID in groupIDs:
+                yield self._updateMember.on(self._txn,
+                    groupID=groupID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=self._resourceID,
+                    resourceName=self.name(),
+                    revision=self.addressbook()._syncTokenRevision,
+                    removed=True
+                )
+
         # add to foreign member table row by UID (aboForeignMembers on address books)
         memberAddress = "urn:uuid:" + self._uid
-        for groupID in set([groupIDRow[0] for groupIDRow in groupIDRows]) - set([self._ownerAddressBookResourceID]):
+        aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
+        for groupID in set(groupIDs) - set([self._ownerAddressBookResourceID]):
             yield Insert(
                 {aboForeignMembers.GROUP_ID: groupID,
                  aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
@@ -1588,16 +1626,6 @@
         returnValue(rows)
 
 
-    @inlineCallbacks
-    def _changeAddressBookRevision(self, addressbook, inserting=False):
-        if inserting:
-            yield addressbook._insertRevision(self._name)
-        else:
-            yield addressbook._updateRevision(self._name)
-
-        yield addressbook.notifyChanged()
-
-
     # Stuff from put_addressbook_common
     def fullValidation(self, component, inserting):
         """
@@ -1608,7 +1636,14 @@
 
         # Valid data sizes
         if config.MaxResourceSize:
-            vcardsize = len(str(component))
+            if self._componentResourceKindToKind(component) == _ABO_KIND_GROUP:
+                thinGroup = deepcopy(component)
+                thinGroup.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
+                thinGroup.removeProperties("X-ADDRESSBOOKSERVER-KIND")
+                thinGroup.removeProperties("UID")
+                vcardsize = len(str(thinGroup))
+            else:
+                vcardsize = len(str(component))
             if vcardsize > config.MaxResourceSize:
                 raise ObjectResourceTooBigError()
 
@@ -1687,30 +1722,22 @@
 
         self._componentChanged = False
 
-        # Handle all validation operations here.
-        self.fullValidation(component, inserting)
+        if self._options.get("coaddedUIDs") is None:
+            # Handle all validation operations here.
+            self.fullValidation(component, inserting)
 
-        # UID lock - this will remain active until the end of the current txn
-        if not inserting or self._options.get("coaddedUIDs") is None:
+            # UID lock - this will remain active until the end of the current txn
             yield self._lockUID(component, inserting)
 
+            if inserting:
+                yield self.addressbook()._insertRevision(self._name)
+            else:
+                yield self.addressbook()._updateRevision(self._name)
+
+            yield self.addressbook().notifyChanged()
+
         yield self.updateDatabase(component, inserting=inserting)
-        yield self._changeAddressBookRevision(self._addressbook, inserting)
 
-        if self.owned():
-            # update revision table of the sharee group address book
-            if self._kind == _ABO_KIND_GROUP:  # optimization
-                invites = yield self.sharingInvites()
-                for invite in invites:
-                    shareeHome = (yield self._txn.homeWithResourceID(self.addressbook()._home._homeType, invite.shareeHomeID()))
-                    yield self._changeAddressBookRevision(shareeHome.addressbook(), inserting)
-                    # one is enough because all have the same resourceID
-                    break
-        else:
-            if self.addressbook()._resourceID != self._ownerAddressBookResourceID:
-                # update revisions table of shared group's containing address book
-                yield self._changeAddressBookRevision(self.ownerHome().addressbook(), inserting)
-
         returnValue(self._componentChanged)
 
 
@@ -1726,15 +1753,6 @@
 
 
     @classmethod
-    def _deleteMembersWithGroupIDAndMemberIDsQuery(cls, groupID, memberIDs):
-        aboMembers = schema.ABO_MEMBERS
-        return Delete(
-            aboMembers,
-            Where=(aboMembers.GROUP_ID == groupID).And(
-                    aboMembers.MEMBER_ID.In(Parameter("memberIDs", len(memberIDs)))))
-
-
-    @classmethod
     def _deleteForeignMembersWithGroupIDAndMembeAddrsQuery(cls, groupID, memberAddrs):
         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
         return Delete(
@@ -1763,6 +1781,38 @@
                     abo.MODIFIED))
 
 
+    @classproperty
+    def _insertMember(cls): #@NoSelf
+        """
+        DAL statement insert a group member
+        """
+        aboMembers = schema.ABO_MEMBERS
+        return Insert(
+            {aboMembers.GROUP_ID: Parameter("groupID"),
+             aboMembers.ADDRESSBOOK_ID: Parameter("addressbookID"),
+             aboMembers.MEMBER_ID: Parameter("memberID"),
+             aboMembers.RESOURCE_NAME: Parameter("resourceName"),
+             aboMembers.REVISION: Parameter("revision"),
+             aboMembers.REMOVED: Parameter("removed"), }
+        )
+
+
+    @classproperty
+    def _updateMember(cls): #@NoSelf
+        """
+        DAL statement update a group member
+        """
+        aboMembers = schema.ABO_MEMBERS
+        return Update(
+            {aboMembers.RESOURCE_NAME: Parameter("resourceName"),
+             aboMembers.REVISION: Parameter("revision"),
+             aboMembers.REMOVED: Parameter("removed"), },
+            Where=(aboMembers.GROUP_ID == Parameter("groupID"))
+              .And(aboMembers.ADDRESSBOOK_ID == Parameter("addressbookID"))
+              .And(aboMembers.MEMBER_ID == Parameter("memberID")),
+       )
+
+
     @inlineCallbacks
     def updateDatabase(self, component, expand_until=None, reCreate=False, #@UnusedVariable
                        inserting=False):
@@ -1829,11 +1879,11 @@
             componentText = str(component)
 
             # remove unneeded fields to get stored _objectText
-            thinComponent = deepcopy(component)
-            thinComponent.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
-            thinComponent.removeProperties("X-ADDRESSBOOKSERVER-KIND")
-            thinComponent.removeProperties("UID")
-            self._objectText = str(thinComponent)
+            thinGroup = deepcopy(component)
+            thinGroup.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
+            thinGroup.removeProperties("X-ADDRESSBOOKSERVER-KIND")
+            thinGroup.removeProperties("UID")
+            self._objectText = str(thinGroup)
         else:
             componentText = str(component)
             self._objectText = componentText
@@ -1867,12 +1917,12 @@
             # delete foreign members table row for this object
             groupIDRows = yield Delete(
                 aboForeignMembers,
-                # should this be scoped to the owner address book?
                 Where=aboForeignMembers.MEMBER_ADDRESS == "urn:uuid:" + self._uid,
                 Return=aboForeignMembers.GROUP_ID
             ).on(self._txn)
             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"
@@ -1880,12 +1930,27 @@
 
             # add to member table rows
             for groupID in groupIDs:
-                yield Insert(
-                    {aboMembers.GROUP_ID: groupID,
-                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                     aboMembers.MEMBER_ID: self._resourceID, }
-                ).on(self._txn)
-
+                @inlineCallbacks
+                def doInsert(subt):
+                    yield self._insertMember.on(subt,
+                        groupID=groupID,
+                        addressbookID=self._ownerAddressBookResourceID,
+                        memberID=self._resourceID,
+                        resourceName="",
+                        revision=self.addressbook()._syncTokenRevision,
+                        removed=False
+                    )
+                try:
+                    yield self._txn.subtransaction(doInsert)
+                except AllRetriesFailed:
+                    yield self._updateMember.on(self._txn,
+                        groupID=groupID,
+                        addressbookID=self._ownerAddressBookResourceID,
+                        memberID=self._resourceID,
+                        resourceName="",
+                        revision=self.addressbook()._syncTokenRevision,
+                        removed=False
+                   )
         else:
             self._modified = (yield Update(
                 {abo.VCARD_TEXT: self._objectText,
@@ -1901,26 +1966,55 @@
                 memberIDs.append(self._resourceID)
 
             # get current members
-            currentMemberRows = yield Select([aboMembers.MEMBER_ID],
+            currentMemberRows = yield Select([aboMembers.MEMBER_ID, aboMembers.REMOVED],
                  From=aboMembers,
                  Where=aboMembers.GROUP_ID == self._resourceID,).on(self._txn)
-            currentMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows]
+            currentMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows if not currentMemberRow[1]]
+            removedMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows if currentMemberRow[1]]
 
-            memberIDsToDelete = set(currentMemberIDs) - set(memberIDs)
-            memberIDsToAdd = set(memberIDs) - set(currentMemberIDs)
+            memberIDsToRemove = set(currentMemberIDs) - set(memberIDs)
+            memberIDsToAdd = set(memberIDs) - set(currentMemberIDs) - set(removedMemberIDs)
+            memberIDsToReadd = set(memberIDs) & set(removedMemberIDs)
 
-            if memberIDsToDelete:
-                yield self._deleteMembersWithGroupIDAndMemberIDsQuery(self._resourceID, memberIDsToDelete).on(
-                    self._txn, memberIDs=memberIDsToDelete
-                )
-
             for memberIDToAdd in memberIDsToAdd:
-                yield Insert(
-                    {aboMembers.GROUP_ID: self._resourceID,
-                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                     aboMembers.MEMBER_ID: memberIDToAdd, }
-                ).on(self._txn)
+                yield self._insertMember.on(self._txn,
+                    groupID=self._resourceID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=memberIDToAdd,
+                    resourceName="",
+                    revision=self.addressbook()._syncTokenRevision,
+                    removed=False
+               )
 
+            for memberIDToUpdate in memberIDsToReadd:
+                yield self._updateMember.on(self._txn,
+                    groupID=self._resourceID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=memberIDToUpdate,
+                    resourceName="",
+                    revision=self.addressbook()._syncTokenRevision,
+                    removed=False
+               )
+
+            # get member names
+            abo = schema.ADDRESSBOOK_OBJECT
+            memberIDNameRows = (
+                yield self._columnsWithResourceIDsQuery(
+                    [abo.RESOURCE_ID, abo.RESOURCE_NAME],
+                    memberIDsToRemove
+                ).on(self._txn, resourceIDs=memberIDsToRemove)
+            ) if memberIDsToRemove else []
+
+            for memberIDToRemove, memberNameToRemove in memberIDNameRows:
+                yield self._updateMember.on(self._txn,
+                    groupID=self._resourceID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=memberIDToRemove,
+                    resourceName=memberNameToRemove,
+                    revision=self.addressbook()._syncTokenRevision,
+                    removed=True
+               )
+
             # don't bother with aboForeignMembers on address books
             if self._resourceID != self._ownerAddressBookResourceID:
 
@@ -1990,7 +2084,8 @@
                     memberRows = yield Select(
                         [aboMembers.MEMBER_ID],
                          From=aboMembers,
-                         Where=aboMembers.GROUP_ID == self._resourceID,
+                         Where=(aboMembers.GROUP_ID == self._resourceID)
+                            .And(aboMembers.REMOVED == False),
                     ).on(self._txn)
                     memberIDs = [memberRow[0] for memberRow in memberRows]
 

Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py	2013-08-26 17:42:59 UTC (rev 11644)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py	2013-08-26 23:00:00 UTC (rev 11645)
@@ -576,7 +576,7 @@
 
         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
         aboMembers = schema.ABO_MEMBERS
-        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
+        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, Where=aboMembers.REMOVED == False).on(txn)
         self.assertEqual(memberRows, [])
 
         foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers).on(txn)
@@ -597,7 +597,7 @@
             )
         subgroupObject = yield adbk.createAddressBookObjectWithName("sg.vcf", subgroup)
 
-        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
+        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, Where=aboMembers.REMOVED == False).on(txn)
         self.assertEqual(sorted(memberRows), sorted([
                                                      [groupObject._resourceID, subgroupObject._resourceID],
                                                      [subgroupObject._resourceID, personObject._resourceID],
@@ -607,7 +607,7 @@
         self.assertEqual(foreignMemberRows, [])
 
         yield subgroupObject.remove()
-        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
+        memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, Where=aboMembers.REMOVED == False).on(txn)
         self.assertEqual(memberRows, [])
 
         foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers,

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-08-26 17:42:59 UTC (rev 11644)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql	2013-08-26 23:00:00 UTC (rev 11645)
@@ -448,6 +448,12 @@
 insert into ADDRESSBOOK_OBJECT_KIND values (3, 'location');
 
 
+---------------
+-- Revisions --
+---------------
+
+create sequence REVISION_SEQ;
+
 ---------------------------------
 -- Address Book Object Members --
 ---------------------------------
@@ -455,7 +461,10 @@
 create table ABO_MEMBERS (
     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_ID             integer      not null references ADDRESSBOOK_OBJECT,						-- member AddressBook Object's RESOURCE_ID
+    MEMBER_ID             integer      not null, --references ADDRESSBOOK_OBJECT,						-- member AddressBook Object's RESOURCE_ID
+  	RESOURCE_NAME         varchar(255),
+  	REVISION              integer      default nextval('REVISION_SEQ') not null,
+  	REMOVED               boolean      default false not null,
 
     primary key (GROUP_ID, MEMBER_ID) -- implicit index
 );
@@ -507,9 +516,7 @@
 -- Revisions --
 ---------------
 
-create sequence REVISION_SEQ;
 
-
 -------------------------------
 -- Calendar Object Revisions --
 -------------------------------
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130826/788870ec/attachment-0001.html>


More information about the calendarserver-changes mailing list