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

source_changes at macosforge.org source_changes at macosforge.org
Mon Feb 11 17:07:09 PST 2013


Revision: 10695
          http://trac.calendarserver.org//changeset/10695
Author:   gaya at apple.com
Date:     2013-02-11 17:07:09 -0800 (Mon, 11 Feb 2013)
Log Message:
-----------
checkpoint.  CalDAV's CardDAV/sharing-addressbooks.xml now work.

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/sharing.py
    CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/storebridge.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/iaddressbookstore.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql.py
    CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql_schema/current.sql

Modified: CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/sharing.py	2013-02-12 00:37:11 UTC (rev 10694)
+++ CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/sharing.py	2013-02-12 01:07:09 UTC (rev 10695)
@@ -38,7 +38,7 @@
 from twisted.internet.defer import succeed, inlineCallbacks, DeferredList, \
     returnValue
 
-from twistedcaldav import customxml, caldavxml, carddavxml
+from twistedcaldav import customxml, caldavxml
 from twistedcaldav.config import config
 from twistedcaldav.customxml import calendarserver_namespace
 from twistedcaldav.directory.wiki import WikiDirectoryService, getWikiAccess
@@ -515,12 +515,12 @@
         elif self.isAddressBookCollection() or self.isGroup():
             shareeHome = yield self._newStoreObject._txn.addressbookHomeWithUID(shareeUID, create=True)
 
-        shareeHomeChild = yield self._newStoreObject.shareWith(shareeHome,
+        shareeStoreObject = yield self._newStoreObject.shareWith(shareeHome,
                                                     mode=invitationAccessToBindModeMap[access],
                                                     status=_BIND_STATUS_INVITED,
                                                     message=summary)
 
-        invitation = Invitation(shareeHomeChild)
+        invitation = Invitation(shareeStoreObject)
         returnValue(invitation)
 
 
@@ -529,7 +529,7 @@
         mode = None if access is None else invitationAccessToBindModeMap[access]
         status = None if state is None else invitationStateToBindStatusMap[state]
 
-        yield self._newStoreObject.updateShare(invitation._shareeHomeChild, mode=mode, status=status, message=summary)
+        yield self._newStoreObject.updateShare(invitation._shareeStoreObject, mode=mode, status=status, message=summary)
         assert not access or access == invitation.access(), "access=%s != invitation.access()=%s" % (access, invitation.access())
         assert not state or state == invitation.state(), "state=%s != invitation.state()=%s" % (state, invitation.state())
         assert not summary or summary == invitation.summary(), "summary=%s != invitation.summary()=%s" % (summary, invitation.summary())
@@ -635,9 +635,11 @@
         if sharee:
             if self.isCalendarCollection():
                 shareeHomeResource = yield sharee.calendarHome(request)
+                displayName = yield shareeHomeResource.removeShareByUID(request, invitation.uid())
             elif self.isAddressBookCollection() or self.isGroup():
                 shareeHomeResource = yield sharee.addressBookHome(request)
-            displayName = (yield shareeHomeResource.removeShareByUID(request, invitation.uid()))
+                yield shareeHomeResource.removeShareByUID(request, invitation.uid())
+                displayName = None
             # If current user state is accepted then we send an invite with the new state, otherwise
             # we cancel any existing invites for the user
             if invitation and invitation.state() != "ACCEPTED":
@@ -646,7 +648,7 @@
                 yield self.sendInviteNotification(invitation, request, displayName=displayName, notificationState="DELETED")
 
         # Direct shares for  with valid sharee principal will already be deleted
-        yield self._newStoreObject.unshareWith(invitation._shareeHomeChild.viewerHome())
+        yield self._newStoreObject.unshareWith(invitation._shareeStoreObject.viewerHome())
 
         returnValue(True)
 
@@ -771,6 +773,7 @@
                     "%s: %s" % (", ".join(error_text), inviteset,),
                 ))
 
+
         def _handleInviteRemove(inviteremove):
             userid = None
             access = []
@@ -914,6 +917,7 @@
         customxml.InviteReply: _xmlHandleInviteReply,
     }
 
+
     def isGroup(self):
         try:
             return self._newStoreObject._kind == _ABO_KIND_GROUP
@@ -937,6 +941,7 @@
         ("text", "xml") : xmlRequestHandler,
     }
 
+
 invitationAccessMapToXML = {
     "read-only"           : customxml.ReadAccess,
     "read-write"          : customxml.ReadWriteAccess,
@@ -966,32 +971,33 @@
     }
 invitationAccessFromBindModeMap = dict((v, k) for k, v in invitationAccessToBindModeMap.iteritems())
 
+
 class Invitation(object):
     """
         Invitation is a read-only wrapper for CommonHomeChild, that uses terms similar LegacyInvite sharing.py code base.
     """
-    def __init__(self, shareeHomeChild):
-        self._shareeHomeChild = shareeHomeChild
+    def __init__(self, shareeStoreObject):
+        self._shareeStoreObject = shareeStoreObject
 
 
     def uid(self):
-        return self._shareeHomeChild.shareUID()
+        return self._shareeStoreObject.shareUID()
 
 
     def shareeUID(self):
-        return self._shareeHomeChild.viewerHome().uid()
+        return self._shareeStoreObject.viewerHome().uid()
 
 
     def access(self):
-        return invitationAccessFromBindModeMap.get(self._shareeHomeChild.shareMode())
+        return invitationAccessFromBindModeMap.get(self._shareeStoreObject.shareMode())
 
 
     def state(self):
-        return invitationStateFromBindStatusMap.get(self._shareeHomeChild.shareStatus())
+        return invitationStateFromBindStatusMap.get(self._shareeStoreObject.shareStatus())
 
 
     def summary(self):
-        return self._shareeHomeChild.shareMessage()
+        return self._shareeStoreObject.shareMessage()
 
 
 
@@ -1003,19 +1009,19 @@
 
     @inlineCallbacks
     def provisionShare(self, child, request=None):
-        share = yield self._shareForHomeChild(child._newStoreObject, request)
+        share = yield self._shareForStoreObject(child._newStoreObject, request)
         if share:
             child.setShare(share)
 
 
     @inlineCallbacks
-    def _shareForHomeChild(self, child, request=None):
-        # Try to find a matching share
-        if not child or child.owned():
+    def _shareForStoreObject(self, storeObject, request=None):
+        # Find a matching share
+        if not storeObject or storeObject.owned():
             returnValue(None)
 
         # get the shared object's URL
-        sharer = self.principalForUID(child.ownerHome().uid())
+        sharer = self.principalForUID(storeObject.ownerHome().uid())
 
         if not request:
             # FIXEME:  Fake up a request that can be used to get the sharer home resource
@@ -1029,17 +1035,18 @@
         elif self._newStoreHome._homeType == EADDRESSBOOKTYPE:
             sharerHomeCollection = yield sharer.addressBookHome(request)
 
-        sharerHomeChild = yield child.ownerHome().childWithID(child._resourceID)
+        sharerHomeChild = yield storeObject.ownerHome().childWithID(storeObject._resourceID)
         if sharerHomeChild:
+            assert sharerHomeChild != storeObject
             url = joinURL(sharerHomeCollection.url(), sharerHomeChild.name())
-            share = Share(shareeHomeChild=child, sharerHomeChildOrGroup=sharerHomeChild, url=url)
-        else:
-            for sharerHomeChild in (yield child.ownerHome().children()):
+            share = Share(shareeStoreObject=storeObject, sharerStoreObject=sharerHomeChild, url=url)
+        elif self._newStoreHome._homeType == EADDRESSBOOKTYPE:
+            for sharerHomeChild in (yield storeObject.ownerHome().children()):
                 if sharerHomeChild.owned():
-                    sharedGroup = yield sharerHomeChild.objectResourceWithID(child._resourceID)
+                    sharedGroup = yield sharerHomeChild.objectResourceWithID(storeObject._resourceID)
                     if sharedGroup:
                         url = joinURL(sharerHomeCollection.url(), sharerHomeChild.name(), sharedGroup.name())
-                        share = Share(shareeHomeChild=child, sharerHomeChildOrGroup=sharedGroup, url=url)
+                        share = Share(shareeStoreObject=storeObject, sharerStoreObject=sharedGroup, url=url)
                         break
 
         returnValue(share)
@@ -1048,17 +1055,16 @@
     @inlineCallbacks
     def _shareForUID(self, shareUID, request):
 
-        # since child.shareUID() == child.name() for indirect shares
-        child = yield self._newStoreHome.childWithBindName(shareUID)
+        child = yield self._newStoreHome.objectWithBindName(shareUID)
         if child:
-            share = yield self._shareForHomeChild(child, request)
+            share = yield self._shareForStoreObject(child, request)
             if share and share.uid() == shareUID:
                 returnValue(share)
 
         # find direct shares
         children = yield self._newStoreHome.children()
         for child in children:
-            share = yield self._shareForHomeChild(child, request)
+            share = yield self._shareForStoreObject(child, request)
             if share and share.uid() == shareUID:
                 returnValue(share)
 
@@ -1077,9 +1083,9 @@
             share = oldShare
         else:
             sharedResource = yield request.locateResource(hostUrl)
-            shareeHomeChild = yield self._newStoreHome.childWithBindName(inviteUID)
+            shareeStoreObject = yield self._newStoreHome.objectWithBindName(inviteUID)
 
-            share = Share(shareeHomeChild=shareeHomeChild, sharerHomeChildOrGroup=sharedResource._newStoreObject, url=hostUrl)
+            share = Share(shareeStoreObject=shareeStoreObject, sharerStoreObject=sharedResource._newStoreObject, url=hostUrl)
 
         response = yield self._acceptShare(request, not oldShare, share, displayname)
         returnValue(response)
@@ -1094,12 +1100,12 @@
             share = oldShare
         else:
             sharedCollection = yield request.locateResource(hostUrl)
-            shareeHomeChild = yield sharedCollection._newStoreObject.shareWith(shareeHome=self._newStoreHome,
+            shareeStoreObject = yield sharedCollection._newStoreObject.shareWith(shareeHome=self._newStoreHome,
                                                     mode=_BIND_MODE_DIRECT,
                                                     status=_BIND_STATUS_ACCEPTED,
                                                     message=displayname)
 
-            share = Share(shareeHomeChild=shareeHomeChild, sharerHomeChildOrGroup=sharedCollection._newStoreObject, url=hostUrl)
+            share = Share(shareeStoreObject=shareeStoreObject, sharerStoreObject=sharedCollection._newStoreObject, url=hostUrl)
 
         response = yield self._acceptShare(request, not oldShare, share, displayname)
         returnValue(response)
@@ -1110,52 +1116,63 @@
 
         # Get shared collection in non-share mode first
         sharedResource = yield request.locateResource(share.url())
+        sharee = self.principalForUID(share.shareeUID())
 
-        # For a direct share we will copy any calendar-color over using the owners view
-        color = None
-        if share.direct() and isNewShare and sharedResource.isCalendarCollection():
-            try:
-                color = (yield sharedResource.readProperty(customxml.CalendarColor, request))
-            except HTTPError:
-                pass
-
-        sharee = self.principalForUID(share.shareeUID())
         if sharedResource.isCalendarCollection():
             shareeHomeResource = yield sharee.calendarHome(request)
-        elif sharedResource.isAddressBookCollection() or sharedResource.isGroup():
-            shareeHomeResource = yield sharee.addressBookHome(request)
-        shareeURL = joinURL(shareeHomeResource.url(), share.name())
-        shareeCollection = yield request.locateResource(shareeURL)
-        shareeCollection.setShare(share)
+            shareeCalenderURL = joinURL(shareeHomeResource.url(), share.name())
+            shareeCalender = yield request.locateResource(shareeCalenderURL)
+            shareeCalender.setShare(share)
 
-        #FIXME: addcresourceType to dead properties for share groups -
-        #        it's already there for shared address book and calendars
-        if isNewShare and sharedResource.isGroup():
-            shareeCollection.writeDeadProperty(carddavxml.ResourceType.addressbook)
+            # For calendars only, per-user displayname and color
+            if displayname:
+                yield shareeCalender.writeProperty(element.DisplayName.fromString(displayname), request)
 
-        # For calendars only, per-user displayname and color
-        if displayname and shareeCollection.isCalendarCollection():
-            yield shareeCollection.writeProperty(element.DisplayName.fromString(displayname), request)
+            if isNewShare:
+                # For a direct share we will copy any calendar-color over using the owners view
+                if share.direct():
+                    try:
+                        color = yield sharedResource.readProperty(customxml.CalendarColor, request)
+                    except HTTPError:
+                        color = None
+                    if color:
+                        yield shareeCalender.writeProperty(customxml.CalendarColor.fromString(color), request)
 
-        if color:
-            yield shareeCollection.writeProperty(customxml.CalendarColor.fromString(color), request)
+                # Calendars always start out transparent and with empty default alarms
+                yield shareeCalender.writeProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Transparent()), request)
+                yield shareeCalender.writeProperty(caldavxml.DefaultAlarmVEventDateTime.fromString(""), request)
+                yield shareeCalender.writeProperty(caldavxml.DefaultAlarmVEventDate.fromString(""), request)
+                yield shareeCalender.writeProperty(caldavxml.DefaultAlarmVToDoDateTime.fromString(""), request)
+                yield shareeCalender.writeProperty(caldavxml.DefaultAlarmVToDoDate.fromString(""), request)
 
-        # Calendars always start out transparent and with empty default alarms
-        if isNewShare and shareeCollection.isCalendarCollection():
-            yield shareeCollection.writeProperty(caldavxml.ScheduleCalendarTransp(caldavxml.Transparent()), request)
-            yield shareeCollection.writeProperty(caldavxml.DefaultAlarmVEventDateTime.fromString(""), request)
-            yield shareeCollection.writeProperty(caldavxml.DefaultAlarmVEventDate.fromString(""), request)
-            yield shareeCollection.writeProperty(caldavxml.DefaultAlarmVToDoDateTime.fromString(""), request)
-            yield shareeCollection.writeProperty(caldavxml.DefaultAlarmVToDoDate.fromString(""), request)
+        elif sharedResource.isAddressBookCollection():
+            shareeHomeResource = yield sharee.addressBookHome(request)
+            shareeAddressBookURL = joinURL(shareeHomeResource.url(), share.sharerUID())
+            shareeAddressBook = yield request.locateResource(shareeAddressBookURL)
+            shareeAddressBook.setShare(share)
 
+        elif sharedResource.isGroup():
+            shareeHomeResource = yield sharee.addressBookHome(request)
+            shareeGroupURL = joinURL(shareeHomeResource.url(), share.sharerUID(), share.name())
+            shareeGroup = yield request.locateResource(shareeGroupURL)
+            shareeGroup.setShare(share)
+
         # Notify client of changes
         yield self.notifyChanged()
 
+        #FIXME: shouldn't sharedAsURLbe the same as shareeCollectionURL ?
+        if sharedResource.isCalendarCollection():
+            sharedAsURL = joinURL(self.url(), share.name())
+        elif sharedResource.isAddressBookCollection():
+            sharedAsURL = joinURL(self.url(), share.sharerUID())
+        elif sharedResource.isGroup():
+            sharedAsURL = joinURL(self.url(), share.sharerUID(), share.name())
+
         # Return the URL of the shared collection
         returnValue(XMLResponse(
             code=responsecode.OK,
             element=customxml.SharedAs(
-                element.HRef.fromString(joinURL(self.url(), share.name()))
+                element.HRef.fromString(sharedAsURL)
             )
         ))
 
@@ -1196,7 +1213,6 @@
         Remove a shared collection but do not send a decline back. Return the
         current display name of the shared collection.
         """
-
         shareURL = joinURL(self.url(), share.name())
         shared = (yield request.locateResource(shareURL))
         displayname = shared.displayName()
@@ -1210,9 +1226,9 @@
                 inbox.processFreeBusyCalendar(shareURL, False)
 
         if share.direct():
-            yield share._sharerHomeChildOrGroup.unshareWith(share._shareeHomeChild.viewerHome())
+            yield share._sharerStoreObject.unshareWith(share._shareeStoreObject.viewerHome())
         else:
-            yield share._sharerHomeChildOrGroup.updateShare(share._shareeHomeChild, status=_BIND_STATUS_DECLINED)
+            yield share._sharerStoreObject.updateShare(share._shareeStoreObject, status=_BIND_STATUS_DECLINED)
 
         returnValue(displayname)
 
@@ -1222,9 +1238,7 @@
 
         # Remove it if it is in the DB
         yield self.removeShareByUID(request, inviteUID)
-
         yield self._changeShare(request, "DECLINED", hostUrl, inviteUID)
-
         returnValue(Response(code=responsecode.NO_CONTENT))
 
 
@@ -1233,7 +1247,6 @@
         """
         Accept or decline an invite to a shared collection.
         """
-
         # Change state in sharer invite
         ownerPrincipal = (yield self.ownerPrincipal(request))
         ownerPrincipalUID = ownerPrincipal.principalUID()
@@ -1298,7 +1311,9 @@
 
 
     def _handleInviteReply(self, request, invitereplydoc):
-        """ Handle a user accepting or declining a sharing invite """
+        """
+        Handle a user accepting or declining a sharing invite
+        """
         hostUrl = None
         accepted = None
         summary = None
@@ -1329,13 +1344,12 @@
             return self.declineShare(request, hostUrl, replytoUID)
 
 
-
 class Share(object):
 
-    def __init__(self, sharerHomeChildOrGroup, shareeHomeChild, url):
-        self._shareeHomeChild = shareeHomeChild
-        self._sharerHomeChildOrGroup = sharerHomeChildOrGroup
-        self._sharedResourceURL = url
+    def __init__(self, sharerStoreObject, shareeStoreObject, url):
+        self._shareeStoreObject = shareeStoreObject
+        self._sharerStoreObject = sharerStoreObject
+        self._sharerResourceURL = url
 
 
     @classmethod
@@ -1345,27 +1359,31 @@
 
     def uid(self):
         # Move to CommonHomeChild shareUID?
-        if self._shareeHomeChild.shareMode() == _BIND_MODE_DIRECT:
-            return self.directUID(shareeHome=self._shareeHomeChild.viewerHome(), sharerHomeChild=self._sharerHomeChildOrGroup,)
+        if self._shareeStoreObject.shareMode() == _BIND_MODE_DIRECT:
+            return self.directUID(shareeHome=self._shareeStoreObject.viewerHome(), sharerHomeChild=self._sharerStoreObject,)
         else:
-            return self._shareeHomeChild.shareUID()
+            return self._shareeStoreObject.shareUID()
 
 
     def direct(self):
-        return self._shareeHomeChild.shareMode() == _BIND_MODE_DIRECT
+        return self._shareeStoreObject.shareMode() == _BIND_MODE_DIRECT
 
 
     def url(self):
-        return self._sharedResourceURL
+        return self._sharerResourceURL
 
 
     def name(self):
-        return self._shareeHomeChild.name()
+        return self._shareeStoreObject.name()
 
 
     def summary(self):
-        return self._shareeHomeChild.shareMessage()
+        return self._shareeStoreObject.shareMessage()
 
 
     def shareeUID(self):
-        return self._shareeHomeChild.viewerHome().uid()
+        return self._shareeStoreObject.viewerHome().uid()
+
+
+    def sharerUID(self):
+        return self._shareeStoreObject.ownerHome().uid()

Modified: CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/storebridge.py	2013-02-12 00:37:11 UTC (rev 10694)
+++ CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/storebridge.py	2013-02-12 01:07:09 UTC (rev 10695)
@@ -73,7 +73,7 @@
 from twext.web2.filter.location import addLocation
 
 from txdav.carddav.iaddressbookstore import GroupWithUnsharedAddressNotAllowedError, \
-    DeleteOfShadowGroupNotAllowedError
+    DeleteOfGroupForSharedAddressBookNotAllowedError
 
 """
 Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
@@ -3010,7 +3010,7 @@
         try:
             returnValue((yield super(AddressBookObjectResource, self).http_DELETE(request)))
 
-        except DeleteOfShadowGroupNotAllowedError:
+        except DeleteOfGroupForSharedAddressBookNotAllowedError:
             raise HTTPError(StatusResponse(
                 FORBIDDEN,
                 "Sharee cannot delete group vcard shadowing shared address book",)

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py	2013-02-12 00:37:11 UTC (rev 10694)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py	2013-02-12 01:07:09 UTC (rev 10695)
@@ -44,7 +44,7 @@
 from txdav.carddav.datastore.util import validateAddressBookComponent
 from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook, \
     IAddressBookObject, GroupWithUnsharedAddressNotAllowedError, \
-    DeleteOfShadowGroupNotAllowedError
+    DeleteOfGroupForSharedAddressBookNotAllowedError
 from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
     CommonObjectResource, EADDRESSBOOKTYPE, SharingMixIn
 from txdav.common.datastore.sql_legacy import PostgresLegacyABIndexEmulator
@@ -56,7 +56,8 @@
     ADDRESSBOOK_OBJECT_REVISIONS_AND_BIND_TABLE, \
     _ABO_KIND_PERSON, _ABO_KIND_GROUP, _ABO_KIND_RESOURCE, \
     _ABO_KIND_LOCATION, schema, \
-    _BIND_MODE_OWN, _BIND_MODE_WRITE, _BIND_STATUS_ACCEPTED
+    _BIND_MODE_OWN, _BIND_MODE_WRITE, _BIND_STATUS_ACCEPTED, \
+    _BIND_STATUS_DECLINED, _BIND_STATUS_INVITED
 from txdav.xml.rfc2518 import ResourceType
 
 from zope.interface.declarations import implements
@@ -102,7 +103,7 @@
     removeAddressBookWithName = CommonHome.removeChildWithName
 
 
-    def childWithBindName(self, name):
+    def objectWithBindName(self, name):
         """
         Retrieve the child with the given C{name} contained in this
         home.
@@ -112,6 +113,7 @@
         """
         return self._childClass.objectWithBindName(self, name)
 
+
     @inlineCallbacks
     def remove(self):
         ah = schema.ADDRESSBOOK_HOME
@@ -158,13 +160,16 @@
         kwds = {"homeResourceID" : self._resourceID}
         yield Delete(
             From=bind,
-            Where=(bind.HOME_RESOURCE_ID == Parameter("homeResourceID")).And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED)
+            Where=(bind.HOME_RESOURCE_ID == Parameter("homeResourceID")
+                   ).And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED)
         ).on(self._txn, **kwds)
 
+
     @classmethod
     def addressbookName(cls):
         return "addressbook"
 
+
     @inlineCallbacks
     def addressbook(self):
         returnValue((yield self.addressbookWithName(self.addressbookName())))
@@ -225,9 +230,11 @@
     removeAddressBookObjectWithUID = CommonHomeChild.removeObjectResourceWithUID
     addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
 
+
     def shareeABName(self):
         return self._home.uid()
 
+
     def initPropertyStore(self, props):
         # Setup peruser special properties
         props.setSpecialProperties(
@@ -247,6 +254,16 @@
         return MimeType.fromString("text/vcard; charset=utf-8")
 
 
+    @classmethod
+    @inlineCallbacks
+    def create(cls, home, name):
+
+        if name != home.addressbookName():
+            raise NotImplementedError()
+
+        returnValue((yield super(AddressBook, cls).create(home, name)))
+
+
     def unshare(self):
         """
         Unshares a collection, regardless of which "direction" it was shared.
@@ -258,8 +275,8 @@
     def listObjectResources(self):
 
         result = yield super(AddressBook, self).listObjectResources()
-        if not self.owned():
-            groupForSharedAB = yield self._groupForSharedABName()
+        if not self.owned() and not self.fullyShared():
+            groupForSharedAB = yield self._groupForEntireAB_Name()
             if not groupForSharedAB in result:
                 result.append(groupForSharedAB)
         returnValue(result)
@@ -273,44 +290,23 @@
             count = rows[0][0]
 
             #add in group for shared address book
-            if not self.owned():
+            if not self.owned() and not self.fullyShared():
                 count += 1
             returnValue(count)
         returnValue(len(self._objectNames))
 
 
     @inlineCallbacks
-    def _ownerGroupAndAddressBook(self):
-        """ 
-        Find the owner shared group and owner address book.  owner shared group may be None 
-        """
-        ownerGroup = None
-        if self.owned():
-            yield None
-            ownerAddressBook = self
-        else:
-            ownerAddressBook = yield self.ownerAddressBookHome().childWithID(self._resourceID)
-            if not ownerAddressBook:
-                for addressbook in (yield self.ownerAddressBookHome().addressbooks()):
-                    ownerGroup = yield addressbook.objectResourceWithID(self._resourceID)
-                    if ownerGroup:
-                        ownerAddressBook = addressbook
-                        break
-
-        returnValue((ownerGroup, ownerAddressBook))
-
-
-    @inlineCallbacks
-    def ownerGroup(self):
-        if not hasattr(self, "_ownerGroup"):
-            self._ownerGroup, self._ownerAddressBook = yield self._ownerGroupAndAddressBook()
-        returnValue(self._ownerGroup)
-
-
-    @inlineCallbacks
     def ownerAddressBook(self):
         if not hasattr(self, "_ownerAddressBook"):
-            self._ownerGroup, self._ownerAddressBook = yield self._ownerGroupAndAddressBook()
+            if self.owned():
+                yield None
+                self._ownerAddressBook = self
+            else:
+                ownerHome = yield self.ownerAddressBookHome()
+                assert self._home != ownerHome
+                self._ownerAddressBook = yield ownerHome.addressbook()
+                assert self._ownerAddressBook != self
         returnValue(self._ownerAddressBook)
 
 
@@ -325,13 +321,13 @@
 
 
     @inlineCallbacks
-    def _groupForSharedABRow(self): #@NoSelf
+    def _groupForEntireAB_Row(self): #@NoSelf
 
         returnValue([
             self._resourceID, # obj.ADDRESSBOOK_RESOURCE_ID,
             self._resourceID, # obj.RESOURCE_ID,
-            (yield self._groupForSharedABName()), # obj.RESOURCE_NAME, shared name is UID and thus avoids collisions
-            (yield self._groupForSharedABUID()), # obj.UID, shared name is uuid
+            (yield self._groupForEntireAB_Name()), # obj.RESOURCE_NAME, shared name is UID and thus avoids collisions
+            (yield self._groupForEntireAB_UID()), # obj.UID, shared name is uuid
             _ABO_KIND_GROUP, # obj.KIND,
             "1", # obj.MD5, unused
             "1", # Len(obj.TEXT), unused
@@ -341,38 +337,31 @@
 
 
     @inlineCallbacks
-    def _groupForSharedABName(self):
-        ownerGroup = yield self.ownerGroup()
-        if ownerGroup:
-            returnValue(ownerGroup.name())
-        else:
-            returnValue((yield self.ownerAddressBook()).name() + ".vcf")
+    def _groupForEntireAB_Name(self):
+        returnValue((yield self.ownerAddressBook()).name() + ".vcf")
 
 
     @inlineCallbacks
-    def _groupForSharedABUID(self):
+    def _groupForEntireAB_UID(self):
         yield None
         returnValue(self.name())
 
+
     @inlineCallbacks
-    def _groupForSharedABComponent(self):
+    def _groupForEntireAB_Component(self):
 
-        ownerGroup = yield self.ownerGroup()
-        if ownerGroup:
-            returnValue((yield ownerGroup.component()))
-        else:
-            n = (yield self.ownerAddressBook()).shareeABName()
-            fn = n
-            uid = self.name()
+        n = (yield self.ownerAddressBook()).shareeABName()
+        fn = n
+        uid = self.name()
 
-#            it would be nice to use the owner.displayName() full name here
-#            uid = (yield self.ownerAddressBook()).ownerHome().uid()
-#            owner = yield self.principalForUID(uid)
-#            n = owner.name()
-#            fn = owner.displayName()
+        #  it would be nice to use the owner.displayName() full name here
+        #      uid = (yield self.ownerAddressBook()).ownerHome().uid()
+        #      owner = yield self.principalForUID(uid)
+        #      n = owner.name()
+        #      fn = owner.displayName()
 
-            component = VCard.fromString(
-                """BEGIN:VCARD
+        component = VCard.fromString(
+            """BEGIN:VCARD
 VERSION:3.0
 PRODID:%s
 UID:%s
@@ -381,24 +370,25 @@
 X-ADDRESSBOOKSERVER-KIND:group
 END:VCARD
 """.replace("\n", "\r\n") % (vCardProductID, uid, n, fn,)
-            )
+        )
 
-            # then get member UIDs
-            abo = schema.ADDRESSBOOK_OBJECT
-            memberUIDRows = yield self._abObjectColumnsWithAddressBookResourceID(
-                             [abo.VCARD_UID]).on(
-                                self._txn, addressbookResourceID=self._resourceID)
-            memberUIDs = [memberUIDRow[0] for memberUIDRow in memberUIDRows]
+        # then get member UIDs
+        abo = schema.ADDRESSBOOK_OBJECT
+        memberUIDRows = yield self._abObjectColumnsWithAddressBookResourceID(
+                         [abo.VCARD_UID]).on(
+                            self._txn, addressbookResourceID=self._resourceID)
+        memberUIDs = [memberUIDRow[0] for memberUIDRow in memberUIDRows]
 
-            # add prefix to get property string
-            memberAddresses = ["urn:uuid:" + memberUID for memberUID in memberUIDs]
+        # add prefix to get property string
+        memberAddresses = ["urn:uuid:" + memberUID for memberUID in memberUIDs]
 
-            # now add the properties to the component
-            for memberAddress in sorted(memberAddresses):
-                component.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
+        # now add the properties to the component
+        for memberAddress in sorted(memberAddresses):
+            component.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
 
-            returnValue(component)
+        returnValue(component)
 
+
     @classproperty
     def _metadataByIDQuery(cls): #@NoSelf
         """
@@ -446,7 +436,6 @@
         returnValue(result)
 
 
-
     @classmethod
     @inlineCallbacks
     def loadAllObjects(cls, home):
@@ -462,30 +451,30 @@
         # Load from the main table first
         dataRows = yield cls._childrenAndMetadataForHomeID.on(home._txn, homeID=home._resourceID)
 
-
-        # TODO: Simplify.  Try to do one pass only.
-        # get sharedHomeIDs
-        sharedHomeIDToDataRowMap = {}
+        # get ownerHomeIDs
+        ownerHomeIDToDataRowMap = {}
         for dataRow in dataRows:
-            bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = dataRow[:6] #@UnusedVariable
-            if bindStatus != _BIND_MODE_OWN:
+            bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = dataRow[:6] #@UnusedVariable
+            if bindStatus == _BIND_MODE_OWN:
+                ownerHomeIDToDataRowMap[home._resourceID] = dataRow
+            else:
                 ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
-                sharedHomeIDToDataRowMap[ownerHomeID] = dataRow
+                ownerHomeIDToDataRowMap[ownerHomeID] = dataRow
 
+
         # now get group rows:
         groupBindRows = yield AddressBookObject._childrenAndMetadataForHomeID.on(home._txn, homeID=home._resourceID)
         for groupBindRow in groupBindRows:
-            bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = groupBindRow[:6] #@UnusedVariable
-            ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
-            if ownerHomeID in sharedHomeIDToDataRowMap:
-                if bindMode == _BIND_MODE_WRITE:
-                    sharedHomeIDToDataRowMap[ownerHomeID][0] |= _BIND_MODE_WRITE # or together bind modes
-            else:
-                groupBindRow[3] = None # resourceName or bindName
+            bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = groupBindRow[:6] #@UnusedVariable        
+            ownerAddressBookIDRows = yield AddressBookObject._parentIDForObjectID.on(home._txn, objectID=resourceID)
+            ownerHomeID = yield cls.ownerHomeID(home._txn, ownerAddressBookIDRows[0][0])
+            if ownerHomeID not in ownerHomeIDToDataRowMap:
+                groupBindRow[0] = _BIND_MODE_WRITE
+                groupBindRow[3] = None # bindName
                 groupBindRow[5] = None # bindMessage
-                sharedHomeIDToDataRowMap[ownerHomeID] = groupBindRow
+                ownerHomeIDToDataRowMap[ownerHomeID] = groupBindRow
 
-        if dataRows:
+        if ownerHomeIDToDataRowMap:
 
             # Get property stores for all these child resources (if any found)
             propertyStores = (yield PropertyStore.forMultipleResources(
@@ -494,70 +483,50 @@
                 home._resourceID
             ))
 
-            revisions = (yield cls._revisionsForHomeID.on(home._txn, homeID=home._resourceID))
+            revisions = yield cls._revisionsForHomeID.on(home._txn, homeID=home._resourceID)
             revisions = dict(revisions)
 
-        # Create the actual objects merging in properties
-        for items in dataRows:
-            bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = items[:6] #@UnusedVariable
-            metadata = items[6:]
+            # Create the actual objects merging in properties
+            for ownerHomeID, dataRow in ownerHomeIDToDataRowMap.iteritems():
+                bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = dataRow[:6] #@UnusedVariable
+                metadata = dataRow[6:]
 
-            if bindStatus == _BIND_MODE_OWN:
-                child = cls(
-                    home=home,
-                    name=resourceName, resourceID=resourceID,
-                    mode=bindMode, status=bindStatus,
-                    message=bindMessage,
-                )
-            else:
-                ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
-                ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
-                ownerAddressBook = yield ownerHome.addressbook()
+                if bindStatus == _BIND_MODE_OWN:
+                    child = cls(
+                        home=home,
+                        name=bindName, resourceID=resourceID,
+                        mode=bindMode, status=bindStatus,
+                        message=bindMessage,
+                    )
+                else:
+                    ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
+                    ownerAddressBook = yield ownerHome.addressbook()
 
-                child = cls(
-                    home=home,
-                    name=ownerAddressBook.shareeABName(), resourceID=ownerAddressBook._resourceID,
-                    mode=bindMode, status=bindStatus,
-                    message=bindMessage, ownerHome=ownerHome,
-                    bindName=resourceName
-                )
+                    child = cls(
+                        home=home,
+                        name=ownerAddressBook.shareeABName(), resourceID=ownerAddressBook._resourceID,
+                        mode=bindMode, status=bindStatus,
+                        message=bindMessage, ownerHome=ownerHome,
+                        bindName=bindName
+                    )
 
-            for attr, value in zip(cls.metadataAttributes(), metadata):
-                setattr(child, attr, value)
-            child._syncTokenRevision = revisions[resourceID]
-            propstore = propertyStores.get(resourceID, None)
+                for attr, value in zip(cls.metadataAttributes(), metadata):
+                    setattr(child, attr, value)
+                child._syncTokenRevision = revisions[resourceID]
+                propstore = propertyStores.get(resourceID, None)
 
-            # We have to re-adjust the property store object to account for possible shared
-            # collections as previously we loaded them all as if they were owned
-            if bindStatus != _BIND_MODE_OWN:
-                propstore._setDefaultUserUID(ownerHome.uid())
-            yield child._loadPropertyStore(propstore)
-            results.append(child)
+                # We have to re-adjust the property store object to account for possible shared
+                # collections as previously we loaded them all as if they were owned
+                if bindStatus != _BIND_MODE_OWN:
+                    propstore._setDefaultUserUID(ownerHome.uid())
+                yield child._loadPropertyStore(propstore)
+                results.append(child)
+
         returnValue(results)
 
 
-
     @classmethod
-    def _columnsWithParentIDQuery(cls, columns): #@NoSelf
-        obj = cls._objectSchema
-        return Select(columns, From=obj,
-                      Where=obj.PARENT_RESOURCE_ID == Parameter("parentID"))
-
-    @classmethod
     @inlineCallbacks
-    def _columnsWithParent(cls, columns, parent):
-        returnValue((yield cls._columnsWithParentIDQuery(columns).on(
-            parent._txn, parentID=parent._resourceID)))
-
-    @classmethod
-    @inlineCallbacks
-    def _resourceIDsWithParent(cls, parent):
-        obj = cls._objectSchema
-        returnValue((yield  cls._columnsWithParent([obj.RESOURCE_ID], parent)))
-
-
-    @classmethod
-    @inlineCallbacks
     def objectWithName(cls, home, name):
         # replaces objectWithName()
         """
@@ -588,7 +557,7 @@
         if rows is None:
 
             # name must be a home uid
-            ownerHome = yield home._txn.addressbookHomeWithUID(name, create=True)
+            ownerHome = yield home._txn.addressbookHomeWithUID(name)
             if ownerHome:
                 # see if address book resource id in bind table
                 ownerAddressBook = yield ownerHome.addressbook()
@@ -596,28 +565,22 @@
                 sharedABBindRows = yield cls._bindForResourceIDAndHomeID.on(
                     home._txn, resourceID=ownerAddressBook._resourceID, homeID=home._resourceID)
                 if sharedABBindRows:
-                    bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = sharedABBindRows[0] #@UnusedVariable
+                    bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = sharedABBindRows[0] #@UnusedVariable
                     if bindStatus == _BIND_STATUS_ACCEPTED:
                         sharedABBindRows[0].append(ownerHome._resourceID)
                         rows = [sharedABBindRows[0]]
 
-                # get group binds
-                #FIXME:  use join
-                ownerABObjectIDRows = yield cls._resourceIDsWithParent(ownerAddressBook)
-                if ownerABObjectIDRows:
-                    ownerABObjectIDs = [ownerABObjectIDRow[0] for ownerABObjectIDRow in ownerABObjectIDRows]
-                    groupBindRows = yield AddressBookObject._bindForGroupIDsAndHomeID(ownerABObjectIDs).on(home._txn, groupIDs=ownerABObjectIDs, homeID=home._resourceID)
-                    for groupBindRow in groupBindRows:
-                        bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = groupBindRow #@UnusedVariable
-                        if bindStatus == _BIND_STATUS_ACCEPTED:
-                            if rows:
-                                if bindMode == _BIND_MODE_WRITE:
-                                    rows[0][0] |= _BIND_MODE_WRITE
-                            else:
-                                groupBindRow[3] = None # resourceName or bindName
-                                groupBindRow[5] = None # bindMessage
-                                groupBindRow.append(ownerHome._resourceID)
-                                rows = [groupBindRow]
+                if not rows:
+                    groupBindRows = yield AddressBookObject._acceptedBindWithHomeIDAndAddressBookID.on(
+                            home._txn, homeID=home._resourceID, addressbookID=ownerAddressBook._resourceID
+                    )
+                    if groupBindRows:
+                        groupBindRow = groupBindRows[0]
+                        groupBindRow[0] = _BIND_MODE_WRITE
+                        groupBindRow[3] = None # bindName
+                        groupBindRow[5] = None # bindMessage
+                        groupBindRow.append(ownerHome._resourceID)
+                        rows = [groupBindRow]
 
             if rows and queryCacher:
                 # Cache the result
@@ -626,7 +589,7 @@
         if not rows:
             returnValue(None)
 
-        bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage, ownerHomeID = rows[0] #@UnusedVariable
+        bindMode, homeID, resourceID, bindName, bindStatus, bindMessage, ownerHomeID = rows[0] #@UnusedVariable
         ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
         ownerAddressBook = yield ownerHome.addressbook()
         child = cls(
@@ -634,7 +597,7 @@
                 name=ownerAddressBook.shareeABName(), resourceID=ownerAddressBook._resourceID,
                 mode=bindMode, status=bindStatus,
                 message=bindMessage, ownerHome=ownerHome,
-                bindName=resourceName,
+                bindName=bindName,
             )
         yield child.initFromStore()
         returnValue(child)
@@ -654,23 +617,25 @@
         @return: an L{CommonHomeChild} or C{None} if no such child
             exists.
         """
-        if bindName == home.addressbookName():
-            returnValue((yield cls.objectWithName(home, bindName)))
-        else:
-            rows = yield cls._childBindForNameAndHomeID.on(home._txn, name=bindName, homeID=home._resourceID)
-            if not rows:
-                rows = yield AddressBookObject._childBindForNameAndHomeID.on(home._txn, name=bindName, homeID=home._resourceID)
-            if rows:
-                bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
+        bindRows = yield cls._acceptedBindForNameAndHomeID.on(home._txn, name=bindName, homeID=home._resourceID)
+        if bindRows:
+            bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = bindRows[0] #@UnusedVariable
+            #TODO: use childWithName, since it is cached by querycacher
+            returnValue((yield home.childWithID(resourceID)))
 
-                ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
-                ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
-                ownerAddressBook = yield ownerHome.addressbook()
-                #returnValue((yield cls.objectWithName(home, ownerAddressBook.shareeABName())))
-                returnValue((yield cls.objectWithID(home, ownerAddressBook._resourceID)))
+        groupBindRows = yield AddressBookObject._acceptedBindForNameAndHomeID.on(home._txn, name=bindName, homeID=home._resourceID)
+        if groupBindRows:
+            bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = groupBindRows[0] #@UnusedVariable
+            ownerAddressBookIDRows = yield AddressBookObject._parentIDForObjectID.on(home._txn, objectID=resourceID)
+            #TODO: use childWithName, since it is cached by querycacher
+            sharedAB = yield cls.objectWithID(home, ownerAddressBookIDRows[0][0])
+            #sharedAB = yield home.childWithID(ownerAddressBookIDRows[0][0])
+            result = yield sharedAB.objectResourceWithID(resourceID)
+            returnValue(result)
 
         returnValue(None)
 
+
     @classmethod
     @inlineCallbacks
     def objectWithID(cls, home, resourceID):
@@ -687,45 +652,43 @@
             home._txn, resourceID=resourceID, homeID=home._resourceID)
 
         if rows:
-            bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = rows[0] #@UnusedVariable
             if bindMode == _BIND_MODE_OWN:
                 child = cls(
                     home=home,
-                    name=resourceName, resourceID=resourceID,
+                    name=bindName, resourceID=resourceID,
                     mode=bindMode, status=bindStatus,
-                    message=bindMessage, ownerHome=home,
+                    message=bindMessage,
                 )
                 yield child.initFromStore()
                 returnValue(child)
+        else:
 
+            groupBindRows = yield AddressBookObject._bindWithHomeIDAndAddressBookID.on(
+                    home._txn, homeID=home._resourceID, addressbookID=resourceID
+            )
+            if groupBindRows:
+                #bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = groupBindRows[0] #@UnusedVariable
+                groupBindRow = groupBindRows[0]
+                groupBindRow[0] = _BIND_MODE_WRITE
+                groupBindRow[3] = None # bindName
+                groupBindRow[5] = None # bindMessage
+                rows = [groupBindRow]
+
+        if not rows:
+            returnValue(None)
+
         ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
         ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
         ownerAddressBook = yield ownerHome.addressbook()
 
-        ownerABObjectIDRows = yield cls._resourceIDsWithParent(ownerAddressBook)
-        if ownerABObjectIDRows:
-            ownerABObjectIDs = [ownerABObjectIDRow[0] for ownerABObjectIDRow in ownerABObjectIDRows]
-            groupBindRows = yield AddressBookObject._bindForGroupIDsAndHomeID(ownerABObjectIDs).on(home._txn, groupIDs=ownerABObjectIDs, homeID=home._resourceID)
-            for groupBindRow in groupBindRows:
-                bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = groupBindRow #@UnusedVariable
-                if rows:
-                    if bindMode == _BIND_MODE_WRITE:
-                        rows[0][0] |= _BIND_MODE_WRITE
-                else:
-                    groupBindRow[3] = None # resourceName or bindName
-                    groupBindRow[5] = None # bindMessage
-                    rows = [groupBindRow]
-
-        if not rows:
-            returnValue(None)
-
-        bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
+        bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = rows[0] #@UnusedVariable
         child = cls(
             home=home,
             name=ownerAddressBook.shareeABName(), resourceID=ownerAddressBook._resourceID,
             mode=bindMode, status=bindStatus,
             message=bindMessage, ownerHome=ownerHome,
-            bindName=resourceName
+            bindName=bindName
         )
         yield child.initFromStore()
         returnValue(child)
@@ -764,39 +727,124 @@
         )
 
 
-    '''
+    def shareUID(self):
+        """
+        @see: L{ICalendar.shareUID}
+        """
+        return self._bindName
+
+
+    def fullyShared(self):
+        return bool(self._bindName)
+
+
     @classmethod
     @inlineCallbacks
-    def invitedObjectWithName(cls, home, name):
+    def listObjects(cls, home):
         """
-        Retrieve the child with the given C{name} contained in the given
-        C{home}.
+        Retrieve the names of the children with invitations in the given home.
 
-        @param home: a L{CommonHome}.
+        @return: an iterable of C{str}s.
+        """
+        names = set()
+        rows = yield cls._acceptedBindForHomeID.on(
+            home._txn, homeID=home._resourceID
+        )
+        rows.extend((yield AddressBookObject._acceptedBindForHomeID.on(
+            home._txn, homeID=home._resourceID
+        )))
+        for bindMode, homeID, resourceID, bindName, bindStatus, bindMessage in rows: #@UnusedVariable
+            if bindMode == _BIND_MODE_OWN:
+                addressbook = yield home.addressbook()
+                names |= set([addressbook.name()])
+            else:
+                ownerHome = yield home._txn.homeWithResourceID(home._homeType, homeID)
+                ownerAddressBook = yield ownerHome.addressbook()
+                names |= set([ownerAddressBook.shareeABName()])
+        returnValue(tuple(names))
 
-        @param name: a string; the name of the L{CommonHomeChild} to retrieve.
 
-        @param owned: a boolean - whether or not to get a shared child
-        @return: an L{CommonHomeChild} or C{None} if no such child
-            exists.
+    @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))),
+                      )
 
-        result = yield super(AddressBook, cls).invitedObjectWithName(home, name)
 
-        if not result:
-            result = yield AddressBookObject.invitedObjectWithName(home, name)
-
-        returnValue(result)
-    '''
-
-    def shareUID(self):
+    @classmethod
+    @inlineCallbacks
+    def _objectIDsInExpandedGroupIDs(cls, txn, groupIDs, includeGroupIDs=True):
         """
-        @see: L{ICalendar.shareUID}
+        Get all AddressBookObject resource IDs contains in the given shared groups with the given groupIDs
         """
-        return self._bindName
+        objectIDs = set(groupIDs) if includeGroupIDs else set()
+        examinedIDs = set()
+        remainingIDs = set(groupIDs)
+        while remainingIDs:
+            memberRows = yield cls._memberIDsWithGroupIDsQuery(remainingIDs).on(txn, groupIDs=remainingIDs)
+            objectIDs |= set([memberRow[0] for memberRow in memberRows])
+            examinedIDs |= remainingIDs
+            remainingIDs = objectIDs - examinedIDs
 
+        returnValue(tuple(objectIDs))
 
+
     @inlineCallbacks
+    def invitedGroupIDs(self):
+        if self.owned():
+            returnValue([])
+        else:
+            groupBindRows = yield AddressBookObject._invitedBindWithHomeIDAndAddressBookID.on(
+                    self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
+            )
+            #for bindMode, homeID, resourceID, bindName, bindStatus, bindMessage in groupBindRows: #@UnusedVariable
+            returnValue([groupBindRow[2] for groupBindRow in groupBindRows])
+
+
+    @inlineCallbacks
+    def acceptedGroupIDs(self):
+        if self.owned():
+            returnValue([])
+        else:
+            groupBindRows = yield AddressBookObject._acceptedBindWithHomeIDAndAddressBookID.on(
+                    self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
+            )
+            #for bindMode, homeID, resourceID, bindName, bindStatus, bindMessage in groupBindRows: #@UnusedVariable
+        returnValue([groupBindRow[2] for groupBindRow in groupBindRows])
+
+
+    @inlineCallbacks
+    def writeableAcceptedGroupIDs(self):
+        if self.owned():
+            returnValue([])
+        else:
+            groupBindRows = yield AddressBookObject._acceptedBindWithHomeIDAndAddressBookID.on(
+                    self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
+            )
+            readwriteGroupIDs = []
+            readonlyGroupIDs = []
+            for bindMode, homeID, resourceID, bindName, bindStatus, bindMessage in groupBindRows: #@UnusedVariable
+                if bindMode == _BIND_MODE_WRITE:
+                    readwriteGroupIDs.append(resourceID)
+                else:
+                    readonlyGroupIDs.append(resourceID)
+
+            if readonlyGroupIDs and readwriteGroupIDs:
+                # expand read-write groups and remove any subgroups from read-only group list
+                allWriteableIDs = yield self._addressbook._objectIDsInExpandedGroupIDs(self._txn, readwriteGroupIDs)
+                adjustedReadOnlyGroupIDs = set(readonlyGroupIDs) - set(allWriteableIDs)
+                adjustedReadWriteGroupIDs = set(readwriteGroupIDs) | (set(readonlyGroupIDs) - adjustedReadOnlyGroupIDs)
+            else:
+                #adjustedReadOnlyGroupIDs = readonlyGroupIDs
+                adjustedReadWriteGroupIDs = readwriteGroupIDs
+            returnValue(tuple(adjustedReadWriteGroupIDs))
+
+
+    @inlineCallbacks
     def asShared(self):
         """
         Retrieve all the versions of this L{CommonHomeChild} as it is shared to
@@ -811,27 +859,38 @@
         if not self.owned():
             returnValue([])
 
-        # get all accepted binds
-        acceptedRows = yield self._sharedBindForResourceID.on(
+        # get all accepted shared binds
+        rows = yield self._sharedBindForResourceID.on(
             self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
         )
+        homeIDToBindRowMap = dict([(row[1], row) for row in rows])
 
+        groupBindRows = yield AddressBookObject._acceptedBindWithAddressBookID.on(
+                self._txn, addressbookID=self._resourceID
+        )
+        for groupBindRow in groupBindRows:
+            bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = groupBindRow #@UnusedVariable
+            if homeID not in homeIDToBindRowMap:
+                groupBindRow[0] = _BIND_MODE_WRITE
+                groupBindRow[3] = None # bindName
+                groupBindRow[5] = None # bindMessage
+                homeIDToBindRowMap[homeID] = groupBindRow
+
+        result = []
         cls = self._home._childClass # for ease of grepping...
-        result = []
-        for bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage in acceptedRows: #@UnusedVariable
-            assert bindStatus == _BIND_STATUS_ACCEPTED
-            # TODO: this could all be issued in parallel; no need to serialize
-            # the loop.
+        for bindMode, homeID, resourceID, bindName, bindStatus, bindMessage in homeIDToBindRowMap.values(): #@UnusedVariable
+
             home = yield self._txn.homeWithResourceID(self._home._homeType, homeID)
             new = cls(
                 home=home,
                 name=self.shareeABName(), resourceID=self._resourceID,
                 mode=bindMode, status=bindStatus,
                 message=bindMessage, ownerHome=self._home,
-                bindName=resourceName
+                bindName=bindName
             )
             yield new.initFromStore()
             result.append(new)
+
         returnValue(result)
 
 
@@ -850,22 +909,35 @@
         if not self.owned():
             returnValue([])
 
+        # get all accepted shared binds
         rows = yield self._invitedBindForResourceID.on(
-            self._txn, resourceID=self._resourceID, homeID=self._home._resourceID,
+            self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
         )
+        homeIDToBindRowMap = dict([(row[1], row) for row in rows])
+
+        groupBindRows = yield AddressBookObject._invitedBindWithAddressBookID.on(
+                self._txn, addressbookID=self._resourceID
+        )
+        for groupBindRow in groupBindRows:
+            bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = groupBindRow #@UnusedVariable
+            if homeID not in homeIDToBindRowMap:
+                groupBindRow[0] = _BIND_MODE_WRITE
+                groupBindRow[3] = None # bindName
+                groupBindRow[5] = None # bindMessage
+                homeIDToBindRowMap[homeID] = groupBindRow
+                break
+
+        result = []
         cls = self._home._childClass # for ease of grepping...
+        for bindMode, homeID, resourceID, bindName, bindStatus, bindMessage in homeIDToBindRowMap.values(): #@UnusedVariable
 
-        result = []
-        for bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage in rows: #@UnusedVariable
-            # TODO: this could all be issued in parallel; no need to serialize
-            # the loop.
             home = yield self._txn.homeWithResourceID(self._home._homeType, homeID)
             new = cls(
                 home=home,
                 name=self.shareeABName(), resourceID=self._resourceID,
                 mode=bindMode, status=bindStatus,
                 message=bindMessage, ownerHome=self._home,
-                bindName=resourceName
+                bindName=bindName
             )
             yield new.initFromStore()
             result.append(new)
@@ -873,55 +945,49 @@
         returnValue(result)
 
 
-    @classmethod
     @inlineCallbacks
-    def listObjects(cls, home):
+    def unshareWith(self, shareeHome):
         """
-        Retrieve the names of the children with invitations in the given home.
+        Remove the shared version of this (owned) L{CommonHomeChild} from the
+        referenced L{CommonHome}.
 
-        @return: an iterable of C{str}s.
-        """
-        names = set()
-        rows = yield cls._childBindForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )
-        rows.extend((yield AddressBookObject._childBindForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )))
-        for bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage in rows: #@UnusedVariable
-            if bindMode == _BIND_MODE_OWN:
-                addressbook = yield home.addressbook()
-                names |= set([addressbook.name()])
-            else:
-                ownerHome = yield home._txn.homeWithResourceID(home._homeType, homeID)
-                ownerAddressBook = ownerHome.addressbook()
-                names |= set([ownerAddressBook.shareeeABName()])
-        returnValue(tuple(names))
+        @see: L{CommonHomeChild.shareWith}
 
+        @param shareeHome: The home with which this L{CommonHomeChild} was
+            previously shared.
 
-    @classmethod
-    @inlineCallbacks
-    def listInvitedObjects(cls, home):
+        @return: a L{Deferred} which will fire with the previously-used name.
         """
-        Retrieve the names of the children with invitations in the given home.
+        sharedAddressBook = yield shareeHome.addressbookWithName(self.shareeABName())
+        if not sharedAddressBook:
+            returnValue(None)
 
-        @return: an iterable of C{str}s.
-        """
-        names = set()
-        rows = yield cls._invitedBindForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )
-        rows.extend((yield AddressBookObject._invitedBindForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )))
-        if rows:
-            for bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage in rows: #@UnusedVariable
-                ownerHome = yield home._txn.homeWithResourceID(home._homeType, homeID)
-                ownerAddressBook = ownerHome.addressbook()
-                names |= set([ownerAddressBook.shareeeABName()])
-        returnValue(tuple(names))
+        sharedRemoval = False
+        if sharedAddressBook.fullyShared():
+            groupIDs = yield sharedAddressBook.acceptedGroupIDs()
+            sharedRemoval = not bool(len(groupIDs))
+            if sharedRemoval:
+                sharedAddressBook._deletedSyncToken(sharedRemoval=True)
 
+        queryCacher = self._txn._queryCacher
+        if queryCacher:
+            cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self.shareeABName())
+            queryCacher.invalidateAfterCommit(self._txn, cacheKey)
 
+        rows = yield self._deleteBindWithResourceIDAndHomeID.on(self._txn, resourceID=self._resourceID,
+             homeID=shareeHome._resourceID)
+
+        resourceName = None
+        if rows and sharedRemoval:
+            resourceName = self.shareeABName()
+            shareeHome._children.pop(resourceName, None)
+
+        # Must send notification to ensure cache invalidation occurs
+        yield self.notifyChanged()
+
+        returnValue(resourceName)
+
+
 class AddressBookObject(CommonObjectResource, SharingMixIn):
 
     implements(IAddressBookObject)
@@ -967,35 +1033,32 @@
     @inlineCallbacks
     def remove(self):
 
-        if self._addressbook.owned():
+        if self.owned():
             if self._kind == _ABO_KIND_GROUP: # optimization
                 # need to invalidate queryCacher of sharee's home
                 queryCacher = self._txn._queryCacher
                 if queryCacher:
-                    for shareeAddressBook in (yield self.asShared()):
+                    for shareeAddressBook in (yield self._addressbook.asShared()):
                         cacheKey = queryCacher.keyForObjectWithName(shareeAddressBook._home._resourceID, shareeAddressBook._name)
                         yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
         else:
             # sharee cannot delete group representing shared address book
             if self._resourceID == self._addressbook._resourceID:
-                raise DeleteOfShadowGroupNotAllowedError
+                raise DeleteOfGroupForSharedAddressBookNotAllowedError
 
-
         aboMembers = schema.ABO_MEMBERS
         aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
 
-        ownerGroup = yield self._addressbook.ownerGroup()
-        if ownerGroup:
+        if not self.owned() and not self._addressbook.fullyShared():
             # convert delete in sharee shared group address book to remove of memberships
             # that make this object visible to the sharee
 
-            objectIDs = yield ownerGroup._allGroupObjectIDs()
-            assert self._ownerAddressBookResourceID not in objectIDs, "self._ownerAddressBookResourceID=%s not in objectIDs=%s" % (self._ownerAddressBookResourceID, objectIDs,)
+            writeableAcceptedGroupIDs = yield self._addressbook.writeableAcceptedGroupIDs()
+            if writeableAcceptedGroupIDs:
+                objectsIDs = yield self._addressbook._objectIDsInExpandedGroupIDs(self._txn, writeableAcceptedGroupIDs)
+                yield self._deleteMembersWithMemberIDAndGroupIDsQuery(self._resourceID, objectsIDs).on(
+                    self._txn, groupIDs=objectsIDs)
 
-            if objectIDs:
-                yield self._deleteMembersWithMemberIDAndGroupIDsQuery(self._resourceID, objectIDs).on(
-                    self._txn, groupIDs=objectIDs)
-
             ownerAddressBook = yield self._addressbook.ownerAddressBook()
             yield self._changeAddressBookRevision(ownerAddressBook)
 
@@ -1022,8 +1085,9 @@
             self._ownerAddressBookResourceID = None
             self._component = None
 
+
     @classmethod
-    def _allWithResourceIDAnd(cls, resourceIDs, column, paramName):
+    def _allColumnsWithResourceIDsAnd(cls, resourceIDs, column, paramName):
         """
         DAL query for all columns where PARENT_RESOURCE_ID matches a parentID
         parameter and a given instance column matches a given parameter name.
@@ -1037,17 +1101,17 @@
 
 
     @classmethod
-    def _allWithResourceIDAndName(cls, resourceIDs): #@NoSelf
-        return cls._allWithResourceIDAnd(resourceIDs, cls._objectSchema.RESOURCE_NAME, "name")
+    def _allColumnsWithResourceIDsAndName(cls, resourceIDs): #@NoSelf
+        return cls._allColumnsWithResourceIDsAnd(resourceIDs, cls._objectSchema.RESOURCE_NAME, "name")
 
 
     @classmethod
-    def _allWithResourceIDAndUID(cls, resourceIDs): #@NoSelf
-        return cls._allWithResourceIDAnd(resourceIDs, cls._objectSchema.UID, "uid")
+    def _allColumnsWithResourceIDsAndUID(cls, resourceIDs): #@NoSelf
+        return cls._allColumnsWithResourceIDsAnd(resourceIDs, cls._objectSchema.UID, "uid")
 
 
     @classproperty
-    def _allWithResourceID(cls): #@NoSelf
+    def _allColumnsWithResourceID(cls): #@NoSelf
         obj = cls._objectSchema
         return Select(
             cls._allColumns, From=obj,
@@ -1064,48 +1128,47 @@
 
         @return: L{self} if object exists in the DB, else C{None}
         """
-        ownerGroup = yield self._addressbook.ownerGroup()
-        if ownerGroup:
-
-            objectIDs = yield ownerGroup._allGroupObjectIDs()
+        rows = None
+        if self.owned() or self._addressbook.fullyShared(): # owned or fully shared
             if self._name:
-                rows = (yield self._allWithResourceIDAndName(objectIDs).on(
+                rows = yield self._allColumnsWithParentAndName.on(
                     self._txn, name=self._name,
-                    resourceIDs=objectIDs,)) if objectIDs else []
-            elif self._uid:
-                rows = (yield self._allWithResourceIDAndUID(objectIDs).on(
-                    self._txn, uid=self._uid,
-                    resourceIDs=objectIDs,)) if objectIDs else []
-            elif self._resourceID:
-                rows = (yield self._allWithResourceID.on(
-                    self._txn, resourceID=self._resourceID,)) if (self._resourceID in objectIDs) else []
-
-        else:
-            if self._name:
-                rows = yield self._allWithParentAndName.on(
-                    self._txn, name=self._name,
                     parentID=self._parentCollection._resourceID)
             elif self._uid:
-                rows = yield self._allWithParentAndUID.on(
+                rows = yield self._allColumnsWithParentAndUID.on(
                     self._txn, uid=self._uid,
                     parentID=self._parentCollection._resourceID)
             elif self._resourceID:
-                rows = yield self._allWithParentAndID.on(
+                rows = yield self._allColumnsWithParentAndID.on(
                     self._txn, resourceID=self._resourceID,
                     parentID=self._parentCollection._resourceID)
 
-
-        # get special group vCard
-        if not rows and not self._addressbook.owned():
+            if not rows and self._addressbook.fullyShared(): # perhaps add special group
+                if self._name:
+                    if self._name == (yield self._addressbook._groupForEntireAB_Name()):
+                        rows = [(yield self._addressbook._groupForEntireAB_Row())]
+                elif self._uid:
+                    if self._uid == (yield self._addressbook._groupForEntireAB_UID()):
+                        rows = [(yield self._addressbook._groupForEntireAB_Row())]
+                elif self._resourceID:
+                    if self._resourceID == self._addressbook._resourceID:
+                        rows = [(yield self._addressbook._groupForEntireAB_Row())]
+        else:
+            acceptedGroupIDs = yield self._addressbook.acceptedGroupIDs()
+            allowedObjectIDs = yield self._addressbook._objectIDsInExpandedGroupIDs(self._txn, acceptedGroupIDs)
             if self._name:
-                if self._name == (yield self._addressbook._groupForSharedABName()):
-                    rows = [(yield self._addressbook._groupForSharedABRow())]
+                rows = (yield self._allColumnsWithResourceIDsAndName(allowedObjectIDs).on(
+                    self._txn, name=self._name,
+                    resourceIDs=allowedObjectIDs,)) if allowedObjectIDs else []
             elif self._uid:
-                if self._uid == (yield self._addressbook._groupForSharedABUID()):
-                    rows = [(yield self._addressbook._groupForSharedABRow())]
+                rows = (yield self._allColumnsWithResourceIDsAndUID(allowedObjectIDs).on(
+                    self._txn, uid=self._uid,
+                    resourceIDs=allowedObjectIDs,)) if allowedObjectIDs else []
             elif self._resourceID:
-                if self._resourceID == self._addressbook._resourceID:
-                    rows = [(yield self._addressbook._groupForSharedABRow())]
+                invitedGroupIDs = yield self._addressbook.invitedGroupIDs()
+                allowedObjectIDs = tuple(set(allowedObjectIDs) | set(invitedGroupIDs))
+                rows = (yield self._allColumnsWithResourceID.on(
+                    self._txn, resourceID=self._resourceID,)) if (self._resourceID in allowedObjectIDs) else []
 
         if rows:
             self._initFromRow(tuple(rows[0]))
@@ -1117,6 +1180,16 @@
                 self._md5 = hashlib.md5(componentText).hexdigest()
                 self._size = len(componentText)
 
+                groupBindRows = yield AddressBookObject._bindForResourceIDAndHomeID.on(
+                    self._txn, resourceID=self._resourceID, homeID=self._home._resourceID)
+
+                if groupBindRows:
+                    bindMode, homeID, resourceID, bindName, bindStatus, bindMessage = groupBindRows[0] #@UnusedVariable\
+                    self._bindMode = bindMode
+                    self._bindStatus = bindStatus
+                    self._bindMessage = bindMessage
+                    self._bindName = bindName
+
             yield self._loadPropertyStore()
 
             returnValue(self)
@@ -1124,34 +1197,14 @@
             returnValue(None)
 
 
-    @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 _allGroupObjectIDs(self):
+    def _objectIDsInExpandedGroup(self):
         """
-        Get all addressbookobject resource IDs in this address book
+        Get all AddressBookObject resource IDs contained in this shared group
         """
+        returnValue((yield self._addressbook._objectIDsInExpandedGroupIDs(self._txn, [self._resourceID])))
 
-        objectIDs = set() if self.owned() else set([self._resourceID, ])
-        examinedIDs = set()
-        remainingIDs = set([self._resourceID, ])
-        while remainingIDs:
-            memberRows = yield self._memberIDsWithGroupIDsQuery(remainingIDs).on(self._txn, groupIDs=remainingIDs)
-            objectIDs |= set([memberRow[0] for memberRow in memberRows])
-            examinedIDs |= remainingIDs
-            remainingIDs = objectIDs - examinedIDs
 
-        returnValue(tuple(objectIDs))
-
-
     @classproperty
     def _allColumns(cls): #@NoSelf
         """
@@ -1189,7 +1242,7 @@
 
 
     @classmethod
-    def _abObjectColumnsWithResourceIDsQuery(cls, columns, resourceIDs): #@NoSelf
+    def _columnsWithResourceIDsQuery(cls, columns, resourceIDs): #@NoSelf
         """
         DAL statement to retrieve addressbook object rows with given columns.
         """
@@ -1200,19 +1253,16 @@
 
     @classmethod
     @inlineCallbacks
-    def _allColumnsWithParent(cls, parent): #@NoSelf
-
-        ownerGroup = yield parent.ownerGroup()
-        if ownerGroup:
-            objectIDs = yield ownerGroup._allGroupObjectIDs()
-            rows = (yield cls._abObjectColumnsWithResourceIDsQuery(cls._allColumns, objectIDs).on(
-                parent._txn, resourceIDs=objectIDs)) if objectIDs else []
+    def _allColumnsWithParent(cls, addressbook): #@NoSelf
+        if addressbook.owned() or addressbook.fullyShared():
+            rows = yield super(AddressBookObject, cls)._allColumnsWithParent(addressbook)
+            if addressbook.fullyShared():
+                rows.append((yield addressbook._groupForEntireAB_Row()))
         else:
-            rows = yield super(AddressBookObject, cls)._allColumnsWithParent(parent)
-            # add group vCard for shared address books
-            if not parent.owned():
-                rows.append((yield parent._groupForSharedABRow()))
-
+            acceptedGroupIDs = addressbook.acceptedGroupIDs()
+            allowedObjectIDs = yield addressbook._objectIDsInExpandedGroupIDs(addressbook._txn, acceptedGroupIDs)
+            rows = (yield cls._columnsWithResourceIDsQuery(cls._allColumns, allowedObjectIDs).on(
+                addressbook._txn, resourceIDs=allowedObjectIDs))
         returnValue(rows)
 
 
@@ -1226,20 +1276,17 @@
 
     @classmethod
     @inlineCallbacks
-    def _allColumnsWithParentAndNames(cls, parent, names): #@NoSelf
+    def _allColumnsWithParentAndNames(cls, addressbook, names): #@NoSelf
 
-        ownerGroup = yield parent.ownerGroup()
-        if ownerGroup:
-            objectIDs = yield parent._allAddressBookObjectIDs()
-            rows = (yield cls._allColumnsWithResourceIDsAndNamesQuery(objectIDs, names).on(
-                parent._txn, resourceIDs=objectIDs, names=names)) if objectIDs else []
+        if addressbook.owned() or addressbook.fullyShared():
+            rows = yield super(AddressBookObject, cls)._allColumnsWithParentAndNames(addressbook, names)
+            if addressbook.fullyShared() and (yield addressbook._groupForEntireAB_Name()) in names:
+                rows.append((yield addressbook._groupForEntireAB_Row()))
         else:
-            rows = yield super(AddressBookObject, cls)._allColumnsWithParentAndNames(parent, names)
-
-            # add group vCard for shared address books
-            if not parent.owned() and (yield parent._groupForSharedABName()) in names:
-                rows.append((yield parent._groupForSharedABRow(parent)))
-
+            acceptedGroupIDs = addressbook.acceptedGroupIDs()
+            allowedObjectIDs = yield addressbook._objectIDsInExpandedGroupIDs(addressbook._txn, acceptedGroupIDs)
+            rows = yield cls._allColumnsWithResourceIDsAndNamesQuery(allowedObjectIDs, names).on(
+                addressbook._txn, resourceIDs=allowedObjectIDs, names=names)
         returnValue(rows)
 
 
@@ -1260,7 +1307,7 @@
         yield self.updateDatabase(component, inserting=inserting)
         yield self._changeAddressBookRevision(self._addressbook, inserting)
 
-        if self._addressbook.owned():
+        if self.owned():
             # update revision table of the sharee group address book
             if self._kind == _ABO_KIND_GROUP:  # optimization
                 for shareeAddressBook in (yield self.asShared()):
@@ -1369,13 +1416,19 @@
             foundUIDs = [memberRow[1] for memberRow in memberRows]
             foreignMemberAddrs.extend(["urn:uuid:" + missingUID for missingUID in set(memberUIDs) - set(foundUIDs)])
 
-            #in shared group, all members must be inside the shared group
-            ownerGroup = yield self._addressbook.ownerGroup()
-            if ownerGroup:
-                if foreignMemberAddrs or \
-                    set(memberIDs) - set((yield ownerGroup._allGroupObjectIDs())):
-                    raise GroupWithUnsharedAddressNotAllowedError
+            if not self.owned():
+                if not self._addressbook.fullyShared():
+                    #in shared ab defined by groups, all members must be inside the shared groups
 
+                    #FIXME: does this apply to whole-shared address books too?
+                    if foreignMemberAddrs:
+                        raise GroupWithUnsharedAddressNotAllowedError
+
+                    acceptedGroupIDs = yield self._addressbook.acceptedGroupIDs()
+                    allowedObjectIDs = yield self._addressbook._objectIDsInExpandedGroupIDs(self._txn, acceptedGroupIDs)
+                    if set(memberIDs) - set(allowedObjectIDs):
+                        raise GroupWithUnsharedAddressNotAllowedError
+
             # don't store group members in object text
 
             # sort addreses in component text
@@ -1434,14 +1487,13 @@
             ).on(self._txn)
             groupIDs = [groupIDRow[0] for groupIDRow in groupIDRows]
 
-            # add group if of this owner address book
-            # groupIDs.append(self._ownerAddressBookResourceID)
+            # FIXME: Is this correct?
+            if not self.owned():
+                if not self._addressbook.fullyShared() or self._addressbook.shareMode() != _BIND_MODE_WRITE:
+                    writeableAcceptedGroupIDs = yield self._addressbook.writeableAcceptedGroupIDs()
+                    assert writeableAcceptedGroupIDs, "no access"
+                    groupIDs.extend(writeableAcceptedGroupIDs)
 
-            # add owner group if there is one
-            ownerGroup = yield self._addressbook.ownerGroup()
-            if ownerGroup:
-                groupIDs.append(ownerGroup._resourceID)
-
             # add to member table rows
             for groupID in groupIDs:
                 yield Insert(
@@ -1515,10 +1567,8 @@
 
         if self._component is None:
 
-            if not self._addressbook.owned() and  self._resourceID == self._addressbook._resourceID:
-
-                component = yield self._addressbook._groupForSharedABComponent()
-
+            if not self.owned() and  self._resourceID == self._addressbook._resourceID:
+                component = yield self._addressbook._groupForEntireAB_Component()
             else:
                 text = yield self._text()
 
@@ -1553,7 +1603,7 @@
 
                     # then get member UIDs
                     abo = schema.ADDRESSBOOK_OBJECT
-                    memberUIDRows = (yield self._abObjectColumnsWithResourceIDsQuery(
+                    memberUIDRows = (yield self._columnsWithResourceIDsQuery(
                                      [abo.VCARD_UID],
                                      memberIDs).on(
                                         self._txn, resourceIDs=memberIDs)
@@ -1571,7 +1621,6 @@
                                                     ).on(self._txn)
                     foreignMembers = [foreignMemberRow[0] for foreignMemberRow in foreignMemberRows]
 
-
                     # now add the properties to the component
                     for memberAddress in sorted(memberAddresses + foreignMembers):
                         component.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
@@ -1596,13 +1645,51 @@
     def ownerHome(self):
         return self._addressbook.ownerHome()
 
+
     def viewerHome(self):
         return self._addressbook.viewerHome()
 
 
-    def notifyChanged(self):
-        self._addressbook.notifyChanged()
+    def shareMode(self):
+        """
+        @see: L{ICalendar.shareMode}
+        """
+        if hasattr(self, "_bindMode"):
+            return self._bindMode
+        else:
+            return self._addressbook.shareMode()
 
+
+    def shareStatus(self):
+        """
+        @see: L{ICalendar.shareStatus}
+        """
+        if hasattr(self, "_bindStatus"):
+            return self._bindStatus
+        else:
+            return self._addressbook.shareStatus()
+
+
+    def shareMessage(self):
+        """
+        @see: L{ICalendar.shareMessage}
+        """
+        if hasattr(self, "_bindMessage"):
+            return self._bindMessage
+        else:
+            return self._addressbook.shareMessage()
+
+
+    def shareUID(self):
+        """
+        @see: L{ICalendar.shareUID}
+        """
+        if hasattr(self, "_bindName"):
+            return self._bindName
+        else:
+            return self._addressbook.shareUID()
+
+
     @classmethod
     @inlineCallbacks
     def ownerHomeID(cls, txn, resourceID):
@@ -1617,7 +1704,8 @@
 
         returnValue(ownerHomeRows[0][0])
 
-    # TODO: use class vars and CommonHomeChild._childrenAndMetadataForHomeID() instead
+
+    # TODO: use _homeChildMetaDataSchema and CommonHomeChild._childrenAndMetadataForHomeID() instead
     @classproperty
     def _childrenAndMetadataForHomeID(cls): #@NoSelf
         aboBind = cls._bindSchema
@@ -1639,50 +1727,7 @@
                            ).And(aboBind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
 
 
-
-    '''
-    @classmethod
     @inlineCallbacks
-    def invitedObjectWithName(cls, home, name):
-        """
-        Retrieve the child with the given C{name} contained in the given
-        C{home}.
-
-        @param home: a L{CommonHome}.
-
-        @param name: a string; the name of the L{CommonHomeChild} to retrieve.
-
-        @param owned: a boolean - whether or not to get a shared child
-        @return: an L{CommonHomeChild} or C{None} if no such child
-            exists.
-        """
-
-        rows = yield cls._invitedBindForNameAndHomeID.on(home._txn,
-                              name=name, homeID=home._resourceID)
-        if not rows:
-            returnValue(None)
-
-        bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
-
-        ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
-        ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
-        ownerAddressBook = yield ownerHome.addressbook()
-
-        addressbook = AddressBook(
-            home=home,
-            name=ownerAddressBook.shareeABName(), resourceID=ownerAddressBook._resourceID,
-            mode=bindMode, status=bindStatus,
-            message=bindMessage, ownerHome=ownerHome,
-        )
-        yield addressbook.initFromStore()
-
-        group = addressbook.objectResourceWithID(name, resourceID)
-
-        returnValue(group)
-    '''
-
-
-    @inlineCallbacks
     def shareWith(self, shareeHome, mode, status=None, message=None):
         """
         Share this (owned) L{CommonHomeChild} with another home.
@@ -1706,62 +1751,269 @@
         @rtype: L{str}
         """
 
-        bindName = yield self._shareWith(shareeHome, mode, status=status, message=message)
-        addressbook = self.addressbook()
+        yield self._shareWith(shareeHome, mode, status=status, message=message)
+        addressbook = yield self.addressbook()
         shareeAddressBook = yield shareeHome.childWithID(addressbook._resourceID)
+
         sharedGroup = yield shareeAddressBook.objectResourceWithID(self._resourceID)
-
-        #FIXME
-        sharedGroup._bindName = bindName
+        yield self._addressbook.notifyChanged()
         returnValue(sharedGroup)
 
 
-    def shareMode(self):
+    @inlineCallbacks
+    def asShared(self):
         """
-        @see: L{ICalendar.shareMode}
+        Retrieve all the versions of this L{CommonHomeChild} as it is shared to
+        everyone.
+
+        @see: L{ICalendarHome.asShared}
+
+        @return: L{CommonHomeChild} objects that represent this
+            L{CommonHomeChild} as a child of different L{CommonHome}s
+        @rtype: a L{Deferred} which fires with a L{list} of L{ICalendar}s.
         """
-        if hasattr(self, "_bindMode"):
-            return self._bindMode
-        else:
-            return self._addressbook.shareMode()
+        if not self.owned():
+            returnValue([])
 
+        # get all accepted shared binds
+        groupBindRows = yield self._sharedBindForResourceID.on(
+            self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
+        )
 
-    def shareStatus(self):
+        result = []
+        for bindMode, homeID, resourceID, bindName, bindStatus, bindMessage in groupBindRows: #@UnusedVariable
+            home = yield self._txn.homeWithResourceID(self._home._homeType, homeID)
+            addressbook = yield home.childWithID(self._addressbook._resourceID)
+            new = yield addressbook.objectResourceWithID(resourceID)
+            result.append(new)
+
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def asInvited(self):
         """
-        @see: L{ICalendar.shareStatus}
+        Retrieve all the versions of this L{CommonHomeChild} as it is shared to
+        everyone.
+
+        @see: L{ICalendarHome.asShared}
+
+        @return: L{CommonHomeChild} objects that represent this
+            L{CommonHomeChild} as a child of different L{CommonHome}s
+        @rtype: a L{Deferred} which fires with a L{list} of L{ICalendar}s.
         """
-        if hasattr(self, "_bindStatus"):
-            return self._bindStatus
-        else:
-            return self._addressbook.shareStatus()
+        if not self.owned():
+            returnValue([])
 
+        # get all accepted shared binds
+        groupBindRows = yield self._invitedBindForResourceID.on(
+            self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
+        )
 
-    def shareMessage(self):
+        result = []
+        for bindMode, homeID, resourceID, bindName, bindStatus, bindMessage in groupBindRows: #@UnusedVariable
+            home = yield self._txn.homeWithResourceID(self._home._homeType, homeID)
+            addressbook = yield home.childWithID(self._addressbook._resourceID)
+            new = yield addressbook.objectResourceWithID(resourceID)
+            result.append(new)
+
+        returnValue(result)
+
+
+    @classproperty
+    def _parentIDForObjectID(cls): #@NoSelf
+        #TODO: This query could be part of previously called query using object schema join
+        obj = cls._objectSchema
+        return Select([obj.PARENT_RESOURCE_ID],
+                      From=obj,
+                      Where=obj.RESOURCE_ID == Parameter("objectID")
+                    )
+
+
+    @inlineCallbacks
+    def unshareWith(self, shareeHome):
         """
-        @see: L{ICalendar.shareMessage}
+        Remove the shared version of this (owned) L{CommonHomeChild} from the
+        referenced L{CommonHome}.
+
+        @see: L{CommonHomeChild.shareWith}
+
+        @param shareeHome: The home with which this L{CommonHomeChild} was
+            previously shared.
+
+        @return: a L{Deferred} which will fire with the previously-used name.
         """
-        if hasattr(self, "_bindMessage"):
-            return self._bindMessage
-        else:
-            return self._addressbook.shareMessage()
 
+        sharedAddressBook = yield shareeHome.addressbookWithName(self._addressbook.shareeABName())
+        if not sharedAddressBook:
+            returnValue(None)
 
-    def shareUID(self):
+        sharedRemoval = False
+        if not sharedAddressBook.fullyShared():
+            groupIDs = yield sharedAddressBook.acceptedGroupIDs()
+            assert self._resourceID in groupIDs
+            sharedRemoval = len(groupIDs) == 1
+            if sharedRemoval:
+                sharedAddressBook._deletedSyncToken(sharedRemoval=True)
+
+        queryCacher = self._txn._queryCacher
+        if queryCacher:
+            cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self._addressbook.shareeABName())
+            queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+
+        rows = yield self._deleteBindWithResourceIDAndHomeID.on(self._txn, resourceID=self._resourceID,
+             homeID=shareeHome._resourceID)
+
+        resourceName = None
+        if rows and sharedRemoval:
+            resourceName = rows[0][0]
+            shareeHome._children.pop(resourceName, None)
+
+        # Must send notification to ensure cache invalidation occurs
+        yield self._addressbook.notifyChanged()
+
+        returnValue(resourceName)
+
+
+    @inlineCallbacks
+    def updateShare(self, shareeView, mode=None, status=None, message=None, name=None):
         """
-        @see: L{ICalendar.shareUID}
+        Update share mode, status, and message for a home child shared with
+        this (owned) L{CommonHomeChild}.
+
+        @param shareeView: The sharee home child that shares this.
+        @type shareeView: L{CommonHomeChild}
+
+        @param mode: The sharing mode; L{_BIND_MODE_READ} or
+            L{_BIND_MODE_WRITE} or None to not update
+        @type mode: L{str}
+
+        @param status: The sharing status; L{_BIND_STATUS_INVITED} or
+            L{_BIND_STATUS_ACCEPTED} or L{_BIND_STATUS_DECLINED} or
+            L{_BIND_STATUS_INVALID}  or None to not update
+        @type status: L{str}
+
+        @param message: The proposed message to go along with the share, which
+            will be used as the default display name, or None to not update
+        @type message: L{str}
+
+        @param name: The bind resource name or None to not update
+        @type message: L{str}
+
+        @return: the name of the shared item in the sharee's home.
+        @rtype: a L{Deferred} which fires with a L{str}
         """
-        if hasattr(self, "_bindName"):
-            return self._bindName
-        else:
-            return self._addressbook.shareUID()
+        # TODO: raise a nice exception if shareeView is not, in fact, a shared
+        # version of this same L{CommonHomeChild}
 
 
-    @classmethod
-    def _bindForGroupIDsAndHomeID(cls, groupIDs): #@NoSelf
+        #remove None parameters, and substitute None for empty string
+        bind = self._bindSchema
+        columnMap = dict([(k, v if v else None)
+                          for k, v in {bind.BIND_MODE:mode,
+                            bind.BIND_STATUS:status,
+                            bind.MESSAGE:message,
+                            bind.RESOURCE_NAME:name}.iteritems() if v is not None])
+
+        if len(columnMap):
+
+            #TODO:  with bit of parameter wrangling, call shareWith() here instead.
+            sharedname = yield self._updateBindColumnsQuery(columnMap).on(
+                            self._txn,
+                            resourceID=self._resourceID, homeID=shareeView._home._resourceID
+                        )
+
+            #update affected attributes
+            if mode:
+                shareeView._bindMode = columnMap[bind.BIND_MODE]
+
+            if status:
+                shareeView._bindStatus = columnMap[bind.BIND_STATUS]
+                if shareeView._bindStatus == _BIND_STATUS_ACCEPTED:
+                    yield shareeView._addressbook._initSyncToken()
+                elif shareeView._bindStatus == _BIND_STATUS_DECLINED:
+                    shareeView._addressbook._deletedSyncToken(sharedRemoval=True)
+                    shareeView._home._children.pop(shareeView._addressbook._name, None)
+
+            if message:
+                shareeView._bindMessage = columnMap[bind.MESSAGE]
+
+            queryCacher = self._txn._queryCacher
+            if queryCacher:
+                cacheKey = queryCacher.keyForObjectWithName(shareeView._home._resourceID, shareeView._addressbook._name)
+                queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+
+            shareeView._name = sharedname[0][0]
+
+            # Must send notification to ensure cache invalidation occurs
+            yield self._addressbook.notifyChanged()
+
+        returnValue(shareeView._name)
+
+
+    @classproperty
+    def _acceptedBindWithAddressBookID(cls): #@NoSelf
         bind = cls._bindSchema
-        return cls._bindFor(bind.RESOURCE_ID.In(Parameter("groupIDs", len(groupIDs)))
-                               .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
-                               )
+        abo = cls._objectSchema
+        return Select(
+                  cls._bindColumns,
+                  From=bind.join(abo),
+                  Where=(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
+                        .And(bind.RESOURCE_ID == abo.RESOURCE_ID)
+                        .And(abo.ADDRESSBOOK_RESOURCE_ID == Parameter("addressbookID"))
+        )
 
+    @classproperty
+    def _invitedBindWithAddressBookID(cls): #@NoSelf
+        bind = cls._bindSchema
+        abo = cls._objectSchema
+        return Select(
+                  cls._bindColumns,
+                  From=bind.join(abo),
+                  Where=(bind.BIND_STATUS == _BIND_STATUS_INVITED)
+                        .And(bind.RESOURCE_ID == abo.RESOURCE_ID)
+                        .And(abo.ADDRESSBOOK_RESOURCE_ID == Parameter("addressbookID"))
+        )
 
+
+    @classproperty
+    def _acceptedBindWithHomeIDAndAddressBookID(cls): #@NoSelf
+        bind = cls._bindSchema
+        abo = cls._objectSchema
+        return Select(
+                  cls._bindColumns,
+                  From=bind.join(abo),
+                  Where=(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
+                        .And(bind.RESOURCE_ID == abo.RESOURCE_ID)
+                        .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
+                        .And(abo.ADDRESSBOOK_RESOURCE_ID == Parameter("addressbookID"))
+        )
+
+
+    @classproperty
+    def _invitedBindWithHomeIDAndAddressBookID(cls): #@NoSelf
+        bind = cls._bindSchema
+        abo = cls._objectSchema
+        return Select(
+                  cls._bindColumns,
+                  From=bind.join(abo),
+                  Where=(bind.BIND_STATUS == _BIND_STATUS_INVITED)
+                        .And(bind.RESOURCE_ID == abo.RESOURCE_ID)
+                        .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
+                        .And(abo.ADDRESSBOOK_RESOURCE_ID == Parameter("addressbookID"))
+        )
+
+
+    @classproperty
+    def _bindWithHomeIDAndAddressBookID(cls): #@NoSelf
+        bind = cls._bindSchema
+        abo = cls._objectSchema
+        return Select(
+                  cls._bindColumns,
+                  From=bind.join(abo),
+                  Where=(bind.RESOURCE_ID == abo.RESOURCE_ID)
+                        .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
+                        .And(abo.ADDRESSBOOK_RESOURCE_ID == Parameter("addressbookID"))
+        )
+
 AddressBook._objectResourceClass = AddressBookObject

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/iaddressbookstore.py	2013-02-12 00:37:11 UTC (rev 10694)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/iaddressbookstore.py	2013-02-12 01:07:09 UTC (rev 10695)
@@ -27,7 +27,7 @@
 __all__ = [
     # Classes
     "GroupWithUnsharedAddressNotAllowedError",
-    "DeleteOfShadowGroupNotAllowedError",
+    "DeleteOfGroupForSharedAddressBookNotAllowedError",
     "IAddressBookTransaction",
     "IAddressBookHome",
     "IAddressBook",
@@ -38,7 +38,7 @@
     """
     Sharee cannot add or modify group vcard such that result contains addresses of unshared vcards.
     """
-class DeleteOfShadowGroupNotAllowedError(CommonStoreError):
+class DeleteOfGroupForSharedAddressBookNotAllowedError(CommonStoreError):
     """
     Sharee cannot delete group vcard shadowing shared address book
     """

Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql.py	2013-02-12 00:37:11 UTC (rev 10694)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql.py	2013-02-12 01:07:09 UTC (rev 10695)
@@ -1486,15 +1486,6 @@
             return self._childClass.listObjects(self)
 
 
-    def listInvitedChildren(self):
-        """
-        Retrieve the names of the invited children in this home.
-
-        @return: an iterable of C{str}s.
-        """
-        return self._childClass.listInvitedObjects(self)
-
-
     @memoizedKey("name", "_children")
     def childWithName(self, name):
         """
@@ -1507,7 +1498,7 @@
         return self._childClass.objectWithName(self, name)
 
 
-    def childWithVBindName(self, name):
+    def objectWithBindName(self, name):
         """
         Retrieve the child with the given bind identifier contained in this
         home.
@@ -1530,17 +1521,6 @@
         return self._childClass.objectWithID(self, resourceID)
 
 
-    def invitedChildWithName(self, name):
-        """
-        Retrieve the invited child with the given C{name} contained in this
-        home.
-
-        @param name: a string.
-        @return: an L{ICalendar} or C{None} if no such child exists.
-        """
-        return self._childClass.invitedObjectWithName(self, name)
-
-
     @inlineCallbacks
     def createChildWithName(self, name):
         if name.startswith("."):
@@ -2323,6 +2303,77 @@
                      bind.MESSAGE: Parameter("message")})
 
 
+    @classproperty
+    def _bindColumns(cls): #@NoSelf
+        bind = cls._bindSchema
+        return [
+                bind.BIND_MODE,
+                bind.HOME_RESOURCE_ID,
+                bind.RESOURCE_ID,
+                bind.RESOURCE_NAME,
+                bind.BIND_STATUS,
+                bind.MESSAGE,
+                ]
+
+
+    @classmethod
+    def _bindFor(cls, condition): #@NoSelf
+        bind = cls._bindSchema
+        return Select(
+                  cls._bindColumns,
+                  From=bind,
+                  Where=condition
+        )
+
+
+    @classproperty
+    def _sharedBindForResourceID(cls): #@NoSelf
+        bind = cls._bindSchema
+        return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
+                            .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
+                            .And(bind.BIND_MODE != _BIND_MODE_OWN)
+                            )
+
+    @classproperty
+    def _acceptedBindForHomeID(cls): #@NoSelf
+        bind = cls._bindSchema
+        return cls._bindFor((bind.HOME_RESOURCE_ID == Parameter("homeID"))
+                            .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
+
+
+    @classproperty
+    def _invitedBindForResourceID(cls): #@NoSelf
+        bind = cls._bindSchema
+        return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
+                            .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED)
+                            )
+
+
+    @classproperty
+    def _bindForResourceIDAndHomeID(cls): #@NoSelf
+        """
+        DAL query that looks up home bind rows by home child
+        resource ID and home resource ID.
+        """
+        bind = cls._bindSchema
+        return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
+                               .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
+                               )
+
+
+    @classproperty
+    def _acceptedBindForNameAndHomeID(cls): #@NoSelf
+        """
+        DAL query that looks up accepted bind rows by home child
+        resource ID and home resource ID.
+        """
+        bind = cls._bindSchema
+        return cls._bindFor((bind.RESOURCE_NAME == Parameter("name"))
+                               .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
+                               .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
+                               )
+
+
     @inlineCallbacks
     def _shareWith(self, shareeHome, mode, status=None, message=None):
         """
@@ -2369,8 +2420,6 @@
                 resourceID=self._resourceID, homeID=shareeHome._resourceID
             ))[0][0]
 
-        # Must send notification to ensure cache invalidation occurs
-        yield self.notifyChanged()
         returnValue(sharedName)
 
 
@@ -2400,6 +2449,7 @@
 
         yield self._shareWith(shareeHome, mode, status=status, message=message)
         child = yield shareeHome.childWithID(self._resourceID)
+        yield self.notifyChanged()
         returnValue(child)
 
 
@@ -2469,6 +2519,7 @@
                     yield shareeView._initSyncToken()
                 elif shareeView._bindStatus == _BIND_STATUS_DECLINED:
                     shareeView._deletedSyncToken(sharedRemoval=True)
+                    shareeView._home._children.pop(shareeView._name, None)
 
             if message:
                 shareeView._bindMessage = columnMap[bind.MESSAGE]
@@ -2486,6 +2537,17 @@
         returnValue(shareeView._name)
 
 
+    @classproperty
+    def _deleteBindWithResourceIDAndHomeID(cls): #@NoSelf
+        bind = cls._bindSchema
+        return Delete(
+            From=bind,
+            Where=(bind.RESOURCE_ID == Parameter("resourceID"))
+                  .And(bind.HOME_RESOURCE_ID == Parameter("homeID")),
+            Return=bind.RESOURCE_NAME,
+        )
+
+
     @inlineCallbacks
     def unshareWith(self, shareeHome):
         """
@@ -2513,13 +2575,7 @@
 
                 break
 
-        bind = self._bindSchema
-        rows = yield Delete(
-            From=bind,
-            Where=(bind.RESOURCE_ID == Parameter("resourceID"))
-                  .And(bind.HOME_RESOURCE_ID == Parameter("homeID")),
-            Return=bind.RESOURCE_NAME,
-        ).on(self._txn, resourceID=self._resourceID,
+        rows = yield self._deleteBindWithResourceIDAndHomeID.on(self._txn, resourceID=self._resourceID,
              homeID=shareeHome._resourceID)
 
         resourceName = None
@@ -2532,31 +2588,6 @@
 
         returnValue(resourceName)
 
-
-    @classmethod
-    def _bindFor(cls, condition): #@NoSelf
-        bind = cls._bindSchema
-        return Select(
-            [bind.BIND_MODE,
-             bind.HOME_RESOURCE_ID,
-             bind.RESOURCE_ID,
-             bind.RESOURCE_NAME,
-             bind.BIND_STATUS,
-             bind.MESSAGE],
-                  From=bind,
-                  Where=condition
-        )
-
-
-    @classproperty
-    def _sharedBindForResourceID(cls): #@NoSelf
-        bind = cls._bindSchema
-        return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
-                            .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
-                            .And(bind.BIND_MODE != _BIND_MODE_OWN)
-                            )
-
-
     @inlineCallbacks
     def asShared(self):
         """
@@ -2574,7 +2605,7 @@
 
         # get all accepted binds
         acceptedRows = yield self._sharedBindForResourceID.on(
-            self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
+            self._txn, resourceID=self._resourceID,
         )
 
         cls = self._home._childClass # for ease of grepping...
@@ -2594,14 +2625,6 @@
         returnValue(result)
 
 
-    @classproperty
-    def _invitedBindForResourceID(cls): #@NoSelf
-        bind = cls._bindSchema
-        return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
-                            .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED)
-                            )
-
-
     @inlineCallbacks
     def asInvited(self):
         """
@@ -2618,7 +2641,7 @@
             returnValue([])
 
         rows = yield self._invitedBindForResourceID.on(
-            self._txn, resourceID=self._resourceID, homeID=self._home._resourceID,
+            self._txn, resourceID=self._resourceID,
         )
         cls = self._home._childClass # for ease of grepping...
 
@@ -2638,16 +2661,7 @@
         returnValue(result)
 
 
-    @classproperty
-    def _childBindForNameAndHomeID(cls): #@NoSelf
-        bind = cls._bindSchema
-        return cls._bindFor((bind.RESOURCE_NAME == Parameter("name"))
-                               .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
-                               .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
-                               )
 
-
-
     def shareMode(self):
         """
         @see: L{ICalendar.shareMode}
@@ -2682,26 +2696,7 @@
         """
         return self.name()
 
-    @classproperty
-    def _childBindForHomeID(cls): #@NoSelf
-        bind = cls._bindSchema
-        return cls._bindFor((bind.HOME_RESOURCE_ID == Parameter("homeID"))
-                            .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
 
-
-    @classproperty
-    def _bindForResourceIDAndHomeID(cls): #@NoSelf
-        """
-        DAL query that looks up home child names / bind modes by home child
-        resource ID and home resource ID.
-        """
-        bind = cls._bindSchema
-        return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
-                               .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
-                               )
-
-
-
 class CommonHomeChild(LoggingMixIn, FancyEqMixin, _SharedSyncLogic, HomeChildBase, SharingMixIn):
     """
     Common ancestor class of AddressBooks and Calendars.
@@ -2796,7 +2791,7 @@
         @return: an iterable of C{str}s.
         """
         # FIXME: tests don't cover this as directly as they should.
-        rows = yield cls._childBindForHomeID.on(
+        rows = yield cls._acceptedBindForHomeID.on(
                 home._txn, homeID=home._resourceID
         )
         names = [row[3] for row in rows]
@@ -2804,40 +2799,12 @@
 
 
     @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())
+        columns = cls._bindColumns
+        columns = columns.extend(cls.metadataColumns())
         return Select(columns,
                      From=child.join(
                          bind, child.RESOURCE_ID == bind.RESOURCE_ID,
@@ -2848,7 +2815,6 @@
                            ).And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED))
 
 
-
     @inlineCallbacks
     def unshare(self, homeType):
         """
@@ -2942,11 +2908,10 @@
 
 
     @classproperty
-    def _invitedBindForNameAndHomeID(cls): #@NoSelf
+    def _bindForNameAndHomeID(cls): #@NoSelf
         bind = cls._bindSchema
         return cls._bindFor((bind.RESOURCE_NAME == Parameter("name"))
                                .And(bind.HOME_RESOURCE_ID == Parameter("homeID"))
-                               .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED)
                                )
 
 
@@ -2959,42 +2924,6 @@
 
     @classmethod
     @inlineCallbacks
-    def invitedObjectWithName(cls, home, name):
-        """
-        Retrieve the child with the given C{name} contained in the given
-        C{home}.
-
-        @param home: a L{CommonHome}.
-
-        @param name: a string; the name of the L{CommonHomeChild} to retrieve.
-
-        @param owned: a boolean - whether or not to get a shared child
-        @return: an L{CommonHomeChild} or C{None} if no such child
-            exists.
-        """
-        rows = yield cls._invitedBindForNameAndHomeID.on(home._txn,
-                              name=name, homeID=home._resourceID)
-
-        if not rows:
-            returnValue(None)
-
-        bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
-
-        ownerHomeID = yield cls.ownerHomeID(home._txn, resourceID)
-        ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerHomeID)
-
-        child = ownerHome._childClass(
-            home=home,
-            name=resourceName, resourceID=resourceID,
-            mode=bindMode, status=bindStatus,
-            message=bindMessage, ownerHome=ownerHome,
-        )
-        yield child.initFromStore()
-        returnValue(child)
-
-
-    @classmethod
-    @inlineCallbacks
     def objectWithName(cls, home, name):
         # replaces objectWithName()
         """
@@ -3018,7 +2947,7 @@
 
         if rows is None:
             # No cached copy
-            rows = yield cls._childBindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
+            rows = yield cls._acceptedBindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
 
             if rows:
                 bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage = rows[0] #@UnusedVariable
@@ -3037,7 +2966,6 @@
             returnValue(None)
 
         bindMode, homeID, resourceID, resourceName, bindStatus, bindMessage, ownerHomeID = rows[0] #@UnusedVariable
-
         if bindMode == _BIND_MODE_OWN:
             ownerHome = home
         else:
@@ -3110,12 +3038,10 @@
     @classmethod
     @inlineCallbacks
     def create(cls, home, name):
-        child = (yield cls.objectWithName(home, name))
-        if child is not None:
+
+        if (yield cls._bindForNameAndHomeID.on(home._txn,
+                              name=name, homeID=home._resourceID)):
             raise HomeChildNameAlreadyExistsError(name)
-        invite = (yield cls.invitedObjectWithName(home, name))
-        if invite is not None:
-            raise HomeChildNameAlreadyExistsError(name)
 
         if name.startswith("."):
             raise HomeChildNameNotAllowedError(name)
@@ -3425,11 +3351,11 @@
         if resourceID in self._objects:
             return succeed(self._objects[resourceID])
         else:
-            return self._makeObjectResource(resourceID=resourceID)
+            return self._makeObjectResource(resourceID=resourceID, cache=False)
 
 
     @inlineCallbacks
-    def _makeObjectResource(self, name=None, uid=None, resourceID=None):
+    def _makeObjectResource(self, name=None, uid=None, resourceID=None, cache=True):
         """
         We create the empty object first then have it initialize itself from the
         store.
@@ -3442,7 +3368,7 @@
             objectResource = (
                 yield self._objectResourceClass.objectWithName(self, name, uid)
             )
-        if objectResource:
+        if objectResource and cache:
             self._objects[objectResource.name()] = objectResource
             self._objects[objectResource.uid()] = objectResource
             self._objects[objectResource._resourceID] = objectResource
@@ -3987,7 +3913,7 @@
 
 
     @classmethod
-    def _allWithParentAnd(cls, column, paramName):
+    def _allColumnsWithParentAnd(cls, column, paramName):
         """
         DAL query for all columns where PARENT_RESOURCE_ID matches a parentID
         parameter and a given instance column matches a given parameter name.
@@ -4000,18 +3926,18 @@
 
 
     @classproperty
-    def _allWithParentAndName(cls): #@NoSelf
-        return cls._allWithParentAnd(cls._objectSchema.RESOURCE_NAME, "name")
+    def _allColumnsWithParentAndName(cls): #@NoSelf
+        return cls._allColumnsWithParentAnd(cls._objectSchema.RESOURCE_NAME, "name")
 
 
     @classproperty
-    def _allWithParentAndUID(cls): #@NoSelf
-        return cls._allWithParentAnd(cls._objectSchema.UID, "uid")
+    def _allColumnsWithParentAndUID(cls): #@NoSelf
+        return cls._allColumnsWithParentAnd(cls._objectSchema.UID, "uid")
 
 
     @classproperty
-    def _allWithParentAndID(cls): #@NoSelf
-        return cls._allWithParentAnd(cls._objectSchema.RESOURCE_ID, "resourceID")
+    def _allColumnsWithParentAndID(cls): #@NoSelf
+        return cls._allColumnsWithParentAnd(cls._objectSchema.RESOURCE_ID, "resourceID")
 
 
     @inlineCallbacks
@@ -4026,15 +3952,15 @@
         """
 
         if self._name:
-            rows = yield self._allWithParentAndName.on(
+            rows = yield self._allColumnsWithParentAndName.on(
                 self._txn, name=self._name,
                 parentID=self._parentCollection._resourceID)
         elif self._uid:
-            rows = yield self._allWithParentAndUID.on(
+            rows = yield self._allColumnsWithParentAndUID.on(
                 self._txn, uid=self._uid,
                 parentID=self._parentCollection._resourceID)
         elif self._resourceID:
-            rows = yield self._allWithParentAndID.on(
+            rows = yield self._allColumnsWithParentAndID.on(
                 self._txn, resourceID=self._resourceID,
                 parentID=self._parentCollection._resourceID)
         if rows:

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	2013-02-12 00:37:11 UTC (rev 10694)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/common/datastore/sql_schema/current.sql	2013-02-12 01:07:09 UTC (rev 10695)
@@ -402,9 +402,9 @@
   ADDRESSBOOK_BIND(ADDRESSBOOK_RESOURCE_ID);
 
 
------------------------------
+------------------------
 -- AddressBook Object --
------------------------------
+------------------------
 
 create table ADDRESSBOOK_OBJECT (
   RESOURCE_ID             integer      primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130211/d5528635/attachment-0001.html>


More information about the calendarserver-changes mailing list