<!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>[12051] CalendarServer/branches/users/cdaboo/cross-pod-sharing/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/12051">12051</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2013-12-11 14:16:01 -0800 (Wed, 11 Dec 2013)</dd>
</dl>

<h3>Log Message</h3>
<pre>Check point. Implemented &quot;external&quot; classes that can proxy various apis through the conduit to get results
from another pod.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretestcommonpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_utilpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretestutilpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingconduitpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingrequestpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingresourcepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_conduitpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_external_homepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_resourcepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoretestutilpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoreupgradetesttest_migratepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/upgrade/test/test_migrate.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommonicommondatastorepy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/icommondatastore.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtestutilpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -75,7 +75,7 @@
</span><span class="cx">     InvalidAttachmentOperation, DuplicatePrivateCommentsError
</span><span class="cx"> from txdav.caldav.icalendarstore import QuotaExceeded
</span><span class="cx"> from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
</span><del>-    CommonObjectResource, ECALENDARTYPE, CommonHomeExternal
</del><ins>+    CommonObjectResource, ECALENDARTYPE
</ins><span class="cx"> from txdav.common.datastore.sql_legacy import PostgresLegacyIndexEmulator, \
</span><span class="cx">     PostgresLegacyInboxIndexEmulator
</span><span class="cx"> from txdav.common.datastore.sql_tables import _ATTACHMENTS_MODE_NONE, \
</span><span class="lines">@@ -431,13 +431,6 @@
</span><span class="cx">         &quot;VPOLL&quot;: &quot;_default_polls&quot;,
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    def __init__(self, transaction, ownerUID):
-
-        self._externalClass = CalendarHomeExternal
-        self._childClass = Calendar
-        super(CalendarHome, self).__init__(transaction, ownerUID)
-
-
</del><span class="cx">     @classmethod
</span><span class="cx">     def metadataColumns(cls):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -941,127 +934,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class CalendarHomeExternal(CommonHomeExternal, CalendarHome):
-
-    def __init__(self, transaction, ownerUID, resourceID):
-
-        CalendarHome.__init__(self, transaction, ownerUID)
-        CommonHomeExternal.__init__(self, transaction, ownerUID, resourceID)
-
-
-    def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, mode):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def getCalendarResourcesForUID(self, uid):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def calendarObjectWithDropboxID(self, dropboxID):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def getAllDropboxIDs(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def getAllAttachmentNames(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def getAllManagedIDs(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def createdHome(self):
-        &quot;&quot;&quot;
-        No children - make this a no-op.
-        &quot;&quot;&quot;
-        return succeed(None)
-
-
-    def splitCalendars(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def ensureDefaultCalendarsExist(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def setDefaultCalendar(self, calendar, componentType):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def defaultCalendar(self, componentType, create=True):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def isDefaultCalendar(self, calendar):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def getDefaultAlarm(self, vevent, timed):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def setDefaultAlarm(self, alarm, vevent, timed):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def getAvailability(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def setAvailability(self, availability):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-
</del><span class="cx"> class Calendar(CommonHomeChild):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     SQL-based implementation of L{ICalendar}.
</span><span class="lines">@@ -1662,24 +1534,7 @@
</span><span class="cx">         self._cachedComponent = None
</span><span class="cx">         self._cachedCommponentPerUser = {}
</span><span class="cx"> 
</span><del>-    _allColumns = [
-        _objectSchema.RESOURCE_ID,
-        _objectSchema.RESOURCE_NAME,
-        _objectSchema.UID,
-        _objectSchema.MD5,
-        Len(_objectSchema.TEXT),
-        _objectSchema.ATTACHMENTS_MODE,
-        _objectSchema.DROPBOX_ID,
-        _objectSchema.ACCESS,
-        _objectSchema.SCHEDULE_OBJECT,
-        _objectSchema.SCHEDULE_TAG,
-        _objectSchema.SCHEDULE_ETAGS,
-        _objectSchema.PRIVATE_COMMENTS,
-        _objectSchema.CREATED,
-        _objectSchema.MODIFIED
-    ]
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _createInternal(cls, parent, name, component, internal_state, options=None, split_details=None):
</span><span class="lines">@@ -1691,7 +1546,8 @@
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise ObjectResourceNameNotAllowedError(name)
</span><span class="cx"> 
</span><del>-        objectResource = cls(parent, name, None, None, options=options)
</del><ins>+        c = cls._externalClass if parent.external() else cls
+        objectResource = c(parent, name, None, None, options=options)
</ins><span class="cx">         yield objectResource._setComponentInternal(component, inserting=True, internal_state=internal_state, split_details=split_details)
</span><span class="cx">         yield objectResource._loadPropertyStore(created=True)
</span><span class="cx"> 
</span><span class="lines">@@ -1701,27 +1557,51 @@
</span><span class="cx">         returnValue(objectResource)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _initFromRow(self, row):
</del><ins>+    @classmethod
+    def _allColumns(cls): #@NoSelf
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Given a select result using the columns from L{_allColumns}, initialize
-        the calendar object resource state.
</del><ins>+        Full set of columns in the object table that need to be loaded to
+        initialize the object resource state.
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        (self._resourceID,
-         self._name,
-         self._uid,
-         self._md5,
-         self._size,
-         self._attachment,
-         self._dropboxID,
-         self._access,
-         self._schedule_object,
-         self._schedule_tag,
-         self._schedule_etags,
-         self._private_comments,
-         self._created,
-         self._modified,) = tuple(row)
</del><ins>+        obj = cls._objectSchema
+        return [
+            obj.RESOURCE_ID,
+            obj.RESOURCE_NAME,
+            obj.UID,
+            obj.MD5,
+            Len(obj.TEXT),
+            obj.ATTACHMENTS_MODE,
+            obj.DROPBOX_ID,
+            obj.ACCESS,
+            obj.SCHEDULE_OBJECT,
+            obj.SCHEDULE_TAG,
+            obj.SCHEDULE_ETAGS,
+            obj.PRIVATE_COMMENTS,
+            obj.CREATED,
+            obj.MODIFIED
+        ]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
+    def _rowAttributes(cls): #@NoSelf
+        return (
+            &quot;_resourceID&quot;,
+            &quot;_name&quot;,
+            &quot;_uid&quot;,
+            &quot;_md5&quot;,
+            &quot;_size&quot;,
+            &quot;_attachment&quot;,
+            &quot;_dropboxID&quot;,
+            &quot;_access&quot;,
+            &quot;_schedule_object&quot;,
+            &quot;_schedule_tag&quot;,
+            &quot;_schedule_etags&quot;,
+            &quot;_private_comments&quot;,
+            &quot;_created&quot;,
+            &quot;_modified&quot;,
+         )
+
+
</ins><span class="cx">     @property
</span><span class="cx">     def _calendar(self):
</span><span class="cx">         return self._parentCollection
</span><span class="lines">@@ -4499,4 +4379,10 @@
</span><span class="cx"> 
</span><span class="cx">         returnValue(location)
</span><span class="cx"> 
</span><ins>+# Hook-up class relationships at the end after they have all been defined
+from txdav.caldav.datastore.sql_external import CalendarHomeExternal, CalendarExternal, CalendarObjectExternal
+CalendarHome._externalClass = CalendarHomeExternal
+CalendarHome._childClass = Calendar
+Calendar._externalClass = CalendarExternal
</ins><span class="cx"> Calendar._objectResourceClass = CalendarObject
</span><ins>+CalendarObject._externalClass = CalendarObjectExternal
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py (0 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -0,0 +1,167 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+&quot;&quot;&quot;
+SQL backend for CalDAV storage when resources are external.
+&quot;&quot;&quot;
+
+from twisted.internet.defer import succeed
+
+from twext.python.log import Logger
+
+from txdav.caldav.datastore.sql import CalendarHome, Calendar, CalendarObject
+from txdav.common.datastore.sql_external import CommonHomeExternal, CommonHomeChildExternal, \
+    CommonObjectResourceExternal
+
+log = Logger()
+
+class CalendarHomeExternal(CommonHomeExternal, CalendarHome):
+    &quot;&quot;&quot;
+    Wrapper for a CalendarHome that is external and only supports a limited set of operations.
+    &quot;&quot;&quot;
+
+    def __init__(self, transaction, ownerUID, resourceID):
+
+        CalendarHome.__init__(self, transaction, ownerUID)
+        CommonHomeExternal.__init__(self, transaction, ownerUID, resourceID)
+
+
+    def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, mode):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getCalendarResourcesForUID(self, uid):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def calendarObjectWithDropboxID(self, dropboxID):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAllDropboxIDs(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAllAttachmentNames(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAllManagedIDs(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def createdHome(self):
+        &quot;&quot;&quot;
+        No children - make this a no-op.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def splitCalendars(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def ensureDefaultCalendarsExist(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def setDefaultCalendar(self, calendar, componentType):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def defaultCalendar(self, componentType, create=True):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def isDefaultCalendar(self, calendar):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getDefaultAlarm(self, vevent, timed):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def setDefaultAlarm(self, alarm, vevent, timed):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAvailability(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def setAvailability(self, availability):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+
+class CalendarExternal(CommonHomeChildExternal, Calendar):
+    &quot;&quot;&quot;
+    SQL-based implementation of L{ICalendar}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class CalendarObjectExternal(CommonObjectResourceExternal, CalendarObject):
+    &quot;&quot;&quot;
+    SQL-based implementation of L{ICalendar}.
+    &quot;&quot;&quot;
+    pass
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretestcommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/common.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -1703,7 +1703,7 @@
</span><span class="cx">         L{ICalendarStore.withEachCalendarHomeDo} executes its C{action}
</span><span class="cx">         argument repeatedly with all homes that have been created.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        additionalUIDs = set('alpha-uid home2 home3 beta-uid'.split())
</del><ins>+        additionalUIDs = set('user01 home2 home3 uid1'.split())
</ins><span class="cx">         txn = self.transactionUnderTest()
</span><span class="cx">         for name in additionalUIDs:
</span><span class="cx">             yield txn.calendarHomeWithUID(name, create=True)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -1542,8 +1542,8 @@
</span><span class="cx"> 
</span><span class="cx">         # Tests on inbox - resources with properties
</span><span class="cx">         txn = self.transactionUnderTest()
</span><del>-        yield txn.homeWithUID(ECALENDARTYPE, &quot;byNameTest&quot;, create=True)
-        inbox = yield self.calendarUnderTest(txn=txn, name=&quot;inbox&quot;, home=&quot;byNameTest&quot;)
</del><ins>+        yield txn.homeWithUID(ECALENDARTYPE, &quot;user01&quot;, create=True)
+        inbox = yield self.calendarUnderTest(txn=txn, name=&quot;inbox&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         caldata = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="lines">@@ -1574,7 +1574,7 @@
</span><span class="cx">         yield _createInboxItem(&quot;4.ics&quot;, &quot;p4&quot;)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        inbox = yield self.calendarUnderTest(name=&quot;inbox&quot;, home=&quot;byNameTest&quot;)
</del><ins>+        inbox = yield self.calendarUnderTest(name=&quot;inbox&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         yield _tests(inbox)
</span><span class="cx"> 
</span><span class="cx">         resources = yield inbox.objectResourcesWithNames((&quot;1.ics&quot;,))
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_utilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_util.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_util.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_util.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -323,7 +323,12 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(HomeMigrationTests, self).setUp()
</span><del>-        self.theStore = yield buildCalendarStore(self, self.notifierFactory, homes=(&quot;conflict1&quot;, &quot;conflict2&quot;,))
</del><ins>+        self.theStore = yield buildCalendarStore(self, self.notifierFactory, homes=(
+            &quot;conflict1&quot;,
+            &quot;conflict2&quot;,
+            &quot;empty_home&quot;,
+            &quot;non_empty_home&quot;,
+        ))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def storeUnderTest(self):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/util.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/util.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/util.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -62,16 +62,12 @@
</span><span class="cx">         calendarUserAddresses,
</span><span class="cx">         cutype=&quot;INDIVIDUAL&quot;,
</span><span class="cx">         thisServer=True,
</span><ins>+        server=None,
</ins><span class="cx">     ):
</span><span class="cx"> 
</span><del>-        super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames, fullName)
-        self.uid = uid
-        self.shortNames = shortNames
-        self.fullName = fullName
-        self.displayName = self.fullName if self.fullName else self.shortNames[0]
</del><ins>+        super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames, fullName, thisServer, server)
</ins><span class="cx">         self.calendarUserAddresses = calendarUserAddresses
</span><span class="cx">         self.cutype = cutype
</span><del>-        self._thisServer = thisServer
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def canonicalCalendarUserAddress(self):
</span><span class="lines">@@ -90,10 +86,6 @@
</span><span class="cx">         return cua
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def thisServer(self):
-        return self._thisServer
-
-
</del><span class="cx">     def calendarsEnabled(self):
</span><span class="cx">         return True
</span><span class="cx"> 
</span><span class="lines">@@ -148,12 +140,19 @@
</span><span class="cx">     homes.update((
</span><span class="cx">         &quot;home1&quot;,
</span><span class="cx">         &quot;home2&quot;,
</span><del>-        &quot;Home_attachments&quot;,
</del><ins>+        &quot;home3&quot;,
+        &quot;home_attachments&quot;,
</ins><span class="cx">         &quot;home_bad&quot;,
</span><span class="cx">         &quot;home_defaults&quot;,
</span><span class="cx">         &quot;home_no_splits&quot;,
</span><ins>+        &quot;home_provision1&quot;,
+        &quot;home_provision2&quot;,
</ins><span class="cx">         &quot;home_splits&quot;,
</span><span class="cx">         &quot;home_splits_shared&quot;,
</span><ins>+        &quot;uid1&quot;,
+        &quot;uid2&quot;,
+        &quot;new-home&quot;,
+        &quot;xyzzy&quot;,
</ins><span class="cx">     ))
</span><span class="cx">     for uid in homes:
</span><span class="cx">         directory.addRecord(buildDirectoryRecord(uid))
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -14,7 +14,6 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> # #
</span><del>-from txdav.xml import element
</del><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> SQL backend for CardDAV storage.
</span><span class="lines">@@ -62,6 +61,7 @@
</span><span class="cx">     InvalidUIDError, UIDExistsError, ObjectResourceTooBigError, \
</span><span class="cx">     InvalidObjectResourceError, InvalidComponentForStoreError, \
</span><span class="cx">     AllRetriesFailed, ObjectResourceNameAlreadyExistsError
</span><ins>+from txdav.xml import element
</ins><span class="cx"> 
</span><span class="cx"> from zope.interface.declarations import implements
</span><span class="cx"> 
</span><span class="lines">@@ -86,7 +86,6 @@
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, transaction, ownerUID):
</span><span class="cx"> 
</span><del>-        self._childClass = AddressBook
</del><span class="cx">         super(AddressBookHome, self).__init__(transaction, ownerUID)
</span><span class="cx">         self._addressbookPropertyStoreID = None
</span><span class="cx">         self._addressbook = None
</span><span class="lines">@@ -441,9 +440,81 @@
</span><span class="cx">     _objectSchema = schema.ADDRESSBOOK_OBJECT
</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):
</del><ins>+    @classmethod
+    @inlineCallbacks
+    def _getDBDataIndirect(cls, home, name, resourceID, externalID):
+
+        # Get the bind row data
+        row = None
+
+        # TODO: query cacher
+
+        rows = None
+        ownerHome = None
+
+        # TODO: add queryCacher support
+
+        if rows is None:
+            # No cached copy
+            if name:
+                ownerHome = yield home._txn.addressbookHomeWithUID(name)
+                if ownerHome is None:
+                    returnValue(None)
+                resourceID = ownerHome.addressbook()._resourceID
+            rows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
+                home._txn, homeID=home._resourceID, addressbookID=resourceID
+            )
+
+        if not rows:
+            returnValue(None)
+
+        groupID = None
+        overallBindStatus = _BIND_STATUS_INVITED
+        minBindRevision = None
+        for row in rows:
+            bindMode, homeID, resourceGroupID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
+            if groupID is None:
+                groupID = resourceGroupID
+            minBindRevision = min(minBindRevision, bindRevision) if minBindRevision is not None else bindRevision
+            if bindStatus == _BIND_STATUS_ACCEPTED:
+                overallBindStatus = _BIND_STATUS_ACCEPTED
+
+        if ownerHome is None:
+            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, groupID)
+            ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
+
+        bindData = row[:cls.bindColumnCount]
+        additionalBindData = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+
+        # Adjust for aggregate values
+        bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)] = resourceID
+        bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] = ownerHome.uid()
+        bindData[cls.bindColumns().index(cls._bindSchema.BIND_MODE)] = _BIND_MODE_INDIRECT
+        bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)] = overallBindStatus
+        bindData[cls.bindColumns().index(cls._bindSchema.BIND_REVISION)] = minBindRevision
+        bindData[cls.bindColumns().index(cls._bindSchema.MESSAGE)] = &quot;&quot;
+
+        # Get the matching metadata data
+        metadataData = None
+        queryCacher = home._txn._queryCacher
+        if queryCacher:
+            # Retrieve from cache
+            cacheKey = queryCacher.keyForHomeChildMetaData(resourceID)
+            metadataData = yield queryCacher.get(cacheKey)
+
+        if metadataData is None:
+            # No cached copy
+            metadataData = (yield cls._metadataByIDQuery.on(home._txn, resourceID=resourceID))[0]
+            if queryCacher:
+                # Cache the results
+                yield queryCacher.setAfterCommit(home._txn, cacheKey, metadataData)
+
+        returnValue((bindData, additionalBindData, metadataData, ownerHome,))
+
+
+    def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=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)
</del><ins>+        super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName, externalID=externalID)
</ins><span class="cx">         self._index = PostgresLegacyABIndexEmulator(self)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -505,7 +576,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def create(cls, home, name):
</del><ins>+    def create(cls, home, name, externalID=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">@@ -767,32 +838,17 @@
</span><span class="cx"> 
</span><span class="cx">             # Create the actual objects merging in properties
</span><span class="cx">             for ownerHome, dataRow in ownerHomeToDataRowMap.iteritems():
</span><del>-                bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
-                additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
-                metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
</del><ins>+                bindData = dataRow[:cls.bindColumnCount]
+                additionalBindData = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+                metadataData = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
+                propstore = propertyStores.get(ownerHome._addressbookPropertyStoreID, None)
</ins><span class="cx"> 
</span><del>-                child = cls(
-                    home=home,
-                    name=ownerHome.uid(),
-                    resourceID=ownerHome._resourceID,
-                    mode=bindMode,
-                    status=bindStatus,
-                    revision=bindRevision,
-                    message=bindMessage,
-                    ownerHome=ownerHome,
-                )
</del><ins>+                # Some adjustments for addressbook share model
+                bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)] = ownerHome._resourceID
+                bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] = ownerHome.uid()
</ins><span class="cx"> 
</span><del>-                for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
-                    setattr(child, attr, value)
-                for attr, value in zip(cls.metadataAttributes(), metadata):
-                    setattr(child, attr, value)
</del><ins>+                child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, propstore, ownerHome)
</ins><span class="cx">                 child._syncTokenRevision = revisions[child._resourceID]
</span><del>-                propstore = propertyStores.get(ownerHome._addressbookPropertyStoreID, None)
-                # We have to re-adjust the property store object to account for possible shared
-                # collections as previously we loaded them all as if they were owned
-                if propstore:
-                    propstore._setDefaultUserUID(ownerHome.uid())
-                yield child._loadPropertyStore(propstore)
</del><span class="cx">                 results.append(child)
</span><span class="cx"> 
</span><span class="cx">         returnValue(results)
</span><span class="lines">@@ -855,7 +911,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, accepted=True):
</del><ins>+    def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, externalID=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">@@ -867,56 +923,17 @@
</span><span class="cx">         @return: an L{CommonHomeChild} or C{None} if no such child
</span><span class="cx">             exists.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        rows = None
-        ownerHome = None
</del><span class="cx"> 
</span><del>-        # TODO: add queryCacher support
-
-        if rows is None:
-            # No cached copy
-            if name:
-                ownerHome = yield home._txn.addressbookHomeWithUID(name)
-                if ownerHome is None:
-                    returnValue(None)
-                resourceID = ownerHome.addressbook()._resourceID
-            rows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
-                home._txn, homeID=home._resourceID, addressbookID=resourceID
-            )
-
-        if not rows:
</del><ins>+        dbData = yield cls._getDBDataIndirect(home, name, resourceID, externalID)
+        if dbData is None:
</ins><span class="cx">             returnValue(None)
</span><ins>+        bindData, additionalBindData, metadataData, ownerHome = dbData
</ins><span class="cx"> 
</span><del>-        groupID = None
-        overallBindStatus = _BIND_STATUS_INVITED
-        minBindRevision = None
-        for row in rows:
-            bindMode, homeID, resourceGroupID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
-            if groupID is None:
-                groupID = resourceGroupID
-            minBindRevision = min(minBindRevision, bindRevision) if minBindRevision is not None else bindRevision
-            if bindStatus == _BIND_STATUS_ACCEPTED:
-                overallBindStatus = _BIND_STATUS_ACCEPTED
-
-        if accepted is not None and (overallBindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</del><ins>+        bindStatus = bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)]
+        if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</ins><span class="cx">             returnValue(None)
</span><del>-        additionalBind = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</del><span class="cx"> 
</span><del>-        if ownerHome is None:
-            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, groupID)
-            ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
-
-        child = cls(
-            home=home,
-            name=ownerHome.uid(),
-            resourceID=resourceID,
-            mode=_BIND_MODE_INDIRECT,
-            status=overallBindStatus,
-            revision=minBindRevision,
-            message=&quot;&quot;,
-            ownerHome=ownerHome,
-            ownerName=ownerHome.uid()
-        )
-        yield child.initFromStore(additionalBind)
</del><ins>+        child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, None, ownerHome)
</ins><span class="cx">         returnValue(child)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1142,7 +1159,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def shareWith(self, shareeHome, mode, status=None, summary=None):
</del><ins>+    def shareWith(self, shareeHome, mode, status=None, summary=None, shareName=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Share this (owned) L{AddressBookObject} with another home.
</span><span class="cx"> 
</span><span class="lines">@@ -1170,11 +1187,12 @@
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def doInsert(subt):
</span><del>-            newName = self.newShareName()
</del><ins>+            newName = shareName if shareName is not None else self.newShareName()
</ins><span class="cx">             yield self._bindInsertQuery.on(
</span><span class="cx">                 subt,
</span><span class="cx">                 homeID=shareeHome._resourceID,
</span><span class="cx">                 resourceID=self._resourceID,
</span><ins>+                externalID=None,
</ins><span class="cx">                 name=newName,
</span><span class="cx">                 mode=mode,
</span><span class="cx">                 bindStatus=status,
</span><span class="lines">@@ -1210,14 +1228,14 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def createShare(self, shareeUID, mode, summary=None):
</del><ins>+    def createShare(self, shareeUID, mode, summary=None, shareName=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a new shared resource. If the mode is direct, the share is created in accepted state,
</span><span class="cx">         otherwise the share is created in invited state.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if self._kind == _ABO_KIND_GROUP:
</span><del>-            shareeView = yield super(AddressBookObjectSharingMixIn, self).createShare(shareeUID, mode, summary)
</del><ins>+            shareeView = yield super(AddressBookObjectSharingMixIn, self).createShare(shareeUID, mode, summary, shareName)
</ins><span class="cx">             returnValue(shareeView)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -1430,6 +1448,130 @@
</span><span class="cx">     # _homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
+    @inlineCallbacks
+    def makeClass(cls, parent, objectData, groupBindData=None, propstore=None):
+        &quot;&quot;&quot;
+        Given the various database rows, build the actual class.
+
+        @param parent: the parent collection object
+        @type parent: L{AddressBook}
+        @param objectData: the standard set of object columns
+        @type objectData: C{list}
+        @param groupBindData: additional group bind data
+        @type groupBindData: C{list}
+        @param propstore: a property store to use, or C{None} to load it automatically
+        @type propstore: L{PropertyStore}
+
+        @return: the constructed child class
+        @rtype: L{CommonHomeChild}
+        &quot;&quot;&quot;
+
+        c = cls._externalClass if parent.external() else cls
+        child = c(
+            parent,
+            objectData[cls._allColumns().index(cls._objectSchema.RESOURCE_NAME)],
+            objectData[cls._allColumns().index(cls._objectSchema.UID)],
+        )
+
+        for attr, value in zip(child._rowAttributes(), objectData):
+            setattr(child, attr, value)
+
+        yield child._loadPropertyStore(propstore)
+
+        if groupBindData:
+            bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindData[:AddressBookObject.bindColumnCount] #@UnusedVariable
+            child._bindMode = bindMode
+            child._bindStatus = bindStatus
+            child._bindMessage = bindMessage
+            child._bindName = bindName
+        else:
+            invites = yield child.sharingInvites()
+            if len(invites):
+                child._bindMessage = &quot;shared&quot;
+
+        returnValue(child)
+
+
+    @classmethod
+    @inlineCallbacks
+    def _getDBData(cls, parent, name, uid, resourceID):
+        &quot;&quot;&quot;
+        Given a set of identifying information, load the data rows for the object. Only one of
+        L{name}, L{uid} or L{resourceID} is specified - others are C{None}.
+
+        @param parent: the parent collection object
+        @type parent: L{AddressBook}
+        @param name: the resource name
+        @type name: C{str}
+        @param uid: the UID of the data
+        @type uid: C{str}
+        @param resourceID: the resource ID
+        @type resourceID: C{int}
+        &quot;&quot;&quot;
+
+        row = None
+        groupBindRow = None
+
+        if parent.owned() or parent.fullyShared():  # owned or fully shared
+            row = yield super(AddressBookObject, cls)._getDBData(parent, name, uid, resourceID)
+
+            # Might be special group
+            if row is None and parent.fullyShared():
+                if name:
+                    if name == parent._groupForSharedAddressBookName():
+                        row = parent._groupForSharedAddressBookRow()
+                elif uid:
+                    if uid == (yield parent._groupForSharedAddressBookUID()):
+                        row = parent._groupForSharedAddressBookRow()
+                elif resourceID:
+                    if resourceID == parent.id():
+                        rows = parent._groupForSharedAddressBookRow()
+
+        else:
+            acceptedGroupIDs = yield parent.acceptedGroupIDs()
+            allowedObjectIDs = yield parent.expandGroupIDs(parent._txn, acceptedGroupIDs)
+            rows = None
+            if name:
+                if allowedObjectIDs:
+                    rows = (yield cls._allColumnsWithResourceIDsAndName(allowedObjectIDs).on(
+                        parent._txn,
+                        name=name,
+                        resourceIDs=allowedObjectIDs,
+                    ))
+            elif uid:
+                if allowedObjectIDs:
+                    rows = (yield cls._allColumnsWithResourceIDsAndUID(allowedObjectIDs).on(
+                        parent._txn,
+                        uid=uid,
+                        resourceIDs=allowedObjectIDs,
+                    ))
+            elif resourceID:
+                if resourceID not in allowedObjectIDs:
+                    # allow invited groups
+                    allowedObjectIDs = yield parent.unacceptedGroupIDs()
+                if resourceID in allowedObjectIDs:
+                    rows = (yield cls._allColumnsWithResourceID.on(
+                        parent._txn,
+                        resourceID=resourceID,
+                    ))
+            if rows:
+                row = rows[0]
+
+        if row is not None:
+            if row[cls._allColumns().index(cls._objectSchema.KIND)] == _ABO_KIND_GROUP:
+
+                resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+                groupBindRows = yield AddressBookObject._bindForResourceIDAndHomeID.on(
+                    parent._txn, resourceID=resourceID, homeID=parent._home._resourceID
+                )
+
+                if groupBindRows:
+                    groupBindRow = groupBindRows[0]
+
+        returnValue((row, groupBindRow,))
+
+
</ins><span class="cx">     def __init__(self, addressbook, name, uid, resourceID=None, options=None):
</span><span class="cx"> 
</span><span class="cx">         self._kind = None
</span><span class="lines">@@ -1443,6 +1585,7 @@
</span><span class="cx">         self._bindMessage = None
</span><span class="cx">         self._bindName = None
</span><span class="cx">         super(AddressBookObject, self).__init__(addressbook, name, uid, resourceID, options)
</span><ins>+        self._externalID = 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">@@ -1467,6 +1610,15 @@
</span><span class="cx">         return self._resourceID == self.addressbook()._resourceID
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def external(self):
+        &quot;&quot;&quot;
+        Is this an external object.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return self.addressbook().external()
+
+
</ins><span class="cx">     @classmethod
</span><span class="cx">     def _deleteMembersWithMemberIDAndGroupIDsQuery(cls, memberID, groupIDs):
</span><span class="cx">         aboMembers = schema.ABO_MEMBERS
</span><span class="lines">@@ -1553,7 +1705,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         obj = cls._objectSchema
</span><span class="cx">         return Select(
</span><del>-            cls._allColumns, From=obj,
</del><ins>+            cls._allColumns(), From=obj,
</ins><span class="cx">             Where=(column == Parameter(paramName)).And(
</span><span class="cx">                 obj.RESOURCE_ID.In(Parameter(&quot;resourceIDs&quot;, len(resourceIDs)))),
</span><span class="cx">         )
</span><span class="lines">@@ -1573,7 +1725,7 @@
</span><span class="cx">     def _allColumnsWithResourceID(cls): #@NoSelf
</span><span class="cx">         obj = cls._objectSchema
</span><span class="cx">         return Select(
</span><del>-            cls._allColumns, From=obj,
</del><ins>+            cls._allColumns(), From=obj,
</ins><span class="cx">             Where=obj.RESOURCE_ID == Parameter(&quot;resourceID&quot;),)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1609,92 +1761,20 @@
</span><span class="cx">         returnValue(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def initFromStore(self):
-        &quot;&quot;&quot;
-        Initialise this object from the store. We read in and cache all the
-        extra metadata from the DB to avoid having to do DB queries for those
-        individually later. Either the name or uid is present, so we have to
-        tweak the query accordingly.
</del><ins>+    def _objectWithNameOrID(cls, parent, name, uid, resourceID):
</ins><span class="cx"> 
</span><del>-        @return: L{self} if object exists in the DB, else C{None}
-        &quot;&quot;&quot;
-        abo = None
-        if self.owned() or self.addressbook().fullyShared():  # owned or fully shared
-            abo = yield super(AddressBookObject, self).initFromStore()
</del><ins>+        row, groupBindRow = yield cls._getDBData(parent, name, uid, resourceID)
</ins><span class="cx"> 
</span><del>-            # Might be special group
-            if abo is None and self.addressbook().fullyShared():
-                rows = None
-                if self._name:
-                    if self._name == self.addressbook()._groupForSharedAddressBookName():
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
-                elif self._uid:
-                    if self._uid == (yield self.addressbook()._groupForSharedAddressBookUID()):
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
-                elif self._resourceID:
-                    if self.isGroupForSharedAddressBook():
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
-
-                if rows:
-                    self._initFromRow(tuple(rows[0]))
-                    yield self._loadPropertyStore()
-                    abo = self
-
</del><ins>+        if row:
+            child = yield cls.makeClass(parent, row, groupBindRow)
+            returnValue(child)
</ins><span class="cx">         else:
</span><del>-            acceptedGroupIDs = yield self.addressbook().acceptedGroupIDs()
-            allowedObjectIDs = yield self.addressbook().expandGroupIDs(self._txn, acceptedGroupIDs)
-            rows = None
-            if self._name:
-                if allowedObjectIDs:
-                    rows = (yield self._allColumnsWithResourceIDsAndName(allowedObjectIDs).on(
-                        self._txn, name=self._name,
-                        resourceIDs=allowedObjectIDs,
-                    ))
-            elif self._uid:
-                if allowedObjectIDs:
-                    rows = (yield self._allColumnsWithResourceIDsAndUID(allowedObjectIDs).on(
-                        self._txn, uid=self._uid,
-                        resourceIDs=allowedObjectIDs,
-                    ))
-            elif self._resourceID:
-                if self._resourceID not in allowedObjectIDs:
-                    # allow invited groups
-                    allowedObjectIDs = yield self.addressbook().unacceptedGroupIDs()
-                if self._resourceID in allowedObjectIDs:
-                    rows = (yield self._allColumnsWithResourceID.on(
-                        self._txn, resourceID=self._resourceID,
-                    ))
-            if rows:
-                self._initFromRow(tuple(rows[0]))
-                yield self._loadPropertyStore()
-                abo = self
-
-        if abo is not None:
-            if self._kind == _ABO_KIND_GROUP:
-
-                groupBindRows = yield AddressBookObject._bindForResourceIDAndHomeID.on(
-                    self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
-                )
-
-                if groupBindRows:
-                    groupBindRow = groupBindRows[0]
-                    bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
-                    self._bindMode = bindMode
-                    self._bindStatus = bindStatus
-                    self._bindMessage = bindMessage
-                    self._bindName = bindName
-                else:
-                    invites = yield self.sharingInvites()
-                    if len(invites):
-                        self._bindMessage = &quot;shared&quot;
-
-            returnValue(self)
-        else:
</del><span class="cx">             returnValue(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @classproperty
</del><ins>+    @classmethod
</ins><span class="cx">     def _allColumns(cls): #@NoSelf
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Full set of columns in the object table that need to be loaded to
</span><span class="lines">@@ -1710,24 +1790,23 @@
</span><span class="cx">             obj.MD5,
</span><span class="cx">             Len(obj.TEXT),
</span><span class="cx">             obj.CREATED,
</span><del>-            obj.MODIFIED,
</del><ins>+            obj.MODIFIED
</ins><span class="cx">         ]
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _initFromRow(self, row):
-        &quot;&quot;&quot;
-        Given a select result using the columns from L{_allColumns}, initialize
-        the object resource state.
-        &quot;&quot;&quot;
-        (self._ownerAddressBookResourceID,
-         self._resourceID,
-         self._name,
-         self._uid,
-         self._kind,
-         self._md5,
-         self._size,
-         self._created,
-         self._modified,) = tuple(row)
</del><ins>+    @classmethod
+    def _rowAttributes(cls): #@NoSelf
+        return (
+            &quot;_ownerAddressBookResourceID&quot;,
+            &quot;_resourceID&quot;,
+            &quot;_name&quot;,
+            &quot;_uid&quot;,
+            &quot;_kind&quot;,
+            &quot;_md5&quot;,
+            &quot;_size&quot;,
+            &quot;_created&quot;,
+            &quot;_modified&quot;,
+         )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="lines">@@ -1750,7 +1829,7 @@
</span><span class="cx">         else:
</span><span class="cx">             acceptedGroupIDs = yield addressbook.acceptedGroupIDs()
</span><span class="cx">             allowedObjectIDs = yield addressbook.expandGroupIDs(addressbook._txn, acceptedGroupIDs)
</span><del>-            rows = yield cls._columnsWithResourceIDsQuery(cls._allColumns, allowedObjectIDs).on(
</del><ins>+            rows = yield cls._columnsWithResourceIDsQuery(cls._allColumns(), allowedObjectIDs).on(
</ins><span class="cx">                 addressbook._txn, resourceIDs=allowedObjectIDs
</span><span class="cx">             )
</span><span class="cx">         returnValue(rows)
</span><span class="lines">@@ -1759,7 +1838,7 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     def _allColumnsWithResourceIDsAndNamesQuery(cls, resourceIDs, names):
</span><span class="cx">         obj = cls._objectSchema
</span><del>-        return Select(cls._allColumns, From=obj,
</del><ins>+        return Select(cls._allColumns(), From=obj,
</ins><span class="cx">                       Where=(obj.RESOURCE_ID.In(Parameter(&quot;resourceIDs&quot;, len(resourceIDs))).And(
</span><span class="cx">                           obj.RESOURCE_NAME.In(Parameter(&quot;names&quot;, len(names))))),)
</span><span class="cx"> 
</span><span class="lines">@@ -2336,4 +2415,10 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+# Hook-up class relationships at the end after they have all been defined
+from txdav.carddav.datastore.sql_external import AddressBookHomeExternal, AddressBookExternal, AddressBookObjectExternal
+AddressBookHome._externalClass = AddressBookHomeExternal
+AddressBookHome._childClass = AddressBook
+AddressBook._externalClass = AddressBookExternal
</ins><span class="cx"> AddressBook._objectResourceClass = AddressBookObject
</span><ins>+AddressBookObject._externalClass = AddressBookObjectExternal
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresql_externalpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py (0 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -0,0 +1,81 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+&quot;&quot;&quot;
+SQL backend for CardDAV storage when resources are external.
+&quot;&quot;&quot;
+
+from twisted.internet.defer import succeed
+
+from twext.python.log import Logger
+
+from txdav.carddav.datastore.sql import AddressBookHome, AddressBook, \
+    AddressBookObject
+from txdav.common.datastore.sql_external import CommonHomeExternal, CommonHomeChildExternal, \
+    CommonObjectResourceExternal
+
+log = Logger()
+
+class AddressBookHomeExternal(CommonHomeExternal, AddressBookHome):
+
+    def __init__(self, transaction, ownerUID, resourceID):
+
+        AddressBookHome.__init__(self, transaction, ownerUID)
+        CommonHomeExternal.__init__(self, transaction, ownerUID, resourceID)
+
+
+    def hasAddressBookResourceUIDSomewhereElse(self, uid, ok_object, mode):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAddressBookResourcesForUID(self, uid):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def createdHome(self):
+        &quot;&quot;&quot;
+        No children - make this a no-op.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def addressbook(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+
+class AddressBookExternal(CommonHomeChildExternal, AddressBook):
+    &quot;&quot;&quot;
+    SQL-based implementation of L{IAddressBook}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class AddressBookObjectExternal(CommonObjectResourceExternal, AddressBookObject):
+    &quot;&quot;&quot;
+    SQL-based implementation of L{ICalendar}.
+    &quot;&quot;&quot;
+    pass
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/test/test_sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -41,7 +41,7 @@
</span><span class="cx"> from txdav.common.icommondatastore import NoSuchObjectResourceError
</span><span class="cx"> from txdav.common.datastore.sql import EADDRESSBOOKTYPE, CommonObjectResource
</span><span class="cx"> from txdav.common.datastore.sql_tables import  _ABO_KIND_PERSON, _ABO_KIND_GROUP, schema
</span><del>-from txdav.common.datastore.test.util import buildStore
</del><ins>+from txdav.common.datastore.test.util import buildStore, cleanStore
</ins><span class="cx"> 
</span><span class="cx"> from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
</span><span class="cx"> 
</span><span class="lines">@@ -55,7 +55,22 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(AddressBookSQLStorageTests, self).setUp()
</span><del>-        self._sqlStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        self._sqlStore = yield buildStore(
+            self,
+            self.notifierFactory,
+            homes=(
+                &quot;home1&quot;,
+                &quot;home2&quot;,
+                &quot;home3&quot;,
+                &quot;home_bad&quot;,
+                &quot;home_empty&quot;,
+                &quot;homeNew&quot;,
+                &quot;new-home&quot;,
+                &quot;uid1&quot;,
+                &quot;uid2&quot;,
+                &quot;xyzzy&quot;,
+            )
+        )
</ins><span class="cx">         yield self.populate()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -288,7 +303,7 @@
</span><span class="cx">         Test that two concurrent attempts to PUT different address book object resources to the
</span><span class="cx">         same address book home does not cause a deadlock.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        addressbookStore = self._sqlStore
</ins><span class="cx"> 
</span><span class="cx">         # Provision the home and addressbook now
</span><span class="cx">         txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -394,7 +409,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that kind property UID is stored correctly in database
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        addressbookStore = self._sqlStore
</ins><span class="cx"> 
</span><span class="cx">         # Provision the home and addressbook, one user and one group
</span><span class="cx">         txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -440,7 +455,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that kind property vCard is stored correctly in database
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        addressbookStore = self._sqlStore
</ins><span class="cx"> 
</span><span class="cx">         # Provision the home and addressbook, one user and one group
</span><span class="cx">         txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -531,7 +546,8 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that kind property vCard is stored correctly in database
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        addressbookStore = self._sqlStore
+        cleanStore(self, addressbookStore)
</ins><span class="cx"> 
</span><span class="cx">         # Provision the home and addressbook, one user and one group
</span><span class="cx">         txn = addressbookStore.newTransaction()
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingconduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -14,6 +14,8 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><ins>+from twext.python.log import Logger
+
</ins><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> 
</span><span class="cx"> from txdav.common.datastore.podding.request import ConduitRequest
</span><span class="lines">@@ -25,21 +27,17 @@
</span><span class="cx">     &quot;PoddingConduitResource&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-class BadMessageError(Exception):
-    pass
</del><ins>+log = Logger()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-
-class InvalidCrossPodRequestError(Exception):
</del><ins>+class FailedCrossPodRequestError(RuntimeError):
+    &quot;&quot;&quot;
+    Request returned an error.
+    &quot;&quot;&quot;
</ins><span class="cx">     pass
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class FailedCrossPodRequestError(Exception):
-    pass
-
-
-
</del><span class="cx"> class PoddingConduit(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     This class is the API/RPC bridge between cross-pod requests and the store.
</span><span class="lines">@@ -51,7 +49,7 @@
</span><span class="cx">     the other keys are arguments to that call.
</span><span class="cx"> 
</span><span class="cx">     Each response C{dict} has a &quot;result&quot; key that indicates the call result, and other
</span><del>-    optional keys for any parameters returned by the call.
</del><ins>+    optional keys for any values returned by the call.
</ins><span class="cx"> 
</span><span class="cx">     The conduit provides two methods for each action: one for the sending side and one for
</span><span class="cx">     the receiving side, called &quot;send_{action}&quot; and &quot;recv_{action}&quot;, respectively, where
</span><span class="lines">@@ -63,10 +61,14 @@
</span><span class="cx">     The &quot;recv_{action}&quot; calls take a single C{dict} argument that is the deserialized JSON
</span><span class="cx">     data from the incoming request. The return value is a C{dict} with the result.
</span><span class="cx"> 
</span><ins>+    Some simple forms of send_/recv_ methods can be auto-generated to simplify coding.
+
</ins><span class="cx">     Right now this conduit is used for cross-pod sharing operations. In the future we will
</span><span class="cx">     likely use it for cross-pod migration.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+    conduitRequestClass = ConduitRequest
+
</ins><span class="cx">     def __init__(self, store):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param store: the L{CommonDataStore} in use.
</span><span class="lines">@@ -91,18 +93,73 @@
</span><span class="cx">         if source is None:
</span><span class="cx">             raise DirectoryRecordNotFoundError(&quot;Cross-pod source: {}&quot;.format(source_guid))
</span><span class="cx">         if not source.thisServer():
</span><del>-            raise InvalidCrossPodRequestError(&quot;Cross-pod source not on this server: {}&quot;.format(source_guid))
</del><ins>+            raise FailedCrossPodRequestError(&quot;Cross-pod source not on this server: {}&quot;.format(source_guid))
</ins><span class="cx"> 
</span><span class="cx">         destination = self.store.directoryService().recordWithUID(destination_guid)
</span><span class="cx">         if destination is None:
</span><span class="cx">             raise DirectoryRecordNotFoundError(&quot;Cross-pod destination: {}&quot;.format(destination_guid))
</span><span class="cx">         if destination.thisServer():
</span><del>-            raise InvalidCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(destination_guid))
</del><ins>+            raise FailedCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(destination_guid))
</ins><span class="cx"> 
</span><span class="cx">         return (source, destination,)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def sendRequest(self, txn, recipient, data):
+
+        request = self.conduitRequestClass(recipient.server(), data)
+        try:
+            response = (yield request.doRequest(txn))
+        except Exception as e:
+            raise FailedCrossPodRequestError(&quot;Failed cross-pod request: {}&quot;.format(e))
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def processRequest(self, data):
+        &quot;&quot;&quot;
+        Process the request.
+
+        @param data: the JSON data to process
+        @type data: C{dict}
+        &quot;&quot;&quot;
+        # Must have a dict with an &quot;action&quot; key
+        try:
+            action = data[&quot;action&quot;]
+        except (KeyError, TypeError) as e:
+            log.error(&quot;JSON data must have an object as its root with an 'action' attribute: {ex}\n{json}&quot;, ex=e, json=data)
+            raise FailedCrossPodRequestError(&quot;JSON data must have an object as its root with an 'action' attribute: {}\n{}&quot;.format(e, data,))
+
+        if action == &quot;ping&quot;:
+            result = {&quot;result&quot;: &quot;ok&quot;}
+            returnValue(result)
+
+        method = &quot;recv_{}&quot;.format(action)
+        if not hasattr(self, method):
+            log.error(&quot;Unsupported action: {action}&quot;, action=action)
+            raise FailedCrossPodRequestError(&quot;Unsupported action: {}&quot;.format(action))
+
+        # Need a transaction to work with
+        txn = self.store.newTransaction(repr(&quot;Conduit request&quot;))
+
+        # Do the actual request processing
+        try:
+            result = (yield getattr(self, method)(txn, data))
+        except Exception as e:
+            yield txn.abort()
+            log.error(&quot;Failed action: {action}, {ex}&quot;, action=action, ex=e)
+            raise FailedCrossPodRequestError(&quot;Failed action: {}, {}&quot;.format(action, e))
+
+        yield txn.commit()
+
+        returnValue(result)
+
+
+    #
+    # Invite related apis
+    #
+
+    @inlineCallbacks
</ins><span class="cx">     def send_shareinvite(self, txn, homeType, ownerUID, ownerID, ownerName, shareeUID, shareUID, bindMode, summary, supported_components):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Send a sharing invite cross-pod message.
</span><span class="lines">@@ -127,7 +184,7 @@
</span><span class="cx">         @type supported_components: C{str}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
</del><ins>+        _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
</ins><span class="cx"> 
</span><span class="cx">         action = {
</span><span class="cx">             &quot;action&quot;: &quot;shareinvite&quot;,
</span><span class="lines">@@ -143,10 +200,8 @@
</span><span class="cx">         if supported_components is not None:
</span><span class="cx">             action[&quot;supported-components&quot;] = supported_components
</span><span class="cx"> 
</span><del>-        request = ConduitRequest(sharee.server(), action)
-        response = (yield request.doRequest(txn))
-        if response[&quot;result&quot;] != &quot;ok&quot;:
-            raise FailedCrossPodRequestError(response[&quot;description&quot;])
</del><ins>+        result = yield self.sendRequest(txn, recipient, action)
+        returnValue(result)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -159,15 +214,12 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if message[&quot;action&quot;] != &quot;shareinvite&quot;:
</span><del>-            raise BadMessageError(&quot;Wrong action '{}' for recv_shareinvite&quot;.format(message[&quot;action&quot;]))
</del><ins>+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_shareinvite&quot;.format(message[&quot;action&quot;]))
</ins><span class="cx"> 
</span><span class="cx">         # Create a share
</span><span class="cx">         shareeHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;sharee&quot;], create=True)
</span><span class="cx">         if shareeHome is None or shareeHome.external():
</span><del>-            returnValue({
-                &quot;result&quot;: &quot;bad&quot;,
-                &quot;description&quot;: &quot;Invalid sharee UID specified&quot;,
-            })
</del><ins>+            raise FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
</ins><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             yield shareeHome.processExternalInvite(
</span><span class="lines">@@ -180,14 +232,10 @@
</span><span class="cx">                 supported_components=message.get(&quot;supported-components&quot;)
</span><span class="cx">             )
</span><span class="cx">         except ExternalShareFailed as e:
</span><del>-            returnValue({
-                &quot;result&quot;: &quot;bad&quot;,
-                &quot;description&quot;: str(e),
-            })
</del><ins>+            raise FailedCrossPodRequestError(str(e))
</ins><span class="cx"> 
</span><span class="cx">         returnValue({
</span><span class="cx">             &quot;result&quot;: &quot;ok&quot;,
</span><del>-            &quot;description&quot;: &quot;Success&quot;
</del><span class="cx">         })
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -208,7 +256,7 @@
</span><span class="cx">         @type shareUID: C{str}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
</del><ins>+        _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
</ins><span class="cx"> 
</span><span class="cx">         action = {
</span><span class="cx">             &quot;action&quot;: &quot;shareuninvite&quot;,
</span><span class="lines">@@ -219,10 +267,8 @@
</span><span class="cx">             &quot;share_id&quot;: shareUID,
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        request = ConduitRequest(sharee.server(), action)
-        response = (yield request.doRequest(txn))
-        if response[&quot;result&quot;] != &quot;ok&quot;:
-            raise FailedCrossPodRequestError(response[&quot;description&quot;])
</del><ins>+        result = yield self.sendRequest(txn, recipient, action)
+        returnValue(result)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -235,15 +281,12 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if message[&quot;action&quot;] != &quot;shareuninvite&quot;:
</span><del>-            raise BadMessageError(&quot;Wrong action '{}' for recv_shareuninvite&quot;.format(message[&quot;action&quot;]))
</del><ins>+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_shareuninvite&quot;.format(message[&quot;action&quot;]))
</ins><span class="cx"> 
</span><span class="cx">         # Create a share
</span><span class="cx">         shareeHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;sharee&quot;], create=True)
</span><span class="cx">         if shareeHome is None or shareeHome.external():
</span><del>-            returnValue({
-                &quot;result&quot;: &quot;bad&quot;,
-                &quot;description&quot;: &quot;Invalid sharee UID specified&quot;,
-            })
</del><ins>+            FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
</ins><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             yield shareeHome.processExternalUninvite(
</span><span class="lines">@@ -252,14 +295,10 @@
</span><span class="cx">                 message[&quot;share_id&quot;],
</span><span class="cx">             )
</span><span class="cx">         except ExternalShareFailed as e:
</span><del>-            returnValue({
-                &quot;result&quot;: &quot;bad&quot;,
-                &quot;description&quot;: str(e),
-            })
</del><ins>+            FailedCrossPodRequestError(str(e))
</ins><span class="cx"> 
</span><span class="cx">         returnValue({
</span><span class="cx">             &quot;result&quot;: &quot;ok&quot;,
</span><del>-            &quot;description&quot;: &quot;Success&quot;
</del><span class="cx">         })
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -272,9 +311,9 @@
</span><span class="cx">         @type homeType: C{int}
</span><span class="cx">         @param ownerUID: GUID of the sharer.
</span><span class="cx">         @type ownerUID: C{str}
</span><del>-        @param shareeUID: GUID of the sharee
</del><ins>+        @param shareeUID: GUID of the recipient
</ins><span class="cx">         @type shareeUID: C{str}
</span><del>-        @param shareUID: Resource/invite ID for sharee
</del><ins>+        @param shareUID: Resource/invite ID for recipient
</ins><span class="cx">         @type shareUID: C{str}
</span><span class="cx">         @param bindStatus: bind mode for the share
</span><span class="cx">         @type bindStatus: C{str}
</span><span class="lines">@@ -282,7 +321,7 @@
</span><span class="cx">         @type summary: C{str}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        _ignore_owner, sharee = self.validRequst(shareeUID, ownerUID)
</del><ins>+        _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
</ins><span class="cx"> 
</span><span class="cx">         action = {
</span><span class="cx">             &quot;action&quot;: &quot;sharereply&quot;,
</span><span class="lines">@@ -295,10 +334,8 @@
</span><span class="cx">         if summary is not None:
</span><span class="cx">             action[&quot;summary&quot;] = summary
</span><span class="cx"> 
</span><del>-        request = ConduitRequest(sharee.server(), action)
-        response = (yield request.doRequest(txn))
-        if response[&quot;result&quot;] != &quot;ok&quot;:
-            raise FailedCrossPodRequestError(response[&quot;description&quot;])
</del><ins>+        result = yield self.sendRequest(txn, recipient, action)
+        returnValue(result)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -311,15 +348,12 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if message[&quot;action&quot;] != &quot;sharereply&quot;:
</span><del>-            raise BadMessageError(&quot;Wrong action '{}' for recv_sharereply&quot;.format(message[&quot;action&quot;]))
</del><ins>+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_sharereply&quot;.format(message[&quot;action&quot;]))
</ins><span class="cx"> 
</span><span class="cx">         # Create a share
</span><span class="cx">         ownerHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;owner&quot;])
</span><span class="cx">         if ownerHome is None or ownerHome.external():
</span><del>-            returnValue({
-                &quot;result&quot;: &quot;bad&quot;,
-                &quot;description&quot;: &quot;Invalid owner UID specified&quot;,
-            })
</del><ins>+            FailedCrossPodRequestError(&quot;Invalid owner UID specified&quot;)
</ins><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             yield ownerHome.processExternalReply(
</span><span class="lines">@@ -330,12 +364,126 @@
</span><span class="cx">                 summary=message.get(&quot;summary&quot;)
</span><span class="cx">             )
</span><span class="cx">         except ExternalShareFailed as e:
</span><del>-            returnValue({
-                &quot;result&quot;: &quot;bad&quot;,
-                &quot;description&quot;: str(e),
-            })
</del><ins>+            FailedCrossPodRequestError(str(e))
</ins><span class="cx"> 
</span><span class="cx">         returnValue({
</span><span class="cx">             &quot;result&quot;: &quot;ok&quot;,
</span><del>-            &quot;description&quot;: &quot;Success&quot;
</del><span class="cx">         })
</span><ins>+
+
+    #
+    # Sharer data access related apis
+    #
+
+    def _send(self, action, shareeView):
+        &quot;&quot;&quot;
+        Base behavior for an operation on a sharee resource.
+
+        @param shareeView: sharee resource being operated on.
+        @type shareeView: L{CommonHomeChildExternal}
+        &quot;&quot;&quot;
+
+        homeType = shareeView.ownerHome()._homeType
+        ownerUID = shareeView.ownerHome().uid()
+        ownerID = shareeView.external_id()
+        shareeUID = shareeView.viewerHome().uid()
+
+        _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
+
+        result = {
+            &quot;action&quot;: action,
+            &quot;type&quot;: homeType,
+            &quot;owner&quot;: ownerUID,
+            &quot;owner_id&quot;: ownerID,
+            &quot;sharee&quot;: shareeUID,
+        }
+        return result, recipient
+
+
+    @inlineCallbacks
+    def _recv(self, txn, message, expected_action):
+        &quot;&quot;&quot;
+        Base behavior for sharer data access.
+
+        @param message: message arguments
+        @type message: C{dict}
+        &quot;&quot;&quot;
+
+        if message[&quot;action&quot;] != expected_action:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_{}&quot;.format(message[&quot;action&quot;], expected_action))
+
+        # Create a share
+        ownerHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;owner&quot;], create=True)
+        if ownerHome is None or ownerHome.external():
+            FailedCrossPodRequestError(&quot;Invalid owner UID specified&quot;)
+
+        ownerHomeChild = yield ownerHome.childWithID(message[&quot;owner_id&quot;])
+        if ownerHomeChild is None:
+            FailedCrossPodRequestError(&quot;Invalid owner shared resource specified&quot;)
+
+        returnValue((ownerHome, ownerHomeChild))
+
+
+    #
+    # Simple calls are ones where there is no argument and a single return value. We can simplify
+    # code generation for these by dynamically generating the appropriate class methods.
+    #
+
+    @inlineCallbacks
+    def _simple_send(self, actionName, shareeView, args=None, kwargs=None):
+        &quot;&quot;&quot;
+        A simple send operation that returns a value.
+
+        @param actionName: name of the action.
+        @type actionName: C{str}
+        @param shareeView: sharee resource being operated on.
+        @type shareeView: L{CommonHomeChildExternal}
+        @param args: list of optional arguments.
+        @type args: C{list}
+        @param kwargs: optional keyword arguments.
+        @type kwargs: C{dict}
+        &quot;&quot;&quot;
+
+        action, recipient = self._send(actionName, shareeView)
+        if args is not None:
+            action[&quot;arguments&quot;] = args
+        if kwargs is not None:
+            action[&quot;keywords&quot;] = kwargs
+        result = yield self.sendRequest(shareeView._txn, recipient, action)
+        returnValue(result[&quot;value&quot;])
+
+
+    @inlineCallbacks
+    def _simple_recv(self, txn, actionName, message, method):
+        &quot;&quot;&quot;
+        A simple recv operation that returns a value. We also look for an optional set of arguments/keywords
+        and include those only if present.
+
+        @param actionName: name of the action.
+        @type actionName: C{str}
+        @param message: message arguments
+        @type message: C{dict}
+        @param method: name of the method to execute on the shared resource to get the result.
+        @type method: C{str}
+        &quot;&quot;&quot;
+
+        _ignore_ownerHome, ownerHomeChild = yield self._recv(txn, message, actionName)
+        value = yield getattr(ownerHomeChild, method)(*message.get(&quot;arguments&quot;, ()), **message.get(&quot;keywords&quot;, {}))
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+            &quot;value&quot;: value,
+        })
+
+
+    @classmethod
+    def _make_simple_action(cls, action, method):
+        setattr(cls, &quot;send_{}&quot;.format(action), lambda self, shareeView, *args, **kwargs: self._simple_send(action, shareeView, args, kwargs))
+        setattr(cls, &quot;recv_{}&quot;.format(action), lambda self, txn, message: self._simple_recv(txn, action, message, method))
+
+
+PoddingConduit._make_simple_action(&quot;countobjects&quot;, &quot;countObjectResources&quot;)
+PoddingConduit._make_simple_action(&quot;listobjects&quot;, &quot;listObjectResources&quot;)
+PoddingConduit._make_simple_action(&quot;synctoken&quot;, &quot;syncToken&quot;)
+PoddingConduit._make_simple_action(&quot;resourcenamessincerevision&quot;, &quot;resourceNamesSinceRevision&quot;)
+PoddingConduit._make_simple_action(&quot;resourceuidforname&quot;, &quot;resourceUIDForName&quot;)
+PoddingConduit._make_simple_action(&quot;resourcenameforuid&quot;, &quot;resourceNameForUID&quot;)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingrequestpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -73,7 +73,7 @@
</span><span class="cx">         except Exception as e:
</span><span class="cx">             # Request failed
</span><span class="cx">             log.error(&quot;Could not do cross-pod request : {request} {ex}&quot;, request=self, ex=e)
</span><del>-            raise ValueError(&quot;Failed cross-pod request: {}&quot;.format(response.code))
</del><ins>+            raise ValueError(&quot;Failed cross-pod request: {}&quot;.format(e))
</ins><span class="cx"> 
</span><span class="cx">         returnValue(data)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -32,6 +32,7 @@
</span><span class="cx"> 
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers
</span><ins>+from txdav.common.datastore.podding.conduit import FailedCrossPodRequestError
</ins><span class="cx"> 
</span><span class="cx"> __all__ = [
</span><span class="cx">     &quot;ConduitResource&quot;,
</span><span class="lines">@@ -138,36 +139,14 @@
</span><span class="cx">             self.log.error(&quot;Invalid JSON data in request: {ex}\n{body}&quot;, ex=e, body=body)
</span><span class="cx">             raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;Invalid JSON data in request: {}\n{}&quot;.format(e, body)))
</span><span class="cx"> 
</span><del>-        # Must have a dict with an &quot;action&quot; key
</del><ins>+        # Get the conduit to process the data
</ins><span class="cx">         try:
</span><del>-            action = j[&quot;action&quot;]
-        except (KeyError, TypeError) as e:
-            self.log.error(&quot;JSON data must have an object as its root with an 'action' attribute: {ex}\n{json}&quot;, ex=e, json=j)
-            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;JSON data must have an object as its root with an 'action' attribute: {}\n{}&quot;.format(e, j,)))
-
-        if action == &quot;ping&quot;:
-            result = {&quot;result&quot;: &quot;ok&quot;}
-            response = JSONResponse(responsecode.OK, result)
-            returnValue(response)
-
-        method = &quot;recv_{}&quot;.format(action)
-        if not hasattr(self.store.conduit, method):
-            self.log.error(&quot;Unsupported action: {action}&quot;, action=action)
-            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;Unsupported action: {}&quot;.format(action)))
-
-        # Need a transaction to work with
-        txn = self.store.newTransaction(repr(request))
-
-        # Do the POST processing treating this as a non-local schedule
-        try:
-            result = (yield getattr(self.store.conduit, method)(txn, j))
</del><ins>+            result = yield self.store.conduit.processRequest(j)
+        except FailedCrossPodRequestError as e:
+            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
</ins><span class="cx">         except Exception as e:
</span><del>-            yield txn.abort()
-            self.log.error(&quot;Failed action: {action}, {ex}&quot;, action=action, ex=e)
-            raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, &quot;Failed action: {}, {}&quot;.format(action, e)))
</del><ins>+            raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, str(e)))
</ins><span class="cx"> 
</span><del>-        yield txn.commit()
-
</del><span class="cx">         response = JSONResponse(responsecode.OK, result)
</span><span class="cx">         returnValue(response)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_conduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -16,17 +16,22 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.python.clsprop import classproperty
</span><span class="cx"> import twext.web2.dav.test.util
</span><del>-from twisted.internet.defer import inlineCallbacks, succeed
</del><ins>+from twisted.internet.defer import inlineCallbacks, succeed, returnValue
</ins><span class="cx"> from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, Server
</span><span class="cx"> from txdav.caldav.datastore.test.util import buildCalendarStore, \
</span><span class="cx">     TestCalendarStoreDirectoryRecord
</span><span class="cx"> from txdav.common.datastore.podding.resource import ConduitResource
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
</span><span class="cx"> from txdav.common.datastore.podding.conduit import PoddingConduit, \
</span><del>-    InvalidCrossPodRequestError
</del><ins>+    FailedCrossPodRequestError
</ins><span class="cx"> from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
</span><ins>+from txdav.common.datastore.podding.test.util import MultiStoreConduitTest, \
+    FakeConduitRequest
+from txdav.common.datastore.sql_tables import _BIND_STATUS_ACCEPTED
+from pycalendar.datetime import DateTime
+from twistedcaldav.ical import Component
</ins><span class="cx"> 
</span><del>-class Conduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
</del><ins>+class TestConduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
</ins><span class="cx"> 
</span><span class="cx">     class FakeConduit(object):
</span><span class="cx"> 
</span><span class="lines">@@ -40,7 +45,7 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><del>-        yield super(Conduit, self).setUp()
</del><ins>+        yield super(TestConduit, self).setUp()
</ins><span class="cx">         self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
</span><span class="cx">         self.directory = self._sqlCalendarStore.directoryService()
</span><span class="cx"> 
</span><span class="lines">@@ -113,4 +118,378 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, &quot;bogus01&quot;, &quot;user02&quot;)
</span><span class="cx">         self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, &quot;user01&quot;, &quot;bogus02&quot;)
</span><del>-        self.assertRaises(InvalidCrossPodRequestError, conduit.validRequst, &quot;user01&quot;, &quot;user02&quot;)
</del><ins>+        self.assertRaises(FailedCrossPodRequestError, conduit.validRequst, &quot;user01&quot;, &quot;user02&quot;)
+
+
+
+class TestConduitToConduit(MultiStoreConduitTest):
+
+    class FakeConduit(PoddingConduit):
+
+        @inlineCallbacks
+        def send_fake(self, txn, ownerUID, shareeUID):
+            _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
+            action = {
+                &quot;action&quot;: &quot;fake&quot;,
+                &quot;echo&quot;: &quot;bravo&quot;
+            }
+
+            result = yield self.sendRequest(txn, sharee, action)
+            returnValue(result)
+
+
+        def recv_fake(self, txn, j):
+            return succeed({
+                &quot;result&quot;: &quot;ok&quot;,
+                &quot;back2u&quot;: j[&quot;echo&quot;],
+                &quot;more&quot;: &quot;bits&quot;,
+            })
+
+
+    def makeConduit(self, store):
+        &quot;&quot;&quot;
+        Use our own variant.
+        &quot;&quot;&quot;
+        conduit = self.FakeConduit(store)
+        conduit.conduitRequestClass = FakeConduitRequest
+        return conduit
+
+
+    @inlineCallbacks
+    def test_fake_action(self):
+        &quot;&quot;&quot;
+        Cross-pod request works when conduit does support the action.
+        &quot;&quot;&quot;
+
+        txn = self.transactionUnderTest()
+        store1 = self.storeUnderTest()
+        response = yield store1.conduit.send_fake(txn, &quot;user01&quot;, &quot;puser01&quot;)
+        self.assertTrue(&quot;result&quot; in response)
+        self.assertEqual(response[&quot;result&quot;], &quot;ok&quot;)
+        self.assertTrue(&quot;back2u&quot; in response)
+        self.assertEqual(response[&quot;back2u&quot;], &quot;bravo&quot;)
+        self.assertTrue(&quot;more&quot; in response)
+        self.assertEqual(response[&quot;more&quot;], &quot;bits&quot;)
+        yield txn.commit()
+
+        store2 = self.otherStoreUnderTest()
+        txn = store2.newTransaction()
+        response = yield store2.conduit.send_fake(txn, &quot;puser01&quot;, &quot;user01&quot;)
+        self.assertTrue(&quot;result&quot; in response)
+        self.assertEqual(response[&quot;result&quot;], &quot;ok&quot;)
+        self.assertTrue(&quot;back2u&quot; in response)
+        self.assertEqual(response[&quot;back2u&quot;], &quot;bravo&quot;)
+        self.assertTrue(&quot;more&quot; in response)
+        self.assertEqual(response[&quot;more&quot;], &quot;bits&quot;)
+        yield txn.commit()
+
+
+
+class TestConduitAPI(MultiStoreConduitTest):
+    &quot;&quot;&quot;
+    Test that the conduit api works.
+    &quot;&quot;&quot;
+
+    nowYear = {&quot;now&quot;: DateTime.getToday().getYear()}
+
+    caldata1 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid1
+DTSTART:{now:04d}0102T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
+
+    caldata2 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:ui2
+DTSTART:{now:04d}0102T160000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
+
+    @inlineCallbacks
+    def test_basic_share(self):
+        &quot;&quot;&quot;
+        Test that basic invite/uninvite works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        shared = yield  calendar1.shareeView(&quot;puser01&quot;)
+        self.assertEqual(shared.shareStatus(), _BIND_STATUS_ACCEPTED)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        self.assertTrue(shared is not None)
+        self.assertTrue(shared.external())
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield calendar1.uninviteUserFromShare(&quot;puser01&quot;)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        self.assertTrue(shared is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_countobjects(self):
+        &quot;&quot;&quot;
+        Test that action=countobjects works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        count = yield shared.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        count = yield calendar1.countObjectResources()
+        self.assertEqual(count, 1)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        count = yield shared.countObjectResources()
+        self.assertEqual(count, 1)
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        count = yield calendar1.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        count = yield shared.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_listobjects(self):
+        &quot;&quot;&quot;
+        Test that action=listobjects works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        objects = yield shared.listObjectResources()
+        self.assertEqual(set(objects), set())
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
+        objects = yield calendar1.listObjectResources()
+        self.assertEqual(set(objects), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        objects = yield shared.listObjectResources()
+        self.assertEqual(set(objects), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        objects = yield calendar1.listObjectResources()
+        self.assertEqual(set(objects), set((&quot;2.ics&quot;,)))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        objects = yield shared.listObjectResources()
+        self.assertEqual(set(objects), set((&quot;2.ics&quot;,)))
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_synctoken(self):
+        &quot;&quot;&quot;
+        Test that action=synctoken works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_1 = yield calendar1.syncToken()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_1 = yield shared.syncToken()
+        yield self.otherCommit()
+
+        self.assertEqual(token1_1, token2_1)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_2 = yield calendar1.syncToken()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_2 = yield shared.syncToken()
+        yield self.otherCommit()
+
+        self.assertNotEqual(token1_1, token1_2)
+        self.assertEqual(token1_2, token2_2)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        count = yield calendar1.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_3 = yield calendar1.syncToken()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_3 = yield shared.syncToken()
+        yield self.otherCommit()
+
+        self.assertNotEqual(token1_1, token1_3)
+        self.assertNotEqual(token1_2, token1_3)
+        self.assertEqual(token1_3, token2_3)
+
+
+    @inlineCallbacks
+    def test_resourcenamessincerevision(self):
+        &quot;&quot;&quot;
+        Test that action=synctoken works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_1 = yield calendar1.syncToken()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_1 = yield shared.syncToken()
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_2 = yield calendar1.syncToken()
+        names1 = yield calendar1.resourceNamesSinceToken(token1_1)
+        self.assertEqual(names1, ([&quot;1.ics&quot;], [],))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_2 = yield shared.syncToken()
+        names2 = yield shared.resourceNamesSinceToken(token2_1)
+        self.assertEqual(names2, ([&quot;1.ics&quot;], [],))
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        count = yield calendar1.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_3 = yield calendar1.syncToken()
+        names1 = yield calendar1.resourceNamesSinceToken(token1_2)
+        self.assertEqual(names1, ([], [&quot;1.ics&quot;],))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_3 = yield shared.syncToken()
+        names2 = yield shared.resourceNamesSinceToken(token2_2)
+        self.assertEqual(names2, ([], [&quot;1.ics&quot;],))
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        names1 = yield calendar1.resourceNamesSinceToken(token1_3)
+        self.assertEqual(names1, ([], [],))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        names2 = yield shared.resourceNamesSinceToken(token2_3)
+        self.assertEqual(names2, ([], [],))
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_resourceuidforname(self):
+        &quot;&quot;&quot;
+        Test that action=resourceuidforname works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        uid = yield calendar1.resourceUIDForName(&quot;1.ics&quot;)
+        self.assertEqual(uid, &quot;uid1&quot;)
+        uid = yield calendar1.resourceUIDForName(&quot;2.ics&quot;)
+        self.assertTrue(uid is None)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        uid = yield shared.resourceUIDForName(&quot;1.ics&quot;)
+        self.assertEqual(uid, &quot;uid1&quot;)
+        uid = yield shared.resourceUIDForName(&quot;2.ics&quot;)
+        self.assertTrue(uid is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_resourcenameforuid(self):
+        &quot;&quot;&quot;
+        Test that action=resourcenameforuid works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        uid = yield calendar1.resourceNameForUID(&quot;uid1&quot;)
+        self.assertEqual(uid, &quot;1.ics&quot;)
+        uid = yield calendar1.resourceNameForUID(&quot;uid2&quot;)
+        self.assertTrue(uid is None)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        uid = yield shared.resourceNameForUID(&quot;uid1&quot;)
+        self.assertEqual(uid, &quot;1.ics&quot;)
+        uid = yield shared.resourceNameForUID(&quot;uid2&quot;)
+        self.assertTrue(uid is None)
+        yield self.otherCommit()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_external_homepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -86,7 +86,8 @@
</span><span class="cx">             home = yield self.transactionUnderTest().calendarHomeWithUID(&quot;puser{:02d}&quot;.format(i), create=True)
</span><span class="cx">             self.assertTrue(home is not None)
</span><span class="cx">             self.assertEqual(home._status, _HOME_STATUS_EXTERNAL)
</span><del>-            self.assertRaises(AssertionError, home.childWithName, &quot;calendar&quot;)
</del><ins>+            calendar = yield home.childWithName(&quot;calendar&quot;)
+            self.assertTrue(calendar is None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_resourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -25,12 +25,13 @@
</span><span class="cx"> from txdav.common.datastore.podding.resource import ConduitResource
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
</span><span class="cx"> import json
</span><ins>+from txdav.common.datastore.podding.conduit import PoddingConduit
</ins><span class="cx"> 
</span><span class="cx"> class ConduitPOST (CommonCommonTests, twext.web2.dav.test.util.TestCase):
</span><span class="cx"> 
</span><del>-    class FakeConduit(object):
</del><ins>+    class FakeConduit(PoddingConduit):
</ins><span class="cx"> 
</span><del>-        def recv_fake(self, j):
</del><ins>+        def recv_fake(self, txn, j):
</ins><span class="cx">             return succeed({
</span><span class="cx">                 &quot;result&quot;: &quot;ok&quot;,
</span><span class="cx">                 &quot;back2u&quot;: j[&quot;echo&quot;],
</span><span class="lines">@@ -214,7 +215,8 @@
</span><span class="cx">         Cross-pod request fails when conduit does not support the action.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.patch(self.storeUnderTest(), &quot;conduit&quot;, self.FakeConduit())
</del><ins>+        store = self.storeUnderTest()
+        self.patch(store, &quot;conduit&quot;, self.FakeConduit(store))
</ins><span class="cx"> 
</span><span class="cx">         request = SimpleRequest(
</span><span class="cx">             self.site,
</span><span class="lines">@@ -242,7 +244,8 @@
</span><span class="cx">         Cross-pod request works when conduit does support the action.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.patch(self.storeUnderTest(), &quot;conduit&quot;, self.FakeConduit())
</del><ins>+        store = self.storeUnderTest()
+        self.patch(store, &quot;conduit&quot;, self.FakeConduit(store))
</ins><span class="cx"> 
</span><span class="cx">         request = SimpleRequest(
</span><span class="cx">             self.site,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtestutilpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py (0 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -0,0 +1,202 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from txdav.caldav.datastore.scheduling.ischedule.localservers import Server, \
+    Servers
+from txdav.caldav.datastore.test.util import \
+    TestCalendarStoreDirectoryRecord, TestCalendarStoreDirectoryService
+from txdav.common.datastore.podding.conduit import PoddingConduit
+from txdav.common.datastore.test.util import CommonCommonTests, SQLStoreBuilder,\
+    theStoreBuilder
+
+import twext.web2.dav.test.util
+from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
+
+class FakeConduitRequest(object):
+    &quot;&quot;&quot;
+    A conduit request that sends messages internally rather than using HTTP
+    &quot;&quot;&quot;
+
+    storeMap = {}
+
+    @classmethod
+    def addServerStore(cls, server, store):
+        &quot;&quot;&quot;
+        Add a store mapped to a server. These mappings are used to &quot;deliver&quot; conduit
+        requests to the appropriate store.
+
+        @param uri: the server
+        @type uri: L{Server}
+        @param store: the store
+        @type store: L{ICommonDataStore}
+        &quot;&quot;&quot;
+
+        cls.storeMap[server.details()] = store
+
+
+    def __init__(self, server, data):
+
+        self.server = server
+        self.data = data
+
+
+    @inlineCallbacks
+    def doRequest(self, txn):
+
+        # Generate an HTTP client request
+        try:
+            response = (yield self._processRequest())
+        except Exception as e:
+            raise ValueError(&quot;Failed cross-pod request: {}&quot;.format(e))
+
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def _processRequest(self):
+        &quot;&quot;&quot;
+        Process the request by sending it to the relevant server.
+
+        @return: the HTTP response.
+        @rtype: L{Response}
+        &quot;&quot;&quot;
+
+        store = self.storeMap[self.server.details()]
+        result = yield store.conduit.processRequest(self.data)
+        returnValue(result)
+
+
+
+class MultiStoreConduitTest(CommonCommonTests, twext.web2.dav.test.util.TestCase):
+
+    theStoreBuilder2 = SQLStoreBuilder(secondary=True)
+    otherTransaction = None
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(MultiStoreConduitTest, self).setUp()
+
+        server1 = Server(&quot;A&quot;, &quot;http://127.0.0.1:8008&quot;, &quot;A&quot;, True)
+        Servers.addServer(server1)
+
+        server2 = Server(&quot;B&quot;, &quot;http://127.0.0.1:8108&quot;, &quot;B&quot;, False)
+        Servers.addServer(server2)
+
+        self._sqlCalendarStore1 = yield self.makeStore(theStoreBuilder, True, server1, server2)
+        self._sqlCalendarStore2 = yield self.makeStore(self.theStoreBuilder2, False, server1, server2)
+
+        FakeConduitRequest.addServerStore(server1, self._sqlCalendarStore1)
+        FakeConduitRequest.addServerStore(server2, self._sqlCalendarStore2)
+
+
+    def storeUnderTest(self):
+        &quot;&quot;&quot;
+        Return a store for testing.
+        &quot;&quot;&quot;
+        return self._sqlCalendarStore1
+
+
+    def otherStoreUnderTest(self):
+        &quot;&quot;&quot;
+        Return a store for testing.
+        &quot;&quot;&quot;
+        return self._sqlCalendarStore2
+
+
+    def newOtherTransaction(self):
+        assert self.otherTransaction is None
+        store2 = self.otherStoreUnderTest()
+        self.otherTransaction = store2.newTransaction()
+        return self.otherTransaction
+
+
+    @inlineCallbacks
+    def otherCommit(self):
+        assert self.otherTransaction is not None
+        yield self.otherTransaction.commit()
+        self.otherTransaction = None
+
+
+    @inlineCallbacks
+    def otherAbort(self):
+        assert self.otherTransaction is not None
+        yield self.otherTransaction.abort()
+        self.otherTransaction = None
+
+
+    @inlineCallbacks
+    def makeStore(self, builder, internal, server1, server2):
+
+        directory = self.makeDirectory(internal, server1, server2)
+        store = yield builder.buildStore(self, self.notifierFactory, directory)
+        store.queryCacher = None     # Cannot use query caching
+        store.conduit = self.makeConduit(store)
+        returnValue(store)
+
+
+    def makeDirectory(self, internal, server1, server2):
+
+        directory = TestCalendarStoreDirectoryService()
+
+        # User accounts
+        for ctr in range(1, 100):
+            directory.addRecord(TestCalendarStoreDirectoryRecord(
+                &quot;user%02d&quot; % (ctr,),
+                (&quot;user%02d&quot; % (ctr,),),
+                &quot;User %02d&quot; % (ctr,),
+                frozenset((
+                    &quot;urn:uuid:user%02d&quot; % (ctr,),
+                    &quot;mailto:user%02d@example.com&quot; % (ctr,),
+                )),
+                thisServer=internal,
+                server=server1
+            ))
+
+        for ctr in range(1, 100):
+            directory.addRecord(TestCalendarStoreDirectoryRecord(
+                &quot;puser{:02d}&quot;.format(ctr),
+                (&quot;puser{:02d}&quot;.format(ctr),),
+                &quot;Puser {:02d}&quot;.format(ctr),
+                frozenset((
+                    &quot;urn:uuid:puser{:02d}&quot;.format(ctr),
+                    &quot;mailto:puser{:02d}@example.com&quot;.format(ctr),
+                )),
+                thisServer=not internal,
+                server=server2
+            ))
+
+        return directory
+
+
+    def makeConduit(self, store):
+        conduit = PoddingConduit(store)
+        conduit.conduitRequestClass = FakeConduitRequest
+        return conduit
+
+
+    @inlineCallbacks
+    def createShare(self, ownerGUID, shareeGUID, name=&quot;calendar&quot;):
+
+        home = yield self.homeUnderTest(name=ownerGUID, create=True)
+        calendar = yield home.calendarWithName(&quot;calendar&quot;)
+        yield calendar.inviteUserToShare(shareeGUID, _BIND_MODE_WRITE, &quot;shared&quot;, shareName=&quot;shared-calendar&quot;)
+        yield self.commit()
+
+        home2 = yield self.homeUnderTest(txn=self.newOtherTransaction(), name=shareeGUID)
+        yield home2.acceptShare(&quot;shared-calendar&quot;)
+        yield self.otherCommit()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -67,7 +67,7 @@
</span><span class="cx">     _BIND_MODE_INDIRECT, _HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema, splitSQLString
</span><span class="cx"> from txdav.common.icommondatastore import ConcurrentModification, \
</span><del>-    RecordNotAllowedError, ExternalShareFailed
</del><ins>+    RecordNotAllowedError, ExternalShareFailed, ShareNotAllowed
</ins><span class="cx"> from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
</span><span class="cx">     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
</span><span class="cx">     ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
</span><span class="lines">@@ -477,7 +477,7 @@
</span><span class="cx">         self._label = label
</span><span class="cx">         self._migrating = migrating
</span><span class="cx">         self._primaryHomeType = None
</span><del>-        self._disableCache = disableCache
</del><ins>+        self._disableCache = disableCache or not store.queryCachingEnabled()
</ins><span class="cx">         if disableCache:
</span><span class="cx">             self._queryCacher = None
</span><span class="cx">         else:
</span><span class="lines">@@ -1562,6 +1562,25 @@
</span><span class="cx"> 
</span><span class="cx">     _cacher = None  # Initialize in derived classes
</span><span class="cx"> 
</span><ins>+    @classmethod
+    @inlineCallbacks
+    def makeClass(cls, transaction, ownerUID, no_cache=False):
+        &quot;&quot;&quot;
+        Build the actual home class taking into account the possibility that we might need to
+        switch in the external version of the class.
+
+        @param transaction: transaction
+        @type transaction: L{CommonStoreTransaction}
+        @param ownerUID: owner UID of home to load
+        @type ownerUID: C{str}
+        @param no_cache: should cached query be used
+        @type no_cache: C{bool}
+        &quot;&quot;&quot;
+        home = cls(transaction, ownerUID)
+        actualHome = yield home.initFromStore(no_cache)
+        returnValue(actualHome)
+
+
</ins><span class="cx">     def __init__(self, transaction, ownerUID):
</span><span class="cx">         self._txn = transaction
</span><span class="cx">         self._ownerUID = ownerUID
</span><span class="lines">@@ -1708,6 +1727,10 @@
</span><span class="cx">                 actualHome = self
</span><span class="cx">             yield actualHome.initMetaDataFromStore()
</span><span class="cx">             yield actualHome._loadPropertyStore()
</span><ins>+
+            for factory_type, factory in self._txn._notifierFactories.items():
+                actualHome.addNotifier(factory_type, factory.newNotifier(actualHome))
+
</ins><span class="cx">             returnValue(actualHome)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -1756,10 +1779,7 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def homeWithUID(cls, txn, uid, create=False):
</span><del>-        homeObject = cls(txn, uid)
-        for factory_type, factory in txn._notifierFactories.items():
-            homeObject.addNotifier(factory_type, factory.newNotifier(homeObject))
-        homeObject = (yield homeObject.initFromStore())
</del><ins>+        homeObject = yield cls.makeClass(txn, uid)
</ins><span class="cx">         if homeObject is not None:
</span><span class="cx">             returnValue(homeObject)
</span><span class="cx">         else:
</span><span class="lines">@@ -1769,7 +1789,7 @@
</span><span class="cx">             # Determine if the user is local or external
</span><span class="cx">             record = txn.directoryService().recordWithUID(uid)
</span><span class="cx">             if record is None:
</span><del>-                raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {uid}&quot;.format(uid=uid))
</del><ins>+                raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {}&quot;.format(uid))
</ins><span class="cx"> 
</span><span class="cx">             state = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
</span><span class="cx"> 
</span><span class="lines">@@ -1794,10 +1814,7 @@
</span><span class="cx">                 yield savepoint.rollback(txn)
</span><span class="cx"> 
</span><span class="cx">                 # Retry the query - row may exist now, if not re-raise
</span><del>-                homeObject = cls(txn, uid)
-                for factory_type, factory in txn._notifierFactories.items():
-                    homeObject.addNotifier(factory_type, factory.newNotifier(homeObject))
-                homeObject = (yield homeObject.initFromStore())
</del><ins>+                homeObject = yield cls.makeClass(txn, uid)
</ins><span class="cx">                 if homeObject:
</span><span class="cx">                     returnValue(homeObject)
</span><span class="cx">                 else:
</span><span class="lines">@@ -1808,10 +1825,7 @@
</span><span class="cx">                 # Note that we must not cache the owner_uid-&gt;resource_id
</span><span class="cx">                 # mapping in _cacher when creating as we don't want that to appear
</span><span class="cx">                 # until AFTER the commit
</span><del>-                home = cls(txn, uid)
-                for factory_type, factory in txn._notifierFactories.items():
-                    home.addNotifier(factory_type, factory.newNotifier(home))
-                home = (yield home.initFromStore(no_cache=True))
</del><ins>+                home = yield cls.makeClass(txn, uid, no_cache=True)
</ins><span class="cx">                 yield home.createdHome()
</span><span class="cx">                 returnValue(home)
</span><span class="cx"> 
</span><span class="lines">@@ -2533,165 +2547,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class CommonHomeExternal(CommonHome):
-    &quot;&quot;&quot;
-    A CommonHome for a user not hosted on this system, but on another pod. This is needed to provide a
-    &quot;reference&quot; to the external user so we can share with them. Actual operations to list child resources, etc
-    are all stubbed out since no data for the user is actually hosted in this store.
-    &quot;&quot;&quot;
-
-    def __init__(self, transaction, ownerUID, resourceID):
-        super(CommonHomeExternal, self).__init__(transaction, ownerUID)
-        self._resourceID = resourceID
-        self._status = _HOME_STATUS_EXTERNAL
-
-
-    def initFromStore(self, no_cache=False):
-        &quot;&quot;&quot;
-        Never called - this should be done by CommonHome.initFromStore only.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def external(self):
-        &quot;&quot;&quot;
-        Is this an external home.
-
-        @return: a string.
-        &quot;&quot;&quot;
-        return True
-
-
-    def children(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def loadChildren(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def listChildren(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def objectWithShareUID(self, shareUID):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def invitedObjectWithShareUID(self, shareUID):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    @memoizedKey(&quot;name&quot;, &quot;_children&quot;)
-    @inlineCallbacks
-    def createChildWithName(self, name, externalID=None):
-        &quot;&quot;&quot;
-        No real children - only external ones.
-        &quot;&quot;&quot;
-        if externalID is None:
-            raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-        child = yield super(CommonHomeExternal, self).createChildWithName(name, externalID)
-        returnValue(child)
-
-
-    def removeChildWithName(self, name):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def syncToken(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def resourceNamesSinceRevision(self, revision, depth):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    @inlineCallbacks
-    def _loadPropertyStore(self):
-        &quot;&quot;&quot;
-        No property store - stub to a NonePropertyStore.
-        &quot;&quot;&quot;
-        props = yield PropertyStore.load(
-            self.uid(),
-            self.uid(),
-            self._txn,
-            self._resourceID,
-            notifyCallback=self.notifyChanged
-        )
-        self._propertyStore = props
-
-
-    def properties(self):
-        return self._propertyStore
-
-
-    def objectResourcesWithUID(self, uid, ignore_children=[], allowShared=True):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def objectResourceWithID(self, rid):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-    def notifyChanged(self):
-        &quot;&quot;&quot;
-        Notifications are not handled for external homes - make this a no-op.
-        &quot;&quot;&quot;
-        return succeed(None)
-
-
-    def bumpModified(self):
-        &quot;&quot;&quot;
-        No changes recorded for external homes - make this a no-op.
-        &quot;&quot;&quot;
-        return succeed(None)
-
-
-    def removeUnacceptedShares(self):
-        &quot;&quot;&quot;
-        No children.
-        &quot;&quot;&quot;
-        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-#    def ownerHomeAndChildNameForChildID(self, resourceID):
-#        &quot;&quot;&quot;
-#        No children.
-#        &quot;&quot;&quot;
-#        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
-
-
-
</del><span class="cx"> class _SharedSyncLogic(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Logic for maintaining sync-token shared between notification collections and
</span><span class="lines">@@ -3507,7 +3362,7 @@
</span><span class="cx">                 subt,
</span><span class="cx">                 homeID=shareeHome._resourceID,
</span><span class="cx">                 resourceID=self._resourceID,
</span><del>-                externalID=None,
</del><ins>+                externalID=self._externalID,
</ins><span class="cx">                 name=newName,
</span><span class="cx">                 mode=mode,
</span><span class="cx">                 bindStatus=status,
</span><span class="lines">@@ -3997,6 +3852,7 @@
</span><span class="cx">         &quot;_resourceID&quot;,
</span><span class="cx">     )
</span><span class="cx"> 
</span><ins>+    _externalClass = None
</ins><span class="cx">     _objectResourceClass = None
</span><span class="cx"> 
</span><span class="cx">     _bindSchema = None
</span><span class="lines">@@ -4007,6 +3863,142 @@
</span><span class="cx">     _objectSchema = None
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
+    @inlineCallbacks
+    def makeClass(cls, home, bindData, additionalBindData, metadataData, propstore=None, ownerHome=None):
+        &quot;&quot;&quot;
+        Given the various database rows, build the actual class.
+
+        @param home: the parent home object
+        @type home: L{CommonHome}
+        @param bindData: the standard set of bind columns
+        @type bindData: C{list}
+        @param additionalBindData: additional bind data specific to sub-classes
+        @type additionalBindData: C{list}
+        @param metadataData: metadata data
+        @type metadataData: C{list}
+        @param propstore: a property store to use, or C{None} to load it automatically
+        @type propstore: L{PropertyStore}
+        @param ownerHome: the home of the owner, or C{None} to figure it out automatically
+        @type ownerHome: L{CommonHome}
+
+        @return: the constructed child class
+        @rtype: L{CommonHomeChild}
+        &quot;&quot;&quot;
+
+        bindMode, _ignore_homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = bindData
+
+        if ownerHome is None:
+            if bindMode == _BIND_MODE_OWN:
+                ownerHome = home
+                ownerName = name
+            else:
+                ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
+        else:
+            ownerName = None
+
+        c = cls._externalClass if ownerHome.external() else cls
+        child = c(
+            home=home,
+            name=name,
+            resourceID=resourceID,
+            mode=bindMode,
+            status=bindStatus,
+            revision=bindRevision,
+            message=bindMessage,
+            ownerHome=ownerHome,
+            ownerName=ownerName,
+            externalID=externalID,
+        )
+
+        if additionalBindData:
+            for attr, value in zip(child.additionalBindAttributes(), additionalBindData):
+                setattr(child, attr, value)
+
+        if metadataData:
+            for attr, value in zip(child.metadataAttributes(), metadataData):
+                setattr(child, attr, value)
+
+        # We have to re-adjust the property store object to account for possible shared
+        # collections as previously we loaded them all as if they were owned
+        if propstore and bindMode != _BIND_MODE_OWN:
+            propstore._setDefaultUserUID(ownerHome.uid())
+        yield child._loadPropertyStore(propstore)
+
+        returnValue(child)
+
+
+    @classmethod
+    @inlineCallbacks
+    def _getDBData(cls, home, name, resourceID, externalID):
+        &quot;&quot;&quot;
+        Given a set of identifying information, load the data rows for the object. Only one of
+        L{name}, L{resourceID} or L{externalID} is specified - others are C{None}.
+
+        @param home: the parent home object
+        @type home: L{CommonHome}
+        @param name: the resource name
+        @type name: C{str}
+        @param resourceID: the resource ID
+        @type resourceID: C{int}
+        @param externalID: the resource ID of the external (cross-pod) referenced item
+        @type externalID: C{int}
+        &quot;&quot;&quot;
+
+        # Get the bind row data
+        row = None
+        queryCacher = home._txn._queryCacher
+
+        if queryCacher:
+            # Retrieve data from cache
+            if name:
+                cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
+            elif resourceID:
+                cacheKey = queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID)
+            elif externalID:
+                cacheKey = queryCacher.keyForObjectWithExternalID(home._resourceID, externalID)
+            row = yield queryCacher.get(cacheKey)
+
+        if row is None:
+            # No cached copy
+            if name:
+                rows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
+            elif resourceID:
+                rows = yield cls._bindForResourceIDAndHomeID.on(home._txn, resourceID=resourceID, homeID=home._resourceID)
+            elif externalID:
+                rows = yield cls._bindForExternalIDAndHomeID.on(home._txn, externalID=externalID, homeID=home._resourceID)
+            row = rows[0] if rows else None
+
+        if not row:
+            returnValue(None)
+
+        if queryCacher:
+            # Cache the result
+            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithName(home._resourceID, name), row)
+            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID), row)
+            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithExternalID(home._resourceID, externalID), row)
+
+        bindData = row[:cls.bindColumnCount]
+        additionalBindData = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+        resourceID = bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)]
+
+        # Get the matching metadata data
+        metadataData = None
+        if queryCacher:
+            # Retrieve from cache
+            cacheKey = queryCacher.keyForHomeChildMetaData(resourceID)
+            metadataData = yield queryCacher.get(cacheKey)
+
+        if metadataData is None:
+            # No cached copy
+            metadataData = (yield cls._metadataByIDQuery.on(home._txn, resourceID=resourceID))[0]
+            if queryCacher:
+                # Cache the results
+                yield queryCacher.setAfterCommit(home._txn, cacheKey, metadataData)
+
+        returnValue((bindData, additionalBindData, metadataData,))
+
+
</ins><span class="cx">     def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
</span><span class="cx"> 
</span><span class="cx">         self._home = home
</span><span class="lines">@@ -4062,7 +4054,7 @@
</span><span class="cx">         rows = yield cls._acceptedBindForHomeID.on(
</span><span class="cx">             home._txn, homeID=home._resourceID
</span><span class="cx">         )
</span><del>-        names = [row[3] for row in rows]
</del><ins>+        names = [row[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] for row in rows]
</ins><span class="cx">         returnValue(names)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -4095,41 +4087,16 @@
</span><span class="cx"> 
</span><span class="cx">         # Create the actual objects merging in properties
</span><span class="cx">         for dataRow in dataRows:
</span><del>-            bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
-            additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
-            metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
</del><ins>+            bindData = dataRow[:cls.bindColumnCount] #@UnusedVariable
+            resourceID = bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)]
+            additionalBindData = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+            metadataData = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
+            propstore = propertyStores.get(resourceID, None)
</ins><span class="cx"> 
</span><del>-            if bindMode == _BIND_MODE_OWN:
-                ownerHome = home
-                ownerName = bindName
-            else:
-                #TODO: get all ownerHomeIDs at once
-                ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
-
-            child = cls(
-                home=home,
-                name=bindName,
-                resourceID=resourceID,
-                mode=bindMode,
-                status=bindStatus,
-                revision=bindRevision,
-                message=bindMessage,
-                ownerHome=ownerHome,
-                ownerName=ownerName,
-                externalID=externalID,
-            )
-            for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
-                setattr(child, attr, value)
-            for attr, value in zip(cls.metadataAttributes(), metadata):
-                setattr(child, attr, value)
</del><ins>+            child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, propstore)
</ins><span class="cx">             child._syncTokenRevision = revisions[resourceID]
</span><del>-            propstore = propertyStores.get(resourceID, None)
-            # We have to re-adjust the property store object to account for possible shared
-            # collections as previously we loaded them all as if they were owned
-            if propstore and bindMode != _BIND_MODE_OWN:
-                propstore._setDefaultUserUID(ownerHome.uid())
-            yield child._loadPropertyStore(propstore)
</del><span class="cx">             results.append(child)
</span><ins>+
</ins><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -4163,63 +4130,17 @@
</span><span class="cx">         @return: an L{CommonHomeChild} or C{None} if no such child
</span><span class="cx">             exists.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        rows = None
-        queryCacher = home._txn._queryCacher
</del><span class="cx"> 
</span><del>-        if queryCacher:
-            # Retrieve data from cache
-            if name:
-                cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
-            elif resourceID:
-                cacheKey = queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID)
-            elif externalID:
-                cacheKey = queryCacher.keyForObjectWithExternalID(home._resourceID, externalID)
-            rows = yield queryCacher.get(cacheKey)
-
-        if rows is None:
-            # No cached copy
-            if name:
-                rows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
-            elif resourceID:
-                rows = yield cls._bindForResourceIDAndHomeID.on(home._txn, resourceID=resourceID, homeID=home._resourceID)
-            elif resourceID:
-                rows = yield cls._bindForExternalIDAndHomeID.on(home._txn, externalID=externalID, homeID=home._resourceID)
-
-        if not rows:
</del><ins>+        dbData = yield cls._getDBData(home, name, resourceID, externalID)
+        if dbData is None:
</ins><span class="cx">             returnValue(None)
</span><ins>+        bindData, additionalBindData, metadataData = dbData
</ins><span class="cx"> 
</span><del>-        row = rows[0]
-        bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
-
-        if queryCacher:
-            # Cache the result
-            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithName(home._resourceID, name), rows)
-            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID), rows)
-            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithExternalID(home._resourceID, externalID), rows)
-
</del><ins>+        bindStatus = bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)]
</ins><span class="cx">         if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx">             returnValue(None)
</span><del>-        additionalBind = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</del><span class="cx"> 
</span><del>-        if bindMode == _BIND_MODE_OWN:
-            ownerHome = home
-            ownerName = name
-        else:
-            ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
-
-        child = cls(
-            home=home,
-            name=name,
-            resourceID=resourceID,
-            mode=bindMode,
-            status=bindStatus,
-            revision=bindRevision,
-            message=bindMessage,
-            ownerHome=ownerHome,
-            ownerName=ownerName,
-            externalID=externalID,
-        )
-        yield child.initFromStore(additionalBind)
</del><ins>+        child = yield cls.makeClass(home, bindData, additionalBindData, metadataData)
</ins><span class="cx">         returnValue(child)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -4289,36 +4210,6 @@
</span><span class="cx">                       Where=child.RESOURCE_ID == Parameter(&quot;resourceID&quot;))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def initFromStore(self, additionalBind=None):
-        &quot;&quot;&quot;
-        Initialise this object from the store, based on its already-populated
-        resource ID. We read in and cache all the extra metadata from the DB to
-        avoid having to do DB queries for those individually later.
-        &quot;&quot;&quot;
-        queryCacher = self._txn._queryCacher
-        if queryCacher:
-            # Retrieve from cache
-            cacheKey = queryCacher.keyForHomeChildMetaData(self._resourceID)
-            dataRows = yield queryCacher.get(cacheKey)
-        else:
-            dataRows = None
-        if dataRows is None:
-            # No cached copy
-            dataRows = (yield self._metadataByIDQuery.on(self._txn, resourceID=self._resourceID))[0]
-            if queryCacher:
-                # Cache the results
-                yield queryCacher.setAfterCommit(self._txn, cacheKey, dataRows)
-
-        if additionalBind:
-            for attr, value in zip(self.additionalBindAttributes(), additionalBind):
-                setattr(self, attr, value)
-
-        for attr, value in zip(self.metadataAttributes(), dataRows):
-            setattr(self, attr, value)
-        yield self._loadPropertyStore()
-
-
</del><span class="cx">     def id(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Retrieve the store identifier for this collection.
</span><span class="lines">@@ -4329,6 +4220,15 @@
</span><span class="cx">         return self._resourceID
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def external_id(self):
+        &quot;&quot;&quot;
+        Retrieve the external store identifier for this collection.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return self._externalID
+
+
</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">@@ -4385,6 +4285,10 @@
</span><span class="cx"> 
</span><span class="cx">         @return: a L{Deferred} which fires when the modification is complete.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
+        if self.isShared() or self.external():
+            raise ShareNotAllowed(&quot;Cannot rename a shared collection&quot;)
+
</ins><span class="cx">         oldName = self._name
</span><span class="cx"> 
</span><span class="cx">         yield self.invalidateQueryCache()
</span><span class="lines">@@ -4521,8 +4425,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def countObjectResources(self):
</span><span class="cx">         if self._objectNames is None:
</span><del>-            rows = yield self._objectCountQuery.on(
-                self._txn, resourceID=self._resourceID)
</del><ins>+            rows = yield self._objectCountQuery.on(self._txn, resourceID=self._resourceID)
</ins><span class="cx">             returnValue(rows[0][0])
</span><span class="cx">         returnValue(len(self._objectNames))
</span><span class="cx"> 
</span><span class="lines">@@ -4612,7 +4515,7 @@
</span><span class="cx">         obj = cls._objectSchema
</span><span class="cx">         return Select(
</span><span class="cx">             [obj.UID], From=obj,
</span><del>-            Where=(obj.UID == Parameter(&quot;name&quot;)
</del><ins>+            Where=(obj.RESOURCE_NAME == Parameter(&quot;name&quot;)
</ins><span class="cx">                   ).And(obj.PARENT_RESOURCE_ID == Parameter(&quot;resourceID&quot;)))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -4971,10 +4874,84 @@
</span><span class="cx">         &quot;_parentCollection&quot;,
</span><span class="cx">     )
</span><span class="cx"> 
</span><ins>+    _externalClass = None
</ins><span class="cx">     _objectSchema = None
</span><span class="cx"> 
</span><span class="cx">     BATCH_LOAD_SIZE = 50
</span><span class="cx"> 
</span><ins>+
+    @classmethod
+    @inlineCallbacks
+    def makeClass(cls, parent, objectData, propstore=None):
+        &quot;&quot;&quot;
+        Given the various database rows, build the actual class.
+
+        @param parent: the parent collection object
+        @type parent: L{CommonHomeChild}
+        @param objectData: the standard set of object columns
+        @type objectData: C{list}
+        @param propstore: a property store to use, or C{None} to load it automatically
+        @type propstore: L{PropertyStore}
+
+        @return: the constructed child class
+        @rtype: L{CommonHomeChild}
+        &quot;&quot;&quot;
+
+        c = cls._externalClass if parent.external() else cls
+        child = c(
+            parent,
+            objectData[cls._allColumns().index(cls._objectSchema.RESOURCE_NAME)],
+            objectData[cls._allColumns().index(cls._objectSchema.UID)],
+        )
+
+        for attr, value in zip(child._rowAttributes(), objectData):
+            setattr(child, attr, value)
+
+        yield child._loadPropertyStore(propstore)
+
+        returnValue(child)
+
+
+    @classmethod
+    @inlineCallbacks
+    def _getDBData(cls, parent, name, uid, resourceID):
+        &quot;&quot;&quot;
+        Given a set of identifying information, load the data rows for the object. Only one of
+        L{name}, L{uid} or L{resourceID} is specified - others are C{None}.
+
+        @param parent: the parent collection object
+        @type parent: L{CommonHomeChild}
+        @param name: the resource name
+        @type name: C{str}
+        @param uid: the UID of the data
+        @type uid: C{str}
+        @param resourceID: the resource ID
+        @type resourceID: C{int}
+        &quot;&quot;&quot;
+
+        rows = None
+        if name:
+            rows = yield cls._allColumnsWithParentAndName.on(
+                parent._txn,
+                name=name,
+                parentID=parent._resourceID
+            )
+        elif uid:
+            rows = yield cls._allColumnsWithParentAndUID.on(
+                parent._txn,
+                uid=uid,
+                parentID=parent._resourceID
+            )
+        elif resourceID:
+            rows = yield cls._allColumnsWithParentAndID.on(
+                parent._txn,
+                resourceID=resourceID,
+                parentID=parent._resourceID
+            )
+
+        returnValue(rows[0] if rows else None)
+
+
</ins><span class="cx">     def __init__(self, parent, name, uid, resourceID=None, options=None): #@UnusedVariable
</span><span class="cx">         self._parentCollection = parent
</span><span class="cx">         self._resourceID = resourceID
</span><span class="lines">@@ -4992,7 +4969,7 @@
</span><span class="cx">     @classproperty
</span><span class="cx">     def _allColumnsWithParentQuery(cls): #@NoSelf
</span><span class="cx">         obj = cls._objectSchema
</span><del>-        return Select(cls._allColumns, From=obj,
</del><ins>+        return Select(cls._allColumns(), From=obj,
</ins><span class="cx">                       Where=obj.PARENT_RESOURCE_ID == Parameter(&quot;parentID&quot;))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -5033,11 +5010,10 @@
</span><span class="cx"> 
</span><span class="cx">         # Create the actual objects merging in properties
</span><span class="cx">         for row in dataRows:
</span><del>-            child = cls(parent, &quot;&quot;, None)
-            child._initFromRow(tuple(row))
-            yield child._loadPropertyStore(
-                props=propertyStores.get(child._resourceID, None)
-            )
</del><ins>+            resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+            propstore = propertyStores.get(resourceID, None)
+
+            child = yield cls.makeClass(parent, row, propstore=propstore)
</ins><span class="cx">             results.append(child)
</span><span class="cx"> 
</span><span class="cx">         returnValue(results)
</span><span class="lines">@@ -5062,7 +5038,7 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     def _allColumnsWithParentAndNamesQuery(cls, names):
</span><span class="cx">         obj = cls._objectSchema
</span><del>-        return Select(cls._allColumns, From=obj,
</del><ins>+        return Select(cls._allColumns(), From=obj,
</ins><span class="cx">                       Where=(obj.PARENT_RESOURCE_ID == Parameter(&quot;parentID&quot;)).And(
</span><span class="cx">                           obj.RESOURCE_NAME.In(Parameter(&quot;names&quot;, len(names)))))
</span><span class="cx"> 
</span><span class="lines">@@ -5107,11 +5083,10 @@
</span><span class="cx"> 
</span><span class="cx">         # Create the actual objects merging in properties
</span><span class="cx">         for row in dataRows:
</span><del>-            child = cls(parent, &quot;&quot;, None)
-            child._initFromRow(tuple(row))
-            yield child._loadPropertyStore(
-                props=propertyStores.get(child._resourceID, None)
-            )
</del><ins>+            resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+            propstore = propertyStores.get(resourceID, None)
+
+            child = yield cls.makeClass(parent, row, propstore=propstore)
</ins><span class="cx">             results.append(child)
</span><span class="cx"> 
</span><span class="cx">         returnValue(results)
</span><span class="lines">@@ -5119,18 +5094,29 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def objectWithName(cls, parent, name, uid):
</span><del>-        objectResource = cls(parent, name, uid, None)
-        return objectResource.initFromStore()
</del><ins>+        return cls._objectWithNameOrID(parent, name, uid, None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def objectWithID(cls, parent, resourceID):
</span><del>-        objectResource = cls(parent, None, None, resourceID)
-        return objectResource.initFromStore()
</del><ins>+        return cls._objectWithNameOrID(parent, None, None, resourceID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def _objectWithNameOrID(cls, parent, name, uid, resourceID):
+
+        row = yield cls._getDBData(parent, name, uid, resourceID)
+
+        if row:
+            child = yield cls.makeClass(parent, row)
+            returnValue(child)
+        else:
+            returnValue(None)
+
+
+    @classmethod
+    @inlineCallbacks
</ins><span class="cx">     def create(cls, parent, name, component, options=None):
</span><span class="cx"> 
</span><span class="cx">         child = (yield parent.objectResourceWithName(name))
</span><span class="lines">@@ -5140,13 +5126,13 @@
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise ObjectResourceNameNotAllowedError(name)
</span><span class="cx"> 
</span><del>-        objectResource = cls(parent, name, None, None, options=options)
</del><ins>+        c = cls._externalClass if parent.external() else cls
+        objectResource = c(parent, name, None, None, options=options)
</ins><span class="cx">         yield objectResource.setComponent(component, inserting=True)
</span><span class="cx">         yield objectResource._loadPropertyStore(created=True)
</span><span class="cx"> 
</span><span class="cx">         # Note: setComponent triggers a notification, so we don't need to
</span><span class="cx">         # call notify( ) here like we do for object removal.
</span><del>-
</del><span class="cx">         returnValue(objectResource)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -5157,7 +5143,7 @@
</span><span class="cx">         parameter and a given instance column matches a given parameter name.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return Select(
</span><del>-            cls._allColumns, From=cls._objectSchema,
</del><ins>+            cls._allColumns(), From=cls._objectSchema,
</ins><span class="cx">             Where=(column == Parameter(paramName)).And(
</span><span class="cx">                 cls._objectSchema.PARENT_RESOURCE_ID == Parameter(&quot;parentID&quot;))
</span><span class="cx">         )
</span><span class="lines">@@ -5178,38 +5164,7 @@
</span><span class="cx">         return cls._allColumnsWithParentAnd(cls._objectSchema.RESOURCE_ID, &quot;resourceID&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def initFromStore(self):
-        &quot;&quot;&quot;
-        Initialise this object from the store. We read in and cache all the
-        extra metadata from the DB to avoid having to do DB queries for those
-        individually later. Either the name or uid is present, so we have to
-        tweak the query accordingly.
-
-        @return: L{self} if object exists in the DB, else C{None}
-        &quot;&quot;&quot;
-
-        if self._name:
-            rows = yield self._allColumnsWithParentAndName.on(
-                self._txn, name=self._name,
-                parentID=self._parentCollection._resourceID)
-        elif self._uid:
-            rows = yield self._allColumnsWithParentAndUID.on(
-                self._txn, uid=self._uid,
-                parentID=self._parentCollection._resourceID)
-        elif self._resourceID:
-            rows = yield self._allColumnsWithParentAndID.on(
-                self._txn, resourceID=self._resourceID,
-                parentID=self._parentCollection._resourceID)
-        if rows:
-            self._initFromRow(tuple(rows[0]))
-            yield self._loadPropertyStore()
-            returnValue(self)
-        else:
-            returnValue(None)
-
-
-    @classproperty
</del><ins>+    @classmethod
</ins><span class="cx">     def _allColumns(cls): #@NoSelf
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Full set of columns in the object table that need to be loaded to
</span><span class="lines">@@ -5227,18 +5182,17 @@
</span><span class="cx">         ]
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _initFromRow(self, row):
-        &quot;&quot;&quot;
-        Given a select result using the columns from L{_allColumns}, initialize
-        the object resource state.
-        &quot;&quot;&quot;
-        (self._resourceID,
-         self._name,
-         self._uid,
-         self._md5,
-         self._size,
-         self._created,
-         self._modified,) = tuple(row)
</del><ins>+    @classmethod
+    def _rowAttributes(cls): #@NoSelf
+        return (
+            &quot;_resourceID&quot;,
+            &quot;_name&quot;,
+            &quot;_uid&quot;,
+            &quot;_md5&quot;,
+            &quot;_size&quot;,
+            &quot;_created&quot;,
+            &quot;_modified&quot;,
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -5542,11 +5496,11 @@
</span><span class="cx">             # Determine if the user is local or external
</span><span class="cx">             record = txn.directoryService().recordWithUID(uid)
</span><span class="cx">             if record is None:
</span><del>-                raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {uid}&quot;.format(uid=uid))
</del><ins>+                raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {}&quot;.format(uid))
</ins><span class="cx"> 
</span><span class="cx">             state = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
</span><span class="cx">             if state == _HOME_STATUS_EXTERNAL:
</span><del>-                raise RecordNotAllowedError(&quot;Cannot store notifications for external user: {uid}&quot;.format(uid=uid))
</del><ins>+                raise RecordNotAllowedError(&quot;Cannot store notifications for external user: {}&quot;.format(uid))
</ins><span class="cx"> 
</span><span class="cx">             # Use savepoint so we can do a partial rollback if there is a race
</span><span class="cx">             # condition where this row has already been inserted
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_externalpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py (0 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -0,0 +1,354 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from txdav.common.icommondatastore import NonExistentExternalShare, \
+    ExternalShareFailed
+&quot;&quot;&quot;
+SQL data store.
+&quot;&quot;&quot;
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+from twext.internet.decorate import memoizedKey
+from twext.python.log import Logger
+
+from txdav.base.propertystore.sql import PropertyStore
+from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
+    CommonObjectResource
+from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL
+
+
+log = Logger()
+
+class CommonHomeExternal(CommonHome):
+    &quot;&quot;&quot;
+    A CommonHome for a user not hosted on this system, but on another pod. This is needed to provide a
+    &quot;reference&quot; to the external user so we can share with them. Actual operations to list child resources, etc
+    are all stubbed out since no data for the user is actually hosted in this store.
+    &quot;&quot;&quot;
+
+    def __init__(self, transaction, ownerUID, resourceID):
+        super(CommonHomeExternal, self).__init__(transaction, ownerUID)
+        self._resourceID = resourceID
+        self._status = _HOME_STATUS_EXTERNAL
+
+
+    def initFromStore(self, no_cache=False):
+        &quot;&quot;&quot;
+        Never called - this should be done by CommonHome.initFromStore only.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def external(self):
+        &quot;&quot;&quot;
+        Is this an external home.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return True
+
+
+    def children(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def loadChildren(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def listChildren(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def objectWithShareUID(self, shareUID):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def invitedObjectWithShareUID(self, shareUID):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    @memoizedKey(&quot;name&quot;, &quot;_children&quot;)
+    @inlineCallbacks
+    def createChildWithName(self, name, externalID=None):
+        &quot;&quot;&quot;
+        No real children - only external ones.
+        &quot;&quot;&quot;
+        if externalID is None:
+            raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+        child = yield super(CommonHomeExternal, self).createChildWithName(name, externalID)
+        returnValue(child)
+
+
+    def removeChildWithName(self, name):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def syncToken(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def resourceNamesSinceRevision(self, revision, depth):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    @inlineCallbacks
+    def _loadPropertyStore(self):
+        &quot;&quot;&quot;
+        No property store - stub to a NonePropertyStore.
+        &quot;&quot;&quot;
+        props = yield PropertyStore.load(
+            self.uid(),
+            self.uid(),
+            self._txn,
+            self._resourceID,
+            notifyCallback=self.notifyChanged
+        )
+        self._propertyStore = props
+
+
+    def properties(self):
+        return self._propertyStore
+
+
+    def objectResourcesWithUID(self, uid, ignore_children=[], allowShared=True):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def objectResourceWithID(self, rid):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def notifyChanged(self):
+        &quot;&quot;&quot;
+        Notifications are not handled for external homes - make this a no-op.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def bumpModified(self):
+        &quot;&quot;&quot;
+        No changes recorded for external homes - make this a no-op.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def removeUnacceptedShares(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+#    def ownerHomeAndChildNameForChildID(self, resourceID):
+#        &quot;&quot;&quot;
+#        No children.
+#        &quot;&quot;&quot;
+#        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+
+class CommonHomeChildExternal(CommonHomeChild):
+    &quot;&quot;&quot;
+    A CommonHomeChild for a collection not hosted on this system, but on another pod. This will forward
+    specific apis to the other pod using cross-pod requests.
+    &quot;&quot;&quot;
+
+    def external(self):
+        &quot;&quot;&quot;
+        Is this an external home.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return True
+
+
+    def fixNonExistentExternalShare(self):
+        &quot;&quot;&quot;
+        An external request has returned and indicates the external share no longer exists. That
+        means this shared resource is an &quot;orphan&quot; and needs to be remove (uninvited) to clean things up.
+        &quot;&quot;&quot;
+        log.error(&quot;Non-existent share detected and removed for {share}&quot;, share=self)
+        ownerView = yield self.ownerView()
+        yield ownerView.removeShare(self)
+
+
+    def remove(self, rid):
+        &quot;&quot;&quot;
+        External shares are never removed directly - instead they must be &quot;uninvited&quot;.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeChildExternal: not supported&quot;)
+
+
+    @inlineCallbacks
+    def objectResources(self):
+        raise NotImplementedError(&quot;TODO: external resource&quot;)
+
+
+    @inlineCallbacks
+    def objectResourcesWithNames(self, names):
+        raise NotImplementedError(&quot;TODO: external resource&quot;)
+
+
+    @inlineCallbacks
+    def listObjectResources(self):
+        if self._objectNames is None:
+            try:
+                self._objectNames = yield self._txn.store().conduit.send_listobjects(self)
+            except NonExistentExternalShare:
+                yield self.fixNonExistentExternalShare()
+                raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        returnValue(self._objectNames)
+
+
+    @inlineCallbacks
+    def countObjectResources(self):
+        if self._objectNames is None:
+            try:
+                count = yield self._txn.store().conduit.send_countobjects(self)
+            except NonExistentExternalShare:
+                yield self.fixNonExistentExternalShare()
+                raise ExternalShareFailed(&quot;External share does not exist&quot;)
+            returnValue(count)
+        returnValue(len(self._objectNames))
+
+
+    def objectResourceWithName(self, name):
+        raise NotImplementedError(&quot;TODO: external resource&quot;)
+
+
+    def objectResourceWithUID(self, uid):
+        raise NotImplementedError(&quot;TODO: external resource&quot;)
+
+
+    def objectResourceWithID(self, resourceID):
+        raise NotImplementedError(&quot;TODO: external resource&quot;)
+
+
+    @inlineCallbacks
+    def resourceNameForUID(self, uid):
+        try:
+            resource = self._objects[uid]
+            returnValue(resource.name() if resource else None)
+        except KeyError:
+            pass
+
+        try:
+            name = yield self._txn.store().conduit.send_resourcenameforuid(self, uid)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        if name:
+            returnValue(name)
+        else:
+            self._objects[uid] = None
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def resourceUIDForName(self, name):
+        try:
+            resource = self._objects[name]
+            returnValue(resource.uid() if resource else None)
+        except KeyError:
+            pass
+
+        try:
+            uid = yield self._txn.store().conduit.send_resourceuidforname(self, name)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        if uid:
+            returnValue(uid)
+        else:
+            self._objects[name] = None
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def createObjectResourceWithName(self, name, component, options=None):
+        raise NotImplementedError(&quot;TODO: external resource&quot;)
+
+
+    @inlineCallbacks
+    def moveObjectResource(self, child, newparent, newname=None):
+        raise NotImplementedError(&quot;TODO: external resource&quot;)
+
+
+    @inlineCallbacks
+    def syncToken(self):
+        if self._syncTokenRevision is None:
+            try:
+                token = yield self._txn.store().conduit.send_synctoken(self)
+                self._syncTokenRevision = self.revisionFromToken(token)
+            except NonExistentExternalShare:
+                yield self.fixNonExistentExternalShare()
+                raise ExternalShareFailed(&quot;External share does not exist&quot;)
+        returnValue((&quot;%s_%s&quot; % (self._externalID, self._syncTokenRevision,)))
+
+
+    @inlineCallbacks
+    def resourceNamesSinceRevision(self, revision):
+        try:
+            names = yield self._txn.store().conduit.send_resourcenamessincerevision(self, revision)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        returnValue(names)
+
+
+
+class CommonObjectResourceExternal(CommonObjectResource):
+    &quot;&quot;&quot;
+    A CommonObjectResource for a resource not hosted on this system, but on another pod. This will forward
+    specific apis to the other pod using cross-pod requests.
+    &quot;&quot;&quot;
+    pass
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/test_sql.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/test_sql.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/test_sql.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -13,6 +13,7 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><ins>+from txdav.caldav.datastore.test.util import buildDirectoryRecord
</ins><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Tests for L{txdav.common.datastore.sql}.
</span><span class="lines">@@ -34,6 +35,11 @@
</span><span class="cx"> from twext.enterprise.dal.syntax import Insert
</span><span class="cx"> from txdav.common.datastore.sql import fixUUIDNormalization
</span><span class="cx"> 
</span><ins>+from uuid import UUID
+exampleUID = UUID(&quot;a&quot; * 32)
+denormalizedUID = str(exampleUID)
+normalizedUID = denormalizedUID.upper()
+
</ins><span class="cx"> class CommonSQLStoreTests(CommonCommonTests, TestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Tests for shared functionality in L{txdav.common.datastore.sql}.
</span><span class="lines">@@ -46,6 +52,9 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         yield super(CommonSQLStoreTests, self).setUp()
</span><span class="cx">         self._sqlStore = yield buildStore(self, self.notifierFactory)
</span><ins>+        self._sqlStore.directoryService().addRecord(buildDirectoryRecord(denormalizedUID))
+        self._sqlStore.directoryService().addRecord(buildDirectoryRecord(normalizedUID))
+        self._sqlStore.directoryService().addRecord(buildDirectoryRecord(&quot;uid&quot;))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def storeUnderTest(self):
</span><span class="lines">@@ -421,10 +430,3 @@
</span><span class="cx">         yield fixUUIDNormalization(self.storeUnderTest())
</span><span class="cx">         self.assertEqual((yield self.allHomeUIDs(schema.ADDRESSBOOK_HOME)),
</span><span class="cx">                          [[normalizedUID]])
</span><del>-
-
-
-from uuid import UUID
-exampleUID = UUID(&quot;a&quot; * 32)
-denormalizedUID = str(exampleUID)
-normalizedUID = denormalizedUID.upper()
</del></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/util.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/util.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/test/util.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -122,30 +122,68 @@
</span><span class="cx"> 
</span><span class="cx">     implements(IStoreDirectoryRecord)
</span><span class="cx"> 
</span><del>-    def __init__(self, uid, shortNames, fullName):
</del><ins>+    def __init__(self, uid, shortNames, fullName, thisServer=True, server=None):
</ins><span class="cx">         self.uid = uid
</span><span class="cx">         self.shortNames = shortNames
</span><span class="cx">         self.fullName = fullName
</span><span class="cx">         self.displayName = self.fullName if self.fullName else self.shortNames[0]
</span><ins>+        self._thisServer = thisServer
+        self._server = server
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def thisServer(self):
+        return self._thisServer
</ins><span class="cx"> 
</span><ins>+
+    def server(self):
+        return self._server
+
+
+
+def buildDirectory(homes=None):
+
+    directory = TestStoreDirectoryService()
+
+    # User accounts
+    for ctr in range(1, 100):
+        directory.addRecord(TestStoreDirectoryRecord(
+            &quot;user%02d&quot; % (ctr,),
+            (&quot;user%02d&quot; % (ctr,),),
+            &quot;User %02d&quot; % (ctr,),
+        ))
+
+    homes = set(homes) if homes is not None else set()
+    for uid in homes:
+        directory.addRecord(buildDirectoryRecord(uid))
+
+    return directory
+
+
+
+def buildDirectoryRecord(uid):
+    return TestStoreDirectoryRecord(
+        uid,
+        (uid,),
+        uid.capitalize(),
+    )
+
+
+
</ins><span class="cx"> class SQLStoreBuilder(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test-fixture-builder which can construct a PostgresStore.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    sharedService = None
-    currentTestID = None
</del><ins>+    def __init__(self, secondary=False):
+        self.sharedService = None
+        self.currentTestID = None
+        self.sharedDBPath = &quot;_test_sql_db&quot; + str(os.getpid()) + (&quot;-2&quot; if secondary else &quot;&quot;)
</ins><span class="cx"> 
</span><del>-    SHARED_DB_PATH = &quot;_test_sql_db&quot; + str(os.getpid())
</del><span class="cx"> 
</span><del>-
-    @classmethod
-    def createService(cls, serviceFactory):
</del><ins>+    def createService(self, serviceFactory):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a L{PostgresService} to use for building a store.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        dbRoot = CachingFilePath(cls.SHARED_DB_PATH)
</del><ins>+        dbRoot = CachingFilePath(self.sharedDBPath)
</ins><span class="cx">         return PostgresService(
</span><span class="cx">             dbRoot, serviceFactory, current_sql_schema, resetSchema=True,
</span><span class="cx">             databaseName=&quot;caldav&quot;,
</span><span class="lines">@@ -161,17 +199,15 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @classmethod
-    def childStore(cls):
</del><ins>+    def childStore(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a store suitable for use in a child process, that is hooked up
</span><span class="cx">         to the store that a parent test process is managing.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         disableMemcacheForTest(TestCase())
</span><span class="cx">         staticQuota = 3000
</span><del>-        attachmentRoot = (CachingFilePath(cls.SHARED_DB_PATH)
-                          .child(&quot;attachments&quot;))
-        stubsvc = cls.createService(lambda cf: Service())
</del><ins>+        attachmentRoot = (CachingFilePath(self.sharedDBPath).child(&quot;attachments&quot;))
+        stubsvc = self.createService(lambda cf: Service())
</ins><span class="cx"> 
</span><span class="cx">         cp = ConnectionPool(stubsvc.produceConnection, maxConnections=1)
</span><span class="cx">         # Attach the service to the running reactor.
</span><span class="lines">@@ -187,17 +223,17 @@
</span><span class="cx">         return cds
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def buildStore(self, testCase, notifierFactory, directoryService=None):
</del><ins>+    def buildStore(self, testCase, notifierFactory, directoryService=None, homes=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Do the necessary work to build a store for a particular test case.
</span><span class="cx"> 
</span><span class="cx">         @return: a L{Deferred} which fires with an L{IDataStore}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         disableMemcacheForTest(testCase)
</span><del>-        dbRoot = CachingFilePath(self.SHARED_DB_PATH)
</del><ins>+        dbRoot = CachingFilePath(self.sharedDBPath)
</ins><span class="cx">         attachmentRoot = dbRoot.child(&quot;attachments&quot;)
</span><span class="cx">         if directoryService is None:
</span><del>-            directoryService = TestStoreDirectoryService()
</del><ins>+            directoryService = buildDirectory(homes=homes)
</ins><span class="cx">         if self.sharedService is None:
</span><span class="cx">             ready = Deferred()
</span><span class="cx">             def getReady(connectionFactory, storageService):
</span><span class="lines">@@ -244,8 +280,7 @@
</span><span class="cx">         attachmentRoot.createDirectory()
</span><span class="cx"> 
</span><span class="cx">         currentTestID = testCase.id()
</span><del>-        cp = ConnectionPool(self.sharedService.produceConnection,
-                            maxConnections=5)
</del><ins>+        cp = ConnectionPool(self.sharedService.produceConnection, maxConnections=5)
</ins><span class="cx">         quota = deriveQuota(testCase)
</span><span class="cx">         store = CommonDataStore(
</span><span class="cx">             cp.connection,
</span><span class="lines">@@ -307,6 +342,7 @@
</span><span class="cx"> 
</span><span class="cx"> theStoreBuilder = SQLStoreBuilder()
</span><span class="cx"> buildStore = theStoreBuilder.buildStore
</span><ins>+cleanStore = theStoreBuilder.cleanStore
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> _notSet = object()
</span><span class="lines">@@ -678,13 +714,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def homeUnderTest(self, txn=None, name=&quot;home1&quot;):
</del><ins>+    def homeUnderTest(self, txn=None, name=&quot;home1&quot;, create=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get the calendar home detailed by C{requirements['home1']}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if txn is None:
</span><span class="cx">             txn = self.transactionUnderTest()
</span><del>-        returnValue((yield txn.calendarHomeWithUID(name)))
</del><ins>+        returnValue((yield txn.calendarHomeWithUID(name, create=create)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoreupgradetesttest_migratepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/upgrade/test/test_migrate.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/upgrade/test/test_migrate.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/upgrade/test/test_migrate.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -143,7 +143,17 @@
</span><span class="cx">             self.filesPath, {&quot;push&quot;: StubNotifierFactory()}, TestStoreDirectoryService(), True, True
</span><span class="cx">         )
</span><span class="cx">         self.sqlStore = yield theStoreBuilder.buildStore(
</span><del>-            self, StubNotifierFactory()
</del><ins>+            self,
+            StubNotifierFactory(),
+            homes=(
+                &quot;home1&quot;,
+                &quot;home2&quot;,
+                &quot;home3&quot;,
+                &quot;home_defaults&quot;,
+                &quot;home_no_splits&quot;,
+                &quot;home_splits&quot;,
+                &quot;home_splits_shared&quot;,
+            )
</ins><span class="cx">         )
</span><span class="cx">         self.upgrader = UpgradeToDatabaseStep(self.fileStore, self.sqlStore)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommonicommondatastorepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/icommondatastore.py (12050 => 12051)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/icommondatastore.py        2013-12-11 15:40:55 UTC (rev 12050)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/icommondatastore.py        2013-12-11 22:16:01 UTC (rev 12051)
</span><span class="lines">@@ -213,12 +213,28 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class ShareNotAllowed(CommonStoreError):
+    &quot;&quot;&quot;
+    An operation on a shared resource is not allowed.
+    &quot;&quot;&quot;
+
+
+
</ins><span class="cx"> class ExternalShareFailed(CommonStoreError):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     An external sharee operation failed.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class NonExistentExternalShare(CommonStoreError):
+    &quot;&quot;&quot;
+    An external sharee operation failed because the share does not exist on the
+    other pod. The caller of the external request receiving this exception should
+    remove the local external share to &quot;heal&quot; this mismatch.
+    &quot;&quot;&quot;
+
+
</ins><span class="cx"> # Indexing / sync tokens
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>