<!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 &quot;objectWithExternalID:%s:%s&quot; % (homeResourceID, externalID)
</del><ins>+    def keyForObjectWithBindUID(self, homeResourceID, bindUID):
+        return &quot;objectWithBindUID:%s:%s&quot; % (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">     &quot;CalendarObject&quot;,
</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)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.CALENDAR_HOME}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class CalendarMetaDataRecord(SerializableRecord, fromTable(schema.CALENDAR_METADATA)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.CALENDAR_METADATA}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class CalendarBindRecord(SerializableRecord, fromTable(schema.CALENDAR_BIND)):
+    &quot;&quot;&quot;
+    @DynamicAttrs
+    L{Record} for L{schema.CALENDAR_BIND}.
+    &quot;&quot;&quot;
+    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 = &quot;calendarHomeResourceID&quot;
+    _bindResourceIDAttributeName = &quot;calendarResourceID&quot;
+
</ins><span class="cx">     # Mapping of iCalendar property name to DB column name
</span><span class="cx">     _queryFields = {
</span><span class="cx">         &quot;UID&quot;: _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=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield self.commit()
+
+        shared_name = yield self._createShare()
+
+        shared = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        results = yield shared.sharingBindRecords()
+        self.assertEqual(len(results), 1)
+        self.assertEqual(results.keys(), [&quot;user02&quot;])
+        self.assertEqual(results[&quot;user02&quot;].calendarResourceName, shared_name)
+
+
+    @inlineCallbacks
+    def test_sharedToBindRecords(self):
+
+        yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield self.commit()
+
+        shared_name = yield self._createShare()
+
+        home = yield self.homeUnderTest(name=&quot;user02&quot;)
+        results = yield home.sharedToBindRecords()
+        self.assertEqual(len(results), 1)
+        self.assertEqual(results.keys(), [&quot;user01&quot;])
+        sharedRecord = results[&quot;user01&quot;][0]
+        ownerRecord = results[&quot;user01&quot;][1]
+        metadataRecord = results[&quot;user01&quot;][2]
+        self.assertEqual(ownerRecord.calendarResourceName, &quot;calendar&quot;)
+        self.assertEqual(sharedRecord.calendarResourceName, shared_name)
+        self.assertEqual(metadataRecord.supportedComponents, None)
+
+
+
</ins><span class="cx"> class GroupSharingTests(BaseSharingTests):
</span><span class="cx">     &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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 &quot;write&quot; 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):
+        &quot;&quot;&quot;
+        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.
+        &quot;&quot;&quot;
+
+        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):
+        &quot;&quot;&quot;
+        Get all the existing L{CalendarBindRecord}'s from the remote store.
+        &quot;&quot;&quot;
+
+        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):
+        &quot;&quot;&quot;
+        Create L{CalendarBindRecord} records in the local store.
+        &quot;&quot;&quot;
+
+        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):
+        &quot;&quot;&quot;
+        Sync all the collections shared to the migrating user from the remote store.
+        &quot;&quot;&quot;
+        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):
+        &quot;&quot;&quot;
+        Get the names and sharer UIDs for remote shared calendars.
+        &quot;&quot;&quot;
+
+        # 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):
+        &quot;&quot;&quot;
+        Create L{CalendarBindRecord} records in the local store.
+        &quot;&quot;&quot;
+
+        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(&quot;An existing share must be present&quot;)
+            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):
+    &quot;&quot;&quot;
+    Test that L{CrossPodHomeSync} sharing sync works.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def _createShare(self, shareFrom, shareTo):
+        # Invite
+        txnindex = 1 if shareFrom[0] == &quot;p&quot; else 0
+        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(txnindex), name=shareFrom, create=True)
+        calendar = yield home.childWithName(&quot;calendar&quot;)
+        shareeView = yield calendar.inviteUIDToShare(shareTo, _BIND_MODE_READ, &quot;summary&quot;)
+        inviteUID = shareeView.shareUID()
+        yield self.commitTransaction(txnindex)
+
+        # Accept
+        txnindex = 1 if shareTo[0] == &quot;p&quot; 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):
+        &quot;&quot;&quot;
+        Test that L{sharedCollectionsReconcile} copies over the full set of delegates and caches associated groups..
+        &quot;&quot;&quot;
+
+        # Create home
+        yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
+        yield self.commitTransaction(0)
+
+        # Shared by migrating user
+        shared_name_02 = yield self._createShare(&quot;user01&quot;, &quot;user02&quot;)
+        shared_name_03 = yield self._createShare(&quot;user01&quot;, &quot;puser03&quot;)
+
+        # Shared to migrating user
+        shared_name_04 = yield self._createShare(&quot;user04&quot;, &quot;user01&quot;)
+        shared_name_05 = yield self._createShare(&quot;puser05&quot;, &quot;user01&quot;)
+
+        # Sync from remote side
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;)
+        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(&quot;calendar&quot;)
+        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=&quot;puser03&quot;)
+        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">     &quot;&quot;&quot;
</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">     &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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">             &quot;action&quot;: &quot;shareinvite&quot;,
</span><span class="cx">             &quot;type&quot;: homeType,
</span><span class="cx">             &quot;owner&quot;: ownerUID,
</span><del>-            &quot;owner_id&quot;: ownerID,
</del><span class="cx">             &quot;owner_name&quot;: ownerName,
</span><span class="cx">             &quot;sharee&quot;: shareeUID,
</span><span class="cx">             &quot;share_id&quot;: shareUID,
</span><span class="cx">             &quot;mode&quot;: bindMode,
</span><ins>+            &quot;bind_uid&quot;: bindUID,
</ins><span class="cx">             &quot;summary&quot;: summary,
</span><span class="cx">             &quot;properties&quot;: 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[&quot;owner&quot;],
</span><del>-            request[&quot;owner_id&quot;],
</del><span class="cx">             request[&quot;owner_name&quot;],
</span><span class="cx">             request[&quot;share_id&quot;],
</span><span class="cx">             request[&quot;mode&quot;],
</span><ins>+            request[&quot;bind_uid&quot;],
</ins><span class="cx">             request[&quot;summary&quot;],
</span><span class="cx">             request[&quot;properties&quot;],
</span><span class="cx">             supported_components=request.get(&quot;supported-components&quot;)
</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">         &quot;&quot;&quot;
</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">             &quot;action&quot;: &quot;shareuninvite&quot;,
</span><span class="cx">             &quot;type&quot;: homeType,
</span><span class="cx">             &quot;owner&quot;: ownerUID,
</span><del>-            &quot;owner_id&quot;: ownerID,
</del><ins>+            &quot;bind_uid&quot;: bindUID,
</ins><span class="cx">             &quot;sharee&quot;: shareeUID,
</span><span class="cx">             &quot;share_id&quot;: 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[&quot;owner&quot;],
</span><del>-            request[&quot;owner_id&quot;],
</del><ins>+            request[&quot;bind_uid&quot;],
</ins><span class="cx">             request[&quot;share_id&quot;],
</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">         &quot;&quot;&quot;
</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):
+        &quot;&quot;&quot;
+        Convert the value to the external (JSON-based) representation.
+        &quot;&quot;&quot;
+        return dict([(k, v.serialize(),) for k, v in value.items()])
+
+
+    @staticmethod
+    def _to_serialize_dict_list_serialized_value(value):
+        &quot;&quot;&quot;
+        Convert the value to the external (JSON-based) representation.
+        &quot;&quot;&quot;
+        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, &quot;home_metadata&quot;, &quot;serialize&quot;, classMethod=False)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;home_get_all_group_attendees&quot;, &quot;getAllGroupAttendees&quot;, classMethod=False, transform_recv_result=StoreAPIConduitMixin._to_serialize_pair_list)
</span><ins>+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;home_shared_to_records&quot;, &quot;sharedToBindRecords&quot;, 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, &quot;homechild_listobjects&quot;, &quot;listObjects&quot;, classMethod=True)
</span><span class="lines">@@ -150,6 +167,7 @@
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_synctokenrevision&quot;, &quot;syncTokenRevision&quot;)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_resourcenamessincerevision&quot;, &quot;resourceNamesSinceRevision&quot;, transform_send_result=UtilityConduitMixin._to_tuple)
</span><span class="cx"> UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_search&quot;, &quot;search&quot;)
</span><ins>+UtilityConduitMixin._make_simple_action(StoreAPIConduitMixin, &quot;homechild_sharing_records&quot;, &quot;sharingBindRecords&quot;, 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, &quot;objectresource_loadallobjects&quot;, &quot;loadAllObjects&quot;, 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):
+        &quot;&quot;&quot;
+        Is this an normal (internal) home.
+
+        @return: a L{bool}.
+        &quot;&quot;&quot;
+        return self._status == _HOME_STATUS_NORMAL
+
+
</ins><span class="cx">     def external(self):
</span><span class="cx">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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(&quot;.&quot;):
</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">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</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):
-        &quot;&quot;&quot;
-        Retrieve the external store identifier for this collection.
-
-        @return: a string.
-        &quot;&quot;&quot;
-        return self._externalID
-
-
</del><span class="cx">     def external(self):
</span><span class="cx">         &quot;&quot;&quot;
</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(&quot;name&quot;, &quot;_children&quot;)
</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">         &quot;&quot;&quot;
</span><span class="cx">         No real children - only external ones.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        if externalID is None:
</del><ins>+        if bindUID is None:
</ins><span class="cx">             raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
</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">         &quot;&quot;&quot;
</span><del>-        if child._externalID is None:
</del><ins>+        if child._bindUID is None:
</ins><span class="cx">             raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
</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(&quot;CommonHomeExternal: not supported&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-#    def ownerHomeAndChildNameForChildID(self, resourceID):
-#        &quot;&quot;&quot;
-#        No children.
-#        &quot;&quot;&quot;
-#        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
</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">     &quot;&quot;&quot;
</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">     &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME,
</span><span class="cx">     &quot;CALENDAR_RESOURCE_ID&quot; integer not null references CALENDAR on delete cascade,
</span><del>-    &quot;EXTERNAL_ID&quot; integer default null,
</del><span class="cx">     &quot;CALENDAR_RESOURCE_NAME&quot; nvarchar2(255),
</span><span class="cx">     &quot;BIND_MODE&quot; integer not null,
</span><span class="cx">     &quot;BIND_STATUS&quot; integer not null,
</span><span class="cx">     &quot;BIND_REVISION&quot; integer default 0 not null,
</span><ins>+    &quot;BIND_UID&quot; nvarchar2(36) default null,
</ins><span class="cx">     &quot;MESSAGE&quot; nclob,
</span><span class="cx">     &quot;TRANSP&quot; integer default 0 not null,
</span><span class="cx">     &quot;ALARM_VEVENT_TIMED&quot; nclob default null,
</span><span class="lines">@@ -277,11 +277,11 @@
</span><span class="cx"> create table SHARED_ADDRESSBOOK_BIND (
</span><span class="cx">     &quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_HOME,
</span><span class="cx">     &quot;OWNER_HOME_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_HOME on delete cascade,
</span><del>-    &quot;EXTERNAL_ID&quot; integer default null,
</del><span class="cx">     &quot;ADDRESSBOOK_RESOURCE_NAME&quot; nvarchar2(255),
</span><span class="cx">     &quot;BIND_MODE&quot; integer not null,
</span><span class="cx">     &quot;BIND_STATUS&quot; integer not null,
</span><span class="cx">     &quot;BIND_REVISION&quot; integer default 0 not null,
</span><ins>+    &quot;BIND_UID&quot; nvarchar2(36) default null,
</ins><span class="cx">     &quot;MESSAGE&quot; nclob, 
</span><span class="cx">     primary key (&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;OWNER_HOME_RESOURCE_ID&quot;), 
</span><span class="cx">     unique (&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;ADDRESSBOOK_RESOURCE_NAME&quot;)
</span><span class="lines">@@ -331,11 +331,11 @@
</span><span class="cx"> create table SHARED_GROUP_BIND (
</span><span class="cx">     &quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_HOME,
</span><span class="cx">     &quot;GROUP_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_OBJECT on delete cascade,
</span><del>-    &quot;EXTERNAL_ID&quot; integer default null,
</del><span class="cx">     &quot;GROUP_ADDRESSBOOK_NAME&quot; nvarchar2(255),
</span><span class="cx">     &quot;BIND_MODE&quot; integer not null,
</span><span class="cx">     &quot;BIND_STATUS&quot; integer not null,
</span><span class="cx">     &quot;BIND_REVISION&quot; integer default 0 not null,
</span><ins>+    &quot;BIND_UID&quot; nvarchar2(36) default null,
</ins><span class="cx">     &quot;MESSAGE&quot; nclob, 
</span><span class="cx">     primary key (&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;GROUP_RESOURCE_ID&quot;), 
</span><span class="cx">     unique (&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;GROUP_ADDRESSBOOK_NAME&quot;)
</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 (&quot;BIND_UID&quot; nvarchar2(36) default null);
+
+alter table SHARED_ADDRESSBOOK_BIND
+        drop column EXTERNAL_ID
+        add (&quot;BIND_UID&quot; nvarchar2(36) default null);
+
+alter table SHARED_GROUP_BIND
+        drop column EXTERNAL_ID
+        add (&quot;BIND_UID&quot; nvarchar2(36) default null);
+
+
</ins><span class="cx"> -- New table
</span><span class="cx"> create table CALENDAR_MIGRATION (
</span><span class="cx">     &quot;CALENDAR_HOME_RESOURCE_ID&quot; 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">         &quot;&quot;&quot;
</span><span class="lines">@@ -93,42 +93,10 @@
</span><span class="cx">             raise ExternalShareFailed(&quot;Invalid owner UID: {}&quot;.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(
-                        &quot;External invite collection name is present with a &quot;
-                        &quot;different externalID and still has shares&quot;
-                    )
-                    raise
-                log.error(
-                    &quot;External invite collection name is present with a &quot;
-                    &quot;different externalID - trying to fix&quot;
-                )
-                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, &quot;setSupportedComponents&quot;)
-            ):
-                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">         &quot;&quot;&quot;
</span><span class="cx">         External invite received.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -154,7 +122,7 @@
</span><span class="cx">             raise ExternalShareFailed(&quot;Invalid owner UID: {}&quot;.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(&quot;Invalid share ID: {}&quot;.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):
+        &quot;&quot;&quot;
+        Create the L{CommonHomeChild} object that used as a &quot;stub&quot; 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}
+        &quot;&quot;&quot;
+        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(
+                    &quot;External invite collection name is present with a &quot;
+                    &quot;different bindUID and still has shares&quot;
+                )
+                raise
+            log.error(
+                &quot;External invite collection name is present with a &quot;
+                &quot;different bindUID - trying to fix&quot;
+            )
+            yield self.removeExternalChild(oldOwnerView)
+            ownerView = yield self.createChildWithName(
+                name, bindUID=bindUID
+            )
+
+        if (
+            supported_components is not None and
+            hasattr(ownerView, &quot;setSupportedComponents&quot;)
+        ):
+            yield ownerView.setSupportedComponents(supported_components)
+
+        returnValue(ownerView)
+
+
+    @inlineCallbacks
+    def sharedToBindRecords(self):
+        &quot;&quot;&quot;
+        Return an L{dict} that maps home/directory uid to a sharing bind record for collections shared to this user.
+        &quot;&quot;&quot;
+
+        # 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">     &quot;SharingInvitation&quot;,
</span><span class="cx">     [&quot;uid&quot;, &quot;ownerUID&quot;, &quot;ownerHomeID&quot;, &quot;shareeUID&quot;, &quot;shareeHomeID&quot;, &quot;mode&quot;, &quot;status&quot;, &quot;summary&quot;]
</span><span class="lines">@@ -223,10 +295,10 @@
</span><span class="cx">         return Insert({
</span><span class="cx">             bind.HOME_RESOURCE_ID: Parameter(&quot;homeID&quot;),
</span><span class="cx">             bind.RESOURCE_ID: Parameter(&quot;resourceID&quot;),
</span><del>-            bind.EXTERNAL_ID: Parameter(&quot;externalID&quot;),
</del><span class="cx">             bind.RESOURCE_NAME: Parameter(&quot;name&quot;),
</span><span class="cx">             bind.BIND_MODE: Parameter(&quot;mode&quot;),
</span><span class="cx">             bind.BIND_STATUS: Parameter(&quot;bindStatus&quot;),
</span><ins>+            bind.BIND_UID: Parameter(&quot;bindUID&quot;),
</ins><span class="cx">             bind.MESSAGE: Parameter(&quot;message&quot;),
</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">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</span><span class="cx">         bind = cls._bindSchema
</span><del>-        return cls._bindFor((bind.EXTERNAL_ID == Parameter(&quot;externalID&quot;))
</del><ins>+        return cls._bindFor((bind.BIND_UID == Parameter(&quot;bindUID&quot;))
</ins><span class="cx">                             .And(bind.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)))
</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, &quot;getSupportedComponents&quot;) 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):
+        &quot;&quot;&quot;
+        Return an L{dict} that maps home/directory uid to a sharing bind record.
+        &quot;&quot;&quot;
+        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):
+        &quot;&quot;&quot;
+        @see: L{ICalendar.bindUID}
+        &quot;&quot;&quot;
+        return self._bindUID
+
+
</ins><span class="cx">     def accepted(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @see: L{ICalendar.shareStatus}
</span><span class="lines">@@ -1159,13 +1269,13 @@
</span><span class="cx">         &quot;&quot;&quot;
</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">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         return (
</span><del>-            &quot;_bindMode&quot;,
</del><span class="cx">             &quot;_homeResourceID&quot;,
</span><span class="cx">             &quot;_resourceID&quot;,
</span><del>-            &quot;_externalID&quot;,
</del><span class="cx">             &quot;_name&quot;,
</span><ins>+            &quot;_bindMode&quot;,
</ins><span class="cx">             &quot;_bindStatus&quot;,
</span><span class="cx">             &quot;_bindRevision&quot;,
</span><ins>+            &quot;_bindUID&quot;,
</ins><span class="cx">             &quot;_bindMessage&quot;,
</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>