[CalendarServer-changes] [11645] CalendarServer/branches/users/gaya/sharedgroupfixes
source_changes at macosforge.org
source_changes at macosforge.org
Mon Aug 26 16:00:00 PDT 2013
Revision: 11645
http://trac.calendarserver.org//changeset/11645
Author: gaya at apple.com
Date: 2013-08-26 16:00:00 -0700 (Mon, 26 Aug 2013)
Log Message:
-----------
Add revision info to group schema
Modified Paths:
--------------
CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py
CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py
CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py
CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql
Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py 2013-08-26 17:42:59 UTC (rev 11644)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/twistedcaldav/storebridge.py 2013-08-26 23:00:00 UTC (rev 11645)
@@ -2607,7 +2607,7 @@
# Content-type check
content_type = request.headers.getHeader("content-type")
if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "calendar"):
- log.error("MIME type %s not allowed in calendar collection" % (content_type,))
+ log.error("MIME type {content_type} not allowed in calendar collection", content_type=content_type)
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(caldav_namespace, "supported-calendar-data"),
@@ -3245,7 +3245,7 @@
# Content-type check
content_type = request.headers.getHeader("content-type")
if content_type is not None and (content_type.mediaType, content_type.mediaSubtype) != ("text", "vcard"):
- log.error("MIME type %s not allowed in vcard collection" % (content_type,))
+ log.error("MIME type {content_type} not allowed in vcard collection", content_type=content_type)
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
(carddav_namespace, "supported-address-data"),
Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py 2013-08-26 17:42:59 UTC (rev 11644)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/sql.py 2013-08-26 23:00:00 UTC (rev 11645)
@@ -493,9 +493,26 @@
@inlineCallbacks
+ def removedObjectResource(self, child):
+ """
+ just like CommonHomeChild.removedObjectResource() but does not call self._deleteRevision(child.name())
+ """
+ self._objects.pop(child.name(), None)
+ self._objects.pop(child.uid(), None)
+ if self._objectNames and child.name() in self._objectNames:
+ self._objectNames.remove(child.name())
+ #yield self._deleteRevision(child.name())
+ yield self.notifyChanged()
+
+
+ @inlineCallbacks
def remove(self):
if self._resourceID == self._home._resourceID:
+
+ # Note that revision table is NOT queried for removes
+ yield self._updateRevision(self.name())
+
# Allow remove, as a way to reset the address book to an empty state
for abo in (yield self.objectResources()):
yield abo.remove()
@@ -503,9 +520,6 @@
yield self.unshare() # storebridge should already have done this
- # Note that revision table is NOT queried for removes
- yield self._updateRevision(self.name())
-
yield self.properties()._removeResource()
yield self._loadPropertyStore()
@@ -966,7 +980,8 @@
"""
aboMembers = schema.ABO_MEMBERS
return Select([aboMembers.MEMBER_ID], From=aboMembers,
- Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs))),
+ Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))
+ .And(aboMembers.REMOVED == False),
)
@@ -1302,15 +1317,6 @@
return self._resourceID == self.addressbook()._resourceID
- @classmethod
- def _deleteMembersWithMemberIDAndGroupIDsQuery(cls, memberID, groupIDs):
- aboMembers = schema.ABO_MEMBERS
- return Delete(
- aboMembers,
- Where=(aboMembers.MEMBER_ID == memberID).And(
- aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs)))))
-
-
@inlineCallbacks
def remove(self):
@@ -1327,29 +1333,61 @@
if readWriteGroupIDs:
readWriteObjectIDs = yield self.addressbook().expandGroupIDs(self._txn, readWriteGroupIDs)
- # can't delete item in shared group, even if user has addressbook unbind
+ # can't delete item in read-only shared group, even if user has addressbook unbind
if self._resourceID not in readWriteObjectIDs:
raise HTTPError(FORBIDDEN)
- # convert delete in sharee shared group address book to remove of memberships
- # that make this object visible to the sharee
- if readWriteObjectIDs:
- yield self._deleteMembersWithMemberIDAndGroupIDsQuery(self._resourceID, readWriteObjectIDs).on(
- self._txn, groupIDs=readWriteObjectIDs
- )
+ # get sync token for delete now
+ yield self.addressbook()._deleteRevision(self.name())
+ # remove group memberships that make this UID visible
+ abo = schema.ADDRESSBOOK_OBJECT
+ groupIDRows = (
+ yield Select([abo.RESOURCE_ID],
+ From=abo,
+ Where=(abo.KIND == _ABO_KIND_GROUP)
+ .And(abo.RESOURCE_ID.In(Parameter("readWriteObjectIDs", len(readWriteObjectIDs)))),
+ ).on(self._txn, readWriteObjectIDs=readWriteObjectIDs)
+ )
+ groupIDs = [groupIDRow[0] for groupIDRow in groupIDRows]
+ for groupID in groupIDs:
+ yield self._updateMember.on(self._txn,
+ groupID=groupID,
+ addressbookID=self._ownerAddressBookResourceID,
+ memberID=self._resourceID,
+ resourceName=self.name(),
+ revision=self.addressbook()._syncTokenRevision,
+ removed=True
+ )
+ else:
+ # get sync token for delete now
+ yield self.addressbook()._deleteRevision(self.name())
+
aboMembers = schema.ABO_MEMBERS
- aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
-
- groupIDRows = yield Delete(
- aboMembers,
- Where=aboMembers.MEMBER_ID == self._resourceID,
- Return=aboMembers.GROUP_ID
+ groupIDRows = yield Select(
+ [aboMembers.GROUP_ID],
+ From=aboMembers,
+ Where=(aboMembers.MEMBER_ID == self._resourceID)
+ .And(aboMembers.REMOVED == False),
).on(self._txn)
+ groupIDs = [groupIDRow[0] for groupIDRow in groupIDRows]
+ if self.owned() or self.addressbook().fullyShared():
+ # remove memberships
+ for groupID in groupIDs:
+ yield self._updateMember.on(self._txn,
+ groupID=groupID,
+ addressbookID=self._ownerAddressBookResourceID,
+ memberID=self._resourceID,
+ resourceName=self.name(),
+ revision=self.addressbook()._syncTokenRevision,
+ removed=True
+ )
+
# add to foreign member table row by UID (aboForeignMembers on address books)
memberAddress = "urn:uuid:" + self._uid
- for groupID in set([groupIDRow[0] for groupIDRow in groupIDRows]) - set([self._ownerAddressBookResourceID]):
+ aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
+ for groupID in set(groupIDs) - set([self._ownerAddressBookResourceID]):
yield Insert(
{aboForeignMembers.GROUP_ID: groupID,
aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
@@ -1588,16 +1626,6 @@
returnValue(rows)
- @inlineCallbacks
- def _changeAddressBookRevision(self, addressbook, inserting=False):
- if inserting:
- yield addressbook._insertRevision(self._name)
- else:
- yield addressbook._updateRevision(self._name)
-
- yield addressbook.notifyChanged()
-
-
# Stuff from put_addressbook_common
def fullValidation(self, component, inserting):
"""
@@ -1608,7 +1636,14 @@
# Valid data sizes
if config.MaxResourceSize:
- vcardsize = len(str(component))
+ if self._componentResourceKindToKind(component) == _ABO_KIND_GROUP:
+ thinGroup = deepcopy(component)
+ thinGroup.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
+ thinGroup.removeProperties("X-ADDRESSBOOKSERVER-KIND")
+ thinGroup.removeProperties("UID")
+ vcardsize = len(str(thinGroup))
+ else:
+ vcardsize = len(str(component))
if vcardsize > config.MaxResourceSize:
raise ObjectResourceTooBigError()
@@ -1687,30 +1722,22 @@
self._componentChanged = False
- # Handle all validation operations here.
- self.fullValidation(component, inserting)
+ if self._options.get("coaddedUIDs") is None:
+ # Handle all validation operations here.
+ self.fullValidation(component, inserting)
- # UID lock - this will remain active until the end of the current txn
- if not inserting or self._options.get("coaddedUIDs") is None:
+ # UID lock - this will remain active until the end of the current txn
yield self._lockUID(component, inserting)
+ if inserting:
+ yield self.addressbook()._insertRevision(self._name)
+ else:
+ yield self.addressbook()._updateRevision(self._name)
+
+ yield self.addressbook().notifyChanged()
+
yield self.updateDatabase(component, inserting=inserting)
- yield self._changeAddressBookRevision(self._addressbook, inserting)
- if self.owned():
- # update revision table of the sharee group address book
- if self._kind == _ABO_KIND_GROUP: # optimization
- invites = yield self.sharingInvites()
- for invite in invites:
- shareeHome = (yield self._txn.homeWithResourceID(self.addressbook()._home._homeType, invite.shareeHomeID()))
- yield self._changeAddressBookRevision(shareeHome.addressbook(), inserting)
- # one is enough because all have the same resourceID
- break
- else:
- if self.addressbook()._resourceID != self._ownerAddressBookResourceID:
- # update revisions table of shared group's containing address book
- yield self._changeAddressBookRevision(self.ownerHome().addressbook(), inserting)
-
returnValue(self._componentChanged)
@@ -1726,15 +1753,6 @@
@classmethod
- def _deleteMembersWithGroupIDAndMemberIDsQuery(cls, groupID, memberIDs):
- aboMembers = schema.ABO_MEMBERS
- return Delete(
- aboMembers,
- Where=(aboMembers.GROUP_ID == groupID).And(
- aboMembers.MEMBER_ID.In(Parameter("memberIDs", len(memberIDs)))))
-
-
- @classmethod
def _deleteForeignMembersWithGroupIDAndMembeAddrsQuery(cls, groupID, memberAddrs):
aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
return Delete(
@@ -1763,6 +1781,38 @@
abo.MODIFIED))
+ @classproperty
+ def _insertMember(cls): #@NoSelf
+ """
+ DAL statement insert a group member
+ """
+ aboMembers = schema.ABO_MEMBERS
+ return Insert(
+ {aboMembers.GROUP_ID: Parameter("groupID"),
+ aboMembers.ADDRESSBOOK_ID: Parameter("addressbookID"),
+ aboMembers.MEMBER_ID: Parameter("memberID"),
+ aboMembers.RESOURCE_NAME: Parameter("resourceName"),
+ aboMembers.REVISION: Parameter("revision"),
+ aboMembers.REMOVED: Parameter("removed"), }
+ )
+
+
+ @classproperty
+ def _updateMember(cls): #@NoSelf
+ """
+ DAL statement update a group member
+ """
+ aboMembers = schema.ABO_MEMBERS
+ return Update(
+ {aboMembers.RESOURCE_NAME: Parameter("resourceName"),
+ aboMembers.REVISION: Parameter("revision"),
+ aboMembers.REMOVED: Parameter("removed"), },
+ Where=(aboMembers.GROUP_ID == Parameter("groupID"))
+ .And(aboMembers.ADDRESSBOOK_ID == Parameter("addressbookID"))
+ .And(aboMembers.MEMBER_ID == Parameter("memberID")),
+ )
+
+
@inlineCallbacks
def updateDatabase(self, component, expand_until=None, reCreate=False, #@UnusedVariable
inserting=False):
@@ -1829,11 +1879,11 @@
componentText = str(component)
# remove unneeded fields to get stored _objectText
- thinComponent = deepcopy(component)
- thinComponent.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
- thinComponent.removeProperties("X-ADDRESSBOOKSERVER-KIND")
- thinComponent.removeProperties("UID")
- self._objectText = str(thinComponent)
+ thinGroup = deepcopy(component)
+ thinGroup.removeProperties("X-ADDRESSBOOKSERVER-MEMBER")
+ thinGroup.removeProperties("X-ADDRESSBOOKSERVER-KIND")
+ thinGroup.removeProperties("UID")
+ self._objectText = str(thinGroup)
else:
componentText = str(component)
self._objectText = componentText
@@ -1867,12 +1917,12 @@
# delete foreign members table row for this object
groupIDRows = yield Delete(
aboForeignMembers,
- # should this be scoped to the owner address book?
Where=aboForeignMembers.MEMBER_ADDRESS == "urn:uuid:" + self._uid,
Return=aboForeignMembers.GROUP_ID
).on(self._txn)
groupIDs = set([groupIDRow[0] for groupIDRow in groupIDRows])
+ # add vCard to writable groups
if not self.owned() and not self.addressbook().fullyShared():
readWriteGroupIDs = yield self.addressbook().readWriteGroupIDs()
assert readWriteGroupIDs, "no access"
@@ -1880,12 +1930,27 @@
# add to member table rows
for groupID in groupIDs:
- yield Insert(
- {aboMembers.GROUP_ID: groupID,
- aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
- aboMembers.MEMBER_ID: self._resourceID, }
- ).on(self._txn)
-
+ @inlineCallbacks
+ def doInsert(subt):
+ yield self._insertMember.on(subt,
+ groupID=groupID,
+ addressbookID=self._ownerAddressBookResourceID,
+ memberID=self._resourceID,
+ resourceName="",
+ revision=self.addressbook()._syncTokenRevision,
+ removed=False
+ )
+ try:
+ yield self._txn.subtransaction(doInsert)
+ except AllRetriesFailed:
+ yield self._updateMember.on(self._txn,
+ groupID=groupID,
+ addressbookID=self._ownerAddressBookResourceID,
+ memberID=self._resourceID,
+ resourceName="",
+ revision=self.addressbook()._syncTokenRevision,
+ removed=False
+ )
else:
self._modified = (yield Update(
{abo.VCARD_TEXT: self._objectText,
@@ -1901,26 +1966,55 @@
memberIDs.append(self._resourceID)
# get current members
- currentMemberRows = yield Select([aboMembers.MEMBER_ID],
+ currentMemberRows = yield Select([aboMembers.MEMBER_ID, aboMembers.REMOVED],
From=aboMembers,
Where=aboMembers.GROUP_ID == self._resourceID,).on(self._txn)
- currentMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows]
+ currentMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows if not currentMemberRow[1]]
+ removedMemberIDs = [currentMemberRow[0] for currentMemberRow in currentMemberRows if currentMemberRow[1]]
- memberIDsToDelete = set(currentMemberIDs) - set(memberIDs)
- memberIDsToAdd = set(memberIDs) - set(currentMemberIDs)
+ memberIDsToRemove = set(currentMemberIDs) - set(memberIDs)
+ memberIDsToAdd = set(memberIDs) - set(currentMemberIDs) - set(removedMemberIDs)
+ memberIDsToReadd = set(memberIDs) & set(removedMemberIDs)
- if memberIDsToDelete:
- yield self._deleteMembersWithGroupIDAndMemberIDsQuery(self._resourceID, memberIDsToDelete).on(
- self._txn, memberIDs=memberIDsToDelete
- )
-
for memberIDToAdd in memberIDsToAdd:
- yield Insert(
- {aboMembers.GROUP_ID: self._resourceID,
- aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
- aboMembers.MEMBER_ID: memberIDToAdd, }
- ).on(self._txn)
+ yield self._insertMember.on(self._txn,
+ groupID=self._resourceID,
+ addressbookID=self._ownerAddressBookResourceID,
+ memberID=memberIDToAdd,
+ resourceName="",
+ revision=self.addressbook()._syncTokenRevision,
+ removed=False
+ )
+ for memberIDToUpdate in memberIDsToReadd:
+ yield self._updateMember.on(self._txn,
+ groupID=self._resourceID,
+ addressbookID=self._ownerAddressBookResourceID,
+ memberID=memberIDToUpdate,
+ resourceName="",
+ revision=self.addressbook()._syncTokenRevision,
+ removed=False
+ )
+
+ # get member names
+ abo = schema.ADDRESSBOOK_OBJECT
+ memberIDNameRows = (
+ yield self._columnsWithResourceIDsQuery(
+ [abo.RESOURCE_ID, abo.RESOURCE_NAME],
+ memberIDsToRemove
+ ).on(self._txn, resourceIDs=memberIDsToRemove)
+ ) if memberIDsToRemove else []
+
+ for memberIDToRemove, memberNameToRemove in memberIDNameRows:
+ yield self._updateMember.on(self._txn,
+ groupID=self._resourceID,
+ addressbookID=self._ownerAddressBookResourceID,
+ memberID=memberIDToRemove,
+ resourceName=memberNameToRemove,
+ revision=self.addressbook()._syncTokenRevision,
+ removed=True
+ )
+
# don't bother with aboForeignMembers on address books
if self._resourceID != self._ownerAddressBookResourceID:
@@ -1990,7 +2084,8 @@
memberRows = yield Select(
[aboMembers.MEMBER_ID],
From=aboMembers,
- Where=aboMembers.GROUP_ID == self._resourceID,
+ Where=(aboMembers.GROUP_ID == self._resourceID)
+ .And(aboMembers.REMOVED == False),
).on(self._txn)
memberIDs = [memberRow[0] for memberRow in memberRows]
Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py 2013-08-26 17:42:59 UTC (rev 11644)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/carddav/datastore/test/test_sql.py 2013-08-26 23:00:00 UTC (rev 11645)
@@ -576,7 +576,7 @@
aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
aboMembers = schema.ABO_MEMBERS
- memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
+ memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, Where=aboMembers.REMOVED == False).on(txn)
self.assertEqual(memberRows, [])
foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers).on(txn)
@@ -597,7 +597,7 @@
)
subgroupObject = yield adbk.createAddressBookObjectWithName("sg.vcf", subgroup)
- memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
+ memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, Where=aboMembers.REMOVED == False).on(txn)
self.assertEqual(sorted(memberRows), sorted([
[groupObject._resourceID, subgroupObject._resourceID],
[subgroupObject._resourceID, personObject._resourceID],
@@ -607,7 +607,7 @@
self.assertEqual(foreignMemberRows, [])
yield subgroupObject.remove()
- memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers,).on(txn)
+ memberRows = yield Select([aboMembers.GROUP_ID, aboMembers.MEMBER_ID], From=aboMembers, Where=aboMembers.REMOVED == False).on(txn)
self.assertEqual(memberRows, [])
foreignMemberRows = yield Select([aboForeignMembers.GROUP_ID, aboForeignMembers.MEMBER_ADDRESS], From=aboForeignMembers,
Modified: CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql 2013-08-26 17:42:59 UTC (rev 11644)
+++ CalendarServer/branches/users/gaya/sharedgroupfixes/txdav/common/datastore/sql_schema/current.sql 2013-08-26 23:00:00 UTC (rev 11645)
@@ -448,6 +448,12 @@
insert into ADDRESSBOOK_OBJECT_KIND values (3, 'location');
+---------------
+-- Revisions --
+---------------
+
+create sequence REVISION_SEQ;
+
---------------------------------
-- Address Book Object Members --
---------------------------------
@@ -455,7 +461,10 @@
create table ABO_MEMBERS (
GROUP_ID integer not null references ADDRESSBOOK_OBJECT on delete cascade, -- AddressBook Object's (kind=='group') RESOURCE_ID
ADDRESSBOOK_ID integer not null references ADDRESSBOOK_HOME on delete cascade,
- MEMBER_ID integer not null references ADDRESSBOOK_OBJECT, -- member AddressBook Object's RESOURCE_ID
+ MEMBER_ID integer not null, --references ADDRESSBOOK_OBJECT, -- member AddressBook Object's RESOURCE_ID
+ RESOURCE_NAME varchar(255),
+ REVISION integer default nextval('REVISION_SEQ') not null,
+ REMOVED boolean default false not null,
primary key (GROUP_ID, MEMBER_ID) -- implicit index
);
@@ -507,9 +516,7 @@
-- Revisions --
---------------
-create sequence REVISION_SEQ;
-
-------------------------------
-- Calendar Object Revisions --
-------------------------------
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130826/788870ec/attachment-0001.html>
More information about the calendarserver-changes
mailing list