[CalendarServer-changes] [14476] CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav
source_changes at macosforge.org
source_changes at macosforge.org
Tue Feb 24 19:35:13 PST 2015
Revision: 14476
http://trac.calendarserver.org//changeset/14476
Author: cdaboo at apple.com
Date: 2015-02-24 19:35:13 -0800 (Tue, 24 Feb 2015)
Log Message:
-----------
Checkpoint: sharing state sync. Still have clean-up piece to do.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/base/datastore/util.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/sql.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/sharing_invites.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current.sql
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_51_to_52.sql
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_51_to_52.sql
CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/base/datastore/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/base/datastore/util.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/base/datastore/util.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -100,8 +100,8 @@
# Home child objects by external id
- def keyForObjectWithExternalID(self, homeResourceID, externalID):
- return "objectWithExternalID:%s:%s" % (homeResourceID, externalID)
+ def keyForObjectWithBindUID(self, homeResourceID, bindUID):
+ return "objectWithBindUID:%s:%s" % (homeResourceID, bindUID)
# Home metadata (Created/Modified)
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -28,7 +28,7 @@
"CalendarObject",
]
-from twext.enterprise.dal.record import fromTable
+from twext.enterprise.dal.record import fromTable, SerializableRecord
from twext.enterprise.dal.syntax import Count, ColumnSyntax, Delete, \
Insert, Len, Max, Parameter, Select, Update, utcNowSQL
from twext.enterprise.locking import NamedLock
@@ -401,6 +401,33 @@
+class CalendarHomeRecord(SerializableRecord, fromTable(schema.CALENDAR_HOME)):
+ """
+ @DynamicAttrs
+ L{Record} for L{schema.CALENDAR_HOME}.
+ """
+ pass
+
+
+
+class CalendarMetaDataRecord(SerializableRecord, fromTable(schema.CALENDAR_METADATA)):
+ """
+ @DynamicAttrs
+ L{Record} for L{schema.CALENDAR_METADATA}.
+ """
+ pass
+
+
+
+class CalendarBindRecord(SerializableRecord, fromTable(schema.CALENDAR_BIND)):
+ """
+ @DynamicAttrs
+ L{Record} for L{schema.CALENDAR_BIND}.
+ """
+ pass
+
+
+
class CalendarHome(CommonHome):
implements(ICalendarHome)
@@ -1002,6 +1029,12 @@
_objectSchema = schema.CALENDAR_OBJECT
_timeRangeSchema = schema.TIME_RANGE
+ _homeRecordClass = CalendarHomeRecord
+ _metadataRecordClass = CalendarMetaDataRecord
+ _bindRecordClass = CalendarBindRecord
+ _bindHomeIDAttributeName = "calendarHomeResourceID"
+ _bindResourceIDAttributeName = "calendarResourceID"
+
# Mapping of iCalendar property name to DB column name
_queryFields = {
"UID": _objectSchema.UID,
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -587,7 +587,42 @@
yield self.commit()
+ @inlineCallbacks
+ def test_sharingBindRecords(self):
+ yield self.calendarUnderTest(home="user01", name="calendar")
+ yield self.commit()
+
+ shared_name = yield self._createShare()
+
+ shared = yield self.calendarUnderTest(home="user01", name="calendar")
+ results = yield shared.sharingBindRecords()
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results.keys(), ["user02"])
+ self.assertEqual(results["user02"].calendarResourceName, shared_name)
+
+
+ @inlineCallbacks
+ def test_sharedToBindRecords(self):
+
+ yield self.calendarUnderTest(home="user01", name="calendar")
+ yield self.commit()
+
+ shared_name = yield self._createShare()
+
+ home = yield self.homeUnderTest(name="user02")
+ results = yield home.sharedToBindRecords()
+ self.assertEqual(len(results), 1)
+ self.assertEqual(results.keys(), ["user01"])
+ sharedRecord = results["user01"][0]
+ ownerRecord = results["user01"][1]
+ metadataRecord = results["user01"][2]
+ self.assertEqual(ownerRecord.calendarResourceName, "calendar")
+ self.assertEqual(sharedRecord.calendarResourceName, shared_name)
+ self.assertEqual(metadataRecord.supportedComponents, None)
+
+
+
class GroupSharingTests(BaseSharingTests):
"""
Test store-based group sharing.
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/sql.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/sql.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -462,7 +462,7 @@
@classmethod
@inlineCallbacks
- def _getDBDataIndirect(cls, home, name, resourceID, externalID):
+ def _getDBDataIndirect(cls, home, name, resourceID, bindUID):
# Get the bind row data
row = None
@@ -492,7 +492,7 @@
overallBindStatus = _BIND_STATUS_INVITED
minBindRevision = None
for row in rows:
- bindMode, homeID, resourceGroupID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
+ homeID, resourceGroupID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
if groupID is None:
groupID = resourceGroupID
minBindRevision = min(minBindRevision, bindRevision) if minBindRevision is not None else bindRevision
@@ -532,9 +532,9 @@
returnValue((bindData, additionalBindData, metadataData, ownerHome,))
- def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
+ def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, bindUID=None):
ownerName = ownerHome.addressbook().name() if ownerHome else None
- super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName, externalID=externalID)
+ super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName, bindUID=bindUID)
def __repr__(self):
@@ -851,7 +851,7 @@
@classmethod
- def create(cls, home, name, externalID=None):
+ def create(cls, home, name, bindUID=None):
if name == home.addressbook().name():
# raise HomeChildNameAlreadyExistsError
pass
@@ -1117,7 +1117,7 @@
home._txn, homeID=home._resourceID
)
for groupRow in groupRows:
- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
+ homeID, resourceID, bindName, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerAddressBookID)
names |= set([ownerHome.uid()])
@@ -1145,7 +1145,7 @@
)
# get ownerHomeIDs
for dataRow in dataRows:
- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
+ homeID, resourceID, bindName, bindMode, bindStatus, bindRevision, bindUID, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
ownerHome = yield home.ownerHomeWithChildID(resourceID)
ownerHomeToDataRowMap[ownerHome] = dataRow
@@ -1154,12 +1154,16 @@
home._txn, homeID=home._resourceID
)
for groupBindRow in groupBindRows:
- bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
+ homeID, resourceID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
if ownerHome not in ownerHomeToDataRowMap:
- groupBindRow[0] = _BIND_MODE_INDIRECT
- groupBindRow[3:7] = 4 * [None] # bindName, bindStatus, bindRevision, bindMessage
+ groupBindRow[cls.bindColumns().index(cls._bindSchema.BIND_MODE)] = _BIND_MODE_INDIRECT
+ groupBindRow[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] = None
+ groupBindRow[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)] = None
+ groupBindRow[cls.bindColumns().index(cls._bindSchema.BIND_REVISION)] = None
+ groupBindRow[cls.bindColumns().index(cls._bindSchema.BIND_UID)] = None
+ groupBindRow[cls.bindColumns().index(cls._bindSchema.MESSAGE)] = None
ownerHomeToDataRowMap[ownerHome] = groupBindRow
if ownerHomeToDataRowMap:
@@ -1248,7 +1252,7 @@
@classmethod
@inlineCallbacks
- def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
+ def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, bindUID=None, accepted=True):
# replaces objectWithName()
"""
Synthesize and indirect child for matching name or id based on whether shared groups exist.
@@ -1261,7 +1265,7 @@
exists.
"""
- dbData = yield cls._getDBDataIndirect(home, name, resourceID, externalID)
+ dbData = yield cls._getDBDataIndirect(home, name, resourceID, bindUID)
if dbData is None:
returnValue(None)
bindData, additionalBindData, metadataData, ownerHome = dbData
@@ -1399,7 +1403,7 @@
readWriteGroupIDs = set()
readOnlyGroupIDs = set()
for groupBindRow in groupBindRows:
- bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
+ homeID, resourceID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
if bindMode == _BIND_MODE_WRITE:
readWriteGroupIDs.add(resourceID)
else:
@@ -1460,7 +1464,7 @@
readWriteGroupIDs = []
readOnlyGroupIDs = []
for groupBindRow in groupBindRows:
- bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
+ homeID, resourceID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
if bindMode == _BIND_MODE_WRITE:
readWriteGroupIDs.append(resourceID)
else:
@@ -1578,10 +1582,10 @@
subt,
homeID=shareeHome._resourceID,
resourceID=self._resourceID,
- externalID=None,
name=newName,
mode=mode,
bindStatus=status,
+ bindUID=None,
message=summary
)
returnValue(newName)
@@ -1896,7 +1900,7 @@
yield child._loadPropertyStore(propstore)
if groupBindData:
- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindData[:AddressBookObject.bindColumnCount] #@UnusedVariable
+ homeID, resourceID, bindName, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindData[:AddressBookObject.bindColumnCount] #@UnusedVariable
child._bindMode = bindMode
child._bindStatus = bindStatus
child._bindMessage = bindMessage
@@ -1997,7 +2001,7 @@
self._bindName = None
self._bindRevision = None
super(AddressBookObject, self).__init__(addressbook, name, uid, resourceID, options)
- self._externalID = None
+ self._bindUID = None
self._options = {} if options is None else options
@@ -2206,7 +2210,7 @@
)
if groupBindRows:
groupBindRow = groupBindRows[0]
- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
+ homeID, resourceID, bindName, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
returnValue(None)
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -22,7 +22,7 @@
from txdav.caldav.icalendarstore import ComponentUpdateState
from txdav.common.datastore.podding.migration.sync_metadata import CalendarMigrationRecord, \
CalendarObjectMigrationRecord, AttachmentMigrationRecord
-from txdav.caldav.datastore.sql import ManagedAttachment
+from txdav.caldav.datastore.sql import ManagedAttachment, CalendarBindRecord
from txdav.common.datastore.sql_external import NotificationCollectionExternal
from txdav.common.datastore.sql_notification import NotificationCollection
from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL
@@ -176,19 +176,20 @@
# TODO: Re-write attachment URIs - not sure if we need this as reverse proxy may take care of it
pass
- # TODO: group attendee reconcile
+ # Group attendee reconcile
yield self.groupAttendeeReconcile()
- # TODO: delegates reconcile
+ # Delegates reconcile
yield self.delegateReconcile()
# TODO: shared collections reconcile
- pass
+ yield self.sharedByCollectionsReconcile()
+ yield self.sharedToCollectionsReconcile()
# TODO: group sharee reconcile
pass
- # TODO: notifications
+ # Notifications
yield self.notificationsReconcile()
# TODO: work items
@@ -943,3 +944,160 @@
# Do this via the "write" API so that sync revisions are updated properly, rather than just
# inserting the records directly.
yield notifications.writeNotificationObject(record.notificationUID, record.notificationType, record.notificationData)
+
+
+ @inlineCallbacks
+ def sharedByCollectionsReconcile(self):
+ """
+ Sync all the collections shared by the migrating user from the remote store. We will do this one calendar at a time since
+ there could be a large number of sharees per calendar.
+ """
+
+ calendars = yield self.getSyncState()
+
+ len_records = 0
+ for calendar in calendars.values():
+ records = yield self.sharedByCollectionRecords(calendar.remoteResourceID)
+ records = records.items()
+
+ # Batch setting resources for the local home
+ len_records += len(records)
+ while records:
+ yield self.makeSharedByCollections(records[:50], calendar.localResourceID)
+ records = records[50:]
+
+ returnValue(len_records)
+
+
+ @inTransactionWrapper
+ @inlineCallbacks
+ def sharedByCollectionRecords(self, txn, remote_id):
+ """
+ Get all the existing L{CalendarBindRecord}'s from the remote store.
+ """
+
+ remote_home = yield self._remoteHome(txn)
+ remote_calendar = yield remote_home.childWithID(remote_id)
+ records = yield remote_calendar.sharingBindRecords()
+ returnValue(records)
+
+
+ @inTransactionWrapper
+ @inlineCallbacks
+ def makeSharedByCollections(self, txn, records, calendar_id):
+ """
+ Create L{CalendarBindRecord} records in the local store.
+ """
+
+ for shareeUID, record in records:
+ shareeHome = yield txn.calendarHomeWithUID(shareeUID, create=True)
+
+ # First look for an existing record that could be present if the migrating user had
+ # previously shared with this sharee as a cross-pod share
+ oldrecord = yield CalendarBindRecord.querysimple(
+ txn,
+ calendarHomeResourceID=shareeHome.id(),
+ calendarResourceName=record.calendarResourceName,
+ )
+
+ # FIXME: need to figure out sync-token and bind revision changes
+
+ if oldrecord:
+ # Point old record to the new local calendar being shared
+ yield oldrecord[0].update(
+ calendarResourceID=calendar_id,
+ bindRevision=0,
+ )
+ else:
+ # Map the record resource ids and insert a new record
+ record.calendarHomeResourceID = shareeHome.id()
+ record.calendarResourceID = calendar_id
+ record.bindRevision = 0
+ yield record.insert(txn)
+
+
+ @inlineCallbacks
+ def sharedToCollectionsReconcile(self):
+ """
+ Sync all the collections shared to the migrating user from the remote store.
+ """
+ records = yield self.sharedToCollectionRecords()
+ records = records.items()
+ len_records = len(records)
+
+ while records:
+ yield self.makeSharedToCollections(records[:50])
+ records = records[50:]
+
+ returnValue(len_records)
+
+
+ @inTransactionWrapper
+ @inlineCallbacks
+ def sharedToCollectionRecords(self, txn):
+ """
+ Get the names and sharer UIDs for remote shared calendars.
+ """
+
+ # List of calendars from the remote side
+ home = yield self._remoteHome(txn)
+ if home is None:
+ returnValue(None)
+ results = yield home.sharedToBindRecords()
+ returnValue(results)
+
+
+ @inTransactionWrapper
+ @inlineCallbacks
+ def makeSharedToCollections(self, txn, records):
+ """
+ Create L{CalendarBindRecord} records in the local store.
+ """
+
+ for sharerUID, (shareeRecord, ownerRecord, metadataRecord) in records:
+ sharerHome = yield txn.calendarHomeWithUID(sharerUID, create=True)
+
+ # We need to figure out the right thing to do based on whether the sharer is local to this pod
+ # (the one where the migrated user will be hosted) vs located on another pod
+
+ if sharerHome.normal():
+ # First look for an existing record that must be present if the migrating user had
+ # previously been shared with by this sharee
+ oldrecord = yield CalendarBindRecord.querysimple(
+ txn,
+ calendarResourceName=shareeRecord.calendarResourceName,
+ )
+ if len(oldrecord) == 1:
+ # Point old record to the new local calendar home
+ yield oldrecord[0].update(
+ calendarHomeResourceID=self.homeId,
+ )
+ else:
+ raise AssertionError("An existing share must be present")
+ else:
+ # We have an external user. That sharer may have already shared the calendar with some other user
+ # on this pod, in which case there is already a CALENDAR table entry for it, and we need the
+ # resource ID from that to use in the new CALENDAR_BIND record we create. If a pre-existing share
+ # is not present, then we have to create the CALENDAR table entry and associated pieces
+
+ # Look for pre-existing share with the same external ID
+ oldrecord = yield CalendarBindRecord.querysimple(
+ txn,
+ calendarHomeResourceID=sharerHome.id(),
+ bindUID=ownerRecord.bindUID,
+ )
+ if oldrecord:
+ # Map the record resource ids and insert a new record
+ calendar_id = oldrecord.calendarResourceID
+ else:
+ sharerView = yield sharerHome.createCollectionForExternalShare(
+ ownerRecord.calendarResourceName,
+ ownerRecord.bindUID,
+ metadataRecord.supportedComponents,
+ )
+ calendar_id = sharerView.id()
+
+ shareeRecord.calendarHomeResourceID = self.homeId
+ shareeRecord.calendarResourceID = calendar_id
+ shareeRecord.bindRevision = 0
+ yield shareeRecord.insert(txn)
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -30,7 +30,8 @@
from txdav.common.datastore.sql_directory import DelegateRecord, \
ExternalDelegateGroupsRecord, DelegateGroupsRecord
from txdav.common.datastore.sql_notification import NotificationCollection
-from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_EXTERNAL
+from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_EXTERNAL, \
+ _BIND_MODE_READ
from txdav.common.datastore.test.util import populateCalendarsFrom
from txdav.who.delegates import Delegates
from txweb2.http_headers import MimeType
@@ -976,6 +977,81 @@
+class TestSharingSync(MultiStoreConduitTest):
+ """
+ Test that L{CrossPodHomeSync} sharing sync works.
+ """
+
+ @inlineCallbacks
+ def _createShare(self, shareFrom, shareTo):
+ # Invite
+ txnindex = 1 if shareFrom[0] == "p" else 0
+ home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(txnindex), name=shareFrom, create=True)
+ calendar = yield home.childWithName("calendar")
+ shareeView = yield calendar.inviteUIDToShare(shareTo, _BIND_MODE_READ, "summary")
+ inviteUID = shareeView.shareUID()
+ yield self.commitTransaction(txnindex)
+
+ # Accept
+ txnindex = 1 if shareTo[0] == "p" else 0
+ shareeHome = yield self.homeUnderTest(txn=self.theTransactionUnderTest(txnindex), name=shareTo)
+ shareeView = yield shareeHome.acceptShare(inviteUID)
+ sharedName = shareeView.name()
+ yield self.commitTransaction(txnindex)
+
+ returnValue(sharedName)
+
+
+ @inlineCallbacks
+ def test_shared_collections_reconcile(self):
+ """
+ Test that L{sharedCollectionsReconcile} copies over the full set of delegates and caches associated groups..
+ """
+
+ # Create home
+ yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name="user01", create=True)
+ yield self.commitTransaction(0)
+
+ # Shared by migrating user
+ shared_name_02 = yield self._createShare("user01", "user02")
+ shared_name_03 = yield self._createShare("user01", "puser03")
+
+ # Shared to migrating user
+ shared_name_04 = yield self._createShare("user04", "user01")
+ shared_name_05 = yield self._createShare("puser05", "user01")
+
+ # Sync from remote side
+ syncer = CrossPodHomeSync(self.theStoreUnderTest(1), "user01")
+ yield syncer.loadRecord()
+ yield syncer.sync()
+ changes = yield syncer.sharedByCollectionsReconcile()
+ self.assertEqual(changes, 2)
+ changes = yield syncer.sharedToCollectionsReconcile()
+ self.assertEqual(changes, 2)
+
+ # Local calendar exists with shares
+ home1 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1), name=syncer.migratingUid())
+ calendar1 = yield home1.childWithName("calendar")
+ invites1 = yield calendar1.sharingInvites()
+ self.assertEqual(len(invites1), 2)
+ self.assertEqual(set([invite.uid for invite in invites1]), set((shared_name_02, shared_name_03,)))
+ yield self.commitTransaction(1)
+
+ # Local sharee can access it
+ home1 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1), name="puser03")
+ calendar1 = yield home1.childWithName(shared_name_03)
+ self.assertTrue(calendar1 is not None)
+
+ # Local shared calendars exist
+ home1 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1), name=syncer.migratingUid())
+ calendar1 = yield home1.childWithName(shared_name_04)
+ self.assertTrue(calendar1 is not None)
+ calendar1 = yield home1.childWithName(shared_name_05)
+ self.assertTrue(calendar1 is not None)
+ yield self.commitTransaction(1)
+
+
+
class TestGroupAttendeeSync(MultiStoreConduitTest):
"""
GroupAttendeeReconciliation tests
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/sharing_invites.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/sharing_invites.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/sharing_invites.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -26,7 +26,7 @@
"""
@inlineCallbacks
- def send_shareinvite(self, txn, homeType, ownerUID, ownerID, ownerName, shareeUID, shareUID, bindMode, summary, copy_properties, supported_components):
+ def send_shareinvite(self, txn, homeType, ownerUID, ownerName, shareeUID, shareUID, bindMode, bindUID, summary, copy_properties, supported_components):
"""
Send a sharing invite cross-pod message.
@@ -34,8 +34,6 @@
@type homeType: C{int}
@param ownerUID: UID of the sharer.
@type ownerUID: C{str}
- @param ownerID: resource ID of the sharer calendar
- @type ownerID: C{int}
@param ownerName: owner's name of the sharer calendar
@type ownerName: C{str}
@param shareeUID: UID of the sharee
@@ -44,6 +42,8 @@
@type shareUID: C{str}
@param bindMode: bind mode for the share
@type bindMode: C{str}
+ @param bindUID: bind UID of the sharer calendar
+ @type bindUID: C{str}
@param summary: sharing message
@type summary: C{str}
@param copy_properties: C{str} name/value for properties to be copied
@@ -58,11 +58,11 @@
"action": "shareinvite",
"type": homeType,
"owner": ownerUID,
- "owner_id": ownerID,
"owner_name": ownerName,
"sharee": shareeUID,
"share_id": shareUID,
"mode": bindMode,
+ "bind_uid": bindUID,
"summary": summary,
"properties": copy_properties,
}
@@ -89,10 +89,10 @@
# Create a share
yield shareeHome.processExternalInvite(
request["owner"],
- request["owner_id"],
request["owner_name"],
request["share_id"],
request["mode"],
+ request["bind_uid"],
request["summary"],
request["properties"],
supported_components=request.get("supported-components")
@@ -100,7 +100,7 @@
@inlineCallbacks
- def send_shareuninvite(self, txn, homeType, ownerUID, ownerID, shareeUID, shareUID):
+ def send_shareuninvite(self, txn, homeType, ownerUID, bindUID, shareeUID, shareUID):
"""
Send a sharing uninvite cross-pod message.
@@ -108,8 +108,8 @@
@type homeType: C{int}
@param ownerUID: UID of the sharer.
@type ownerUID: C{str}
- @param ownerID: resource ID of the sharer calendar
- @type ownerID: C{int}
+ @param bindUID: bind UID of the sharer calendar
+ @type bindUID: C{str}
@param shareeUID: UID of the sharee
@type shareeUID: C{str}
@param shareUID: Resource/invite ID for sharee
@@ -122,7 +122,7 @@
"action": "shareuninvite",
"type": homeType,
"owner": ownerUID,
- "owner_id": ownerID,
+ "bind_uid": bindUID,
"sharee": shareeUID,
"share_id": shareUID,
}
@@ -147,7 +147,7 @@
# Remove a share
yield shareeHome.processExternalUninvite(
request["owner"],
- request["owner_id"],
+ request["bind_uid"],
request["share_id"],
)
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -135,11 +135,28 @@
"""
return [[a.serialize(), b.serialize(), ] for a, b in value]
+
+ @staticmethod
+ def _to_serialize_dict_value(value):
+ """
+ Convert the value to the external (JSON-based) representation.
+ """
+ return dict([(k, v.serialize(),) for k, v in value.items()])
+
+
+ @staticmethod
+ def _to_serialize_dict_list_serialized_value(value):
+ """
+ Convert the value to the external (JSON-based) representation.
+ """
+ return dict([(k, UtilityConduitMixin._to_serialize_list(v),) for k, v in value.items()])
+
# These are the actions on store objects we need to expose via the conduit api
# Calls on L{CommonHome} objects
UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_metadata", "serialize", classMethod=False)
UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_get_all_group_attendees", "getAllGroupAttendees", classMethod=False, transform_recv_result=StoreAPIConduitMixin._to_serialize_pair_list)
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_shared_to_records", "sharedToBindRecords", transform_recv_result=StoreAPIConduitMixin._to_serialize_dict_list_serialized_value)
# Calls on L{CommonHomeChild} objects
UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_listobjects", "listObjects", classMethod=True)
@@ -150,6 +167,7 @@
UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_synctokenrevision", "syncTokenRevision")
UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_resourcenamessincerevision", "resourceNamesSinceRevision", transform_send_result=UtilityConduitMixin._to_tuple)
UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_search", "search")
+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_sharing_records", "sharingBindRecords", transform_recv_result=StoreAPIConduitMixin._to_serialize_dict_value)
# Calls on L{CommonObjectResource} objects
UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "objectresource_loadallobjects", "loadAllObjects", classMethod=True, transform_recv_result=UtilityConduitMixin._to_serialize_list)
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -1885,6 +1885,15 @@
return self._authzUID
+ def normal(self):
+ """
+ Is this an normal (internal) home.
+
+ @return: a L{bool}.
+ """
+ return self._status == _HOME_STATUS_NORMAL
+
+
def external(self):
"""
Is this an external home.
@@ -2106,15 +2115,15 @@
return self._childClass.objectWithID(self, resourceID)
- def childWithExternalID(self, externalID):
+ def childWithBindUID(self, bindUID):
"""
- Retrieve the child with the given C{externalID} contained in this
+ Retrieve the child with the given C{bindUID} contained in this
home.
@param name: a string.
@return: an L{ICalendar} or C{None} if no such child exists.
"""
- return self._childClass.objectWithExternalID(self, externalID)
+ return self._childClass.objectWithBindUID(self, bindUID)
def allChildWithID(self, resourceID):
@@ -2129,11 +2138,11 @@
@inlineCallbacks
- def createChildWithName(self, name, externalID=None):
+ def createChildWithName(self, name, bindUID=None):
if name.startswith("."):
raise HomeChildNameNotAllowedError(name)
- child = yield self._childClass.create(self, name, externalID=externalID)
+ child = yield self._childClass.create(self, name, bindUID=bindUID)
returnValue(child)
@@ -2706,9 +2715,13 @@
Get the owner home for a shared child ID and the owner's name for that bound child.
Subclasses may override.
"""
- ownerHomeID, ownerName = (yield self._childClass._ownerHomeWithResourceID.on(self._txn, resourceID=resourceID))[0]
- ownerHome = yield self._txn.homeWithResourceID(self._homeType, ownerHomeID)
- returnValue((ownerHome, ownerName))
+ rows = yield self._childClass._ownerHomeWithResourceID.on(self._txn, resourceID=resourceID)
+ if rows:
+ ownerHomeID, ownerName = rows[0]
+ ownerHome = yield self._txn.homeWithResourceID(self._homeType, ownerHomeID)
+ returnValue((ownerHome, ownerName))
+ else:
+ returnValue((None, None))
@@ -2725,6 +2738,11 @@
)
_externalClass = None
+ _homeRecordClass = None
+ _metadataRecordClass = None
+ _bindRecordClass = None
+ _bindHomeIDAttributeName = None
+ _bindResourceIDAttributeName = None
_objectResourceClass = None
_bindSchema = None
@@ -2758,7 +2776,7 @@
@rtype: L{CommonHomeChild}
"""
- bindMode, _ignore_homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = bindData
+ _ignore_homeID, resourceID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = bindData
if ownerHome is None:
if bindMode == _BIND_MODE_OWN:
@@ -2769,7 +2787,7 @@
else:
ownerName = None
- c = cls._externalClass if ownerHome.externalClass() else cls
+ c = cls._externalClass if ownerHome and ownerHome.externalClass() else cls
child = c(
home=home,
name=name,
@@ -2780,7 +2798,7 @@
message=bindMessage,
ownerHome=ownerHome,
ownerName=ownerName,
- externalID=externalID,
+ bindUID=bindUID,
)
if additionalBindData:
@@ -2793,7 +2811,7 @@
# We have to re-adjust the property store object to account for possible shared
# collections as previously we loaded them all as if they were owned
- if propstore and bindMode != _BIND_MODE_OWN:
+ if ownerHome and propstore and bindMode != _BIND_MODE_OWN:
propstore._setDefaultUserUID(ownerHome.uid())
yield child._loadPropertyStore(propstore)
@@ -2802,10 +2820,10 @@
@classmethod
@inlineCallbacks
- def _getDBData(cls, home, name, resourceID, externalID):
+ def _getDBData(cls, home, name, resourceID, bindUID):
"""
Given a set of identifying information, load the data rows for the object. Only one of
- L{name}, L{resourceID} or L{externalID} is specified - others are C{None}.
+ L{name}, L{resourceID} or L{bindUID} is specified - others are C{None}.
@param home: the parent home object
@type home: L{CommonHome}
@@ -2813,8 +2831,8 @@
@type name: C{str}
@param resourceID: the resource ID
@type resourceID: C{int}
- @param externalID: the resource ID of the external (cross-pod) referenced item
- @type externalID: C{int}
+ @param bindUID: the unique ID of the external (cross-pod) referenced item
+ @type bindUID: C{int}
"""
# Get the bind row data
@@ -2827,8 +2845,8 @@
cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
elif resourceID:
cacheKey = queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID)
- elif externalID:
- cacheKey = queryCacher.keyForObjectWithExternalID(home._resourceID, externalID)
+ elif bindUID:
+ cacheKey = queryCacher.keyForObjectWithBindUID(home._resourceID, bindUID)
row = yield queryCacher.get(cacheKey)
if row is None:
@@ -2837,8 +2855,8 @@
rows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
elif resourceID:
rows = yield cls._bindForResourceIDAndHomeID.on(home._txn, resourceID=resourceID, homeID=home._resourceID)
- elif externalID:
- rows = yield cls._bindForExternalIDAndHomeID.on(home._txn, externalID=externalID, homeID=home._resourceID)
+ elif bindUID:
+ rows = yield cls._bindForBindUIDAndHomeID.on(home._txn, bindUID=bindUID, homeID=home._resourceID)
row = rows[0] if rows else None
if not row:
@@ -2848,7 +2866,7 @@
# Cache the result
queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithName(home._resourceID, name), row)
queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID), row)
- queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithExternalID(home._resourceID, externalID), row)
+ queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithBindUID(home._resourceID, bindUID), row)
bindData = row[:cls.bindColumnCount]
additionalBindData = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
@@ -2871,15 +2889,15 @@
returnValue((bindData, additionalBindData, metadataData,))
- def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
+ def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, bindUID=None):
self._home = home
self._name = name
self._resourceID = resourceID
- self._externalID = externalID
self._bindMode = mode
self._bindStatus = status
self._bindRevision = revision
+ self._bindUID = bindUID
self._bindMessage = message
self._ownerHome = home if ownerHome is None else ownerHome
self._ownerName = name if ownerName is None else ownerName
@@ -2943,9 +2961,10 @@
# Load from the main table first
dataRows = (yield cls._childrenAndMetadataForHomeID.on(home._txn, homeID=home._resourceID))
+ resourceID_index = cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)
if dataRows:
# Get property stores
- childResourceIDs = [dataRow[2] for dataRow in dataRows]
+ childResourceIDs = [dataRow[resourceID_index] for dataRow in dataRows]
propertyStores = yield PropertyStore.forMultipleResourcesWithResourceIDs(
home.uid(), None, None, home._txn, childResourceIDs
@@ -2958,7 +2977,7 @@
# Create the actual objects merging in properties
for dataRow in dataRows:
bindData = dataRow[:cls.bindColumnCount]
- resourceID = bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)]
+ resourceID = bindData[resourceID_index]
additionalBindData = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
metadataData = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
propstore = propertyStores.get(resourceID, None)
@@ -2981,13 +3000,13 @@
@classmethod
- def objectWithExternalID(cls, home, externalID, accepted=True):
- return cls.objectWith(home, externalID=externalID, accepted=accepted)
+ def objectWithBindUID(cls, home, bindUID, accepted=True):
+ return cls.objectWith(home, bindUID=bindUID, accepted=accepted)
@classmethod
@inlineCallbacks
- def objectWith(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
+ def objectWith(cls, home, name=None, resourceID=None, bindUID=None, accepted=True):
"""
Create the object using one of the specified arguments as the key to load it. One
and only one of the keyword arguments must be set.
@@ -3007,7 +3026,7 @@
@rtype: C{CommonHomeChild}
"""
- dbData = yield cls._getDBData(home, name, resourceID, externalID)
+ dbData = yield cls._getDBData(home, name, resourceID, bindUID)
if dbData is None:
returnValue(None)
bindData, additionalBindData, metadataData = dbData
@@ -3044,7 +3063,7 @@
@classmethod
@inlineCallbacks
- def create(cls, home, name, externalID=None):
+ def create(cls, home, name, bindUID=None):
if (yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)):
raise HomeChildNameAlreadyExistsError(name)
@@ -3059,7 +3078,7 @@
_created, _modified = (yield cls._insertHomeChildMetaData.on(home._txn, resourceID=resourceID))[0]
# Bind table needs entry
yield cls._bindInsertQuery.on(
- home._txn, homeID=home._resourceID, resourceID=resourceID, externalID=externalID,
+ home._txn, homeID=home._resourceID, resourceID=resourceID, bindUID=bindUID,
name=name, mode=_BIND_MODE_OWN, bindStatus=_BIND_STATUS_ACCEPTED,
message=None,
)
@@ -3096,15 +3115,6 @@
return self._resourceID
- def external_id(self):
- """
- Retrieve the external store identifier for this collection.
-
- @return: a string.
- """
- return self._externalID
-
-
def external(self):
"""
Is this an external home.
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -89,13 +89,13 @@
@memoizedKey("name", "_children")
@inlineCallbacks
- def createChildWithName(self, name, externalID=None):
+ def createChildWithName(self, name, bindUID=None):
"""
No real children - only external ones.
"""
- if externalID is None:
+ if bindUID is None:
raise AssertionError("CommonHomeExternal: not supported")
- child = yield super(CommonHomeExternal, self).createChildWithName(name, externalID)
+ child = yield super(CommonHomeExternal, self).createChildWithName(name, bindUID)
returnValue(child)
@@ -112,7 +112,7 @@
Remove an external child. Check that it is invalid or unused before calling this because if there
are valid references to it, removing will break things.
"""
- if child._externalID is None:
+ if child._bindUID is None:
raise AssertionError("CommonHomeExternal: not supported")
yield super(CommonHomeExternal, self).removeChildWithName(child.name())
@@ -186,11 +186,17 @@
raise AssertionError("CommonHomeExternal: not supported")
-# def ownerHomeAndChildNameForChildID(self, resourceID):
-# """
-# No children.
-# """
-# raise AssertionError("CommonHomeExternal: not supported")
+ @inlineCallbacks
+ def sharedToBindRecords(self):
+ results = yield self._txn.store().conduit.send_home_shared_to_records(self)
+ returnValue(dict([(
+ k,
+ (
+ self._childClass._bindRecordClass.deserialize(v[0]),
+ self._childClass._bindRecordClass.deserialize(v[1]),
+ self._childClass._metadataRecordClass.deserialize(v[2]),
+ ),
+ ) for k, v in results.items()]))
@@ -225,8 +231,8 @@
@classmethod
@inlineCallbacks
- def objectWith(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
- mapping = yield home._txn.store().conduit.send_homechild_objectwith(home, name, resourceID, externalID, accepted)
+ def objectWith(cls, home, name=None, resourceID=None, bindUID=None, accepted=True):
+ mapping = yield home._txn.store().conduit.send_homechild_objectwith(home, name, resourceID, bindUID, accepted)
if mapping:
child = yield cls.deserialize(home, mapping)
@@ -351,7 +357,13 @@
returnValue(results)
+ @inlineCallbacks
+ def sharingBindRecords(self):
+ results = yield self._txn.store().conduit.send_homechild_sharing_records(self)
+ returnValue(dict([(k, self._bindRecordClass.deserialize(v),) for k, v in results.items()]))
+
+
class CommonObjectResourceExternal(CommonObjectResource):
"""
A CommonObjectResource for a resource not hosted on this system, but on another pod. This will forward
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current-oracle-dialect.sql 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current-oracle-dialect.sql 2015-02-25 03:35:13 UTC (rev 14476)
@@ -99,11 +99,11 @@
create table CALENDAR_BIND (
"CALENDAR_HOME_RESOURCE_ID" integer not null references CALENDAR_HOME,
"CALENDAR_RESOURCE_ID" integer not null references CALENDAR on delete cascade,
- "EXTERNAL_ID" integer default null,
"CALENDAR_RESOURCE_NAME" nvarchar2(255),
"BIND_MODE" integer not null,
"BIND_STATUS" integer not null,
"BIND_REVISION" integer default 0 not null,
+ "BIND_UID" nvarchar2(36) default null,
"MESSAGE" nclob,
"TRANSP" integer default 0 not null,
"ALARM_VEVENT_TIMED" nclob default null,
@@ -277,11 +277,11 @@
create table SHARED_ADDRESSBOOK_BIND (
"ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME,
"OWNER_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
- "EXTERNAL_ID" integer default null,
"ADDRESSBOOK_RESOURCE_NAME" nvarchar2(255),
"BIND_MODE" integer not null,
"BIND_STATUS" integer not null,
"BIND_REVISION" integer default 0 not null,
+ "BIND_UID" nvarchar2(36) default null,
"MESSAGE" nclob,
primary key ("ADDRESSBOOK_HOME_RESOURCE_ID", "OWNER_HOME_RESOURCE_ID"),
unique ("ADDRESSBOOK_HOME_RESOURCE_ID", "ADDRESSBOOK_RESOURCE_NAME")
@@ -331,11 +331,11 @@
create table SHARED_GROUP_BIND (
"ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME,
"GROUP_RESOURCE_ID" integer not null references ADDRESSBOOK_OBJECT on delete cascade,
- "EXTERNAL_ID" integer default null,
"GROUP_ADDRESSBOOK_NAME" nvarchar2(255),
"BIND_MODE" integer not null,
"BIND_STATUS" integer not null,
"BIND_REVISION" integer default 0 not null,
+ "BIND_UID" nvarchar2(36) default null,
"MESSAGE" nclob,
primary key ("ADDRESSBOOK_HOME_RESOURCE_ID", "GROUP_RESOURCE_ID"),
unique ("ADDRESSBOOK_HOME_RESOURCE_ID", "GROUP_ADDRESSBOOK_NAME")
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current.sql 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current.sql 2015-02-25 03:35:13 UTC (rev 14476)
@@ -190,11 +190,11 @@
create table CALENDAR_BIND (
CALENDAR_HOME_RESOURCE_ID integer not null references CALENDAR_HOME,
CALENDAR_RESOURCE_ID integer not null references CALENDAR on delete cascade,
- EXTERNAL_ID integer default null,
CALENDAR_RESOURCE_NAME varchar(255) not null,
BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
BIND_REVISION integer default 0 not null,
+ BIND_UID varchar(36) default null,
MESSAGE text,
TRANSP integer default 0 not null, -- enum CALENDAR_TRANSP
ALARM_VEVENT_TIMED text default null,
@@ -502,11 +502,11 @@
create table SHARED_ADDRESSBOOK_BIND (
ADDRESSBOOK_HOME_RESOURCE_ID integer not null references ADDRESSBOOK_HOME,
OWNER_HOME_RESOURCE_ID integer not null references ADDRESSBOOK_HOME on delete cascade,
- EXTERNAL_ID integer default null,
ADDRESSBOOK_RESOURCE_NAME varchar(255) not null,
BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
BIND_REVISION integer default 0 not null,
+ BIND_UID varchar(36) default null,
MESSAGE text, -- FIXME: xml?
primary key (ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID), -- implicit index
@@ -603,11 +603,11 @@
create table SHARED_GROUP_BIND (
ADDRESSBOOK_HOME_RESOURCE_ID integer not null references ADDRESSBOOK_HOME,
GROUP_RESOURCE_ID integer not null references ADDRESSBOOK_OBJECT on delete cascade,
- EXTERNAL_ID integer default null,
GROUP_ADDRESSBOOK_NAME varchar(255) not null,
BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
BIND_REVISION integer default 0 not null,
+ BIND_UID varchar(36) default null,
MESSAGE text, -- FIXME: xml?
primary key (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_RESOURCE_ID), -- implicit index
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_51_to_52.sql
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_51_to_52.sql 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_51_to_52.sql 2015-02-25 03:35:13 UTC (rev 14476)
@@ -21,6 +21,20 @@
-- New status value
insert into HOME_STATUS (DESCRIPTION, ID) values ('migrating', 3);
+-- Change columns
+alter table CALENDAR_BIND
+ drop column EXTERNAL_ID
+ add ("BIND_UID" nvarchar2(36) default null);
+
+alter table SHARED_ADDRESSBOOK_BIND
+ drop column EXTERNAL_ID
+ add ("BIND_UID" nvarchar2(36) default null);
+
+alter table SHARED_GROUP_BIND
+ drop column EXTERNAL_ID
+ add ("BIND_UID" nvarchar2(36) default null);
+
+
-- New table
create table CALENDAR_MIGRATION (
"CALENDAR_HOME_RESOURCE_ID" integer references CALENDAR_HOME on delete cascade,
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_51_to_52.sql
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_51_to_52.sql 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_51_to_52.sql 2015-02-25 03:35:13 UTC (rev 14476)
@@ -21,6 +21,20 @@
-- New status value
insert into HOME_STATUS values (3, 'migrating');
+-- Change columns
+alter table CALENDAR_BIND
+ drop column EXTERNAL_ID,
+ add column BIND_UID varchar(36) default null;
+
+alter table SHARED_ADDRESSBOOK_BIND
+ drop column EXTERNAL_ID,
+ add column BIND_UID varchar(36) default null;
+
+alter table SHARED_GROUP_BIND
+ drop column EXTERNAL_ID,
+ add column BIND_UID varchar(36) default null;
+
+
-- New table
create table CALENDAR_MIGRATION (
CALENDAR_HOME_RESOURCE_ID integer references CALENDAR_HOME on delete cascade,
Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py 2015-02-24 23:14:43 UTC (rev 14475)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py 2015-02-25 03:35:13 UTC (rev 14476)
@@ -78,7 +78,7 @@
#
@inlineCallbacks
def processExternalInvite(
- self, ownerUID, ownerRID, ownerName, shareUID, bindMode, summary,
+ self, ownerUID, ownerName, shareUID, bindMode, bindUID, summary,
copy_invite_properties, supported_components=None
):
"""
@@ -93,42 +93,10 @@
raise ExternalShareFailed("Invalid owner UID: {}".format(ownerUID))
# Try to find owner calendar via its external id
- ownerView = yield ownerHome.childWithExternalID(ownerRID)
+ ownerView = yield ownerHome.childWithBindUID(bindUID)
if ownerView is None:
- try:
- ownerView = yield ownerHome.createChildWithName(
- ownerName, externalID=ownerRID
- )
- except HomeChildNameAlreadyExistsError:
- # This is odd - it means we possibly have a left over sharer
- # collection which the sharer likely removed and re-created
- # with the same name but now it has a different externalID and
- # is not found by the initial query. What we do is check to see
- # whether any shares still reference the old ID - if they do we
- # are hosed. If not, we can remove the old item and create a new one.
- oldOwnerView = yield ownerHome.childWithName(ownerName)
- invites = yield oldOwnerView.sharingInvites()
- if len(invites) != 0:
- log.error(
- "External invite collection name is present with a "
- "different externalID and still has shares"
- )
- raise
- log.error(
- "External invite collection name is present with a "
- "different externalID - trying to fix"
- )
- yield ownerHome.removeExternalChild(oldOwnerView)
- ownerView = yield ownerHome.createChildWithName(
- ownerName, externalID=ownerRID
- )
+ ownerView = yield ownerHome.createCollectionForExternalShare(ownerName, bindUID, supported_components)
- if (
- supported_components is not None and
- hasattr(ownerView, "setSupportedComponents")
- ):
- yield ownerView.setSupportedComponents(supported_components)
-
# Now carry out the share operation
if bindMode == _BIND_MODE_DIRECT:
shareeView = yield ownerView.directShareWithUser(
@@ -143,7 +111,7 @@
@inlineCallbacks
- def processExternalUninvite(self, ownerUID, ownerRID, shareUID):
+ def processExternalUninvite(self, ownerUID, bindUID, shareUID):
"""
External invite received.
"""
@@ -154,7 +122,7 @@
raise ExternalShareFailed("Invalid owner UID: {}".format(ownerUID))
# Try to find owner calendar via its external id
- ownerView = yield ownerHome.childWithExternalID(ownerRID)
+ ownerView = yield ownerHome.childWithBindUID(bindUID)
if ownerView is None:
raise ExternalShareFailed("Invalid share ID: {}".format(shareUID))
@@ -200,7 +168,111 @@
yield shareeHome.declineShare(shareUID)
+ @inlineCallbacks
+ def createCollectionForExternalShare(self, name, bindUID, supported_components):
+ """
+ Create the L{CommonHomeChild} object that used as a "stub" to represent the external
+ object on the other pod for the sharer.
+ @param name: name of the collection
+ @type name: L{str}
+ @param bindUID: id on other pod
+ @type bindUID: L{str}
+ @param supported_components: optional set of support components
+ @type supported_components: L{str}
+ """
+ try:
+ ownerView = yield self.createChildWithName(
+ name, bindUID=bindUID
+ )
+ except HomeChildNameAlreadyExistsError:
+ # This is odd - it means we possibly have a left over sharer
+ # collection which the sharer likely removed and re-created
+ # with the same name but now it has a different bindUID and
+ # is not found by the initial query. What we do is check to see
+ # whether any shares still reference the old ID - if they do we
+ # are hosed. If not, we can remove the old item and create a new one.
+ oldOwnerView = yield self.childWithName(name)
+ invites = yield oldOwnerView.sharingInvites()
+ if len(invites) != 0:
+ log.error(
+ "External invite collection name is present with a "
+ "different bindUID and still has shares"
+ )
+ raise
+ log.error(
+ "External invite collection name is present with a "
+ "different bindUID - trying to fix"
+ )
+ yield self.removeExternalChild(oldOwnerView)
+ ownerView = yield self.createChildWithName(
+ name, bindUID=bindUID
+ )
+
+ if (
+ supported_components is not None and
+ hasattr(ownerView, "setSupportedComponents")
+ ):
+ yield ownerView.setSupportedComponents(supported_components)
+
+ returnValue(ownerView)
+
+
+ @inlineCallbacks
+ def sharedToBindRecords(self):
+ """
+ Return an L{dict} that maps home/directory uid to a sharing bind record for collections shared to this user.
+ """
+
+ # Get shared to bind records
+ records = yield self._childClass._bindRecordClass.query(
+ self._txn,
+ (getattr(self._childClass._bindRecordClass, self._childClass._bindHomeIDAttributeName) == self.id()).And(
+ self._childClass._bindRecordClass.bindMode != _BIND_MODE_OWN
+ )
+ )
+ records = dict([(getattr(record, self._childClass._bindResourceIDAttributeName), record) for record in records])
+ if not records:
+ returnValue({})
+
+ # Look up the owner records for each of the shared to records
+ ownerRecords = yield self._childClass._bindRecordClass.query(
+ self._txn,
+ (getattr(self._childClass._bindRecordClass, self._childClass._bindResourceIDAttributeName).In(records.keys())).And(
+ self._childClass._bindRecordClass.bindMode == _BIND_MODE_OWN
+ )
+ )
+
+ # Important - this method is called when migrating shared-to records to some other pod. For that to work all the
+ # owner records must have a bindUID assigned to them. Normally bindUIDs are assigned the first time an external
+ # share is created, but migration will implicitly create the external share
+ for ownerRecord in ownerRecords:
+ if not ownerRecord.bindUID:
+ yield ownerRecord.update(bindUID=str(uuid4()))
+
+ ownerRecords = dict([(getattr(record, self._childClass._bindResourceIDAttributeName), record) for record in ownerRecords])
+
+ # Look up the metadata records for each of the shared to records
+ metadataRecords = yield self._childClass._metadataRecordClass.query(
+ self._txn,
+ self._childClass._metadataRecordClass.resourceID.In(records.keys()),
+ )
+ metadataRecords = dict([(record.resourceID, record) for record in metadataRecords])
+
+ # Map the owner records to home ownerUIDs
+ homeIDs = dict([(
+ getattr(record, self._childClass._bindHomeIDAttributeName), getattr(record, self._childClass._bindResourceIDAttributeName)
+ ) for record in ownerRecords.values()])
+ homes = yield self._childClass._homeRecordClass.query(
+ self._txn,
+ self._childClass._homeRecordClass.resourceID.In(homeIDs.keys()),
+ )
+ homeMap = dict((homeIDs[home.resourceID], home.ownerUID,) for home in homes)
+
+ returnValue(dict([(homeMap[calendarID], (records[calendarID], ownerRecords[calendarID], metadataRecords[calendarID],),) for calendarID in records]))
+
+
+
SharingInvitation = namedtuple(
"SharingInvitation",
["uid", "ownerUID", "ownerHomeID", "shareeUID", "shareeHomeID", "mode", "status", "summary"]
@@ -223,10 +295,10 @@
return Insert({
bind.HOME_RESOURCE_ID: Parameter("homeID"),
bind.RESOURCE_ID: Parameter("resourceID"),
- bind.EXTERNAL_ID: Parameter("externalID"),
bind.RESOURCE_NAME: Parameter("name"),
bind.BIND_MODE: Parameter("mode"),
bind.BIND_STATUS: Parameter("bindStatus"),
+ bind.BIND_UID: Parameter("bindUID"),
bind.MESSAGE: Parameter("message"),
})
@@ -309,13 +381,13 @@
@classproperty
- def _bindForExternalIDAndHomeID(cls):
+ def _bindForBindUIDAndHomeID(cls):
"""
DAL query that looks up home bind rows by home child
resource ID and home resource ID.
"""
bind = cls._bindSchema
- return cls._bindFor((bind.EXTERNAL_ID == Parameter("externalID"))
+ return cls._bindFor((bind.BIND_UID == Parameter("bindUID"))
.And(bind.HOME_RESOURCE_ID == Parameter("homeID")))
@@ -580,15 +652,24 @@
@inlineCallbacks
def _sendExternalInvite(self, shareeView):
+ # Must make sure this collection has a BIND_UID assigned
+ if not self._bindUID:
+ self._bindUID = str(uuid4())
+ yield self._updateBindColumnsQuery({self._bindSchema.BIND_UID: self._bindUID}).on(
+ self._txn,
+ resourceID=self.id(), homeID=self.ownerHome().id()
+ )
+
+ # Now send the invite
yield self._txn.store().conduit.send_shareinvite(
self._txn,
shareeView.ownerHome()._homeType,
shareeView.ownerHome().uid(),
- self.id(),
self.shareName(),
shareeView.viewerHome().uid(),
shareeView.shareUID(),
shareeView.shareMode(),
+ self.bindUID(),
shareeView.shareMessage(),
self.getInviteCopyProperties(),
supported_components=self.getSupportedComponents() if hasattr(self, "getSupportedComponents") else None,
@@ -602,7 +683,7 @@
self._txn,
shareeView.ownerHome()._homeType,
shareeView.ownerHome().uid(),
- self.id(),
+ self.bindUID(),
shareeView.viewerHome().uid(),
shareeView.shareUID(),
)
@@ -723,10 +804,10 @@
subt,
homeID=shareeHome._resourceID,
resourceID=self._resourceID,
- externalID=self._externalID,
name=newName,
mode=mode,
bindStatus=status,
+ bindUID=None,
message=summary
)
returnValue(newName)
@@ -939,6 +1020,28 @@
@inlineCallbacks
+ def sharingBindRecords(self):
+ """
+ Return an L{dict} that maps home/directory uid to a sharing bind record.
+ """
+ if not self.owned():
+ returnValue({})
+
+ records = yield self._bindRecordClass.querysimple(
+ self._txn,
+ **{self._bindResourceIDAttributeName: self.id()}
+ )
+ homeIDs = [getattr(record, self._bindHomeIDAttributeName) for record in records]
+ homes = yield self._homeRecordClass.query(
+ self._txn,
+ self._homeRecordClass.resourceID.In(homeIDs),
+ )
+ homeMap = dict((home.resourceID, home.ownerUID,) for home in homes)
+
+ returnValue(dict([(homeMap[getattr(record, self._bindHomeIDAttributeName)], record,) for record in records if record.bindMode != _BIND_MODE_OWN]))
+
+
+ @inlineCallbacks
def _initBindRevision(self):
yield self.syncToken() # init self._syncTokenRevision if None
self._bindRevision = self._syncTokenRevision
@@ -1086,6 +1189,13 @@
return self._bindStatus
+ def bindUID(self):
+ """
+ @see: L{ICalendar.bindUID}
+ """
+ return self._bindUID
+
+
def accepted(self):
"""
@see: L{ICalendar.shareStatus}
@@ -1159,13 +1269,13 @@
"""
return (
- cls._bindSchema.BIND_MODE,
cls._bindSchema.HOME_RESOURCE_ID,
cls._bindSchema.RESOURCE_ID,
- cls._bindSchema.EXTERNAL_ID,
cls._bindSchema.RESOURCE_NAME,
+ cls._bindSchema.BIND_MODE,
cls._bindSchema.BIND_STATUS,
cls._bindSchema.BIND_REVISION,
+ cls._bindSchema.BIND_UID,
cls._bindSchema.MESSAGE
)
@@ -1179,13 +1289,13 @@
"""
return (
- "_bindMode",
"_homeResourceID",
"_resourceID",
- "_externalID",
"_name",
+ "_bindMode",
"_bindStatus",
"_bindRevision",
+ "_bindUID",
"_bindMessage",
)
@@ -1251,4 +1361,4 @@
yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForHomeChildMetaData(self._resourceID))
yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithName(self._home._resourceID, self._name))
yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithResourceID(self._home._resourceID, self._resourceID))
- yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithExternalID(self._home._resourceID, self._externalID))
+ yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithBindUID(self._home._resourceID, self._bindUID))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150224/050a09cc/attachment-0001.html>
More information about the calendarserver-changes
mailing list