<!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>[14084] CalendarServer/trunk</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/14084">14084</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-10-16 09:12:30 -0700 (Thu, 16 Oct 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Cross-pod proxy support. Still needs work to better handle errors and improve performance.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkconfcaldavdtestpodAplist">CalendarServer/trunk/conf/caldavd-test-podA.plist</a></li>
<li><a href="#CalendarServertrunkconfcaldavdtestpodBplist">CalendarServer/trunk/conf/caldavd-test-podB.plist</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingischedulelocalserverspy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingischeduletesttest_localserverspy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingconduitpy">CalendarServer/trunk/txdav/common/datastore/podding/conduit.py</a></li>
<li><a href="#CalendarServertrunktxdavwhocachepy">CalendarServer/trunk/txdav/who/cache.py</a></li>
<li><a href="#CalendarServertrunktxdavwhodelegatespy">CalendarServer/trunk/txdav/who/delegates.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServertrunkbintestpods">CalendarServer/trunk/bin/testpods</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingattachmentspy">CalendarServer/trunk/txdav/common/datastore/podding/attachments.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingbasepy">CalendarServer/trunk/txdav/common/datastore/podding/base.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingdirectorypy">CalendarServer/trunk/txdav/common/datastore/podding/directory.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingsharing_basepy">CalendarServer/trunk/txdav/common/datastore/podding/sharing_base.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingsharing_invitespy">CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingsharing_storepy">CalendarServer/trunk/txdav/common/datastore/podding/sharing_store.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkbintestpods"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/bin/testpods (0 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/bin/testpods                                (rev 0)
+++ CalendarServer/trunk/bin/testpods        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -0,0 +1,173 @@
</span><ins>+#!/bin/sh
+# -*- sh-basic-offset: 2 -*-
+
+##
+# Copyright (c) 2005-2014 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.
+##
+
+set -e;
+set -u;
+
+wd=&quot;$(cd &quot;$(dirname &quot;$0&quot;)/..&quot; &amp;&amp; pwd -L)&quot;;
+
+. &quot;${wd}/bin/_build.sh&quot;;
+
+init_build &gt; /dev/null;
+
+cdt=&quot;${py_virtualenv}/src/caldavtester&quot;;
+
+##
+# Command line handling
+##
+
+   verbose=&quot;&quot;;
+serverinfo=&quot;${cdt}/scripts/server/serverinfo-pod.xml&quot;;
+  printres=&quot;&quot;;
+    subdir=&quot;&quot;;
+    random=&quot;--random&quot;;
+      seed=&quot;&quot;;
+       ssl=&quot;&quot;;
+  cdtdebug=&quot;&quot;;
+
+usage ()
+{
+  program=&quot;$(basename &quot;$0&quot;)&quot;;
+  echo &quot;Usage: ${program} [-v] [-s serverinfo]&quot;;
+  echo &quot;Options:&quot;;
+  echo &quot;        -d  Set the script subdirectory&quot;;
+  echo &quot;        -h  Print this help and exit&quot;;
+  echo &quot;        -o  Execute tests in order&quot;;
+  echo &quot;        -r  Print request and response&quot;;
+  echo &quot;        -s  Set the serverinfo.xml&quot;;
+  echo &quot;        -t  Set the CalDAVTester directory&quot;;
+  echo &quot;        -x  Random seed to use.&quot;;
+  echo &quot;        -v  Verbose.&quot;;
+  echo &quot;        -z  Use SSL.&quot;;
+  echo &quot;        -D  Turn on CalDAVTester debugging&quot;;
+
+  if [ &quot;${1-}&quot; == &quot;-&quot; ]; then return 0; fi;
+  exit 64;
+}
+
+while getopts 'Dhvrozt:s:d:x:' option; do
+  case &quot;$option&quot; in
+    '?') usage; ;;
+    'h') usage -; exit 0; ;;
+    't') cdt=&quot;${OPTARG}&quot;; serverinfo=&quot;${OPTARG}/scripts/server/serverinfo-pod.xml&quot;; ;;
+    'd') subdir=&quot;--subdir=${OPTARG}&quot;; ;;
+    's') serverinfo=&quot;${OPTARG}&quot;; ;;
+    'r') printres=&quot;--always-print-request --always-print-response&quot;; ;;
+    'v') verbose=&quot;v&quot;; ;;
+    'o') random=&quot;&quot;; ;;
+    'x') seed=&quot;--random-seed ${OPTARG}&quot;; ;;
+    'z') ssl=&quot;--ssl&quot;; ;;
+    'D') cdtdebug=&quot;--debug&quot;; ;;
+  esac;
+done;
+
+shift $((${OPTIND} - 1));
+
+if [ $# == 0 ]; then
+  set - &quot;--all&quot;;
+fi;
+
+##
+# Do The Right Thing
+##
+
+do_setup=&quot;false&quot;;
+develop &gt; /dev/null;
+
+# Set up sandbox
+
+sandboxdir=&quot;/tmp/cdt_server_sandbox&quot;
+
+if [ -d &quot;${sandboxdir}&quot; ]; then
+  rm -rf &quot;${sandboxdir}&quot;
+fi;
+
+configdir=&quot;${sandboxdir}/Config&quot;
+serverrootA=&quot;${sandboxdir}/podA&quot;
+serverrootB=&quot;${sandboxdir}/podB&quot;
+
+mkdir -p &quot;${configdir}/auth&quot;
+mkdir -p &quot;${serverrootA}/Logs&quot; &quot;${serverrootA}/Run&quot; &quot;${serverrootA}/Data/Documents&quot;
+mkdir -p &quot;${serverrootB}/Logs&quot; &quot;${serverrootB}/Run&quot; &quot;${serverrootB}/Data/Documents&quot;
+
+cp conf/caldavd-test.plist &quot;${configdir}/caldavd-cdt.plist&quot;
+cp conf/caldavd-test-podA.plist &quot;${configdir}/caldavd-cdt-podA.plist&quot;
+cp conf/caldavd-test-podB.plist &quot;${configdir}/caldavd-cdt-podB.plist&quot;
+cp conf/auth/proxies-test-pod.xml &quot;${configdir}/auth/proxies-cdt.xml&quot;
+cp conf/auth/resources-test-pod.xml &quot;${configdir}/auth/resources-cdt.xml&quot;
+cp conf/auth/augments-test-pod.xml &quot;${configdir}/auth/augments-cdt.xml&quot;
+cp conf/auth/accounts-test-pod.xml &quot;${configdir}/auth/accounts-cdt.xml&quot;
+
+# Modify the plists
+
+python -c &quot;import plistlib; f=plistlib.readPlist('${configdir}/caldavd-cdt.plist'); f['ConfigRoot'] = '${configdir}'; f['RunRoot'] = 'Run'; f['Authentication']['Kerberos']['Enabled'] = False; plistlib.writePlist(f, '${configdir}/caldavd-cdt.plist');&quot;
+python -c &quot;import plistlib; f=plistlib.readPlist('${configdir}/caldavd-cdt-podA.plist'); f['ImportConfig'] = '${configdir}/caldavd-cdt.plist'; f['ServerRoot'] = '${serverrootA}'; f['ProxyLoadFromFile'] = '${configdir}/auth/proxies-cdt.xml'; f['ResourceService']['params']['xmlFile'] = '${configdir}/auth/resources-cdt.xml'; f['DirectoryService']['params']['xmlFile'] = '${configdir}/auth/accounts-cdt.xml'; f['AugmentService']['params']['xmlFiles'] = ['${configdir}/auth/augments-cdt.xml']; plistlib.writePlist(f, '${configdir}/caldavd-cdt-podA.plist');&quot;
+python -c &quot;import plistlib; f=plistlib.readPlist('${configdir}/caldavd-cdt-podB.plist'); f['ImportConfig'] = '${configdir}/caldavd-cdt.plist'; f['ServerRoot'] = '${serverrootB}'; f['ProxyLoadFromFile'] = '${configdir}/auth/proxies-cdt.xml'; f['ResourceService']['params']['xmlFile'] = '${configdir}/auth/resources-cdt.xml'; f['DirectoryService']['params']['xmlFile'] = '${configdir}/auth/accounts-cdt.xml'; f['AugmentService']['params']['xmlFiles'] = ['${configdir}/auth/augments-cdt.xml']; plistlib.writePlist(f, '${configdir}/caldavd-cdt-podB.plist');&quot;
+
+runpod() {
+        local podsuffix=&quot;$1&quot;; shift;
+
+        # Start the server
+        
+        &quot;${wd}/bin/run&quot; -nd -c &quot;${configdir}/caldavd-cdt-${podsuffix}.plist&quot;
+        
+        /bin/echo -n &quot;Waiting for server ${podsuffix} to start up...&quot;
+        
+        while [ ! -f &quot;${sandboxdir}/${podsuffix}/Run/caldav-instance-0.pid&quot; ]; do
+          sleep 1
+          /bin/echo -n &quot;.&quot;
+        done;
+        
+        echo &quot;Server ${podsuffix} has started&quot;
+}
+
+stoppod() {
+        local podsuffix=&quot;$1&quot;; shift;
+
+        echo &quot;Stopping server ${podsuffix}&quot;
+        &quot;${wd}/bin/run&quot; -nk -c &quot;${configdir}/caldavd-cdt-${podsuffix}.plist&quot;
+}
+
+runpod &quot;podA&quot;;
+runpod &quot;podB&quot;;
+
+# Don't exit if testcaldav.py fails, because we need to clean up afterwards.
+
+set +e
+
+# Run CDT
+
+echo &quot;Starting CDT run&quot;
+
+cd &quot;${cdt}&quot; &amp;&amp; &quot;${python}&quot; testcaldav.py ${random} ${seed} ${ssl} ${cdtdebug} --print-details-onfail ${printres} -s &quot;${serverinfo}&quot; -x scripts/tests-pod ${subdir} &quot;$@&quot;;
+
+# Capture exit status of testcaldav.py to use as this script's exit status.
+
+STATUS=$?
+
+# Re-enable exit on failure incase run -nk fails
+
+set -e
+
+stoppod &quot;podA&quot;;
+stoppod &quot;podB&quot;;
+
+# Exit with the exit status of testcaldav.py, to reflect the test suite's result
+
+exit $STATUS
</ins><span class="cx">Property changes on: CalendarServer/trunk/bin/testpods
</span><span class="cx">___________________________________________________________________
</span></span></pre></div>
<a id="svnexecutable"></a>
<div class="addfile"><h4>Added: svn:executable</h4></div>
<a id="CalendarServertrunkconfcaldavdtestpodAplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/conf/caldavd-test-podA.plist (14083 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/conf/caldavd-test-podA.plist        2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/conf/caldavd-test-podA.plist        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -55,7 +55,7 @@
</span><span class="cx">     &lt;key&gt;DirectoryService&lt;/key&gt;
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><del>-      &lt;string&gt;twistedcaldav.directory.xmlfile.XMLDirectoryService&lt;/string&gt;
</del><ins>+      &lt;string&gt;xml&lt;/string&gt;
</ins><span class="cx">       
</span><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="lines">@@ -70,7 +70,7 @@
</span><span class="cx">       &lt;key&gt;Enabled&lt;/key&gt;
</span><span class="cx">       &lt;true/&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><del>-      &lt;string&gt;twistedcaldav.directory.xmlfile.XMLDirectoryService&lt;/string&gt;
</del><ins>+      &lt;string&gt;xml&lt;/string&gt;
</ins><span class="cx">       
</span><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="lines">@@ -202,8 +202,6 @@
</span><span class="cx">                 &lt;/dict&gt;
</span><span class="cx">       &lt;key&gt;MaxClients&lt;/key&gt;
</span><span class="cx">       &lt;integer&gt;5&lt;/integer&gt;
</span><del>-      &lt;key&gt;memcached&lt;/key&gt;
-      &lt;string&gt;../memcached/_root/bin/memcached&lt;/string&gt; &lt;!-- Find in PATH --&gt;
</del><span class="cx">       &lt;key&gt;Options&lt;/key&gt;
</span><span class="cx">       &lt;array&gt;
</span><span class="cx">         &lt;!--&lt;string&gt;-vv&lt;/string&gt;--&gt;
</span></span></pre></div>
<a id="CalendarServertrunkconfcaldavdtestpodBplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/conf/caldavd-test-podB.plist (14083 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/conf/caldavd-test-podB.plist        2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/conf/caldavd-test-podB.plist        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -62,7 +62,7 @@
</span><span class="cx">     &lt;key&gt;DirectoryService&lt;/key&gt;
</span><span class="cx">     &lt;dict&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><del>-      &lt;string&gt;twistedcaldav.directory.xmlfile.XMLDirectoryService&lt;/string&gt;
</del><ins>+      &lt;string&gt;xml&lt;/string&gt;
</ins><span class="cx">       
</span><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="lines">@@ -77,7 +77,7 @@
</span><span class="cx">       &lt;key&gt;Enabled&lt;/key&gt;
</span><span class="cx">       &lt;true/&gt;
</span><span class="cx">       &lt;key&gt;type&lt;/key&gt;
</span><del>-      &lt;string&gt;twistedcaldav.directory.xmlfile.XMLDirectoryService&lt;/string&gt;
</del><ins>+      &lt;string&gt;xml&lt;/string&gt;
</ins><span class="cx">       
</span><span class="cx">       &lt;key&gt;params&lt;/key&gt;
</span><span class="cx">       &lt;dict&gt;
</span><span class="lines">@@ -101,19 +101,6 @@
</span><span class="cx">       &lt;/dict&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><span class="cx"> 
</span><del>-    &lt;!-- Sqlite ProxyDB Service - must use the same one as Pod A--&gt;
-    &lt;key&gt;ProxyDBService&lt;/key&gt;
-    &lt;dict&gt;
-      &lt;key&gt;type&lt;/key&gt;
-      &lt;string&gt;twistedcaldav.directory.calendaruserproxy.ProxySqliteDB&lt;/string&gt;
-      
-      &lt;key&gt;params&lt;/key&gt;
-      &lt;dict&gt;
-        &lt;key&gt;dbpath&lt;/key&gt;
-        &lt;string&gt;./data/podA/Data/proxies.sqlite&lt;/string&gt;
-      &lt;/dict&gt;
-    &lt;/dict&gt;
-
</del><span class="cx">     &lt;key&gt;ProxyLoadFromFile&lt;/key&gt;
</span><span class="cx">     &lt;string&gt;./conf/auth/proxies-test-pod.xml&lt;/string&gt;
</span><span class="cx"> 
</span><span class="lines">@@ -222,8 +209,6 @@
</span><span class="cx">                 &lt;/dict&gt;
</span><span class="cx">       &lt;key&gt;MaxClients&lt;/key&gt;
</span><span class="cx">       &lt;integer&gt;5&lt;/integer&gt;
</span><del>-      &lt;key&gt;memcached&lt;/key&gt;
-      &lt;string&gt;../memcached/_root/bin/memcached&lt;/string&gt; &lt;!-- Find in PATH --&gt;
</del><span class="cx">       &lt;key&gt;Options&lt;/key&gt;
</span><span class="cx">       &lt;array&gt;
</span><span class="cx">         &lt;!--&lt;string&gt;-vv&lt;/string&gt;--&gt;
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingischedulelocalserverspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py (14083 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py        2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -105,6 +105,10 @@
</span><span class="cx">         return self._thisServer
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def allServersExceptThis(self):
+        return filter(lambda x: x != self._thisServer, self._servers.values())
+
+
</ins><span class="cx">     def installReverseProxies(self, maxClients):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Install a reverse proxy for each of the other servers in the &quot;pod&quot;.
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingischeduletesttest_localserverspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py (14083 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py        2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/test/test_localservers.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -93,6 +93,7 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertTrue(servers.getServerById(&quot;00001&quot;).thisServer)
</span><span class="cx">         self.assertFalse(servers.getServerById(&quot;00002&quot;).thisServer)
</span><ins>+        self.assertEqual(servers.getThisServer(), servers.getServerById(&quot;00001&quot;))
</ins><span class="cx"> 
</span><span class="cx">         self.patch(config, &quot;ServerHostName&quot;, &quot;caldav2.example.com&quot;)
</span><span class="cx">         self.patch(config, &quot;SSLPort&quot;, 8443)
</span><span class="lines">@@ -104,8 +105,30 @@
</span><span class="cx"> 
</span><span class="cx">         self.assertFalse(servers.getServerById(&quot;00001&quot;).thisServer)
</span><span class="cx">         self.assertTrue(servers.getServerById(&quot;00002&quot;).thisServer)
</span><ins>+        self.assertEqual(servers.getThisServer(), servers.getServerById(&quot;00002&quot;))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def test_all_except_this_server(self):
+
+        servers = self._setupServers()
+
+        self.assertTrue(servers.getServerById(&quot;00001&quot;).thisServer)
+        self.assertFalse(servers.getServerById(&quot;00002&quot;).thisServer)
+        self.assertEqual(servers.allServersExceptThis(), [servers.getServerById(&quot;00002&quot;), ])
+
+        self.patch(config, &quot;ServerHostName&quot;, &quot;caldav2.example.com&quot;)
+        self.patch(config, &quot;SSLPort&quot;, 8443)
+        self.patch(config, &quot;BindSSLPorts&quot;, [8843])
+
+        xmlFile = StringIO.StringIO(ServerTests.data1)
+        servers = ServersDB()
+        servers.load(xmlFile, ignoreIPLookupFailures=True)
+
+        self.assertFalse(servers.getServerById(&quot;00001&quot;).thisServer)
+        self.assertTrue(servers.getServerById(&quot;00002&quot;).thisServer)
+        self.assertEqual(servers.allServersExceptThis(), [servers.getServerById(&quot;00001&quot;), ])
+
+
</ins><span class="cx">     def test_check_this_ip(self):
</span><span class="cx"> 
</span><span class="cx">         servers = self._setupServers()
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingattachmentspy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/podding/attachments.py (0 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/attachments.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/attachments.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -0,0 +1,204 @@
</span><ins>+##
+# Copyright (c) 2013-2014 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 twisted.python.reflect import namedClass
+
+
+class AttachmentsPoddingConduitMixin(object):
+    &quot;&quot;&quot;
+    Defines the cross-pod API for managed attachments that will be mixed into the
+    L{PoddingConduit} class.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def send_add_attachment(self, objectResource, rids, content_type, filename, stream):
+        &quot;&quot;&quot;
+        Managed attachment addAttachment call.
+
+        @param objectResource: child resource having an attachment added
+        @type objectResource: L{CalendarObject}
+        @param rids: list of recurrence ids
+        @type rids: C{list}
+        @param content_type: content type of attachment data
+        @type content_type: L{MimeType}
+        @param filename: name of attachment
+        @type filename: C{str}
+        @param stream: attachment data stream
+        @type stream: L{IStream}
+        &quot;&quot;&quot;
+
+        actionName = &quot;add-attachment&quot;
+        shareeView = objectResource._parentCollection
+        request, recipient = yield self._getRequestForResource(actionName, shareeView, objectResource)
+        request[&quot;rids&quot;] = rids
+        request[&quot;filename&quot;] = filename
+
+        response = yield self.sendRequest(shareeView._txn, recipient, request, stream, content_type)
+
+        if response[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue(response[&quot;value&quot;])
+        elif response[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(response[&quot;class&quot;])(response[&quot;result&quot;])
+
+
+    @inlineCallbacks
+    def recv_add_attachment(self, txn, request):
+        &quot;&quot;&quot;
+        Process an addAttachment cross-pod request. Request arguments as per L{send_add_attachment}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        actionName = &quot;add-attachment&quot;
+        _ignore_shareeView, objectResource = yield self._getResourcesForRequest(txn, request, actionName)
+        try:
+            attachment, location = yield objectResource.addAttachment(
+                request[&quot;rids&quot;],
+                request[&quot;streamType&quot;],
+                request[&quot;filename&quot;],
+                request[&quot;stream&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;request&quot;: str(e),
+            })
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+            &quot;value&quot;: (attachment.managedID(), location,),
+        })
+
+
+    @inlineCallbacks
+    def send_update_attachment(self, objectResource, managed_id, content_type, filename, stream):
+        &quot;&quot;&quot;
+        Managed attachment updateAttachment call.
+
+        @param objectResource: child resource having an attachment added
+        @type objectResource: L{CalendarObject}
+        @param managed_id: managed-id to update
+        @type managed_id: C{str}
+        @param content_type: content type of attachment data
+        @type content_type: L{MimeType}
+        @param filename: name of attachment
+        @type filename: C{str}
+        @param stream: attachment data stream
+        @type stream: L{IStream}
+        &quot;&quot;&quot;
+
+        actionName = &quot;update-attachment&quot;
+        shareeView = objectResource._parentCollection
+        request, recipient = yield self._getRequestForResource(actionName, shareeView, objectResource)
+        request[&quot;managedID&quot;] = managed_id
+        request[&quot;filename&quot;] = filename
+
+        response = yield self.sendRequest(shareeView._txn, recipient, request, stream, content_type)
+
+        if response[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue(response[&quot;value&quot;])
+        elif response[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(response[&quot;class&quot;])(response[&quot;result&quot;])
+
+
+    @inlineCallbacks
+    def recv_update_attachment(self, txn, request):
+        &quot;&quot;&quot;
+        Process an updateAttachment cross-pod request. Request arguments as per L{send_update_attachment}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        actionName = &quot;update-attachment&quot;
+        _ignore_shareeView, objectResource = yield self._getResourcesForRequest(txn, request, actionName)
+        try:
+            attachment, location = yield objectResource.updateAttachment(
+                request[&quot;managedID&quot;],
+                request[&quot;streamType&quot;],
+                request[&quot;filename&quot;],
+                request[&quot;stream&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;request&quot;: str(e),
+            })
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+            &quot;value&quot;: (attachment.managedID(), location,),
+        })
+
+
+    @inlineCallbacks
+    def send_remove_attachment(self, objectResource, rids, managed_id):
+        &quot;&quot;&quot;
+        Managed attachment removeAttachment call.
+
+        @param objectResource: child resource having an attachment added
+        @type objectResource: L{CalendarObject}
+        @param rids: list of recurrence ids
+        @type rids: C{list}
+        @param managed_id: managed-id to update
+        @type managed_id: C{str}
+        &quot;&quot;&quot;
+
+        actionName = &quot;remove-attachment&quot;
+        shareeView = objectResource._parentCollection
+        request, recipient = yield self._getRequestForResource(actionName, shareeView, objectResource)
+        request[&quot;rids&quot;] = rids
+        request[&quot;managedID&quot;] = managed_id
+
+        response = yield self.sendRequest(shareeView._txn, recipient, request)
+
+        if response[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue(response[&quot;value&quot;])
+        elif response[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(response[&quot;class&quot;])(response[&quot;result&quot;])
+
+
+    @inlineCallbacks
+    def recv_remove_attachment(self, txn, request):
+        &quot;&quot;&quot;
+        Process an removeAttachment cross-pod request. Request arguments as per L{send_remove_attachment}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        actionName = &quot;remove-attachment&quot;
+        _ignore_shareeView, objectResource = yield self._getResourcesForRequest(txn, request, actionName)
+        try:
+            yield objectResource.removeAttachment(
+                request[&quot;rids&quot;],
+                request[&quot;managedID&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;request&quot;: str(e),
+            })
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+            &quot;value&quot;: None,
+        })
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingbasepy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/podding/base.py (0 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/base.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/base.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -0,0 +1,21 @@
</span><ins>+##
+# Copyright (c) 2013-2014 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.
+##
+
+class FailedCrossPodRequestError(RuntimeError):
+    &quot;&quot;&quot;
+    Request returned an error.
+    &quot;&quot;&quot;
+    pass
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingconduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/podding/conduit.py (14083 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/conduit.py        2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/common/datastore/podding/conduit.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -17,29 +17,26 @@
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><del>-from twisted.python.reflect import namedClass
</del><span class="cx"> 
</span><del>-from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
-from txdav.common.datastore.podding.request import ConduitRequest
</del><span class="cx"> from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
</span><del>-from txdav.common.icommondatastore import ExternalShareFailed
</del><ins>+from txdav.common.datastore.podding.attachments import AttachmentsPoddingConduitMixin
+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
+from txdav.common.datastore.podding.directory import DirectoryPoddingConduitMixin
+from txdav.common.datastore.podding.request import ConduitRequest
+from txdav.common.datastore.podding.sharing_invites import SharingInvitesPoddingConduitMixin
+from txdav.common.datastore.podding.sharing_store import SharingStorePoddingConduitMixin
</ins><span class="cx"> 
</span><del>-from twistedcaldav.caldavxml import TimeRange
</del><span class="cx"> 
</span><del>-
</del><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class FailedCrossPodRequestError(RuntimeError):
</del><ins>+class PoddingConduit(
+    AttachmentsPoddingConduitMixin,
+    SharingInvitesPoddingConduitMixin,
+    SharingStorePoddingConduitMixin,
+    DirectoryPoddingConduitMixin,
+):
</ins><span class="cx">     &quot;&quot;&quot;
</span><del>-    Request returned an error.
-    &quot;&quot;&quot;
-    pass
-
-
-
-class PoddingConduit(object):
-    &quot;&quot;&quot;
</del><span class="cx">     This class is the API/RPC bridge between cross-pod requests and the store.
</span><span class="cx"> 
</span><span class="cx">     Each cross-pod request/response is described by a Python C{dict} that is serialized
</span><span class="lines">@@ -63,8 +60,8 @@
</span><span class="cx"> 
</span><span class="cx">     Some simple forms of send_/recv_ methods can be auto-generated to simplify coding.
</span><span class="cx"> 
</span><del>-    Right now this conduit is used for cross-pod sharing operations. In the future we will
-    likely use it for cross-pod migration.
</del><ins>+    Actual implementations of this will be done via mix-ins for the different sub-systems using
+    the conduit.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     conduitRequestClass = ConduitRequest
</span><span class="lines">@@ -105,10 +102,14 @@
</span><span class="cx">         returnValue((source, destination,))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
</del><span class="cx">     def sendRequest(self, txn, recipient, data, stream=None, streamType=None):
</span><ins>+        return self.sendRequestToServer(txn, recipient.server(), data, stream, streamType)
</ins><span class="cx"> 
</span><del>-        request = self.conduitRequestClass(recipient.server(), data, stream, streamType)
</del><ins>+
+    @inlineCallbacks
+    def sendRequestToServer(self, txn, server, data, stream=None, streamType=None):
+
+        request = self.conduitRequestClass(server, data, stream, streamType)
</ins><span class="cx">         try:
</span><span class="cx">             response = (yield request.doRequest(txn))
</span><span class="cx">         except Exception as e:
</span><span class="lines">@@ -154,691 +155,3 @@
</span><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         returnValue(result)
</span><del>-
-
-    #
-    # Invite related apis
-    #
-
-    @inlineCallbacks
-    def send_shareinvite(self, txn, homeType, ownerUID, ownerID, ownerName, shareeUID, shareUID, bindMode, summary, copy_properties, supported_components):
-        &quot;&quot;&quot;
-        Send a sharing invite cross-pod message.
-
-        @param homeType: Type of home being shared.
-        @type homeType: C{int}
-        @param ownerUID: UID of the sharer.
-        @type ownerUID: C{str}
-        @param ownerID: resource ID of the sharer calendar
-        @type ownerID: C{int}
-        @param ownerName: owner's name of the sharer calendar
-        @type ownerName: C{str}
-        @param shareeUID: UID of the sharee
-        @type shareeUID: C{str}
-        @param shareUID: Resource/invite ID for sharee
-        @type shareUID: C{str}
-        @param bindMode: bind mode for the share
-        @type bindMode: C{str}
-        @param summary: sharing message
-        @type summary: C{str}
-        @param copy_properties: C{str} name/value for properties to be copied
-        @type copy_properties: C{dict}
-        @param supported_components: supproted components, may be C{None}
-        @type supported_components: C{str}
-        &quot;&quot;&quot;
-
-        _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
-
-        action = {
-            &quot;action&quot;: &quot;shareinvite&quot;,
-            &quot;type&quot;: homeType,
-            &quot;owner&quot;: ownerUID,
-            &quot;owner_id&quot;: ownerID,
-            &quot;owner_name&quot;: ownerName,
-            &quot;sharee&quot;: shareeUID,
-            &quot;share_id&quot;: shareUID,
-            &quot;mode&quot;: bindMode,
-            &quot;summary&quot;: summary,
-            &quot;properties&quot;: copy_properties,
-        }
-        if supported_components is not None:
-            action[&quot;supported-components&quot;] = supported_components
-
-        result = yield self.sendRequest(txn, recipient, action)
-        returnValue(result)
-
-
-    @inlineCallbacks
-    def recv_shareinvite(self, txn, message):
-        &quot;&quot;&quot;
-        Process a sharing invite cross-pod message. Message arguments as per L{send_shareinvite}.
-
-        @param message: message arguments
-        @type message: C{dict}
-        &quot;&quot;&quot;
-
-        if message[&quot;action&quot;] != &quot;shareinvite&quot;:
-            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_shareinvite&quot;.format(message[&quot;action&quot;]))
-
-        # Create a share
-        shareeHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;sharee&quot;], create=True)
-        if shareeHome is None or shareeHome.external():
-            raise FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
-
-        try:
-            yield shareeHome.processExternalInvite(
-                message[&quot;owner&quot;],
-                message[&quot;owner_id&quot;],
-                message[&quot;owner_name&quot;],
-                message[&quot;share_id&quot;],
-                message[&quot;mode&quot;],
-                message[&quot;summary&quot;],
-                message[&quot;properties&quot;],
-                supported_components=message.get(&quot;supported-components&quot;)
-            )
-        except ExternalShareFailed as e:
-            raise FailedCrossPodRequestError(str(e))
-
-        returnValue({
-            &quot;result&quot;: &quot;ok&quot;,
-        })
-
-
-    @inlineCallbacks
-    def send_shareuninvite(self, txn, homeType, ownerUID, ownerID, shareeUID, shareUID):
-        &quot;&quot;&quot;
-        Send a sharing uninvite cross-pod message.
-
-        @param homeType: Type of home being shared.
-        @type homeType: C{int}
-        @param ownerUID: UID of the sharer.
-        @type ownerUID: C{str}
-        @param ownerID: resource ID of the sharer calendar
-        @type ownerID: C{int}
-        @param shareeUID: UID of the sharee
-        @type shareeUID: C{str}
-        @param shareUID: Resource/invite ID for sharee
-        @type shareUID: C{str}
-        &quot;&quot;&quot;
-
-        _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
-
-        action = {
-            &quot;action&quot;: &quot;shareuninvite&quot;,
-            &quot;type&quot;: homeType,
-            &quot;owner&quot;: ownerUID,
-            &quot;owner_id&quot;: ownerID,
-            &quot;sharee&quot;: shareeUID,
-            &quot;share_id&quot;: shareUID,
-        }
-
-        result = yield self.sendRequest(txn, recipient, action)
-        returnValue(result)
-
-
-    @inlineCallbacks
-    def recv_shareuninvite(self, txn, message):
-        &quot;&quot;&quot;
-        Process a sharing uninvite cross-pod message. Message arguments as per L{send_shareuninvite}.
-
-        @param message: message arguments
-        @type message: C{dict}
-        &quot;&quot;&quot;
-
-        if message[&quot;action&quot;] != &quot;shareuninvite&quot;:
-            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_shareuninvite&quot;.format(message[&quot;action&quot;]))
-
-        # Create a share
-        shareeHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;sharee&quot;], create=True)
-        if shareeHome is None or shareeHome.external():
-            FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
-
-        try:
-            yield shareeHome.processExternalUninvite(
-                message[&quot;owner&quot;],
-                message[&quot;owner_id&quot;],
-                message[&quot;share_id&quot;],
-            )
-        except ExternalShareFailed as e:
-            FailedCrossPodRequestError(str(e))
-
-        returnValue({
-            &quot;result&quot;: &quot;ok&quot;,
-        })
-
-
-    @inlineCallbacks
-    def send_sharereply(self, txn, homeType, ownerUID, shareeUID, shareUID, bindStatus, summary=None):
-        &quot;&quot;&quot;
-        Send a sharing reply cross-pod message.
-
-        @param homeType: Type of home being shared.
-        @type homeType: C{int}
-        @param ownerUID: UID of the sharer.
-        @type ownerUID: C{str}
-        @param shareeUID: UID of the recipient
-        @type shareeUID: C{str}
-        @param shareUID: Resource/invite ID for recipient
-        @type shareUID: C{str}
-        @param bindStatus: bind mode for the share
-        @type bindStatus: C{str}
-        @param summary: sharing message
-        @type summary: C{str}
-        &quot;&quot;&quot;
-
-        _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
-
-        action = {
-            &quot;action&quot;: &quot;sharereply&quot;,
-            &quot;type&quot;: homeType,
-            &quot;owner&quot;: ownerUID,
-            &quot;sharee&quot;: shareeUID,
-            &quot;share_id&quot;: shareUID,
-            &quot;status&quot;: bindStatus,
-        }
-        if summary is not None:
-            action[&quot;summary&quot;] = summary
-
-        result = yield self.sendRequest(txn, recipient, action)
-        returnValue(result)
-
-
-    @inlineCallbacks
-    def recv_sharereply(self, txn, message):
-        &quot;&quot;&quot;
-        Process a sharing reply cross-pod message. Message arguments as per L{send_sharereply}.
-
-        @param message: message arguments
-        @type message: C{dict}
-        &quot;&quot;&quot;
-
-        if message[&quot;action&quot;] != &quot;sharereply&quot;:
-            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_sharereply&quot;.format(message[&quot;action&quot;]))
-
-        # Create a share
-        ownerHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;owner&quot;])
-        if ownerHome is None or ownerHome.external():
-            FailedCrossPodRequestError(&quot;Invalid owner UID specified&quot;)
-
-        try:
-            yield ownerHome.processExternalReply(
-                message[&quot;owner&quot;],
-                message[&quot;sharee&quot;],
-                message[&quot;share_id&quot;],
-                message[&quot;status&quot;],
-                summary=message.get(&quot;summary&quot;)
-            )
-        except ExternalShareFailed as e:
-            FailedCrossPodRequestError(str(e))
-
-        returnValue({
-            &quot;result&quot;: &quot;ok&quot;,
-        })
-
-
-    #
-    # Managed attachment related apis
-    #
-
-    @inlineCallbacks
-    def send_add_attachment(self, objectResource, rids, content_type, filename, stream):
-        &quot;&quot;&quot;
-        Managed attachment addAttachment call.
-
-        @param objectResource: child resource having an attachment added
-        @type objectResource: L{CalendarObject}
-        @param rids: list of recurrence ids
-        @type rids: C{list}
-        @param content_type: content type of attachment data
-        @type content_type: L{MimeType}
-        @param filename: name of attachment
-        @type filename: C{str}
-        @param stream: attachment data stream
-        @type stream: L{IStream}
-        &quot;&quot;&quot;
-
-        actionName = &quot;add-attachment&quot;
-        shareeView = objectResource._parentCollection
-        action, recipient = yield self._send(actionName, shareeView, objectResource)
-        action[&quot;rids&quot;] = rids
-        action[&quot;filename&quot;] = filename
-        result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
-        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;])
-
-
-    @inlineCallbacks
-    def recv_add_attachment(self, txn, message):
-        &quot;&quot;&quot;
-        Process an addAttachment cross-pod message. Message arguments as per L{send_add_attachment}.
-
-        @param message: message arguments
-        @type message: C{dict}
-        &quot;&quot;&quot;
-
-        actionName = &quot;add-attachment&quot;
-        _ignore_shareeView, objectResource = yield self._recv(txn, message, actionName)
-        try:
-            attachment, location = yield objectResource.addAttachment(
-                message[&quot;rids&quot;],
-                message[&quot;streamType&quot;],
-                message[&quot;filename&quot;],
-                message[&quot;stream&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),
-            })
-
-        returnValue({
-            &quot;result&quot;: &quot;ok&quot;,
-            &quot;value&quot;: (attachment.managedID(), location,),
-        })
-
-
-    @inlineCallbacks
-    def send_update_attachment(self, objectResource, managed_id, content_type, filename, stream):
-        &quot;&quot;&quot;
-        Managed attachment updateAttachment call.
-
-        @param objectResource: child resource having an attachment added
-        @type objectResource: L{CalendarObject}
-        @param managed_id: managed-id to update
-        @type managed_id: C{str}
-        @param content_type: content type of attachment data
-        @type content_type: L{MimeType}
-        @param filename: name of attachment
-        @type filename: C{str}
-        @param stream: attachment data stream
-        @type stream: L{IStream}
-        &quot;&quot;&quot;
-
-        actionName = &quot;update-attachment&quot;
-        shareeView = objectResource._parentCollection
-        action, recipient = yield self._send(actionName, shareeView, objectResource)
-        action[&quot;managedID&quot;] = managed_id
-        action[&quot;filename&quot;] = filename
-        result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
-        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;])
-
-
-    @inlineCallbacks
-    def recv_update_attachment(self, txn, message):
-        &quot;&quot;&quot;
-        Process an updateAttachment cross-pod message. Message arguments as per L{send_update_attachment}.
-
-        @param message: message arguments
-        @type message: C{dict}
-        &quot;&quot;&quot;
-
-        actionName = &quot;update-attachment&quot;
-        _ignore_shareeView, objectResource = yield self._recv(txn, message, actionName)
-        try:
-            attachment, location = yield objectResource.updateAttachment(
-                message[&quot;managedID&quot;],
-                message[&quot;streamType&quot;],
-                message[&quot;filename&quot;],
-                message[&quot;stream&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),
-            })
-
-        returnValue({
-            &quot;result&quot;: &quot;ok&quot;,
-            &quot;value&quot;: (attachment.managedID(), location,),
-        })
-
-
-    @inlineCallbacks
-    def send_remove_attachment(self, objectResource, rids, managed_id):
-        &quot;&quot;&quot;
-        Managed attachment removeAttachment call.
-
-        @param objectResource: child resource having an attachment added
-        @type objectResource: L{CalendarObject}
-        @param rids: list of recurrence ids
-        @type rids: C{list}
-        @param managed_id: managed-id to update
-        @type managed_id: C{str}
-        &quot;&quot;&quot;
-
-        actionName = &quot;remove-attachment&quot;
-        shareeView = objectResource._parentCollection
-        action, recipient = yield self._send(actionName, shareeView, objectResource)
-        action[&quot;rids&quot;] = rids
-        action[&quot;managedID&quot;] = managed_id
-        result = yield self.sendRequest(shareeView._txn, recipient, action)
-        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;])
-
-
-    @inlineCallbacks
-    def recv_remove_attachment(self, txn, message):
-        &quot;&quot;&quot;
-        Process an removeAttachment cross-pod message. Message arguments as per L{send_remove_attachment}.
-
-        @param message: message arguments
-        @type message: C{dict}
-        &quot;&quot;&quot;
-
-        actionName = &quot;remove-attachment&quot;
-        _ignore_shareeView, objectResource = yield self._recv(txn, message, actionName)
-        try:
-            yield objectResource.removeAttachment(
-                message[&quot;rids&quot;],
-                message[&quot;managedID&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),
-            })
-
-        returnValue({
-            &quot;result&quot;: &quot;ok&quot;,
-            &quot;value&quot;: None,
-        })
-
-
-    #
-    # Sharer data access related apis
-    #
-
-    @inlineCallbacks
-    def _send(self, action, parent, child=None):
-        &quot;&quot;&quot;
-        Base behavior for an operation on a L{CommonHomeChild}.
-
-        @param shareeView: sharee resource being operated on.
-        @type shareeView: L{CommonHomeChildExternal}
-        &quot;&quot;&quot;
-
-        homeType = parent.ownerHome()._homeType
-        ownerUID = parent.ownerHome().uid()
-        ownerID = parent.external_id()
-        shareeUID = parent.viewerHome().uid()
-
-        _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
-
-        result = {
-            &quot;action&quot;: action,
-            &quot;type&quot;: homeType,
-            &quot;owner&quot;: ownerUID,
-            &quot;owner_id&quot;: ownerID,
-            &quot;sharee&quot;: shareeUID,
-        }
-        if child is not None:
-            result[&quot;resource_id&quot;] = child.id()
-        returnValue((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))
-
-        # Get a share
-        ownerHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;owner&quot;])
-        if ownerHome is None or ownerHome.external():
-            FailedCrossPodRequestError(&quot;Invalid owner UID specified&quot;)
-
-        shareeHome = yield txn.homeWithUID(message[&quot;type&quot;], message[&quot;sharee&quot;])
-        if shareeHome is None or not shareeHome.external():
-            FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
-
-        shareeView = yield shareeHome.childWithID(message[&quot;owner_id&quot;])
-        if shareeView is None:
-            FailedCrossPodRequestError(&quot;Invalid shared resource specified&quot;)
-
-        resourceID = message.get(&quot;resource_id&quot;, None)
-        if resourceID is not None:
-            objectResource = yield shareeView.objectResourceWithID(resourceID)
-            if objectResource is None:
-                FailedCrossPodRequestError(&quot;Invalid owner shared object resource specified&quot;)
-        else:
-            objectResource = None
-
-        returnValue((shareeView, objectResource,))
-
-
-    #
-    # 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, objectResource=None, transform=None, 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 objectResource: the resource being operated on, or C{None} for classmethod.
-        @type objectResource: L{CommonObjectResourceExternal}
-        @param transform: a function used to convert the JSON result into return values.
-        @type transform: C{callable}
-        @param args: list of optional arguments.
-        @type args: C{list}
-        @param kwargs: optional keyword arguments.
-        @type kwargs: C{dict}
-        &quot;&quot;&quot;
-
-        action, recipient = yield self._send(actionName, shareeView, objectResource)
-        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)
-        if result[&quot;result&quot;] == &quot;ok&quot;:
-            returnValue(result[&quot;value&quot;] if transform is None else transform(result[&quot;value&quot;], shareeView, objectResource))
-        elif result[&quot;result&quot;] == &quot;exception&quot;:
-            raise namedClass(result[&quot;class&quot;])(result[&quot;message&quot;])
-
-
-    @inlineCallbacks
-    def _simple_recv(self, txn, actionName, message, method, onHomeChild=True, transform=None):
-        &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}
-        @param transform: method to call on returned JSON value to convert it to something useful.
-        @type transform: C{callable}
-        &quot;&quot;&quot;
-
-        shareeView, objectResource = yield self._recv(txn, message, actionName)
-        try:
-            if onHomeChild:
-                # Operate on the L{CommonHomeChild}
-                value = yield getattr(shareeView, 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(shareeView._objectResourceClass, method)(shareeView, *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),
-            })
-
-        returnValue({
-            &quot;result&quot;: &quot;ok&quot;,
-            &quot;value&quot;: transform(value, shareeView, objectResource) if transform is not None else value,
-        })
-
-
-    @inlineCallbacks
-    def send_freebusy(
-        self,
-        calresource,
-        timerange,
-        matchtotal,
-        excludeuid,
-        organizer,
-        organizerPrincipal,
-        same_calendar_user,
-        servertoserver,
-        event_details,
-    ):
-        action, recipient = yield self._send(&quot;freebusy&quot;, calresource)
-        action[&quot;timerange&quot;] = [timerange.start.getText(), timerange.end.getText()]
-        action[&quot;matchtotal&quot;] = matchtotal
-        action[&quot;excludeuid&quot;] = excludeuid
-        action[&quot;organizer&quot;] = organizer
-        action[&quot;organizerPrincipal&quot;] = organizerPrincipal
-        action[&quot;same_calendar_user&quot;] = same_calendar_user
-        action[&quot;servertoserver&quot;] = servertoserver
-        action[&quot;event_details&quot;] = event_details
-        result = yield self.sendRequest(calresource._txn, recipient, action)
-        if result[&quot;result&quot;] == &quot;ok&quot;:
-            returnValue((result[&quot;fbresults&quot;], result[&quot;matchtotal&quot;],))
-        elif result[&quot;result&quot;] == &quot;exception&quot;:
-            raise namedClass(result[&quot;class&quot;])(result[&quot;message&quot;])
-
-
-    @inlineCallbacks
-    def recv_freebusy(self, txn, message):
-        &quot;&quot;&quot;
-        Process a freebusy cross-pod message. Message arguments as per L{send_freebusy}.
-
-        @param message: message arguments
-        @type message: C{dict}
-        &quot;&quot;&quot;
-
-        shareeView, _ignore_objectResource = yield self._recv(txn, message, &quot;freebusy&quot;)
-        try:
-            # Operate on the L{CommonHomeChild}
-            fbinfo = [[], [], []]
-            matchtotal = yield generateFreeBusyInfo(
-                shareeView,
-                fbinfo,
-                TimeRange(start=message[&quot;timerange&quot;][0], end=message[&quot;timerange&quot;][1]),
-                message[&quot;matchtotal&quot;],
-                message[&quot;excludeuid&quot;],
-                message[&quot;organizer&quot;],
-                message[&quot;organizerPrincipal&quot;],
-                message[&quot;same_calendar_user&quot;],
-                message[&quot;servertoserver&quot;],
-                message[&quot;event_details&quot;],
-                logItems=None
-            )
-        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),
-            })
-
-        for i in range(3):
-            for j in range(len(fbinfo[i])):
-                fbinfo[i][j] = fbinfo[i][j].getText()
-
-        returnValue({
-            &quot;result&quot;: &quot;ok&quot;,
-            &quot;fbresults&quot;: fbinfo,
-            &quot;matchtotal&quot;: matchtotal,
-        })
-
-
-    @staticmethod
-    def _to_tuple(value, shareeView, objectResource):
-        return tuple(value)
-
-
-    @staticmethod
-    def _to_string(value, shareeView, objectResource):
-        return str(value)
-
-
-    @staticmethod
-    def _to_externalize(value, shareeView, objectResource):
-        if isinstance(value, shareeView._objectResourceClass):
-            value = value.externalize()
-        elif value is not None:
-            value = [v.externalize() for v in value]
-        return value
-
-
-    @classmethod
-    def _make_simple_homechild_action(cls, action, method, transform_recv=None, transform_send=None):
-        setattr(
-            cls,
-            &quot;send_{}&quot;.format(action),
-            lambda self, shareeView, *args, **kwargs:
-                self._simple_send(action, shareeView, transform=transform_send, args=args, kwargs=kwargs)
-        )
-        setattr(
-            cls,
-            &quot;recv_{}&quot;.format(action),
-            lambda self, txn, message:
-                self._simple_recv(txn, action, message, method, transform=transform_recv)
-        )
-
-
-    @classmethod
-    def _make_simple_object_action(cls, action, method, transform_recv=None, transform_send=None):
-        setattr(
-            cls,
-            &quot;send_{}&quot;.format(action),
-            lambda self, shareeView, objectResource, *args, **kwargs:
-                self._simple_send(action, shareeView, objectResource, transform=transform_send, 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_recv)
-        )
-
-
-# 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;resourceuidforname&quot;, &quot;resourceUIDForName&quot;)
-PoddingConduit._make_simple_homechild_action(&quot;resourcenameforuid&quot;, &quot;resourceNameForUID&quot;)
-PoddingConduit._make_simple_homechild_action(&quot;movehere&quot;, &quot;moveObjectResourceHere&quot;)
-PoddingConduit._make_simple_homechild_action(&quot;moveaway&quot;, &quot;moveObjectResourceAway&quot;)
-PoddingConduit._make_simple_homechild_action(&quot;synctoken&quot;, &quot;syncToken&quot;)
-PoddingConduit._make_simple_homechild_action(&quot;resourcenamessincerevision&quot;, &quot;resourceNamesSinceRevision&quot;, transform_send=PoddingConduit._to_tuple)
-PoddingConduit._make_simple_homechild_action(&quot;search&quot;, &quot;search&quot;)
-
-# Calls on L{CommonObjectResource} objects
-PoddingConduit._make_simple_object_action(&quot;loadallobjects&quot;, &quot;loadAllObjects&quot;, transform_recv=PoddingConduit._to_externalize)
-PoddingConduit._make_simple_object_action(&quot;loadallobjectswithnames&quot;, &quot;loadAllObjectsWithNames&quot;, transform_recv=PoddingConduit._to_externalize)
-PoddingConduit._make_simple_object_action(&quot;objectwith&quot;, &quot;objectWith&quot;, transform_recv=PoddingConduit._to_externalize)
-PoddingConduit._make_simple_object_action(&quot;create&quot;, &quot;create&quot;, transform_recv=PoddingConduit._to_externalize)
-PoddingConduit._make_simple_object_action(&quot;setcomponent&quot;, &quot;setComponent&quot;)
-PoddingConduit._make_simple_object_action(&quot;component&quot;, &quot;component&quot;, transform_recv=PoddingConduit._to_string)
-PoddingConduit._make_simple_object_action(&quot;remove&quot;, &quot;remove&quot;)
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingdirectorypy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/podding/directory.py (0 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/directory.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/directory.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -0,0 +1,215 @@
</span><ins>+##
+# Copyright (c) 2013-2014 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 twisted.python.reflect import namedClass
+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
+from txdav.who.delegates import _delegatesOfUIDs, _delegatedToUIDs, setDelegates
+
+
+class DirectoryPoddingConduitMixin(object):
+    &quot;&quot;&quot;
+    Defines the cross-pod API for common directory operations that will be mixed into the
+    L{PoddingConduit} class.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def send_set_delegates(self, txn, delegator, delegates, readWrite):
+        &quot;&quot;&quot;
+        Set delegates for delegator on another pod.
+
+        @param txn: transaction to use
+        @type txn: L{CommonStoreTransaction}
+        @param delegator: delegator to set
+        @type delegator: L{DirectoryRecord}
+        @param delegates: delegates to set
+        @type delegates: L{list} of L{DirectoryRecord}
+        @param readWrite: if True, read and write access delegates are returned;
+            read-only access otherwise
+        &quot;&quot;&quot;
+        if delegator.thisServer():
+            raise FailedCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(delegator.uid))
+
+        request = {
+            &quot;action&quot;: &quot;set-delegates&quot;,
+            &quot;uid&quot;: delegator.uid,
+            &quot;delegates&quot;: [delegate.uid for delegate in delegates],
+            &quot;read-write&quot;: readWrite,
+        }
+        response = yield self.sendRequestToServer(txn, delegator.server(), request)
+
+        if response[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue(None)
+        elif response[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(response[&quot;class&quot;])(response[&quot;result&quot;])
+
+
+    @inlineCallbacks
+    def recv_set_delegates(self, txn, request):
+        &quot;&quot;&quot;
+        Process a set delegates cross-pod request. Request arguments as per L{send_set_delegates}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        if request[&quot;action&quot;] != &quot;set-delegates&quot;:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_set_delegates&quot;.format(request[&quot;action&quot;]))
+
+        try:
+            delegator = yield txn.directoryService().recordWithUID(request[&quot;uid&quot;])
+            if delegator is None or not delegator.thisServer():
+                raise FailedCrossPodRequestError(&quot;Cross-pod delegate not on this server: {}&quot;.format(delegator.uid))
+
+            delegates = []
+            for uid in request[&quot;delegates&quot;]:
+                delegate = yield txn.directoryService().recordWithUID(uid)
+                if delegate is None:
+                    raise FailedCrossPodRequestError(&quot;Cross-pod delegate missing on this server: {}&quot;.format(uid))
+                delegates.append(delegate)
+
+            yield setDelegates(txn, delegator, delegates, request[&quot;read-write&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;request&quot;: str(e),
+            })
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+        })
+
+
+    @inlineCallbacks
+    def send_get_delegates(self, txn, delegator, readWrite, expanded=False):
+        &quot;&quot;&quot;
+        Get delegates from another pod.
+
+        @param txn: transaction to use
+        @type txn: L{CommonStoreTransaction}
+        @param delegator: delegator to lookup
+        @type delegator: L{DirectoryRecord}
+        @param readWrite: if True, read and write access delegates are returned;
+            read-only access otherwise
+        &quot;&quot;&quot;
+        if delegator.thisServer():
+            raise FailedCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(delegator.uid))
+
+        request = {
+            &quot;action&quot;: &quot;get-delegates&quot;,
+            &quot;uid&quot;: delegator.uid,
+            &quot;read-write&quot;: readWrite,
+            &quot;expanded&quot;: expanded,
+        }
+        response = yield self.sendRequestToServer(txn, delegator.server(), request)
+
+        if response[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue(set(response[&quot;value&quot;]))
+        elif response[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(response[&quot;class&quot;])(response[&quot;result&quot;])
+
+
+    @inlineCallbacks
+    def recv_get_delegates(self, txn, request):
+        &quot;&quot;&quot;
+        Process an delegates cross-pod request. Request arguments as per L{send_get_delegates}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        if request[&quot;action&quot;] != &quot;get-delegates&quot;:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_get_delegates&quot;.format(request[&quot;action&quot;]))
+
+        try:
+            delegator = yield txn.directoryService().recordWithUID(request[&quot;uid&quot;])
+            if delegator is None or not delegator.thisServer():
+                raise FailedCrossPodRequestError(&quot;Cross-pod delegate not on this server: {}&quot;.format(delegator.uid))
+
+            delegates = yield _delegatesOfUIDs(txn, delegator, request[&quot;read-write&quot;], request[&quot;expanded&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;request&quot;: str(e),
+            })
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+            &quot;value&quot;: list(delegates),
+        })
+
+
+    @inlineCallbacks
+    def send_get_delegators(self, txn, server, delegate, readWrite):
+        &quot;&quot;&quot;
+        Get delegators from another pod.
+
+        @param txn: transaction to use
+        @type txn: L{CommonStoreTransaction}
+        @param server: server to query
+        @type server: L{Server}
+        @param delegate: delegate to lookup
+        @type delegate: L{DirectoryRecord}
+        @param readWrite: if True, read and write access delegates are returned;
+            read-only access otherwise
+        &quot;&quot;&quot;
+        if not delegate.thisServer():
+            raise FailedCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(delegate.uid))
+
+        request = {
+            &quot;action&quot;: &quot;get-delegators&quot;,
+            &quot;uid&quot;: delegate.uid,
+            &quot;read-write&quot;: readWrite,
+        }
+        response = yield self.sendRequestToServer(txn, server, request)
+
+        if response[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue(set(response[&quot;value&quot;]))
+        elif response[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(response[&quot;class&quot;])(response[&quot;result&quot;])
+
+
+    @inlineCallbacks
+    def recv_get_delegators(self, txn, request):
+        &quot;&quot;&quot;
+        Process an delegators cross-pod request. Request arguments as per L{send_get_delegators}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        if request[&quot;action&quot;] != &quot;get-delegators&quot;:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_get_delegators&quot;.format(request[&quot;action&quot;]))
+
+        try:
+            delegate = yield txn.directoryService().recordWithUID(request[&quot;uid&quot;])
+            if delegate is None or delegate.thisServer():
+                raise FailedCrossPodRequestError(&quot;Cross-pod delegate missing or on this server: {}&quot;.format(delegate.uid))
+
+            delegateors = yield _delegatedToUIDs(txn, delegate, request[&quot;read-write&quot;], onlyThisServer=True)
+        except Exception as e:
+            returnValue({
+                &quot;result&quot;: &quot;exception&quot;,
+                &quot;class&quot;: &quot;.&quot;.join((e.__class__.__module__, e.__class__.__name__,)),
+                &quot;request&quot;: str(e),
+            })
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+            &quot;value&quot;: list(delegateors),
+        })
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingsharing_basepy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/podding/sharing_base.py (0 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/sharing_base.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/sharing_base.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -0,0 +1,94 @@
</span><ins>+##
+# Copyright (c) 2013-2014 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.common.datastore.podding.base import FailedCrossPodRequestError
+
+
+class SharingCommonPoddingConduit(object):
+    &quot;&quot;&quot;
+    Defines common cross-pod API for sharing that will be mixed into the L{PoddingConduit} class.
+    &quot;&quot;&quot;
+
+    #
+    # Sharer data access related apis
+    #
+
+    @inlineCallbacks
+    def _getRequestForResource(self, action, parent, child=None):
+        &quot;&quot;&quot;
+        Create a request for an operation on a L{CommonHomeChild}. This is used when building the JSON
+        request object prior to sending it.
+
+        @param shareeView: sharee resource being operated on.
+        @type shareeView: L{CommonHomeChildExternal}
+        &quot;&quot;&quot;
+
+        homeType = parent.ownerHome()._homeType
+        ownerUID = parent.ownerHome().uid()
+        ownerID = parent.external_id()
+        shareeUID = parent.viewerHome().uid()
+
+        _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
+
+        result = {
+            &quot;action&quot;: action,
+            &quot;type&quot;: homeType,
+            &quot;owner&quot;: ownerUID,
+            &quot;owner_id&quot;: ownerID,
+            &quot;sharee&quot;: shareeUID,
+        }
+        if child is not None:
+            result[&quot;resource_id&quot;] = child.id()
+        returnValue((result, recipient))
+
+
+    @inlineCallbacks
+    def _getResourcesForRequest(self, txn, request, expected_action):
+        &quot;&quot;&quot;
+        Find the resources associated with the request. This is used when a JSON request has been received
+        and the underlying store objects the request refers to need to be found.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        if request[&quot;action&quot;] != expected_action:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_{}&quot;.format(request[&quot;action&quot;], expected_action))
+
+        # Get a share
+        ownerHome = yield txn.homeWithUID(request[&quot;type&quot;], request[&quot;owner&quot;])
+        if ownerHome is None or ownerHome.external():
+            FailedCrossPodRequestError(&quot;Invalid owner UID specified&quot;)
+
+        shareeHome = yield txn.homeWithUID(request[&quot;type&quot;], request[&quot;sharee&quot;])
+        if shareeHome is None or not shareeHome.external():
+            FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
+
+        shareeView = yield shareeHome.childWithID(request[&quot;owner_id&quot;])
+        if shareeView is None:
+            FailedCrossPodRequestError(&quot;Invalid shared resource specified&quot;)
+
+        resourceID = request.get(&quot;resource_id&quot;, None)
+        if resourceID is not None:
+            objectResource = yield shareeView.objectResourceWithID(resourceID)
+            if objectResource is None:
+                FailedCrossPodRequestError(&quot;Invalid owner shared object resource specified&quot;)
+        else:
+            objectResource = None
+
+        returnValue((shareeView, objectResource,))
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingsharing_invitespy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py (0 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/sharing_invites.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -0,0 +1,246 @@
</span><ins>+##
+# Copyright (c) 2013-2014 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.common.icommondatastore import ExternalShareFailed
+from txdav.common.datastore.podding.base import FailedCrossPodRequestError
+from txdav.common.datastore.podding.sharing_base import SharingCommonPoddingConduit
+
+
+class SharingInvitesPoddingConduitMixin(SharingCommonPoddingConduit):
+    &quot;&quot;&quot;
+    Defines the cross-pod API for sharing invites that will be mixed into the
+    L{PoddingConduit} class.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def send_shareinvite(self, txn, homeType, ownerUID, ownerID, ownerName, shareeUID, shareUID, bindMode, summary, copy_properties, supported_components):
+        &quot;&quot;&quot;
+        Send a sharing invite cross-pod message.
+
+        @param homeType: Type of home being shared.
+        @type homeType: C{int}
+        @param ownerUID: UID of the sharer.
+        @type ownerUID: C{str}
+        @param ownerID: resource ID of the sharer calendar
+        @type ownerID: C{int}
+        @param ownerName: owner's name of the sharer calendar
+        @type ownerName: C{str}
+        @param shareeUID: UID of the sharee
+        @type shareeUID: C{str}
+        @param shareUID: Resource/invite ID for sharee
+        @type shareUID: C{str}
+        @param bindMode: bind mode for the share
+        @type bindMode: C{str}
+        @param summary: sharing message
+        @type summary: C{str}
+        @param copy_properties: C{str} name/value for properties to be copied
+        @type copy_properties: C{dict}
+        @param supported_components: supproted components, may be C{None}
+        @type supported_components: C{str}
+        &quot;&quot;&quot;
+
+        _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
+
+        request = {
+            &quot;action&quot;: &quot;shareinvite&quot;,
+            &quot;type&quot;: homeType,
+            &quot;owner&quot;: ownerUID,
+            &quot;owner_id&quot;: ownerID,
+            &quot;owner_name&quot;: ownerName,
+            &quot;sharee&quot;: shareeUID,
+            &quot;share_id&quot;: shareUID,
+            &quot;mode&quot;: bindMode,
+            &quot;summary&quot;: summary,
+            &quot;properties&quot;: copy_properties,
+        }
+        if supported_components is not None:
+            request[&quot;supported-components&quot;] = supported_components
+
+        result = yield self.sendRequest(txn, recipient, request)
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def recv_shareinvite(self, txn, request):
+        &quot;&quot;&quot;
+        Process a sharing invite cross-pod request. Request arguments as per L{send_shareinvite}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        if request[&quot;action&quot;] != &quot;shareinvite&quot;:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_shareinvite&quot;.format(request[&quot;action&quot;]))
+
+        # Sharee home on this pod must exist (create if needed)
+        shareeHome = yield txn.homeWithUID(request[&quot;type&quot;], request[&quot;sharee&quot;], create=True)
+        if shareeHome is None or shareeHome.external():
+            raise FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
+
+        # Create a share
+        try:
+            yield shareeHome.processExternalInvite(
+                request[&quot;owner&quot;],
+                request[&quot;owner_id&quot;],
+                request[&quot;owner_name&quot;],
+                request[&quot;share_id&quot;],
+                request[&quot;mode&quot;],
+                request[&quot;summary&quot;],
+                request[&quot;properties&quot;],
+                supported_components=request.get(&quot;supported-components&quot;)
+            )
+        except ExternalShareFailed as e:
+            raise FailedCrossPodRequestError(str(e))
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+        })
+
+
+    @inlineCallbacks
+    def send_shareuninvite(self, txn, homeType, ownerUID, ownerID, shareeUID, shareUID):
+        &quot;&quot;&quot;
+        Send a sharing uninvite cross-pod message.
+
+        @param homeType: Type of home being shared.
+        @type homeType: C{int}
+        @param ownerUID: UID of the sharer.
+        @type ownerUID: C{str}
+        @param ownerID: resource ID of the sharer calendar
+        @type ownerID: C{int}
+        @param shareeUID: UID of the sharee
+        @type shareeUID: C{str}
+        @param shareUID: Resource/invite ID for sharee
+        @type shareUID: C{str}
+        &quot;&quot;&quot;
+
+        _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
+
+        request = {
+            &quot;action&quot;: &quot;shareuninvite&quot;,
+            &quot;type&quot;: homeType,
+            &quot;owner&quot;: ownerUID,
+            &quot;owner_id&quot;: ownerID,
+            &quot;sharee&quot;: shareeUID,
+            &quot;share_id&quot;: shareUID,
+        }
+
+        result = yield self.sendRequest(txn, recipient, request)
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def recv_shareuninvite(self, txn, request):
+        &quot;&quot;&quot;
+        Process a sharing uninvite cross-pod request. Request arguments as per L{send_shareuninvite}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        if request[&quot;action&quot;] != &quot;shareuninvite&quot;:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_shareuninvite&quot;.format(request[&quot;action&quot;]))
+
+        # Sharee home on this pod must already exist
+        shareeHome = yield txn.homeWithUID(request[&quot;type&quot;], request[&quot;sharee&quot;])
+        if shareeHome is None or shareeHome.external():
+            FailedCrossPodRequestError(&quot;Invalid sharee UID specified&quot;)
+
+        # Remove a share
+        try:
+            yield shareeHome.processExternalUninvite(
+                request[&quot;owner&quot;],
+                request[&quot;owner_id&quot;],
+                request[&quot;share_id&quot;],
+            )
+        except ExternalShareFailed as e:
+            FailedCrossPodRequestError(str(e))
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+        })
+
+
+    @inlineCallbacks
+    def send_sharereply(self, txn, homeType, ownerUID, shareeUID, shareUID, bindStatus, summary=None):
+        &quot;&quot;&quot;
+        Send a sharing reply cross-pod message.
+
+        @param homeType: Type of home being shared.
+        @type homeType: C{int}
+        @param ownerUID: UID of the sharer.
+        @type ownerUID: C{str}
+        @param shareeUID: UID of the recipient
+        @type shareeUID: C{str}
+        @param shareUID: Resource/invite ID for recipient
+        @type shareUID: C{str}
+        @param bindStatus: bind mode for the share
+        @type bindStatus: C{str}
+        @param summary: sharing message
+        @type summary: C{str}
+        &quot;&quot;&quot;
+
+        _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
+
+        request = {
+            &quot;action&quot;: &quot;sharereply&quot;,
+            &quot;type&quot;: homeType,
+            &quot;owner&quot;: ownerUID,
+            &quot;sharee&quot;: shareeUID,
+            &quot;share_id&quot;: shareUID,
+            &quot;status&quot;: bindStatus,
+        }
+        if summary is not None:
+            request[&quot;summary&quot;] = summary
+
+        result = yield self.sendRequest(txn, recipient, request)
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def recv_sharereply(self, txn, request):
+        &quot;&quot;&quot;
+        Process a sharing reply cross-pod request. Request arguments as per L{send_sharereply}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        if request[&quot;action&quot;] != &quot;sharereply&quot;:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_sharereply&quot;.format(request[&quot;action&quot;]))
+
+        # Sharer home on this pod must already exist
+        ownerHome = yield txn.homeWithUID(request[&quot;type&quot;], request[&quot;owner&quot;])
+        if ownerHome is None or ownerHome.external():
+            FailedCrossPodRequestError(&quot;Invalid owner UID specified&quot;)
+
+        # Process a reply
+        try:
+            yield ownerHome.processExternalReply(
+                request[&quot;owner&quot;],
+                request[&quot;sharee&quot;],
+                request[&quot;share_id&quot;],
+                request[&quot;status&quot;],
+                summary=request.get(&quot;summary&quot;)
+            )
+        except ExternalShareFailed as e:
+            FailedCrossPodRequestError(str(e))
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+        })
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingsharing_storepy"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/podding/sharing_store.py (0 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/sharing_store.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/sharing_store.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -0,0 +1,268 @@
</span><ins>+##
+# Copyright (c) 2013-2014 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 twisted.python.reflect import namedClass
+
+from txdav.common.datastore.podding.sharing_base import SharingCommonPoddingConduit
+from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
+
+from twistedcaldav.caldavxml import TimeRange
+
+
+class SharingStorePoddingConduitMixin(SharingCommonPoddingConduit):
+    &quot;&quot;&quot;
+    Defines the cross-pod API for access to shared resource data that will be mixed into the
+    L{PoddingConduit} class.
+    &quot;&quot;&quot;
+
+    #
+    # 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, objectResource=None, transform=None, 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 objectResource: the resource being operated on, or C{None} for classmethod.
+        @type objectResource: L{CommonObjectResourceExternal}
+        @param transform: a function used to convert the JSON response into return values.
+        @type transform: C{callable}
+        @param args: list of optional arguments.
+        @type args: C{list}
+        @param kwargs: optional keyword arguments.
+        @type kwargs: C{dict}
+        &quot;&quot;&quot;
+
+        request, recipient = yield self._getRequestForResource(actionName, shareeView, objectResource)
+        if args is not None:
+            request[&quot;arguments&quot;] = args
+        if kwargs is not None:
+            request[&quot;keywords&quot;] = kwargs
+        response = yield self.sendRequest(shareeView._txn, recipient, request)
+        if response[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue(response[&quot;value&quot;] if transform is None else transform(response[&quot;value&quot;], shareeView, objectResource))
+        elif response[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(response[&quot;class&quot;])(response[&quot;result&quot;])
+
+
+    @inlineCallbacks
+    def _simple_recv(self, txn, actionName, request, method, onHomeChild=True, transform=None):
+        &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 request: request arguments
+        @type request: C{dict}
+        @param method: name of the method to execute on the shared resource to get the result.
+        @type method: C{str}
+        @param transform: method to call on returned JSON value to convert it to something useful.
+        @type transform: C{callable}
+        &quot;&quot;&quot;
+
+        shareeView, objectResource = yield self._getResourcesForRequest(txn, request, actionName)
+        try:
+            if onHomeChild:
+                # Operate on the L{CommonHomeChild}
+                value = yield getattr(shareeView, method)(*request.get(&quot;arguments&quot;, ()), **request.get(&quot;keywords&quot;, {}))
+            else:
+                # Operate on the L{CommonObjectResource}
+                if objectResource is not None:
+                    value = yield getattr(objectResource, method)(*request.get(&quot;arguments&quot;, ()), **request.get(&quot;keywords&quot;, {}))
+                else:
+                    # classmethod call
+                    value = yield getattr(shareeView._objectResourceClass, method)(shareeView, *request.get(&quot;arguments&quot;, ()), **request.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;request&quot;: str(e),
+            })
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+            &quot;value&quot;: transform(value, shareeView, objectResource) if transform is not None else value,
+        })
+
+
+    @inlineCallbacks
+    def send_freebusy(
+        self,
+        calresource,
+        timerange,
+        matchtotal,
+        excludeuid,
+        organizer,
+        organizerPrincipal,
+        same_calendar_user,
+        servertoserver,
+        event_details,
+    ):
+        &quot;&quot;&quot;
+        Request free busy information for a shared calendar collection hosted on a different pod. See
+        L{txdav.caldav.datastore.scheduling.freebusy} for the base free busy lookup behavior.
+        &quot;&quot;&quot;
+        action, recipient = yield self._getRequestForResource(&quot;freebusy&quot;, calresource)
+        action[&quot;timerange&quot;] = [timerange.start.getText(), timerange.end.getText()]
+        action[&quot;matchtotal&quot;] = matchtotal
+        action[&quot;excludeuid&quot;] = excludeuid
+        action[&quot;organizer&quot;] = organizer
+        action[&quot;organizerPrincipal&quot;] = organizerPrincipal
+        action[&quot;same_calendar_user&quot;] = same_calendar_user
+        action[&quot;servertoserver&quot;] = servertoserver
+        action[&quot;event_details&quot;] = event_details
+
+        response = yield self.sendRequest(calresource._txn, recipient, action)
+
+        if response[&quot;result&quot;] == &quot;ok&quot;:
+            returnValue((response[&quot;fbresults&quot;], response[&quot;matchtotal&quot;],))
+        elif response[&quot;result&quot;] == &quot;exception&quot;:
+            raise namedClass(response[&quot;class&quot;])(response[&quot;result&quot;])
+
+
+    @inlineCallbacks
+    def recv_freebusy(self, txn, request):
+        &quot;&quot;&quot;
+        Process a freebusy cross-pod request. Message arguments as per L{send_freebusy}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        &quot;&quot;&quot;
+
+        shareeView, _ignore_objectResource = yield self._getResourcesForRequest(txn, request, &quot;freebusy&quot;)
+        try:
+            # Operate on the L{CommonHomeChild}
+            fbinfo = [[], [], []]
+            matchtotal = yield generateFreeBusyInfo(
+                shareeView,
+                fbinfo,
+                TimeRange(start=request[&quot;timerange&quot;][0], end=request[&quot;timerange&quot;][1]),
+                request[&quot;matchtotal&quot;],
+                request[&quot;excludeuid&quot;],
+                request[&quot;organizer&quot;],
+                request[&quot;organizerPrincipal&quot;],
+                request[&quot;same_calendar_user&quot;],
+                request[&quot;servertoserver&quot;],
+                request[&quot;event_details&quot;],
+                logItems=None
+            )
+        except Exception as e:
+            returnValue({
+                &quot;result&quot;: &quot;exception&quot;,
+                &quot;class&quot;: &quot;.&quot;.join((e.__class__.__module__, e.__class__.__name__,)),
+                &quot;request&quot;: str(e),
+            })
+
+        # Convert L{DateTime} objects to text for JSON response
+        for i in range(3):
+            for j in range(len(fbinfo[i])):
+                fbinfo[i][j] = fbinfo[i][j].getText()
+
+        returnValue({
+            &quot;result&quot;: &quot;ok&quot;,
+            &quot;fbresults&quot;: fbinfo,
+            &quot;matchtotal&quot;: matchtotal,
+        })
+
+
+    #
+    # Methods used to transform arguments to or results from a JSON request or response.
+    #
+    @staticmethod
+    def _to_tuple(value, shareeView, objectResource):
+        return tuple(value)
+
+
+    @staticmethod
+    def _to_string(value, shareeView, objectResource):
+        return str(value)
+
+
+    @staticmethod
+    def _to_externalize(value, shareeView, objectResource):
+        &quot;&quot;&quot;
+        Convert the value to the external (JSON-based) representation.
+        &quot;&quot;&quot;
+        if isinstance(value, shareeView._objectResourceClass):
+            value = value.externalize()
+        elif value is not None:
+            value = [v.externalize() for v in value]
+        return value
+
+
+    #
+    # Factory methods for binding actions to the conduit class
+    #
+    @classmethod
+    def _make_simple_homechild_action(cls, action, method, transform_recv=None, transform_send=None):
+        setattr(
+            cls,
+            &quot;send_{}&quot;.format(action),
+            lambda self, shareeView, *args, **kwargs:
+                self._simple_send(action, shareeView, transform=transform_send, args=args, kwargs=kwargs)
+        )
+        setattr(
+            cls,
+            &quot;recv_{}&quot;.format(action),
+            lambda self, txn, message:
+                self._simple_recv(txn, action, message, method, transform=transform_recv)
+        )
+
+
+    @classmethod
+    def _make_simple_object_action(cls, action, method, transform_recv=None, transform_send=None):
+        setattr(
+            cls,
+            &quot;send_{}&quot;.format(action),
+            lambda self, shareeView, objectResource, *args, **kwargs:
+                self._simple_send(action, shareeView, objectResource, transform=transform_send, 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_recv)
+        )
+
+# These are the actions on store objects we need to expose via the conduit api
+
+# Calls on L{CommonHomeChild} objects
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;countobjects&quot;, &quot;countObjectResources&quot;)
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;listobjects&quot;, &quot;listObjectResources&quot;)
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;resourceuidforname&quot;, &quot;resourceUIDForName&quot;)
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;resourcenameforuid&quot;, &quot;resourceNameForUID&quot;)
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;movehere&quot;, &quot;moveObjectResourceHere&quot;)
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;moveaway&quot;, &quot;moveObjectResourceAway&quot;)
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;synctoken&quot;, &quot;syncToken&quot;)
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;resourcenamessincerevision&quot;, &quot;resourceNamesSinceRevision&quot;, transform_send=SharingStorePoddingConduitMixin._to_tuple)
+SharingStorePoddingConduitMixin._make_simple_homechild_action(&quot;search&quot;, &quot;search&quot;)
+
+# Calls on L{CommonObjectResource} objects
+SharingStorePoddingConduitMixin._make_simple_object_action(&quot;loadallobjects&quot;, &quot;loadAllObjects&quot;, transform_recv=SharingStorePoddingConduitMixin._to_externalize)
+SharingStorePoddingConduitMixin._make_simple_object_action(&quot;loadallobjectswithnames&quot;, &quot;loadAllObjectsWithNames&quot;, transform_recv=SharingStorePoddingConduitMixin._to_externalize)
+SharingStorePoddingConduitMixin._make_simple_object_action(&quot;objectwith&quot;, &quot;objectWith&quot;, transform_recv=SharingStorePoddingConduitMixin._to_externalize)
+SharingStorePoddingConduitMixin._make_simple_object_action(&quot;create&quot;, &quot;create&quot;, transform_recv=SharingStorePoddingConduitMixin._to_externalize)
+SharingStorePoddingConduitMixin._make_simple_object_action(&quot;setcomponent&quot;, &quot;setComponent&quot;)
+SharingStorePoddingConduitMixin._make_simple_object_action(&quot;component&quot;, &quot;component&quot;, transform_recv=SharingStorePoddingConduitMixin._to_string)
+SharingStorePoddingConduitMixin._make_simple_object_action(&quot;remove&quot;, &quot;remove&quot;)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavwhocachepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/cache.py (14083 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/cache.py        2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/who/cache.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -82,6 +82,7 @@
</span><span class="cx">     def __init__(self, directory, expireSeconds=30):
</span><span class="cx">         BaseDirectoryService.__init__(self, directory.realmName)
</span><span class="cx">         self._directory = directory
</span><ins>+        self.serversDB = directory.serversDB
</ins><span class="cx">         self._directoryTiming = hasattr(self._directory, &quot;_addTiming&quot;)
</span><span class="cx">         self._expireSeconds = expireSeconds
</span><span class="cx">         self.resetCache()
</span></span></pre></div>
<a id="CalendarServertrunktxdavwhodelegatespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/who/delegates.py (14083 => 14084)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/who/delegates.py        2014-10-16 16:09:16 UTC (rev 14083)
+++ CalendarServer/trunk/txdav/who/delegates.py        2014-10-16 16:12:30 UTC (rev 14084)
</span><span class="lines">@@ -20,8 +20,11 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from twisted.python.constants import Names, NamedConstant
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue, succeed, \
+    DeferredList
</ins><span class="cx"> 
</span><ins>+from twistedcaldav.config import config
+
</ins><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twext.who.idirectory import (
</span><span class="cx">     RecordType as BaseRecordType, FieldName, NotAllowedError
</span><span class="lines">@@ -67,6 +70,7 @@
</span><span class="cx">         this record.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         parentUID, _ignore_proxyType = self.uid.split(u&quot;#&quot;)
</span><ins>+        parentRecord = yield self.service._masterDirectory.recordWithUID(parentUID)
</ins><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def _members(txn):
</span><span class="lines">@@ -74,15 +78,11 @@
</span><span class="cx">                 RecordType.readDelegateGroup, RecordType.writeDelegateGroup
</span><span class="cx">             ):  # Members are delegates of this record
</span><span class="cx">                 readWrite = (self.recordType is RecordType.writeDelegateGroup)
</span><del>-                delegateUIDs = (
-                    yield txn.delegates(parentUID, readWrite, expanded=expanded)
-                )
</del><ins>+                delegateUIDs = yield _delegatesOfUIDs(txn, parentRecord, readWrite, expanded=expanded)
</ins><span class="cx"> 
</span><span class="cx">             else:  # Members have delegated to this record
</span><span class="cx">                 readWrite = (self.recordType is RecordType.writeDelegatorGroup)
</span><del>-                delegateUIDs = (
-                    yield txn.delegators(parentUID, readWrite)
-                )
</del><ins>+                delegateUIDs = yield _delegatedToUIDs(txn, parentRecord, readWrite)
</ins><span class="cx">             returnValue(delegateUIDs)
</span><span class="cx"> 
</span><span class="cx">         delegateUIDs = yield self.service._store.inTransaction(
</span><span class="lines">@@ -121,18 +121,13 @@
</span><span class="cx">             m=[r.uid for r in memberRecords]
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        @inlineCallbacks
</del><ins>+        delegator = (
+            yield self.service._masterDirectory.recordWithUID(parentUID)
+        )
+
</ins><span class="cx">         def _setMembers(txn):
</span><del>-            yield txn.removeDelegates(parentUID, readWrite)
-            yield txn.removeDelegateGroups(parentUID, readWrite)
</del><ins>+            return setDelegates(txn, delegator, memberRecords, readWrite)
</ins><span class="cx"> 
</span><del>-            delegator = (
-                yield self.service._masterDirectory.recordWithUID(parentUID)
-            )
-
-            for delegate in memberRecords:
-                yield addDelegate(txn, delegator, delegate, readWrite)
-
</del><span class="cx">         yield self.service._store.inTransaction(
</span><span class="cx">             &quot;DirectoryRecord.setMembers&quot;, _setMembers
</span><span class="cx">         )
</span><span class="lines">@@ -229,6 +224,32 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><ins>+def setDelegates(txn, delegator, delegates, readWrite):
+    &quot;&quot;&quot;
+    Sets the full set of delegates for a delegator.
+
+    We need to take multiple pods into account by re-directing this request
+    to the cross-pod conduit if the delegator is not local to this pod.
+
+    @param delegator: the delegator's directory record
+    @type delegator: L{IDirectoryRecord}
+    @param delegates: the delegates directory records
+    @type delegates: L{list}} of L{IDirectoryRecord}
+    @param readWrite: if True, read and write access is granted; read-only
+        access otherwise
+    &quot;&quot;&quot;
+    if delegator.thisServer():
+        yield txn.removeDelegates(delegator.uid, readWrite)
+        yield txn.removeDelegateGroups(delegator.uid, readWrite)
+
+        for delegate in delegates:
+            yield addDelegate(txn, delegator, delegate, readWrite)
+    else:
+        yield _podSetDelegates(txn, delegator, delegates, readWrite)
+
+
+
+@inlineCallbacks
</ins><span class="cx"> def addDelegate(txn, delegator, delegate, readWrite):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Adds &quot;delegate&quot; as a delegate of &quot;delegator&quot;.  The type of access is
</span><span class="lines">@@ -295,11 +316,10 @@
</span><span class="cx">     @return: the set of directory records
</span><span class="cx">     @rtype: a Deferred which fires a set of L{IDirectoryRecord}
</span><span class="cx">     &quot;&quot;&quot;
</span><ins>+    delegateUIDs = yield _delegatesOfUIDs(txn, delegator, readWrite, expanded)
+
</ins><span class="cx">     records = []
</span><span class="cx">     directory = delegator.service
</span><del>-    delegateUIDs = (
-        yield txn.delegates(delegator.uid, readWrite, expanded=expanded)
-    )
</del><span class="cx">     for uid in delegateUIDs:
</span><span class="cx">         if uid != delegator.uid:
</span><span class="cx">             record = (yield directory.recordWithUID(uid))
</span><span class="lines">@@ -322,12 +342,126 @@
</span><span class="cx">     @return: the set of directory records
</span><span class="cx">     @rtype: a Deferred which fires a set of L{IDirectoryRecord}
</span><span class="cx">     &quot;&quot;&quot;
</span><ins>+    delegatorUIDs = yield _delegatedToUIDs(txn, delegate, readWrite)
+
</ins><span class="cx">     records = []
</span><span class="cx">     directory = delegate.service
</span><del>-    delegatorUIDs = (yield txn.delegators(delegate.uid, readWrite))
</del><span class="cx">     for uid in delegatorUIDs:
</span><span class="cx">         if uid != delegate.uid:
</span><span class="cx">             record = (yield directory.recordWithUID(uid))
</span><span class="cx">             if record is not None:
</span><span class="cx">                 records.append(record)
</span><span class="cx">     returnValue(records)
</span><ins>+
+
+
+@inlineCallbacks
+def _delegatesOfUIDs(txn, delegator, readWrite, expanded=False):
+    &quot;&quot;&quot;
+    Return the UIDs of the delegates of &quot;delegator&quot;.  The type of access
+    is specified by the &quot;readWrite&quot; parameter.
+
+    We need to take multiple pods into account by re-directing this request
+    to the cross-pod conduit if the delegator is not local to this pod.
+
+    @param delegator: the delegator's directory record
+    @type delegator: L{IDirectoryRecord}
+    @param readWrite: if True, read and write access delegates are returned;
+        read-only access otherwise
+    @return: the set of directory record uids
+    @rtype: a Deferred which fires a set of L{str}
+    &quot;&quot;&quot;
+
+    if delegator.thisServer():
+        delegateUIDs = yield txn.delegates(delegator.uid, readWrite, expanded=expanded)
+    else:
+        delegateUIDs = yield _podDelegates(txn, delegator, readWrite, expanded=expanded)
+    returnValue(delegateUIDs)
+
+
+
+@inlineCallbacks
+def _delegatedToUIDs(txn, delegate, readWrite, onlyThisServer=False):
+    &quot;&quot;&quot;
+    Return the UIDs of those who have delegated to &quot;delegate&quot;.  The type of
+    access is specified by the &quot;readWrite&quot; parameter.
+
+    We need to take multiple pods into account by re-directing this request
+    to the cross-pod conduit if the delegate is not local to this pod.
+
+    @param delegate: the delegate's directory record
+    @type delegate: L{IDirectoryRecord}
+    @param readWrite: if True, read and write access delegators are returned;
+        read-only access otherwise
+    @param onlyThisServer: used when doing the query as part of a cross-pod request since that
+        should only returns results for this server
+    @type onlyThisServer: L{bool}
+    @return: the set of directory record uids
+    @rtype: a Deferred which fires a set of L{str}
+    &quot;&quot;&quot;
+
+
+    delegatorUIDs = (yield txn.delegators(delegate.uid, readWrite))
+    if not onlyThisServer and config.Servers.Enabled:
+        delegatorUIDs.update((yield _podDelegators(txn, delegate, readWrite)))
+    returnValue(delegatorUIDs)
+
+
+
+def _podSetDelegates(txn, delegator, delegates, readWrite):
+    &quot;&quot;&quot;
+    Sets the full set of delegates for a delegator.
+
+    We need to take multiple pods into account by re-directing this request
+    to the cross-pod conduit if the delegator is not local to this pod.
+
+    @param delegator: the delegator's directory record
+    @type delegator: L{IDirectoryRecord}
+    @param delegates: the delegates directory records
+    @type delegates: L{list}} of L{IDirectoryRecord}
+    @param readWrite: if True, read and write access is granted; read-only
+        access otherwise
+    &quot;&quot;&quot;
+    return txn.store().conduit.send_set_delegates(txn, delegator, delegates, readWrite)
+
+
+
+def _podDelegates(txn, delegator, readWrite, expanded=False):
+    &quot;&quot;&quot;
+    Do a cross-pod request to get the delegates for this delegator.
+
+    @param delegator: the delegator's directory record
+    @type delegator: L{IDirectoryRecord}
+    @param readWrite: if True, read and write access delegates are returned;
+        read-only access otherwise
+    @return: the set of directory record uids
+    @rtype: a Deferred which fires a set of L{str}
+    &quot;&quot;&quot;
+
+    return txn.store().conduit.send_get_delegates(txn, delegator, readWrite, expanded)
+
+
+
+@inlineCallbacks
+def _podDelegators(txn, delegate, readWrite):
+    &quot;&quot;&quot;
+    Do a cross-pod request to get the delegators for this delegate. We need to iterate over all
+    other pod servers to get results from each one.
+
+    @param delegate: the delegate's directory record
+    @type delegate: L{IDirectoryRecord}
+    @param readWrite: if True, read and write access delegates are returned;
+        read-only access otherwise
+    @return: the set of directory record uids
+    @rtype: a Deferred which fires a set of L{str}
+    &quot;&quot;&quot;
+
+    results = yield DeferredList([
+        txn.store().conduit.send_get_delegators(txn, server, delegate, readWrite) for
+        server in txn.directoryService().serversDB.allServersExceptThis()
+    ], consumeErrors=True)
+    delegators = set()
+    for result in results:
+        if result and result[0]:
+            delegators.update(result[1])
+    returnValue(delegators)
</ins></span></pre>
</div>
</div>

</body>
</html>