<!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>[12082] 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/12082">12082</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2013-12-12 20:00:05 -0800 (Thu, 12 Dec 2013)</dd>
</dl>

<h3>Log Message</h3>
<pre>Checkpoint: more complete cross-pod api calls.</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="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.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="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingconduitpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.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="#CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.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 (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -1516,6 +1516,7 @@
</span><span class="cx">     implements(ICalendarObject)
</span><span class="cx"> 
</span><span class="cx">     _objectSchema = schema.CALENDAR_OBJECT
</span><ins>+    _componentClass = VComponent
</ins><span class="cx"> 
</span><span class="cx">     def __init__(self, calendar, name, uid, resourceID=None, options=None):
</span><span class="cx"> 
</span><span class="lines">@@ -1539,7 +1540,7 @@
</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="cx"> 
</span><del>-        child = (yield cls.objectWithName(parent, name, None))
</del><ins>+        child = (yield cls.objectWithName(parent, name))
</ins><span class="cx">         if child:
</span><span class="cx">             raise ObjectResourceNameAlreadyExistsError(name)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -162,6 +162,9 @@
</span><span class="cx"> 
</span><span class="cx"> class CalendarObjectExternal(CommonObjectResourceExternal, CalendarObject):
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    SQL-based implementation of L{ICalendar}.
</del><ins>+    SQL-based implementation of L{ICalendarObject}.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     pass
</span><ins>+
+
+CalendarExternal._objectResourceClass = CalendarObjectExternal
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -1443,6 +1443,8 @@
</span><span class="cx">     _objectSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx">     _bindSchema = schema.SHARED_GROUP_BIND
</span><span class="cx"> 
</span><ins>+    _componentClass = VCard
+
</ins><span class="cx">     # used by CommonHomeChild._childrenAndMetadataForHomeID() only
</span><span class="cx">     # _homeChildSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx">     # _homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="lines">@@ -1576,10 +1578,6 @@
</span><span class="cx"> 
</span><span class="cx">         self._kind = None
</span><span class="cx">         self._ownerAddressBookResourceID = None
</span><del>-        # _self._component is the cached, current component
-        # super._objectText now contains the text as read of the database only,
-        #     not including group member text
-        self._component = None
</del><span class="cx">         self._bindMode = None
</span><span class="cx">         self._bindStatus = None
</span><span class="cx">         self._bindMessage = None
</span><span class="lines">@@ -1676,7 +1674,8 @@
</span><span class="cx">         yield super(AddressBookObject, self).remove()
</span><span class="cx">         self._kind = None
</span><span class="cx">         self._ownerAddressBookResourceID = None
</span><del>-        self._component = None
</del><ins>+        self._objectText = None
+        self._cachedComponent = None
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1763,7 +1762,7 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _objectWithNameOrID(cls, parent, name, uid, resourceID):
</del><ins>+    def objectWith(cls, parent, name=None, uid=None, resourceID=None):
</ins><span class="cx"> 
</span><span class="cx">         row, groupBindRow = yield cls._getDBData(parent, name, uid, resourceID)
</span><span class="cx"> 
</span><span class="lines">@@ -2111,7 +2110,7 @@
</span><span class="cx">             self._objectText = componentText
</span><span class="cx"> 
</span><span class="cx">         self._size = len(self._objectText)
</span><del>-        self._component = component
</del><ins>+        self._cachedComponent = component
</ins><span class="cx">         self._md5 = hashlib.md5(componentText).hexdigest()
</span><span class="cx">         self._componentChanged = originalComponentText != componentText
</span><span class="cx"> 
</span><span class="lines">@@ -2228,7 +2227,7 @@
</span><span class="cx">         only allowed in good data.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        if self._component is None:
</del><ins>+        if self._cachedComponent is None:
</ins><span class="cx"> 
</span><span class="cx">             if self.isGroupForSharedAddressBook():
</span><span class="cx">                 component = yield self.addressbook()._groupForSharedAddressBookComponent()
</span><span class="lines">@@ -2294,9 +2293,9 @@
</span><span class="cx">                     component.addProperty(Property(&quot;X-ADDRESSBOOKSERVER-KIND&quot;, &quot;group&quot;))
</span><span class="cx">                     component.addProperty(Property(&quot;UID&quot;, self._uid))
</span><span class="cx"> 
</span><del>-            self._component = component
</del><ins>+            self._cachedComponent = component
</ins><span class="cx"> 
</span><del>-        returnValue(self._component)
</del><ins>+        returnValue(self._cachedComponent)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def moveValidation(self, destination, name):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -76,6 +76,8 @@
</span><span class="cx"> 
</span><span class="cx"> class AddressBookObjectExternal(CommonObjectResourceExternal, AddressBookObject):
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    SQL-based implementation of L{ICalendar}.
</del><ins>+    SQL-based implementation of L{IAddressBookObject}.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     pass
</span><ins>+
+AddressBookExternal._objectResourceClass = AddressBookObjectExternal
</ins></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 (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -21,6 +21,7 @@
</span><span class="cx"> from txdav.common.datastore.podding.request import ConduitRequest
</span><span class="cx"> from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
</span><span class="cx"> from txdav.common.icommondatastore import ExternalShareFailed
</span><ins>+from twisted.python.reflect import namedClass
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> __all__ = [
</span><span class="lines">@@ -375,18 +376,18 @@
</span><span class="cx">     # Sharer data access related apis
</span><span class="cx">     #
</span><span class="cx"> 
</span><del>-    def _send(self, action, shareeView):
</del><ins>+    def _send(self, action, parent, child=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Base behavior for an operation on a sharee resource.
</del><ins>+        Base behavior for an operation on a L{CommonHomeChild}.
</ins><span class="cx"> 
</span><span class="cx">         @param shareeView: sharee resource being operated on.
</span><span class="cx">         @type shareeView: L{CommonHomeChildExternal}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        homeType = shareeView.ownerHome()._homeType
-        ownerUID = shareeView.ownerHome().uid()
-        ownerID = shareeView.external_id()
-        shareeUID = shareeView.viewerHome().uid()
</del><ins>+        homeType = parent.ownerHome()._homeType
+        ownerUID = parent.ownerHome().uid()
+        ownerID = parent.external_id()
+        shareeUID = parent.viewerHome().uid()
</ins><span class="cx"> 
</span><span class="cx">         _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
</span><span class="cx"> 
</span><span class="lines">@@ -397,6 +398,8 @@
</span><span class="cx">             &quot;owner_id&quot;: ownerID,
</span><span class="cx">             &quot;sharee&quot;: shareeUID,
</span><span class="cx">         }
</span><ins>+        if child is not None:
+            result[&quot;resource_id&quot;] = child.id()
</ins><span class="cx">         return result, recipient
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -421,16 +424,24 @@
</span><span class="cx">         if ownerHomeChild is None:
</span><span class="cx">             FailedCrossPodRequestError(&quot;Invalid owner shared resource specified&quot;)
</span><span class="cx"> 
</span><del>-        returnValue((ownerHome, ownerHomeChild))
</del><ins>+        resourceID = message.get(&quot;resource_id&quot;, None)
+        if resourceID is not None:
+            objectResource = yield ownerHomeChild.objectResourceWithID(resourceID)
+            if objectResource is None:
+                FailedCrossPodRequestError(&quot;Invalid owner shared object resource specified&quot;)
+        else:
+            objectResource = None
</ins><span class="cx"> 
</span><ins>+        returnValue((ownerHome, ownerHomeChild, objectResource,))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     #
</span><span class="cx">     # Simple calls are ones where there is no argument and a single return value. We can simplify
</span><span class="cx">     # code generation for these by dynamically generating the appropriate class methods.
</span><span class="cx">     #
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _simple_send(self, actionName, shareeView, args=None, kwargs=None):
</del><ins>+    def _simple_send(self, actionName, shareeView, objectResource=None, args=None, kwargs=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         A simple send operation that returns a value.
</span><span class="cx"> 
</span><span class="lines">@@ -438,23 +449,28 @@
</span><span class="cx">         @type actionName: C{str}
</span><span class="cx">         @param shareeView: sharee resource being operated on.
</span><span class="cx">         @type shareeView: L{CommonHomeChildExternal}
</span><ins>+        @param objectResource: the resource being operated on, or C{None} for classmethod.
+        @type objectResource: L{CommonObjectResourceExternal}
</ins><span class="cx">         @param args: list of optional arguments.
</span><span class="cx">         @type args: C{list}
</span><span class="cx">         @param kwargs: optional keyword arguments.
</span><span class="cx">         @type kwargs: C{dict}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        action, recipient = self._send(actionName, shareeView)
</del><ins>+        action, recipient = self._send(actionName, shareeView, objectResource)
</ins><span class="cx">         if args is not None:
</span><span class="cx">             action[&quot;arguments&quot;] = args
</span><span class="cx">         if kwargs is not None:
</span><span class="cx">             action[&quot;keywords&quot;] = kwargs
</span><span class="cx">         result = yield self.sendRequest(shareeView._txn, recipient, action)
</span><del>-        returnValue(result[&quot;value&quot;])
</del><ins>+        if result[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue(result[&quot;value&quot;])
+        elif result[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(result[&quot;class&quot;])(result[&quot;message&quot;])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _simple_recv(self, txn, actionName, message, method):
</del><ins>+    def _simple_recv(self, txn, actionName, message, method, onHomeChild=True, transform=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         A simple recv operation that returns a value. We also look for an optional set of arguments/keywords
</span><span class="cx">         and include those only if present.
</span><span class="lines">@@ -465,25 +481,76 @@
</span><span class="cx">         @type message: C{dict}
</span><span class="cx">         @param method: name of the method to execute on the shared resource to get the result.
</span><span class="cx">         @type method: C{str}
</span><ins>+        @param transform: method to call on returned JSON value to convert it to something useful.
+        @type transform: C{callable}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        _ignore_ownerHome, ownerHomeChild = yield self._recv(txn, message, actionName)
-        value = yield getattr(ownerHomeChild, method)(*message.get(&quot;arguments&quot;, ()), **message.get(&quot;keywords&quot;, {}))
</del><ins>+        _ignore_ownerHome, ownerHomeChild, objectResource = yield self._recv(txn, message, actionName)
+        try:
+            if onHomeChild:
+                # Operate on the L{CommonHomeChild}
+                value = yield getattr(ownerHomeChild, method)(*message.get(&quot;arguments&quot;, ()), **message.get(&quot;keywords&quot;, {}))
+            else:
+                # Operate on the L{CommonObjectResource}
+                if objectResource is not None:
+                    value = yield getattr(objectResource, method)(*message.get(&quot;arguments&quot;, ()), **message.get(&quot;keywords&quot;, {}))
+                else:
+                    # classmethod call
+                    value = yield getattr(ownerHomeChild._objectResourceClass, method)(ownerHomeChild, *message.get(&quot;arguments&quot;, ()), **message.get(&quot;keywords&quot;, {}))
+        except Exception as e:
+            returnValue({
+                &quot;result&quot;: &quot;exception&quot;,
+                &quot;class&quot;: &quot;.&quot;.join((e.__class__.__module__, e.__class__.__name__,)),
+                &quot;message&quot;: str(e),
+            })
+        if transform is not None:
+            value = transform(value, ownerHomeChild, objectResource)
+
</ins><span class="cx">         returnValue({
</span><span class="cx">             &quot;result&quot;: &quot;ok&quot;,
</span><span class="cx">             &quot;value&quot;: value,
</span><span class="cx">         })
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @staticmethod
+    def _transform_string(value, ownerHomeChild, objectResource):
+        return str(value)
+
+
+    @staticmethod
+    def _transform_externalize(value, ownerHomeChild, objectResource):
+        if isinstance(value, ownerHomeChild._objectResourceClass):
+            value = value.externalize()
+        elif value is not None:
+            value = [v.externalize() for v in value]
+        return value
+
+
</ins><span class="cx">     @classmethod
</span><del>-    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))
</del><ins>+    def _make_simple_homechild_action(cls, action, method):
+        setattr(cls, &quot;send_{}&quot;.format(action), lambda self, shareeView, *args, **kwargs: self._simple_send(action, shareeView, args=args, kwargs=kwargs))
</ins><span class="cx">         setattr(cls, &quot;recv_{}&quot;.format(action), lambda self, txn, message: self._simple_recv(txn, action, message, method))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-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;)
</del><ins>+    @classmethod
+    def _make_simple_object_action(cls, action, method, transform_result=None):
+        setattr(cls, &quot;send_{}&quot;.format(action), lambda self, shareeView, objectResource, *args, **kwargs: self._simple_send(action, shareeView, objectResource, args=args, kwargs=kwargs))
+        setattr(cls, &quot;recv_{}&quot;.format(action), lambda self, txn, message: self._simple_recv(txn, action, message, method, onHomeChild=False, transform=transform_result))
+
+
+# Calls on L{CommonHomeChild} objects
+PoddingConduit._make_simple_homechild_action(&quot;countobjects&quot;, &quot;countObjectResources&quot;)
+PoddingConduit._make_simple_homechild_action(&quot;listobjects&quot;, &quot;listObjectResources&quot;)
+PoddingConduit._make_simple_homechild_action(&quot;synctoken&quot;, &quot;syncToken&quot;)
+PoddingConduit._make_simple_homechild_action(&quot;resourcenamessincerevision&quot;, &quot;resourceNamesSinceRevision&quot;)
+PoddingConduit._make_simple_homechild_action(&quot;resourceuidforname&quot;, &quot;resourceUIDForName&quot;)
+PoddingConduit._make_simple_homechild_action(&quot;resourcenameforuid&quot;, &quot;resourceNameForUID&quot;)
+
+# Calls on L{CommonObjectResource} objects
+PoddingConduit._make_simple_object_action(&quot;loadallobjects&quot;, &quot;loadAllObjects&quot;, transform_result=PoddingConduit._transform_externalize)
+PoddingConduit._make_simple_object_action(&quot;loadallobjectswithnames&quot;, &quot;loadAllObjectsWithNames&quot;, transform_result=PoddingConduit._transform_externalize)
+PoddingConduit._make_simple_object_action(&quot;objectwith&quot;, &quot;objectWith&quot;, transform_result=PoddingConduit._transform_externalize)
+PoddingConduit._make_simple_object_action(&quot;create&quot;, &quot;create&quot;, transform_result=PoddingConduit._transform_externalize)
+PoddingConduit._make_simple_object_action(&quot;setcomponent&quot;, &quot;setComponentText&quot;)
+PoddingConduit._make_simple_object_action(&quot;component&quot;, &quot;component&quot;, transform_result=PoddingConduit._transform_string)
+PoddingConduit._make_simple_object_action(&quot;remove&quot;, &quot;remove&quot;)
</ins></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 (12081 => 12082)</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-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -29,7 +29,9 @@
</span><span class="cx">     FakeConduitRequest
</span><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_STATUS_ACCEPTED
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><del>-from twistedcaldav.ical import Component
</del><ins>+from twistedcaldav.ical import Component, normalize_iCalStr
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
+    ObjectResourceNameNotAllowedError
</ins><span class="cx"> 
</span><span class="cx"> class TestConduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
</span><span class="cx"> 
</span><span class="lines">@@ -208,12 +210,28 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
</span><span class="cx"> 
</span><ins>+    caldata1_changed = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid1
+DTSTART:{now:04d}0102T150000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance changed
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
+
</ins><span class="cx">     caldata2 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><del>-UID:ui2
</del><ins>+UID:uid2
</ins><span class="cx"> DTSTART:{now:04d}0102T160000Z
</span><span class="cx"> DURATION:PT1H
</span><span class="cx"> CREATED:20060102T190000Z
</span><span class="lines">@@ -224,6 +242,22 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
</span><span class="cx"> 
</span><ins>+    caldata3 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid3
+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)
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_basic_share(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -493,3 +527,360 @@
</span><span class="cx">         uid = yield shared.resourceNameForUID(&quot;uid2&quot;)
</span><span class="cx">         self.assertTrue(uid is None)
</span><span class="cx">         yield self.otherCommit()
</span><ins>+
+
+    @inlineCallbacks
+    def test_loadallobjects(self):
+        &quot;&quot;&quot;
+        Test that action=loadallobjects 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;)
+        resource1 = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resource_id1 = resource1.id()
+        resource2 = yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
+        resource_id2 = resource2.id()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResources()
+        byname = dict([(resource.name(), resource) for resource in resources])
+        byuid = dict([(resource.uid(), resource) for resource in resources])
+        self.assertEqual(len(resources), 2)
+        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
+        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid1&quot;, &quot;uid2&quot;,)))
+        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id1, resource_id2,)))
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is byname[&quot;1.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
+        self.assertTrue(resource is byname[&quot;2.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is byuid[&quot;uid1&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
+        self.assertTrue(resource is byuid[&quot;uid2&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id1)
+        self.assertTrue(resource is byname[&quot;1.ics&quot;])
+        resource = yield shared.objectResourceWithID(resource_id2)
+        self.assertTrue(resource is byname[&quot;2.ics&quot;])
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+        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()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResources()
+        byname = dict([(resource.name(), resource) for resource in resources])
+        byuid = dict([(resource.uid(), resource) for resource in resources])
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;2.ics&quot;,)))
+        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid2&quot;,)))
+        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id2,)))
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
+        self.assertTrue(resource is byname[&quot;2.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
+        self.assertTrue(resource is byuid[&quot;uid2&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id1)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id2)
+        self.assertTrue(resource is byname[&quot;2.ics&quot;])
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_loadallobjectswithnames(self):
+        &quot;&quot;&quot;
+        Test that action=loadallobjectswithnames 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;)
+        resource1 = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resource_id1 = resource1.id()
+        yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
+        resource3 = yield  calendar1.createCalendarObjectWithName(&quot;3.ics&quot;, Component.fromString(self.caldata3))
+        resource_id3 = resource3.id()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResources()
+        self.assertEqual(len(resources), 3)
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResourcesWithNames((&quot;1.ics&quot;, &quot;3.ics&quot;,))
+        byname = dict([(resource.name(), resource) for resource in resources])
+        byuid = dict([(resource.uid(), resource) for resource in resources])
+        self.assertEqual(len(resources), 2)
+        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;1.ics&quot;, &quot;3.ics&quot;,)))
+        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid1&quot;, &quot;uid3&quot;,)))
+        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id1, resource_id3,)))
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is byname[&quot;1.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;3.ics&quot;)
+        self.assertTrue(resource is byname[&quot;3.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is byuid[&quot;uid1&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid3&quot;)
+        self.assertTrue(resource is byuid[&quot;uid3&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id1)
+        self.assertTrue(resource is byname[&quot;1.ics&quot;])
+        resource = yield shared.objectResourceWithID(resource_id3)
+        self.assertTrue(resource is byname[&quot;3.ics&quot;])
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+        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()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResourcesWithNames((&quot;1.ics&quot;, &quot;3.ics&quot;,))
+        byname = dict([(resource.name(), resource) for resource in resources])
+        byuid = dict([(resource.uid(), resource) for resource in resources])
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;3.ics&quot;,)))
+        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid3&quot;,)))
+        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id3,)))
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithName(&quot;3.ics&quot;)
+        self.assertTrue(resource is byname[&quot;3.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid3&quot;)
+        self.assertTrue(resource is byuid[&quot;uid3&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id1)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id3)
+        self.assertTrue(resource is byname[&quot;3.ics&quot;])
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_objectwith(self):
+        &quot;&quot;&quot;
+        Test that action=objectwith 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;)
+        resource = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resource_id = resource.id()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+
+        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
+        self.assertTrue(resource is None)
+
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+
+        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
+        self.assertTrue(resource is None)
+
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithID(resource_id)
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+
+        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()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithID(resource_id)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_create(self):
+        &quot;&quot;&quot;
+        Test that action=create 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;)
+        resource = yield shared.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resource_id = resource.id()
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+        self.assertEqual(resource.id(), resource_id)
+        yield self.otherCommit()
+
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        self.assertTrue(object1 is not None)
+        self.assertEqual(object1.name(), &quot;1.ics&quot;)
+        self.assertEqual(object1.uid(), &quot;uid1&quot;)
+        self.assertEqual(object1.id(), resource_id)
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_create_exception(self):
+        &quot;&quot;&quot;
+        Test that action=create fails when a duplicate name is used.
+        &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()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        yield self.failUnlessFailure(shared.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1)), ObjectResourceNameAlreadyExistsError)
+        yield self.otherAbort()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        yield self.failUnlessFailure(shared.createCalendarObjectWithName(&quot;.2.ics&quot;, Component.fromString(self.caldata2)), ObjectResourceNameNotAllowedError)
+        yield self.otherAbort()
+
+
+    @inlineCallbacks
+    def test_setcomponent(self):
+        &quot;&quot;&quot;
+        Test that action=setcomponent 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()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        ical = yield shared_object.component()
+        self.assertTrue(isinstance(ical, Component))
+        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
+        yield self.otherCommit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        changed = yield shared_object.setComponent(Component.fromString(self.caldata1_changed))
+        self.assertFalse(changed)
+        ical = yield shared_object.component()
+        self.assertTrue(isinstance(ical, Component))
+        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
+        yield self.otherCommit()
+
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        ical = yield object1.component()
+        self.assertTrue(isinstance(ical, Component))
+        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_component(self):
+        &quot;&quot;&quot;
+        Test that action=component 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()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        ical = yield shared_object.component()
+        self.assertTrue(isinstance(ical, Component))
+        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_remove(self):
+        &quot;&quot;&quot;
+        Test that action=create 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()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        yield shared_object.remove()
+        yield self.otherCommit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        self.assertTrue(shared_object is None)
+        yield self.otherCommit()
+
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        self.assertTrue(object1 is None)
+        yield self.commit()
</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 (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -4102,33 +4102,39 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def objectWithName(cls, home, name, accepted=True):
</span><del>-        return cls._objectWithNameOrID(home, name=name, accepted=accepted)
</del><ins>+        return cls.objectWith(home, name=name, accepted=accepted)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def objectWithID(cls, home, resourceID, accepted=True):
</span><del>-        return cls._objectWithNameOrID(home, resourceID=resourceID, accepted=accepted)
</del><ins>+        return cls.objectWith(home, resourceID=resourceID, accepted=accepted)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def objectWithExternalID(cls, home, externalID, accepted=True):
</span><del>-        return cls._objectWithNameOrID(home, externalID=externalID, accepted=accepted)
</del><ins>+        return cls.objectWith(home, externalID=externalID, accepted=accepted)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _objectWithNameOrID(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
-        # replaces objectWithName()
</del><ins>+    def objectWith(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Retrieve the child with the given C{name} or C{resourceID} contained in the given
-        C{home}.
</del><ins>+        Create the object using one of the specified arguments as the key to load it. One
+        and only one of the keyword arguments must be set.
</ins><span class="cx"> 
</span><del>-        @param home: a L{CommonHome}.
</del><ins>+        @param parent: parent collection
+        @type parent: L{CommonHomeChild}
+        @param name: name of the resource, or C{None}
+        @type name: C{str}
+        @param uid: resource data UID, or C{None}
+        @type uid: C{str}
+        @param resourceID: resource id
+        @type resourceID: C{int}
+        @param accepted: if C{True} only load owned or accepted share items
+        @type accepted: C{bool}
</ins><span class="cx"> 
</span><del>-        @param name: a string; the name of the L{CommonHomeChild} to retrieve.
-
-        @return: an L{CommonHomeChild} or C{None} if no such child
-            exists.
</del><ins>+        @return: the new object or C{None} if not found
+        @rtype: C{CommonHomeChild}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         dbData = yield cls._getDBData(home, name, resourceID, externalID)
</span><span class="lines">@@ -4376,6 +4382,7 @@
</span><span class="cx">         for result in results:
</span><span class="cx">             self._objects[result.name()] = result
</span><span class="cx">             self._objects[result.uid()] = result
</span><ins>+            self._objects[result.id()] = result
</ins><span class="cx">         self._objectNames = sorted([result.name() for result in results])
</span><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="lines">@@ -4389,6 +4396,7 @@
</span><span class="cx">         for result in results:
</span><span class="cx">             self._objects[result.name()] = result
</span><span class="cx">             self._objects[result.uid()] = result
</span><ins>+            self._objects[result.id()] = result
</ins><span class="cx">         self._objectNames = sorted([result.name() for result in results])
</span><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="lines">@@ -4457,18 +4465,13 @@
</span><span class="cx">         We create the empty object first then have it initialize itself from the
</span><span class="cx">         store.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        if resourceID:
-            objectResource = (
-                yield self._objectResourceClass.objectWithID(self, resourceID)
-            )
-        else:
-            objectResource = (
-                yield self._objectResourceClass.objectWithName(self, name, uid)
-            )
</del><ins>+        objectResource = (
+            yield self._objectResourceClass.objectWith(self, name=name, uid=uid, resourceID=resourceID)
+        )
</ins><span class="cx">         if objectResource:
</span><span class="cx">             self._objects[objectResource.name()] = objectResource
</span><span class="cx">             self._objects[objectResource.uid()] = objectResource
</span><del>-            self._objects[objectResource._resourceID] = objectResource
</del><ins>+            self._objects[objectResource.id()] = objectResource
</ins><span class="cx">         else:
</span><span class="cx">             if resourceID:
</span><span class="cx">                 self._objects[resourceID] = None
</span><span class="lines">@@ -4558,6 +4561,7 @@
</span><span class="cx">         )
</span><span class="cx">         self._objects[objectResource.name()] = objectResource
</span><span class="cx">         self._objects[objectResource.uid()] = objectResource
</span><ins>+        self._objects[objectResource.id()] = objectResource
</ins><span class="cx"> 
</span><span class="cx">         # Note: create triggers a notification when the component is set, so we
</span><span class="cx">         # don't need to call notify() here like we do for object removal.
</span><span class="lines">@@ -4568,6 +4572,7 @@
</span><span class="cx">     def removedObjectResource(self, child):
</span><span class="cx">         self._objects.pop(child.name(), None)
</span><span class="cx">         self._objects.pop(child.uid(), None)
</span><ins>+        self._objects.pop(child.id(), None)
</ins><span class="cx">         if self._objectNames and child.name() in self._objectNames:
</span><span class="cx">             self._objectNames.remove(child.name())
</span><span class="cx">         yield self._deleteRevision(child.name())
</span><span class="lines">@@ -4639,7 +4644,7 @@
</span><span class="cx">         # Clean this collections cache and signal sync change
</span><span class="cx">         self._objects.pop(name, None)
</span><span class="cx">         self._objects.pop(uid, None)
</span><del>-        self._objects.pop(child._resourceID, None)
</del><ins>+        self._objects.pop(child.id(), None)
</ins><span class="cx">         yield self._deleteRevision(name)
</span><span class="cx">         yield self.notifyChanged()
</span><span class="cx"> 
</span><span class="lines">@@ -4670,7 +4675,7 @@
</span><span class="cx">         # Signal sync change on new collection
</span><span class="cx">         newparent._objects.pop(name, None)
</span><span class="cx">         newparent._objects.pop(uid, None)
</span><del>-        newparent._objects.pop(child._resourceID, None)
</del><ins>+        newparent._objects.pop(child.id(), None)
</ins><span class="cx">         yield newparent._insertRevision(newname)
</span><span class="cx">         yield newparent.notifyChanged()
</span><span class="cx"> 
</span><span class="lines">@@ -4876,6 +4881,7 @@
</span><span class="cx"> 
</span><span class="cx">     _externalClass = None
</span><span class="cx">     _objectSchema = None
</span><ins>+    _componentClass = None
</ins><span class="cx"> 
</span><span class="cx">     BATCH_LOAD_SIZE = 50
</span><span class="cx"> 
</span><span class="lines">@@ -4961,7 +4967,8 @@
</span><span class="cx">         self._size = None
</span><span class="cx">         self._created = None
</span><span class="cx">         self._modified = None
</span><del>-        self._notificationData = None
</del><ins>+        self._textData = None
+        self._cachedComponent = None
</ins><span class="cx"> 
</span><span class="cx">         self._locked = False
</span><span class="cx"> 
</span><span class="lines">@@ -5023,7 +5030,9 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def loadAllObjectsWithNames(cls, parent, names):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Load all child objects with the specified names, doing so in batches.
</del><ins>+        Load all child objects with the specified names, doing so in batches (because we need to match
+        using SQL &quot;resource_name in (...)&quot; where there might be a character length limit on the number
+        of items in the set).
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         names = tuple(names)
</span><span class="cx">         results = []
</span><span class="lines">@@ -5062,7 +5071,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Optimize case of single name to load
</span><span class="cx">         if len(names) == 1:
</span><del>-            obj = yield cls.objectWithName(parent, names[0], None)
</del><ins>+            obj = yield cls.objectWithName(parent, names[0])
</ins><span class="cx">             returnValue([obj] if obj else [])
</span><span class="cx"> 
</span><span class="cx">         results = []
</span><span class="lines">@@ -5093,19 +5102,40 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def objectWithName(cls, parent, name, uid):
-        return cls._objectWithNameOrID(parent, name, uid, None)
</del><ins>+    def objectWithName(cls, parent, name):
+        return cls.objectWith(parent, name=name)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><ins>+    def objectWithUID(cls, parent, uid):
+        return cls.objectWith(parent, uid=uid)
+
+
+    @classmethod
</ins><span class="cx">     def objectWithID(cls, parent, resourceID):
</span><del>-        return cls._objectWithNameOrID(parent, None, None, resourceID)
</del><ins>+        return cls.objectWith(parent, resourceID=resourceID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _objectWithNameOrID(cls, parent, name, uid, resourceID):
</del><ins>+    def objectWith(cls, parent, name=None, uid=None, resourceID=None):
+        &quot;&quot;&quot;
+        Create the object using one of the specified arguments as the key to load it. One
+        and only one of the keyword arguments must be set.
</ins><span class="cx"> 
</span><ins>+        @param parent: parent collection
+        @type parent: L{CommonHomeChild}
+        @param name: name of the resource, or C{None}
+        @type name: C{str}
+        @param uid: resource data UID, or C{None}
+        @type uid: C{str}
+        @param resourceID: resource id
+        @type resourceID: C{int}
+
+        @return: the new object or C{None} if not found
+        @rtype: C{CommonObjectResource}
+        &quot;&quot;&quot;
+
</ins><span class="cx">         row = yield cls._getDBData(parent, name, uid, resourceID)
</span><span class="cx"> 
</span><span class="cx">         if row:
</span><span class="lines">@@ -5195,6 +5225,27 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def externalize(self):
+        &quot;&quot;&quot;
+        Create a dictionary mapping key attributes so this object can be sent over a cross-pod call
+        and reconstituted at the other end. Note that the other end may have a different schema so
+        the attributes may not match exactly and will need to be processed accordingly.
+        &quot;&quot;&quot;
+        return dict([(attr[1:], getattr(self, attr)) for attr in self._rowAttributes()])
+
+
+    @classmethod
+    def internalize(cls, mapping):
+        &quot;&quot;&quot;
+        Given a mapping generated by L{externalize}, convert the values into an array of database
+        like items that conforms to the ordering of L{_allColumns} so it can be fed into L{makeClass}.
+        Note that there may be a schema mismatch with the external data, so treat missing items as
+        C{None} and ignore extra items.
+        &quot;&quot;&quot;
+
+        return [mapping.get(row[1:]) for row in cls._rowAttributes()]
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _loadPropertyStore(self, props=None, created=False):
</span><span class="cx">         if props is None:
</span><span class="lines">@@ -5309,6 +5360,14 @@
</span><span class="cx">         raise NotImplementedError
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def setComponentText(self, component_text, inserting=False, options=None):
+        &quot;&quot;&quot;
+        This api is needed for cross-pod calls where the component is serialized as a str and we need
+        to convert it back to the actual component class.
+        &quot;&quot;&quot;
+        return self.setComponent(self._componentClass.fromString(component_text), inserting, options)
+
+
</ins><span class="cx">     def component(self):
</span><span class="cx">         raise NotImplementedError
</span><span class="cx"> 
</span><span class="lines">@@ -5361,7 +5420,8 @@
</span><span class="cx">         self._size = None
</span><span class="cx">         self._created = None
</span><span class="cx">         self._modified = None
</span><del>-        self._notificationData = None
</del><ins>+        self._textData = None
+        self._cachedComponent = None
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def removeNotifyCategory(self):
</span><span class="lines">@@ -5417,19 +5477,19 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _text(self):
</span><del>-        if self._notificationData is None:
</del><ins>+        if self._textData is None:
</ins><span class="cx">             texts = (
</span><span class="cx">                 yield self._textByIDQuery.on(self._txn,
</span><span class="cx">                                              resourceID=self._resourceID)
</span><span class="cx">             )
</span><span class="cx">             if texts:
</span><span class="cx">                 text = texts[0][0]
</span><del>-                self._notificationData = text
</del><ins>+                self._textData = text
</ins><span class="cx">                 returnValue(text)
</span><span class="cx">             else:
</span><span class="cx">                 raise ConcurrentModification()
</span><span class="cx">         else:
</span><del>-            returnValue(self._notificationData)
</del><ins>+            returnValue(self._textData)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py (12081 => 12082)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py        2013-12-13 03:03:31 UTC (rev 12081)
+++ CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py        2013-12-13 04:00:05 UTC (rev 12082)
</span><span class="lines">@@ -225,16 +225,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def objectResources(self):
-        raise NotImplementedError(&quot;TODO: external resource&quot;)
-
-
-    @inlineCallbacks
-    def objectResourcesWithNames(self, names):
-        raise NotImplementedError(&quot;TODO: external resource&quot;)
-
-
-    @inlineCallbacks
</del><span class="cx">     def listObjectResources(self):
</span><span class="cx">         if self._objectNames is None:
</span><span class="cx">             try:
</span><span class="lines">@@ -258,18 +248,6 @@
</span><span class="cx">         returnValue(len(self._objectNames))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    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;)
-
-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def resourceNameForUID(self, uid):
</span><span class="cx">         try:
</span><span class="lines">@@ -314,11 +292,21 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def createObjectResourceWithName(self, name, component, options=None):
</span><ins>+        &quot;&quot;&quot;
+        Actually I think we can defer this to the object resource class's .create()
+        &quot;&quot;&quot;
</ins><span class="cx">         raise NotImplementedError(&quot;TODO: external resource&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def moveObjectResource(self, child, newparent, newname=None):
</span><ins>+        &quot;&quot;&quot;
+        The base class does an optimization to avoid removing/re-creating
+        the actual object resource data. That might not always be possible
+        with external shares if the shared resource is moved to a collection
+        that is not shared or shared by someone else on a different (third)
+        pod. The best bet here is to treat the move as a delete/create.
+        &quot;&quot;&quot;
</ins><span class="cx">         raise NotImplementedError(&quot;TODO: external resource&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -351,4 +339,81 @@
</span><span class="cx">     A CommonObjectResource for a resource not hosted on this system, but on another pod. This will forward
</span><span class="cx">     specific apis to the other pod using cross-pod requests.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    pass
</del><ins>+
+    @classmethod
+    @inlineCallbacks
+    def loadAllObjects(cls, parent):
+        mapping_list = yield parent._txn.store().conduit.send_loadallobjects(parent, None)
+
+        results = []
+        if mapping_list:
+            for mapping in mapping_list:
+                child = yield cls.makeClass(parent, cls.internalize(mapping))
+                results.append(child)
+        returnValue(results)
+
+
+    @classmethod
+    @inlineCallbacks
+    def loadAllObjectsWithNames(cls, parent, names):
+        mapping_list = yield parent._txn.store().conduit.send_loadallobjectswithnames(parent, None, names)
+
+        results = []
+        if mapping_list:
+            for mapping in mapping_list:
+                child = yield cls.makeClass(parent, cls.internalize(mapping))
+                results.append(child)
+        returnValue(results)
+
+
+    @classmethod
+    @inlineCallbacks
+    def objectWith(cls, parent, name=None, uid=None, resourceID=None):
+        mapping = yield parent._txn.store().conduit.send_objectwith(parent, None, name, uid, resourceID)
+
+        if mapping:
+            child = yield cls.makeClass(parent, cls.internalize(mapping))
+            returnValue(child)
+        else:
+            returnValue(None)
+
+
+    @classmethod
+    @inlineCallbacks
+    def create(cls, parent, name, component, options=None):
+        mapping = yield parent._txn.store().conduit.send_create(parent, None, name, component, options=options)
+
+        if mapping:
+            child = yield cls.makeClass(parent, cls.internalize(mapping))
+            returnValue(child)
+        else:
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def setComponent(self, component, inserting=False, options=None):
+        changed = yield self._txn.store().conduit.send_setcomponent(self.parentCollection(), self, str(component), inserting, options)
+        self._cachedComponent = None
+        returnValue(changed)
+
+
+    @inlineCallbacks
+    def component(self):
+        if self._cachedComponent is None:
+            text = yield self._txn.store().conduit.send_component(self.parentCollection(), self)
+            self._cachedComponent = self._componentClass.fromString(text)
+
+        returnValue(self._cachedComponent)
+
+
+    @inlineCallbacks
+    def moveTo(self, destination, name=None):
+        &quot;&quot;&quot;
+        Probably OK to leave this to the base implementation which calls up to the parent after some validation.
+        &quot;&quot;&quot;
+        raise NotImplementedError
+
+
+    @inlineCallbacks
+    def remove(self):
+        yield self._txn.store().conduit.send_remove(self.parentCollection(), self)
</ins></span></pre>
</div>
</div>

</body>
</html>