[CalendarServer-changes] [10296]
source_changes at macosforge.org
source_changes at macosforge.org
Wed Jan 9 16:04:13 PST 2013
Revision: 10296
http://trac.calendarserver.org//changeset/10296
Author: gaya at apple.com
Date: 2013-01-09 16:04:13 -0800 (Wed, 09 Jan 2013)
Log Message:
-----------
add fake group object to shared address book
Modified Paths:
--------------
CalDAVTester/branches/users/gaya/sharedgroupstester/scripts/tests/CardDAV/sharing-addressbooks.xml
CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py
Modified: CalDAVTester/branches/users/gaya/sharedgroupstester/scripts/tests/CardDAV/sharing-addressbooks.xml
===================================================================
--- CalDAVTester/branches/users/gaya/sharedgroupstester/scripts/tests/CardDAV/sharing-addressbooks.xml 2013-01-09 16:39:21 UTC (rev 10295)
+++ CalDAVTester/branches/users/gaya/sharedgroupstester/scripts/tests/CardDAV/sharing-addressbooks.xml 2013-01-10 00:04:13 UTC (rev 10296)
@@ -297,11 +297,11 @@
</verify>
</request>
</test>
- <test name='13' ignore='yes'>
+ <test name='13' ignore='no'>
<description>Sharee sees group with shared address book members</description>
<request user="$userid2:" pswd="$pswd2:" print-response='no'>
<method>GET</method>
- <ruri>$sharedaddressbook:/shared</ruri>
+ <ruri>$sharedaddressbook:/shared.vcf</ruri>
<verify>
<callback>addressDataMatch</callback>
<arg>
@@ -315,11 +315,11 @@
</verify>
</request>
</test>
- <test name='14' ignore='yes'>
+ <test name='14' ignore='no'>
<description>Sharee cannot delete special group</description>
<request user="$userid2:" pswd="$pswd2:" print-response='no'>
<method>DELETE</method>
- <ruri>$sharedaddressbook:/shared</ruri>
+ <ruri>$sharedaddressbook:/shared.vcf</ruri>
<verify>
<callback>statusCode</callback>
<arg>
@@ -745,11 +745,11 @@
</verify>
</request>
</test>
- <test name='12.1' ignore='yes'>
+ <test name='12.1' ignore='no'>
<description>Sharee sees group with shared address book members</description>
<request user="$userid2:" pswd="$pswd2:" print-response='no'>
<method>GET</method>
- <ruri>$sharedaddressbook:/addressbook</ruri>
+ <ruri>$sharedaddressbook:/addressbook.vcf</ruri>
<verify>
<callback>addressDataMatch</callback>
<arg>
@@ -763,11 +763,11 @@
</verify>
</request>
</test>
- <test name='12.2' ignore='yes'>
+ <test name='12.2' ignore='no'>
<description>Sharee cannot delete special group</description>
<request user="$userid2:" pswd="$pswd2:" print-response='no'>
<method>DELETE</method>
- <ruri>$sharedaddressbook:/addressbook</ruri>
+ <ruri>$sharedaddressbook:/addressbook.vcf</ruri>
<verify>
<callback>statusCode</callback>
<arg>
Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py 2013-01-09 16:39:21 UTC (rev 10295)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py 2013-01-10 00:04:13 UTC (rev 10296)
@@ -57,8 +57,6 @@
_ABO_KIND_LOCATION, schema
from txdav.xml.rfc2518 import ResourceType
-from uuid import uuid4
-
from zope.interface.declarations import implements
@@ -87,6 +85,7 @@
_cacher = Memcacher("SQL.adbkhome", pickle=True, key_normalization=False)
+
def __init__(self, transaction, ownerUID, notifiers):
self._childClass = AddressBook
@@ -140,6 +139,7 @@
class AddressBookSharingMixIn(SharingMixIn):
+
@classproperty
def _insertABObject(cls): #@NoSelf
"""
@@ -170,6 +170,16 @@
Where=obj.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs))),)
+ @classmethod
+ def _abObjectColumnsWithAddressBookResourceID(cls, columns,): #@NoSelf
+ """
+ DAL statement to retrieve addressbook object rows with given columns.
+ """
+ obj = cls._objectSchema
+ return Select(columns, From=obj,
+ Where=obj.ADDRESSBOOK_RESOURCE_ID == Parameter("addressbookResourceID"),)
+
+
class AddressBook(CommonHomeChild, AddressBookSharingMixIn):
"""
SQL-based implementation of L{IAddressBook}.
@@ -192,6 +202,7 @@
_revisionsBindTable = ADDRESSBOOK_OBJECT_REVISIONS_AND_BIND_TABLE
_objectTable = ADDRESSBOOK_OBJECT_TABLE
+
def __init__(self, *args, **kw):
super(AddressBook, self).__init__(*args, **kw)
self._index = PostgresLegacyABIndexEmulator(self)
@@ -205,10 +216,12 @@
def resourceType(self):
return ResourceType.addressbook #@UndefinedVariable
+
#FIXME: Only used for shared group resouretype in SharedResourceMixin.upgradeToShare() and SharedResourceMixin.downgradeFromShare()
def objectResourcesHaveProperties(self):
return True
+
ownerAddressBookHome = CommonHomeChild.ownerHome
addressbookObjects = CommonHomeChild.objectResources
listAddressbookObjects = CommonHomeChild.listObjectResources
@@ -245,94 +258,62 @@
"""
return super(AddressBook, self).unshare(EADDRESSBOOKTYPE)
- '''
- @classmethod
+
@inlineCallbacks
- def _createChild(cls, home, name): #@NoSelf
- # Create this object
+ def listObjectResources(self):
- # TODO: N, FN, set to resource name for now,
- # but "may" have to change when shared
- # and perhaps will need to reflect a per-user property
- uid = str(uuid4())
- component = VCard.fromString(
- """BEGIN:VCARD
-VERSION:3.0
-PRODID:%s
-UID:%s
-FN:%s
-N:%s;;;;
-X-ADDRESSBOOKSERVER-KIND:group
-END:VCARD
-""".replace("\n", "\r\n") % (vCardProductID, uid, name, name)
- )
+ result = yield super(AddressBook, self).listObjectResources()
+ if not self.owned():
+ sharedABGroupName = yield self._sharedABGroupName()
+ if not sharedABGroupName in result:
+ result.append(sharedABGroupName)
+ returnValue(result)
- componentText = str(component)
- md5 = hashlib.md5(componentText).hexdigest()
- resourceID, created, modified = (#@UnusedVariable
- yield cls._insertABObject.on(
- home._txn,
- addressbookResourceID=None,
- name=name,
- text=componentText,
- uid=component.resourceUID(),
- md5=md5,
- kind=_ABO_KIND_GROUP,
- ))[0]
+ @inlineCallbacks
+ def countObjectResources(self):
+ if self.owned():
+ result = yield super(AddressBook, self).countObjectResources()
+ else:
+ result = len((yield self.listObjectResources()))
+ returnValue(result)
- returnValue((resourceID, created, modified))
- '''
- @classmethod
- def _memberIDsWithGroupIDsQuery(cls, groupIDs): #@NoSelf
- """
- DAL query to load all object resource names for a home child.
- """
- aboMembers = schema.ABO_MEMBERS
- return Select([aboMembers.MEMBER_ID], From=aboMembers,
- Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs))),
- )
+ @inlineCallbacks
+ def ownerAddressBook(self):
+ if not hasattr(self, "_ownerAddressBook"):
+ if self.owned():
+ self._ownerAddressBook = self
+ else:
+ self._ownerAddressBook = yield self.ownerAddressBookHome().childWithID(self._resourceID)
+ returnValue(self._ownerAddressBook)
@inlineCallbacks
- def _allAddressBookObjectIDs(self):
- """
- Get all addressbookobject resource IDs in this address book
- """
- # TODO: see if result can be cached in attr
+ def _sharedABGroupRow(self): #@NoSelf
- objectIDs = set() if self.owned() else set([self._resourceID, ])
- examinedIDs = set()
- remainingIDs = set([self._resourceID, ])
- while remainingIDs:
- memberRows = yield self._memberIDsWithGroupIDsQuery(remainingIDs).on(self._txn, groupIDs=remainingIDs)
- objectIDs |= set([memberRow[0] for memberRow in memberRows])
- examinedIDs |= remainingIDs
- remainingIDs = objectIDs - examinedIDs
+ returnValue([
+ self._resourceID, # obj.ADDRESSBOOK_RESOURCE_ID,
+ self._resourceID, # obj.RESOURCE_ID,
+ (yield self._sharedABGroupName()), # obj.RESOURCE_NAME, shared name is UID and thus avoids collisions
+ (yield self._sharedABGroupUID()), # obj.UID, shared name is uuid
+ _ABO_KIND_GROUP, # obj.KIND,
+ "1", # obj.MD5, unused
+ "1", # Len(obj.TEXT), unused
+ self._created, # obj.CREATED,
+ self._modified, # obj.CREATED,
+ ])
- returnValue(tuple(objectIDs))
- '''
@inlineCallbacks
- def listObjectResources(self):
- if self._objectNames is None:
- abo = schema.ADDRESSBOOK_OBJECT
- objectIDs = yield self._allAddressBookObjectIDs()
- rows = (yield self._abObjectColumnsWithResourceIDsQuery(
- [abo.RESOURCE_NAME],
- objectIDs).on(
- self._txn, resourceIDs=objectIDs)
- ) if objectIDs else []
- self._objectNames = sorted([row[0] for row in rows])
+ def _sharedABGroupName(self):
+ returnValue((yield self.ownerAddressBook()).name() + ".vcf")
- returnValue(self._objectNames)
-
@inlineCallbacks
- def countObjectResources(self):
- returnValue(len((yield self._allAddressBookObjectIDs())))
- '''
+ def _sharedABGroupUID(self):
+ yield None
+ returnValue(self.name())
class AddressBookObject(CommonObjectResource, AddressBookSharingMixIn):
@@ -401,14 +382,14 @@
# convert delete in sharee shared group address book to remove of memberships
# that make this object visible to the sharee
- ownerAddressBook = yield self.ownerAddressBook()
- objectIDs = yield ownerAddressBook._allAddressBookObjectIDs()
+ objectIDs = yield ownerGroup._allGroupObjectIDs()
assert self._ownerAddressBookResourceID not in objectIDs
if objectIDs:
yield self._deleteMembersWithMemberIDAndGroupIDsQuery(self._resourceID, objectIDs).on(
self._txn, groupIDs=objectIDs)
+ ownerAddressBook = yield self.ownerAddressBook()
yield self._changeAddressBookRevision(ownerAddressBook)
else:
@@ -477,22 +458,37 @@
@return: L{self} if object exists in the DB, else C{None}
"""
- if isinstance(self._addressbook, AddressBook):
- returnValue((yield super(AddressBookObject, self).initFromStore()))
+ #print("initFromStore: self=%s, self._name=%s, self._uid=%s, self._resourceID=%s" % (self, self._name, self._uid, self._resourceID,))
- objectIDs = yield self._addressbook._allAddressBookObjectIDs()
if self._name:
- rows = (yield self._allWithResourceIDAndName(objectIDs).on(
+ rows = yield self._allWithParentAndName.on(
self._txn, name=self._name,
- resourceIDs=objectIDs,)) if objectIDs else []
+ parentID=self._parentCollection._resourceID)
elif self._uid:
- rows = (yield self._allWithResourceIDAndUID(objectIDs).on(
+ rows = yield self._allWithParentAndUID.on(
self._txn, uid=self._uid,
- resourceIDs=objectIDs,)) if objectIDs else []
+ parentID=self._parentCollection._resourceID)
elif self._resourceID:
- rows = (yield self._allWithResourceID.on(
- self._txn, resourceID=self._resourceID,)) if (self._resourceID in objectIDs) else []
+ rows = yield self._allWithParentAndID.on(
+ self._txn, resourceID=self._resourceID,
+ parentID=self._parentCollection._resourceID)
+ #print("initFromStore: self=%s, rows=%s" % (self, rows,))
+
+ # get special group vCard
+ if not rows and not self._addressbook.owned():
+ if self._name:
+ if self._name == (yield self._addressbook._sharedABGroupName()):
+ rows = [(yield self._addressbook._sharedABGroupRow())]
+ elif self._uid:
+ if self._uid == (yield self._addressbook._sharedABGroupUID()):
+ rows = [(yield self._addressbook._sharedABGroupRow())]
+ elif self._resourceID:
+ if self._resourceID == self._addressbook._resourceID:
+ rows = [(yield self._addressbook._sharedABGroupRow())]
+
+ #print("initFromStore: self=%s, rows2=%s" % (self, rows,))
+
if rows:
self._initFromRow(tuple(rows[0]))
@@ -510,6 +506,35 @@
returnValue(None)
+ @classmethod
+ def _memberIDsWithGroupIDsQuery(cls, groupIDs): #@NoSelf
+ """
+ DAL query to load all object resource names for a home child.
+ """
+ aboMembers = schema.ABO_MEMBERS
+ return Select([aboMembers.MEMBER_ID], From=aboMembers,
+ Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs))),
+ )
+
+ @inlineCallbacks
+ def _allGroupObjectIDs(self):
+ """
+ Get all addressbookobject resource IDs in this address book
+ """
+ # TODO: see if result can be cached in attr
+
+ objectIDs = set() if self.owned() else set([self._resourceID, ])
+ examinedIDs = set()
+ remainingIDs = set([self._resourceID, ])
+ while remainingIDs:
+ memberRows = yield self._memberIDsWithGroupIDsQuery(remainingIDs).on(self._txn, groupIDs=remainingIDs)
+ objectIDs |= set([memberRow[0] for memberRow in memberRows])
+ examinedIDs |= remainingIDs
+ remainingIDs = objectIDs - examinedIDs
+
+ returnValue(tuple(objectIDs))
+
+
@classproperty
def _allColumns(cls): #@NoSelf
"""
@@ -545,18 +570,26 @@
self._created,
self._modified,) = tuple(row)
+
@classmethod
@inlineCallbacks
def _allColumnsWithParent(cls, parent): #@NoSelf
- if isinstance(parent, AddressBook):
- returnValue((yield super(AddressBookObject, cls)._allColumnsWithParent(parent)))
- objectIDs = yield parent._allAddressBookObjectIDs()
- rows = (yield cls._abObjectColumnsWithResourceIDsQuery(cls._allColumns, objectIDs).on(
- parent._txn, resourceIDs=objectIDs)) if objectIDs else []
- returnValue(rows)
+ #print("_allColumnsWithParent: cls=%s, parent=%s" % (cls, parent,))
+ if parent.owned() or isinstance(parent, AddressBook):
+ result = yield super(AddressBookObject, cls)._allColumnsWithParent(parent)
+ else:
+ objectIDs = yield parent._allGroupObjectIDs()
+ result = (yield cls._abObjectColumnsWithResourceIDsQuery(cls._allColumns, objectIDs).on(
+ parent._txn, resourceIDs=objectIDs)) if objectIDs else []
+ # add group vCard for shared address books
+ if not parent.owned():
+ result.append((yield parent._sharedABGroupRow()))
+ returnValue(result)
+
+
@classmethod
def _allColumnsWithResourceIDsAndNamesQuery(cls, resourceIDs, names): #@NoSelf
obj = cls._objectSchema
@@ -568,12 +601,22 @@
@classmethod
@inlineCallbacks
def _allColumnsWithParentAndNames(cls, parent, names): #@NoSelf
- objectIDs = yield parent._allAddressBookObjectIDs()
- rows = (yield cls._allColumnsWithResourceIDsAndNamesQuery(objectIDs, names).on(
- parent._txn, resourceIDs=objectIDs, names=names)) if objectIDs else []
- returnValue(rows)
+ #print("_allColumnsWithParentAndNames: cls=%s, parent=%s, names=%s" % (cls, parent, names))
+ if parent.owned() or isinstance(parent, AddressBook):
+ result = yield super(AddressBookObject, cls)._allColumnsWithParentAndNames(parent, names)
+ else:
+ objectIDs = yield parent._allGroupObjectIDs()
+ result = (yield cls._allColumnsWithResourceIDsAndNamesQuery(objectIDs, names).on(
+ parent._txn, resourceIDs=objectIDs, names=names)) if objectIDs else []
+ # add group vCard for shared address books
+ if not parent.owned() and (yield parent._sharedABGroupName()) in names:
+ result.append((yield parent._sharedABGroupRow(parent)))
+
+ returnValue(result)
+
+
@inlineCallbacks
def _changeAddressBookRevision(self, addressbook, inserting=False):
if inserting:
@@ -614,17 +657,13 @@
Find the owner shared group and owner address book. owner shared group may be None
"""
ownerGroup = None
- ownerAddressBook = None
- if self._addressbook.owned():
- ownerAddressBook = self._addressbook
- else:
- ownerAddressBook = yield self._addressbook.ownerAddressBookHome().childWithID(self._addressbook._resourceID)
- if not ownerAddressBook:
- for addressbook in (yield self._addressbook.ownerAddressBookHome().addressbooks()):
- ownerGroup = yield addressbook.objectResourceWithID(self._addressbook._resourceID)
- if ownerGroup:
- ownerAddressBook = addressbook
- break
+ ownerAddressBook = yield self._addressbook.ownerAddressBook()
+ if not ownerAddressBook:
+ for addressbook in (yield self._addressbook.ownerAddressBookHome().addressbooks()):
+ ownerGroup = yield addressbook.objectResourceWithID(self._addressbook._resourceID)
+ if ownerGroup:
+ ownerAddressBook = addressbook
+ break
returnValue((ownerGroup, ownerAddressBook))
@@ -717,9 +756,10 @@
foreignMemberAddrs.extend(["urn:uuid:" + missingUID for missingUID in set(memberUIDs) - set(foundUIDs)])
#in shared group, all members must be inside the shared group
- if (yield self.ownerGroup()):
+ ownerGroup = yield self.ownerGroup()
+ if ownerGroup:
if foreignMemberAddrs or \
- set(memberIDs) - set((yield self._addressbook._allAddressBookObjectIDs())):
+ set(memberIDs) - set((yield ownerGroup._allGroupObjectIDs())):
raise GroupWithUnsharedAddressNotAllowedError
# don't store group members in object text
@@ -861,62 +901,97 @@
if self._component is None:
- text = yield self._text()
+ if not self._addressbook.owned() and self._resourceID == self._addressbook._resourceID:
- try:
- component = VCard.fromString(text)
- except InvalidVCardDataError, e:
- # This is a really bad situation, so do raise
- raise InternalDataStoreError(
- "Data corruption detected (%s) in id: %s"
- % (e, self._resourceID)
+ N = self.name()
+ if N.endswith(".vcf"):
+ N = N[:-4]
+ FN = N
+
+ component = VCard.fromString(
+ """BEGIN:VCARD
+VERSION:3.0
+PRODID:%s
+UID:%s
+FN:%s
+N:%s;;;;
+X-ADDRESSBOOKSERVER-KIND:group
+END:VCARD
+""".replace("\n", "\r\n") % (vCardProductID, self.uid(), N, FN,)
)
- # Fix any bogus data we can
- fixed, unfixed = component.validVCardData(doFix=True, doRaise=False)
- if unfixed:
- self.log_error("Address data id=%s had unfixable problems:\n %s" % (self._resourceID, "\n ".join(unfixed),))
-
- if fixed:
- self.log_error("Address data id=%s had fixable problems:\n %s" % (self._resourceID, "\n ".join(fixed),))
-
- if self._kind == _ABO_KIND_GROUP:
- assert not component.hasProperty("X-ADDRESSBOOKSERVER-MEMBER"), "database group vCard text contains members %s" % (component,)
-
- # generate "X-ADDRESSBOOKSERVER-MEMBER" properties
- # first get member resource ids
- aboMembers = schema.ABO_MEMBERS
- memberRows = yield Select([aboMembers.MEMBER_ID],
- From=aboMembers,
- Where=aboMembers.GROUP_ID == self._resourceID,).on(self._txn)
- memberIDs = [memberRow[0] for memberRow in memberRows]
-
# then get member UIDs
abo = schema.ADDRESSBOOK_OBJECT
- memberUIDRows = (yield self._abObjectColumnsWithResourceIDsQuery(
- [abo.VCARD_UID],
- memberIDs).on(
- self._txn, resourceIDs=memberIDs)
- ) if memberIDs else []
+ memberUIDRows = yield self._abObjectColumnsWithAddressBookResourceID(
+ [abo.VCARD_UID]).on(
+ self._txn, addressbookResourceID=self._addressbook._resourceID)
memberUIDs = [memberUIDRow[0] for memberUIDRow in memberUIDRows]
# add prefix to get property string
memberAddresses = ["urn:uuid:" + memberUID for memberUID in memberUIDs]
- # get foreign members
- aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
- foreignMemberRows = yield Select([aboForeignMembers.MEMBER_ADDRESS],
- From=aboForeignMembers,
- Where=aboForeignMembers.GROUP_ID == self._resourceID,
- ).on(self._txn)
- foreignMembers = [foreignMemberRow[0] for foreignMemberRow in foreignMemberRows]
-
-
# now add the properties to the component
- for memberAddress in sorted(memberAddresses + foreignMembers):
+ for memberAddress in sorted(memberAddresses):
component.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
+ else:
+ text = yield self._text()
+
+ try:
+ component = VCard.fromString(text)
+ except InvalidVCardDataError, e:
+ # This is a really bad situation, so do raise
+ raise InternalDataStoreError(
+ "Data corruption detected (%s) in id: %s"
+ % (e, self._resourceID)
+ )
+
+ # Fix any bogus data we can
+ fixed, unfixed = component.validVCardData(doFix=True, doRaise=False)
+
+ if unfixed:
+ self.log_error("Address data id=%s had unfixable problems:\n %s" % (self._resourceID, "\n ".join(unfixed),))
+
+ if fixed:
+ self.log_error("Address data id=%s had fixable problems:\n %s" % (self._resourceID, "\n ".join(fixed),))
+
+ if self._kind == _ABO_KIND_GROUP:
+ assert not component.hasProperty("X-ADDRESSBOOKSERVER-MEMBER"), "database group vCard text contains members %s" % (component,)
+
+ # generate "X-ADDRESSBOOKSERVER-MEMBER" properties
+ # first get member resource ids
+ aboMembers = schema.ABO_MEMBERS
+ memberRows = yield Select([aboMembers.MEMBER_ID],
+ From=aboMembers,
+ Where=aboMembers.GROUP_ID == self._resourceID,).on(self._txn)
+ memberIDs = [memberRow[0] for memberRow in memberRows]
+
+ # then get member UIDs
+ abo = schema.ADDRESSBOOK_OBJECT
+ memberUIDRows = (yield self._abObjectColumnsWithResourceIDsQuery(
+ [abo.VCARD_UID],
+ memberIDs).on(
+ self._txn, resourceIDs=memberIDs)
+ ) if memberIDs else []
+ memberUIDs = [memberUIDRow[0] for memberUIDRow in memberUIDRows]
+
+ # add prefix to get property string
+ memberAddresses = ["urn:uuid:" + memberUID for memberUID in memberUIDs]
+
+ # get foreign members
+ aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
+ foreignMemberRows = yield Select([aboForeignMembers.MEMBER_ADDRESS],
+ From=aboForeignMembers,
+ Where=aboForeignMembers.GROUP_ID == self._resourceID,
+ ).on(self._txn)
+ foreignMembers = [foreignMemberRow[0] for foreignMemberRow in foreignMemberRows]
+
+
+ # now add the properties to the component
+ for memberAddress in sorted(memberAddresses + foreignMembers):
+ component.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
+
self._component = component
returnValue(self._component)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130109/2eca8456/attachment-0001.html>
More information about the calendarserver-changes
mailing list