[prev in list] [next in list] [prev in thread] [next in thread] 

List:       calendarserver-changes
Subject:    [CalendarServer-changes] [11645] CalendarServer/branches/users/gaya/sharedgroupfixes
From:       source_changes () macosforge ! org
Date:       2013-08-26 22:58:11
Message-ID: 20130826230000.6885F140F10 () svn ! calendarserver ! org
[Download RAW message or body]

[Attachment #2 (multipart/alternative)]


Revision: 11645
          http://trac.calendarserver.org//changeset/11645
Author:   gaya@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 --
 -------------------------------


[Attachment #5 (text/html)]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[11645] CalendarServer/branches/users/gaya/sharedgroupfixes</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: \
verdana,arial,helvetica,sans-serif; font-size: 10pt;  } #msg dl a { font-weight: \
bold} #msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: \
bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: \
6px; } #logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em \
0; } #logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg \
h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; } \
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; \
} #logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: \
-1.5em; padding-left: 1.5em; } #logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em \
1em 0 1em; background: white;} #logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid \
#fa0; border-bottom: 1px solid #fa0; background: #fff; } #logmsg table th { \
text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted \
#fa0; } #logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: \
0.2em 0.5em; } #logmsg table thead th { text-align: center; border-bottom: 1px solid \
#fa0; } #logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: \
6px; } #patch { width: 100%; }
#patch h4 {font-family: \
verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
 #patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, \
#patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins \
{background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del \
{background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, \
                .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a \
href="http://trac.calendarserver.org//changeset/11645">11645</a></dd> <dt>Author</dt> \
<dd>gaya@apple.com</dd> <dt>Date</dt> <dd>2013-08-26 16:00:00 -0700 (Mon, 26 Aug \
2013)</dd> </dl>

<h3>Log Message</h3>
<pre>Add revision info to group schema</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesusersgayasharedgroupfixestwistedcaldavstorebridgep \
y">CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py</a></li>
 <li><a href="#CalendarServerbranchesusersgayasharedgroupfixestxdavcarddavdatastoresql \
py">CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py</a></li>
 <li><a href="#CalendarServerbranchesusersgayasharedgroupfixestxdavcarddavdatastoretes \
ttest_sqlpy">CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py</a></li>
 <li><a href="#CalendarServerbranchesusersgayasharedgroupfixestxdavcommondatastoresql_ \
schemacurrentsql">CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql</a></li>
 </ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesusersgayasharedgroupfixestwistedcaldavstorebridgepy"></a>
 <div class="modfile"><h4>Modified: \
CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py \
(11644 => 11645)</h4> <pre class="diff"><span>
<span class="info">--- \
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) </span><span class="lines">@@ -2607,7 +2607,7 @@
</span><span class="cx">         # Content-type check
</span><span class="cx">         content_type = \
request.headers.getHeader(&quot;content-type&quot;) </span><span class="cx">         \
if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) \
!= (&quot;text&quot;, &quot;calendar&quot;): </span><del>-            \
log.error(&quot;MIME type %s not allowed in calendar collection&quot; % \
(content_type,)) </del><ins>+            log.error(&quot;MIME type {content_type} not \
allowed in calendar collection&quot;, content_type=content_type) </ins><span \
class="cx">             raise HTTPError(ErrorResponse( </span><span class="cx">       \
responsecode.FORBIDDEN, </span><span class="cx">                 (caldav_namespace, \
&quot;supported-calendar-data&quot;), </span><span class="lines">@@ -3245,7 +3245,7 \
@@ </span><span class="cx">         # Content-type check
</span><span class="cx">         content_type = \
request.headers.getHeader(&quot;content-type&quot;) </span><span class="cx">         \
if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) \
!= (&quot;text&quot;, &quot;vcard&quot;): </span><del>-            \
log.error(&quot;MIME type %s not allowed in vcard collection&quot; % (content_type,)) \
</del><ins>+            log.error(&quot;MIME type {content_type} not allowed in vcard \
collection&quot;, content_type=content_type) </ins><span class="cx">             \
raise HTTPError(ErrorResponse( </span><span class="cx">                 \
responsecode.FORBIDDEN, </span><span class="cx">                 (carddav_namespace, \
&quot;supported-address-data&quot;), </span></span></pre></div>
<a id="CalendarServerbranchesusersgayasharedgroupfixestxdavcarddavdatastoresqlpy"></a>
 <div class="modfile"><h4>Modified: \
CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py \
(11644 => 11645)</h4> <pre class="diff"><span>
<span class="info">--- \
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) </span><span class="lines">@@ -493,9 +493,26 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def removedObjectResource(self, child):
+        &quot;&quot;&quot;
+            just like CommonHomeChild.removedObjectResource() but does not call \
self._deleteRevision(child.name()) +        &quot;&quot;&quot;
+        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
</ins><span class="cx">     def remove(self):
</span><span class="cx"> 
</span><span class="cx">         if self._resourceID == self._home._resourceID:
</span><ins>+
+            # Note that revision table is NOT queried for removes
+            yield self._updateRevision(self.name())
+
</ins><span class="cx">             # Allow remove, as a way to reset the address \
book to an empty state </span><span class="cx">             for abo in (yield \
self.objectResources()): </span><span class="cx">                 yield abo.remove()
</span><span class="lines">@@ -503,9 +520,6 @@
</span><span class="cx"> 
</span><span class="cx">             yield self.unshare()  # storebridge should \
already have done this </span><span class="cx"> 
</span><del>-            # Note that revision table is NOT queried for removes
-            yield self._updateRevision(self.name())
-
</del><span class="cx">             yield self.properties()._removeResource()
</span><span class="cx">             yield self._loadPropertyStore()
</span><span class="cx"> 
</span><span class="lines">@@ -966,7 +980,8 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         aboMembers = schema.ABO_MEMBERS
</span><span class="cx">         return Select([aboMembers.MEMBER_ID], \
From=aboMembers, </span><del>-                      \
Where=aboMembers.GROUP_ID.In(Parameter(&quot;groupIDs&quot;, len(groupIDs))), \
</del><ins>+                      \
Where=aboMembers.GROUP_ID.In(Parameter(&quot;groupIDs&quot;, len(groupIDs))) +        \
.And(aboMembers.REMOVED == False), </ins><span class="cx">                       )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1302,15 +1317,6 @@
</span><span class="cx">         return self._resourceID == \
self.addressbook()._resourceID </span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @classmethod
-    def _deleteMembersWithMemberIDAndGroupIDsQuery(cls, memberID, groupIDs):
-        aboMembers = schema.ABO_MEMBERS
-        return Delete(
-            aboMembers,
-            Where=(aboMembers.MEMBER_ID == memberID).And(
-                    aboMembers.GROUP_ID.In(Parameter(&quot;groupIDs&quot;, \
                len(groupIDs)))))
-
-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def remove(self):
</span><span class="cx"> 
</span><span class="lines">@@ -1327,29 +1333,61 @@
</span><span class="cx">             if readWriteGroupIDs:
</span><span class="cx">                 readWriteObjectIDs = yield \
self.addressbook().expandGroupIDs(self._txn, readWriteGroupIDs) </span><span \
class="cx">  </span><del>-            # can't delete item in shared group, even if \
user has addressbook unbind </del><ins>+            # can't delete item in read-only \
shared group, even if user has addressbook unbind </ins><span class="cx">             \
if self._resourceID not in readWriteObjectIDs: </span><span class="cx">               \
raise HTTPError(FORBIDDEN) </span><span class="cx"> 
</span><del>-            # 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
-                )
</del><ins>+            # get sync token for delete now
+            yield self.addressbook()._deleteRevision(self.name())
</ins><span class="cx"> 
</span><ins>+            # 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(&quot;readWriteObjectIDs&quot;, \
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())
+
</ins><span class="cx">         aboMembers = schema.ABO_MEMBERS
</span><del>-        aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
-
-        groupIDRows = yield Delete(
-            aboMembers,
-            Where=aboMembers.MEMBER_ID == self._resourceID,
-            Return=aboMembers.GROUP_ID
</del><ins>+        groupIDRows = yield Select(
+            [aboMembers.GROUP_ID],
+            From=aboMembers,
+            Where=(aboMembers.MEMBER_ID == self._resourceID)
+                .And(aboMembers.REMOVED == False),
</ins><span class="cx">         ).on(self._txn)
</span><ins>+        groupIDs = [groupIDRow[0] for groupIDRow in groupIDRows]
</ins><span class="cx"> 
</span><ins>+        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
+                )
+
</ins><span class="cx">         # add to foreign member table row by UID \
(aboForeignMembers on address books) </span><span class="cx">         memberAddress = \
&quot;urn:uuid:&quot; + self._uid </span><del>-        for groupID in \
set([groupIDRow[0] for groupIDRow in groupIDRows]) - \
set([self._ownerAddressBookResourceID]): </del><ins>+        aboForeignMembers = \
schema.ABO_FOREIGN_MEMBERS +        for groupID in set(groupIDs) - \
set([self._ownerAddressBookResourceID]): </ins><span class="cx">             yield \
Insert( </span><span class="cx">                 {aboForeignMembers.GROUP_ID: \
groupID, </span><span class="cx">                  aboForeignMembers.ADDRESSBOOK_ID: \
self._ownerAddressBookResourceID, </span><span class="lines">@@ -1588,16 +1626,6 @@
</span><span class="cx">         returnValue(rows)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def _changeAddressBookRevision(self, addressbook, inserting=False):
-        if inserting:
-            yield addressbook._insertRevision(self._name)
-        else:
-            yield addressbook._updateRevision(self._name)
-
-        yield addressbook.notifyChanged()
-
-
</del><span class="cx">     # Stuff from put_addressbook_common
</span><span class="cx">     def fullValidation(self, component, inserting):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -1608,7 +1636,14 @@
</span><span class="cx"> 
</span><span class="cx">         # Valid data sizes
</span><span class="cx">         if config.MaxResourceSize:
</span><del>-            vcardsize = len(str(component))
</del><ins>+            if self._componentResourceKindToKind(component) == \
_ABO_KIND_GROUP: +                thinGroup = deepcopy(component)
+                thinGroup.removeProperties(&quot;X-ADDRESSBOOKSERVER-MEMBER&quot;)
+                thinGroup.removeProperties(&quot;X-ADDRESSBOOKSERVER-KIND&quot;)
+                thinGroup.removeProperties(&quot;UID&quot;)
+                vcardsize = len(str(thinGroup))
+            else:
+                vcardsize = len(str(component))
</ins><span class="cx">             if vcardsize &gt; config.MaxResourceSize:
</span><span class="cx">                 raise ObjectResourceTooBigError()
</span><span class="cx"> 
</span><span class="lines">@@ -1687,30 +1722,22 @@
</span><span class="cx"> 
</span><span class="cx">         self._componentChanged = False
</span><span class="cx"> 
</span><del>-        # Handle all validation operations here.
-        self.fullValidation(component, inserting)
</del><ins>+        if self._options.get(&quot;coaddedUIDs&quot;) is None:
+            # Handle all validation operations here.
+            self.fullValidation(component, inserting)
</ins><span class="cx"> 
</span><del>-        # UID lock - this will remain active until the end of the \
                current txn
-        if not inserting or self._options.get(&quot;coaddedUIDs&quot;) is None:
</del><ins>+            # UID lock - this will remain active until the end of the \
current txn </ins><span class="cx">             yield self._lockUID(component, \
inserting) </span><span class="cx"> 
</span><ins>+            if inserting:
+                yield self.addressbook()._insertRevision(self._name)
+            else:
+                yield self.addressbook()._updateRevision(self._name)
+
+            yield self.addressbook().notifyChanged()
+
</ins><span class="cx">         yield self.updateDatabase(component, \
inserting=inserting) </span><del>-        yield \
self._changeAddressBookRevision(self._addressbook, inserting) </del><span class="cx"> \
 </span><del>-        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)
-
</del><span class="cx">         returnValue(self._componentChanged)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1726,15 +1753,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def _deleteMembersWithGroupIDAndMemberIDsQuery(cls, groupID, \
                memberIDs):
-        aboMembers = schema.ABO_MEMBERS
-        return Delete(
-            aboMembers,
-            Where=(aboMembers.GROUP_ID == groupID).And(
-                    aboMembers.MEMBER_ID.In(Parameter(&quot;memberIDs&quot;, \
                len(memberIDs)))))
-
-
-    @classmethod
</del><span class="cx">     def \
_deleteForeignMembersWithGroupIDAndMembeAddrsQuery(cls, groupID, memberAddrs): \
</span><span class="cx">         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS \
</span><span class="cx">         return Delete( </span><span class="lines">@@ -1763,6 \
+1781,38 @@ </span><span class="cx">                     abo.MODIFIED))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classproperty
+    def _insertMember(cls): #@NoSelf
+        &quot;&quot;&quot;
+        DAL statement insert a group member
+        &quot;&quot;&quot;
+        aboMembers = schema.ABO_MEMBERS
+        return Insert(
+            {aboMembers.GROUP_ID: Parameter(&quot;groupID&quot;),
+             aboMembers.ADDRESSBOOK_ID: Parameter(&quot;addressbookID&quot;),
+             aboMembers.MEMBER_ID: Parameter(&quot;memberID&quot;),
+             aboMembers.RESOURCE_NAME: Parameter(&quot;resourceName&quot;),
+             aboMembers.REVISION: Parameter(&quot;revision&quot;),
+             aboMembers.REMOVED: Parameter(&quot;removed&quot;), }
+        )
+
+
+    @classproperty
+    def _updateMember(cls): #@NoSelf
+        &quot;&quot;&quot;
+        DAL statement update a group member
+        &quot;&quot;&quot;
+        aboMembers = schema.ABO_MEMBERS
+        return Update(
+            {aboMembers.RESOURCE_NAME: Parameter(&quot;resourceName&quot;),
+             aboMembers.REVISION: Parameter(&quot;revision&quot;),
+             aboMembers.REMOVED: Parameter(&quot;removed&quot;), },
+            Where=(aboMembers.GROUP_ID == Parameter(&quot;groupID&quot;))
+              .And(aboMembers.ADDRESSBOOK_ID == \
Parameter(&quot;addressbookID&quot;)) +              .And(aboMembers.MEMBER_ID == \
Parameter(&quot;memberID&quot;)), +       )
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def updateDatabase(self, component, expand_until=None, \
reCreate=False, #@UnusedVariable </span><span class="cx">                        \
inserting=False): </span><span class="lines">@@ -1829,11 +1879,11 @@
</span><span class="cx">             componentText = str(component)
</span><span class="cx"> 
</span><span class="cx">             # remove unneeded fields to get stored \
_objectText </span><del>-            thinComponent = deepcopy(component)
-            thinComponent.removeProperties(&quot;X-ADDRESSBOOKSERVER-MEMBER&quot;)
-            thinComponent.removeProperties(&quot;X-ADDRESSBOOKSERVER-KIND&quot;)
-            thinComponent.removeProperties(&quot;UID&quot;)
-            self._objectText = str(thinComponent)
</del><ins>+            thinGroup = deepcopy(component)
+            thinGroup.removeProperties(&quot;X-ADDRESSBOOKSERVER-MEMBER&quot;)
+            thinGroup.removeProperties(&quot;X-ADDRESSBOOKSERVER-KIND&quot;)
+            thinGroup.removeProperties(&quot;UID&quot;)
+            self._objectText = str(thinGroup)
</ins><span class="cx">         else:
</span><span class="cx">             componentText = str(component)
</span><span class="cx">             self._objectText = componentText
</span><span class="lines">@@ -1867,12 +1917,12 @@
</span><span class="cx">             # delete foreign members table row for this \
object </span><span class="cx">             groupIDRows = yield Delete(
</span><span class="cx">                 aboForeignMembers,
</span><del>-                # should this be scoped to the owner address book?
</del><span class="cx">                 Where=aboForeignMembers.MEMBER_ADDRESS == \
&quot;urn:uuid:&quot; + self._uid, </span><span class="cx">                 \
Return=aboForeignMembers.GROUP_ID </span><span class="cx">             \
).on(self._txn) </span><span class="cx">             groupIDs = set([groupIDRow[0] \
for groupIDRow in groupIDRows]) </span><span class="cx"> 
</span><ins>+            # add vCard to writable groups
</ins><span class="cx">             if not self.owned() and not \
self.addressbook().fullyShared(): </span><span class="cx">                 \
readWriteGroupIDs = yield self.addressbook().readWriteGroupIDs() </span><span \
class="cx">                 assert readWriteGroupIDs, &quot;no access&quot; \
</span><span class="lines">@@ -1880,12 +1930,27 @@ </span><span class="cx"> 
</span><span class="cx">             # add to member table rows
</span><span class="cx">             for groupID in groupIDs:
</span><del>-                yield Insert(
-                    {aboMembers.GROUP_ID: groupID,
-                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                     aboMembers.MEMBER_ID: self._resourceID, }
-                ).on(self._txn)
-
</del><ins>+                @inlineCallbacks
+                def doInsert(subt):
+                    yield self._insertMember.on(subt,
+                        groupID=groupID,
+                        addressbookID=self._ownerAddressBookResourceID,
+                        memberID=self._resourceID,
+                        resourceName=&quot;&quot;,
+                        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=&quot;&quot;,
+                        revision=self.addressbook()._syncTokenRevision,
+                        removed=False
+                   )
</ins><span class="cx">         else:
</span><span class="cx">             self._modified = (yield Update(
</span><span class="cx">                 {abo.VCARD_TEXT: self._objectText,
</span><span class="lines">@@ -1901,26 +1966,55 @@
</span><span class="cx">                 memberIDs.append(self._resourceID)
</span><span class="cx"> 
</span><span class="cx">             # get current members
</span><del>-            currentMemberRows = yield Select([aboMembers.MEMBER_ID],
</del><ins>+            currentMemberRows = yield Select([aboMembers.MEMBER_ID, \
aboMembers.REMOVED], </ins><span class="cx">                  From=aboMembers,
</span><span class="cx">                  Where=aboMembers.GROUP_ID == \
self._resourceID,).on(self._txn) </span><del>-            currentMemberIDs = \
[currentMemberRow[0] for currentMemberRow in currentMemberRows] </del><ins>+          \
currentMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows if \
not currentMemberRow[1]] +            removedMemberIDs = [currentMemberRow[0] for \
currentMemberRow in currentMemberRows if currentMemberRow[1]] </ins><span class="cx"> \
 </span><del>-            memberIDsToDelete = set(currentMemberIDs) - set(memberIDs)
-            memberIDsToAdd = set(memberIDs) - set(currentMemberIDs)
</del><ins>+            memberIDsToRemove = set(currentMemberIDs) - set(memberIDs)
+            memberIDsToAdd = set(memberIDs) - set(currentMemberIDs) - \
set(removedMemberIDs) +            memberIDsToReadd = set(memberIDs) &amp; \
set(removedMemberIDs) </ins><span class="cx"> 
</span><del>-            if memberIDsToDelete:
-                yield \
self._deleteMembersWithGroupIDAndMemberIDsQuery(self._resourceID, \
                memberIDsToDelete).on(
-                    self._txn, memberIDs=memberIDsToDelete
-                )
-
</del><span class="cx">             for memberIDToAdd in memberIDsToAdd:
</span><del>-                yield Insert(
-                    {aboMembers.GROUP_ID: self._resourceID,
-                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
-                     aboMembers.MEMBER_ID: memberIDToAdd, }
-                ).on(self._txn)
</del><ins>+                yield self._insertMember.on(self._txn,
+                    groupID=self._resourceID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=memberIDToAdd,
+                    resourceName=&quot;&quot;,
+                    revision=self.addressbook()._syncTokenRevision,
+                    removed=False
+               )
</ins><span class="cx"> 
</span><ins>+            for memberIDToUpdate in memberIDsToReadd:
+                yield self._updateMember.on(self._txn,
+                    groupID=self._resourceID,
+                    addressbookID=self._ownerAddressBookResourceID,
+                    memberID=memberIDToUpdate,
+                    resourceName=&quot;&quot;,
+                    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
+               )
+
</ins><span class="cx">             # don't bother with aboForeignMembers on address \
books </span><span class="cx">             if self._resourceID != \
self._ownerAddressBookResourceID: </span><span class="cx"> 
</span><span class="lines">@@ -1990,7 +2084,8 @@
</span><span class="cx">                     memberRows = yield Select(
</span><span class="cx">                         [aboMembers.MEMBER_ID],
</span><span class="cx">                          From=aboMembers,
</span><del>-                         Where=aboMembers.GROUP_ID == self._resourceID,
</del><ins>+                         Where=(aboMembers.GROUP_ID == self._resourceID)
+                            .And(aboMembers.REMOVED == False),
</ins><span class="cx">                     ).on(self._txn)
</span><span class="cx">                     memberIDs = [memberRow[0] for memberRow \
in memberRows] </span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesusersgayasharedgroupfixestxdavcarddavdatastoretesttest_sqlpy"></a>
 <div class="modfile"><h4>Modified: \
CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py \
(11644 => 11645)</h4> <pre class="diff"><span>
<span class="info">--- \
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) </span><span class="lines">@@ -576,7 +576,7 @@
</span><span class="cx"> 
</span><span class="cx">         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
</span><span class="cx">         aboMembers = schema.ABO_MEMBERS
</span><del>-        memberRows = yield Select([aboMembers.GROUP_ID, \
aboMembers.MEMBER_ID], From=aboMembers,).on(txn) </del><ins>+        memberRows = \
yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, \
Where=aboMembers.REMOVED == False).on(txn) </ins><span class="cx">         \
self.assertEqual(memberRows, []) </span><span class="cx"> 
</span><span class="cx">         foreignMemberRows = yield \
Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], \
From=aboForeignMembers).on(txn) </span><span class="lines">@@ -597,7 +597,7 @@
</span><span class="cx">             )
</span><span class="cx">         subgroupObject = yield \
adbk.createAddressBookObjectWithName(&quot;sg.vcf&quot;, subgroup) </span><span \
class="cx">  </span><del>-        memberRows = yield Select([aboMembers.GROUP_ID, \
aboMembers.MEMBER_ID], From=aboMembers,).on(txn) </del><ins>+        memberRows = \
yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, \
Where=aboMembers.REMOVED == False).on(txn) </ins><span class="cx">         \
self.assertEqual(sorted(memberRows), sorted([ </span><span class="cx">                \
[groupObject._resourceID, subgroupObject._resourceID], </span><span class="cx">       \
[subgroupObject._resourceID, personObject._resourceID], </span><span class="lines">@@ \
-607,7 +607,7 @@ </span><span class="cx">         self.assertEqual(foreignMemberRows, \
[]) </span><span class="cx"> 
</span><span class="cx">         yield subgroupObject.remove()
</span><del>-        memberRows = yield Select([aboMembers.GROUP_ID, \
aboMembers.MEMBER_ID], From=aboMembers,).on(txn) </del><ins>+        memberRows = \
yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, \
Where=aboMembers.REMOVED == False).on(txn) </ins><span class="cx">         \
self.assertEqual(memberRows, []) </span><span class="cx"> 
</span><span class="cx">         foreignMemberRows = yield \
Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], \
From=aboForeignMembers, </span></span></pre></div>
<a id="CalendarServerbranchesusersgayasharedgroupfixestxdavcommondatastoresql_schemacurrentsql"></a>
 <div class="modfile"><h4>Modified: \
CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql \
(11644 => 11645)</h4> <pre class="diff"><span>
<span class="info">--- \
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) </span><span class="lines">@@ -448,6 +448,12 @@
</span><span class="cx"> insert into ADDRESSBOOK_OBJECT_KIND values (3, 'location');
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+---------------
+-- Revisions --
+---------------
+
+create sequence REVISION_SEQ;
+
</ins><span class="cx"> ---------------------------------
</span><span class="cx"> -- Address Book Object Members --
</span><span class="cx"> ---------------------------------
</span><span class="lines">@@ -455,7 +461,10 @@
</span><span class="cx"> create table ABO_MEMBERS (
</span><span class="cx">     GROUP_ID              integer      not null references \
ADDRESSBOOK_OBJECT on delete cascade,	-- AddressBook Object's (kind=='group') \
RESOURCE_ID </span><span class="cx">  	ADDRESSBOOK_ID		  integer      not null \
references ADDRESSBOOK_HOME on delete cascade, </span><del>-    MEMBER_ID             \
integer      not null references ADDRESSBOOK_OBJECT,						-- member AddressBook \
Object's RESOURCE_ID </del><ins>+    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, </ins><span class="cx"> 
</span><span class="cx">     primary key (GROUP_ID, MEMBER_ID) -- implicit index
</span><span class="cx"> );
</span><span class="lines">@@ -507,9 +516,7 @@
</span><span class="cx"> -- Revisions --
</span><span class="cx"> ---------------
</span><span class="cx"> 
</span><del>-create sequence REVISION_SEQ;
</del><span class="cx"> 
</span><del>-
</del><span class="cx"> -------------------------------
</span><span class="cx"> -- Calendar Object Revisions --
</span><span class="cx"> -------------------------------
</span></span></pre>
</div>
</div>

</body>
</html>



_______________________________________________
calendarserver-changes mailing list
calendarserver-changes@lists.macosforge.org
https://lists.macosforge.org/mailman/listinfo/calendarserver-changes


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic