<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[14476] CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/14476">14476</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-02-24 19:35:13 -0800 (Tue, 24 Feb 2015)</dd>
</dl>
<h3>Log Message</h3>
<pre>Checkpoint: sharing state sync. Still have clean-up piece to do.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavbasedatastoreutilpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/base/datastore/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoresqlpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoretesttest_sql_sharingpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcarddavdatastoresqlpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_home_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingsharing_invitespy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/sharing_invites.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingstore_apipy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_externalpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_schemacurrentoracledialectsql">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current-oracle-dialect.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_schemacurrentsql">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_51_to_52sql">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_51_to_52.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_51_to_52sql">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_51_to_52.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_sharingpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavbasedatastoreutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/base/datastore/util.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -100,8 +100,8 @@
</span><span class="cx">
</span><span class="cx"> # Home child objects by external id
</span><span class="cx">
</span><del>- def keyForObjectWithExternalID(self, homeResourceID, externalID):
- return "objectWithExternalID:%s:%s" % (homeResourceID, externalID)
</del><ins>+ def keyForObjectWithBindUID(self, homeResourceID, bindUID):
+ return "objectWithBindUID:%s:%s" % (homeResourceID, bindUID)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> # Home metadata (Created/Modified)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/sql.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -28,7 +28,7 @@
</span><span class="cx"> "CalendarObject",
</span><span class="cx"> ]
</span><span class="cx">
</span><del>-from twext.enterprise.dal.record import fromTable
</del><ins>+from twext.enterprise.dal.record import fromTable, SerializableRecord
</ins><span class="cx"> from twext.enterprise.dal.syntax import Count, ColumnSyntax, Delete, \
</span><span class="cx"> Insert, Len, Max, Parameter, Select, Update, utcNowSQL
</span><span class="cx"> from twext.enterprise.locking import NamedLock
</span><span class="lines">@@ -401,6 +401,33 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><ins>+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
+
+
+
</ins><span class="cx"> class CalendarHome(CommonHome):
</span><span class="cx">
</span><span class="cx"> implements(ICalendarHome)
</span><span class="lines">@@ -1002,6 +1029,12 @@
</span><span class="cx"> _objectSchema = schema.CALENDAR_OBJECT
</span><span class="cx"> _timeRangeSchema = schema.TIME_RANGE
</span><span class="cx">
</span><ins>+ _homeRecordClass = CalendarHomeRecord
+ _metadataRecordClass = CalendarMetaDataRecord
+ _bindRecordClass = CalendarBindRecord
+ _bindHomeIDAttributeName = "calendarHomeResourceID"
+ _bindResourceIDAttributeName = "calendarResourceID"
+
</ins><span class="cx"> # Mapping of iCalendar property name to DB column name
</span><span class="cx"> _queryFields = {
</span><span class="cx"> "UID": _objectSchema.UID,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoretesttest_sql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -587,7 +587,42 @@
</span><span class="cx"> yield self.commit()
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @inlineCallbacks
+ def test_sharingBindRecords(self):
</ins><span class="cx">
</span><ins>+ 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)
+
+
+
</ins><span class="cx"> class GroupSharingTests(BaseSharingTests):
</span><span class="cx"> """
</span><span class="cx"> Test store-based group sharing.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/sql.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -462,7 +462,7 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def _getDBDataIndirect(cls, home, name, resourceID, externalID):
</del><ins>+ def _getDBDataIndirect(cls, home, name, resourceID, bindUID):
</ins><span class="cx">
</span><span class="cx"> # Get the bind row data
</span><span class="cx"> row = None
</span><span class="lines">@@ -492,7 +492,7 @@
</span><span class="cx"> overallBindStatus = _BIND_STATUS_INVITED
</span><span class="cx"> minBindRevision = None
</span><span class="cx"> for row in rows:
</span><del>- bindMode, homeID, resourceGroupID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
</del><ins>+ homeID, resourceGroupID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
</ins><span class="cx"> if groupID is None:
</span><span class="cx"> groupID = resourceGroupID
</span><span class="cx"> minBindRevision = min(minBindRevision, bindRevision) if minBindRevision is not None else bindRevision
</span><span class="lines">@@ -532,9 +532,9 @@
</span><span class="cx"> returnValue((bindData, additionalBindData, metadataData, ownerHome,))
</span><span class="cx">
</span><span class="cx">
</span><del>- def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
</del><ins>+ def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, bindUID=None):
</ins><span class="cx"> ownerName = ownerHome.addressbook().name() if ownerHome else None
</span><del>- super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName, externalID=externalID)
</del><ins>+ super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName, bindUID=bindUID)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def __repr__(self):
</span><span class="lines">@@ -851,7 +851,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><del>- def create(cls, home, name, externalID=None):
</del><ins>+ def create(cls, home, name, bindUID=None):
</ins><span class="cx"> if name == home.addressbook().name():
</span><span class="cx"> # raise HomeChildNameAlreadyExistsError
</span><span class="cx"> pass
</span><span class="lines">@@ -1117,7 +1117,7 @@
</span><span class="cx"> home._txn, homeID=home._resourceID
</span><span class="cx"> )
</span><span class="cx"> for groupRow in groupRows:
</span><del>- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+ homeID, resourceID, bindName, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx"> ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
</span><span class="cx"> ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerAddressBookID)
</span><span class="cx"> names |= set([ownerHome.uid()])
</span><span class="lines">@@ -1145,7 +1145,7 @@
</span><span class="cx"> )
</span><span class="cx"> # get ownerHomeIDs
</span><span class="cx"> for dataRow in dataRows:
</span><del>- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
</del><ins>+ homeID, resourceID, bindName, bindMode, bindStatus, bindRevision, bindUID, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
</ins><span class="cx"> ownerHome = yield home.ownerHomeWithChildID(resourceID)
</span><span class="cx"> ownerHomeToDataRowMap[ownerHome] = dataRow
</span><span class="cx">
</span><span class="lines">@@ -1154,12 +1154,16 @@
</span><span class="cx"> home._txn, homeID=home._resourceID
</span><span class="cx"> )
</span><span class="cx"> for groupBindRow in groupBindRows:
</span><del>- bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+ homeID, resourceID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx"> ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
</span><span class="cx"> ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
</span><span class="cx"> if ownerHome not in ownerHomeToDataRowMap:
</span><del>- groupBindRow[0] = _BIND_MODE_INDIRECT
- groupBindRow[3:7] = 4 * [None] # bindName, bindStatus, bindRevision, bindMessage
</del><ins>+ 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
</ins><span class="cx"> ownerHomeToDataRowMap[ownerHome] = groupBindRow
</span><span class="cx">
</span><span class="cx"> if ownerHomeToDataRowMap:
</span><span class="lines">@@ -1248,7 +1252,7 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
</del><ins>+ def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, bindUID=None, accepted=True):
</ins><span class="cx"> # replaces objectWithName()
</span><span class="cx"> """
</span><span class="cx"> Synthesize and indirect child for matching name or id based on whether shared groups exist.
</span><span class="lines">@@ -1261,7 +1265,7 @@
</span><span class="cx"> exists.
</span><span class="cx"> """
</span><span class="cx">
</span><del>- dbData = yield cls._getDBDataIndirect(home, name, resourceID, externalID)
</del><ins>+ dbData = yield cls._getDBDataIndirect(home, name, resourceID, bindUID)
</ins><span class="cx"> if dbData is None:
</span><span class="cx"> returnValue(None)
</span><span class="cx"> bindData, additionalBindData, metadataData, ownerHome = dbData
</span><span class="lines">@@ -1399,7 +1403,7 @@
</span><span class="cx"> readWriteGroupIDs = set()
</span><span class="cx"> readOnlyGroupIDs = set()
</span><span class="cx"> for groupBindRow in groupBindRows:
</span><del>- bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+ homeID, resourceID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx"> if bindMode == _BIND_MODE_WRITE:
</span><span class="cx"> readWriteGroupIDs.add(resourceID)
</span><span class="cx"> else:
</span><span class="lines">@@ -1460,7 +1464,7 @@
</span><span class="cx"> readWriteGroupIDs = []
</span><span class="cx"> readOnlyGroupIDs = []
</span><span class="cx"> for groupBindRow in groupBindRows:
</span><del>- bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+ homeID, resourceID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx"> if bindMode == _BIND_MODE_WRITE:
</span><span class="cx"> readWriteGroupIDs.append(resourceID)
</span><span class="cx"> else:
</span><span class="lines">@@ -1578,10 +1582,10 @@
</span><span class="cx"> subt,
</span><span class="cx"> homeID=shareeHome._resourceID,
</span><span class="cx"> resourceID=self._resourceID,
</span><del>- externalID=None,
</del><span class="cx"> name=newName,
</span><span class="cx"> mode=mode,
</span><span class="cx"> bindStatus=status,
</span><ins>+ bindUID=None,
</ins><span class="cx"> message=summary
</span><span class="cx"> )
</span><span class="cx"> returnValue(newName)
</span><span class="lines">@@ -1896,7 +1900,7 @@
</span><span class="cx"> yield child._loadPropertyStore(propstore)
</span><span class="cx">
</span><span class="cx"> if groupBindData:
</span><del>- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindData[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+ homeID, resourceID, bindName, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindData[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx"> child._bindMode = bindMode
</span><span class="cx"> child._bindStatus = bindStatus
</span><span class="cx"> child._bindMessage = bindMessage
</span><span class="lines">@@ -1997,7 +2001,7 @@
</span><span class="cx"> self._bindName = None
</span><span class="cx"> self._bindRevision = None
</span><span class="cx"> super(AddressBookObject, self).__init__(addressbook, name, uid, resourceID, options)
</span><del>- self._externalID = None
</del><ins>+ self._bindUID = None
</ins><span class="cx"> self._options = {} if options is None else options
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -2206,7 +2210,7 @@
</span><span class="cx"> )
</span><span class="cx"> if groupBindRows:
</span><span class="cx"> groupBindRow = groupBindRows[0]
</span><del>- bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+ homeID, resourceID, bindName, bindMode, bindStatus, bindRevision, bindUID, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx">
</span><span class="cx"> if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx"> returnValue(None)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -22,7 +22,7 @@
</span><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState
</span><span class="cx"> from txdav.common.datastore.podding.migration.sync_metadata import CalendarMigrationRecord, \
</span><span class="cx"> CalendarObjectMigrationRecord, AttachmentMigrationRecord
</span><del>-from txdav.caldav.datastore.sql import ManagedAttachment
</del><ins>+from txdav.caldav.datastore.sql import ManagedAttachment, CalendarBindRecord
</ins><span class="cx"> from txdav.common.datastore.sql_external import NotificationCollectionExternal
</span><span class="cx"> from txdav.common.datastore.sql_notification import NotificationCollection
</span><span class="cx"> from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL
</span><span class="lines">@@ -176,19 +176,20 @@
</span><span class="cx"> # TODO: Re-write attachment URIs - not sure if we need this as reverse proxy may take care of it
</span><span class="cx"> pass
</span><span class="cx">
</span><del>- # TODO: group attendee reconcile
</del><ins>+ # Group attendee reconcile
</ins><span class="cx"> yield self.groupAttendeeReconcile()
</span><span class="cx">
</span><del>- # TODO: delegates reconcile
</del><ins>+ # Delegates reconcile
</ins><span class="cx"> yield self.delegateReconcile()
</span><span class="cx">
</span><span class="cx"> # TODO: shared collections reconcile
</span><del>- pass
</del><ins>+ yield self.sharedByCollectionsReconcile()
+ yield self.sharedToCollectionsReconcile()
</ins><span class="cx">
</span><span class="cx"> # TODO: group sharee reconcile
</span><span class="cx"> pass
</span><span class="cx">
</span><del>- # TODO: notifications
</del><ins>+ # Notifications
</ins><span class="cx"> yield self.notificationsReconcile()
</span><span class="cx">
</span><span class="cx"> # TODO: work items
</span><span class="lines">@@ -943,3 +944,160 @@
</span><span class="cx"> # Do this via the "write" API so that sync revisions are updated properly, rather than just
</span><span class="cx"> # inserting the records directly.
</span><span class="cx"> yield notifications.writeNotificationObject(record.notificationUID, record.notificationType, record.notificationData)
</span><ins>+
+
+ @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)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_home_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -30,7 +30,8 @@
</span><span class="cx"> from txdav.common.datastore.sql_directory import DelegateRecord, \
</span><span class="cx"> ExternalDelegateGroupsRecord, DelegateGroupsRecord
</span><span class="cx"> from txdav.common.datastore.sql_notification import NotificationCollection
</span><del>-from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_EXTERNAL
</del><ins>+from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_EXTERNAL, \
+ _BIND_MODE_READ
</ins><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom
</span><span class="cx"> from txdav.who.delegates import Delegates
</span><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="lines">@@ -976,6 +977,81 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><ins>+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)
+
+
+
</ins><span class="cx"> class TestGroupAttendeeSync(MultiStoreConduitTest):
</span><span class="cx"> """
</span><span class="cx"> GroupAttendeeReconciliation tests
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingsharing_invitespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/sharing_invites.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -26,7 +26,7 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def send_shareinvite(self, txn, homeType, ownerUID, ownerID, ownerName, shareeUID, shareUID, bindMode, summary, copy_properties, supported_components):
</del><ins>+ def send_shareinvite(self, txn, homeType, ownerUID, ownerName, shareeUID, shareUID, bindMode, bindUID, summary, copy_properties, supported_components):
</ins><span class="cx"> """
</span><span class="cx"> Send a sharing invite cross-pod message.
</span><span class="cx">
</span><span class="lines">@@ -34,8 +34,6 @@
</span><span class="cx"> @type homeType: C{int}
</span><span class="cx"> @param ownerUID: UID of the sharer.
</span><span class="cx"> @type ownerUID: C{str}
</span><del>- @param ownerID: resource ID of the sharer calendar
- @type ownerID: C{int}
</del><span class="cx"> @param ownerName: owner's name of the sharer calendar
</span><span class="cx"> @type ownerName: C{str}
</span><span class="cx"> @param shareeUID: UID of the sharee
</span><span class="lines">@@ -44,6 +42,8 @@
</span><span class="cx"> @type shareUID: C{str}
</span><span class="cx"> @param bindMode: bind mode for the share
</span><span class="cx"> @type bindMode: C{str}
</span><ins>+ @param bindUID: bind UID of the sharer calendar
+ @type bindUID: C{str}
</ins><span class="cx"> @param summary: sharing message
</span><span class="cx"> @type summary: C{str}
</span><span class="cx"> @param copy_properties: C{str} name/value for properties to be copied
</span><span class="lines">@@ -58,11 +58,11 @@
</span><span class="cx"> "action": "shareinvite",
</span><span class="cx"> "type": homeType,
</span><span class="cx"> "owner": ownerUID,
</span><del>- "owner_id": ownerID,
</del><span class="cx"> "owner_name": ownerName,
</span><span class="cx"> "sharee": shareeUID,
</span><span class="cx"> "share_id": shareUID,
</span><span class="cx"> "mode": bindMode,
</span><ins>+ "bind_uid": bindUID,
</ins><span class="cx"> "summary": summary,
</span><span class="cx"> "properties": copy_properties,
</span><span class="cx"> }
</span><span class="lines">@@ -89,10 +89,10 @@
</span><span class="cx"> # Create a share
</span><span class="cx"> yield shareeHome.processExternalInvite(
</span><span class="cx"> request["owner"],
</span><del>- request["owner_id"],
</del><span class="cx"> request["owner_name"],
</span><span class="cx"> request["share_id"],
</span><span class="cx"> request["mode"],
</span><ins>+ request["bind_uid"],
</ins><span class="cx"> request["summary"],
</span><span class="cx"> request["properties"],
</span><span class="cx"> supported_components=request.get("supported-components")
</span><span class="lines">@@ -100,7 +100,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def send_shareuninvite(self, txn, homeType, ownerUID, ownerID, shareeUID, shareUID):
</del><ins>+ def send_shareuninvite(self, txn, homeType, ownerUID, bindUID, shareeUID, shareUID):
</ins><span class="cx"> """
</span><span class="cx"> Send a sharing uninvite cross-pod message.
</span><span class="cx">
</span><span class="lines">@@ -108,8 +108,8 @@
</span><span class="cx"> @type homeType: C{int}
</span><span class="cx"> @param ownerUID: UID of the sharer.
</span><span class="cx"> @type ownerUID: C{str}
</span><del>- @param ownerID: resource ID of the sharer calendar
- @type ownerID: C{int}
</del><ins>+ @param bindUID: bind UID of the sharer calendar
+ @type bindUID: C{str}
</ins><span class="cx"> @param shareeUID: UID of the sharee
</span><span class="cx"> @type shareeUID: C{str}
</span><span class="cx"> @param shareUID: Resource/invite ID for sharee
</span><span class="lines">@@ -122,7 +122,7 @@
</span><span class="cx"> "action": "shareuninvite",
</span><span class="cx"> "type": homeType,
</span><span class="cx"> "owner": ownerUID,
</span><del>- "owner_id": ownerID,
</del><ins>+ "bind_uid": bindUID,
</ins><span class="cx"> "sharee": shareeUID,
</span><span class="cx"> "share_id": shareUID,
</span><span class="cx"> }
</span><span class="lines">@@ -147,7 +147,7 @@
</span><span class="cx"> # Remove a share
</span><span class="cx"> yield shareeHome.processExternalUninvite(
</span><span class="cx"> request["owner"],
</span><del>- request["owner_id"],
</del><ins>+ request["bind_uid"],
</ins><span class="cx"> request["share_id"],
</span><span class="cx"> )
</span><span class="cx">
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingstore_apipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/store_api.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -135,11 +135,28 @@
</span><span class="cx"> """
</span><span class="cx"> return [[a.serialize(), b.serialize(), ] for a, b in value]
</span><span class="cx">
</span><ins>+
+ @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()])
+
</ins><span class="cx"> # These are the actions on store objects we need to expose via the conduit api
</span><span class="cx">
</span><span class="cx"> # Calls on L{CommonHome} objects
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_metadata", "serialize", classMethod=False)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_get_all_group_attendees", "getAllGroupAttendees", classMethod=False, transform_recv_result=StoreAPIConduitMixin._to_serialize_pair_list)
</span><ins>+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "home_shared_to_records", "sharedToBindRecords", transform_recv_result=StoreAPIConduitMixin._to_serialize_dict_list_serialized_value)
</ins><span class="cx">
</span><span class="cx"> # Calls on L{CommonHomeChild} objects
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_listobjects", "listObjects", classMethod=True)
</span><span class="lines">@@ -150,6 +167,7 @@
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_synctokenrevision", "syncTokenRevision")
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_resourcenamessincerevision", "resourceNamesSinceRevision", transform_send_result=UtilityConduitMixin._to_tuple)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_search", "search")
</span><ins>+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "homechild_sharing_records", "sharingBindRecords", transform_recv_result=StoreAPIConduitMixin._to_serialize_dict_value)
</ins><span class="cx">
</span><span class="cx"> # Calls on L{CommonObjectResource} objects
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, "objectresource_loadallobjects", "loadAllObjects", classMethod=True, transform_recv_result=UtilityConduitMixin._to_serialize_list)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1885,6 +1885,15 @@
</span><span class="cx"> return self._authzUID
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def normal(self):
+ """
+ Is this an normal (internal) home.
+
+ @return: a L{bool}.
+ """
+ return self._status == _HOME_STATUS_NORMAL
+
+
</ins><span class="cx"> def external(self):
</span><span class="cx"> """
</span><span class="cx"> Is this an external home.
</span><span class="lines">@@ -2106,15 +2115,15 @@
</span><span class="cx"> return self._childClass.objectWithID(self, resourceID)
</span><span class="cx">
</span><span class="cx">
</span><del>- def childWithExternalID(self, externalID):
</del><ins>+ def childWithBindUID(self, bindUID):
</ins><span class="cx"> """
</span><del>- Retrieve the child with the given C{externalID} contained in this
</del><ins>+ Retrieve the child with the given C{bindUID} contained in this
</ins><span class="cx"> home.
</span><span class="cx">
</span><span class="cx"> @param name: a string.
</span><span class="cx"> @return: an L{ICalendar} or C{None} if no such child exists.
</span><span class="cx"> """
</span><del>- return self._childClass.objectWithExternalID(self, externalID)
</del><ins>+ return self._childClass.objectWithBindUID(self, bindUID)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> def allChildWithID(self, resourceID):
</span><span class="lines">@@ -2129,11 +2138,11 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def createChildWithName(self, name, externalID=None):
</del><ins>+ def createChildWithName(self, name, bindUID=None):
</ins><span class="cx"> if name.startswith("."):
</span><span class="cx"> raise HomeChildNameNotAllowedError(name)
</span><span class="cx">
</span><del>- child = yield self._childClass.create(self, name, externalID=externalID)
</del><ins>+ child = yield self._childClass.create(self, name, bindUID=bindUID)
</ins><span class="cx"> returnValue(child)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -2706,9 +2715,13 @@
</span><span class="cx"> Get the owner home for a shared child ID and the owner's name for that bound child.
</span><span class="cx"> Subclasses may override.
</span><span class="cx"> """
</span><del>- ownerHomeID, ownerName = (yield self._childClass._ownerHomeWithResourceID.on(self._txn, resourceID=resourceID))[0]
- ownerHome = yield self._txn.homeWithResourceID(self._homeType, ownerHomeID)
- returnValue((ownerHome, ownerName))
</del><ins>+ 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))
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -2725,6 +2738,11 @@
</span><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> _externalClass = None
</span><ins>+ _homeRecordClass = None
+ _metadataRecordClass = None
+ _bindRecordClass = None
+ _bindHomeIDAttributeName = None
+ _bindResourceIDAttributeName = None
</ins><span class="cx"> _objectResourceClass = None
</span><span class="cx">
</span><span class="cx"> _bindSchema = None
</span><span class="lines">@@ -2758,7 +2776,7 @@
</span><span class="cx"> @rtype: L{CommonHomeChild}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- bindMode, _ignore_homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = bindData
</del><ins>+ _ignore_homeID, resourceID, name, bindMode, bindStatus, bindRevision, bindUID, bindMessage = bindData
</ins><span class="cx">
</span><span class="cx"> if ownerHome is None:
</span><span class="cx"> if bindMode == _BIND_MODE_OWN:
</span><span class="lines">@@ -2769,7 +2787,7 @@
</span><span class="cx"> else:
</span><span class="cx"> ownerName = None
</span><span class="cx">
</span><del>- c = cls._externalClass if ownerHome.externalClass() else cls
</del><ins>+ c = cls._externalClass if ownerHome and ownerHome.externalClass() else cls
</ins><span class="cx"> child = c(
</span><span class="cx"> home=home,
</span><span class="cx"> name=name,
</span><span class="lines">@@ -2780,7 +2798,7 @@
</span><span class="cx"> message=bindMessage,
</span><span class="cx"> ownerHome=ownerHome,
</span><span class="cx"> ownerName=ownerName,
</span><del>- externalID=externalID,
</del><ins>+ bindUID=bindUID,
</ins><span class="cx"> )
</span><span class="cx">
</span><span class="cx"> if additionalBindData:
</span><span class="lines">@@ -2793,7 +2811,7 @@
</span><span class="cx">
</span><span class="cx"> # We have to re-adjust the property store object to account for possible shared
</span><span class="cx"> # collections as previously we loaded them all as if they were owned
</span><del>- if propstore and bindMode != _BIND_MODE_OWN:
</del><ins>+ if ownerHome and propstore and bindMode != _BIND_MODE_OWN:
</ins><span class="cx"> propstore._setDefaultUserUID(ownerHome.uid())
</span><span class="cx"> yield child._loadPropertyStore(propstore)
</span><span class="cx">
</span><span class="lines">@@ -2802,10 +2820,10 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def _getDBData(cls, home, name, resourceID, externalID):
</del><ins>+ def _getDBData(cls, home, name, resourceID, bindUID):
</ins><span class="cx"> """
</span><span class="cx"> Given a set of identifying information, load the data rows for the object. Only one of
</span><del>- L{name}, L{resourceID} or L{externalID} is specified - others are C{None}.
</del><ins>+ L{name}, L{resourceID} or L{bindUID} is specified - others are C{None}.
</ins><span class="cx">
</span><span class="cx"> @param home: the parent home object
</span><span class="cx"> @type home: L{CommonHome}
</span><span class="lines">@@ -2813,8 +2831,8 @@
</span><span class="cx"> @type name: C{str}
</span><span class="cx"> @param resourceID: the resource ID
</span><span class="cx"> @type resourceID: C{int}
</span><del>- @param externalID: the resource ID of the external (cross-pod) referenced item
- @type externalID: C{int}
</del><ins>+ @param bindUID: the unique ID of the external (cross-pod) referenced item
+ @type bindUID: C{int}
</ins><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> # Get the bind row data
</span><span class="lines">@@ -2827,8 +2845,8 @@
</span><span class="cx"> cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
</span><span class="cx"> elif resourceID:
</span><span class="cx"> cacheKey = queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID)
</span><del>- elif externalID:
- cacheKey = queryCacher.keyForObjectWithExternalID(home._resourceID, externalID)
</del><ins>+ elif bindUID:
+ cacheKey = queryCacher.keyForObjectWithBindUID(home._resourceID, bindUID)
</ins><span class="cx"> row = yield queryCacher.get(cacheKey)
</span><span class="cx">
</span><span class="cx"> if row is None:
</span><span class="lines">@@ -2837,8 +2855,8 @@
</span><span class="cx"> rows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
</span><span class="cx"> elif resourceID:
</span><span class="cx"> rows = yield cls._bindForResourceIDAndHomeID.on(home._txn, resourceID=resourceID, homeID=home._resourceID)
</span><del>- elif externalID:
- rows = yield cls._bindForExternalIDAndHomeID.on(home._txn, externalID=externalID, homeID=home._resourceID)
</del><ins>+ elif bindUID:
+ rows = yield cls._bindForBindUIDAndHomeID.on(home._txn, bindUID=bindUID, homeID=home._resourceID)
</ins><span class="cx"> row = rows[0] if rows else None
</span><span class="cx">
</span><span class="cx"> if not row:
</span><span class="lines">@@ -2848,7 +2866,7 @@
</span><span class="cx"> # Cache the result
</span><span class="cx"> queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithName(home._resourceID, name), row)
</span><span class="cx"> queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID), row)
</span><del>- queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithExternalID(home._resourceID, externalID), row)
</del><ins>+ queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithBindUID(home._resourceID, bindUID), row)
</ins><span class="cx">
</span><span class="cx"> bindData = row[:cls.bindColumnCount]
</span><span class="cx"> additionalBindData = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</span><span class="lines">@@ -2871,15 +2889,15 @@
</span><span class="cx"> returnValue((bindData, additionalBindData, metadataData,))
</span><span class="cx">
</span><span class="cx">
</span><del>- def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
</del><ins>+ def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, bindUID=None):
</ins><span class="cx">
</span><span class="cx"> self._home = home
</span><span class="cx"> self._name = name
</span><span class="cx"> self._resourceID = resourceID
</span><del>- self._externalID = externalID
</del><span class="cx"> self._bindMode = mode
</span><span class="cx"> self._bindStatus = status
</span><span class="cx"> self._bindRevision = revision
</span><ins>+ self._bindUID = bindUID
</ins><span class="cx"> self._bindMessage = message
</span><span class="cx"> self._ownerHome = home if ownerHome is None else ownerHome
</span><span class="cx"> self._ownerName = name if ownerName is None else ownerName
</span><span class="lines">@@ -2943,9 +2961,10 @@
</span><span class="cx"> # Load from the main table first
</span><span class="cx"> dataRows = (yield cls._childrenAndMetadataForHomeID.on(home._txn, homeID=home._resourceID))
</span><span class="cx">
</span><ins>+ resourceID_index = cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)
</ins><span class="cx"> if dataRows:
</span><span class="cx"> # Get property stores
</span><del>- childResourceIDs = [dataRow[2] for dataRow in dataRows]
</del><ins>+ childResourceIDs = [dataRow[resourceID_index] for dataRow in dataRows]
</ins><span class="cx">
</span><span class="cx"> propertyStores = yield PropertyStore.forMultipleResourcesWithResourceIDs(
</span><span class="cx"> home.uid(), None, None, home._txn, childResourceIDs
</span><span class="lines">@@ -2958,7 +2977,7 @@
</span><span class="cx"> # Create the actual objects merging in properties
</span><span class="cx"> for dataRow in dataRows:
</span><span class="cx"> bindData = dataRow[:cls.bindColumnCount]
</span><del>- resourceID = bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)]
</del><ins>+ resourceID = bindData[resourceID_index]
</ins><span class="cx"> additionalBindData = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</span><span class="cx"> metadataData = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
</span><span class="cx"> propstore = propertyStores.get(resourceID, None)
</span><span class="lines">@@ -2981,13 +3000,13 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><del>- def objectWithExternalID(cls, home, externalID, accepted=True):
- return cls.objectWith(home, externalID=externalID, accepted=accepted)
</del><ins>+ def objectWithBindUID(cls, home, bindUID, accepted=True):
+ return cls.objectWith(home, bindUID=bindUID, accepted=accepted)
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def objectWith(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
</del><ins>+ def objectWith(cls, home, name=None, resourceID=None, bindUID=None, accepted=True):
</ins><span class="cx"> """
</span><span class="cx"> Create the object using one of the specified arguments as the key to load it. One
</span><span class="cx"> and only one of the keyword arguments must be set.
</span><span class="lines">@@ -3007,7 +3026,7 @@
</span><span class="cx"> @rtype: C{CommonHomeChild}
</span><span class="cx"> """
</span><span class="cx">
</span><del>- dbData = yield cls._getDBData(home, name, resourceID, externalID)
</del><ins>+ dbData = yield cls._getDBData(home, name, resourceID, bindUID)
</ins><span class="cx"> if dbData is None:
</span><span class="cx"> returnValue(None)
</span><span class="cx"> bindData, additionalBindData, metadataData = dbData
</span><span class="lines">@@ -3044,7 +3063,7 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- def create(cls, home, name, externalID=None):
</del><ins>+ def create(cls, home, name, bindUID=None):
</ins><span class="cx">
</span><span class="cx"> if (yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)):
</span><span class="cx"> raise HomeChildNameAlreadyExistsError(name)
</span><span class="lines">@@ -3059,7 +3078,7 @@
</span><span class="cx"> _created, _modified = (yield cls._insertHomeChildMetaData.on(home._txn, resourceID=resourceID))[0]
</span><span class="cx"> # Bind table needs entry
</span><span class="cx"> yield cls._bindInsertQuery.on(
</span><del>- home._txn, homeID=home._resourceID, resourceID=resourceID, externalID=externalID,
</del><ins>+ home._txn, homeID=home._resourceID, resourceID=resourceID, bindUID=bindUID,
</ins><span class="cx"> name=name, mode=_BIND_MODE_OWN, bindStatus=_BIND_STATUS_ACCEPTED,
</span><span class="cx"> message=None,
</span><span class="cx"> )
</span><span class="lines">@@ -3096,15 +3115,6 @@
</span><span class="cx"> return self._resourceID
</span><span class="cx">
</span><span class="cx">
</span><del>- def external_id(self):
- """
- Retrieve the external store identifier for this collection.
-
- @return: a string.
- """
- return self._externalID
-
-
</del><span class="cx"> def external(self):
</span><span class="cx"> """
</span><span class="cx"> Is this an external home.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -89,13 +89,13 @@
</span><span class="cx">
</span><span class="cx"> @memoizedKey("name", "_children")
</span><span class="cx"> @inlineCallbacks
</span><del>- def createChildWithName(self, name, externalID=None):
</del><ins>+ def createChildWithName(self, name, bindUID=None):
</ins><span class="cx"> """
</span><span class="cx"> No real children - only external ones.
</span><span class="cx"> """
</span><del>- if externalID is None:
</del><ins>+ if bindUID is None:
</ins><span class="cx"> raise AssertionError("CommonHomeExternal: not supported")
</span><del>- child = yield super(CommonHomeExternal, self).createChildWithName(name, externalID)
</del><ins>+ child = yield super(CommonHomeExternal, self).createChildWithName(name, bindUID)
</ins><span class="cx"> returnValue(child)
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -112,7 +112,7 @@
</span><span class="cx"> Remove an external child. Check that it is invalid or unused before calling this because if there
</span><span class="cx"> are valid references to it, removing will break things.
</span><span class="cx"> """
</span><del>- if child._externalID is None:
</del><ins>+ if child._bindUID is None:
</ins><span class="cx"> raise AssertionError("CommonHomeExternal: not supported")
</span><span class="cx"> yield super(CommonHomeExternal, self).removeChildWithName(child.name())
</span><span class="cx">
</span><span class="lines">@@ -186,11 +186,17 @@
</span><span class="cx"> raise AssertionError("CommonHomeExternal: not supported")
</span><span class="cx">
</span><span class="cx">
</span><del>-# def ownerHomeAndChildNameForChildID(self, resourceID):
-# """
-# No children.
-# """
-# raise AssertionError("CommonHomeExternal: not supported")
</del><ins>+ @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()]))
</ins><span class="cx">
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -225,8 +231,8 @@
</span><span class="cx">
</span><span class="cx"> @classmethod
</span><span class="cx"> @inlineCallbacks
</span><del>- 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)
</del><ins>+ 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)
</ins><span class="cx">
</span><span class="cx"> if mapping:
</span><span class="cx"> child = yield cls.deserialize(home, mapping)
</span><span class="lines">@@ -351,7 +357,13 @@
</span><span class="cx"> returnValue(results)
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @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()]))
</ins><span class="cx">
</span><ins>+
+
</ins><span class="cx"> class CommonObjectResourceExternal(CommonObjectResource):
</span><span class="cx"> """
</span><span class="cx"> A CommonObjectResource for a resource not hosted on this system, but on another pod. This will forward
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_schemacurrentoracledialectsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current-oracle-dialect.sql (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -99,11 +99,11 @@
</span><span class="cx"> create table CALENDAR_BIND (
</span><span class="cx"> "CALENDAR_HOME_RESOURCE_ID" integer not null references CALENDAR_HOME,
</span><span class="cx"> "CALENDAR_RESOURCE_ID" integer not null references CALENDAR on delete cascade,
</span><del>- "EXTERNAL_ID" integer default null,
</del><span class="cx"> "CALENDAR_RESOURCE_NAME" nvarchar2(255),
</span><span class="cx"> "BIND_MODE" integer not null,
</span><span class="cx"> "BIND_STATUS" integer not null,
</span><span class="cx"> "BIND_REVISION" integer default 0 not null,
</span><ins>+ "BIND_UID" nvarchar2(36) default null,
</ins><span class="cx"> "MESSAGE" nclob,
</span><span class="cx"> "TRANSP" integer default 0 not null,
</span><span class="cx"> "ALARM_VEVENT_TIMED" nclob default null,
</span><span class="lines">@@ -277,11 +277,11 @@
</span><span class="cx"> create table SHARED_ADDRESSBOOK_BIND (
</span><span class="cx"> "ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME,
</span><span class="cx"> "OWNER_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME on delete cascade,
</span><del>- "EXTERNAL_ID" integer default null,
</del><span class="cx"> "ADDRESSBOOK_RESOURCE_NAME" nvarchar2(255),
</span><span class="cx"> "BIND_MODE" integer not null,
</span><span class="cx"> "BIND_STATUS" integer not null,
</span><span class="cx"> "BIND_REVISION" integer default 0 not null,
</span><ins>+ "BIND_UID" nvarchar2(36) default null,
</ins><span class="cx"> "MESSAGE" nclob,
</span><span class="cx"> primary key ("ADDRESSBOOK_HOME_RESOURCE_ID", "OWNER_HOME_RESOURCE_ID"),
</span><span class="cx"> unique ("ADDRESSBOOK_HOME_RESOURCE_ID", "ADDRESSBOOK_RESOURCE_NAME")
</span><span class="lines">@@ -331,11 +331,11 @@
</span><span class="cx"> create table SHARED_GROUP_BIND (
</span><span class="cx"> "ADDRESSBOOK_HOME_RESOURCE_ID" integer not null references ADDRESSBOOK_HOME,
</span><span class="cx"> "GROUP_RESOURCE_ID" integer not null references ADDRESSBOOK_OBJECT on delete cascade,
</span><del>- "EXTERNAL_ID" integer default null,
</del><span class="cx"> "GROUP_ADDRESSBOOK_NAME" nvarchar2(255),
</span><span class="cx"> "BIND_MODE" integer not null,
</span><span class="cx"> "BIND_STATUS" integer not null,
</span><span class="cx"> "BIND_REVISION" integer default 0 not null,
</span><ins>+ "BIND_UID" nvarchar2(36) default null,
</ins><span class="cx"> "MESSAGE" nclob,
</span><span class="cx"> primary key ("ADDRESSBOOK_HOME_RESOURCE_ID", "GROUP_RESOURCE_ID"),
</span><span class="cx"> unique ("ADDRESSBOOK_HOME_RESOURCE_ID", "GROUP_ADDRESSBOOK_NAME")
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_schemacurrentsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/current.sql (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -190,11 +190,11 @@
</span><span class="cx"> create table CALENDAR_BIND (
</span><span class="cx"> CALENDAR_HOME_RESOURCE_ID integer not null references CALENDAR_HOME,
</span><span class="cx"> CALENDAR_RESOURCE_ID integer not null references CALENDAR on delete cascade,
</span><del>- EXTERNAL_ID integer default null,
</del><span class="cx"> CALENDAR_RESOURCE_NAME varchar(255) not null,
</span><span class="cx"> BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
</span><span class="cx"> BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
</span><span class="cx"> BIND_REVISION integer default 0 not null,
</span><ins>+ BIND_UID varchar(36) default null,
</ins><span class="cx"> MESSAGE text,
</span><span class="cx"> TRANSP integer default 0 not null, -- enum CALENDAR_TRANSP
</span><span class="cx"> ALARM_VEVENT_TIMED text default null,
</span><span class="lines">@@ -502,11 +502,11 @@
</span><span class="cx"> create table SHARED_ADDRESSBOOK_BIND (
</span><span class="cx"> ADDRESSBOOK_HOME_RESOURCE_ID integer not null references ADDRESSBOOK_HOME,
</span><span class="cx"> OWNER_HOME_RESOURCE_ID integer not null references ADDRESSBOOK_HOME on delete cascade,
</span><del>- EXTERNAL_ID integer default null,
</del><span class="cx"> ADDRESSBOOK_RESOURCE_NAME varchar(255) not null,
</span><span class="cx"> BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
</span><span class="cx"> BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
</span><span class="cx"> BIND_REVISION integer default 0 not null,
</span><ins>+ BIND_UID varchar(36) default null,
</ins><span class="cx"> MESSAGE text, -- FIXME: xml?
</span><span class="cx">
</span><span class="cx"> primary key (ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID), -- implicit index
</span><span class="lines">@@ -603,11 +603,11 @@
</span><span class="cx"> create table SHARED_GROUP_BIND (
</span><span class="cx"> ADDRESSBOOK_HOME_RESOURCE_ID integer not null references ADDRESSBOOK_HOME,
</span><span class="cx"> GROUP_RESOURCE_ID integer not null references ADDRESSBOOK_OBJECT on delete cascade,
</span><del>- EXTERNAL_ID integer default null,
</del><span class="cx"> GROUP_ADDRESSBOOK_NAME varchar(255) not null,
</span><span class="cx"> BIND_MODE integer not null, -- enum CALENDAR_BIND_MODE
</span><span class="cx"> BIND_STATUS integer not null, -- enum CALENDAR_BIND_STATUS
</span><span class="cx"> BIND_REVISION integer default 0 not null,
</span><ins>+ BIND_UID varchar(36) default null,
</ins><span class="cx"> MESSAGE text, -- FIXME: xml?
</span><span class="cx">
</span><span class="cx"> primary key (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_RESOURCE_ID), -- implicit index
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_51_to_52sql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_51_to_52.sql (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -21,6 +21,20 @@
</span><span class="cx"> -- New status value
</span><span class="cx"> insert into HOME_STATUS (DESCRIPTION, ID) values ('migrating', 3);
</span><span class="cx">
</span><ins>+-- 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);
+
+
</ins><span class="cx"> -- New table
</span><span class="cx"> create table CALENDAR_MIGRATION (
</span><span class="cx"> "CALENDAR_HOME_RESOURCE_ID" integer references CALENDAR_HOME on delete cascade,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_51_to_52sql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_51_to_52.sql (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -21,6 +21,20 @@
</span><span class="cx"> -- New status value
</span><span class="cx"> insert into HOME_STATUS values (3, 'migrating');
</span><span class="cx">
</span><ins>+-- 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;
+
+
</ins><span class="cx"> -- New table
</span><span class="cx"> create table CALENDAR_MIGRATION (
</span><span class="cx"> CALENDAR_HOME_RESOURCE_ID                integer references CALENDAR_HOME on delete cascade,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py (14475 => 14476)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -78,7 +78,7 @@
</span><span class="cx"> #
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def processExternalInvite(
</span><del>- self, ownerUID, ownerRID, ownerName, shareUID, bindMode, summary,
</del><ins>+ self, ownerUID, ownerName, shareUID, bindMode, bindUID, summary,
</ins><span class="cx"> copy_invite_properties, supported_components=None
</span><span class="cx"> ):
</span><span class="cx"> """
</span><span class="lines">@@ -93,42 +93,10 @@
</span><span class="cx"> raise ExternalShareFailed("Invalid owner UID: {}".format(ownerUID))
</span><span class="cx">
</span><span class="cx"> # Try to find owner calendar via its external id
</span><del>- ownerView = yield ownerHome.childWithExternalID(ownerRID)
</del><ins>+ ownerView = yield ownerHome.childWithBindUID(bindUID)
</ins><span class="cx"> if ownerView is None:
</span><del>- 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
- )
</del><ins>+ ownerView = yield ownerHome.createCollectionForExternalShare(ownerName, bindUID, supported_components)
</ins><span class="cx">
</span><del>- if (
- supported_components is not None and
- hasattr(ownerView, "setSupportedComponents")
- ):
- yield ownerView.setSupportedComponents(supported_components)
-
</del><span class="cx"> # Now carry out the share operation
</span><span class="cx"> if bindMode == _BIND_MODE_DIRECT:
</span><span class="cx"> shareeView = yield ownerView.directShareWithUser(
</span><span class="lines">@@ -143,7 +111,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><del>- def processExternalUninvite(self, ownerUID, ownerRID, shareUID):
</del><ins>+ def processExternalUninvite(self, ownerUID, bindUID, shareUID):
</ins><span class="cx"> """
</span><span class="cx"> External invite received.
</span><span class="cx"> """
</span><span class="lines">@@ -154,7 +122,7 @@
</span><span class="cx"> raise ExternalShareFailed("Invalid owner UID: {}".format(ownerUID))
</span><span class="cx">
</span><span class="cx"> # Try to find owner calendar via its external id
</span><del>- ownerView = yield ownerHome.childWithExternalID(ownerRID)
</del><ins>+ ownerView = yield ownerHome.childWithBindUID(bindUID)
</ins><span class="cx"> if ownerView is None:
</span><span class="cx"> raise ExternalShareFailed("Invalid share ID: {}".format(shareUID))
</span><span class="cx">
</span><span class="lines">@@ -200,7 +168,111 @@
</span><span class="cx"> yield shareeHome.declineShare(shareUID)
</span><span class="cx">
</span><span class="cx">
</span><ins>+ @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.
</ins><span class="cx">
</span><ins>+ @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]))
+
+
+
</ins><span class="cx"> SharingInvitation = namedtuple(
</span><span class="cx"> "SharingInvitation",
</span><span class="cx"> ["uid", "ownerUID", "ownerHomeID", "shareeUID", "shareeHomeID", "mode", "status", "summary"]
</span><span class="lines">@@ -223,10 +295,10 @@
</span><span class="cx"> return Insert({
</span><span class="cx"> bind.HOME_RESOURCE_ID: Parameter("homeID"),
</span><span class="cx"> bind.RESOURCE_ID: Parameter("resourceID"),
</span><del>- bind.EXTERNAL_ID: Parameter("externalID"),
</del><span class="cx"> bind.RESOURCE_NAME: Parameter("name"),
</span><span class="cx"> bind.BIND_MODE: Parameter("mode"),
</span><span class="cx"> bind.BIND_STATUS: Parameter("bindStatus"),
</span><ins>+ bind.BIND_UID: Parameter("bindUID"),
</ins><span class="cx"> bind.MESSAGE: Parameter("message"),
</span><span class="cx"> })
</span><span class="cx">
</span><span class="lines">@@ -309,13 +381,13 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @classproperty
</span><del>- def _bindForExternalIDAndHomeID(cls):
</del><ins>+ def _bindForBindUIDAndHomeID(cls):
</ins><span class="cx"> """
</span><span class="cx"> DAL query that looks up home bind rows by home child
</span><span class="cx"> resource ID and home resource ID.
</span><span class="cx"> """
</span><span class="cx"> bind = cls._bindSchema
</span><del>- return cls._bindFor((bind.EXTERNAL_ID == Parameter("externalID"))
</del><ins>+ return cls._bindFor((bind.BIND_UID == Parameter("bindUID"))
</ins><span class="cx"> .And(bind.HOME_RESOURCE_ID == Parameter("homeID")))
</span><span class="cx">
</span><span class="cx">
</span><span class="lines">@@ -580,15 +652,24 @@
</span><span class="cx"> @inlineCallbacks
</span><span class="cx"> def _sendExternalInvite(self, shareeView):
</span><span class="cx">
</span><ins>+ # 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
</ins><span class="cx"> yield self._txn.store().conduit.send_shareinvite(
</span><span class="cx"> self._txn,
</span><span class="cx"> shareeView.ownerHome()._homeType,
</span><span class="cx"> shareeView.ownerHome().uid(),
</span><del>- self.id(),
</del><span class="cx"> self.shareName(),
</span><span class="cx"> shareeView.viewerHome().uid(),
</span><span class="cx"> shareeView.shareUID(),
</span><span class="cx"> shareeView.shareMode(),
</span><ins>+ self.bindUID(),
</ins><span class="cx"> shareeView.shareMessage(),
</span><span class="cx"> self.getInviteCopyProperties(),
</span><span class="cx"> supported_components=self.getSupportedComponents() if hasattr(self, "getSupportedComponents") else None,
</span><span class="lines">@@ -602,7 +683,7 @@
</span><span class="cx"> self._txn,
</span><span class="cx"> shareeView.ownerHome()._homeType,
</span><span class="cx"> shareeView.ownerHome().uid(),
</span><del>- self.id(),
</del><ins>+ self.bindUID(),
</ins><span class="cx"> shareeView.viewerHome().uid(),
</span><span class="cx"> shareeView.shareUID(),
</span><span class="cx"> )
</span><span class="lines">@@ -723,10 +804,10 @@
</span><span class="cx"> subt,
</span><span class="cx"> homeID=shareeHome._resourceID,
</span><span class="cx"> resourceID=self._resourceID,
</span><del>- externalID=self._externalID,
</del><span class="cx"> name=newName,
</span><span class="cx"> mode=mode,
</span><span class="cx"> bindStatus=status,
</span><ins>+ bindUID=None,
</ins><span class="cx"> message=summary
</span><span class="cx"> )
</span><span class="cx"> returnValue(newName)
</span><span class="lines">@@ -939,6 +1020,28 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> @inlineCallbacks
</span><ins>+ 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
</ins><span class="cx"> def _initBindRevision(self):
</span><span class="cx"> yield self.syncToken() # init self._syncTokenRevision if None
</span><span class="cx"> self._bindRevision = self._syncTokenRevision
</span><span class="lines">@@ -1086,6 +1189,13 @@
</span><span class="cx"> return self._bindStatus
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def bindUID(self):
+ """
+ @see: L{ICalendar.bindUID}
+ """
+ return self._bindUID
+
+
</ins><span class="cx"> def accepted(self):
</span><span class="cx"> """
</span><span class="cx"> @see: L{ICalendar.shareStatus}
</span><span class="lines">@@ -1159,13 +1269,13 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> return (
</span><del>- cls._bindSchema.BIND_MODE,
</del><span class="cx"> cls._bindSchema.HOME_RESOURCE_ID,
</span><span class="cx"> cls._bindSchema.RESOURCE_ID,
</span><del>- cls._bindSchema.EXTERNAL_ID,
</del><span class="cx"> cls._bindSchema.RESOURCE_NAME,
</span><ins>+ cls._bindSchema.BIND_MODE,
</ins><span class="cx"> cls._bindSchema.BIND_STATUS,
</span><span class="cx"> cls._bindSchema.BIND_REVISION,
</span><ins>+ cls._bindSchema.BIND_UID,
</ins><span class="cx"> cls._bindSchema.MESSAGE
</span><span class="cx"> )
</span><span class="cx">
</span><span class="lines">@@ -1179,13 +1289,13 @@
</span><span class="cx"> """
</span><span class="cx">
</span><span class="cx"> return (
</span><del>- "_bindMode",
</del><span class="cx"> "_homeResourceID",
</span><span class="cx"> "_resourceID",
</span><del>- "_externalID",
</del><span class="cx"> "_name",
</span><ins>+ "_bindMode",
</ins><span class="cx"> "_bindStatus",
</span><span class="cx"> "_bindRevision",
</span><ins>+ "_bindUID",
</ins><span class="cx"> "_bindMessage",
</span><span class="cx"> )
</span><span class="cx">
</span><span class="lines">@@ -1251,4 +1361,4 @@
</span><span class="cx"> yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForHomeChildMetaData(self._resourceID))
</span><span class="cx"> yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithName(self._home._resourceID, self._name))
</span><span class="cx"> yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithResourceID(self._home._resourceID, self._resourceID))
</span><del>- yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithExternalID(self._home._resourceID, self._externalID))
</del><ins>+ yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithBindUID(self._home._resourceID, self._bindUID))
</ins></span></pre>
</div>
</div>
</body>
</html>