[CalendarServer-changes] [13731] CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/ datastore/sql.py

source_changes at macosforge.org source_changes at macosforge.org
Mon Jul 7 16:41:26 PDT 2014


Revision: 13731
          http://trac.calendarserver.org//changeset/13731
Author:   gaya at apple.com
Date:     2014-07-07 16:41:26 -0700 (Mon, 07 Jul 2014)
Log Message:
-----------
checkpoint, add high-level sharing interface

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/sql.py

Modified: CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/sql.py	2014-07-07 16:52:57 UTC (rev 13730)
+++ CalendarServer/branches/users/gaya/groupsharee2/txdav/caldav/datastore/sql.py	2014-07-07 23:41:26 UTC (rev 13731)
@@ -27,13 +27,8 @@
 ]
 
 from twext.enterprise.dal.record import fromTable
-from twext.enterprise.dal.syntax import Delete
-from twext.enterprise.dal.syntax import Insert
-from twext.enterprise.dal.syntax import Len
-from twext.enterprise.dal.syntax import Parameter
-from twext.enterprise.dal.syntax import Select, Count, ColumnSyntax
-from twext.enterprise.dal.syntax import Update
-from twext.enterprise.dal.syntax import utcNowSQL
+from twext.enterprise.dal.syntax import Count, ColumnSyntax, Delete, \
+    Insert, Len, Max, Parameter, Select, Update, utcNowSQL
 from twext.enterprise.locking import NamedLock
 from twext.enterprise.jobqueue import WorkItem
 from twext.enterprise.util import parseSQLTimestamp
@@ -89,7 +84,9 @@
 from txdav.common.datastore.sql_tables import _ATTACHMENTS_MODE_NONE, \
     _ATTACHMENTS_MODE_WRITE, schema, _BIND_MODE_OWN, \
     _ATTACHMENTS_MODE_READ, _TRANSP_OPAQUE, _TRANSP_TRANSPARENT, \
-    _BIND_MODE_GROUP
+    _BIND_MODE_GROUP, _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_MODE_DIRECT, \
+    _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, _BIND_STATUS_INVALID, \
+    _BIND_STATUS_INVITED, _BIND_STATUS_DELETED
 from txdav.common.icommondatastore import IndexedSearchException, \
     InternalDataStoreError, HomeChildNameAlreadyExistsError, \
     HomeChildNameNotAllowedError, ObjectResourceTooBigError, \
@@ -97,7 +94,7 @@
     ObjectResourceNameNotAllowedError, TooManyObjectResourcesError, \
     InvalidUIDError, UIDExistsError, UIDExistsElsewhereError, \
     InvalidResourceMove, InvalidComponentForStoreError, \
-    NoSuchObjectResourceError, ConcurrentModification, AllRetriesFailed
+    NoSuchObjectResourceError, ConcurrentModification
 from txdav.xml import element
 from txdav.xml.parser import WebDAVDocument
 
@@ -1781,28 +1778,29 @@
         @return: the name of the shared calendar in the new calendar home.
         @rtype: L{str}
         """
-        record = (
-            yield self._txn.directoryService().recordWithUID(shareeUID.decode("utf-8"))
-        ) if True or (
-              config.Sharing.Enabled and
-              config.Sharing.Calendars.Enabled and
-              config.Sharing.Calendars.Groups.Enabled
-        ) else None
-
-        if record is None or record.recordType != RecordType.group:
+        record = yield self._txn.directoryService().recordWithUID(shareeUID.decode("utf-8"))
+        if (
+            record is None or
+            record.recordType != RecordType.group or not (False and
+                config.Sharing.Enabled and
+                config.Sharing.Calendars.Enabled and
+                config.Sharing.Calendars.Groups.Enabled
+            )
+        ):
             shareeHome = yield self._txn.calendarHomeWithUID(shareeUID, create=True)
             returnValue(
                 (yield self.shareWith(shareeHome, mode, status, summary, shareName))
             )
 
         # shareWith every member of group not already shared to
-        members = yield record.expandedMembers()
-        for member in members:
-            shareeHome = yield self._txn.calendarHomeWithUID(member.uid(), create=True)
+        groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self._txn.groupByUID(record.uid)
+        memberUIDs = yield self._txn.membersOfGroup(groupID)
+        for memberUID in memberUIDs:
+            shareeHome = yield self._txn.calendarHomeWithUID(memberUID, create=True)
             if (yield shareeHome.childWithID(self._resourceID)) is None:
                 yield self.shareWith(shareeHome, _BIND_MODE_GROUP, status)
 
-        yield self.updateGroupLink(shareeUID, mode)
+        yield self.updateShareeGroupLink(shareeUID, mode)
         returnValue(None)
 
 
@@ -1815,13 +1813,9 @@
         if (yield self.updateShareeGroupLink, groupUID):
             record = (
                 yield self._txn.directoryService().recordWithUID(groupUID.decode("utf-8"))
-            ) if True or (
-                  config.Sharing.Enabled and
-                  config.Sharing.Calendars.Enabled and
-                  config.Sharing.Calendars.Groups.Enabled
-            ) else None
-            members = yield record.expandedMembers() if record is not None else []
-            memberUIDs = set([member.uid() for member in members])
+            )
+            groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self._txn.groupByUID(record.uid)
+            memberUIDs = yield self._txn.membersOfGroup(groupID)
             boundUIDs = set()
 
             bind = schema.CALENDAR_BIND
@@ -1842,8 +1836,8 @@
                     yield self.removeShare(shareeView)
                     changed = True
 
-            for member in [member for member in members if member.uid() not in boundUIDs]:
-                shareeHome = yield self._txn.calendarHomeWithUID(member.uid(), create=True)
+            for memberUID in memberUIDs - boundUIDs:
+                shareeHome = yield self._txn.calendarHomeWithUID(memberUID, create=True)
                 if (yield shareeHome.childWithID(self._resourceID)) is None:
                     yield self.shareWith(shareeHome, _BIND_MODE_GROUP)
                     changed = True
@@ -1863,7 +1857,7 @@
         rows = yield Select(
             [gs.MEMBERSHIP_HASH, gs.MODE],
             From=gs,
-            Where=gs.HOME_ID == self.ownerHome()._resourceID.And(
+            Where=(gs.CALENDAR_HOME_ID == self.ownerHome()._resourceID).And(
                 gs.CALENDAR_ID == self._resourceID).And(
                 gs.GROUP_ID == groupID)
         ).on(self._txn)
@@ -1877,7 +1871,7 @@
             if updateMap:
                 yield Update(
                     updateMap,
-                    Where=gs.HOME_ID == self.ownerHome()._resourceID.And(
+                    Where=(gs.CALENDAR_HOME_ID == self.ownerHome()._resourceID).And(
                         gs.CALENDAR_ID == self._resourceID).And(
                         gs.GROUP_ID == groupID
                     )
@@ -1887,7 +1881,7 @@
             yield Insert({
                 gs.MEMBERSHIP_HASH: membershipHash,
                 gs.GROUP_BIND_MODE: mode,
-                gs.HOME_ID: self.ownerHome()._resourceID,
+                gs.CALENDAR_HOME_ID: self.ownerHome()._resourceID,
                 gs.CALENDAR_ID: self._resourceID,
                 gs.GROUP_ID: groupID,
             }).on(self._txn)
@@ -1901,18 +1895,191 @@
         if self._bindMode == _BIND_MODE_GROUP:
             gs = schema.GROUP_SHAREE
             rows = yield Select(
-                [gs.GROUP_BIND_MODE],
+                [Max(gs.GROUP_BIND_MODE)], # _BIND_MODE_WRITE > _BIND_MODE_READ
                 From=gs,
-                Where=gs.HOME_ID == self.ownerHome()._resourceID.And(
+                Where=(gs.CALENDAR_HOME_ID == self.ownerHome()._resourceID).And(
                     gs.CALENDAR_ID == self._resourceID
                 )
             ).on(self._txn)
-            groupShareMode = rows[0][0] if rows else None
+            groupShareMode = rows[0][0]
             returnValue(groupShareMode)
         else:
             returnValue(self._bindMode)
 
 
+    @inlineCallbacks
+    def removeShare(self, shareeView):
+        """
+        Remove the shared version of this (owned) L{CommonHomeChild} from the
+        referenced L{CommonHome}.
+
+        If user share and user is in shared group, change to group share.
+
+        @see: L{CommonHomeChild.shareWith}
+
+        @param shareeView: The shared resource being removed.
+
+        @return: a L{Deferred} which will fire with the previous shareUID
+        """
+
+        # see if after share is removed, user is still shared by a group sharee
+        effectiveShareMode = None
+        oldShareMode = shareeView.shareMode()
+        if oldShareMode in (_BIND_MODE_DIRECT, _BIND_MODE_READ, _BIND_MODE_WRITE, _BIND_MODE_GROUP,):
+
+            gs = schema.GROUP_SHAREE
+            rows = yield Select(
+                [gs.GROUP_ID, gs.GROUP_BIND_MODE],
+                From=gs,
+                Where=(gs.CALENDAR_HOME_ID == self.ownerHome()._resourceID).And(
+                    gs.CALENDAR_ID == self._resourceID)
+            ).on(self._txn)
+            shareeHomeUID = shareeView.viewerHome().uid()
+            for groupID, groupShareMode in rows:
+                memberUIDs = yield self._txn.membersOfGroup(groupID)
+                if shareeHomeUID in memberUIDs:
+                    if effectiveShareMode is None:
+                        effectiveShareMode = groupShareMode
+                    elif groupShareMode > effectiveShareMode:
+                        effectiveShareMode = groupShareMode
+
+        if effectiveShareMode is None:
+            # no group sharee for this user so let super do work
+            returnValue((yield super(Calendar, self).removeShare(shareeView)))
+        elif oldShareMode != _BIND_MODE_GROUP:
+            # change to group share
+            yield self.updateShare(shareeView, mode=_BIND_MODE_GROUP)
+            returnValue(None)
+
+        #TODO: effectiveShareMode may change between _BIND_MODE_READ & _BIND_MODE_READ.
+        #        Is that OK?
+
+
+    @inlineCallbacks
+    def removeShareWithUID(self, shareeUID):
+        """
+        Unshare this (owned) L{CommonHomeChild} with another principal.
+
+        @param shareeUID: The UID of the sharee.
+        @type: L{str}
+        """
+        record = yield self._txn.directoryService().recordWithUID(shareeUID.decode("utf-8"))
+        if (
+            record is None or
+            record.recordType != RecordType.group or not (False and
+                config.Sharing.Enabled and
+                config.Sharing.Calendars.Enabled and
+                config.Sharing.Calendars.Groups.Enabled
+            )
+        ):
+            returnValue(None)
+
+        # get group membership
+        groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self._txn.groupByUID(record.uid)
+        memberUIDs = yield self._txn.membersOfGroup(groupID)
+
+        # update groupsharee so that removeShare works
+        gs = schema.GROUP_SHAREE
+        yield Delete(
+            From=gs,
+            Where=(gs.CALENDAR_HOME_ID == self.ownerHome()._resourceID).And(
+                gs.CALENDAR_ID == self._resourceID).And(
+                gs.GROUP_ID == groupID)
+        ).on(self._txn)
+
+        for memberUID in memberUIDs:
+            shareeHome = yield self._txn.calendarHomeWithUID(memberUID, create=True)
+            shareeView = yield shareeHome.childWithID(self._resourceID)
+            yield self.removeShare(shareeView)
+
+
+    #
+    # Higher level API
+    #
+    @inlineCallbacks
+    def inviteUserToShare(self, shareeUID, mode, summary, shareName=None):
+        """
+        Invite a user to share this collection - either create the share if it does not exist, or
+        update the existing share with new values. Make sure a notification is sent as well.
+
+        @param shareeUID: UID of the sharee
+        @type shareeUID: C{str}
+        @param mode: access mode
+        @type mode: C{int}
+        @param summary: share message
+        @type summary: C{str}
+        """
+
+        # Look for existing invite and update its fields or create new one
+        shareeView = yield self.shareeView(shareeUID)
+        if shareeView is not None:
+            status = _BIND_STATUS_INVITED if shareeView.shareStatus() in (_BIND_STATUS_DECLINED, _BIND_STATUS_INVALID) else None
+            yield self.updateShare(shareeView, mode=mode, status=status, summary=summary)
+        else:
+            shareeView = yield self.createShare(shareeUID=shareeUID, mode=mode, summary=summary, shareName=shareName)
+
+        # Check for external
+        if shareeView.viewerHome().external():
+            yield self._sendExternalInvite(shareeView)
+        else:
+            # Send invite notification
+            yield self._sendInviteNotification(shareeView)
+        returnValue(shareeView)
+
+
+    @inlineCallbacks
+    def directShareWithUser(self, shareeUID, shareName=None):
+        """
+        Create a direct share with the specified user. Note it is currently up to the app layer
+        to enforce access control - this is not ideal as we really should have control of that in
+        the store. Once we do, this api will need to verify that access is allowed for a direct share.
+
+        NB no invitations are used with direct sharing.
+
+        @param shareeUID: UID of the sharee
+        @type shareeUID: C{str}
+        """
+
+        # Ignore if it already exists
+        shareeView = yield self.shareeView(shareeUID)
+        if shareeView is None:
+            shareeView = yield self.createShare(shareeUID=shareeUID, mode=_BIND_MODE_DIRECT, shareName=shareName)
+            yield shareeView.newShare()
+
+            # Check for external
+            if shareeView.viewerHome().external():
+                yield self._sendExternalInvite(shareeView)
+
+        returnValue(shareeView)
+
+
+    @inlineCallbacks
+    def uninviteUserFromShare(self, shareeUID):
+        """
+        Remove a user from a share. Make sure a notification is sent as well.
+
+        @param shareeUID: UID of the sharee
+        @type shareeUID: C{str}
+        """
+        # Cancel invites - we'll just use whatever userid we are given
+
+        shareeView = yield self.shareeView(shareeUID)
+        if shareeView is not None:
+            if shareeView.viewerHome().external():
+                yield self._sendExternalUninvite(shareeView)
+            else:
+                # 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 not shareeView.direct():
+                    if shareeView.shareStatus() != _BIND_STATUS_ACCEPTED:
+                        yield self._removeInviteNotification(shareeView)
+                    else:
+                        yield self._sendInviteNotification(shareeView, notificationState=_BIND_STATUS_DELETED)
+
+            # Remove the bind
+            yield self.removeShare(shareeView)
+
+
 icalfbtype_to_indexfbtype = {
     "UNKNOWN"         : 0,
     "FREE"            : 1,
@@ -2112,7 +2279,12 @@
 
             groupRecord = yield self.directoryService().recordWithCalendarUserAddress(groupCUA)
             if groupRecord:
-                members = yield groupRecord.expandedMembers()
+                # get members from cached membership
+                groupID, _ignore_name, _ignore_membershipHash, _ignore_modDate = yield self._txn.groupByUID(groupRecord.uid)
+                members = [(yield self.directoryService().recordWithUID(memberUID))
+                    for memberUID in
+                    (yield self._txn.membersOfGroup(groupID))
+                ]
                 groupCUAToAttendeeMemberPropMap[groupRecord.canonicalCalendarUserAddress()] = tuple(
                     [member.attendeeProperty(params={"MEMBER": groupCUA}) for member in sorted(members, key=lambda x: x.uid)]
                 )
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140707/0d504b72/attachment-0001.html>


More information about the calendarserver-changes mailing list