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

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

<h3>Log Message</h3>
<pre>Checkpoint changes: more api changes for carddav. Re-working group sharing support is next.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboosharinginthestoretwistedcaldavresourcepy">CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboosharinginthestoretwistedcaldavstorebridgepy">CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboosharinginthestoretxdavcarddavdatastoresqlpy">CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboosharinginthestoretxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboosharinginthestoretxdavcommondatastoresql_schemacurrentsql">CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql_schema/current.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboosharinginthestoretxdavcommondatastoresql_tablespy">CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql_tables.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboosharinginthestoretwistedcaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/resource.py (11962 => 11963)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/resource.py        2013-11-18 19:24:16 UTC (rev 11962)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/resource.py        2013-11-18 21:59:19 UTC (rev 11963)
</span><span class="lines">@@ -859,8 +859,12 @@
</span><span class="cx">         Return the DAV:owner property value (MUST be a DAV:href or None).
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        if self.isShareeResource():
-            parent = (yield self.locateParent(request, self._share_url))
</del><ins>+        if hasattr(self, &quot;_newStoreObject&quot;):
+            if not hasattr(self._newStoreObject, &quot;ownerHome&quot;):
+                home = self._newStoreObject.parentCollection().ownerHome()
+            else:
+                home = self._newStoreObject.ownerHome()
+            returnValue(element.HRef(self.principalForUID(home.uid()).principalURL()))
</ins><span class="cx">         else:
</span><span class="cx">             parent = (yield self.locateParent(request, request.urlForResource(self)))
</span><span class="cx">         if parent and isinstance(parent, CalDAVResource):
</span><span class="lines">@@ -875,8 +879,12 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return the DAV:owner property value (MUST be a DAV:href or None).
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        if self.isShareeResource():
-            parent = (yield self.locateParent(request, self._share_url))
</del><ins>+        if hasattr(self, &quot;_newStoreObject&quot;):
+            if not hasattr(self._newStoreObject, &quot;ownerHome&quot;):
+                home = self._newStoreObject.parentCollection().ownerHome()
+            else:
+                home = self._newStoreObject.ownerHome()
+            returnValue(self.principalForUID(home.uid()))
</ins><span class="cx">         else:
</span><span class="cx">             parent = (yield self.locateParent(request, request.urlForResource(self)))
</span><span class="cx">         if parent and isinstance(parent, CalDAVResource):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboosharinginthestoretwistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/storebridge.py (11962 => 11963)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/storebridge.py        2013-11-18 19:24:16 UTC (rev 11962)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/storebridge.py        2013-11-18 21:59:19 UTC (rev 11963)
</span><span class="lines">@@ -3110,8 +3110,8 @@
</span><span class="cx">         call super and provision group share
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         abObjectResource = yield super(AddressBookCollectionResource, self).makeChild(name)
</span><del>-        if abObjectResource.exists() and abObjectResource._newStoreObject.shareUID() is not None:
-            abObjectResource = yield self.parentResource().provisionShare(abObjectResource)
</del><ins>+        #if abObjectResource.exists() and abObjectResource._newStoreObject.shareUID() is not None:
+        #    abObjectResource = yield self.parentResource().provisionShare(abObjectResource)
</ins><span class="cx">         returnValue(abObjectResource)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboosharinginthestoretxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/carddav/datastore/sql.py (11962 => 11963)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/carddav/datastore/sql.py        2013-11-18 19:24:16 UTC (rev 11962)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/carddav/datastore/sql.py        2013-11-18 21:59:19 UTC (rev 11963)
</span><span class="lines">@@ -55,7 +55,7 @@
</span><span class="cx"> from txdav.common.datastore.sql_tables import _ABO_KIND_PERSON, \
</span><span class="cx">     _ABO_KIND_GROUP, _ABO_KIND_RESOURCE, _ABO_KIND_LOCATION, schema, \
</span><span class="cx">     _BIND_MODE_OWN, _BIND_MODE_WRITE, _BIND_STATUS_ACCEPTED, \
</span><del>-    _BIND_STATUS_DECLINED, _BIND_STATUS_INVITED
</del><ins>+    _BIND_STATUS_DECLINED, _BIND_STATUS_INVITED, _BIND_MODE_READ
</ins><span class="cx"> from txdav.common.icommondatastore import InternalDataStoreError, \
</span><span class="cx">     InvalidUIDError, UIDExistsError, ObjectResourceTooBigError, \
</span><span class="cx">     InvalidObjectResourceError, InvalidComponentForStoreError, \
</span><span class="lines">@@ -101,13 +101,36 @@
</span><span class="cx">     removeAddressBookWithName = CommonHome.removeChildWithName
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @classproperty
-    def _resourceIDAndHomeResourceIDFromOwnerQuery(cls): #@NoSelf
-        home = cls._homeSchema
-        return Select([home.RESOURCE_ID, home.ADDRESSBOOK_PROPERTY_STORE_ID],
-                      From=home, Where=home.OWNER_UID == Parameter(&quot;ownerUID&quot;))
</del><ins>+    @classmethod
+    def homeColumns(cls):
+        &quot;&quot;&quot;
+        Return a list of column names to retrieve when doing an ownerUID-&gt;home lookup.
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+        # Common behavior is to have created and modified
</ins><span class="cx"> 
</span><ins>+        return (
+            cls._homeSchema.RESOURCE_ID,
+            cls._homeSchema.OWNER_UID,
+            cls._homeSchema.ADDRESSBOOK_PROPERTY_STORE_ID,
+        )
+
+
+    @classmethod
+    def homeAttributes(cls):
+        &quot;&quot;&quot;
+        Return a list of attributes names to map L{homeColumns} to.
+        &quot;&quot;&quot;
+
+        # Common behavior is to have created and modified
+
+        return (
+            &quot;_resourceID&quot;,
+            &quot;_ownerUID&quot;,
+            &quot;_addressbookPropertyStoreID&quot;,
+        )
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def initFromStore(self, no_cache=False):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -115,48 +138,22 @@
</span><span class="cx">         extra meta-data from the DB to avoid having to do DB queries for those
</span><span class="cx">         individually later.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        result = yield self._cacher.get(self._ownerUID)
-        if result is None:
-            result = yield self._resourceIDAndHomeResourceIDFromOwnerQuery.on(
-                self._txn, ownerUID=self._ownerUID)
-            if result and not no_cache:
-                yield self._cacher.set(self._ownerUID, result)
</del><span class="cx"> 
</span><del>-        if result:
-            self._resourceID, self._addressbookPropertyStoreID = result[0]
</del><ins>+        result = yield super(AddressBookHome, self).initFromStore(no_cache)
+        if result is not None:
</ins><span class="cx"> 
</span><del>-            queryCacher = self._txn._queryCacher
-            if queryCacher:
-                # Get cached copy
-                cacheKey = queryCacher.keyForHomeMetaData(self._resourceID)
-                data = yield queryCacher.get(cacheKey)
-            else:
-                data = None
-            if data is None:
-                # Don't have a cached copy
-                data = (yield self._metaDataQuery.on(
-                    self._txn, resourceID=self._resourceID))[0]
-                if queryCacher:
-                    # Cache the data
-                    yield queryCacher.setAfterCommit(self._txn, cacheKey, data)
-
-            # self._created, self._modified = data
-            yield self._loadPropertyStore()
-
</del><span class="cx">             # created owned address book
</span><span class="cx">             addressbook = AddressBook(
</span><span class="cx">                 home=self,
</span><del>-                name=&quot;addressbook&quot;, resourceID=self._resourceID,
-                mode=_BIND_MODE_OWN, status=_BIND_STATUS_ACCEPTED,
</del><ins>+                name=&quot;addressbook&quot;,
+                resourceID=self._resourceID,
+                mode=_BIND_MODE_OWN,
+                status=_BIND_STATUS_ACCEPTED,
</ins><span class="cx">             )
</span><del>-            self._created, self._modified = data
</del><span class="cx">             yield addressbook._loadPropertyStore()
</span><del>-            yield addressbook._initIsShared()
</del><span class="cx">             self._addressbook = addressbook
</span><span class="cx"> 
</span><del>-            returnValue(self)
-        else:
-            returnValue(None)
</del><ins>+        returnValue(result)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -204,6 +201,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         super(AddressBookHome, self).removeUnacceptedShares()
</span><span class="cx"> 
</span><ins>+        # Remove group binds too
</ins><span class="cx">         bind = AddressBookObject._bindSchema
</span><span class="cx">         kwds = {&quot;homeResourceID&quot; : self._resourceID}
</span><span class="cx">         yield Delete(
</span><span class="lines">@@ -318,30 +316,173 @@
</span><span class="cx">         Sharing code shared between AddressBook and AddressBookObject
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def _isSharedOrInvited(self):
</del><ins>+    def sharedResourceType(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        return True if this L{AddressBook} is shared or invited
</del><ins>+        The sharing resource type
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        sharedRows = []
-        if self.owned():
-            bind = self._bindSchema
-            sharedRows = yield self._bindFor(
-                (bind.RESOURCE_ID == Parameter(&quot;resourceID&quot;))).on(
-                self._txn, resourceID=self._resourceID,
-            )
</del><ins>+        return &quot;addressbook&quot;
</ins><span class="cx"> 
</span><del>-        returnValue(bool(sharedRows))
</del><span class="cx"> 
</span><ins>+    def newShareName(self):
+        &quot;&quot;&quot;
+        For shared address books the resource name of a share is the ownerUID of the owner's resource.
+        &quot;&quot;&quot;
+        return self.ownerHome().uid()
</ins><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def _initIsShared(self):
-        isShared = yield self._isSharedOrInvited()
-        self.setShared(isShared)
</del><span class="cx"> 
</span><ins>+#    @inlineCallbacks
+#    def updateShare(self, shareeView, mode=None, status=None, summary=None):
+#        &quot;&quot;&quot;
+#        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 summary: The proposed message to go along with the share, which
+#            will be used as the default display name, or None to not update
+#        @type summary: L{str}
+#
+#        @return: the name of the shared item in the sharee's home.
+#        @rtype: a L{Deferred} which fires with a L{str}
+#        &quot;&quot;&quot;
+#        # TODO: raise a nice exception if shareeView is not, in fact, a shared
+#        # version of this same L{CommonHomeChild}
+#
+#        # remove None parameters, and substitute None for empty string
+#        bind = self._bindSchema
+#        columnMap = dict([(k, v if v != &quot;&quot; else None)
+#                          for k, v in {bind.BIND_MODE:mode,
+#                            bind.BIND_STATUS:status,
+#                            bind.MESSAGE:summary}.iteritems() if v is not None])
+#
+#        if len(columnMap):
+#
+#            # count accepted
+#            if status is not None:
+#                previouslyAcceptedBindCount = 1 if not shareeView.indirect() else 0
+#                previouslyAcceptedBindCount += len((yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
+#                        self._txn, homeID=shareeView.viewerHome()._resourceID, addressbookID=shareeView._resourceID
+#                )))
+#
+#            yield self._updateBindColumnsQuery(columnMap).on(
+#                self._txn,
+#                resourceID=self._resourceID, homeID=shareeView.viewerHome()._resourceID
+#            )
+#
+#            # update affected attributes
+#            if mode is not None:
+#                shareeView._bindMode = columnMap[bind.BIND_MODE]
+#
+#            if status is not None:
+#                shareeView._bindStatus = columnMap[bind.BIND_STATUS]
+#                if shareeView._bindStatus == _BIND_STATUS_ACCEPTED:
+#                    if 0 == previouslyAcceptedBindCount:
+#                        yield shareeView._initSyncToken()
+#                        yield shareeView._initBindRevision()
+#                        shareeView.viewerHome()._children[shareeView._name] = shareeView
+#                        shareeView.viewerHome()._children[shareeView._resourceID] = shareeView
+#                elif shareeView._bindStatus == _BIND_STATUS_DECLINED:
+#                    if 1 == previouslyAcceptedBindCount:
+#                        yield shareeView._deletedSyncToken(sharedRemoval=True)
+#                        shareeView.viewerHome()._children.pop(shareeView._name, None)
+#                        shareeView.viewerHome()._children.pop(shareeView._resourceID, None)
+#
+#            if summary is not None:
+#                shareeView._bindMessage = columnMap[bind.MESSAGE]
+#
+#            queryCacher = self._txn._queryCacher
+#            if queryCacher:
+#                cacheKey = queryCacher.keyForObjectWithName(shareeView._home._resourceID, shareeView._name)
+#                yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+#                cacheKey = queryCacher.keyForObjectWithResourceID(shareeView._home._resourceID, shareeView._resourceID)
+#                yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+#
+#            # Must send notification to ensure cache invalidation occurs
+#            yield self.notifyPropertyChanged()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-class AddressBook(CommonHomeChild, AddressBookSharingMixIn):
</del><ins>+#    @inlineCallbacks
+#    def unshareWith(self, shareeHome):
+#        &quot;&quot;&quot;
+#        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 previous shareUID
+#        &quot;&quot;&quot;
+#        sharedAddressBook = yield shareeHome.addressbookWithName(self.name())
+#        if sharedAddressBook:
+#
+#            acceptedBindCount = 1 if not sharedAddressBook.indirect() else 0
+#            acceptedBindCount += len((yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
+#                    self._txn, homeID=shareeHome._resourceID, addressbookID=sharedAddressBook._resourceID
+#            )))
+#            if acceptedBindCount == 1:
+#                yield sharedAddressBook._deletedSyncToken(sharedRemoval=True)
+#                shareeHome._children.pop(self.name(), None)
+#                shareeHome._children.pop(sharedAddressBook._resourceID, None)
+#            elif sharedAddressBook.indirect():
+#                # FIXME: remove objects for this group only using self.removeObjectResource
+#                self._objectNames = None
+#
+#            # Must send notification to ensure cache invalidation occurs
+#            yield self.notifyPropertyChanged()
+#
+#        # delete binds including invites
+#        deletedBindNameRows = yield self._deleteBindForResourceIDAndHomeID.on(self._txn, resourceID=self._resourceID,
+#             homeID=shareeHome._resourceID
+#        )
+#        if deletedBindNameRows:
+#            deletedBindName = deletedBindNameRows[0][0]
+#            queryCacher = self._txn._queryCacher
+#            if queryCacher:
+#                cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self.name())
+#                queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+#        else:
+#            deletedBindName = None
+#
+#        self._initIsShared()
+#        returnValue(deletedBindName)
+
+
+#    @inlineCallbacks
+#    def _isSharedOrInvited(self):
+#        &quot;&quot;&quot;
+#        return True if this L{AddressBook} is shared or invited
+#        &quot;&quot;&quot;
+#        sharedRows = []
+#        if self.owned():
+#            bind = self._bindSchema
+#            sharedRows = yield self._bindFor(
+#                (bind.RESOURCE_ID == Parameter(&quot;resourceID&quot;))).on(
+#                self._txn, resourceID=self._resourceID,
+#            )
+#
+#        returnValue(bool(sharedRows))
+#
+#
+#    @inlineCallbacks
+#    def _initIsShared(self):
+#        isShared = yield self._isSharedOrInvited()
+#        self.setShared(isShared)
+
+
+
+class AddressBook(AddressBookSharingMixIn, CommonHomeChild):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     SQL-based implementation of L{IAddressBook}.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -356,7 +497,7 @@
</span><span class="cx">     _objectSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None):
</del><ins>+    def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None):
</ins><span class="cx">         ownerName = ownerHome.addressbook().name() if ownerHome else None
</span><span class="cx">         super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName)
</span><span class="cx">         self._index = PostgresLegacyABIndexEmulator(self)
</span><span class="lines">@@ -394,13 +535,6 @@
</span><span class="cx">     addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def newShareName(self):
-        &quot;&quot;&quot;
-        For shared address books the resource name of a share is the ownerUID of the owner's resource.
-        &quot;&quot;&quot;
-        return self.ownerHome().uid()
-
-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _loadPropertyStore(self, props=None):
</span><span class="cx">         if props is None:
</span><span class="lines">@@ -426,13 +560,6 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def sharedResourceType(self):
-        &quot;&quot;&quot;
-        The sharing resource type
-        &quot;&quot;&quot;
-        return &quot;addressbook&quot;
-
-
</del><span class="cx">     def contentType(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         The content type of addressbook objects is text/vcard.
</span><span class="lines">@@ -491,20 +618,22 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def listObjectResources(self):
</span><ins>+        # Check for non-group shared
+        if self.owned() or not self.indirect():
+            result = yield super(AddressBook, self).listObjectResources()
+            returnValue(result)
+
+        # Group shared
</ins><span class="cx">         if self._objectNames is None:
</span><del>-            if self.owned() or self.fullyShared():
-                rows = yield self._objectResourceNamesQuery.on(
-                    self._txn, resourceID=self._resourceID)
-            else:
-                acceptedGroupIDs = yield self.acceptedGroupIDs()
-                allowedObjectIDs = yield self.expandGroupIDs(self._txn, acceptedGroupIDs)
-                rows = (yield self._objectResourceNamesWithResourceIDsQuery(allowedObjectIDs).on(
-                    self._txn, resourceIDs=allowedObjectIDs
-                ))
</del><ins>+            acceptedGroupIDs = yield self.acceptedGroupIDs()
+            allowedObjectIDs = yield self.expandGroupIDs(self._txn, acceptedGroupIDs)
+            rows = (yield self._objectResourceNamesWithResourceIDsQuery(allowedObjectIDs).on(
+                self._txn, resourceIDs=allowedObjectIDs
+            ))
</ins><span class="cx">             objectNames = [row[0] for row in rows]
</span><span class="cx"> 
</span><span class="cx">             # account for fully-shared address book group
</span><del>-            if self.fullyShared():
</del><ins>+            if not self.indirect():
</ins><span class="cx">                 if not self._groupForSharedAddressBookName() in objectNames:
</span><span class="cx">                     objectNames.append(self._groupForSharedAddressBookName())
</span><span class="cx">             self._objectNames = sorted(objectNames)
</span><span class="lines">@@ -514,18 +643,18 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def countObjectResources(self):
</span><ins>+        # Check for non-group shared
+        if self.owned() or not self.indirect():
+            result = yield super(AddressBook, self).countObjectResources()
+            returnValue(result)
+
+        # Group shared
</ins><span class="cx">         if self._objectNames is None:
</span><del>-            if self.owned() or self.fullyShared():
-                rows = yield self._objectCountQuery.on(
-                    self._txn, resourceID=self._resourceID
-                )
-                count = rows[0][0]
-            else:
-                acceptedGroupIDs = yield self.acceptedGroupIDs()
-                count = len((yield self.expandGroupIDs(self._txn, acceptedGroupIDs)))
</del><ins>+            acceptedGroupIDs = yield self.acceptedGroupIDs()
+            count = len((yield self.expandGroupIDs(self._txn, acceptedGroupIDs)))
</ins><span class="cx"> 
</span><span class="cx">             # account for fully-shared address book group
</span><del>-            if self.fullyShared():
</del><ins>+            if not self.indirect():
</ins><span class="cx">                 count += 1
</span><span class="cx">             returnValue(count)
</span><span class="cx"> 
</span><span class="lines">@@ -618,6 +747,48 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def listObjects(cls, home):
+        &quot;&quot;&quot;
+        Retrieve the names of the children with invitations in the given home. Make sure
+        to include the default owner address book.
+
+        @return: an iterable of C{str}s.
+        &quot;&quot;&quot;
+        names = yield super(AddressBook, cls).listObjects(home)
+        names.insert(0, home.addressbook().name())
+        returnValue(names)
+
+#    @classmethod
+#    @inlineCallbacks
+#    def listObjects(cls, home):
+#        &quot;&quot;&quot;
+#        Retrieve the names of the children with invitations in the given home.
+#
+#        @return: an iterable of C{str}s.
+#        &quot;&quot;&quot;
+#        names = set([home.addressbook().name()])
+#
+#        rows = yield cls._acceptedBindForHomeID.on(
+#            home._txn, homeID=home._resourceID
+#        )
+#        for row in rows:
+#            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
+#            ownerHome = yield home._txn.homeWithResourceID(home._homeType, resourceID, create=True)
+#            names |= set([ownerHome.uid()])
+#
+#        groupRows = yield AddressBookObject._acceptedBindForHomeID.on(
+#            home._txn, homeID=home._resourceID
+#        )
+#        for groupRow in groupRows:
+#            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
+#            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
+#            ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerAddressBookID, create=True)
+#            names |= set([ownerHome.uid()])
+#        returnValue(tuple(names))
+
+
+    @classmethod
+    @inlineCallbacks
</ins><span class="cx">     def loadAllObjects(cls, home):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Load all L{CommonHomeChild} instances which are children of a given
</span><span class="lines">@@ -626,78 +797,94 @@
</span><span class="cx">         operations to keep this constant wrt the number of children.  This is an
</span><span class="cx">         optimization for Depth:1 operations on the home.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        results = [home.addressbook()]
-        ownerHomeToDataRowMap = {}
</del><span class="cx"> 
</span><del>-        # Load from the main table first
-        dataRows = yield cls._childrenAndMetadataForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )
-        # get ownerHomeIDs
-        for dataRow in dataRows:
-            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
-            ownerHome = yield home.ownerHomeWithChildID(resourceID)
-            ownerHomeToDataRowMap[ownerHome] = dataRow
</del><ins>+        results = yield super(AddressBook, cls).loadAllObjects(home)
+        results.insert(0, home.addressbook())
+        returnValue(results)
</ins><span class="cx"> 
</span><del>-        # now get group rows:
-        groupBindRows = yield AddressBookObject._childrenAndMetadataForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )
-        for groupBindRow in groupBindRows:
-            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
-            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
-            ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
-            if ownerHome not in ownerHomeToDataRowMap:
-                groupBindRow[0] = _BIND_MODE_WRITE
-                groupBindRow[3] = None  # name
-                groupBindRow[4] = None  # bindStatus
-                groupBindRow[6] = None  # bindMessage
-                ownerHomeToDataRowMap[ownerHome] = groupBindRow
</del><span class="cx"> 
</span><del>-        if ownerHomeToDataRowMap:
-            # Get property stores for all these child resources (if any found)
-            addressbookPropertyStoreIDs = [ownerHome._addressbookPropertyStoreID for ownerHome in ownerHomeToDataRowMap]
-            propertyStores = yield PropertyStore.forMultipleResourcesWithResourceIDs(
-                home.uid(), home._txn, addressbookPropertyStoreIDs
-            )
</del><ins>+#    @classmethod
+#    @inlineCallbacks
+#    def loadAllObjects(cls, home):
+#        &quot;&quot;&quot;
+#        Load all L{CommonHomeChild} instances which are children of a given
+#        L{CommonHome} and return a L{Deferred} firing a list of them.  This must
+#        create the child classes and initialize them using &quot;batched&quot; SQL
+#        operations to keep this constant wrt the number of children.  This is an
+#        optimization for Depth:1 operations on the home.
+#        &quot;&quot;&quot;
+#        results = [home.addressbook()]
+#        ownerHomeToDataRowMap = {}
+#
+#        # Load from the main table first
+#        dataRows = yield cls._childrenAndMetadataForHomeID.on(
+#            home._txn, homeID=home._resourceID
+#        )
+#        # get ownerHomeIDs
+#        for dataRow in dataRows:
+#            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
+#            ownerHome = yield home.ownerHomeWithChildID(resourceID)
+#            ownerHomeToDataRowMap[ownerHome] = dataRow
+#
+#        # now get group rows:
+#        groupBindRows = yield AddressBookObject._childrenAndMetadataForHomeID.on(
+#            home._txn, homeID=home._resourceID
+#        )
+#        for groupBindRow in groupBindRows:
+#            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
+#            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
+#            ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
+#            if ownerHome not in ownerHomeToDataRowMap:
+#                groupBindRow[0] = _BIND_MODE_WRITE
+#                groupBindRow[3] = None  # name
+#                groupBindRow[4] = None  # bindStatus
+#                groupBindRow[6] = None  # bindMessage
+#                ownerHomeToDataRowMap[ownerHome] = groupBindRow
+#
+#        if ownerHomeToDataRowMap:
+#            # Get property stores for all these child resources (if any found)
+#            addressbookPropertyStoreIDs = [ownerHome._addressbookPropertyStoreID for ownerHome in ownerHomeToDataRowMap]
+#            propertyStores = yield PropertyStore.forMultipleResourcesWithResourceIDs(
+#                home.uid(), home._txn, addressbookPropertyStoreIDs
+#            )
+#
+#            addressbookResourceIDs = [ownerHome.addressbook()._resourceID for ownerHome in ownerHomeToDataRowMap]
+#            revisions = yield cls._revisionsForResourceIDs(addressbookResourceIDs).on(home._txn, resourceIDs=addressbookResourceIDs)
+#            revisions = dict(revisions)
+#
+#            # Create the actual objects merging in properties
+#            for ownerHome, dataRow in ownerHomeToDataRowMap.iteritems():
+#                bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
+#                additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+#                metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
+#
+#                child = cls(
+#                    home=home,
+#                    name=name,
+#                    resourceID=ownerHome._resourceID,
+#                    mode=bindMode,
+#                    status=bindStatus,
+#                    revision=bindRevision,
+#                    message=bindMessage,
+#                    ownerHome=ownerHome,
+#                )
+#
+#                for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
+#                    setattr(child, attr, value)
+#                for attr, value in zip(cls.metadataAttributes(), metadata):
+#                    setattr(child, attr, value)
+#                child._syncTokenRevision = revisions[child._resourceID]
+#                propstore = propertyStores.get(ownerHome._addressbookPropertyStoreID, 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 propstore:
+#                    propstore._setDefaultUserUID(ownerHome.uid())
+#                yield child._loadPropertyStore(propstore)
+#                results.append(child)
+#
+#        returnValue(results)
</ins><span class="cx"> 
</span><del>-            addressbookResourceIDs = [ownerHome.addressbook()._resourceID for ownerHome in ownerHomeToDataRowMap]
-            revisions = yield cls._revisionsForResourceIDs(addressbookResourceIDs).on(home._txn, resourceIDs=addressbookResourceIDs)
-            revisions = dict(revisions)
</del><span class="cx"> 
</span><del>-            # Create the actual objects merging in properties
-            for ownerHome, dataRow in ownerHomeToDataRowMap.iteritems():
-                bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
-                additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
-                metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
-
-                child = cls(
-                    home=home,
-                    name=name,
-                    resourceID=ownerHome._resourceID,
-                    mode=bindMode,
-                    status=bindStatus,
-                    revision=bindRevision,
-                    message=bindMessage,
-                    ownerHome=ownerHome,
-                )
-
-                for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
-                    setattr(child, attr, value)
-                for attr, value in zip(cls.metadataAttributes(), metadata):
-                    setattr(child, attr, value)
-                child._syncTokenRevision = revisions[child._resourceID]
-                propstore = propertyStores.get(ownerHome._addressbookPropertyStoreID, 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 propstore:
-                    propstore._setDefaultUserUID(ownerHome.uid())
-                yield child._loadPropertyStore(propstore)
-                results.append(child)
-
-        returnValue(results)
-
-
</del><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def objectWithName(cls, home, name, accepted=True):
</span><span class="lines">@@ -709,78 +896,99 @@
</span><span class="cx"> 
</span><span class="cx">         @param name: a string; the name of the L{CommonHomeChild} to retrieve.
</span><span class="cx"> 
</span><del>-        @return: an L{CommonHomeChild} or C{None} if no such child
-            exists.
</del><ins>+        @return: an L{CommonHomeChild} or C{None} if no such child exists.
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        if accepted and name == home.addressbook().name():
</del><ins>+
+        if name == home.addressbook().name():
</ins><span class="cx">             returnValue(home.addressbook())
</span><del>-        # shared address books only from this point on
</del><span class="cx"> 
</span><del>-        rows = None
-        queryCacher = home._txn._queryCacher
-        ownerHome = None
</del><ins>+        result = yield super(AddressBook, cls).objectWithName(home, name, accepted)
+        returnValue(result)
</ins><span class="cx"> 
</span><del>-        if queryCacher:
-            # Retrieve data from cache
-            cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
-            rows = yield queryCacher.get(cacheKey)
</del><span class="cx"> 
</span><del>-        if not rows:
-            # name must be a home uid
-            ownerHome = yield home._txn.addressbookHomeWithUID(name)
-            if ownerHome:
-                # see if address book resource id in bind table
-                ownerAddressBook = ownerHome.addressbook()
-                bindRows = yield cls._bindForResourceIDAndHomeID.on(
-                    home._txn, resourceID=ownerAddressBook._resourceID, homeID=home._resourceID
-                )
-                if bindRows:
-                    bindRows[0].insert(cls.bindColumnCount, ownerAddressBook._resourceID)
-                    bindRows[0].insert(cls.bindColumnCount + 1, bindRows[0][4])  # cachedStatus = bindStatus
-                    rows = bindRows
-                else:
-                    groupBindRows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
-                            home._txn, homeID=home._resourceID, addressbookID=ownerAddressBook._resourceID
-                    )
-                    for groupBindRow in groupBindRows:
-                        groupBindRow.insert(AddressBookObject.bindColumnCount, ownerAddressBook._resourceID)
-                        groupBindRow.insert(AddressBookObject.bindColumnCount + 1, groupBindRow[4])
-                        groupBindRow[0] = _BIND_MODE_WRITE
-                        groupBindRow[3] = ownerHome.uid()  # bindName
-                        groupBindRow[4] = None  # bindStatus
-                        groupBindRow[6] = None  # bindMessage
-                        rows = [groupBindRow]
-                        break
</del><ins>+#    @classmethod
+#    @inlineCallbacks
+#    def objectWithName(cls, home, name, accepted=True):
+#        &quot;&quot;&quot;
+#        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.
+#
+#        @return: an L{CommonHomeChild} or C{None} if no such child
+#            exists.
+#        &quot;&quot;&quot;
+#        if accepted and name == home.addressbook().name():
+#            returnValue(home.addressbook())
+#        # shared address books only from this point on
+#
+#        rows = None
+#        queryCacher = home._txn._queryCacher
+#        ownerHome = None
+#
+#        if queryCacher:
+#            # Retrieve data from cache
+#            cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
+#            rows = yield queryCacher.get(cacheKey)
+#
+#        if not rows:
+#            # name must be a home uid
+#            ownerHome = yield home._txn.addressbookHomeWithUID(name)
+#            if ownerHome:
+#                # see if address book resource id in bind table
+#                ownerAddressBook = ownerHome.addressbook()
+#                bindRows = yield cls._bindForResourceIDAndHomeID.on(
+#                    home._txn, resourceID=ownerAddressBook._resourceID, homeID=home._resourceID
+#                )
+#                if bindRows:
+#                    bindRows[0].insert(cls.bindColumnCount, ownerAddressBook._resourceID)
+#                    bindRows[0].insert(cls.bindColumnCount + 1, bindRows[0][4])  # cachedStatus = bindStatus
+#                    rows = bindRows
+#                else:
+#                    groupBindRows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
+#                            home._txn, homeID=home._resourceID, addressbookID=ownerAddressBook._resourceID
+#                    )
+#                    for groupBindRow in groupBindRows:
+#                        groupBindRow.insert(AddressBookObject.bindColumnCount, ownerAddressBook._resourceID)
+#                        groupBindRow.insert(AddressBookObject.bindColumnCount + 1, groupBindRow[4])
+#                        groupBindRow[0] = _BIND_MODE_WRITE
+#                        groupBindRow[3] = ownerHome.uid()  # bindName
+#                        groupBindRow[4] = None  # bindStatus
+#                        groupBindRow[6] = None  # bindMessage
+#                        rows = [groupBindRow]
+#                        break
+#
+#            if rows and queryCacher:
+#                # Cache the result
+#                queryCacher.setAfterCommit(home._txn, cacheKey, rows)
+#
+#        if not rows:
+#            returnValue(None)
+#
+#        row = rows[0]
+#        bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage, ownerAddressBookID, cachedBindStatus = row[:cls.bindColumnCount + 2] #@UnusedVariable
+#
+#        # if wrong status, exit here.  Item is in queryCache
+#        if accepted is not None and (cachedBindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
+#            returnValue(None)
+#
+#        ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
+#        child = cls(
+#                home=home,
+#                name=name,
+#                resourceID=ownerAddressBookID,
+#                mode=bindMode,
+#                status=bindStatus,
+#                revision=bindRevision,
+#                message=bindMessage,
+#                ownerHome=ownerHome,
+#            )
+#        yield child.initFromStore()
+#        returnValue(child)
</ins><span class="cx"> 
</span><del>-            if rows and queryCacher:
-                # Cache the result
-                queryCacher.setAfterCommit(home._txn, cacheKey, rows)
</del><span class="cx"> 
</span><del>-        if not rows:
-            returnValue(None)
-
-        row = rows[0]
-        bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage, ownerAddressBookID, cachedBindStatus = row[:cls.bindColumnCount + 2] #@UnusedVariable
-
-        # if wrong status, exit here.  Item is in queryCache
-        if accepted is not None and (cachedBindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
-            returnValue(None)
-
-        ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
-        child = cls(
-                home=home,
-                name=name,
-                resourceID=ownerAddressBookID,
-                mode=bindMode,
-                status=bindStatus,
-                revision=bindRevision,
-                message=bindMessage,
-                ownerHome=ownerHome,
-            )
-        yield child.initFromStore()
-        returnValue(child)
-
-
</del><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def objectWithID(cls, home, resourceID, accepted=True):
</span><span class="lines">@@ -796,91 +1004,79 @@
</span><span class="cx">         if home._resourceID == resourceID:
</span><span class="cx">             returnValue(home.addressbook())
</span><span class="cx"> 
</span><del>-        bindRows = yield cls._bindForResourceIDAndHomeID.on(
-            home._txn, resourceID=resourceID, homeID=home._resourceID
-        )
-        if bindRows:
-            bindRow = bindRows[0]
-            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = bindRow[:cls.bindColumnCount] #@UnusedVariable
</del><ins>+        result = yield super(AddressBook, cls).objectWithID(home, resourceID, accepted)
+        returnValue(result)
</ins><span class="cx"> 
</span><del>-            if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
-                returnValue(None)
</del><span class="cx"> 
</span><del>-            ownerHome = yield home.ownerHomeWithChildID(resourceID)
-            if bindStatus == _BIND_STATUS_ACCEPTED:
-                returnValue((yield home.childWithName(ownerHome.uid())))
-            else:
-                returnValue((yield cls.objectWithName(home, ownerHome.uid(), accepted=False)))
</del><ins>+#    @classmethod
+#    @inlineCallbacks
+#    def objectWithID(cls, home, resourceID, accepted=True):
+#        &quot;&quot;&quot;
+#        Retrieve the child with the given C{resourceID} contained in the given
+#        C{home}.
+#
+#        @param home: a L{CommonHome}.
+#        @param resourceID: a string.
+#        @return: an L{CommonHomeChild} or C{None} if no such child
+#            exists.
+#        &quot;&quot;&quot;
+#        if home._resourceID == resourceID:
+#            returnValue(home.addressbook())
+#
+#        bindRows = yield cls._bindForResourceIDAndHomeID.on(
+#            home._txn, resourceID=resourceID, homeID=home._resourceID
+#        )
+#        if bindRows:
+#            bindRow = bindRows[0]
+#            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = bindRow[:cls.bindColumnCount] #@UnusedVariable
+#
+#            if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
+#                returnValue(None)
+#
+#            ownerHome = yield home.ownerHomeWithChildID(resourceID)
+#            if bindStatus == _BIND_STATUS_ACCEPTED:
+#                returnValue((yield home.childWithName(ownerHome.uid())))
+#            else:
+#                returnValue((yield cls.objectWithName(home, ownerHome.uid(), accepted=False)))
+#
+#        groupBindRows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
+#                    home._txn, homeID=home._resourceID, addressbookID=resourceID
+#        )
+#        if groupBindRows:
+#            groupBindRow = groupBindRows[0]
+#            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
+#
+#            if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
+#                returnValue(None)
+#
+#            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
+#            ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
+#            if bindStatus == _BIND_STATUS_ACCEPTED:
+#                returnValue((yield home.childWithName(ownerHome.uid())))
+#            else:
+#                returnValue((yield cls.objectWithName(home, ownerHome.uid(), accepted=False)))
+#
+#        returnValue(None)
</ins><span class="cx"> 
</span><del>-        groupBindRows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
-                    home._txn, homeID=home._resourceID, addressbookID=resourceID
-        )
-        if groupBindRows:
-            groupBindRow = groupBindRows[0]
-            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><span class="cx"> 
</span><del>-            if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
-                returnValue(None)
-
-            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
-            ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
-            if bindStatus == _BIND_STATUS_ACCEPTED:
-                returnValue((yield home.childWithName(ownerHome.uid())))
-            else:
-                returnValue((yield cls.objectWithName(home, ownerHome.uid(), accepted=False)))
-
-        returnValue(None)
-
-
-    def fullyShared(self):
-        return not self.owned() and self._bindStatus == _BIND_STATUS_ACCEPTED
-
-
</del><span class="cx">     @classmethod
</span><del>-    @inlineCallbacks
-    def listObjects(cls, home):
-        &quot;&quot;&quot;
-        Retrieve the names of the children with invitations in the given home.
-
-        @return: an iterable of C{str}s.
-        &quot;&quot;&quot;
-        names = set([home.addressbook().name()])
-
-        rows = yield cls._acceptedBindForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )
-        for row in rows:
-            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
-            ownerHome = yield home._txn.homeWithResourceID(home._homeType, resourceID, create=True)
-            names |= set([ownerHome.uid()])
-
-        groupRows = yield AddressBookObject._acceptedBindForHomeID.on(
-            home._txn, homeID=home._resourceID
-        )
-        for groupRow in groupRows:
-            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
-            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
-            ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerAddressBookID, create=True)
-            names |= set([ownerHome.uid()])
-        returnValue(tuple(names))
-
-
-    @classmethod
</del><span class="cx">     def _memberIDsWithGroupIDsQuery(cls, groupIDs):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DAL query to load all object resource names for a home child.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         aboMembers = schema.ABO_MEMBERS
</span><del>-        return Select([aboMembers.MEMBER_ID], From=aboMembers,
-                      Where=aboMembers.GROUP_ID.In(Parameter(&quot;groupIDs&quot;, len(groupIDs))),
-                      )
</del><ins>+        return Select(
+            [aboMembers.MEMBER_ID],
+            From=aboMembers,
+            Where=aboMembers.GROUP_ID.In(Parameter(&quot;groupIDs&quot;, len(groupIDs))),
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def expandGroupIDs(cls, txn, groupIDs, includeGroupIDs=True):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Get all AddressBookObject resource IDs contains in the given shared groups with the given groupIDs
</del><ins>+        Get all AddressBookObject resource IDs contained in the given shared groups with the given groupIDs
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         objectIDs = set(groupIDs) if includeGroupIDs else set()
</span><span class="cx">         examinedIDs = set()
</span><span class="lines">@@ -898,33 +1094,44 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def unacceptedGroupIDs(self):
</span><ins>+        &quot;&quot;&quot;
+        Return the list of shared groups that have not yet been accepted.
+        &quot;&quot;&quot;
</ins><span class="cx">         if self.owned():
</span><span class="cx">             returnValue([])
</span><span class="cx">         else:
</span><span class="cx">             groupBindRows = yield AddressBookObject._unacceptedBindForHomeIDAndAddressBookID.on(
</span><del>-                    self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</del><ins>+                self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</ins><span class="cx">             )
</span><span class="cx">             returnValue([groupBindRow[2] for groupBindRow in groupBindRows])
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def acceptedGroupIDs(self):
</span><ins>+        &quot;&quot;&quot;
+        Return the list of accepted shared groups.
+        &quot;&quot;&quot;
</ins><span class="cx">         if self.owned():
</span><span class="cx">             returnValue([])
</span><span class="cx">         else:
</span><span class="cx">             groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
</span><del>-                    self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</del><ins>+                self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</ins><span class="cx">             )
</span><span class="cx">             returnValue([groupBindRow[2] for groupBindRow in groupBindRows])
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def accessControlGroupIDs(self):
</span><ins>+        &quot;&quot;&quot;
+        For each accepted shared group, determine what its access mode is and return the sets of read-only
+        and read-write groups. Handle the case where a read-only group is actually nested in a read-write
+        group by putting the read-only one into the read-write list.
+        &quot;&quot;&quot;
</ins><span class="cx">         if self.owned():
</span><span class="cx">             returnValue(([], []))
</span><span class="cx">         else:
</span><span class="cx">             groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
</span><del>-                    self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</del><ins>+                self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</ins><span class="cx">             )
</span><span class="cx">             readWriteGroupIDs = []
</span><span class="cx">             readOnlyGroupIDs = []
</span><span class="lines">@@ -960,19 +1167,32 @@
</span><span class="cx">     # FIXME: Unused:  Use for caching access
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def accessControlObjectIDs(self):
</span><ins>+        &quot;&quot;&quot;
+        For each object resource in this collection, determine what its access mode is and return the sets of read-only
+        and read-write objects. Handle the case where a read-only group is actually nested in a read-write
+        group by putting the read-only one into the read-write list.
+        &quot;&quot;&quot;
+
</ins><span class="cx">         readOnlyIDs = set()
</span><span class="cx">         readWriteIDs = set()
</span><del>-        if self.owned() or self.fullyShared():
-            rows = yield self._allColumnsWithParent(self)
-            ids = set([row[1] for row in rows])
-            if self.fullyShared():
-                ids |= set([self._resourceID, ])
-            if self.owned() or self._bindMode == _BIND_MODE_WRITE:
-                returnValue(tuple(readOnlyIDs), tuple(readWriteIDs))
</del><ins>+
+        # All objects in the collection
+        rows = yield self._allColumnsWithParent(self)
+        ids = set([row[1] for row in rows])
+
+        # Everything is read-write
+        if self.owned() or self._bindMode == _BIND_MODE_WRITE:
+            returnValue(tuple(readOnlyIDs), tuple(ids))
+
+        # Fully shared but mode is read-only
+        if self._bindMode == _BIND_MODE_READ:
+            ids |= set([self._resourceID, ])
</ins><span class="cx">             readOnlyIDs = set(ids)
</span><span class="cx"> 
</span><ins>+        # Look for shared groups and for those that tare read-write, transfer their object ids
+        # to the read-write set
</ins><span class="cx">         groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
</span><del>-                self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</del><ins>+            self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</ins><span class="cx">         )
</span><span class="cx">         readWriteGroupIDs = []
</span><span class="cx">         readOnlyGroupIDs = []
</span><span class="lines">@@ -1010,142 +1230,37 @@
</span><span class="cx">         returnValue((readOnlyIDs + readWriteIDs))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def updateShare(self, shareeView, mode=None, status=None, summary=None):
-        &quot;&quot;&quot;
-        Update share mode, status, and message for a home child shared with
-        this (owned) L{CommonHomeChild}.
</del><span class="cx"> 
</span><del>-        @param shareeView: The sharee home child that shares this.
-        @type shareeView: L{CommonHomeChild}
</del><ins>+class AddressBookObjectSharingMixIn(SharingMixIn):
+    &quot;&quot;&quot;
+        Sharing code for AddressBookObject
+    &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-        @param mode: The sharing mode; L{_BIND_MODE_READ} or
-            L{_BIND_MODE_WRITE} or None to not update
-        @type mode: L{str}
</del><ins>+#    @inlineCallbacks
+#    def _isSharedOrInvited(self):
+#        &quot;&quot;&quot;
+#        return True if this L{AddressBook} is shared or invited
+#        &quot;&quot;&quot;
+#        sharedRows = []
+#        if self.owned():
+#            bind = self._bindSchema
+#            sharedRows = yield self._bindFor(
+#                (bind.RESOURCE_ID == Parameter(&quot;resourceID&quot;))).on(
+#                self._txn, resourceID=self._resourceID,
+#            )
+#
+#        returnValue(bool(sharedRows))
+#
+#
+#    @inlineCallbacks
+#    def _initIsShared(self):
+#        isShared = yield self._isSharedOrInvited()
+#        self.setShared(isShared)
</ins><span class="cx"> 
</span><del>-        @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}
</del><span class="cx"> 
</span><del>-        @param summary: The proposed message to go along with the share, which
-            will be used as the default display name, or None to not update
-        @type summary: L{str}
</del><span class="cx"> 
</span><del>-        @return: the name of the shared item in the sharee's home.
-        @rtype: a L{Deferred} which fires with a L{str}
-        &quot;&quot;&quot;
-        # TODO: raise a nice exception if shareeView is not, in fact, a shared
-        # version of this same L{CommonHomeChild}
</del><ins>+class AddressBookObject(CommonObjectResource, AddressBookObjectSharingMixIn):
</ins><span class="cx"> 
</span><del>-        # remove None parameters, and substitute None for empty string
-        bind = self._bindSchema
-        columnMap = dict([(k, v if v != &quot;&quot; else None)
-                          for k, v in {bind.BIND_MODE:mode,
-                            bind.BIND_STATUS:status,
-                            bind.MESSAGE:summary}.iteritems() if v is not None])
-
-        if len(columnMap):
-
-            # count accepted
-            if status is not None:
-                previouslyAcceptedBindCount = 1 if shareeView.fullyShared() else 0
-                previouslyAcceptedBindCount += len((yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
-                        self._txn, homeID=shareeView.viewerHome()._resourceID, addressbookID=shareeView._resourceID
-                )))
-
-            bindNameRows = yield self._updateBindColumnsQuery(columnMap).on(
-                self._txn,
-                resourceID=self._resourceID, homeID=shareeView.viewerHome()._resourceID
-            )
-
-            # update affected attributes
-            if mode is not None:
-                shareeView._bindMode = columnMap[bind.BIND_MODE]
-
-            if status is not None:
-                shareeView._bindStatus = columnMap[bind.BIND_STATUS]
-                if shareeView._bindStatus == _BIND_STATUS_ACCEPTED:
-                    if 0 == previouslyAcceptedBindCount:
-                        yield shareeView._initSyncToken()
-                        yield shareeView._initBindRevision()
-                        shareeView.viewerHome()._children[shareeView._name] = shareeView
-                        shareeView.viewerHome()._children[shareeView._resourceID] = shareeView
-                elif shareeView._bindStatus == _BIND_STATUS_DECLINED:
-                    if 1 == previouslyAcceptedBindCount:
-                        yield shareeView._deletedSyncToken(sharedRemoval=True)
-                        shareeView.viewerHome()._children.pop(shareeView._name, None)
-                        shareeView.viewerHome()._children.pop(shareeView._resourceID, None)
-
-            if summary is not None:
-                shareeView._bindMessage = columnMap[bind.MESSAGE]
-
-            queryCacher = self._txn._queryCacher
-            if queryCacher:
-                cacheKey = queryCacher.keyForObjectWithName(shareeView._home._resourceID, shareeView._name)
-                yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
-                cacheKey = queryCacher.keyForObjectWithResourceID(shareeView._home._resourceID, shareeView._resourceID)
-                yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
-
-            shareeView._name = bindNameRows[0][0]
-
-            # Must send notification to ensure cache invalidation occurs
-            yield self.notifyPropertyChanged()
-
-        returnValue(shareeView._name)
-
-
-    @inlineCallbacks
-    def unshareWith(self, shareeHome):
-        &quot;&quot;&quot;
-        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 previous shareUID
-        &quot;&quot;&quot;
-        sharedAddressBook = yield shareeHome.addressbookWithName(self.name())
-        if sharedAddressBook:
-
-            acceptedBindCount = 1 if sharedAddressBook.fullyShared() else 0
-            acceptedBindCount += len((yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
-                    self._txn, homeID=shareeHome._resourceID, addressbookID=sharedAddressBook._resourceID
-            )))
-            if acceptedBindCount == 1:
-                yield sharedAddressBook._deletedSyncToken(sharedRemoval=True)
-                shareeHome._children.pop(self.name(), None)
-                shareeHome._children.pop(sharedAddressBook._resourceID, None)
-            elif not sharedAddressBook.fullyShared():
-                # FIXME: remove objects for this group only using self.removeObjectResource
-                self._objectNames = None
-
-            # Must send notification to ensure cache invalidation occurs
-            yield self.notifyPropertyChanged()
-
-        # delete binds including invites
-        deletedBindNameRows = yield self._deleteBindForResourceIDAndHomeID.on(self._txn, resourceID=self._resourceID,
-             homeID=shareeHome._resourceID
-        )
-        if deletedBindNameRows:
-            deletedBindName = deletedBindNameRows[0][0]
-            queryCacher = self._txn._queryCacher
-            if queryCacher:
-                cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self.name())
-                queryCacher.invalidateAfterCommit(self._txn, cacheKey)
-        else:
-            deletedBindName = None
-
-        self._initIsShared()
-        returnValue(deletedBindName)
-
-
-
-class AddressBookObject(CommonObjectResource, AddressBookSharingMixIn):
-
</del><span class="cx">     implements(IAddressBookObject)
</span><span class="cx"> 
</span><span class="cx">     _homeSchema = schema.ADDRESSBOOK_HOME
</span><span class="lines">@@ -1213,7 +1328,7 @@
</span><span class="cx">             if self.isGroupForSharedAddressBook() or self.shareUID():
</span><span class="cx">                 raise HTTPError(FORBIDDEN)
</span><span class="cx"> 
</span><del>-        if not self.owned() and not self.addressbook().fullyShared():
</del><ins>+        if not self.owned() and self.addressbook().indirect():
</ins><span class="cx">             readWriteObjectIDs = []
</span><span class="cx">             readWriteGroupIDs = yield self.addressbook().readWriteGroupIDs()
</span><span class="cx">             if readWriteGroupIDs:
</span><span class="lines">@@ -1264,7 +1379,7 @@
</span><span class="cx">             returnValue(False)
</span><span class="cx"> 
</span><span class="cx">         # if fully shared and rw, must be RW since sharing group read-only has no affect
</span><del>-        if self.addressbook().fullyShared() and self.addressbook().shareMode() == _BIND_MODE_WRITE:
</del><ins>+        if not self.addressbook().indirect() and self.addressbook().shareMode() == _BIND_MODE_WRITE:
</ins><span class="cx">             returnValue(True)
</span><span class="cx"> 
</span><span class="cx">         #otherwise, must be in a read-write group
</span><span class="lines">@@ -1315,34 +1430,9 @@
</span><span class="cx"> 
</span><span class="cx">         @return: L{self} if object exists in the DB, else C{None}
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        rows = None
-        if self.owned() or self.addressbook().fullyShared():  # owned or fully shared
-            if self._name:
-                rows = yield self._allColumnsWithParentAndName.on(
-                    self._txn, name=self._name,
-                    parentID=self._parentCollection._resourceID
-                )
-            elif self._uid:
-                rows = yield self._allColumnsWithParentAndUID.on(
-                    self._txn, uid=self._uid,
-                    parentID=self._parentCollection._resourceID
-                )
-            elif self._resourceID:
-                rows = yield self._allColumnsWithParentAndID.on(
-                    self._txn, resourceID=self._resourceID,
-                    parentID=self._parentCollection._resourceID
-                )
-
-            if not rows and self.addressbook().fullyShared():  # perhaps add special group
-                if self._name:
-                    if self._name == self.addressbook()._groupForSharedAddressBookName():
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
-                elif self._uid:
-                    if self._uid == (yield self.addressbook()._groupForSharedAddressBookUID()):
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
-                elif self._resourceID:
-                    if self.isGroupForSharedAddressBook():
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
</del><ins>+        abo = None
+        if self.owned() or not self.addressbook().indirect():  # owned or fully shared
+            abo = yield super(AddressBookObject, self).initFromStore()
</ins><span class="cx">         else:
</span><span class="cx">             acceptedGroupIDs = yield self.addressbook().acceptedGroupIDs()
</span><span class="cx">             allowedObjectIDs = yield self.addressbook().expandGroupIDs(self._txn, acceptedGroupIDs)
</span><span class="lines">@@ -1366,10 +1456,12 @@
</span><span class="cx">                     rows = (yield self._allColumnsWithResourceID.on(
</span><span class="cx">                         self._txn, resourceID=self._resourceID,
</span><span class="cx">                     ))
</span><ins>+            if rows:
+                self._initFromRow(tuple(rows[0]))
+                yield self._loadPropertyStore()
+                abo = self
</ins><span class="cx"> 
</span><del>-        if rows:
-            self._initFromRow(tuple(rows[0]))
-
</del><ins>+        if abo is not None:
</ins><span class="cx">             if self._kind == _ABO_KIND_GROUP:
</span><span class="cx"> 
</span><span class="cx">                 groupBindRows = yield AddressBookObject._bindForResourceIDAndHomeID.on(
</span><span class="lines">@@ -1384,10 +1476,8 @@
</span><span class="cx">                     self._bindMessage = bindMessage
</span><span class="cx">                     self._bindName = bindName
</span><span class="cx"> 
</span><del>-                yield self._initIsShared()
</del><ins>+                #yield self._initIsShared()
</ins><span class="cx"> 
</span><del>-            yield self._loadPropertyStore()
-
</del><span class="cx">             returnValue(self)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -1442,10 +1532,8 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _allColumnsWithParent(cls, addressbook):
</span><del>-        if addressbook.owned() or addressbook.fullyShared():
</del><ins>+        if addressbook.owned() or not addressbook.indirect():
</ins><span class="cx">             rows = yield super(AddressBookObject, cls)._allColumnsWithParent(addressbook)
</span><del>-            if addressbook.fullyShared():
-                rows.append(addressbook._groupForSharedAddressBookRow())
</del><span class="cx">         else:
</span><span class="cx">             acceptedGroupIDs = yield addressbook.acceptedGroupIDs()
</span><span class="cx">             allowedObjectIDs = yield addressbook.expandGroupIDs(addressbook._txn, acceptedGroupIDs)
</span><span class="lines">@@ -1467,10 +1555,8 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _allColumnsWithParentAndNames(cls, addressbook, names):
</span><span class="cx"> 
</span><del>-        if addressbook.owned() or addressbook.fullyShared():
</del><ins>+        if addressbook.owned() or not addressbook.indirect():
</ins><span class="cx">             rows = yield super(AddressBookObject, cls)._allColumnsWithParentAndNames(addressbook, names)
</span><del>-            if addressbook.fullyShared() and addressbook._groupForSharedAddressBookName() in names:
-                rows.append(addressbook._groupForSharedAddressBookRow())
</del><span class="cx">         else:
</span><span class="cx">             acceptedGroupIDs = yield addressbook.acceptedGroupIDs()
</span><span class="cx">             allowedObjectIDs = yield addressbook.expandGroupIDs(addressbook._txn, acceptedGroupIDs)
</span><span class="lines">@@ -1563,7 +1649,7 @@
</span><span class="cx">                 raise InvalidUIDError(&quot;Cannot change the UID in an existing resource.&quot;)
</span><span class="cx">         else:
</span><span class="cx">             # for partially shared addressbooks, cannot use name that already exists in owner
</span><del>-            if not self.owned() and not self.addressbook().fullyShared():
</del><ins>+            if not self.owned() and self.addressbook().indirect():
</ins><span class="cx">                 nameElsewhere = (yield self.ownerHome().addressbook().addressbookObjectWithName(self.name()))
</span><span class="cx">                 if nameElsewhere is not None:
</span><span class="cx">                     raise ObjectResourceNameAlreadyExistsError(self.name() + ' in use by owning addressbook.')
</span><span class="lines">@@ -1698,7 +1784,7 @@
</span><span class="cx">             foundUIDs.append(self._uid) # circular self reference is OK
</span><span class="cx">             missingUIDs = set(memberUIDs) - set(foundUIDs)
</span><span class="cx"> 
</span><del>-            if not self.owned() and not self.addressbook().fullyShared():
</del><ins>+            if not self.owned() and self.addressbook().indirect():
</ins><span class="cx">                 # in partially shared addressbook, all members UIDs must be inside the shared groups
</span><span class="cx">                 # except during bulk operations, when other UIDs added are OK
</span><span class="cx">                 coaddedUIDs = set() if self._options.get(&quot;coaddedUIDs&quot;) is None else self._options[&quot;coaddedUIDs&quot;]
</span><span class="lines">@@ -1765,7 +1851,7 @@
</span><span class="cx">             ).on(self._txn)
</span><span class="cx">             groupIDs = set([groupIDRow[0] for groupIDRow in groupIDRows])
</span><span class="cx"> 
</span><del>-            if not self.owned() and not self.addressbook().fullyShared():
</del><ins>+            if not self.owned() and self.addressbook().indirect():
</ins><span class="cx">                 readWriteGroupIDs = yield self.addressbook().readWriteGroupIDs()
</span><span class="cx">                 assert readWriteGroupIDs, &quot;no access&quot;
</span><span class="cx">                 groupIDs |= set(readWriteGroupIDs)
</span><span class="lines">@@ -2077,7 +2163,7 @@
</span><span class="cx"> 
</span><span class="cx">         if sharedAddressBook:
</span><span class="cx"> 
</span><del>-            acceptedBindCount = 1 if sharedAddressBook.fullyShared() else 0
</del><ins>+            acceptedBindCount = 1 if not sharedAddressBook.indirect() else 0
</ins><span class="cx">             acceptedBindCount += len((
</span><span class="cx">                 yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
</span><span class="cx">                     self._txn, homeID=shareeHome._resourceID, addressbookID=sharedAddressBook._resourceID
</span><span class="lines">@@ -2106,7 +2192,7 @@
</span><span class="cx">         else:
</span><span class="cx">             deletedBindName = None
</span><span class="cx"> 
</span><del>-        yield self._initIsShared()
</del><ins>+        #yield self._initIsShared()
</ins><span class="cx">         returnValue(deletedBindName)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -2234,7 +2320,7 @@
</span><span class="cx"> 
</span><span class="cx">             # count accepted
</span><span class="cx">             if status is not None:
</span><del>-                previouslyAcceptedBindCount = 1 if self.addressbook().fullyShared() else 0
</del><ins>+                previouslyAcceptedBindCount = 1 if not self.addressbook().indirect() else 0
</ins><span class="cx">                 previouslyAcceptedBindCount += len((
</span><span class="cx">                     yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
</span><span class="cx">                         self._txn, homeID=shareeView.viewerHome()._resourceID, addressbookID=self.addressbook()._resourceID
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboosharinginthestoretxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql.py (11962 => 11963)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql.py        2013-11-18 19:24:16 UTC (rev 11962)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql.py        2013-11-18 21:59:19 UTC (rev 11963)
</span><span class="lines">@@ -64,7 +64,8 @@
</span><span class="cx"> from txdav.common.datastore.common import HomeChildBase
</span><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
</span><span class="cx">     _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, _BIND_STATUS_INVALID, \
</span><del>-    _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_DELETED
</del><ins>+    _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_DELETED, \
+    _BIND_MODE_INDIRECT
</ins><span class="cx"> from txdav.common.datastore.sql_tables import schema, splitSQLString
</span><span class="cx"> from txdav.common.icommondatastore import ConcurrentModification
</span><span class="cx"> from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
</span><span class="lines">@@ -1516,10 +1517,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><del>-    def _resourceIDFromOwnerQuery(cls): #@NoSelf
</del><ins>+    def _homeColumnsFromOwnerQuery(cls): #@NoSelf
</ins><span class="cx">         home = cls._homeSchema
</span><del>-        return Select([home.RESOURCE_ID],
-                      From=home, Where=home.OWNER_UID == Parameter(&quot;ownerUID&quot;))
</del><ins>+        return Select(
+            cls.homeColumns(),
+            From=home,
+            Where=home.OWNER_UID == Parameter(&quot;ownerUID&quot;)
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><span class="lines">@@ -1539,6 +1543,34 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><ins>+    def homeColumns(cls):
+        &quot;&quot;&quot;
+        Return a list of column names to retrieve when doing an ownerUID-&gt;home lookup.
+        &quot;&quot;&quot;
+
+        # Common behavior is to have created and modified
+
+        return (
+            cls._homeSchema.RESOURCE_ID,
+            cls._homeSchema.OWNER_UID,
+        )
+
+
+    @classmethod
+    def homeAttributes(cls):
+        &quot;&quot;&quot;
+        Return a list of attributes names to map L{homeColumns} to.
+        &quot;&quot;&quot;
+
+        # Common behavior is to have created and modified
+
+        return (
+            &quot;_resourceID&quot;,
+            &quot;_ownerUID&quot;,
+        )
+
+
+    @classmethod
</ins><span class="cx">     def metadataColumns(cls):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return a list of column name for retrieval of metadata. This allows
</span><span class="lines">@@ -1579,13 +1611,14 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         result = yield self._cacher.get(self._ownerUID)
</span><span class="cx">         if result is None:
</span><del>-            result = yield self._resourceIDFromOwnerQuery.on(
</del><ins>+            result = yield self._homeColumnsFromOwnerQuery.on(
</ins><span class="cx">                 self._txn, ownerUID=self._ownerUID)
</span><span class="cx">             if result and not no_cache:
</span><span class="cx">                 yield self._cacher.set(self._ownerUID, result)
</span><span class="cx"> 
</span><span class="cx">         if result:
</span><del>-            self._resourceID = result[0][0]
</del><ins>+            for attr, value in zip(self.homeAttributes(), result[0]):
+                setattr(self, attr, value)
</ins><span class="cx"> 
</span><span class="cx">             queryCacher = self._txn._queryCacher
</span><span class="cx">             if queryCacher:
</span><span class="lines">@@ -2929,6 +2962,26 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def indirectShareWithUser(self, shareeUID):
+        &quot;&quot;&quot;
+        Create a indirect share with the specified user. An indirect share is one created as a
+        side-effect of some other object being shared.
+
+        NB no invitations are used with indirect sharing.
+
+        @param shareeUID: UID of the sharee
+        @type shareeUID: C{str}
+        &quot;&quot;&quot;
+
+        # Ignore if it already exists
+        shareeView = yield self.shareeView(shareeUID)
+        if shareeView is None:
+            shareeView = yield self.createShare(shareeUID=shareeUID, mode=_BIND_MODE_INDIRECT)
+            yield shareeView.newShare()
+        returnValue(shareeView)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def uninviteUserFromShare(self, shareeUID):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Remove a user from a share. Make sure a notification is sent as well.
</span><span class="lines">@@ -2942,10 +2995,11 @@
</span><span class="cx">         if shareeView is not None:
</span><span class="cx">             # If current user state is accepted then we send an invite with the new state, otherwise
</span><span class="cx">             # we cancel any existing invites for the user
</span><del>-            if shareeView.shareStatus() != _BIND_STATUS_ACCEPTED:
-                yield self._removeInviteNotification(shareeView)
-            else:
-                yield self._sendInviteNotification(shareeView, notificationState=_BIND_STATUS_DELETED)
</del><ins>+            if shareeView.useInvite():
+                if shareeView.shareStatus() != _BIND_STATUS_ACCEPTED:
+                    yield self._removeInviteNotification(shareeView)
+                else:
+                    yield self._sendInviteNotification(shareeView, notificationState=_BIND_STATUS_DELETED)
</ins><span class="cx"> 
</span><span class="cx">             # Remove the bind
</span><span class="cx">             yield self.removeShare(shareeView)
</span><span class="lines">@@ -2957,7 +3011,7 @@
</span><span class="cx">         This share is being accepted.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        if self.shareStatus() != _BIND_STATUS_ACCEPTED:
</del><ins>+        if self.useInvite() and self.shareStatus() != _BIND_STATUS_ACCEPTED:
</ins><span class="cx">             ownerView = yield self.ownerView()
</span><span class="cx">             yield ownerView.updateShare(self, status=_BIND_STATUS_ACCEPTED)
</span><span class="cx">             yield self.newShare(displayname=summary)
</span><span class="lines">@@ -2970,7 +3024,7 @@
</span><span class="cx">         This share is being declined.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        if self.shareStatus() != _BIND_STATUS_DECLINED:
</del><ins>+        if self.useInvite() and self.shareStatus() != _BIND_STATUS_DECLINED:
</ins><span class="cx">             ownerView = yield self.ownerView()
</span><span class="cx">             yield ownerView.updateShare(self, status=_BIND_STATUS_DECLINED)
</span><span class="cx">             yield self._sendReplyNotification(ownerView)
</span><span class="lines">@@ -2983,10 +3037,10 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         ownerView = yield self.ownerView()
</span><del>-        if self.direct():
</del><ins>+        if self.useInvite():
+            yield self.declineShare()
+        else:
</ins><span class="cx">             yield ownerView.removeShare(self)
</span><del>-        else:
-            yield self.declineShare()
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def newShare(self, displayname=None):
</span><span class="lines">@@ -3004,8 +3058,8 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         invitations = yield self.sharingInvites()
</span><span class="cx"> 
</span><del>-        # remove direct shares as those are not &quot;real&quot; invitations
-        invitations = filter(lambda x: x.mode != _BIND_MODE_DIRECT, invitations)
</del><ins>+        # remove direct/indirect shares as those are not &quot;real&quot; invitations
+        invitations = filter(lambda x: x.mode not in (_BIND_MODE_DIRECT, _BIND_MODE_INDIRECT), invitations)
</ins><span class="cx">         invitations.sort(key=lambda invitation: invitation.shareeUID)
</span><span class="cx">         returnValue(invitations)
</span><span class="cx"> 
</span><span class="lines">@@ -3191,7 +3245,7 @@
</span><span class="cx">         yield self.shareWith(
</span><span class="cx">             shareeHome,
</span><span class="cx">             mode=mode,
</span><del>-            status=_BIND_STATUS_INVITED if mode != _BIND_MODE_DIRECT else _BIND_STATUS_ACCEPTED,
</del><ins>+            status=_BIND_STATUS_INVITED if mode not in (_BIND_MODE_DIRECT, _BIND_MODE_INDIRECT) else _BIND_STATUS_ACCEPTED,
</ins><span class="cx">             summary=summary,
</span><span class="cx">         )
</span><span class="cx">         shareeView = yield self.shareeView(shareeUID)
</span><span class="lines">@@ -3228,10 +3282,11 @@
</span><span class="cx"> 
</span><span class="cx">         #remove None parameters, and substitute None for empty string
</span><span class="cx">         bind = self._bindSchema
</span><del>-        columnMap = dict([(k, v if v != &quot;&quot; else None)
-                          for k, v in {bind.BIND_MODE:mode,
-                            bind.BIND_STATUS:status,
-                            bind.MESSAGE:summary}.iteritems() if v is not None])
</del><ins>+        columnMap = dict([(k, v if v != &quot;&quot; else None) for k, v in {
+            bind.BIND_MODE:mode,
+            bind.BIND_STATUS:status,
+            bind.MESSAGE:summary
+        }.iteritems() if v is not None])
</ins><span class="cx"> 
</span><span class="cx">         if len(columnMap):
</span><span class="cx"> 
</span><span class="lines">@@ -3240,7 +3295,7 @@
</span><span class="cx">                 resourceID=self._resourceID, homeID=shareeView._home._resourceID
</span><span class="cx">             )
</span><span class="cx"> 
</span><del>-            #update affected attributes
</del><ins>+            # Update affected attributes
</ins><span class="cx">             if mode is not None:
</span><span class="cx">                 shareeView._bindMode = columnMap[bind.BIND_MODE]
</span><span class="cx"> 
</span><span class="lines">@@ -3359,7 +3414,8 @@
</span><span class="cx"> 
</span><span class="cx">         bind = self._bindSchema
</span><span class="cx">         yield self._updateBindColumnsQuery(
</span><del>-            {bind.BIND_REVISION : Parameter(&quot;revision&quot;), }).on(
</del><ins>+            {bind.BIND_REVISION : Parameter(&quot;revision&quot;), }
+        ).on(
</ins><span class="cx">             self._txn,
</span><span class="cx">             revision=self._bindRevision,
</span><span class="cx">             resourceID=self._resourceID,
</span><span class="lines">@@ -3442,6 +3498,24 @@
</span><span class="cx">         return self._bindMode == _BIND_MODE_DIRECT
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def indirect(self):
+        &quot;&quot;&quot;
+        Is this an &quot;indirect&quot; share?
+
+        @return: a boolean indicating whether it's indirect.
+        &quot;&quot;&quot;
+        return self._bindMode == _BIND_MODE_INDIRECT
+
+
+    def useInvite(self):
+        &quot;&quot;&quot;
+        Does this type of share use invitations?
+
+        @return: a boolean indicating whether invitations are used.
+        &quot;&quot;&quot;
+        return self._bindMode not in (_BIND_MODE_DIRECT, _BIND_MODE_INDIRECT)
+
+
</ins><span class="cx">     def shareUID(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @see: L{ICalendar.shareUID}
</span><span class="lines">@@ -3670,7 +3744,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # FIXME: tests don't cover this as directly as they should.
</span><span class="cx">         rows = yield cls._acceptedBindForHomeID.on(
</span><del>-                home._txn, homeID=home._resourceID
</del><ins>+            home._txn, homeID=home._resourceID
</ins><span class="cx">         )
</span><span class="cx">         names = [row[3] for row in rows]
</span><span class="cx">         returnValue(names)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboosharinginthestoretxdavcommondatastoresql_schemacurrentsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql_schema/current.sql (11962 => 11963)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql_schema/current.sql        2013-11-18 19:24:16 UTC (rev 11962)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql_schema/current.sql        2013-11-18 21:59:19 UTC (rev 11963)
</span><span class="lines">@@ -169,6 +169,7 @@
</span><span class="cx"> insert into CALENDAR_BIND_MODE values (1, 'read' );
</span><span class="cx"> insert into CALENDAR_BIND_MODE values (2, 'write');
</span><span class="cx"> insert into CALENDAR_BIND_MODE values (3, 'direct');
</span><ins>+insert into CALENDAR_BIND_MODE values (4, 'indirect');
</ins><span class="cx"> 
</span><span class="cx"> -- Enumeration of statuses
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboosharinginthestoretxdavcommondatastoresql_tablespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql_tables.py (11962 => 11963)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql_tables.py        2013-11-18 19:24:16 UTC (rev 11962)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/common/datastore/sql_tables.py        2013-11-18 21:59:19 UTC (rev 11963)
</span><span class="lines">@@ -174,6 +174,7 @@
</span><span class="cx"> _BIND_MODE_READ = _bindMode('read')
</span><span class="cx"> _BIND_MODE_WRITE = _bindMode('write')
</span><span class="cx"> _BIND_MODE_DIRECT = _bindMode('direct')
</span><ins>+_BIND_MODE_INDIRECT = _bindMode('indirect')
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> _addressBookObjectKind = _schemaConstants(
</span></span></pre>
</div>
</div>

</body>
</html>