[CalendarServer-changes] [9325] CalendarServer/branches/users/gaya/sharedabgroups
source_changes at macosforge.org
source_changes at macosforge.org
Fri Jun 1 18:11:12 PDT 2012
Revision: 9325
http://trac.macosforge.org/projects/calendarserver/changeset/9325
Author: gaya at apple.com
Date: 2012-06-01 18:11:11 -0700 (Fri, 01 Jun 2012)
Log Message:
-----------
First working filtered group address books
Modified Paths:
--------------
CalendarServer/branches/users/gaya/sharedabgroups/twistedcaldav/sharing.py
CalendarServer/branches/users/gaya/sharedabgroups/twistedcaldav/storebridge.py
CalendarServer/branches/users/gaya/sharedabgroups/txdav/carddav/datastore/sql.py
CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql_legacy.py
CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql_schema/current.sql
Modified: CalendarServer/branches/users/gaya/sharedabgroups/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/twistedcaldav/sharing.py 2012-06-01 19:33:48 UTC (rev 9324)
+++ CalendarServer/branches/users/gaya/sharedabgroups/twistedcaldav/sharing.py 2012-06-02 01:11:11 UTC (rev 9325)
@@ -32,7 +32,7 @@
from twext.web2.dav.util import allDataFromStream, joinURL
from txdav.xml import element
-from twisted.internet.defer import succeed, inlineCallbacks, DeferredList,\
+from twisted.internet.defer import inlineCallbacks, DeferredList,\
returnValue
from twistedcaldav import customxml, caldavxml
@@ -75,10 +75,11 @@
def upgradeToShare(self):
""" Upgrade this collection to a shared state """
- # Change resourcetype
- rtype = self.resourceType()
- rtype = element.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
- self.writeDeadProperty(rtype)
+ if self.isCollection():
+ # Change resourcetype
+ rtype = self.resourceType()
+ rtype = element.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
+ self.writeDeadProperty(rtype)
# Create invites database
self.invitesDB().create()
@@ -202,7 +203,10 @@
@inlineCallbacks
def isShared(self, request):
""" Return True if this is an owner shared calendar collection """
- returnValue((yield self.isSpecialCollection(customxml.SharedOwner)))
+ if self.isCollection():
+ returnValue((yield self.isSpecialCollection(customxml.SharedOwner)))
+ else:
+ returnValue((yield self.isSharedGroup()))
def setVirtualShare(self, shareePrincipal, share):
@@ -211,7 +215,11 @@
self._share = share
if hasattr(self, "_newStoreObject"):
- self._newStoreObject.setSharingUID(self._shareePrincipal.principalUID())
+ if hasattr(self._newStoreObject, "setSharingUID"): # FIXME: skip this for now for groups
+ self._newStoreObject.setSharingUID(self._shareePrincipal.principalUID())
+ else:
+ print("xxx setVirtualShare() self=%s, shareePrincipal=%s, share=%s: skipping setSharingUID() " % (self, shareePrincipal, share,))
+
def isVirtualShare(self):
@@ -847,17 +855,48 @@
customxml.InviteReply: _xmlHandleInviteReply,
}
+ @inlineCallbacks
+ def isGroup(self):
+ try:
+ vCard = (yield self.vCard())
+ except AttributeError:
+ pass
+ else:
+ self.log_info("vCard = %s" % (vCard,))
+ if vCard.propertyValue("X-ADDRESSBOOKSERVER-KIND") == "group":
+ self.log_info("isGroup() returning True")
+ returnValue(True)
+
+ self.log_info("isGroup() returning False")
+ returnValue(False)
+
+
+ @inlineCallbacks
def POST_handler_content_type(self, request, contentType):
if self.isCollection():
if contentType:
if contentType in self._postHandlers:
- return self._postHandlers[contentType](self, request)
+ returnValue((yield self._postHandlers[contentType](self, request)))
else:
self.log_info("Get a POST of an unsupported content type on a collection type: %s" % (contentType,))
else:
self.log_info("Get a POST with no content type on a collection")
- return succeed(responsecode.FORBIDDEN)
+ elif (yield self.isGroup()):
+ if contentType:
+ if contentType in self._postHandlers:
+ returnValue((yield self._postHandlers[contentType](self, request)))
+ else:
+ self.log_info("Get a POST of an unsupported content type on a group type: %s" % (contentType,))
+ else:
+ self.log_info("Get a POST with no content type on a group")
+
+ else:
+ self.log_info("Got POST on non-collection, non-group object: %s" % (self,))
+
+ returnValue(responsecode.FORBIDDEN)
+
+
_postHandlers = {
("application", "xml") : xmlRequestHandler,
("text", "xml") : xmlRequestHandler,
@@ -1113,8 +1152,12 @@
# Set per-user displayname or color to whatever was given
sharedCollection.setVirtualShare(ownerPrincipal, share)
- if displayname:
- yield sharedCollection.writeProperty(element.DisplayName.fromString(displayname), request)
+ if sharedCollection.isCollection():
+ if displayname:
+ yield sharedCollection.writeProperty(element.DisplayName.fromString(displayname), request)
+ else:
+ print("xxx _acceptShare skipping set of display name")
+
if color:
yield sharedCollection.writeProperty(customxml.CalendarColor.fromString(color), request)
Modified: CalendarServer/branches/users/gaya/sharedabgroups/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/twistedcaldav/storebridge.py 2012-06-01 19:33:48 UTC (rev 9324)
+++ CalendarServer/branches/users/gaya/sharedabgroups/twistedcaldav/storebridge.py 2012-06-02 01:11:11 UTC (rev 9325)
@@ -2160,7 +2160,19 @@
vCard = _CommonObjectResource.component
+ def isSharedGroup(self):
+ print("xxx isSharedGroup: self = %s, self._newStoreObject=%s" % (self, self._newStoreObject, ))
+
+ return self._newStoreObject and self._newStoreObject.isSharedGroup()
+ def invitesDB(self):
+ """
+ Retrieve the new-style invites DB wrapper.
+ """
+ if not hasattr(self, "_invitesDB"):
+ self._invitesDB = self._newStoreObject.retrieveOldInvites()
+ return self._invitesDB
+
class _NotificationChildHelper(object):
"""
Methods for things which are like notification objects.
Modified: CalendarServer/branches/users/gaya/sharedabgroups/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/txdav/carddav/datastore/sql.py 2012-06-01 19:33:48 UTC (rev 9324)
+++ CalendarServer/branches/users/gaya/sharedabgroups/txdav/carddav/datastore/sql.py 2012-06-02 01:11:11 UTC (rev 9325)
@@ -40,7 +40,7 @@
from txdav.common.datastore.sql_legacy import \
PostgresLegacyABIndexEmulator, SQLLegacyAddressBookInvites,\
- SQLLegacyAddressBookShares
+ SQLLegacyAddressBookShares, SQLLegacySharedGroupInvites
from txdav.carddav.datastore.util import validateAddressBookComponent
from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook,\
@@ -60,8 +60,26 @@
ADDRESSBOOK_OBJECT_REVISIONS_AND_BIND_TABLE, schema
from txdav.base.propertystore.base import PropertyName
+from twext.internet.decorate import memoizedKey
+from twisted.internet.defer import succeed
+from txdav.common.icommondatastore import HomeChildNameNotAllowedError
+from txdav.base.propertystore.sql import PropertyStore
+from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
+ _BIND_STATUS_ACCEPTED
+from twext.enterprise.dal.syntax import Delete, utcNowSQL
+from twext.enterprise.dal.syntax import Insert
+from twext.enterprise.dal.syntax import Max
+from twext.enterprise.dal.syntax import Parameter
+from twext.enterprise.dal.syntax import Select
+from twext.enterprise.dal.syntax import Update
+
+from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
+ HomeChildNameAlreadyExistsError, NoSuchHomeChildError, ObjectResourceNameAlreadyExistsError, \
+ NoSuchObjectResourceError
+from twext.python.clsprop import classproperty
+
class AddressBookHome(CommonHome):
implements(IAddressBookHome)
@@ -218,8 +236,429 @@
"""
return MimeType.fromString("text/vcard; charset=utf-8")
+ @classmethod
+ @inlineCallbacks
+ def loadAllObjects(cls, home, owned):
+ print("xxx AddressBook.loadAllObjects() cls=%s" % (cls,))
+ """
+ Load all child objects and return a list of them. This must create the
+ child classes and initialize them using "batched" SQL operations to keep
+ this constant wrt the number of children. This is an optimization for
+ Depth:1 operations on the home.
+ """
+ results = []
+ # Load from the main table first
+ if owned:
+ query = cls._ownedHomeChildrenQuery
+ else:
+ query = cls._sharedHomeChildrenQuery
+ dataRows = (yield query.on(home._txn, resourceID=home._resourceID))
+ print("xxx AddressBook.loadAllObjects() dataRows=%s" % (dataRows,))
+ if dataRows:
+ # Get property stores for all these child resources (if any found)
+ propertyStores = (yield PropertyStore.forMultipleResources(
+ home.uid(), home._txn,
+ cls._bindSchema.RESOURCE_ID, cls._bindSchema.HOME_RESOURCE_ID,
+ home._resourceID
+ ))
+
+ bind = cls._bindSchema
+ rev = cls._revisionsSchema
+ if owned:
+ ownedCond = bind.BIND_MODE == _BIND_MODE_OWN
+ else:
+ ownedCond = bind.BIND_MODE != _BIND_MODE_OWN
+ revisions = (yield Select(
+ [rev.RESOURCE_ID, Max(rev.REVISION)],
+ From=rev.join(bind, rev.RESOURCE_ID == bind.RESOURCE_ID, 'left'),
+ Where=(bind.HOME_RESOURCE_ID == home._resourceID).
+ And(ownedCond).
+ And((rev.RESOURCE_NAME != None).Or(rev.DELETED == False)),
+ GroupBy=rev.RESOURCE_ID
+ ).on(home._txn))
+ revisions = dict(revisions)
+
+
+
+ # Create the actual objects merging in properties
+ for items in dataRows:
+ resourceID, resource_name, groupBindID = items[:3]
+
+ print("xxx AddressBook.loadAllObjects() groupBindID=%s" % (groupBindID,))
+
+ child = None
+ if not owned:
+
+ bind = schema.GROUP_BIND
+ groupIDRows = (yield Select([bind.GROUP_ID,],
+ From=bind,
+ Where=(bind.ADDRESSBOOK_BIND_ID == groupBindID)).on(home._txn))
+
+ print("xxx AddressBook.loadAllObjects() groupIDRows=%s" % (groupIDRows,))
+
+ if groupIDRows:
+ [groupIDs] = groupIDRows
+
+ for groupID in groupIDs:
+
+ #debug, print group members
+ bind = schema.GROUP_MEMBERSHIP
+ memberRows = (yield Select([bind.MEMBER_ID,],
+ From=bind,
+ Where=bind.GROUP_ID == groupID).on(home._txn))
+
+ print("xxx AddressBook.loadAllObjects() for groupID=%s" % (memberRows,))
+
+ child = GroupAddressBook(home, resource_name, resourceID, groupIDs)
+
+ if not child:
+ child = cls(home, resource_name, resourceID, owned)
+
+ metadata = items[3:]
+ for attr, value in zip(cls.metadataAttributes(), metadata):
+ setattr(child, attr, value)
+ child._syncTokenRevision = revisions[resourceID]
+ propstore = propertyStores.get(resourceID, None)
+ yield child._loadPropertyStore(propstore)
+ results.append(child)
+ returnValue(results)
+
+ @classmethod
+ def _homeChildLookup(cls, ownedPart):
+ """
+ Common portions of C{_ownedResourceIDByName}
+ C{_resourceIDSharedToHomeByName}, except for the 'owned' fragment of the
+ Where clause, supplied as an argument.
+
+ ADDED return of bind.GROUP_BIND_ID for shared groups
+ """
+ bind = cls._bindSchema
+ return Select(
+ [bind.RESOURCE_ID, bind.GROUP_BIND_ID,],
+ From=bind,
+ Where=(bind.RESOURCE_NAME == Parameter('objectName')).And(
+ bind.HOME_RESOURCE_ID == Parameter('homeID')).And(
+ ownedPart))
+
+ @classproperty
+ def _resourceIDOwnedByHomeByName(cls): #@NoSelf
+ print("xxx AddressBook._resourceIDOwnedByHomeByName() cls=%s" % (cls,))
+ """
+ DAL query to look up an object resource ID owned by a home, given a
+ resource name (C{objectName}), and a home resource ID
+ (C{homeID}).
+ """
+ return cls._homeChildLookup(
+ cls._bindSchema.BIND_MODE == _BIND_MODE_OWN)
+
+
+ @classproperty
+ def _resourceIDSharedToHomeByName(cls): #@NoSelf
+ print("xxx AddressBook._resourceIDSharedToHomeByName() cls=%s" % (cls,))
+ """
+ DAL query to look up an object resource ID shared to a home, given a
+ resource name (C{objectName}), and a home resource ID
+ (C{homeID}).
+ """
+ return cls._homeChildLookup(
+ (cls._bindSchema.BIND_MODE != _BIND_MODE_OWN).And(
+ cls._bindSchema.BIND_STATUS == _BIND_STATUS_ACCEPTED))
+
+ @classmethod
+ @inlineCallbacks
+ def objectWithName(cls, home, name, owned):
+ print("xxx AddressBook.objectWithName() cls=%s, home=%s, name=%s, owned=%s" % (cls, home, name, owned,))
+ """
+ Retrieve the child with the given C{name} contained in the given
+ C{home}.
+
+ @param home: a L{CommonHome}.
+
+ @param name: a string; the name of the L{CommonHomeChild} to retrieve.
+
+ @param owned: a boolean - whether or not to get a shared child
+ @return: an L{CommonHomeChild} or C{None} if no such child
+ exists.
+ """
+ data = None
+ queryCacher = home._txn.store().queryCacher
+ # Only caching non-shared objects so that we don't need to invalidate
+ # in sql_legacy
+ if owned and queryCacher:
+ # Retrieve data from cache
+ cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
+ data = yield queryCacher.get(cacheKey)
+
+ if data is None:
+ # No cached copy
+ if owned:
+ query = cls._resourceIDOwnedByHomeByName
+ else:
+ query = cls._resourceIDSharedToHomeByName
+ data = yield query.on(home._txn,
+ objectName=name, homeID=home._resourceID)
+ if owned and data and queryCacher:
+ # Cache the result
+ queryCacher.setAfterCommit(home._txn, cacheKey, data)
+
+ print("xxx AddressBook.objectWithName() data=%s name=%s" % (data, name,))
+ #traceback.print_exc()
+
+ didGroupAddressbookQuery = False
+ if not data and owned:
+ data = yield cls._resourceIDSharedToHomeByName.on(home._txn,
+ objectName=name, homeID=home._resourceID)
+
+ print("xxx AddressBook.objectWithName() SHARED data=%s name=%s" % (data, name,))
+ didGroupAddressbookQuery = True
+
+ if not data:
+ returnValue(None)
+
+ resourceID, groupBindID = data[0]
+
+ bind = schema.GROUP_BIND
+ groupIDRows = (yield Select([bind.GROUP_ID,],
+ From=bind,
+ Where=(bind.ADDRESSBOOK_BIND_ID == groupBindID)).on(home._txn))
+
+ print("xxx AddressBook.objectWithName() memberIDRows=%s name=%s" % (groupIDRows, name,))
+
+ if groupIDRows:
+ child = GroupAddressBook(home, name, resourceID, groupIDRows[0][0])
+ elif didGroupAddressbookQuery:
+ child = None
+ else:
+ child = cls(home, name, resourceID, owned)
+
+ if child:
+ yield child.initFromStore()
+ returnValue(child)
+
+
+
+
+class GroupAddressBook(AddressBook):
+ """
+ Implementation of L{IAddressBook} for shared group address books
+ """
+ implements(IAddressBook)
+
+
+ def __init__(self, home, name, resourceID, groupID):
+ """
+ Initialize an addressbook pointing at a path on disk.
+
+ @param name: the subdirectory of addressbookHome where this addressbook
+ resides.
+ @type name: C{str}
+
+ @param addressbookHome: the home containing this addressbook.
+ @type addressbookHome: L{AddressBookHome}
+
+ @param realName: If this addressbook was just created, the name which it
+ will eventually have on disk.
+ @type realName: C{str}
+ """
+
+ super(GroupAddressBook, self).__init__(home, name, resourceID, False)
+ print("xxx GroupAddressBook.__init__() self=%s, memberIDs=%s" % (self, groupID,))
+ self._objectResourceClass = GroupAddressBookObject
+
+ print("xxx GroupAddressBook.objectResources() self=%s, self._objectResourceClass=%s" % (self, self._objectResourceClass))
+
+ self._groupID = groupID
+
+
+ @classproperty
+ def _memberIDsForGroupIDQuery(cls): #@NoSelf
+ bind = schema.GROUP_MEMBERSHIP
+ return Select([bind.MEMBER_ID,],
+ From=bind,
+ Where=bind.GROUP_ID == Parameter("groupID"))
+
+
+
+
+ @classmethod
+ @inlineCallbacks
+ def listObjects(cls, home, owned):
+ print("xxx GroupAddressBook.listObjects() cls=%s" % (cls,))
+ """
+ Retrieve the names of the children that exist in the given home.
+
+ @return: an iterable of C{str}s.
+ """
+ # FIXME: tests don't cover this as directly as they should.
+ if owned:
+ rows = yield cls._ownedChildListQuery.on(
+ home._txn, resourceID=home._resourceID)
+ else:
+ rows = yield cls._sharedChildListQuery.on(
+ home._txn, resourceID=home._resourceID)
+ names = [row[0] for row in rows]
+ returnValue(names)
+
+
+ @classmethod
+ @inlineCallbacks
+ def objectWithName(cls, home, name, owned):
+ print("xxx GroupAddressBook.objectWithName() cls=%s" % (cls,))
+ """
+ Retrieve the child with the given C{name} contained in the given
+ C{home}.
+
+ @param home: a L{CommonHome}.
+
+ @param name: a string; the name of the L{CommonHomeChild} to retrieve.
+
+ @param owned: a boolean - whether or not to get a shared child
+ @return: an L{CommonHomeChild} or C{None} if no such child
+ exists.
+ """
+ data = None
+ queryCacher = home._txn.store().queryCacher
+ # Only caching non-shared objects so that we don't need to invalidate
+ # in sql_legacy
+ if owned and queryCacher:
+ # Retrieve data from cache
+ cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
+ data = yield queryCacher.get(cacheKey)
+
+ if data is None:
+ # No cached copy
+ if owned:
+ query = cls._resourceIDOwnedByHomeByName
+ else:
+ query = cls._resourceIDSharedToHomeByName
+ data = yield query.on(home._txn,
+ objectName=name, homeID=home._resourceID)
+ if owned and data and queryCacher:
+ # Cache the result
+ queryCacher.setAfterCommit(home._txn, cacheKey, data)
+
+ if not data:
+ returnValue(None)
+
+ resourceID = data[0][0]
+ child = cls(home, name, resourceID, owned)
+ yield child.initFromStore()
+ returnValue(child)
+
+
+ @classmethod
+ @inlineCallbacks
+ def objectWithID(cls, home, resourceID):
+ print("xxx GroupAddressBook.objectWithID() cls=%s" % (cls,))
+ """
+ Retrieve the child with the given C{resourceID} contained in the given
+ C{home}.
+
+ @param home: a L{CommonHome}.
+ @param resourceID: a string.
+ @return: an L{CommonHomeChild} or C{None} if no such child
+ exists.
+ """
+ data = yield cls._homeChildByIDQuery.on(
+ home._txn, resourceID=resourceID, homeID=home._resourceID)
+ if not data:
+ returnValue(None)
+
+ # TODO: filter here
+
+ name, mode = data[0]
+ child = cls(home, name, resourceID, mode == _BIND_MODE_OWN)
+ yield child.initFromStore()
+ returnValue(child)
+
+
+ @classproperty
+ def _objectResourceNamesAndIDsQuery(cls): #@NoSelf
+ """
+ DAL query to load all object resource names for a home child.
+ """
+ obj = cls._objectSchema
+ return Select([obj.RESOURCE_NAME, obj.RESOURCE_ID], From=obj,
+ Where=obj.PARENT_RESOURCE_ID == Parameter('resourceID'))
+
+ @inlineCallbacks
+ def allowedChildResourceIDs(self):
+ print("xxx GroupAddressBook.allowedChildResourceIDs() self=%s" % (self,))
+ groupMemberIDRows = yield self._memberIDsForGroupIDQuery.on(
+ self._txn, groupID=self._groupID)
+
+ print("xxx GroupAddressBook.allowedChildResourceIDs(): groupMemberIDRows=%s" % (groupMemberIDRows,))
+ allowedChildResourceIDs = []
+ for groupMemberIDRow in groupMemberIDRows:
+ allowedChildResourceIDs += groupMemberIDRow
+ print("xxx GroupAddressBook.allowedChildResourceIDs(): allowedChildResourceIDs=%s" % (allowedChildResourceIDs,))
+ returnValue(allowedChildResourceIDs)
+
+
+ @inlineCallbacks
+ def listObjectResources(self):
+ print("xxx GroupAddressBook.listObjectResources() self=%s" % (self,))
+ if self._objectNames is None:
+
+ # FIXME: this should be one query
+ rows = yield self._objectResourceNamesAndIDsQuery.on(
+ self._txn, resourceID=self._resourceID)
+ print("xxx GroupAddressBook.listObjectResources(): rows=%s" % (rows,))
+
+ allowedChildResourceIDs = (yield self.allowedChildResourceIDs()) if rows else []
+ print("xxx GroupAddressBook.listObjectResources(): allowedChildResourceIDs=%s" % (allowedChildResourceIDs,))
+
+ names = []
+ for row in rows:
+ print("xxx GroupAddressBook.listObjectResources(): row=%s" % (row,))
+ if row[1] in allowedChildResourceIDs:
+ names += [row[0],]
+
+ self._objectNames = sorted(names)
+ #self._objectNames = sorted([row[0] for row in rows])
+ print("xxx GroupAddressBook.listObjectResources(): self=%s returning=%s" % (self, self._objectNames,))
+ returnValue(self._objectNames)
+
+
+ @inlineCallbacks
+ def resourceNameForUID(self, uid):
+ print("xxx GroupAddressBook.resourceNameForUID() self=%s" % (self,))
+ try:
+ resource = self._objects[uid]
+ returnValue(resource.name() if resource else None)
+ except KeyError:
+ pass
+ rows = yield self._resourceNameForUIDQuery.on(
+ self._txn, uid=uid, resourceID=self._resourceID)
+ #FIXME: Filter
+
+ if rows:
+ returnValue(rows[0][0])
+ else:
+ self._objects[uid] = None
+ returnValue(None)
+
+ @inlineCallbacks
+ def resourceUIDForName(self, name):
+ print("xxx GroupAddressBook.resourceUIDForName() self=%s" % (self,))
+ try:
+ resource = self._objects[name]
+ returnValue(resource.uid() if resource else None)
+ except KeyError:
+ pass
+ rows = yield self._resourceUIDForNameQuery.on(
+ self._txn, name=name, resourceID=self._resourceID)
+
+ #FIXME: Filter
+ if rows:
+ returnValue(rows[0][0])
+ else:
+ self._objects[name] = None
+ returnValue(None)
+
+
class AddressBookObject(CommonObjectResource):
implements(IAddressBookObject)
@@ -230,6 +669,7 @@
def __init__(self, addressbook, name, uid, resourceID=None, metadata=None):
super(AddressBookObject, self).__init__(addressbook, name, uid, resourceID)
+ self._invites = SQLLegacySharedGroupInvites(self)
@property
@@ -339,4 +779,92 @@
return MimeType.fromString("text/vcard; charset=utf-8")
+ @inlineCallbacks
+ def isSharedGroup(self):
+ """
+ FIXME:
+ How do we tell?
+ A shared address book is marked with a property. Should we do the same with a group when it is shared?
+
+ Otherwise, we can find the sharees address book bind table and see if there are any binds to this group.
+ But that requires the sharee context.
+ """
+ self.log_info("isSharedGroup(), self = %s, _resourceID=%s returing True by default" % (self, self._resourceID,))
+
+
+
+ #need this for now, yuk!
+ (yield None)
+ returnValue(True)
+ def retrieveOldInvites(self):
+ return self._invites
+
+
+class GroupAddressBookObject(AddressBookObject):
+ """
+ Override of AddressBookObject that filters children of GroupAddressBook
+ """
+
+ @classmethod
+ @inlineCallbacks
+ def loadAllObjects(cls, parent):
+ print("xxx GroupAddressBookObject.loadAllObjects() cls=%s, parent=%s" % (cls, parent))
+ """
+ Load all child objects and return a list of them. This must create the
+ child classes and initialize them using "batched" SQL operations to keep
+ this constant wrt the number of children. This is an optimization for
+ Depth:1 operations on the collection.
+ """
+
+ results = []
+
+ # Load from the main table first
+ dataRows = yield cls._allColumnsWithParent.on(
+ parent._txn, parentID=parent._resourceID)
+
+
+ #filter
+ print("xxx GroupAddressBookObject.loadAllObjects(): dataRows=%s" % dataRows)
+ if dataRows and hasattr(parent, "allowedChildResourceIDs"):
+
+ allowedChildResourceIDs = (yield parent.allowedChildResourceIDs())
+ print("xxx GroupAddressBookObject.loadAllObjects(): allowedChildResourceIDs=%s" % allowedChildResourceIDs)
+
+ filteredDataRows = []
+ for dataRow in dataRows:
+ if dataRow[0] in allowedChildResourceIDs:
+ filteredDataRows += [dataRow,]
+
+ dataRows = filteredDataRows
+ print("xxx GroupAddressBookObject.loadAllObjects(): filteredDataRows=%s" % dataRows)
+
+
+
+ if dataRows:
+
+ # Get property stores for all these child resources (if any found)
+ if parent.objectResourcesHaveProperties():
+ propertyStores =(yield PropertyStore.forMultipleResources(
+ parent._home.uid(),
+ parent._txn,
+ cls._objectSchema.RESOURCE_ID,
+ cls._objectSchema.PARENT_RESOURCE_ID,
+ parent._resourceID
+ ))
+ else:
+ propertyStores = {}
+
+ # Create the actual objects merging in properties
+ for row in dataRows:
+ child = cls(parent, "", None)
+ child._initFromRow(tuple(row))
+ yield child._loadPropertyStore(
+ props=propertyStores.get(child._resourceID, None)
+ )
+ results.append(child)
+
+ returnValue(results)
+
+
+
Modified: CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql_legacy.py 2012-06-01 19:33:48 UTC (rev 9324)
+++ CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql_legacy.py 2012-06-02 01:11:11 UTC (rev 9325)
@@ -450,7 +450,232 @@
return self._txn.addressbookHomeWithUID(uid, create=True)
+class SQLLegacySharedGroupInvites(SQLLegacyAddressBookInvites):
+ """
+ Emulator for the implicit interface specified by
+ L{twistedcaldav.sharing.InvitesDatabase}.
+ """
+ @classproperty
+ def _childrenInAddressBookWithVCardUIDs(cls): #@NoSelf
+ bind = schema.ADDRESSBOOK_OBJECT
+ return Select([bind.RESOURCE_ID,],
+ From=bind,
+ Where=bind.ADDRESSBOOK_RESOURCE_ID == Parameter("addressbookID") and bind.VCARD_UID.In(Parameter("vcardUIDs")))
+
+ @classproperty
+ def _childInAddressBookWithVCardUID(cls): #@NoSelf
+ bind = schema.ADDRESSBOOK_OBJECT
+ return Select([bind.RESOURCE_ID,],
+ From=bind,
+ Where=(bind.ADDRESSBOOK_RESOURCE_ID == Parameter("addressbookID")).And(bind.VCARD_UID == Parameter("vcardUID")))
+
+ @classproperty
+ def _memberIDsForGroupIDQuery(cls): #@NoSelf
+ bind = schema.GROUP_MEMBERSHIP
+ return Select([bind.GROUP_ID, bind.MEMBER_ID],
+ From=bind,
+ Where=bind.GROUP_ID == Parameter("groupID"))
+
+
+ @classproperty
+ def _insertGroupQuery(cls): #@NoSelf
+ bind = schema.GROUP_MEMBERSHIP
+ return Insert(
+ {
+ bind.GROUP_ID: Parameter("groupID"),
+ bind.MEMBER_ID: Parameter("memberID"),
+ }
+ )
+
+ @classproperty
+ def _deleteGroupQuery(cls): #@NoSelf
+ bind = schema.GROUP_MEMBERSHIP
+ return Delete(From=bind,
+ Where=bind.MEMBER_ID == Parameter("memberID"))
+
+
+ @classproperty
+ def _insertGroupBindQuery(cls): #@NoSelf
+ bind = schema.GROUP_BIND
+ return Insert(
+ {
+ bind.ADDRESSBOOK_BIND_ID: Parameter("addressbookBindID"),
+ bind.GROUP_ID: Parameter("groupID"),
+ }
+ )
+
+ @classproperty
+ def _insertBindQueryReturningBindID(cls): #@NoSelf
+ bind = cls._bindSchema
+ return Insert(
+ {
+ bind.HOME_RESOURCE_ID: Parameter("homeID"),
+ bind.RESOURCE_ID: Parameter("resourceID"),
+ bind.BIND_MODE: Parameter("mode"),
+ bind.BIND_STATUS: Parameter("status"),
+ bind.MESSAGE: Parameter("message"),
+ bind.RESOURCE_NAME: Parameter("resourceName"),
+ bind.SEEN_BY_OWNER: False,
+ bind.SEEN_BY_SHAREE: False,
+ },
+ Return=bind.GROUP_BIND_ID
+ )
+
+ @inlineCallbacks
+ def updateMembership(self, group):
+ # first off, get a group list
+ @inlineCallbacks
+ def groupMemberUIDs(group):
+ vCard = (yield group.component())
+ memberUIDs = []
+ for memberProp in tuple(vCard.properties("X-ADDRESSBOOKSERVER-MEMBER")):
+ oneMember = memberProp.value()[len("urn:uuid:"):]
+ if len(oneMember):
+ memberUIDs.append(oneMember)
+ returnValue(memberUIDs)
+
+ print("updateMembership(): %s" % (group,))
+ memberUIDs = (yield groupMemberUIDs( group ))
+ print("updateMembership(): memberUIDs=%s" % (memberUIDs,))
+
+ memberResourceIDs = []
+ for memberUID in memberUIDs:
+
+ memberRow = (yield self._childInAddressBookWithVCardUID.on(
+ self._txn,
+ addressbookID=group._parentCollection._resourceID,
+ vcardUID=memberUID,
+ ))
+ print("updateMembership(): memberUID=%s, memberRow=%s" % (memberUID, memberRow, ))
+ if memberRow:
+ [[memberResourceID]] = memberRow
+ memberResourceIDs += [memberResourceID,]
+
+ # expand groups here
+
+ # add group resource id, and make set
+ memberResourceIDs += [group._resourceID,]
+ memberResourceIDs = set(memberResourceIDs)
+ print("updateMembership(): memberResourceIDs=%s" % (memberResourceIDs, ))
+
+ # get current members
+ currentMemberRows = (yield self._memberIDsForGroupIDQuery.on(
+ self._txn,
+ groupID=group._resourceID,
+ ))
+ print("updateMembership(): currentMemberRows=%s" % (currentMemberRows, ))
+
+ currentMemberResourceIDs = []
+ for row in tuple(currentMemberRows):
+ [groupID, memberID] = row
+ currentMemberResourceIDs += [memberID,]
+
+ currentMemberResourceIDs = set(currentMemberResourceIDs)
+ print("updateMembership(): currentMemberResourceIDs=%s" % (currentMemberResourceIDs, ))
+
+ membersToDelete = currentMemberResourceIDs.difference(memberResourceIDs)
+ print("updateMembership(): membersToDelete=%s" % (membersToDelete, ))
+ for memberToDelete in membersToDelete:
+ (yield self._deleteGroupQuery.on(
+ self._txn,
+ groupID=group._resourceID,
+ memberID=memberToDelete,
+ ))
+
+ memberResourceIDsToAdd = memberResourceIDs.difference(currentMemberResourceIDs)
+ print("updateMembership(): memberResourceIDsToAdd=%s" % (memberResourceIDsToAdd, ))
+ for memberResourceIDToAdd in memberResourceIDsToAdd:
+ (yield self._insertGroupQuery.on(
+ self._txn,
+ groupID=group._resourceID,
+ memberID=memberResourceIDToAdd,
+ ))
+
+
+ @inlineCallbacks
+ def addOrUpdateRecord(self, record):
+ print("yyy addOrUpdateRecord() leg")
+ bindMode = {'read-only': _BIND_MODE_READ,
+ 'read-write': _BIND_MODE_WRITE}[record.access]
+ bindStatus = {
+ "NEEDS-ACTION": _BIND_STATUS_INVITED,
+ "ACCEPTED": _BIND_STATUS_ACCEPTED,
+ "DECLINED": _BIND_STATUS_DECLINED,
+ "INVALID": _BIND_STATUS_INVALID,
+ }[record.state]
+ shareeHome = yield self._getHomeWithUID(record.principalUID)
+ rows = yield self._idsForInviteUID.on(self._txn,
+ inviteuid=record.inviteuid)
+
+ # FIXME: Do the BIND table query before the INVITE table query because BIND currently has proper
+ # constraints in place, whereas INVITE does not. Really we need to do this in a sub-transaction so
+ # we can roll back if any one query fails.
+ if rows:
+ print("xxx addOrUpdateRecord() leg rows=%s" % (rows,))
+ [[resourceID, homeResourceID]] = rows
+ yield self._updateBindQuery.on(
+ self._txn,
+ mode=bindMode, status=bindStatus, message=record.summary,
+ resourceID=resourceID, homeID=homeResourceID
+ )
+ yield self._updateInviteQuery.on(
+ self._txn, name=record.name, uid=record.inviteuid
+ )
+ else:
+
+ #update membership
+ (yield self.updateMembership(self._collection))
+
+
+
+
+
+ #get members in address book
+ print("xxx addOrUpdateRecord(): self._collection = %s" % (self._collection,))
+
+ print("xxx _insertBindQuery(): homeID = %s" % (shareeHome._resourceID,))
+ print("xxx _insertBindQuery(): resourceID = %s" % (self._collection._parentCollection._resourceID,))
+ print("xxx _insertBindQuery(): resourceName = %s" % (record.inviteuid,))
+ print("xxx _insertBindQuery(): mode = %s" % (bindMode,))
+ print("xxx _insertBindQuery(): status = %s" % (bindStatus,))
+ print("xxx _insertBindQuery(): message = %s" % (record.summary,))
+ bindIDRow = (yield self._insertBindQueryReturningBindID.on(
+ self._txn,
+ homeID=shareeHome._resourceID,
+ resourceID=self._collection._parentCollection._resourceID,
+ resourceName=record.inviteuid,
+ mode=bindMode,
+ status=bindStatus,
+ message=record.summary
+ ))
+
+ [[bindID]] = bindIDRow
+ print("xxx _insertGroupBindQuery(): groupID = %s" % (self._collection._resourceID,))
+ print("xxx _insertGroupBindQuery(): bindID = %s" % (bindID,))
+ yield self._insertGroupBindQuery.on(
+ self._txn,
+ groupID=self._collection._resourceID,
+ addressbookBindID=bindID,
+ )
+ print("xxx _insertInviteQuery(): uid = %s" % (record.inviteuid,))
+ print("xxx _insertInviteQuery(): name = %s" % (record.name,))
+ print("xxx _insertInviteQuery(): homeID = %s" % (shareeHome._resourceID,))
+ print("xxx _insertInviteQuery(): resourceID = %s" % (self._collection._parentCollection._resourceID,))
+ yield self._insertInviteQuery.on(
+ self._txn, uid=record.inviteuid,
+ name=record.name,
+ homeID=shareeHome._resourceID,
+ resourceID=self._collection._parentCollection._resourceID,
+ recipient=record.userid
+ )
+
+ # Must send notification to ensure cache invalidation occurs
+ #self._collection.notifyChanged()
+ self._collection._parentCollection.notifyChanged()
+
+
+
class SQLLegacyShares(object):
_homeTable = None
Modified: CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql_schema/current.sql 2012-06-01 19:33:48 UTC (rev 9324)
+++ CalendarServer/branches/users/gaya/sharedabgroups/txdav/common/datastore/sql_schema/current.sql 2012-06-02 01:11:11 UTC (rev 9325)
@@ -349,6 +349,17 @@
);
+--------------------------
+-- AddressBook Metadata --
+--------------------------
+
+create table ADDRESSBOOK_METADATA (
+ RESOURCE_ID integer primary key references ADDRESSBOOK on delete cascade, -- implicit index
+ CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+ MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
----------------------
-- AddressBook Bind --
----------------------
@@ -358,6 +369,7 @@
create table ADDRESSBOOK_BIND (
ADDRESSBOOK_HOME_RESOURCE_ID integer not null references ADDRESSBOOK_HOME,
ADDRESSBOOK_RESOURCE_ID integer not null references ADDRESSBOOK on delete cascade,
+ GROUP_BIND_ID integer primary key default nextval('RESOURCE_ID_SEQ'),
-- An invitation which hasn't been accepted yet will not yet have a resource
-- name, so this field may be null.
@@ -369,7 +381,7 @@
SEEN_BY_SHAREE boolean not null,
MESSAGE text, -- FIXME: xml?
- primary key(ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_ID), -- implicit index
+ -- primary key(ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_ID), -- implicit index
unique(ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME) -- implicit index
);
@@ -391,27 +403,29 @@
);
--------------------------
--- Shared Group --
+-- Shared Group Members --
--------------------------
-create table ADDRESSBOOK_GROUP_MEMBER (
+create table GROUP_MEMBERSHIP (
GROUP_ID integer references ADDRESSBOOK_OBJECT,
- MEMBER integer references ADDRESSBOOK_OBJECT,
- primary key(GROUP_ID, MEMBER) -- implicit index
+ MEMBER_ID integer references ADDRESSBOOK_OBJECT,
+ primary key(GROUP_ID, MEMBER_ID) -- implicit index
);
---------------------------
--- AddressBook Metadata --
---------------------------
-create table ADDRESSBOOK_METADATA (
- RESOURCE_ID integer primary key references ADDRESSBOOK on delete cascade, -- implicit index
- GROUP_ID integer references ADDRESSBOOK_OBJECT, -- shared group
- CREATED timestamp default timezone('UTC', CURRENT_TIMESTAMP),
- MODIFIED timestamp default timezone('UTC', CURRENT_TIMESTAMP)
+--------------------------------
+-- Shared Address Book Groups --
+--------------------------------
+
+create table GROUP_BIND (
+ ADDRESSBOOK_BIND_ID integer references ADDRESSBOOK_BIND,
+ GROUP_ID integer, -- temporary, should use line below when parser is fixed
+-- GROUP_ID integer references GROUP_MEMBERSHIP(GROUP_ID),
+-- or add the following line
+-- foreign key(GROUP_ID) references GROUP_MEMBERSHIP(GROUP_ID)
+ primary key(ADDRESSBOOK_BIND_ID, GROUP_ID) -- implicit index
);
-
---------------
-- Revisions --
---------------
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120601/2c890411/attachment-0001.html>
More information about the calendarserver-changes
mailing list