[CalendarServer-changes] [10002] CalendarServer/branches/users/gaya/sharedgroups

source_changes at macosforge.org source_changes at macosforge.org
Sat Nov 3 09:05:43 PDT 2012


Revision: 10002
          http://trac.calendarserver.org//changeset/10002
Author:   gaya at apple.com
Date:     2012-11-03 09:05:43 -0700 (Sat, 03 Nov 2012)
Log Message:
-----------
checkpoint changes

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/sharing.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/caldav/datastore/file.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/test/test_sql.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/file.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql_schema/current.sql
    CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/upgrade/test/test_migrate.py

Modified: CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/sharing.py	2012-11-02 20:50:49 UTC (rev 10001)
+++ CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/sharing.py	2012-11-03 16:05:43 UTC (rev 10002)
@@ -33,7 +33,7 @@
 from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
     _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_STATUS_INVITED, \
     _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, \
-    _BIND_STATUS_INVALID
+    _BIND_STATUS_INVALID, _ABO_KIND_GROUP
 from txdav.xml import element
 
 from twisted.internet.defer import succeed, inlineCallbacks, DeferredList, \
@@ -116,10 +116,12 @@
     def upgradeToShare(self):
         """ Upgrade this collection to a shared state """
 
-        # Change resourcetype
-        rtype = self.resourceType()
-        rtype = element.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
-        self.writeDeadProperty(rtype)
+        if self.isCollection():
+            # Change resourcetype
+            rtype = self.resourceType()
+            rtype = element.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
+            self.writeDeadProperty(rtype)
+        # TODO: set group resource type?
 
     @inlineCallbacks
     def downgradeFromShare(self, request):
@@ -202,7 +204,7 @@
         # Get the home collection
         if self.isCalendarCollection():
             shareeHomeResource = yield sharee.calendarHome(request)
-        elif self.isAddressBookCollection():
+        elif self.isAddressBookCollection() or self.isGroup():
             shareeHomeResource = yield sharee.addressBookHome(request)
         else:
             raise HTTPError(ErrorResponse(
@@ -232,7 +234,7 @@
     @inlineCallbacks
     def isShared(self, request):
         """ Return True if this is an owner shared calendar collection """
-        returnValue((yield self.isSpecialCollection(customxml.SharedOwner)))
+        returnValue((yield self.isSpecialCollection(customxml.SharedOwner)) or (yield self._allInvitations()))
 
 
     def setShare(self, share):
@@ -253,7 +255,7 @@
         # Remove from sharee's calendar/address book home
         if self.isCalendarCollection():
             shareeHome = yield sharee.calendarHome(request)
-        elif self.isAddressBookCollection():
+        elif self.isAddressBookCollection() or self.isGroup():
             shareeHome = yield sharee.addressBookHome(request)
         returnValue((yield shareeHome.removeShare(request, self._share)))
 
@@ -287,6 +289,9 @@
             return "calendar"
         elif self.isAddressBookCollection():
             return "addressbook"
+        elif self.isGroup():
+            #TODO: Add group xml resource type ?
+            return "group"
         else:
             return ""
 
@@ -501,7 +506,7 @@
         '''
         if self.isCalendarCollection():
             shareeHome = yield self._newStoreObject._txn.calendarHomeWithUID(shareeUID, create=True)
-        elif self.isAddressBookCollection():
+        elif self.isAddressBookCollection() or self.isGroup():
             shareeHome = yield self._newStoreObject._txn.addressbookHomeWithUID(shareeUID, create=True)
 
         sharedName = yield self._newStoreObject.shareWith(shareeHome,
@@ -622,7 +627,7 @@
         if sharee:
             if self.isCalendarCollection():
                 shareeHomeResource = yield sharee.calendarHome(request)
-            elif self.isAddressBookCollection():
+            elif self.isAddressBookCollection() or self.isGroup():
                 shareeHomeResource = yield sharee.addressBookHome(request)
             displayName = (yield shareeHomeResource.removeShareByUID(request, invitation.uid()))
             # If current user state is accepted then we send an invite with the new state, otherwise
@@ -899,8 +904,22 @@
         customxml.InviteReply: _xmlHandleInviteReply,
     }
 
+    def isGroup(self):
+        try:
+            kind = self._newStoreObject._kind
+        except AttributeError:
+            pass
+        else:
+            if kind == _ABO_KIND_GROUP:
+                self.log_info("isGroup():self=%s returning True" % (self,))
+                return True
+
+        self.log_info("isGroup():self=%s returning False" % (self,))
+        return False
+
+
     def POST_handler_content_type(self, request, contentType):
-        if self.isCollection():
+        if self.isCollection() or self.isGroup():
             if contentType:
                 if contentType in self._postHandlers:
                     return self._postHandlers[contentType](self, request)
@@ -986,10 +1005,8 @@
         if not child or child.owned():
             returnValue(None)
 
-        sharerHomeChild = yield child.ownerHome().childWithID(child._resourceID)
-
         # get the shared object's URL
-        sharer = self.principalForUID(sharerHomeChild.viewerHome().uid())
+        sharer = self.principalForUID(child.ownerHome().uid())
 
         if not request:
             # FIXEME:  Fake up a request that can be used to get the sharer home resource
@@ -1003,9 +1020,19 @@
         elif self._newStoreHome._homeType == EADDRESSBOOKTYPE:
             sharerHomeCollection = yield sharer.addressBookHome(request)
 
-        url = joinURL(sharerHomeCollection.url(), sharerHomeChild.name())
-        share = Share(shareeHomeChild=child, sharerHomeChild=sharerHomeChild, url=url)
+        itemShared = yield child.ownerHome().childWithID(child._resourceID)
+        if itemShared:
+            url = joinURL(sharerHomeCollection.url(), itemShared.name())
+        else:
+            for sharerHomeChild in (yield child.ownerHome().children()):
+                for objectResource in (yield sharerHomeChild.objectResources()):
+                    if objectResource._resourceID == child._resourceID:
+                        itemShared = objectResource
+                        url = joinURL(sharerHomeCollection.url(), itemShared._parentCollection.name(), itemShared.name())
+                        break
 
+        share = Share(shareeHomeChild=child, sharerHomeChild=itemShared, url=url)
+
         returnValue(share)
 
     @inlineCallbacks
@@ -1083,7 +1110,7 @@
         sharee = self.principalForUID(share.shareeUID())
         if sharedCollection.isCalendarCollection():
             shareeHomeResource = yield sharee.calendarHome(request)
-        elif sharedCollection.isAddressBookCollection():
+        elif sharedCollection.isAddressBookCollection() or sharedCollection.isGroup():
             shareeHomeResource = yield sharee.addressBookHome(request)
         shareeURL = joinURL(shareeHomeResource.url(), share.name())
         shareeCollection = yield request.locateResource(shareeURL)

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/caldav/datastore/file.py	2012-11-02 20:50:49 UTC (rev 10001)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/caldav/datastore/file.py	2012-11-03 16:05:43 UTC (rev 10002)
@@ -261,14 +261,6 @@
         return ResourceType.calendar #@UndefinedVariable
 
 
-    def asShared(self):
-        """
-        Stub for interface-compliance tests.
-        """
-        # TODO: implement me.
-        raise NotImplementedError()
-
-
     ownerCalendarHome = CommonHomeChild.ownerHome
     viewerCalendarHome = CommonHomeChild.viewerHome
     calendarObjects = CommonHomeChild.objectResources

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py	2012-11-02 20:50:49 UTC (rev 10001)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py	2012-11-03 16:05:43 UTC (rev 10002)
@@ -44,7 +44,7 @@
 from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook, \
     IAddressBookObject
 from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
-    CommonObjectResource, EADDRESSBOOKTYPE
+    CommonObjectResource, EADDRESSBOOKTYPE, SharingMixIn
 from txdav.common.datastore.sql_legacy import PostgresLegacyABIndexEmulator
 from txdav.common.datastore.sql_tables import ADDRESSBOOK_TABLE, \
     ADDRESSBOOK_BIND_TABLE, ADDRESSBOOK_OBJECT_REVISIONS_TABLE, \
@@ -137,7 +137,7 @@
 
 AddressBookHome._register(EADDRESSBOOKTYPE)
 
-class _AddressBookObjectCommon(object):
+class AddressBookSharingMixIn(SharingMixIn):
 
     @classproperty
     def _insertABObject(cls): #@NoSelf
@@ -160,7 +160,7 @@
                     abo.MODIFIED))
 
 
-class AddressBook(CommonHomeChild, _AddressBookObjectCommon):
+class AddressBook(CommonHomeChild, AddressBookSharingMixIn):
     """
     SQL-based implementation of L{IAddressBook}.
     """
@@ -245,12 +245,13 @@
 
     @classmethod
     @inlineCallbacks
-    def _createChild(cls, home, name):
+    def _createChild(cls, home, name):  #@NoSelf
         # Create this object
 
         # TODO:  N, FN, set to resource name for now,
         #        but "may" have to change when shared
         #        and perhaps will need to reflect a per-user property
+        uid = str(uuid4())
         component = VCard.fromString(
             """BEGIN:VCARD
 VERSION:3.0
@@ -260,13 +261,13 @@
 N:%s;;;;
 X-ADDRESSBOOKSERVER-KIND:group
 END:VCARD
-""".replace("\n", "\r\n") % (vCardProductID, uuid4(), name, name)
+""".replace("\n", "\r\n") % (vCardProductID, uid, name, name)
             )
 
         componentText = str(component)
         md5 = hashlib.md5(componentText).hexdigest()
 
-        resourceID, created, modified = (
+        resourceID, created, modified = (#@UnusedVariable
             yield cls._insertABObject.on(
                 home._txn,
                 addressbookResourceID=None,
@@ -289,16 +290,78 @@
 
 
 
-class AddressBookObject(CommonObjectResource, _AddressBookObjectCommon):
+    @classmethod
+    def _memberIDsWithGroupIDsQuery(cls, groupIDs): #@NoSelf
+        """
+        DAL query to load all object resource names for a home child.
+        """
+        aboMembers = schema.ABO_MEMBERS
+        return Select([aboMembers.MEMBER_ID], From=aboMembers,
+                      Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))
+                      )
 
+    @inlineCallbacks
+    def _addressBookObjectIDs(self):
+        """
+        Get all addressbookobject resource IDs in this address book
+        
+        TODO: optimize
+        """
+        # TODO: if shared, self is a member
+        # allMemberIDs = set() if self.owned() else set([self._resourceID])
+        allMemberIDs = set()
+        examinedIDs = set()
+        remainingIDs = set([self._resourceID])
+        while remainingIDs:
+            memberRows = yield self._memberIDsWithGroupIDsQuery(remainingIDs).on(self._txn, groupIDs=remainingIDs)
+            allMemberIDs |= set([memberRow[0] for memberRow in memberRows])
+            examinedIDs |= remainingIDs
+            remainingIDs = allMemberIDs - examinedIDs
+            print("_addressBookObjectIDs:self=%s, examinedIDs=%s, remainingIDs=%s, allMemberIDs=%s" % (self, examinedIDs, remainingIDs, allMemberIDs,))
+
+        returnValue(tuple(allMemberIDs))
+
+
+    @classmethod
+    def _objectResourceNamesWithResourceIDsQuery(cls, resourceIDs): #@NoSelf
+        """
+        DAL query to load all object resource names for a home child.
+        """
+        abo = schema.ADDRESSBOOK_OBJECT
+        return Select([abo.RESOURCE_NAME], From=abo,
+                      Where=abo.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs)))
+                      )
+
+
+    @inlineCallbacks
+    def listObjectResources(self):
+        if self._objectNames is None:
+            memberIDs = yield self._addressBookObjectIDs()
+            rows = (yield self._objectResourceNamesWithResourceIDsQuery(memberIDs).on(
+                self._txn, resourceIDs=memberIDs)) if memberIDs else []
+            self._objectNames = sorted([row[0] for row in rows])
+
+        returnValue(self._objectNames)
+
+
+    @inlineCallbacks
+    def countObjectResources(self):
+        returnValue(len((yield self._addressBookObjectIDs())))
+
+
+class AddressBookObject(CommonObjectResource, AddressBookSharingMixIn):
+
     implements(IAddressBookObject)
 
     _objectTable = ADDRESSBOOK_OBJECT_TABLE
     _objectSchema = schema.ADDRESSBOOK_OBJECT
+    _bindSchema = schema.ADDRESSBOOK_BIND
 
+
     def __init__(self, addressbook, name, uid, resourceID=None, metadata=None):
 
         self._kind = None
+        self._ownerAddressBookResourceID = None
         super(AddressBookObject, self).__init__(addressbook, name, uid, resourceID)
 
 
@@ -319,6 +382,11 @@
         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
         aboMembers = schema.ABO_MEMBERS
 
+        if not self._addressbook.owned():
+            ownerGroup, ownerAddressBook = yield self._ownerGroupAndAddressBook()  #@UnusedVariable 
+            if ownerGroup:
+                assert False, "updateDatabase() remove of vcard in shared group: need to modify shared group vCard text"
+
         # delete members table row for this object
         groupIDs = yield Delete(
             aboMembers,
@@ -327,29 +395,19 @@
         ).on(self._txn)
 
         # add to foreign member table row by UID
+        assert self._ownerAddressBookResourceID
         for groupID in groupIDs:
-            yield Insert(
-                {aboForeignMembers.GROUP_ID: groupID,
-                 aboForeignMembers.ADDRESSBOOK_ID: self._addressbook._resourceID,
-                 aboForeignMembers.MEMBER_ADDRESS: "urn:uuid:" + self._uid, }
-            ).on(self._txn)
+            if groupID[0] != self._ownerAddressBookResourceID:
+                # remove on address book, add aboForeignMembers row to local groups only
+                yield Insert(
+                        {aboForeignMembers.GROUP_ID: groupID,
+                         aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
+                         aboForeignMembers.MEMBER_ADDRESS: "urn:uuid:" + self._uid, }
+                    ).on(self._txn)
 
-        if self._kind == _ABO_KIND_GROUP:
-
-            # delete this group's members
-            yield Delete(
-                aboMembers,
-                Where=aboMembers.GROUP_ID == self._resourceID,
-            ).on(self._txn)
-
-            # delete this group's foreign members
-            yield Delete(
-                aboForeignMembers,
-                Where=aboForeignMembers.GROUP_ID == self._resourceID,
-            ).on(self._txn)
-
         yield super(AddressBookObject, self).remove()
         self._kind = None
+        self._ownerAddressBookResourceID = None
 
     @classproperty
     def _allColumns(cls): #@NoSelf
@@ -359,6 +417,7 @@
         """
         obj = cls._objectSchema
         return [
+            obj.ADDRESSBOOK_RESOURCE_ID,
             obj.RESOURCE_ID,
             obj.RESOURCE_NAME,
             obj.UID,
@@ -375,7 +434,8 @@
         Given a select result using the columns from L{_allColumns}, initialize
         the object resource state.
         """
-        (self._resourceID,
+        (self._ownerAddressBookResourceID,
+         self._resourceID,
          self._name,
          self._uid,
          self._kind,
@@ -384,7 +444,48 @@
          self._created,
          self._modified,) = tuple(row)
 
+        print("_initFromRow:self=%s, row=%s, self._ownerAddressBookResourceID=%s, self._addressbook=%s" % (self, row, self._ownerAddressBookResourceID, self._addressbook))
+
+    @classmethod
+    def _allColumnsWithResourceIDsQuery(cls, resourceIDs): #@NoSelf
+        obj = cls._objectSchema
+        return Select(cls._allColumns, From=obj,
+                      Where=obj.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs))))
+
+    @classmethod
     @inlineCallbacks
+    def _allColumnsWithParent(cls, parent): #@NoSelf
+
+        memberIDs = yield parent._addressBookObjectIDs()
+        print("_allColumnsWithParent:cls=%s, parent=%s, memberIDs=%s" % (cls, parent, memberIDs,))
+
+        rows = (yield cls._allColumnsWithResourceIDsQuery(memberIDs).on(
+            parent._txn, resourceIDs=memberIDs)) if memberIDs else []
+
+        print("_allColumnsWithParent:cls=%s, parent=%s, rows=%s" % (cls, parent, rows,))
+        returnValue(rows)
+
+
+    @classmethod
+    def _allColumnsWithResourceIDsAndNamesQuery(cls, resourceIDs, names): #@NoSelf
+        obj = cls._objectSchema
+        return Select(cls._allColumns, From=obj,
+                      Where=(obj.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs))).And(
+                          obj.RESOURCE_NAME.In(Parameter("names", len(names))))))
+
+
+    @classmethod
+    @inlineCallbacks
+    def _allColumnsWithParentAndNames(cls, parent, names): #@NoSelf
+        memberIDs = yield parent._addressBookObjectIDs()
+
+        rows = (yield cls._allColumnsWithResourceIDsAndNamesQuery(memberIDs, names).on(
+            parent._txn, resourceIDs=memberIDs, names=names)) if memberIDs else []
+
+        returnValue(rows)
+
+
+    @inlineCallbacks
     def setComponent(self, component, inserting=False):
 
         validateAddressBookComponent(self, self._addressbook, component, inserting)
@@ -399,6 +500,24 @@
 
 
     @inlineCallbacks
+    def _ownerGroupAndAddressBook(self):
+        # find the owning address book
+        ownerGroup = None
+        ownerAddressBook = None
+        if self._addressbook.owned():
+            ownerAddressBook = self._addressbook
+        else:
+            ownerAddressBook = yield self._addressbook.ownerHome().childWithID(self._addressbook._resourceID)
+            if not ownerAddressBook:
+                for addressbook in (yield self._addressbook.ownerHome().children()):
+                    for addressBookObject in (yield addressbook.objectResources()):
+                        if addressBookObject._resourceID == self._addressbook._resourceID:
+                            ownerGroup = addressBookObject
+                            ownerAddressBook = addressbook
+                            break
+        returnValue((ownerGroup, ownerAddressBook))
+
+    @inlineCallbacks
     def updateDatabase(self, component, expand_until=None, reCreate=False,
                        inserting=False):
         """
@@ -437,11 +556,18 @@
         assert inserting or self._kind == kind  # can't change kind. Should be checked in upper layers
         self._kind = kind
 
+        print("updateDatabase:self=%s self._addressbook.=%s self._ownerAddressBookResourceID=%s" % (self, self._addressbook, self._ownerAddressBookResourceID,))
+        print("updateDatabase:self=%s insert=%s, component=%s" % (self, inserting, component))
         if inserting:
+
+            ownerGroup, ownerAddressBook = yield self._ownerGroupAndAddressBook()
+            assert ownerAddressBook
+            self._ownerAddressBookResourceID = ownerAddressBook._resourceID
+
             self._resourceID, self._created, self._modified = (
                 yield self._insertABObject.on(
                     self._txn,
-                    addressbookResourceID=self._addressbook._resourceID,
+                    addressbookResourceID=self._ownerAddressBookResourceID,
                     name=self._name,
                     text=componentText,
                     uid=self._uid,
@@ -449,6 +575,18 @@
                     kind=self._kind,
                     ))[0]
 
+            if ownerGroup:
+                assert False, "updateDatabase() insert for shared group: need to modify shared group vCard text"
+            else:
+                # add row on this address book group table
+                print("updateDatabase1:self=%s Insert(aboMembers.GROUP_ID:%s,  aboMembers.ADDRESSBOOK_ID:%s, aboMembers.MEMBER_ID:%s" % (
+                    self, self._ownerAddressBookResourceID, self._ownerAddressBookResourceID, self._resourceID))
+                yield Insert(
+                    {aboMembers.GROUP_ID: self._ownerAddressBookResourceID,
+                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
+                     aboMembers.MEMBER_ID: self._resourceID, }
+                ).on(self._txn)
+
             # update existing group member tables for this new object
             # delete foreign members table row for this object
             groupIDs = yield Delete(
@@ -459,9 +597,11 @@
 
             # add to member table row by resourceID
             for groupID in groupIDs:
+                print("updateDatabase2:self=%s Insert(aboMembers.GROUP_ID:%s,  aboMembers.ADDRESSBOOK_ID:%s, aboMembers.MEMBER_ID:%s" % (
+                    self, groupID[0], self._ownerAddressBookResourceID, self._resourceID))
                 yield Insert(
-                    {aboMembers.GROUP_ID: groupID,
-                     aboMembers.ADDRESSBOOK_ID: self._addressbook._resourceID,
+                    {aboMembers.GROUP_ID: groupID[0],
+                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
                      aboMembers.MEMBER_ID: self._resourceID, }
                 ).on(self._txn)
 
@@ -475,6 +615,7 @@
 
         if self._kind == _ABO_KIND_GROUP:
 
+            assert self._ownerAddressBookResourceID
             # get member resource ID for each member string, or keep as string
             memberIDs = []
             foreignMemberAddrs = []
@@ -484,7 +625,7 @@
                     memberUID = memberAddr[len("urn:uuid:"):]
                     memberRow = yield Select([abo.RESOURCE_ID],
                                      From=abo,
-                                     Where=((abo.ADDRESSBOOK_RESOURCE_ID == self._addressbook._resourceID)
+                                     Where=((abo.ADDRESSBOOK_RESOURCE_ID == self._ownerAddressBookResourceID)
                                             ).And(abo.VCARD_UID == memberUID)).on(self._txn)
                 if memberRow:
                     memberIDs.append(memberRow[0][0])
@@ -492,9 +633,10 @@
                     foreignMemberAddrs.append(memberAddr)
 
             #get current members
-            currentMemberIDs = yield Select([aboMembers.MEMBER_ID],
+            currentMemberRows = yield Select([aboMembers.MEMBER_ID],
                                  From=aboMembers,
                                  Where=(aboMembers.GROUP_ID == self._resourceID)).on(self._txn)
+            currentMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows]
 
             memberIDsToDelete = set(currentMemberIDs) - set(memberIDs)
             memberIDsToAdd = set(memberIDs) - set(currentMemberIDs)
@@ -506,9 +648,11 @@
                 ).on(self._txn)
 
             for memberIDToAdd in memberIDsToAdd:
+                print("updateDatabase3:self=%s Insert(aboMembers.GROUP_ID:%s,  aboMembers.ADDRESSBOOK_ID:%s, aboMembers.MEMBER_ID:%s" % (
+                    self, self._resourceID, self._ownerAddressBookResourceID, memberIDToAdd))
                 yield Insert(
                     {aboMembers.GROUP_ID: self._resourceID,
-                     aboMembers.ADDRESSBOOK_ID: self._addressbook._resourceID,
+                     aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
                      aboMembers.MEMBER_ID: memberIDToAdd, }
                 ).on(self._txn)
 
@@ -527,9 +671,11 @@
                 ).on(self._txn)
 
             for foreignMemberAddrToAdd in foreignMemberAddrsToAdd:
+                print("updateDatabase4:self=%s Insert(foreignMemberAddrToAdd.GROUP_ID:%s,  foreignMemberAddrToAdd.ADDRESSBOOK_ID:%s, foreignMemberAddrToAdd.MEMBER_ID:%s" % (
+                    self, self._resourceID, self._ownerAddressBookResourceID, foreignMemberAddrToAdd))
                 yield Insert(
                     {aboForeignMembers.GROUP_ID: self._resourceID,
-                     aboForeignMembers.ADDRESSBOOK_ID: self._addressbook._resourceID,
+                     aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
                      aboForeignMembers.MEMBER_ADDRESS: foreignMemberAddrToAdd, }
                 ).on(self._txn)
 
@@ -573,5 +719,14 @@
         return MimeType.fromString("text/vcard; charset=utf-8")
 
 
+    def owned(self):
+        return True
 
+    def ownerHome(self):
+        return self._addressbook.ownerHome()
+
+    def notifyChanged(self):
+        self._addressbook.notifyChanged()
+
+
 AddressBook._objectResourceClass = AddressBookObject

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/test/test_sql.py	2012-11-02 20:50:49 UTC (rev 10001)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/test/test_sql.py	2012-11-03 16:05:43 UTC (rev 10002)
@@ -488,7 +488,7 @@
         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
         aboMembers = schema.ABO_MEMBERS
         memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
-        self.assertEqual(memberRows, [])
+        self.assertEqual(sorted(memberRows), sorted([[adbk._resourceID, personObject._resourceID], [adbk._resourceID, groupObject._resourceID]]))
 
         foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers).on(txn)
         self.assertEqual(foreignMemberRows, [[groupObject._resourceID, "urn:uuid:uid3"]])
@@ -509,7 +509,13 @@
         subgroupObject = yield adbk.createAddressBookObjectWithName("sg.vcf", subgroup)
 
         memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
-        self.assertEqual(memberRows.sort(), [[groupObject._resourceID, subgroupObject._resourceID], [subgroupObject._resourceID, personObject._resourceID]].sort())
+        self.assertEqual(sorted(memberRows), sorted([
+                                                     [groupObject._resourceID, subgroupObject._resourceID],
+                                                     [subgroupObject._resourceID, personObject._resourceID],
+                                                     [adbk._resourceID, personObject._resourceID],
+                                                     [adbk._resourceID, subgroupObject._resourceID],
+                                                     [adbk._resourceID, groupObject._resourceID],
+                                                    ]))
 
         foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers).on(txn)
         self.assertEqual(foreignMemberRows, [])
@@ -517,9 +523,11 @@
 
         yield adbk.removeAddressBookObjectWithName("sg.vcf")
         memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
-        self.assertEqual(memberRows, [])
+        self.assertEqual(sorted(memberRows), sorted([[adbk._resourceID, personObject._resourceID], [adbk._resourceID, groupObject._resourceID]]))
 
-        foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers).on(txn)
+        foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers,
+                                                 #Where=(aboForeignMembers.GROUP_ID == groupObject._resourceID),
+                                                 ).on(txn)
         self.assertEqual(foreignMemberRows, [[groupObject._resourceID, "urn:uuid:uid3"]])
 
         yield home.removeAddressBookWithName("addressbook")

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/file.py	2012-11-02 20:50:49 UTC (rev 10001)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/file.py	2012-11-03 16:05:43 UTC (rev 10002)
@@ -1079,8 +1079,23 @@
                 self._transaction.postCommit(notifier.notify)
             self._transaction.notificationAddedForObject(self)
 
+    @inlineCallbacks
+    def asInvited(self):
+        """
+        Stub for interface-compliance tests.
+        """
+        yield None
+        returnValue([])
 
+    @inlineCallbacks
+    def asShared(self):
+        """
+        Stub for interface-compliance tests.
+        """
+        yield None
+        returnValue([])
 
+
 class CommonObjectResource(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
     """
     @ivar _path: The path of the file on disk

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql.py	2012-11-02 20:50:49 UTC (rev 10001)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql.py	2012-11-03 16:05:43 UTC (rev 10002)
@@ -2036,163 +2036,27 @@
         Maybe notify changed.  (Overridden in NotificationCollection.)
         """
 
-
-
-class CommonHomeChild(LoggingMixIn, FancyEqMixin, _SharedSyncLogic, HomeChildBase):
+class SharingMixIn(object):
     """
-    Common ancestor class of AddressBooks and Calendars.
+        Common class for CommonHomeChild and AddressBookObject
     """
-
-    compareAttributes = (
-        "_name",
-        "_home",
-        "_resourceID",
-    )
-
-    _objectResourceClass = None
-
-    _bindSchema = None
-    _homeSchema = None
-    _homeChildSchema = None
-    _homeChildMetaDataSchema = None
-    _revisionsSchema = None
-    _objectSchema = None
-
-    _bindTable = None
-    _homeChildTable = None
-    _homeChildBindTable = None
-    _revisionsTable = None
-    _revisionsBindTable = None
-    _objectTable = None
-
-
-    def __init__(self, home, name, resourceID, mode, status, message=None, ownerHome=None):
-
-        if home._notifiers:
-            childID = "%s/%s" % (home.uid(), name)
-            notifiers = [notifier.clone(label="collection", id=childID)
-                         for notifier in home._notifiers]
-        else:
-            notifiers = None
-
-        self._home = home
-        self._name = name
-        self._resourceID = resourceID
-        self._bindMode = mode
-        self._bindStatus = status
-        self._bindMessage = message
-        self._ownerHome = home if ownerHome is None else ownerHome
-        self._created = None
-        self._modified = None
-        self._objects = {}
-        self._objectNames = None
-        self._syncTokenRevision = None
-        self._notifiers = notifiers
-        self._index = None  # Derived classes need to set this
-
-
     @classproperty
-    def _childNamesForHomeID(cls): #@NoSelf
-        bind = cls._bindSchema
-        return Select([bind.RESOURCE_NAME], From=bind,
-                      Where=(bind.HOME_RESOURCE_ID ==
-                             Parameter("homeID")).And
-                                (bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
-
-
-    @classmethod
-    def metadataColumns(cls):
+    def _bindInsertQuery(cls, **kw): #@NoSelf
         """
-        Return a list of column name for retrieval of metadata. This allows
-        different child classes to have their own type specific data, but still make use of the
-        common base logic.
+        DAL statement to create a bind entry that connects a collection to its
+        home.
         """
-
-        # Common behavior is to have created and modified
-
-        return (
-            cls._homeChildMetaDataSchema.CREATED,
-            cls._homeChildMetaDataSchema.MODIFIED,
-        )
-
-
-    @classmethod
-    def metadataAttributes(cls):
-        """
-        Return a list of attribute names for retrieval of metadata. This allows
-        different child classes to have their own type specific data, but still make use of the
-        common base logic.
-        """
-
-        # Common behavior is to have created and modified
-
-        return (
-            "_created",
-            "_modified",
-        )
-
-
-    @classmethod
-    @inlineCallbacks
-    def listObjects(cls, home):
-        """
-        Retrieve the names of the children that exist in the given home.
-
-        @return: an iterable of C{str}s.
-        """
-        # FIXME: tests don't cover this as directly as they should.
-        rows = yield cls._childNamesForHomeID.on(
-                home._txn, homeID=home._resourceID)
-        names = [row[0] for row in rows]
-        returnValue(names)
-
-
-    @classproperty
-    def _invitedBindForHomeID(cls): #@NoSelf
         bind = cls._bindSchema
-        return cls._bindFor((bind.HOME_RESOURCE_ID == Parameter("homeID"))
-                            .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED))
+        return Insert({
+            bind.HOME_RESOURCE_ID: Parameter("homeID"),
+            bind.RESOURCE_ID: Parameter("resourceID"),
+            bind.RESOURCE_NAME: Parameter("name"),
+            bind.BIND_MODE: Parameter("mode"),
+            bind.BIND_STATUS: Parameter("bindStatus"),
+            bind.MESSAGE: Parameter("message"),
+        })
 
-
     @classmethod
-    @inlineCallbacks
-    def listInvitedObjects(cls, home):
-        """
-        Retrieve the names of the children with invitations in the given home.
-
-        @return: an iterable of C{str}s.
-        """
-        rows = yield cls._invitedBindForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )
-        names = [row[3] for row in rows]
-        returnValue(names)
-
-
-    @classproperty
-    def _childrenAndMetadataForHomeID(cls): #@NoSelf
-        bind = cls._bindSchema
-        child = cls._homeChildSchema
-        childMetaData = cls._homeChildMetaDataSchema
-
-        columns = [bind.BIND_MODE,
-                   bind.HOME_RESOURCE_ID,
-                   bind.RESOURCE_ID,
-                   bind.RESOURCE_NAME,
-                   bind.BIND_STATUS,
-                   bind.MESSAGE]
-        columns.extend(cls.metadataColumns())
-        return Select(columns,
-                     From=child.join(
-                         bind, child.RESOURCE_ID == bind.RESOURCE_ID,
-                         'left outer').join(
-                         childMetaData, childMetaData.RESOURCE_ID == bind.RESOURCE_ID,
-                         'left outer'),
-                     Where=(bind.HOME_RESOURCE_ID == Parameter("homeID")
-                           ).And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
-
-
-    @classmethod
     def _updateBindColumnsQuery(cls, columnMap): #@NoSelf
         bind = cls._bindSchema
         return Update(columnMap,
@@ -2384,61 +2248,7 @@
         returnValue(resourceName)
 
 
-    def shareMode(self):
-        """
-        @see: L{ICalendar.shareMode}
-        """
-        return self._bindMode
 
-
-    def owned(self):
-        """
-        @see: L{ICalendar.owned}
-        """
-        return self._bindMode == _BIND_MODE_OWN
-
-
-    def shareStatus(self):
-        """
-        @see: L{ICalendar.shareStatus}
-        """
-        return self._bindStatus
-
-
-    def shareMessage(self):
-        """
-        @see: L{ICalendar.shareMessage}
-        """
-        return self._bindMessage
-
-
-    def shareUID(self):
-        """
-        @see: L{ICalendar.shareUID}
-        """
-        return self.name()
-
-
-    @inlineCallbacks
-    def unshare(self, homeType):
-        """
-        Unshares a collection, regardless of which "direction" it was shared.
-
-        @param homeType: a valid store type (ECALENDARTYPE or EADDRESSBOOKTYPE)
-        """
-        if self.owned():
-            # This collection may be shared to others
-            for sharedToHome in [x.viewerHome() for x in (yield self.asShared())]:
-                (yield self.unshareWith(sharedToHome))
-        else:
-            # This collection is shared to me
-            sharerHomeID = (yield self.sharerHomeID())
-            sharerHome = (yield self._txn.homeWithResourceID(homeType,
-                sharerHomeID))
-            sharerCollection = (yield sharerHome.childWithID(self._resourceID))
-            (yield sharerCollection.unshareWith(self._home))
-
-
     @classmethod
     def _bindFor(cls, condition): #@NoSelf
         bind = cls._bindSchema
@@ -2483,7 +2293,7 @@
             self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
         )
 
-        cls = self.__class__ # for ease of grepping...
+        cls = self._home._childClass # for ease of grepping...
         result = []
         for bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage in acceptedRows: #@UnusedVariable
             assert bindStatus == _BIND_STATUS_ACCEPTED
@@ -2526,7 +2336,7 @@
         rows = yield self._invitedBindForResourceID.on(
             self._txn, resourceID=self._resourceID, homeID=self._home._resourceID,
         )
-        cls = self.__class__ # for ease of grepping...
+        cls = self._home._childClass # for ease of grepping...
 
         result = []
         for bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage in rows: #@UnusedVariable
@@ -2540,11 +2350,224 @@
             )
             yield new.initFromStore()
             result.append(new)
+
         returnValue(result)
 
 
+
+
+
+class CommonHomeChild(LoggingMixIn, FancyEqMixin, _SharedSyncLogic, HomeChildBase, SharingMixIn):
+    """
+    Common ancestor class of AddressBooks and Calendars.
+    """
+
+    compareAttributes = (
+        "_name",
+        "_home",
+        "_resourceID",
+    )
+
+    _objectResourceClass = None
+
+    _bindSchema = None
+    _homeSchema = None
+    _homeChildSchema = None
+    _homeChildMetaDataSchema = None
+    _revisionsSchema = None
+    _objectSchema = None
+
+    _bindTable = None
+    _homeChildTable = None
+    _homeChildBindTable = None
+    _revisionsTable = None
+    _revisionsBindTable = None
+    _objectTable = None
+
+
+    def __init__(self, home, name, resourceID, mode, status, message=None, ownerHome=None):
+
+        if home._notifiers:
+            childID = "%s/%s" % (home.uid(), name)
+            notifiers = [notifier.clone(label="collection", id=childID)
+                         for notifier in home._notifiers]
+        else:
+            notifiers = None
+
+        self._home = home
+        self._name = name
+        self._resourceID = resourceID
+        self._bindMode = mode
+        self._bindStatus = status
+        self._bindMessage = message
+        self._ownerHome = home if ownerHome is None else ownerHome
+        self._created = None
+        self._modified = None
+        self._objects = {}
+        self._objectNames = None
+        self._syncTokenRevision = None
+        self._notifiers = notifiers
+        self._index = None  # Derived classes need to set this
+
+
+    @classproperty
+    def _childNamesForHomeID(cls): #@NoSelf
+        bind = cls._bindSchema
+        return Select([bind.RESOURCE_NAME], From=bind,
+                      Where=(bind.HOME_RESOURCE_ID ==
+                             Parameter("homeID")).And
+                                (bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
+
+
     @classmethod
+    def metadataColumns(cls):
+        """
+        Return a list of column name for retrieval of metadata. This allows
+        different child classes to have their own type specific data, but still make use of the
+        common base logic.
+        """
+
+        # Common behavior is to have created and modified
+
+        return (
+            cls._homeChildMetaDataSchema.CREATED,
+            cls._homeChildMetaDataSchema.MODIFIED,
+        )
+
+
+    @classmethod
+    def metadataAttributes(cls):
+        """
+        Return a list of attribute names for retrieval of metadata. This allows
+        different child classes to have their own type specific data, but still make use of the
+        common base logic.
+        """
+
+        # Common behavior is to have created and modified
+
+        return (
+            "_created",
+            "_modified",
+        )
+
+
+    @classmethod
     @inlineCallbacks
+    def listObjects(cls, home):
+        """
+        Retrieve the names of the children that exist in the given home.
+
+        @return: an iterable of C{str}s.
+        """
+        # FIXME: tests don't cover this as directly as they should.
+        rows = yield cls._childNamesForHomeID.on(
+                home._txn, homeID=home._resourceID)
+        names = [row[0] for row in rows]
+        returnValue(names)
+
+
+    @classproperty
+    def _invitedBindForHomeID(cls): #@NoSelf
+        bind = cls._bindSchema
+        return cls._bindFor((bind.HOME_RESOURCE_ID == Parameter("homeID"))
+                            .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED))
+
+
+    @classmethod
+    @inlineCallbacks
+    def listInvitedObjects(cls, home):
+        """
+        Retrieve the names of the children with invitations in the given home.
+
+        @return: an iterable of C{str}s.
+        """
+        rows = yield cls._invitedBindForHomeID.on(
+            home._txn, homeID=home._resourceID
+        )
+        names = [row[3] for row in rows]
+        returnValue(names)
+
+
+    @classproperty
+    def _childrenAndMetadataForHomeID(cls): #@NoSelf
+        bind = cls._bindSchema
+        child = cls._homeChildSchema
+        childMetaData = cls._homeChildMetaDataSchema
+
+        columns = [bind.BIND_MODE,
+                   bind.HOME_RESOURCE_ID,
+                   bind.RESOURCE_ID,
+                   bind.RESOURCE_NAME,
+                   bind.BIND_STATUS,
+                   bind.MESSAGE]
+        columns.extend(cls.metadataColumns())
+        return Select(columns,
+                     From=child.join(
+                         bind, child.RESOURCE_ID == bind.RESOURCE_ID,
+                         'left outer').join(
+                         childMetaData, childMetaData.RESOURCE_ID == bind.RESOURCE_ID,
+                         'left outer'),
+                     Where=(bind.HOME_RESOURCE_ID == Parameter("homeID")
+                           ).And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
+
+
+    def shareMode(self):
+        """
+        @see: L{ICalendar.shareMode}
+        """
+        return self._bindMode
+
+
+    def owned(self):
+        """
+        @see: L{ICalendar.owned}
+        """
+        return self._bindMode == _BIND_MODE_OWN
+
+
+    def shareStatus(self):
+        """
+        @see: L{ICalendar.shareStatus}
+        """
+        return self._bindStatus
+
+
+    def shareMessage(self):
+        """
+        @see: L{ICalendar.shareMessage}
+        """
+        return self._bindMessage
+
+
+    def shareUID(self):
+        """
+        @see: L{ICalendar.shareUID}
+        """
+        return self.name()
+
+
+    @inlineCallbacks
+    def unshare(self, homeType):
+        """
+        Unshares a collection, regardless of which "direction" it was shared.
+
+        @param homeType: a valid store type (ECALENDARTYPE or EADDRESSBOOKTYPE)
+        """
+        if self.owned():
+            # This collection may be shared to others
+            for sharedToHome in [x.viewerHome() for x in (yield self.asShared())]:
+                (yield self.unshareWith(sharedToHome))
+        else:
+            # This collection is shared to me
+            sharerHomeID = (yield self.sharerHomeID())
+            sharerHome = (yield self._txn.homeWithResourceID(homeType,
+                sharerHomeID))
+            sharerCollection = (yield sharerHome.childWithID(self._resourceID))
+            (yield sharerCollection.unshareWith(self._home))
+
+
+    @classmethod
+    @inlineCallbacks
     def loadAllObjects(cls, home):
         """
         Load all L{CommonHomeChild} instances which are children of a given
@@ -2587,8 +2610,7 @@
                 ownerHome = home
             else:
                 #TODO: get all ownerHomeIDs at once
-                ownerHomeID = (yield cls._ownerHomeWithResourceID.on(
-                                home._txn, resourceID=resourceID))[0][0]
+                ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
                 ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
 
             child = cls(
@@ -2622,6 +2644,23 @@
 
     @classmethod
     @inlineCallbacks
+    def ownerHomeID(cls, txn, resourceID):
+
+        ownerHomeRows = yield cls._ownerHomeWithResourceID.on(txn, resourceID=resourceID)
+
+        if not ownerHomeRows:
+            # no owner, so shared item must be group
+            abo = schema.ADDRESSBOOK_OBJECT
+            groupAddressBookRows = yield Select([abo.ADDRESSBOOK_RESOURCE_ID],
+                                         From=abo,
+                                         Where=(abo.RESOURCE_ID == Parameter("resourceID"))).on(txn, resourceID=resourceID)
+            groupAddressBookID = groupAddressBookRows[0][0]
+            ownerHomeRows = yield cls._ownerHomeWithResourceID.on(txn, resourceID=groupAddressBookID)
+
+        returnValue(ownerHomeRows[0][0])
+
+    @classmethod
+    @inlineCallbacks
     def invitedObjectWithName(cls, home, name):
         """
         Retrieve the child with the given C{name} contained in the given
@@ -2643,12 +2682,10 @@
 
         bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
 
-        #TODO:  combine with _invitedBindForNameAndHomeID and sort results
-        ownerHomeID = (yield cls._ownerHomeWithResourceID.on(
-                        home._txn, resourceID=resourceID))[0][0]
+        ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
         ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
 
-        child = cls(
+        child = ownerHome._childClass(
             home=home,
             name=resourceName, resourceID=resourceID,
             mode=bindMode, status=bindStatus,
@@ -2700,8 +2737,7 @@
                 if bindMode == _BIND_MODE_OWN:
                     ownerHomeID = homeID
                 else:
-                    ownerHomeID = (yield cls._ownerHomeWithResourceID.on(
-                                    home._txn, resourceID=resourceID))[0][0]
+                    ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
                 rows[0].append(ownerHomeID)
 
             if rows and queryCacher:
@@ -2762,8 +2798,7 @@
         if bindMode == _BIND_MODE_OWN:
             ownerHome = home
         else:
-            ownerHomeID = (yield cls._ownerHomeWithResourceID.on(
-                            home._txn, resourceID=resourceID))[0][0]
+            ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
             ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
         child = cls(
             home=home,
@@ -2775,22 +2810,6 @@
         returnValue(child)
 
 
-    @classproperty
-    def _bindInsertQuery(cls, **kw): #@NoSelf
-        """
-        DAL statement to create a bind entry that connects a collection to its
-        home.
-        """
-        bind = cls._bindSchema
-        return Insert({
-            bind.HOME_RESOURCE_ID: Parameter("homeID"),
-            bind.RESOURCE_ID: Parameter("resourceID"),
-            bind.RESOURCE_NAME: Parameter("name"),
-            bind.BIND_MODE: Parameter("mode"),
-            bind.BIND_STATUS: Parameter("bindStatus"),
-            bind.MESSAGE: Parameter("message"),
-        })
-
     @classmethod
     @inlineCallbacks
     def create(cls, home, name):
@@ -3016,9 +3035,8 @@
             # we already know who the owner is.
             returnValue(self._home._resourceID)
         else:
-            rid = (yield self._ownerHomeWithResourceID.on(
-                self._txn, resourceID=self._resourceID))[0][0]
-            returnValue(rid)
+            ownerHomeID = yield self.ownerHomeID(self._txn, self._resourceID)
+            returnValue(ownerHomeID)
 
 
     @inlineCallbacks
@@ -3047,6 +3065,7 @@
         returnValue(results)
 
 
+    # TODO: move to Calendar
     @classproperty
     def _objectResourceNamesQuery(cls): #@NoSelf
         """
@@ -3057,6 +3076,7 @@
                       Where=obj.PARENT_RESOURCE_ID == Parameter('resourceID'))
 
 
+    # TODO: Make abstract here, and and move to Calendar
     @inlineCallbacks
     def listObjectResources(self):
         if self._objectNames is None:
@@ -3066,6 +3086,7 @@
         returnValue(self._objectNames)
 
 
+    # TODO: move to Calendar
     @classproperty
     def _objectCountQuery(cls): #@NoSelf
         """
@@ -3076,6 +3097,7 @@
                       Where=obj.PARENT_RESOURCE_ID == Parameter('resourceID'))
 
 
+    # TODO: Make abstract here, and and move to Calendar
     @inlineCallbacks
     def countObjectResources(self):
         if self._objectNames is None:
@@ -3476,13 +3498,21 @@
         self._locked = False
 
 
+    #TODO: move to CalendarObject
     @classproperty
-    def _allColumnsWithParent(cls): #@NoSelf
+    def _allColumnsWithParentQuery(cls): #@NoSelf
         obj = cls._objectSchema
         return Select(cls._allColumns, From=obj,
                       Where=obj.PARENT_RESOURCE_ID == Parameter("parentID"))
 
+    # TODO: Make abstract here, and and move to CalendarObject
+    @classmethod
+    @inlineCallbacks
+    def _allColumnsWithParent(cls, parent):
+        returnValue((yield cls._allColumnsWithParentQuery.on(
+            parent._txn, parentID=parent._resourceID)))
 
+
     @classmethod
     @inlineCallbacks
     def loadAllObjects(cls, parent):
@@ -3496,8 +3526,7 @@
         results = []
 
         # Load from the main table first
-        dataRows = yield cls._allColumnsWithParent.on(
-            parent._txn, parentID=parent._resourceID)
+        dataRows = yield cls._allColumnsWithParent(parent)
 
         if dataRows:
             # Get property stores for all these child resources (if any found)
@@ -3525,14 +3554,6 @@
 
 
     @classmethod
-    def _allColumnsWithParentAndNames(cls, names): #@NoSelf
-        obj = cls._objectSchema
-        return Select(cls._allColumns, From=obj,
-                      Where=(obj.PARENT_RESOURCE_ID == Parameter("parentID")).And(
-                          obj.RESOURCE_NAME.In(Parameter("names", len(names)))))
-
-
-    @classmethod
     @inlineCallbacks
     def loadAllObjectsWithNames(cls, parent, names):
         """
@@ -3548,8 +3569,24 @@
         returnValue(results)
 
 
+    #TODO: move to CalendarObject
     @classmethod
+    def _allColumnsWithParentAndNamesQuery(cls, names): #@NoSelf
+        obj = cls._objectSchema
+        return Select(cls._allColumns, From=obj,
+                      Where=(obj.PARENT_RESOURCE_ID == Parameter("parentID")).And(
+                          obj.RESOURCE_NAME.In(Parameter("names", len(names)))))
+
+
+    # TODO: Make abstract here, and and move to CalendarObject
+    @classmethod
     @inlineCallbacks
+    def _allColumnsWithParentAndNames(cls, parent, names):
+        returnValue((yield cls._allColumnsWithParentAndNamesQuery(names).on(
+            parent._txn, parentID=parent._resourceID, names=names)))
+
+    @classmethod
+    @inlineCallbacks
     def _loadAllObjectsWithNames(cls, parent, names):
         """
         Load all child objects with the specified names. This must create the
@@ -3566,8 +3603,7 @@
         results = []
 
         # Load from the main table first
-        dataRows = yield cls._allColumnsWithParentAndNames(names).on(
-            parent._txn, parentID=parent._resourceID, names=names)
+        dataRows = yield cls._allColumnsWithParentAndNames(parent, names)
 
         if dataRows:
             # Get property stores for all these child resources
@@ -3754,6 +3790,9 @@
     def _txn(self):
         return self._parentCollection._txn
 
+    @property
+    def _home(self):
+        return self._parentCollection._home
 
     def transaction(self):
         return self._parentCollection._txn
@@ -4256,7 +4295,6 @@
                       Where=(obj.NOTIFICATION_HOME_RESOURCE_ID == Parameter(
                           "homeID")))
 
-
     @classmethod
     @inlineCallbacks
     def loadAllObjects(cls, parent):

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql_schema/current.sql	2012-11-02 20:50:49 UTC (rev 10001)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql_schema/current.sql	2012-11-03 16:05:43 UTC (rev 10002)
@@ -341,7 +341,7 @@
 
   create table ADDRESSBOOK_OBJECT (
   RESOURCE_ID             integer      primary key default nextval('RESOURCE_ID_SEQ'),	-- implicit index
-  ADDRESSBOOK_RESOURCE_ID integer      references ADDRESSBOOK_OBJECT on delete cascade,	-- ### could add non-null, but ab woul reference itself
+  ADDRESSBOOK_RESOURCE_ID integer      references ADDRESSBOOK_OBJECT on delete cascade,	-- ### could add non-null, but ab would reference itself
   RESOURCE_NAME           varchar(255) not null,
   VCARD_TEXT              text         not null,
   VCARD_UID               varchar(255) not null,
@@ -373,7 +373,7 @@
 ---------------------------------
 
 create table ABO_MEMBERS (
-    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT,						-- AddressBook Object's (kind=='group') RESOURCE_ID
+    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_OBJECT on delete cascade,	-- only used on insert and whole address book delete
     MEMBER_ID             integer      not null references ADDRESSBOOK_OBJECT,						-- member AddressBook Object's RESOURCE_ID
     primary key(GROUP_ID, MEMBER_ID) -- implicit index
@@ -384,7 +384,7 @@
 ------------------------------------------
 
 create table ABO_FOREIGN_MEMBERS (
-    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT,						-- AddressBook Object's (kind=='group') RESOURCE_ID
+    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_OBJECT on delete cascade,	-- only used on insert and whole address book delete
     MEMBER_ADDRESS  	  varchar(255) not null, 													-- member AddressBook Object's 'calendar' address
     primary key(GROUP_ID, MEMBER_ADDRESS) -- implicit index

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/upgrade/test/test_migrate.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/upgrade/test/test_migrate.py	2012-11-02 20:50:49 UTC (rev 10001)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/upgrade/test/test_migrate.py	2012-11-03 16:05:43 UTC (rev 10002)
@@ -404,8 +404,9 @@
         """
         Create an upgrade service.
         """
+        #FIXME: not parallel
         return UpgradeToDatabaseService(
             self.fileStore, self.sqlStore, self.stubService,
-            parallel=2, spawner=StubSpawner()
+            parallel=0, spawner=StubSpawner()
         )
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121103/76f3b41e/attachment-0001.html>


More information about the calendarserver-changes mailing list