[CalendarServer-changes] [11544] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon Jul 22 12:56:16 PDT 2013


Revision: 11544
          http://trac.calendarserver.org//changeset/11544
Author:   cdaboo at apple.com
Date:     2013-07-22 12:56:16 -0700 (Mon, 22 Jul 2013)
Log Message:
-----------
Fix scaling of asShared() by using a new method that is O(1) wrt number of sharees. Also, handle the case of shares where the
sharer's directory record is partially or fully disabled. Another side-effect: propstore caching is now turned off if SQL
statement caching is disabled.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/test_sharing.py
    CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
    CalendarServer/trunk/txdav/base/propertystore/sql.py
    CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/common.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
    CalendarServer/trunk/txdav/caldav/icalendarstore.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -29,6 +29,8 @@
 from twext.web2.dav.http import ErrorResponse, MultiStatusResponse
 from twext.web2.dav.resource import TwistedACLInheritable
 from twext.web2.dav.util import allDataFromStream, joinURL
+
+from txdav.common.datastore.sql import SharingInvitation
 from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
     _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_STATUS_INVITED, \
     _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, \
@@ -83,14 +85,13 @@
                     customxml.UID.fromString(invitation.uid()) if includeUID else None,
                     element.HRef.fromString(userid),
                     customxml.CommonName.fromString(cn),
-                    customxml.InviteAccess(invitationAccessMapToXML[invitation.access()]()),
-                    invitationStatusMapToXML[invitation.state()](),
+                    customxml.InviteAccess(invitationBindModeToXMLMap[invitation.mode()]()),
+                    invitationBindStatusToXMLMap[invitation.status()](),
                 )
 
             # See if this property is on the shared calendar
             if self.isShared():
-                yield self.validateInvites(request)
-                invitations = yield self._allInvitations()
+                invitations = yield self.validateInvites(request)
                 returnValue(customxml.Invite(
                     *[invitePropertyElement(invitation) for invitation in invitations]
                 ))
@@ -98,20 +99,20 @@
             # See if it is on the sharee calendar
             if self.isShareeResource():
                 original = (yield request.locateResource(self._share.url()))
-                yield original.validateInvites(request)
-                invitations = yield original._allInvitations()
+                if original is not None:
+                    invitations = yield original.validateInvites(request)
 
-                ownerPrincipal = (yield original.ownerPrincipal(request))
-                owner = ownerPrincipal.principalURL()
-                ownerCN = ownerPrincipal.displayName()
+                    ownerPrincipal = (yield original.ownerPrincipal(request))
+                    owner = ownerPrincipal.principalURL()
+                    ownerCN = ownerPrincipal.displayName()
 
-                returnValue(customxml.Invite(
-                    customxml.Organizer(
-                        element.HRef.fromString(owner),
-                        customxml.CommonName.fromString(ownerCN),
-                    ),
-                    *[invitePropertyElement(invitation, includeUID=False) for invitation in invitations]
-                ))
+                    returnValue(customxml.Invite(
+                        customxml.Organizer(
+                            element.HRef.fromString(owner),
+                            customxml.CommonName.fromString(ownerCN),
+                        ),
+                        *[invitePropertyElement(invitation, includeUID=False) for invitation in invitations]
+                    ))
 
         returnValue(None)
 
@@ -157,8 +158,8 @@
             ))
 
         # Only certain states are owner controlled
-        if invitation.state() in ("NEEDS-ACTION", "ACCEPTED", "DECLINED",):
-            yield self._updateInvitation(invitation, state=state, summary=summary)
+        if invitation.status() in (_BIND_STATUS_INVITED, _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED,):
+            yield self._updateInvitation(invitation, status=state, summary=summary)
 
 
     @inlineCallbacks
@@ -328,7 +329,7 @@
         else:
             # Invited shares use access mode from the invite
             # Get the access for self
-            returnValue(Invitation(self._newStoreObject).access())
+            returnValue(invitationAccessFromBindModeMap.get(self._newStoreObject.shareMode()))
 
 
     @inlineCallbacks
@@ -475,11 +476,11 @@
         #assert request
         invitations = yield self._allInvitations()
         for invitation in invitations:
-            if invitation.state() != "INVALID":
+            if invitation.status() != _BIND_STATUS_INVALID:
                 if not (yield self.validUserIDForShare("urn:uuid:" + invitation.shareeUID(), request)):
-                    yield self._updateInvitation(invitation, state="INVALID")
+                    yield self._updateInvitation(invitation, status=_BIND_STATUS_INVALID)
 
-        returnValue(len(invitations))
+        returnValue(invitations)
 
 
     def inviteUserToShare(self, userid, cn, ace, summary, request):
@@ -539,7 +540,7 @@
 
 
     @inlineCallbacks
-    def _createInvitation(self, shareeUID, access, summary,):
+    def _createInvitation(self, shareeUID, mode, summary,):
         """
         Create a new homeChild and wrap it in an Invitation
         """
@@ -549,45 +550,41 @@
             shareeHome = yield self._newStoreObject._txn.addressbookHomeWithUID(shareeUID, create=True)
 
         shareUID = yield self._newStoreObject.shareWith(shareeHome,
-                                                    mode=invitationAccessToBindModeMap[access],
+                                                    mode=mode,
                                                     status=_BIND_STATUS_INVITED,
                                                     message=summary)
         shareeStoreObject = yield shareeHome.invitedObjectWithShareUID(shareUID)
-        invitation = Invitation(shareeStoreObject)
+        invitation = SharingInvitation.fromCommonHomeChild(shareeStoreObject)
         returnValue(invitation)
 
 
     @inlineCallbacks
-    def _updateInvitation(self, invitation, access=None, state=None, summary=None):
-        mode = None if access is None else invitationAccessToBindModeMap[access]
-        status = None if state is None else invitationStateToBindStatusMap[state]
-        yield self._newStoreObject.updateShare(invitation._shareeStoreObject, mode=mode, status=status, message=summary)
+    def _updateInvitation(self, invitation, mode=None, status=None, summary=None):
+        yield self._newStoreObject.updateShareFromSharingInvitation(invitation, mode=mode, status=status, message=summary)
+        if mode is not None:
+            invitation.setMode(mode)
+        if status is not None:
+            invitation.setStatus(status)
+        if summary is not None:
+            invitation.setSummary(summary)
 
 
     @inlineCallbacks
     def _allInvitations(self):
         """
-        Get list of all invitations to this object
-
-        For legacy reasons, all invitations are all invited + shared (accepted, not direct).
-        Combine these two into a single sorted list so code is similar to that for legacy invite db
+        Get list of all invitations (non-direct) to this object.
         """
         if not self.exists():
             returnValue([])
 
-        #TODO: Cache
-        if True:  # not hasattr(self, "_invitations"):
+        invitations = yield self._newStoreObject.sharingInvites()
 
-            acceptedHomeChildren = yield self._newStoreObject.asShared()
-            # remove direct shares (it might be OK not to remove these, but that would be different from legacy code)
-            indirectAccceptedHomeChildren = [homeChild for homeChild in acceptedHomeChildren
-                                             if homeChild.shareMode() != _BIND_MODE_DIRECT]
-            invitedHomeChildren = (yield self._newStoreObject.asInvited()) + indirectAccceptedHomeChildren
+        # remove direct shares as those are not "real" invitations
+        invitations = filter(lambda x: x.mode() != _BIND_MODE_DIRECT, invitations)
 
-            self._invitations = sorted([Invitation(homeChild) for homeChild in invitedHomeChildren],
-                                 key=lambda invitation: invitation.shareeUID())
+        invitations.sort(key=lambda invitation: invitation.shareeUID())
 
-        returnValue(self._invitations)
+        returnValue(invitations)
 
 
     @inlineCallbacks
@@ -627,11 +624,11 @@
         # Look for existing invite and update its fields or create new one
         invitation = yield self._invitationForShareeUID(shareeUID)
         if invitation:
-            yield self._updateInvitation(invitation, access=invitationAccessMapFromXML[type(ace)], summary=summary)
+            yield self._updateInvitation(invitation, mode=invitationBindModeFromXMLMap[type(ace)], summary=summary)
         else:
             invitation = yield self._createInvitation(
                                 shareeUID=shareeUID,
-                                access=invitationAccessMapFromXML[type(ace)],
+                                mode=invitationBindModeFromXMLMap[type(ace)],
                                 summary=summary)
         # Send invite notification
         yield self.sendInviteNotification(invitation, request)
@@ -664,7 +661,7 @@
         # Remove any shared calendar or address book
         sharee = self.principalForUID(invitation.shareeUID())
         if sharee:
-            previousInvitationState = invitation.state()
+            previousInvitationStatus = invitation.status()
             if self.isCalendarCollection():
                 shareeHomeResource = yield sharee.calendarHome(request)
                 displayName = yield shareeHomeResource.removeShareByUID(request, invitation.uid())
@@ -674,13 +671,13 @@
                 displayName = None
             # If current user state is accepted then we send an invite with the new state, otherwise
             # we cancel any existing invites for the user
-            if previousInvitationState != "ACCEPTED":
+            if previousInvitationStatus != _BIND_STATUS_ACCEPTED:
                 yield self.removeInviteNotification(invitation, request)
             else:
                 yield self.sendInviteNotification(invitation, request, displayName=displayName, notificationState="DELETED")
 
         # Direct shares for  with valid sharee principal will already be deleted
-        yield self._newStoreObject.unshareWith(invitation._shareeStoreObject.viewerHome())
+        yield self._newStoreObject.unshareWithUID(invitation.shareeUID())
 
         returnValue(True)
 
@@ -719,7 +716,7 @@
 
         # Generate invite XML
         userid = "urn:uuid:" + invitation.shareeUID()
-        state = notificationState if notificationState else invitation.state()
+        state = notificationState if notificationState else invitation.status()
         summary = invitation.summary() if displayName is None else displayName
 
         typeAttr = {'shared-type': self.sharedResourceType()}
@@ -729,8 +726,8 @@
             customxml.InviteNotification(
                 customxml.UID.fromString(invitation.uid()),
                 element.HRef.fromString(userid),
-                invitationStatusMapToXML[state](),
-                customxml.InviteAccess(invitationAccessMapToXML[invitation.access()]()),
+                invitationBindStatusToXMLMap[state](),
+                customxml.InviteAccess(invitationBindModeToXMLMap[invitation.mode()]()),
                 customxml.HostURL(
                     element.HRef.fromString(hosturl),
                 ),
@@ -877,7 +874,8 @@
             ok_code = responsecode.FAILED_DEPENDENCY
 
         # Do a final validation of the entire set of invites
-        numRecords = (yield self.validateInvites(request))
+        invites = (yield self.validateInvites(request))
+        numRecords = len(invites)
 
         # Set the sharing state on the collection
         shared = self.isShared()
@@ -974,28 +972,21 @@
     }
 
 
-invitationAccessMapToXML = {
-    "read-only"           : customxml.ReadAccess,
-    "read-write"          : customxml.ReadWriteAccess,
+invitationBindStatusToXMLMap = {
+    _BIND_STATUS_INVITED      : customxml.InviteStatusNoResponse,
+    _BIND_STATUS_ACCEPTED     : customxml.InviteStatusAccepted,
+    _BIND_STATUS_DECLINED     : customxml.InviteStatusDeclined,
+    _BIND_STATUS_INVALID      : customxml.InviteStatusInvalid,
+    "DELETED"                 : customxml.InviteStatusDeleted,
 }
-invitationAccessMapFromXML = dict([(v, k) for k, v in invitationAccessMapToXML.iteritems()])
+invitationBindStatusFromXMLMap = dict((v, k) for k, v in invitationBindStatusToXMLMap.iteritems())
 
-invitationStatusMapToXML = {
-    "NEEDS-ACTION" : customxml.InviteStatusNoResponse,
-    "ACCEPTED"     : customxml.InviteStatusAccepted,
-    "DECLINED"     : customxml.InviteStatusDeclined,
-    "DELETED"      : customxml.InviteStatusDeleted,
-    "INVALID"      : customxml.InviteStatusInvalid,
+invitationBindModeToXMLMap = {
+    _BIND_MODE_READ           : customxml.ReadAccess,
+    _BIND_MODE_WRITE          : customxml.ReadWriteAccess,
 }
-invitationStatusMapFromXML = dict([(v, k) for k, v in invitationStatusMapToXML.iteritems()])
+invitationBindModeFromXMLMap = dict((v, k) for k, v in invitationBindModeToXMLMap.iteritems())
 
-invitationStateToBindStatusMap = {
-    "NEEDS-ACTION": _BIND_STATUS_INVITED,
-    "ACCEPTED": _BIND_STATUS_ACCEPTED,
-    "DECLINED": _BIND_STATUS_DECLINED,
-    "INVALID": _BIND_STATUS_INVALID,
-}
-invitationStateFromBindStatusMap = dict((v, k) for k, v in invitationStateToBindStatusMap.iteritems())
 invitationAccessToBindModeMap = {
     "own": _BIND_MODE_OWN,
     "read-only": _BIND_MODE_READ,
@@ -1004,35 +995,6 @@
 invitationAccessFromBindModeMap = dict((v, k) for k, v in invitationAccessToBindModeMap.iteritems())
 
 
-class Invitation(object):
-    """
-        Invitation is a read-only wrapper for CommonHomeChild, that uses terms similar LegacyInvite sharing.py code base.
-    """
-    def __init__(self, shareeStoreObject):
-        self._shareeStoreObject = shareeStoreObject
-
-
-    def uid(self):
-        return self._shareeStoreObject.shareUID()
-
-
-    def shareeUID(self):
-        return self._shareeStoreObject.viewerHome().uid()
-
-
-    def access(self):
-        return invitationAccessFromBindModeMap.get(self._shareeStoreObject.shareMode())
-
-
-    def state(self):
-        return invitationStateFromBindStatusMap.get(self._shareeStoreObject.shareStatus())
-
-
-    def summary(self):
-        return self._shareeStoreObject.shareMessage()
-
-
-
 class SharedHomeMixin(LinkFollowerMixIn):
     """
     A mix-in for calendar/addressbook homes that defines the operations for
@@ -1074,33 +1036,42 @@
         if not storeObject or storeObject.owned():
             returnValue(None)
 
-        # get the shared object's URL
+        # Get the shared object's URL - we may need to fake this if the sharer principal is missing or disabled
+        url = None
         owner = self.principalForUID(storeObject.ownerHome().uid())
+        from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
+        if isinstance(owner, DirectoryCalendarPrincipalResource):
 
-        if not request:
-            # FIXEME:  Fake up a request that can be used to get the owner home resource
-            class _FakeRequest(object):
-                pass
-            fakeRequest = _FakeRequest()
-            setattr(fakeRequest, TRANSACTION_KEY, self._newStoreHome._txn)
-            request = fakeRequest
+            if not request:
+                # FIXEME:  Fake up a request that can be used to get the owner home resource
+                class _FakeRequest(object):
+                    pass
+                fakeRequest = _FakeRequest()
+                setattr(fakeRequest, TRANSACTION_KEY, self._newStoreHome._txn)
+                request = fakeRequest
 
-        if self._newStoreHome._homeType == ECALENDARTYPE:
-            ownerHomeCollection = yield owner.calendarHome(request)
-        elif self._newStoreHome._homeType == EADDRESSBOOKTYPE:
-            ownerHomeCollection = yield owner.addressBookHome(request)
+            if self._newStoreHome._homeType == ECALENDARTYPE:
+                ownerHomeCollection = yield owner.calendarHome(request)
+            elif self._newStoreHome._homeType == EADDRESSBOOKTYPE:
+                ownerHomeCollection = yield owner.addressBookHome(request)
 
+            if ownerHomeCollection is not None:
+                url = ownerHomeCollection.url()
+
+        if url is None:
+            url = "/calendars/__uids__/%s/" % (storeObject.ownerHome().uid(),)
+
         ownerHomeChild = yield storeObject.ownerHome().childWithID(storeObject._resourceID)
         if ownerHomeChild:
             assert ownerHomeChild != storeObject
-            url = joinURL(ownerHomeCollection.url(), ownerHomeChild.name())
+            url = joinURL(url, ownerHomeChild.name())
             share = Share(shareeStoreObject=storeObject, ownerStoreObject=ownerHomeChild, url=url)
         else:
             for ownerHomeChild in (yield storeObject.ownerHome().children()):
                 if ownerHomeChild.owned():
                     sharedGroup = yield ownerHomeChild.objectResourceWithID(storeObject._resourceID)
                     if sharedGroup:
-                        url = joinURL(ownerHomeCollection.url(), ownerHomeChild.name(), sharedGroup.name())
+                        url = joinURL(url, ownerHomeChild.name(), sharedGroup.name())
                         share = Share(shareeStoreObject=storeObject, ownerStoreObject=sharedGroup, url=url)
                         break
 
@@ -1133,8 +1104,7 @@
         oldShare = yield self._shareForUID(inviteUID, request)
 
         # Send the invite reply then add the link
-        yield self._changeShare(request, "ACCEPTED", hostUrl, inviteUID,
-                                displayname)
+        yield self._changeShare(request, _BIND_STATUS_ACCEPTED, hostUrl, inviteUID, displayname)
         if oldShare:
             share = oldShare
         else:
@@ -1145,8 +1115,7 @@
                           ownerStoreObject=sharedResource._newStoreObject,
                           url=hostUrl)
 
-        response = yield self._acceptShare(request, not oldShare, share,
-                                           displayname)
+        response = yield self._acceptShare(request, not oldShare, share, displayname)
         returnValue(response)
 
 
@@ -1172,8 +1141,7 @@
                           ownerStoreObject=sharedCollection._newStoreObject,
                           url=hostUrl)
 
-        response = yield self._acceptShare(request, not oldShare, share,
-                                           displayname)
+        response = yield self._acceptShare(request, not oldShare, share, displayname)
         returnValue(response)
 
 
@@ -1309,12 +1277,12 @@
 
         # Remove it if it is in the DB
         yield self.removeShareByUID(request, inviteUID)
-        yield self._changeShare(request, "DECLINED", hostUrl, inviteUID)
+        yield self._changeShare(request, _BIND_STATUS_DECLINED, hostUrl, inviteUID, processed=True)
         returnValue(Response(code=responsecode.NO_CONTENT))
 
 
     @inlineCallbacks
-    def _changeShare(self, request, state, hostUrl, replytoUID, displayname=None):
+    def _changeShare(self, request, state, hostUrl, replytoUID, displayname=None, processed=False):
         """
         Accept or decline an invite to a shared collection.
         """
@@ -1323,6 +1291,10 @@
         ownerPrincipalUID = ownerPrincipal.principalUID()
         sharedResource = (yield request.locateResource(hostUrl))
         if sharedResource is None:
+            # FIXME: have to return here rather than raise to allow removal of a share for a sharer
+            # whose principal is no longer valid yet still exists in the store. Really we need to get rid of
+            # locateResource calls and just do everything via store objects.
+            returnValue(None)
             # Original shared collection is gone - nothing we can do except ignore it
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
@@ -1331,7 +1303,8 @@
             ))
 
         # Change the record
-        yield sharedResource.changeUserInviteState(request, replytoUID, ownerPrincipalUID, state, displayname)
+        if not processed:
+            yield sharedResource.changeUserInviteState(request, replytoUID, ownerPrincipalUID, state, displayname)
 
         yield self.sendReply(request, ownerPrincipal, sharedResource, state, hostUrl, replytoUID, displayname)
 
@@ -1341,6 +1314,12 @@
 
         # Locate notifications collection for owner
         owner = (yield sharedResource.ownerPrincipal(request))
+        if owner is None:
+            # FIXME: have to return here rather than raise to allow removal of a share for a sharer
+            # whose principal is no longer valid yet still exists in the store. Really we need to get rid of
+            # locateResource calls and just do everything via store objects.
+            returnValue(None)
+
         notificationResource = (yield request.locateResource(owner.notificationURL()))
         notifications = notificationResource._newStoreNotifications
 
@@ -1364,7 +1343,7 @@
                 *(
                     (
                         element.HRef.fromString(cua),
-                        invitationStatusMapToXML[state](),
+                        invitationBindStatusToXMLMap[state](),
                         customxml.HostURL(
                             element.HRef.fromString(hostUrl),
                         ),

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -62,7 +62,7 @@
 from txdav.carddav.iaddressbookstore import GroupWithUnsharedAddressNotAllowedError, \
     GroupForSharedAddressBookDeleteNotAllowedError, SharedGroupDeleteNotAllowedError
 from txdav.common.datastore.sql_tables import _BIND_MODE_READ, _BIND_MODE_WRITE, \
-    _BIND_MODE_DIRECT
+    _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
 from txdav.common.icommondatastore import NoSuchObjectResourceError, \
     TooManyObjectResourcesError, ObjectResourceTooBigError, \
     InvalidObjectResourceError, ObjectResourceNameNotAllowedError, \
@@ -1628,23 +1628,28 @@
     def sharedDropboxACEs(self):
 
         aces = ()
-        calendars = yield self._newStoreCalendarObject._parentCollection.asShared()
-        for calendar in calendars:
 
+        invites = yield self._newStoreCalendarObject._parentCollection.sharingInvites()
+        for invite in invites:
+
+            # Only want accepted invites
+            if invite.status() != _BIND_STATUS_ACCEPTED:
+                continue
+
             userprivs = [
             ]
-            if calendar.shareMode() in (_BIND_MODE_READ, _BIND_MODE_WRITE,):
+            if invite.mode() in (_BIND_MODE_READ, _BIND_MODE_WRITE,):
                 userprivs.append(davxml.Privilege(davxml.Read()))
                 userprivs.append(davxml.Privilege(davxml.ReadACL()))
                 userprivs.append(davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()))
-            if calendar.shareMode() in (_BIND_MODE_READ,):
+            if invite.mode() in (_BIND_MODE_READ,):
                 userprivs.append(davxml.Privilege(davxml.WriteProperties()))
-            if calendar.shareMode() in (_BIND_MODE_WRITE,):
+            if invite.mode() in (_BIND_MODE_WRITE,):
                 userprivs.append(davxml.Privilege(davxml.Write()))
             proxyprivs = list(userprivs)
             proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
 
-            principal = self.principalForUID(calendar._home.uid())
+            principal = self.principalForUID(invite.shareeUID())
             aces += (
                 # Inheritable specific access for the resource's associated principal.
                 davxml.ACE(
@@ -1923,7 +1928,7 @@
 
 
     @inlineCallbacks
-    def _sharedAccessControl(self, calendar, shareMode):
+    def _sharedAccessControl(self, invite):
         """
         Check the shared access mode of this resource, potentially consulting
         an external access method if necessary.
@@ -1939,10 +1944,10 @@
             access control mechanism has dictate the home should no longer have
             any access at all.
         """
-        if shareMode in (_BIND_MODE_DIRECT,):
-            ownerUID = calendar.ownerHome().uid()
+        if invite.mode() in (_BIND_MODE_DIRECT,):
+            ownerUID = invite.ownerUID()
             owner = self.principalForUID(ownerUID)
-            shareeUID = calendar.viewerHome().uid()
+            shareeUID = invite.shareeUID()
             if owner.record.recordType == WikiDirectoryService.recordType_wikis:
                 # Access level comes from what the wiki has granted to the
                 # sharee
@@ -1958,9 +1963,9 @@
                     returnValue(None)
             else:
                 returnValue("original")
-        elif shareMode in (_BIND_MODE_READ,):
+        elif invite.mode() in (_BIND_MODE_READ,):
             returnValue("read-only")
-        elif shareMode in (_BIND_MODE_WRITE,):
+        elif invite.mode() in (_BIND_MODE_WRITE,):
             returnValue("read-write")
         returnValue("original")
 
@@ -1969,19 +1974,23 @@
     def sharedDropboxACEs(self):
 
         aces = ()
-        calendars = yield self._newStoreCalendarObject._parentCollection.asShared()
-        for calendar in calendars:
+        invites = yield self._newStoreCalendarObject._parentCollection.sharingInvites()
+        for invite in invites:
 
+            # Only want accepted invites
+            if invite.status() != _BIND_STATUS_ACCEPTED:
+                continue
+
             privileges = [
                 davxml.Privilege(davxml.Read()),
                 davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
             ]
             userprivs = []
-            access = (yield self._sharedAccessControl(calendar, calendar.shareMode()))
+            access = (yield self._sharedAccessControl(invite))
             if access in ("read-only", "read-write",):
                 userprivs.extend(privileges)
 
-            principal = self.principalForUID(calendar._home.uid())
+            principal = self.principalForUID(invite.shareeUID())
             aces += (
                 # Inheritable specific access for the resource's associated principal.
                 davxml.ACE(

Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -24,6 +24,7 @@
 from twistedcaldav import customxml
 from twistedcaldav import sharing
 from twistedcaldav.config import config
+from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
 from twistedcaldav.resource import CalDAVResource
 from twistedcaldav.sharing import WikiDirectoryService
 from twistedcaldav.test.test_cache import StubResponseCacheResource
@@ -71,7 +72,7 @@
 
 
 
-class FakePrincipal(object):
+class FakePrincipal(DirectoryCalendarPrincipalResource):
 
     def __init__(self, cuaddr, test):
         if cuaddr.startswith("mailto:"):

Modified: CalendarServer/trunk/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/twistedcaldav/test/test_wrapping.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -380,7 +380,7 @@
         if not hasattr(self._sqlCalendarStore, "_dropbox_ok"):
             self._sqlCalendarStore._dropbox_ok = False
         self.patch(self._sqlCalendarStore, "_dropbox_ok", True)
-        self.patch(Calendar, "asShared", lambda self: [])
+        self.patch(Calendar, "sharingInvites", lambda self: [])
 
         yield self.populateOneObject("1.ics", test_event_text)
         calendarObject = yield self.getResource(

Modified: CalendarServer/trunk/txdav/base/propertystore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/sql.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/base/propertystore/sql.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -86,15 +86,15 @@
         def _cache_user_props(uid):
 
             # First check whether uid already has a valid cached entry
-            valid_cached_users = yield self._cacher.get(str(self._resourceID))
-            if valid_cached_users is None:
-                valid_cached_users = set()
+            rows = None
+            if self._cacher is not None:
+                valid_cached_users = yield self._cacher.get(str(self._resourceID))
+                if valid_cached_users is None:
+                    valid_cached_users = set()
 
-            # Fetch cached user data if valid and present
-            if uid in valid_cached_users:
-                rows = yield self._cacher.get(self._cacheToken(uid))
-            else:
-                rows = None
+                # Fetch cached user data if valid and present
+                if uid in valid_cached_users:
+                    rows = yield self._cacher.get(self._cacheToken(uid))
 
             # If no cached data, fetch from SQL DB and cache
             if rows is None:
@@ -103,11 +103,12 @@
                     resourceID=self._resourceID,
                     viewerID=uid,
                 )
-                yield self._cacher.set(self._cacheToken(uid), rows if rows is not None else ())
+                if self._cacher is not None:
+                    yield self._cacher.set(self._cacheToken(uid), rows if rows is not None else ())
 
-                # Mark this uid as valid
-                valid_cached_users.add(uid)
-                yield self._cacher.set(str(self._resourceID), valid_cached_users)
+                    # Mark this uid as valid
+                    valid_cached_users.add(uid)
+                    yield self._cacher.set(str(self._resourceID), valid_cached_users)
 
             for name, value in rows:
                 self._cached[(name, uid)] = value
@@ -129,6 +130,8 @@
         super(PropertyStore, self).__init__(defaultuser, shareUser)
         self._txn = txn
         self._resourceID = resourceID
+        if not self._txn.store().queryCachingEnabled():
+            self._cacher = None
         self._cached = {}
         if not created:
             yield self._refresh(txn)
@@ -305,7 +308,8 @@
                 yield self._insertQuery.on(
                     txn, resourceID=self._resourceID, value=value_str,
                     name=key_str, uid=uid)
-            self._cacher.delete(self._cacheToken(uid))
+            if self._cacher is not None:
+                self._cacher.delete(self._cacheToken(uid))
 
         # Call the registered notification callback - we need to do this as a preCommit since it involves
         # a bunch of deferred operations, but this propstore api is not deferred. preCommit will execute
@@ -337,7 +341,8 @@
                                  resourceID=self._resourceID,
                                  name=key_str, uid=uid
                                 )
-            self._cacher.delete(self._cacheToken(uid))
+            if self._cacher is not None:
+                self._cacher.delete(self._cacheToken(uid))
 
         # Call the registered notification callback - we need to do this as a preCommit since it involves
         # a bunch of deferred operations, but this propstore api is not deferred. preCommit will execute
@@ -368,7 +373,8 @@
         yield self._deleteResourceQuery.on(self._txn, resourceID=self._resourceID)
 
         # Invalidate entire set of cached per-user data for this resource
-        self._cacher.delete(str(self._resourceID))
+        if self._cacher is not None:
+            self._cacher.delete(str(self._resourceID))
 
 
     @inlineCallbacks
@@ -392,5 +398,6 @@
 
         # Invalidate entire set of cached per-user data for this resource and reload
         self._cached = {}
-        self._cacher.delete(str(self._resourceID))
+        if self._cacher is not None:
+            self._cacher.delete(str(self._resourceID))
         yield self._refresh(self._txn)

Modified: CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/base/propertystore/test/test_sql.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -269,5 +269,38 @@
         self.assertEqual(len(store1_user1._cached), 0)
         self.assertFalse("SQL.props:10/user01" in store1_user1._cacher._memcacheProtocol._cache)
 
+
+    @inlineCallbacks
+    def test_cacher_off(self):
+        """
+        Test that properties can still be read and written when the cacher is disabled.
+        """
+
+        self.patch(self.store, "queryCacher", None)
+
+        # Existing store - add a normal property
+        self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
+        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 10)
+        self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
+
+        pname1 = propertyName("dummy1")
+        pvalue1 = propertyValue("*")
+
+        yield store1_user1.__setitem__(pname1, pvalue1)
+        self.assertEqual(store1_user1[pname1], pvalue1)
+
+        self.assertEqual(len(store1_user1._cached), 1)
+        self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
+
+        yield self._txn.commit()
+        self._txn = self.store.newTransaction()
+
+        # Existing store - check a normal property
+        self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
+        store1_user1 = yield PropertyStore.load("user01", None, self._txn, 10)
+        self.assertFalse("SQL.props:10/user01" in PropertyStore._cacher._memcacheProtocol._cache)
+        self.assertEqual(store1_user1[pname1], pvalue1)
+
+
 if PropertyStore is None:
     PropertyStoreTest.skip = importErrorMessage

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -988,9 +988,9 @@
         self.assertEqual(newName, self.sharedName)
         self.assertNotIdentical(otherCal, None)
 
-        invitedCals = yield cal.asShared()
+        invitedCals = yield cal.sharingInvites()
         self.assertEqual(len(invitedCals), 1)
-        self.assertEqual(invitedCals[0].shareMode(), _BIND_MODE_READ)
+        self.assertEqual(invitedCals[0].mode(), _BIND_MODE_READ)
 
 
     @inlineCallbacks
@@ -1007,7 +1007,7 @@
         newName = yield cal.unshareWith(other)
         otherCal = yield other.childWithName(newName)
         self.assertIdentical(otherCal, None)
-        invitedCals = yield cal.asShared()
+        invitedCals = yield cal.sharingInvites()
         self.assertEqual(len(invitedCals), 0)
 
 
@@ -1027,7 +1027,7 @@
         yield cal.unshare()
         otherCal = yield other.childWithName(self.sharedName)
         self.assertEqual(otherCal, None)
-        invitedCals = yield cal.asShared()
+        invitedCals = yield cal.sharingInvites()
         self.assertEqual(len(invitedCals), 0)
 
 
@@ -1047,7 +1047,7 @@
         yield otherCal.unshare()
         otherCal = yield other.childWithName(self.sharedName)
         self.assertEqual(otherCal, None)
-        invitedCals = yield cal.asShared()
+        invitedCals = yield cal.sharingInvites()
         self.assertEqual(len(invitedCals), 0)
 
 
@@ -1062,24 +1062,23 @@
 
 
     @inlineCallbacks
-    def test_asShared(self):
+    def test_sharingInvites(self):
         """
-        L{ICalendar.asShared} returns an iterable of all versions of a shared
+        L{ICalendar.sharingInvites} returns an iterable of all versions of a shared
         calendar.
         """
         cal = yield self.calendarUnderTest()
-        sharedBefore = yield cal.asShared()
-        # It's not shared yet; make sure asShared doesn't include owner version.
+        sharedBefore = yield cal.sharingInvites()
+        # It's not shared yet; make sure sharingInvites doesn't include owner version.
         self.assertEqual(len(sharedBefore), 0)
         yield self.test_shareWith()
         # FIXME: don't know why this separate transaction is needed; remove it.
         yield self.commit()
         cal = yield self.calendarUnderTest()
-        sharedAfter = yield cal.asShared()
+        sharedAfter = yield cal.sharingInvites()
         self.assertEqual(len(sharedAfter), 1)
-        self.assertEqual(sharedAfter[0].shareMode(), _BIND_MODE_WRITE)
-        self.assertEqual(sharedAfter[0].viewerCalendarHome().uid(),
-                         OTHER_HOME_UID)
+        self.assertEqual(sharedAfter[0].mode(), _BIND_MODE_WRITE)
+        self.assertEqual(sharedAfter[0].shareeUID(), OTHER_HOME_UID)
 
 
     @inlineCallbacks

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_file.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -467,7 +467,7 @@
     test_shareAgainChangesMode = test_shareWith
     test_unshareWith = test_shareWith
     test_unshareWithInDifferentTransaction = test_shareWith
-    test_asShared = test_shareWith
+    test_sharingInvites = test_shareWith
     test_unshareSharerSide = test_shareWith
     test_unshareShareeSide = test_shareWith
     test_sharedNotifierID = test_shareWith

Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/icalendarstore.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -385,15 +385,12 @@
         Low-level query to gather names for calendarObjectsSinceToken.
         """
 
-    def asShared(): #@NoSelf
+    def sharingInvites(): #@NoSelf
         """
-        Get a view of this L{ICalendar} as present in everyone's calendar home
-        except for its owner's.
+        Retrieve the list of all L{SharingInvitation} for this L{CommonHomeChild}, irrespective of mode.
 
-        @return: a L{Deferred} which fires with a list of L{ICalendar}s, each
-            L{ICalendar} as seen by its respective sharee.  This means that its
-            C{shareMode} will be something other than L{_BIND_MODE_OWN}, and its
-            L{ICalendar.viewerCalendarHome} will return the home of the sharee.
+        @return: L{SharingInvitation} objects
+        @rtype: a L{Deferred} which fires with a L{list} of L{SharingInvitation}s.
         """
 
     # FIXME: This module should define it's own constants and this

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -48,7 +48,7 @@
     IAddressBookObject, GroupForSharedAddressBookDeleteNotAllowedError, \
     GroupWithUnsharedAddressNotAllowedError, SharedGroupDeleteNotAllowedError
 from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
-    CommonObjectResource, EADDRESSBOOKTYPE, SharingMixIn
+    CommonObjectResource, EADDRESSBOOKTYPE, SharingMixIn, SharingInvitation
 from txdav.common.datastore.sql_legacy import PostgresLegacyABIndexEmulator
 from txdav.common.datastore.sql_tables import _ABO_KIND_PERSON, \
     _ABO_KIND_GROUP, _ABO_KIND_RESOURCE, _ABO_KIND_LOCATION, schema, \
@@ -101,7 +101,7 @@
 
 
     @classproperty
-    def _resourceIDAndHomeResourceIDFromOwnerQuery(cls):  #@NoSelf
+    def _resourceIDAndHomeResourceIDFromOwnerQuery(cls): #@NoSelf
         home = cls._homeSchema
         return Select([home.RESOURCE_ID, home.ADDRESSBOOK_PROPERTY_STORE_ID],
                       From=home, Where=home.OWNER_UID == Parameter("ownerUID"))
@@ -264,7 +264,7 @@
 
 
     @classproperty
-    def _syncTokenQuery(cls):  #@NoSelf
+    def _syncTokenQuery(cls): #@NoSelf
         """
         DAL Select statement to find the sync token.
         """
@@ -308,7 +308,7 @@
 
 
     @classproperty
-    def _changesQuery(cls):  #@NoSelf
+    def _changesQuery(cls): #@NoSelf
         rev = cls._revisionsSchema
         return Select(
             [rev.COLLECTION_NAME,
@@ -370,12 +370,14 @@
 
         returnValue(bool(sharedRows))
 
+
     @inlineCallbacks
     def _initIsShared(self):
         isShared = yield self._isSharedOrInvited()
         self.setShared(isShared)
 
 
+
 class AddressBook(CommonHomeChild, AddressBookSharingMixIn):
     """
     SQL-based implementation of L{IAddressBook}.
@@ -430,10 +432,21 @@
     addressbookObjectsSinceToken = CommonHomeChild.objectResourcesSinceToken
 
 
-    def shareeAddressBookName(self):
-        return self._home.shareeAddressBookName()
+    def shareeName(self):
+        """
+        The sharee's name for a shared address book is the sharer's home ownerUID.
+        """
+        return self.ownerHome().shareeAddressBookName()
 
 
+    def bindNameIsResourceName(self):
+        """
+        For shared address books the resource name of an accepted share is not the same as the name
+        in the bind table.
+        """
+        return False
+
+
     @inlineCallbacks
     def _loadPropertyStore(self, props=None):
         if props is None:
@@ -568,7 +581,7 @@
                       Where=obj.ADDRESSBOOK_HOME_RESOURCE_ID == Parameter("addressbookResourceID"),)
 
 
-    def _fullySharedAddressBookGroupRow(self):  #@NoSelf
+    def _fullySharedAddressBookGroupRow(self): #@NoSelf
         return [
             self._resourceID,  # obj.ADDRESSBOOK_HOME_RESOURCE_ID,
             self._resourceID,  # obj.RESOURCE_ID,
@@ -593,7 +606,7 @@
     @inlineCallbacks
     def _fullySharedAddressBookGroupComponent(self):
 
-        n = self.ownerHome().shareeAddressBookName()
+        n = self.shareeName()
         fn = n
         uid = self.name()
 
@@ -661,7 +674,7 @@
         )
         # get ownerHomeIDs
         for dataRow in dataRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
             ownerHome = yield home.ownerHomeWithChildID(resourceID)
             ownerHomeToDataRowMap[ownerHome] = dataRow
 
@@ -670,7 +683,7 @@
             home._txn, homeID=home._resourceID
         )
         for groupBindRow in groupBindRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
             ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
             ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
             if ownerHome not in ownerHomeToDataRowMap:
@@ -693,7 +706,7 @@
 
             # Create the actual objects merging in properties
             for ownerHome, dataRow in ownerHomeToDataRowMap.iteritems():
-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount]  #@UnusedVariable
+                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
                 additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
                 metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
 
@@ -785,7 +798,7 @@
         if not rows:
             returnValue(None)
 
-        bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage, ownerAddressBookID, cachedBindStatus = rows[0]  #@UnusedVariable
+        bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage, ownerAddressBookID, cachedBindStatus = rows[0] #@UnusedVariable
 
         # if wrong status, exit here.  Item is in queryCache
         if (cachedBindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
@@ -795,7 +808,7 @@
         ownerAddressBook = ownerHome.addressbook()
         child = cls(
                 home=home,
-                name=ownerAddressBook.shareeAddressBookName(), resourceID=ownerAddressBookID,
+                name=ownerAddressBook.shareeName(), resourceID=ownerAddressBookID,
                 mode=bindMode, status=bindStatus,
                 revision=bindRevision,
                 message=bindMessage, ownerHome=ownerHome,
@@ -821,7 +834,7 @@
         """
         bindRows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
         if bindRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRows[0]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRows[0] #@UnusedVariable
             if (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
                 returnValue(None)
 
@@ -837,7 +850,7 @@
             home._txn, name=name, homeID=home._resourceID
         )
         if groupBindRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRows[0]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRows[0] #@UnusedVariable
             if (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
                 returnValue(None)
 
@@ -876,7 +889,7 @@
             home._txn, resourceID=resourceID, homeID=home._resourceID
         )
         if bindRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRows[0]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRows[0] #@UnusedVariable
             if (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
                 returnValue(None)
 
@@ -890,7 +903,7 @@
                     home._txn, homeID=home._resourceID, addressbookID=resourceID
         )
         if groupBindRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRows[0]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRows[0] #@UnusedVariable
             if (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
                 returnValue(None)
 
@@ -929,7 +942,7 @@
             home._txn, homeID=home._resourceID
         )
         for row in rows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
             ownerHome = yield home._txn.homeWithResourceID(home._homeType, resourceID, create=True)
             names |= set([ownerHome.shareeAddressBookName()])
 
@@ -937,7 +950,7 @@
             home._txn, homeID=home._resourceID
         )
         for groupRow in groupRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, 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.shareeAddressBookName()])
@@ -1008,7 +1021,7 @@
             readWriteGroupIDs = []
             readOnlyGroupIDs = []
             for groupBindRow in groupBindRows:
-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount]  #@UnusedVariable
+                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
                 if bindMode == _BIND_MODE_WRITE:
                     readWriteGroupIDs.append(resourceID)
                 else:
@@ -1056,7 +1069,7 @@
         readWriteGroupIDs = []
         readOnlyGroupIDs = []
         for groupBindRow in groupBindRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
             if bindMode == _BIND_MODE_WRITE:
                 readWriteGroupIDs.append(resourceID)
             else:
@@ -1177,60 +1190,6 @@
 
 
     @inlineCallbacks
-    def asShared(self):
-        """
-        Retrieve all the versions of this L{AddressBook} as it is shared to
-        everyone.
-
-        @see: L{IAddressBookHome.asShared}
-
-        @return: L{CommonHomeChild} objects that represent this
-            L{CommonHomeChild} as a child of different L{CommonHome}s
-        @rtype: a L{Deferred} which fires with a L{list} of L{ICalendar}s.
-        """
-        result = []
-        if self.owned():
-            # get all accepted shared binds
-            bindRows = yield self._sharedBindForResourceID.on(
-                self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
-            )
-            for bindRow in bindRows:
-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRow[:self.bindColumnCount]  #@UnusedVariable
-                home = yield self._txn.homeWithResourceID(self._home._homeType, homeID, create=True)
-                new = yield home.childWithName(self.shareeAddressBookName())
-                result.append(new)
-
-        returnValue(result)
-
-
-    @inlineCallbacks
-    def asInvited(self):
-        """
-        Retrieve all the versions of this L{CommonHomeChild} as it is invited to
-        everyone.
-
-        @see: L{ICalendarHome.asInvited}
-
-        @return: L{CommonHomeChild} objects that represent this
-            L{CommonHomeChild} as a child of different L{CommonHome}s
-        @rtype: a L{Deferred} which fires with a L{list} of L{ICalendar}s.
-        """
-        result = []
-        if self.owned():
-            # get all accepted shared binds
-            bindRows = yield self._unacceptedBindForResourceID.on(
-                self._txn, resourceID=self._resourceID
-            )
-            for bindRow in bindRows:
-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = bindRow[:self.bindColumnCount]  #@UnusedVariable
-                home = yield self._txn.homeWithResourceID(self._home._homeType, homeID, create=True)
-                new = yield self.objectWithName(home, self.shareeAddressBookName(), accepted=False)
-                result.append(new)
-
-        returnValue(result)
-
-
-    @inlineCallbacks
     def shareWith(self, shareeHome, mode, status=None, message=None):
         """
             call super and set isShared = True
@@ -1253,7 +1212,7 @@
 
         @return: a L{Deferred} which will fire with the previous shareUID
         """
-        sharedAddressBook = yield shareeHome.addressbookWithName(self.shareeAddressBookName())
+        sharedAddressBook = yield shareeHome.addressbookWithName(self.shareeName())
         if sharedAddressBook:
 
             acceptedBindCount = 1 if sharedAddressBook.fullyShared() else 0
@@ -1279,7 +1238,7 @@
             deletedBindName = deletedBindNameRows[0][0]
             queryCacher = self._txn._queryCacher
             if queryCacher:
-                cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self.shareeAddressBookName())
+                cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self.shareeName())
                 queryCacher.invalidateAfterCommit(self._txn, cacheKey)
         else:
             deletedBindName = None
@@ -1293,6 +1252,7 @@
 
     implements(IAddressBookObject)
 
+    _homeSchema = schema.ADDRESSBOOK_HOME
     _objectSchema = schema.ADDRESSBOOK_OBJECT
     _bindSchema = schema.SHARED_GROUP_BIND
 
@@ -1301,7 +1261,7 @@
     #_homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
 
 
-    def __init__(self, addressbook, name, uid, resourceID=None, options=None):  #@UnusedVariable
+    def __init__(self, addressbook, name, uid, resourceID=None, options=None): #@UnusedVariable
 
         self._kind = None
         self._ownerAddressBookResourceID = None
@@ -1436,7 +1396,7 @@
 
 
     @classproperty
-    def _allColumnsWithResourceID(cls):  #@NoSelf
+    def _allColumnsWithResourceID(cls): #@NoSelf
         obj = cls._objectSchema
         return Select(
             cls._allColumns, From=obj,
@@ -1521,7 +1481,7 @@
 
                 if groupBindRows:
                     groupBindRow = groupBindRows[0]
-                    bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount]  #@UnusedVariable
+                    bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
                     self._bindMode = bindMode
                     self._bindStatus = bindStatus
                     self._bindMessage = bindMessage
@@ -1537,7 +1497,7 @@
 
 
     @classproperty
-    def _allColumns(cls):  #@NoSelf
+    def _allColumns(cls): #@NoSelf
         """
         Full set of columns in the object table that need to be loaded to
         initialize the object resource state.
@@ -1651,7 +1611,7 @@
         self.validAddressDataCheck(component, inserting)
 
 
-    def validAddressDataCheck(self, component, inserting):  #@UnusedVariable
+    def validAddressDataCheck(self, component, inserting): #@UnusedVariable
         """
         Check that the calendar data is valid iCalendar.
         @return:         tuple: (True/False if the calendar data is valid,
@@ -1714,7 +1674,9 @@
         if self.owned():
             # update revision table of the sharee group address book
             if self._kind == _ABO_KIND_GROUP:  # optimization
-                for shareeAddressBook in (yield self.asShared()):
+                invites = yield self.sharingInvites()
+                for invite in invites:
+                    shareeAddressBook = (yield self._txn.homeWithResourceID(self.addressbook()._home._homeType, invite.shareeHomeID()))
                     yield self._changeAddressBookRevision(shareeAddressBook, inserting)
                     # one is enough because all have the same resourceID
                     break
@@ -1757,7 +1719,7 @@
 
 
     @classproperty
-    def _insertABObject(cls):  #@NoSelf
+    def _insertABObject(cls): #@NoSelf
         """
         DAL statement to create an addressbook object with all default values.
         """
@@ -1777,7 +1739,7 @@
 
 
     @inlineCallbacks
-    def updateDatabase(self, component, expand_until=None, reCreate=False,  #@UnusedVariable
+    def updateDatabase(self, component, expand_until=None, reCreate=False, #@UnusedVariable
                        inserting=False):
         """
         Update the database tables for the new data being written.
@@ -2099,7 +2061,7 @@
 
     # same as CommonHomeChild._childrenAndMetadataForHomeID() w/o metadata join
     @classproperty
-    def _childrenAndMetadataForHomeID(cls):  #@NoSelf
+    def _childrenAndMetadataForHomeID(cls): #@NoSelf
         bind = cls._bindSchema
         child = cls._objectSchema
         columns = cls.bindColumns() + cls.additionalBindColumns() + cls.metadataColumns()
@@ -2115,68 +2077,8 @@
         return self.addressbook().notifyChanged()
 
 
-    @inlineCallbacks
-    def asShared(self):
-        """
-        Retrieve all the versions of this L{AddressBookObject} as it is shared to
-        everyone.
-
-        @see: L{IAddressBookHome.asShared}
-
-        @return: L{AddressBookObject} objects that represent this
-            L{AddressBookObject} as a child of different L{AddressBooks}s
-            in different L{CommonHome}s
-        @rtype: a L{Deferred} which fires with a L{list} of L{AddressBookObject}s.
-        """
-        result = []
-        if self.owned():
-            # get all accepted shared binds
-            groupBindRows = yield self._sharedBindForResourceID.on(
-                self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
-            )
-            for groupBindRow in groupBindRows:
-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
-                home = yield self._txn.homeWithResourceID(self._home._homeType, homeID, create=True)
-                addressbook = yield home.childWithName(self._home.shareeAddressBookName())
-                new = yield addressbook.objectResourceWithID(resourceID)
-                result.append(new)
-
-        returnValue(result)
-
-
-    @inlineCallbacks
-    def asInvited(self):
-        """
-        Retrieve all the versions of this L{AddressBookObject} as it is shared to
-        everyone.
-
-        @see: L{ICalendarHome.asShared}
-
-        @return: L{AddressBookObject} objects that represent this
-            L{AddressBookObject} as a child of different L{AddressBooks}s
-            in different L{CommonHome}s
-        @rtype: a L{Deferred} which fires with a L{list} of L{AddressBookObject}s.
-        """
-        result = []
-        if self.owned():
-            # get all accepted shared binds
-            groupBindRows = yield self._unacceptedBindForResourceID.on(
-                self._txn, resourceID=self._resourceID
-            )
-            for groupBindRow in groupBindRows:
-                bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
-                home = yield self._txn.homeWithResourceID(self._home._homeType, homeID, create=True)
-                addressbook = yield home.childWithName(self._home.shareeAddressBookName())
-                if not addressbook:
-                    addressbook = yield AddressBook.objectWithName(home, self._home.shareeAddressBookName(), accepted=False)
-                new = yield AddressBookObject.objectWithID(addressbook, resourceID)  # avoids object cache
-                result.append(new)
-
-        returnValue(result)
-
-
     @classproperty
-    def _addressbookIDForResourceID(cls):  #@NoSelf
+    def _addressbookIDForResourceID(cls): #@NoSelf
         obj = cls._objectSchema
         return Select([obj.PARENT_RESOURCE_ID],
                       From=obj,
@@ -2192,6 +2094,40 @@
 
 
     @inlineCallbacks
+    def sharingInvites(self):
+        """
+        Retrieve the list of all L{SharingInvitation} for this L{CommonHomeChild}, irrespective of mode.
+
+        @return: L{SharingInvitation} objects
+        @rtype: a L{Deferred} which fires with a L{list} of L{SharingInvitation}s.
+        """
+        if not self.owned():
+            returnValue([])
+
+        # get all accepted binds
+        acceptedRows = yield self._sharedInvitationBindForResourceID.on(
+            self._txn, resourceID=self._resourceID, homeID=self.addressbook()._home._resourceID
+        )
+
+        result = []
+        for homeUID, homeRID, resourceID, resourceName, bindMode, bindStatus, bindMessage in acceptedRows: #@UnusedVariable
+            invite = SharingInvitation(
+                resourceName,
+                self.addressbook()._home.name(),
+                self.addressbook()._home._resourceID,
+                homeUID,
+                homeRID,
+                resourceID,
+                self.addressbook().shareeName(),
+                bindMode,
+                bindStatus,
+                bindMessage,
+            )
+            result.append(invite)
+        returnValue(result)
+
+
+    @inlineCallbacks
     def unshare(self):
         """
         Unshares a group, regardless of which "direction" it was shared.
@@ -2199,8 +2135,10 @@
         if self._kind == _ABO_KIND_GROUP:
             if self.owned():
                 # This collection may be shared to others
-                for sharedToHome in [x.viewerHome() for x in (yield self.asShared())]:
-                    yield self.unshareWith(sharedToHome)
+                invites = yield self.sharingInvites()
+                for invite in invites:
+                    shareeHome = (yield self._txn.homeWithResourceID(self.addressbook()._home._homeType, invite.shareeHomeID()))
+                    (yield self.unshareWith(shareeHome))
             else:
                 # This collection is shared to me
                 ownerAddressBook = self.addressbook().ownerHome().addressbook()
@@ -2222,7 +2160,7 @@
 
         @return: a L{Deferred} which will fire with the previously-used name.
         """
-        sharedAddressBook = yield shareeHome.addressbookWithName(self.addressbook().shareeAddressBookName())
+        sharedAddressBook = yield shareeHome.addressbookWithName(self.addressbook().shareeName())
 
         if sharedAddressBook:
 
@@ -2235,7 +2173,7 @@
 
             if acceptedBindCount == 1:
                 yield sharedAddressBook._deletedSyncToken(sharedRemoval=True)
-                shareeHome._children.pop(self.addressbook().shareeAddressBookName(), None)
+                shareeHome._children.pop(self.addressbook().shareeName(), None)
                 shareeHome._children.pop(self.addressbook()._resourceID, None)
 
             # Must send notification to ensure cache invalidation occurs
@@ -2250,7 +2188,7 @@
             deletedBindName = deletedBindNameRows[0][0]
             queryCacher = self._txn._queryCacher
             if queryCacher:
-                cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self.addressbook().shareeAddressBookName())
+                cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, self.addressbook().shareeName())
                 queryCacher.invalidateAfterCommit(self._txn, cacheKey)
         else:
             deletedBindName = None
@@ -2303,7 +2241,7 @@
                 self._txn, resourceID=self._resourceID, homeID=shareeHome._resourceID
             )
             groupBindRow = groupBindRows[0]
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount]  #@UnusedVariable
+            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:self.bindColumnCount] #@UnusedVariable
             if bindStatus == _BIND_STATUS_ACCEPTED:
                 group = yield shareeHome.objectWithShareUID(bindName)
             else:
@@ -2438,7 +2376,7 @@
 
 
     @classproperty
-    def _acceptedBindForHomeIDAndAddressBookID(cls):  #@NoSelf
+    def _acceptedBindForHomeIDAndAddressBookID(cls): #@NoSelf
         bind = cls._bindSchema
         abo = cls._objectSchema
         return Select(
@@ -2452,7 +2390,7 @@
 
 
     @classproperty
-    def _unacceptedBindForHomeIDAndAddressBookID(cls):  #@NoSelf
+    def _unacceptedBindForHomeIDAndAddressBookID(cls): #@NoSelf
         bind = cls._bindSchema
         abo = cls._objectSchema
         return Select(
@@ -2466,7 +2404,7 @@
 
 
     @classproperty
-    def _bindForHomeIDAndAddressBookID(cls):  #@NoSelf
+    def _bindForHomeIDAndAddressBookID(cls): #@NoSelf
         bind = cls._bindSchema
         abo = cls._objectSchema
         return Select(

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_file.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -466,7 +466,6 @@
         can be retrieved with L{IAddressBookHome.addressbookWithName}.
         """
 
-
     @testUnimplemented
     def test_removeAddressBookWithName_exists(self):
         """

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -1285,7 +1285,7 @@
 
 
     @inlineCallbacks
-    def asInvited(self):
+    def sharingInvites(self):
         """
         Stub for interface-compliance tests.
         """
@@ -1293,16 +1293,7 @@
         returnValue([])
 
 
-    @inlineCallbacks
-    def asShared(self):
-        """
-        Stub for interface-compliance tests.
-        """
-        yield None
-        returnValue([])
 
-
-
 class CommonObjectResource(FileMetaDataMixin, FancyEqMixin):
     """
     @ivar _path: The path of the file on disk

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2013-07-22 19:25:12 UTC (rev 11543)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2013-07-22 19:56:16 UTC (rev 11544)
@@ -293,7 +293,17 @@
         returnValue(self._dropbox_ok)
 
 
+    def queryCachingEnabled(self):
+        """
+        Indicate whether SQL statement query caching is enabled. Also controls whether propstore caching is done.
 
+        @return: C{True} if enabled, else C{False}
+        @rtype: C{bool}
+        """
+        return self.queryCacher is not None
+
+
+
 class TransactionStatsCollector(object):
     """
     Used to log each SQL query and statistics about that query during the course of a single transaction.
@@ -2620,6 +2630,125 @@
 
 
 
+class SharingInvitation(object):
+    """
+    SharingInvitation covers all the information needed to expose sharing invites to upper layers. Its primarily used to
+    minimize the need to load full properties/data when only this subset of information is needed.
+    """
+    def __init__(self, uid, owner_uid, owner_rid, sharee_uid, sharee_rid, resource_id, resource_name, mode, status, summary):
+        self._uid = uid
+        self._owner_uid = owner_uid
+        self._owner_rid = owner_rid
+        self._sharee_uid = sharee_uid
+        self._sharee_rid = sharee_rid
+        self._resource_id = resource_id
+        self._resource_name = resource_name
+        self._mode = mode
+        self._status = status
+        self._summary = summary
+
+
+    @classmethod
+    def fromCommonHomeChild(cls, homeChild):
+        return cls(
+            homeChild.shareUID(),
+            homeChild.ownerHome().uid(),
+            homeChild.ownerHome()._resourceID,
+            homeChild.viewerHome().uid(),
+            homeChild.viewerHome()._resourceID,
+            homeChild._resourceID,
+            homeChild.shareeName(),
+            homeChild.shareMode(),
+            homeChild.shareStatus(),
+            homeChild.shareMessage(),
+        )
+
+
+    def uid(self):
+        """
+        This maps to the resource name in the bind table, the "bind name". This is used as the "uid"
+        for invites, and is not necessarily the name of the resource as it appears in the collection.
+        """
+        return self._uid
+
+
+    def ownerUID(self):
+        """
+        The ownerUID of the sharer.
+        """
+        return self._owner_uid
+
+
+    def ownerHomeID(self):
+        """
+        The resourceID of the sharer's L{CommonHome}.
+        """
+        return self._owner_rid
+
+
+    def shareeUID(self):
+        """
+        The ownerUID of the sharee.
+        """
+        return self._sharee_uid
+
+
+    def shareeHomeID(self):
+        """
+        The resourceID of the sharee's L{CommonHome}.
+        """
+        return self._sharee_rid
+
+
+    def resourceID(self):
+        """
+        The resourceID of the shared object.
+        """
+        return self._resource_id
+
+
+    def resourceName(self):
+        """
+        This maps to the name of the shared resource in the collection it is bound into. It is not necessarily the
+        same as the "bind name" which is used as the "uid" for invites.
+        """
+        return self._resource_name
+
+
+    def mode(self):
+        """
+        The sharing mode: one of the _BIND_MODE_XXX values.
+        """
+        return self._mode
+
+
+    def setMode(self, mode):
+        self._mode = mode
+
+
+    def status(self):
+        """
+        The sharing status: one of the _BIND_STATUS_XXX values.
+        """
+        return self._status
+
+
+    def setStatus(self, status):
+        self._status = status
+
+
+    def summary(self):
+        """
+        Message associated with the invitation.
+        """
+        return self._summary
+
+
+    def setSummary(self, summary):
+        self._summary = summary
+
+
+
 class SharingMixIn(object):
     """
     Common class for CommonHomeChild and AddressBookObject
@@ -2682,13 +2811,32 @@
         )
 
 
+    @classmethod
+    def _bindInviteFor(cls, condition): #@NoSelf
+        home = cls._homeSchema
+        bind = cls._bindSchema
+        return Select(
+            [
+                home.OWNER_UID,
+                bind.HOME_RESOURCE_ID,
+                bind.RESOURCE_ID,
+                bind.RESOURCE_NAME,
+                bind.BIND_MODE,
+                bind.BIND_STATUS,
+                bind.MESSAGE,
+            ],
+            From=bind.join(home, on=(bind.HOME_RESOURCE_ID == home.RESOURCE_ID)),
+            Where=condition
+        )
+
+
     @classproperty
-    def _sharedBindForResourceID(cls): #@NoSelf
+    def _sharedInvitationBindForResourceID(cls): #@NoSelf
         bind = cls._bindSchema
-        return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
-                            .And(bind.BIND_STATUS == _BIND_STATUS_ACCEPTED)
-                            .And(bind.BIND_MODE != _BIND_MODE_OWN)
-                            )
+        return cls._bindInviteFor(
+            (bind.RESOURCE_ID == Parameter("resourceID")).And
+            (bind.BIND_MODE != _BIND_MODE_OWN)
+        )
 
 
     @classproperty
@@ -2699,14 +2847,6 @@
 
 
     @classproperty
-    def _unacceptedBindForResourceID(cls): #@NoSelf
-        bind = cls._bindSchema
-        return cls._bindFor((bind.RESOURCE_ID == Parameter("resourceID"))
-                            .And(bind.BIND_STATUS != _BIND_STATUS_ACCEPTED)
-                            )
-
-
-    @classproperty
     def _bindForResourceIDAndHomeID(cls): #@NoSelf
         """
         DAL query that looks up home bind rows by home child
@@ -2791,6 +2931,24 @@
 
 
     @inlineCallbacks
+    def updateShareFromSharingInvitation(self, invitation, mode=None, status=None, message=None, name=None):
+        """
+        Like L{updateShare} except that the original invitation is provided. That is used
+        to find the actual sharee L{CommonHomeChild} which is then passed to L{updateShare}.
+        """
+
+        # Look up the shared child - might be accepted or not. If accepted use the resource name
+        # to look it up, else use the invitation uid (bind name)
+        shareeHome = yield self._txn.homeWithUID(self._home._homeType, invitation.shareeUID())
+        shareeView = yield shareeHome.childWithName(invitation.resourceName())
+        if shareeView is None:
+            shareeView = yield shareeHome.invitedObjectWithShareUID(invitation.uid())
+
+        result = yield self.updateShare(shareeView, mode, status, message, name)
+        returnValue(result)
+
+
+    @inlineCallbacks
     def updateShare(self, shareeView, mode=None, status=None, message=None, name=None):
         """
         Update share mode, status, and message for a home child shared with
@@ -2870,6 +3028,18 @@
 
 
     @inlineCallbacks
+    def unshareWithUID(self, shareeUID):
+        """
+        Like L{unshareWith} except this is passed a sharee UID which is then used to lookup the
+        L{CommonHome} for the sharee to pass to L{unshareWith}.
+        """
+
+        shareeHome = yield self._txn.homeWithUID(self._home._homeType, shareeUID)
+        result = yield self.unshareWith(shareeHome)
+        returnValue(result)
+
+
+    @inlineCallbacks
     def unshareWith(self, shareeHome):
         """
         Remove the shared version of this (owned) L{CommonHomeChild} from the
@@ -2918,8 +3088,10 @@
         """
         if self.owned():
             # This collection may be shared to others
-            for sharedToHome in [x.viewerHome() for x in (yield self.asShared())]:
-                yield self.unshareWith(sharedToHome)
+            invites = yield self.sharingInvites()
+            for invite in invites:
+                shareeHome = (yield self._txn.homeWithResourceID(self._home._homeType, invite.shareeHomeID()))
+                (yield self.unshareWith(shareeHome))
         else:
             # This collection is shared to me
             ownerHomeChild = yield self.ownerHome().childWithID(self._resourceID)
@@ -2927,65 +3099,40 @@
 
 
     @inlineCallbacks
-    def asShared(self):
+    def sharingInvites(self):
         """
-        Retrieve all the versions of this L{CommonHomeChild} as it is shared to
-        everyone.
+        Retrieve the list of all L{SharingInvitation}'s for this L{CommonHomeChild}, irrespective of mode.
 
-        @see: L{ICalendarHome.asShared}
-
-        @return: L{CommonHomeChild} objects that represent this
-            L{CommonHomeChild} as a child of different L{CommonHome}s
-        @rtype: a L{Deferred} which fires with a L{list} of L{ICalendar}s.
+        @return: L{SharingInvitation} objects
+        @rtype: a L{Deferred} which fires with a L{list} of L{SharingInvitation}s.
         """
         if not self.owned():
             returnValue([])
 
         # get all accepted binds
-        acceptedRows = yield self._sharedBindForResourceID.on(
-            self._txn, resourceID=self._resourceID,
+        acceptedRows = yield self._sharedInvitationBindForResourceID.on(
+            self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
         )
 
         result = []
-        for row in acceptedRows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = row[:self.bindColumnCount] #@UnusedVariable
-            home = yield self._txn.homeWithResourceID(self._home._homeType, homeID)
-            new = yield home.objectWithShareUID(bindName)
-            result.append(new)
-
+        for homeUID, homeRID, resourceID, resourceName, bindMode, bindStatus, bindMessage in acceptedRows: #@UnusedVariable
+            invite = SharingInvitation(
+                resourceName,
+                self._home.name(),
+                self._home._resourceID,
+                homeUID,
+                homeRID,
+                resourceID,
+                resourceName if self.bindNameIsResourceName() else self.shareeName(),
+                bindMode,
+                bindStatus,
+                bindMessage,
+            )
+            result.append(invite)
         returnValue(result)
 
 
     @inlineCallbacks
-    def asInvited(self):
-        """
-        Retrieve all the versions of this L{CommonHomeChild} as it is invited to
-        everyone.
-
-        @see: L{ICalendarHome.asInvited}
-
-        @return: L{CommonHomeChild} objects that represent this
-            L{CommonHomeChild} as a child of different L{CommonHome}s
-        @rtype: a L{Deferred} which fires with a L{list} of L{ICalendar}s.
-        """
-        if not self.owned():
-            returnValue([])
-
-        rows = yield self._unacceptedBindForResourceID.on(
-            self._txn, resourceID=self._resourceID,
-        )
-
-        result = []
-        for row in rows:
-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = row[:self.bindColumnCount] #@UnusedVariable
-            home = yield self._txn.homeWithResourceID(self._home._homeType, homeID)
-            new = yield home.invitedObjectWithShareUID(bindName)
-            result.append(new)
-
-        returnValue(result)
-
-
-    @inlineCallbacks
     def _initBindRevision(self):
         self._bindRevision = self._syncTokenRevision
 
@@ -3050,6 +3197,20 @@
         yield self.notifyPropertyChanged()
 
 
+    def shareeName(self):
+        """
+        The sharee's name for a shared L{CommonHomeChild} is the name of the resource by default.
+        """
+        return self.name()
+
+
+    def bindNameIsResourceName(self):
+        """
+        By default, the shared resource name of an accepted share is the same as the name in the bind table.
+        """
+        return True
+
+
     def shareStatus(self):
         """
         @see: L{ICalendar.shareStatus}
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130722/0d04c352/attachment-0001.html>


More information about the calendarserver-changes mailing list