<!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>[12211] 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/12211">12211</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2014-01-02 09:19:18 -0800 (Thu, 02 Jan 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Merge cross-pod sharing work to trunk.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcalendarserverpushtesttest_notifierpy">CalendarServer/trunk/calendarserver/push/test/test_notifier.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertapcaldavpy">CalendarServer/trunk/calendarserver/tap/caldav.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertaputilpy">CalendarServer/trunk/calendarserver/tap/util.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolsdbinspectpy">CalendarServer/trunk/calendarserver/tools/dbinspect.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolspurgepy">CalendarServer/trunk/calendarserver/tools/purge.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolsshelltesttest_vfspy">CalendarServer/trunk/calendarserver/tools/shell/test/test_vfs.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstestcalverifyaccountsxml">CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstestpurgeaccountsxml">CalendarServer/trunk/calendarserver/tools/test/purge/accounts.xml</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstesttest_exportpy">CalendarServer/trunk/calendarserver/tools/test/test_export.py</a></li>
<li><a href="#CalendarServertrunkcalendarservertoolstesttest_purgepy">CalendarServer/trunk/calendarserver/tools/test/test_purge.py</a></li>
<li><a href="#CalendarServertrunkconfcaldavdtestpodBplist">CalendarServer/trunk/conf/caldavd-test-podB.plist</a></li>
<li><a href="#CalendarServertrunksupportbuildsh">CalendarServer/trunk/support/build.sh</a></li>
<li><a href="#CalendarServertrunktwistedpluginscaldavpy">CalendarServer/trunk/twisted/plugins/caldav.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectoryopendirectorybackerpy">CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectoryprincipalpy">CalendarServer/trunk/twistedcaldav/directory/principal.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectoryresourcepy">CalendarServer/trunk/twistedcaldav/directory/resource.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectorytestaccountsxml">CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectorytestaugmentsxml">CalendarServer/trunk/twistedcaldav/directory/test/augments.xml</a></li>
<li><a href="#CalendarServertrunktwistedcaldavdirectorytesttest_directorypy">CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavmethodreport_addressbook_querypy">CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavmethodreport_calendar_querypy">CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavmethodreport_commonpy">CalendarServer/trunk/twistedcaldav/method/report_common.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavmethodreport_multiget_commonpy">CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavresourcepy">CalendarServer/trunk/twistedcaldav/resource.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavsharingpy">CalendarServer/trunk/twistedcaldav/sharing.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstdconfigpy">CalendarServer/trunk/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavstorebridgepy">CalendarServer/trunk/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavtesttest_calendarquerypy">CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavtesttest_sharingpy">CalendarServer/trunk/twistedcaldav/test/test_sharing.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavtesttest_xmlpy">CalendarServer/trunk/twistedcaldav/test/test_xml.py</a></li>
<li><a href="#CalendarServertrunktxdavbasedatastoresubpostgrespy">CalendarServer/trunk/txdav/base/datastore/subpostgres.py</a></li>
<li><a href="#CalendarServertrunktxdavbasedatastoreutilpy">CalendarServer/trunk/txdav/base/datastore/util.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreindex_filepy">CalendarServer/trunk/txdav/caldav/datastore/index_file.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingfreebusypy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/freebusy.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingischedulelocalserverspy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingischedulexmlpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/xml.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoresqlpy">CalendarServer/trunk/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretestcommonpy">CalendarServer/trunk/txdav/caldav/datastore/test/common.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_index_filepy">CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_sqlpy">CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_sql_sharingpy">CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_utilpy">CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretestutilpy">CalendarServer/trunk/txdav/caldav/datastore/test/util.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavicalendardirectoryservicepy">CalendarServer/trunk/txdav/caldav/icalendardirectoryservice.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavicalendarstorepy">CalendarServer/trunk/txdav/caldav/icalendarstore.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoreindex_filepy">CalendarServer/trunk/txdav/carddav/datastore/index_file.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoresqlpy">CalendarServer/trunk/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoretesttest_index_filepy">CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoretesttest_sqlpy">CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoretesttest_sql_sharingpy">CalendarServer/trunk/txdav/carddav/datastore/test/test_sql_sharing.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorefilepy">CalendarServer/trunk/txdav/common/datastore/file.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresqlpy">CalendarServer/trunk/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_schemacurrentoracledialectsql">CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_schemacurrentsql">CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_tablespy">CalendarServer/trunk/txdav/common/datastore/sql_tables.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoretesttest_sqlpy">CalendarServer/trunk/txdav/common/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoretestutilpy">CalendarServer/trunk/txdav/common/datastore/test/util.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoreupgradesqlupgradesutilpy">CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/util.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoreupgradetesttest_migratepy">CalendarServer/trunk/txdav/common/datastore/upgrade/test/test_migrate.py</a></li>
<li><a href="#CalendarServertrunktxdavcommonicommondatastorepy">CalendarServer/trunk/txdav/common/icommondatastore.py</a></li>
<li><a href="#CalendarServertrunktxdavcommonidirectoryservicepy">CalendarServer/trunk/txdav/common/idirectoryservice.py</a></li>
<li><a href="#CalendarServertrunktxweb2httppy">CalendarServer/trunk/txweb2/http.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>CalendarServer/trunk/txdav/caldav/datastore/query/</li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequery__init__py">CalendarServer/trunk/txdav/caldav/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequerybuilderpy">CalendarServer/trunk/txdav/caldav/datastore/query/builder.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequeryfilterpy">CalendarServer/trunk/txdav/caldav/datastore/query/filter.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequerygeneratorpy">CalendarServer/trunk/txdav/caldav/datastore/query/generator.py</a></li>
<li>CalendarServer/trunk/txdav/caldav/datastore/query/test/</li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequerytest__init__py">CalendarServer/trunk/txdav/caldav/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequerytesttest_filterpy">CalendarServer/trunk/txdav/caldav/datastore/query/test/test_filter.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoresql_externalpy">CalendarServer/trunk/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoretesttest_sql_externalpy">CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_external.py</a></li>
<li>CalendarServer/trunk/txdav/carddav/datastore/query/</li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequery__init__py">CalendarServer/trunk/txdav/carddav/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequerybuilderpy">CalendarServer/trunk/txdav/carddav/datastore/query/builder.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequeryfilterpy">CalendarServer/trunk/txdav/carddav/datastore/query/filter.py</a></li>
<li>CalendarServer/trunk/txdav/carddav/datastore/query/test/</li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequerytest__init__py">CalendarServer/trunk/txdav/carddav/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequerytesttest_filterpy">CalendarServer/trunk/txdav/carddav/datastore/query/test/test_filter.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastoresql_externalpy">CalendarServer/trunk/txdav/carddav/datastore/sql_external.py</a></li>
<li>CalendarServer/trunk/txdav/common/datastore/podding/</li>
<li><a href="#CalendarServertrunktxdavcommondatastorepodding__init__py">CalendarServer/trunk/txdav/common/datastore/podding/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingconduitpy">CalendarServer/trunk/txdav/common/datastore/podding/conduit.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingrequestpy">CalendarServer/trunk/txdav/common/datastore/podding/request.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingresourcepy">CalendarServer/trunk/txdav/common/datastore/podding/resource.py</a></li>
<li>CalendarServer/trunk/txdav/common/datastore/podding/test/</li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtest__init__py">CalendarServer/trunk/txdav/common/datastore/podding/test/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtesttest_conduitpy">CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtesttest_external_homepy">CalendarServer/trunk/txdav/common/datastore/podding/test/test_external_home.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtesttest_resourcepy">CalendarServer/trunk/txdav/common/datastore/podding/test/test_resource.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtestutilpy">CalendarServer/trunk/txdav/common/datastore/podding/test/util.py</a></li>
<li>CalendarServer/trunk/txdav/common/datastore/query/</li>
<li><a href="#CalendarServertrunktxdavcommondatastorequery__init__py">CalendarServer/trunk/txdav/common/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequeryexpressionpy">CalendarServer/trunk/txdav/common/datastore/query/expression.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequeryfilegeneratorpy">CalendarServer/trunk/txdav/common/datastore/query/filegenerator.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequerygeneratorpy">CalendarServer/trunk/txdav/common/datastore/query/generator.py</a></li>
<li>CalendarServer/trunk/txdav/common/datastore/query/test/</li>
<li><a href="#CalendarServertrunktxdavcommondatastorequerytest__init__py">CalendarServer/trunk/txdav/common/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequerytesttest_expressionpy">CalendarServer/trunk/txdav/common/datastore/query/test/test_expression.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequerytesttest_generatorpy">CalendarServer/trunk/txdav/common/datastore/query/test/test_generator.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_externalpy">CalendarServer/trunk/txdav/common/datastore/sql_external.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_schemaoldoracledialectv30sql">CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v30.sql</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_schemaoldpostgresdialectv30sql">CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v30.sql</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_30_to_31sql">CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_30_to_31.sql</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_30_to_31sql">CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_30_to_31.sql</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li>CalendarServer/trunk/twistedcaldav/query/</li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequery__init__py">CalendarServer/trunk/txdav/caldav/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequerybuilderpy">CalendarServer/trunk/txdav/caldav/datastore/query/builder.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequeryfilterpy">CalendarServer/trunk/txdav/caldav/datastore/query/filter.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequerygeneratorpy">CalendarServer/trunk/txdav/caldav/datastore/query/generator.py</a></li>
<li>CalendarServer/trunk/txdav/caldav/datastore/query/test/</li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequerytest__init__py">CalendarServer/trunk/txdav/caldav/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastorequerytesttest_filterpy">CalendarServer/trunk/txdav/caldav/datastore/query/test/test_filter.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequery__init__py">CalendarServer/trunk/txdav/carddav/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequerybuilderpy">CalendarServer/trunk/txdav/carddav/datastore/query/builder.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequeryfilterpy">CalendarServer/trunk/txdav/carddav/datastore/query/filter.py</a></li>
<li>CalendarServer/trunk/txdav/carddav/datastore/query/test/</li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequerytest__init__py">CalendarServer/trunk/txdav/carddav/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcarddavdatastorequerytesttest_filterpy">CalendarServer/trunk/txdav/carddav/datastore/query/test/test_filter.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepodding__init__py">CalendarServer/trunk/txdav/common/datastore/podding/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingconduitpy">CalendarServer/trunk/txdav/common/datastore/podding/conduit.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingrequestpy">CalendarServer/trunk/txdav/common/datastore/podding/request.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingresourcepy">CalendarServer/trunk/txdav/common/datastore/podding/resource.py</a></li>
<li>CalendarServer/trunk/txdav/common/datastore/podding/test/</li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtest__init__py">CalendarServer/trunk/txdav/common/datastore/podding/test/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtesttest_conduitpy">CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtesttest_external_homepy">CalendarServer/trunk/txdav/common/datastore/podding/test/test_external_home.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtesttest_resourcepy">CalendarServer/trunk/txdav/common/datastore/podding/test/test_resource.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorepoddingtestutilpy">CalendarServer/trunk/txdav/common/datastore/podding/test/util.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequery__init__py">CalendarServer/trunk/txdav/common/datastore/query/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequeryexpressionpy">CalendarServer/trunk/txdav/common/datastore/query/expression.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequeryfilegeneratorpy">CalendarServer/trunk/txdav/common/datastore/query/filegenerator.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequerygeneratorpy">CalendarServer/trunk/txdav/common/datastore/query/generator.py</a></li>
<li>CalendarServer/trunk/txdav/common/datastore/query/test/</li>
<li><a href="#CalendarServertrunktxdavcommondatastorequerytest__init__py">CalendarServer/trunk/txdav/common/datastore/query/test/__init__.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequerytesttest_expressionpy">CalendarServer/trunk/txdav/common/datastore/query/test/test_expression.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastorequerytesttest_generatorpy">CalendarServer/trunk/txdav/common/datastore/query/test/test_generator.py</a></li>
<li><a href="#CalendarServertrunktxdavcommondatastoresql_legacypy">CalendarServer/trunk/txdav/common/datastore/sql_legacy.py</a></li>
</ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#CalendarServertrunk">CalendarServer/trunk/</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunk"></a>
<div class="propset"><h4>Property changes: CalendarServer/trunk</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnmergeinfo"></a>
<div class="modfile"><h4>Modified: svn:mergeinfo</h4></div>
<span class="cx">/CalendarServer/branches/config-separation:4379-4443
</span><span class="cx">/CalendarServer/branches/egg-info-351:4589-4625
</span><span class="cx">/CalendarServer/branches/generic-sqlstore:6167-6191
</span><span class="cx">/CalendarServer/branches/new-store:5594-5934
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile:5911-5935
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
</span><span class="cx">/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
</span><span class="cx">/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
</span><span class="cx">/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
</span><span class="cx">/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
</span><span class="cx">/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
</span><span class="cx">/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
</span><span class="cx">/CalendarServer/branches/users/cdaboo/json:11622-11912
</span><span class="cx">/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
</span><span class="cx">/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
</span><span class="cx">/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
</span><span class="cx">/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pods:7297-7377
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycard:7227-7237
</span><span class="cx">/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
</span><span class="cx">/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
</span><span class="cx">/CalendarServer/branches/users/cdaboo/reverse-proxy-pods:11875-11900
</span><span class="cx">/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
</span><span class="cx">/CalendarServer/branches/users/cdaboo/sharing-in-the-store:11935-12016
</span><span class="cx">/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
</span><span class="cx">/CalendarServer/branches/users/cdaboo/timezones:7443-7699
</span><span class="cx">/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
</span><span class="cx">/CalendarServer/branches/users/gaya/sharedgroupfixes:12120-12142
</span><span class="cx">/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
</span><span class="cx">/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
</span><span class="cx">/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
</span><span class="cx">/CalendarServer/branches/users/glyph/conn-limit:6574-6577
</span><span class="cx">/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
</span><span class="cx">/CalendarServer/branches/users/glyph/dalify:6932-7023
</span><span class="cx">/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
</span><span class="cx">/CalendarServer/branches/users/glyph/deploybuild:7563-7572
</span><span class="cx">/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
</span><span class="cx">/CalendarServer/branches/users/glyph/disable-quota:7718-7727
</span><span class="cx">/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
</span><span class="cx">/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
</span><span class="cx">/CalendarServer/branches/users/glyph/hang-fix:11465-11491
</span><span class="cx">/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
</span><span class="cx">/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
</span><span class="cx">/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
</span><span class="cx">/CalendarServer/branches/users/glyph/linux-tests:6893-6900
</span><span class="cx">/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
</span><span class="cx">/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
</span><span class="cx">/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
</span><span class="cx">/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
</span><span class="cx">/CalendarServer/branches/users/glyph/new-export:7444-7485
</span><span class="cx">/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle:7106-7155
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
</span><span class="cx">/CalendarServer/branches/users/glyph/other-html:8062-8091
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
</span><span class="cx">/CalendarServer/branches/users/glyph/q:9560-9688
</span><span class="cx">/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
</span><span class="cx">/CalendarServer/branches/users/glyph/quota:7604-7637
</span><span class="cx">/CalendarServer/branches/users/glyph/sendfdport:5388-5424
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
</span><span class="cx">/CalendarServer/branches/users/glyph/sharedpool:6490-6550
</span><span class="cx">/CalendarServer/branches/users/glyph/sharing-api:9192-9205
</span><span class="cx">/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
</span><span class="cx">/CalendarServer/branches/users/glyph/sql-store:5929-6073
</span><span class="cx">/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
</span><span class="cx">/CalendarServer/branches/users/glyph/subtransactions:7248-7258
</span><span class="cx">/CalendarServer/branches/users/glyph/table-alias:8651-8664
</span><span class="cx">/CalendarServer/branches/users/glyph/uidexport:7673-7676
</span><span class="cx">/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
</span><span class="cx">/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
</span><span class="cx">/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
</span><span class="cx">/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
</span><span class="cx">/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
</span><span class="cx">/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
</span><span class="cx">/CalendarServer/branches/users/sagen/applepush:8126-8184
</span><span class="cx">/CalendarServer/branches/users/sagen/inboxitems:7380-7381
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources:5032-5051
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
</span><span class="cx">/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
</span><span class="cx">/CalendarServer/branches/users/sagen/resources-2:5084-5093
</span><span class="cx">/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
</span><span class="cx">/CalendarServer/branches/users/wsanchez/transations:5515-5593
</span><span class="cx">   + /CalDAVTester/trunk:11193-11198
</span><span class="cx">/CalendarServer/branches/config-separation:4379-4443
</span><span class="cx">/CalendarServer/branches/egg-info-351:4589-4625
</span><span class="cx">/CalendarServer/branches/generic-sqlstore:6167-6191
</span><span class="cx">/CalendarServer/branches/new-store:5594-5934
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile:5911-5935
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-5.2-dev:11972
</span><span class="cx">/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
</span><span class="cx">/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cross-pod-sharing:12038-12191
</span><span class="cx">/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
</span><span class="cx">/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
</span><span class="cx">/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
</span><span class="cx">/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
</span><span class="cx">/CalendarServer/branches/users/cdaboo/json:11622-11912
</span><span class="cx">/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
</span><span class="cx">/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
</span><span class="cx">/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
</span><span class="cx">/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pods:7297-7377
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycard:7227-7237
</span><span class="cx">/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
</span><span class="cx">/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
</span><span class="cx">/CalendarServer/branches/users/cdaboo/reverse-proxy-pods:11875-11900
</span><span class="cx">/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
</span><span class="cx">/CalendarServer/branches/users/cdaboo/sharing-in-the-store:11935-12016
</span><span class="cx">/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
</span><span class="cx">/CalendarServer/branches/users/cdaboo/timezones:7443-7699
</span><span class="cx">/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
</span><span class="cx">/CalendarServer/branches/users/gaya/sharedgroupfixes:12120-12142
</span><span class="cx">/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
</span><span class="cx">/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
</span><span class="cx">/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
</span><span class="cx">/CalendarServer/branches/users/glyph/conn-limit:6574-6577
</span><span class="cx">/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
</span><span class="cx">/CalendarServer/branches/users/glyph/dalify:6932-7023
</span><span class="cx">/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
</span><span class="cx">/CalendarServer/branches/users/glyph/deploybuild:7563-7572
</span><span class="cx">/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
</span><span class="cx">/CalendarServer/branches/users/glyph/disable-quota:7718-7727
</span><span class="cx">/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
</span><span class="cx">/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
</span><span class="cx">/CalendarServer/branches/users/glyph/hang-fix:11465-11491
</span><span class="cx">/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
</span><span class="cx">/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
</span><span class="cx">/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
</span><span class="cx">/CalendarServer/branches/users/glyph/linux-tests:6893-6900
</span><span class="cx">/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
</span><span class="cx">/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
</span><span class="cx">/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
</span><span class="cx">/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
</span><span class="cx">/CalendarServer/branches/users/glyph/new-export:7444-7485
</span><span class="cx">/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle:7106-7155
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
</span><span class="cx">/CalendarServer/branches/users/glyph/other-html:8062-8091
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
</span><span class="cx">/CalendarServer/branches/users/glyph/q:9560-9688
</span><span class="cx">/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
</span><span class="cx">/CalendarServer/branches/users/glyph/quota:7604-7637
</span><span class="cx">/CalendarServer/branches/users/glyph/sendfdport:5388-5424
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
</span><span class="cx">/CalendarServer/branches/users/glyph/sharedpool:6490-6550
</span><span class="cx">/CalendarServer/branches/users/glyph/sharing-api:9192-9205
</span><span class="cx">/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
</span><span class="cx">/CalendarServer/branches/users/glyph/sql-store:5929-6073
</span><span class="cx">/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
</span><span class="cx">/CalendarServer/branches/users/glyph/subtransactions:7248-7258
</span><span class="cx">/CalendarServer/branches/users/glyph/table-alias:8651-8664
</span><span class="cx">/CalendarServer/branches/users/glyph/uidexport:7673-7676
</span><span class="cx">/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
</span><span class="cx">/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
</span><span class="cx">/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
</span><span class="cx">/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
</span><span class="cx">/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
</span><span class="cx">/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
</span><span class="cx">/CalendarServer/branches/users/sagen/applepush:8126-8184
</span><span class="cx">/CalendarServer/branches/users/sagen/inboxitems:7380-7381
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources:5032-5051
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
</span><span class="cx">/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
</span><span class="cx">/CalendarServer/branches/users/sagen/resources-2:5084-5093
</span><span class="cx">/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
</span><span class="cx">/CalendarServer/branches/users/wsanchez/transations:5515-5593
</span><a id="CalendarServertrunkcalendarserverpushtesttest_notifierpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/push/test/test_notifier.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/push/test/test_notifier.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/push/test/test_notifier.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -184,10 +184,10 @@
</span><span class="cx"> class NotifierFactory(StoreTestCase):
</span><span class="cx"> 
</span><span class="cx">     requirements = {
</span><del>-        &quot;home1&quot; : {
</del><ins>+        &quot;user01&quot; : {
</ins><span class="cx">             &quot;calendar_1&quot; : {}
</span><span class="cx">         },
</span><del>-        &quot;home2&quot; : {
</del><ins>+        &quot;user02&quot; : {
</ins><span class="cx">             &quot;calendar_1&quot; : {}
</span><span class="cx">         },
</span><span class="cx">     }
</span><span class="lines">@@ -208,23 +208,23 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_homeNotifier(self):
</span><span class="cx"> 
</span><del>-        home = yield self.homeUnderTest()
</del><ins>+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
</ins><span class="cx">         yield home.notifyChanged(category=ChangeCategory.default)
</span><span class="cx">         self.assertEquals(self.notifierFactory.history,
</span><del>-            [(&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high)])
</del><ins>+            [(&quot;/CalDAV/example.com/user01/&quot;, PushPriority.high)])
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_calendarNotifier(self):
</span><span class="cx"> 
</span><del>-        calendar = yield self.calendarUnderTest()
</del><ins>+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;)
</ins><span class="cx">         yield calendar.notifyChanged(category=ChangeCategory.default)
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high)])
</del><ins>+                (&quot;/CalDAV/example.com/user01/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user01/calendar_1/&quot;, PushPriority.high)])
</ins><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -232,28 +232,28 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_shareWithNotifier(self):
</span><span class="cx"> 
</span><del>-        calendar = yield self.calendarUnderTest()
-        yield calendar.inviteUserToShare(&quot;home2&quot;, _BIND_MODE_WRITE, &quot;&quot;)
</del><ins>+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;)
+        yield calendar.inviteUserToShare(&quot;user02&quot;, _BIND_MODE_WRITE, &quot;&quot;)
</ins><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home2/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home2/notification/&quot;, PushPriority.high),
</del><ins>+                (&quot;/CalDAV/example.com/user01/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user01/calendar_1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user02/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user02/notification/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        calendar = yield self.calendarUnderTest()
-        yield calendar.uninviteUserFromShare(&quot;home2&quot;)
</del><ins>+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;)
+        yield calendar.uninviteUserFromShare(&quot;user02&quot;)
</ins><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home2/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home2/notification/&quot;, PushPriority.high),
</del><ins>+                (&quot;/CalDAV/example.com/user01/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user01/calendar_1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user02/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user02/notification/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -262,20 +262,20 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_sharedCalendarNotifier(self):
</span><span class="cx"> 
</span><del>-        calendar = yield self.calendarUnderTest()
-        shareeView = yield calendar.inviteUserToShare(&quot;home2&quot;, _BIND_MODE_WRITE, &quot;&quot;)
</del><ins>+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;)
+        shareeView = yield calendar.inviteUserToShare(&quot;user02&quot;, _BIND_MODE_WRITE, &quot;&quot;)
</ins><span class="cx">         yield shareeView.acceptShare(&quot;&quot;)
</span><span class="cx">         shareName = shareeView.name()
</span><span class="cx">         yield self.commit()
</span><span class="cx">         self.notifierFactory.reset()
</span><span class="cx"> 
</span><del>-        shared = yield self.calendarUnderTest(home=&quot;home2&quot;, name=shareName)
</del><ins>+        shared = yield self.calendarUnderTest(home=&quot;user02&quot;, name=shareName)
</ins><span class="cx">         yield shared.notifyChanged(category=ChangeCategory.default)
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high)])
</del><ins>+                (&quot;/CalDAV/example.com/user01/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user01/calendar_1/&quot;, PushPriority.high)])
</ins><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -283,12 +283,12 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_notificationNotifier(self):
</span><span class="cx"> 
</span><del>-        notifications = yield self.transactionUnderTest().notificationsWithUID(&quot;home1&quot;)
</del><ins>+        notifications = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
</ins><span class="cx">         yield notifications.notifyChanged(category=ChangeCategory.default)
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
-                (&quot;/CalDAV/example.com/home1/notification/&quot;, PushPriority.high)])
</del><ins>+                (&quot;/CalDAV/example.com/user01/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/user01/notification/&quot;, PushPriority.high)])
</ins><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertapcaldavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tap/caldav.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tap/caldav.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1691,11 +1691,9 @@
</span><span class="cx">                 raise StoreNotAvailable()
</span><span class="cx"> 
</span><span class="cx">             from twisted.internet import reactor
</span><del>-            pool = PeerConnectionPool(reactor, store.newTransaction,
-                                      7654, schema)
</del><ins>+            pool = PeerConnectionPool(reactor, store.newTransaction, config.WorkQueue.ampPort, schema)
</ins><span class="cx">             store.queuer = store.queuer.transferProposalCallbacks(pool)
</span><del>-            controlSocket.addFactory(_QUEUE_ROUTE,
-                                     pool.workerListenerFactory())
</del><ins>+            controlSocket.addFactory(_QUEUE_ROUTE, pool.workerListenerFactory())
</ins><span class="cx">             # TODO: now that we have the shared control socket, we should get
</span><span class="cx">             # rid of the connection dispenser and make a shared / async
</span><span class="cx">             # connection pool implementation that can dispense transactions
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertaputilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tap/util.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tap/util.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tap/util.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -92,6 +92,7 @@
</span><span class="cx"> from calendarserver.webadmin.resource import WebAdminResource
</span><span class="cx"> from calendarserver.webcal.resource import WebCalendarResource
</span><span class="cx"> 
</span><ins>+from txdav.common.datastore.podding.resource import ConduitResource
</ins><span class="cx"> from txdav.common.datastore.sql import CommonDataStore as CommonSQLDataStore
</span><span class="cx"> from txdav.common.datastore.file import CommonDataStore as CommonFileDataStore
</span><span class="cx"> from txdav.common.datastore.sql import current_sql_schema
</span><span class="lines">@@ -407,6 +408,7 @@
</span><span class="cx">     rootResourceClass = RootResource
</span><span class="cx">     calendarResourceClass = DirectoryCalendarHomeProvisioningResource
</span><span class="cx">     iScheduleResourceClass = IScheduleInboxResource
</span><ins>+    conduitResourceClass = ConduitResource
</ins><span class="cx">     timezoneServiceResourceClass = TimezoneServiceResource
</span><span class="cx">     timezoneStdServiceResourceClass = TimezoneStdServiceResource
</span><span class="cx">     webCalendarResourceClass = WebCalendarResource
</span><span class="lines">@@ -636,7 +638,7 @@
</span><span class="cx">             addSystemEventTrigger(&quot;after&quot;, &quot;startup&quot;, timezoneStdService.onStartup)
</span><span class="cx"> 
</span><span class="cx">     #
</span><del>-    # iSchedule service for podding
</del><ins>+    # iSchedule/cross-pod service for podding
</ins><span class="cx">     #
</span><span class="cx">     if config.Servers.Enabled:
</span><span class="cx">         log.info(&quot;Setting up iSchedule podding inbox resource: {cls}&quot;, cls=iScheduleResourceClass)
</span><span class="lines">@@ -648,6 +650,14 @@
</span><span class="cx">         )
</span><span class="cx">         root.putChild(config.Servers.InboxName, ischedule)
</span><span class="cx"> 
</span><ins>+        log.info(&quot;Setting up podding conduit resource: {cls}&quot;, cls=conduitResourceClass)
+
+        conduit = conduitResourceClass(
+            root,
+            newStore,
+        )
+        root.putChild(config.Servers.ConduitName, conduit)
+
</ins><span class="cx">     #
</span><span class="cx">     # iSchedule service (not used for podding)
</span><span class="cx">     #
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolsdbinspectpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/dbinspect.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/dbinspect.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tools/dbinspect.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -37,8 +37,8 @@
</span><span class="cx"> from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
</span><span class="cx"> from twistedcaldav.directory import calendaruserproxy
</span><span class="cx"> from twistedcaldav.directory.directory import DirectoryService
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
</span><span class="cx"> from uuid import UUID
</span><span class="cx"> import os
</span><span class="lines">@@ -757,10 +757,10 @@
</span><span class="cx">                           name=&quot;VCALENDAR&quot;,
</span><span class="cx">                        )
</span><span class="cx">                   )
</span><del>-        filter = calendarqueryfilter.Filter(filter)
</del><ins>+        filter = Filter(filter)
</ins><span class="cx">         filter.settimezone(None)
</span><span class="cx"> 
</span><del>-        matches = yield calendar._index.indexedSearch(filter, useruid=uid, fbtype=False)
</del><ins>+        matches = yield calendar.search(filter, useruid=uid, fbtype=False)
</ins><span class="cx">         if matches is None:
</span><span class="cx">             returnValue(None)
</span><span class="cx">         for name, _ignore_uid, _ignore_type in matches:
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolspurgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/purge.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/purge.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tools/purge.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -30,10 +30,9 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import caldavxml
</span><del>-from twistedcaldav.caldavxml import TimeRange
</del><span class="cx"> from twistedcaldav.directory.directory import DirectoryRecord
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -817,13 +816,13 @@
</span><span class="cx">         query_filter = caldavxml.Filter(
</span><span class="cx">               caldavxml.ComponentFilter(
</span><span class="cx">                   caldavxml.ComponentFilter(
</span><del>-                      TimeRange(start=whenString,),
</del><ins>+                      caldavxml.TimeRange(start=whenString,),
</ins><span class="cx">                       name=(&quot;VEVENT&quot;,),
</span><span class="cx">                   ),
</span><span class="cx">                   name=&quot;VCALENDAR&quot;,
</span><span class="cx">                )
</span><span class="cx">           )
</span><del>-        query_filter = calendarqueryfilter.Filter(query_filter)
</del><ins>+        query_filter = Filter(query_filter)
</ins><span class="cx"> 
</span><span class="cx">         count = 0
</span><span class="cx">         txn = self.store.newTransaction()
</span><span class="lines">@@ -844,7 +843,7 @@
</span><span class="cx">                     childNames.append(childName)
</span><span class="cx">             else:
</span><span class="cx">                 # events matching filter
</span><del>-                for childName, _ignore_childUid, _ignore_childType in (yield calendar._index.indexedSearch(query_filter)):
</del><ins>+                for childName, _ignore_childUid, _ignore_childType in (yield calendar.search(query_filter)):
</ins><span class="cx">                     childNames.append(childName)
</span><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolsshelltesttest_vfspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/shell/test/test_vfs.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/shell/test/test_vfs.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tools/shell/test/test_vfs.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -28,45 +28,55 @@
</span><span class="cx"> 
</span><span class="cx"> class TestListEntry(TestCase):
</span><span class="cx">     def test_toString(self):
</span><del>-        self.assertEquals(ListEntry(None, File  , &quot;thingo&quot;           ).toString(), &quot;thingo&quot; )
-        self.assertEquals(ListEntry(None, File  , &quot;thingo&quot;, Foo=&quot;foo&quot;).toString(), &quot;thingo&quot; )
-        self.assertEquals(ListEntry(None, Folder, &quot;thingo&quot;           ).toString(), &quot;thingo/&quot;)
</del><ins>+        self.assertEquals(ListEntry(None, File  , &quot;thingo&quot;).toString(), &quot;thingo&quot;)
+        self.assertEquals(ListEntry(None, File  , &quot;thingo&quot;, Foo=&quot;foo&quot;).toString(), &quot;thingo&quot;)
+        self.assertEquals(ListEntry(None, Folder, &quot;thingo&quot;).toString(), &quot;thingo/&quot;)
</ins><span class="cx">         self.assertEquals(ListEntry(None, Folder, &quot;thingo&quot;, Foo=&quot;foo&quot;).toString(), &quot;thingo/&quot;)
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_fieldNamesImplicit(self):
</span><span class="cx">         # This test assumes File doesn't set list.fieldNames.
</span><span class="cx">         assert not hasattr(File.list, &quot;fieldNames&quot;)
</span><span class="cx"> 
</span><span class="cx">         self.assertEquals(set(ListEntry(File(None, ()), File, &quot;thingo&quot;).fieldNames), set((&quot;Name&quot;,)))
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_fieldNamesExplicit(self):
</span><span class="cx">         def fieldNames(fileClass):
</span><span class="cx">             return ListEntry(fileClass(None, ()), fileClass, &quot;thingo&quot;, Flavor=&quot;Coconut&quot;, Style=&quot;Hard&quot;)
</span><span class="cx"> 
</span><span class="cx">         # Full list
</span><del>-        class MyFile(File):
-            def list(self): return succeed(())
</del><ins>+        class MyFile1(File):
+            def list(self):
+                return succeed(())
</ins><span class="cx">             list.fieldNames = (&quot;Name&quot;, &quot;Flavor&quot;)
</span><del>-        self.assertEquals(fieldNames(MyFile).fieldNames, (&quot;Name&quot;, &quot;Flavor&quot;))
</del><ins>+        self.assertEquals(fieldNames(MyFile1).fieldNames, (&quot;Name&quot;, &quot;Flavor&quot;))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">         # Full list, different order
</span><del>-        class MyFile(File):
-            def list(self): return succeed(())
</del><ins>+        class MyFile2(File):
+            def list(self):
+                return succeed(())
</ins><span class="cx">             list.fieldNames = (&quot;Flavor&quot;, &quot;Name&quot;)
</span><del>-        self.assertEquals(fieldNames(MyFile).fieldNames, (&quot;Flavor&quot;, &quot;Name&quot;))
</del><ins>+        self.assertEquals(fieldNames(MyFile2).fieldNames, (&quot;Flavor&quot;, &quot;Name&quot;))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">         # Omits Name, which is implicitly added
</span><del>-        class MyFile(File):
-            def list(self): return succeed(())
</del><ins>+        class MyFile3(File):
+            def list(self):
+                return succeed(())
</ins><span class="cx">             list.fieldNames = (&quot;Flavor&quot;,)
</span><del>-        self.assertEquals(fieldNames(MyFile).fieldNames, (&quot;Name&quot;, &quot;Flavor&quot;))
</del><ins>+        self.assertEquals(fieldNames(MyFile3).fieldNames, (&quot;Name&quot;, &quot;Flavor&quot;))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">         # Emtpy
</span><del>-        class MyFile(File):
-            def list(self): return succeed(())
</del><ins>+        class MyFile4(File):
+            def list(self):
+                return succeed(())
</ins><span class="cx">             list.fieldNames = ()
</span><del>-        self.assertEquals(fieldNames(MyFile).fieldNames, (&quot;Name&quot;,))
</del><ins>+        self.assertEquals(fieldNames(MyFile4).fieldNames, (&quot;Name&quot;,))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_toFieldsImplicit(self):
</span><span class="cx">         # This test assumes File doesn't set list.fieldNames.
</span><span class="cx">         assert not hasattr(File.list, &quot;fieldNames&quot;)
</span><span class="lines">@@ -77,33 +87,41 @@
</span><span class="cx">             (&quot;thingo&quot;, &quot;Coconut&quot;, &quot;Hard&quot;)
</span><span class="cx">         )
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def test_toFieldsExplicit(self):
</span><span class="cx">         def fields(fileClass):
</span><span class="cx">             return tuple(ListEntry(fileClass(None, ()), fileClass, &quot;thingo&quot;, Flavor=&quot;Coconut&quot;, Style=&quot;Hard&quot;).toFields())
</span><span class="cx"> 
</span><span class="cx">         # Full list
</span><del>-        class MyFile(File):
-            def list(self): return succeed(())
</del><ins>+        class MyFile1(File):
+            def list(self):
+                return succeed(())
</ins><span class="cx">             list.fieldNames = (&quot;Name&quot;, &quot;Flavor&quot;)
</span><del>-        self.assertEquals(fields(MyFile), (&quot;thingo&quot;, &quot;Coconut&quot;))
</del><ins>+        self.assertEquals(fields(MyFile1), (&quot;thingo&quot;, &quot;Coconut&quot;))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">         # Full list, different order
</span><del>-        class MyFile(File):
-            def list(self): return succeed(())
</del><ins>+        class MyFile2(File):
+            def list(self):
+                return succeed(())
</ins><span class="cx">             list.fieldNames = (&quot;Flavor&quot;, &quot;Name&quot;)
</span><del>-        self.assertEquals(fields(MyFile), (&quot;Coconut&quot;, &quot;thingo&quot;))
</del><ins>+        self.assertEquals(fields(MyFile2), (&quot;Coconut&quot;, &quot;thingo&quot;))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">         # Omits Name, which is implicitly added
</span><del>-        class MyFile(File):
-            def list(self): return succeed(())
</del><ins>+        class MyFile3(File):
+            def list(self):
+                return succeed(())
</ins><span class="cx">             list.fieldNames = (&quot;Flavor&quot;,)
</span><del>-        self.assertEquals(fields(MyFile), (&quot;thingo&quot;, &quot;Coconut&quot;))
</del><ins>+        self.assertEquals(fields(MyFile3), (&quot;thingo&quot;, &quot;Coconut&quot;))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">         # Emtpy
</span><del>-        class MyFile(File):
-            def list(self): return succeed(())
</del><ins>+        class MyFile4(File):
+            def list(self):
+                return succeed(())
</ins><span class="cx">             list.fieldNames = ()
</span><del>-        self.assertEquals(fields(MyFile), (&quot;thingo&quot;,))
</del><ins>+        self.assertEquals(fields(MyFile4), (&quot;thingo&quot;,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -114,6 +132,7 @@
</span><span class="cx">     def __init__(self, testCase):
</span><span class="cx">         self.testCase = testCase
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def mktemp(self):
</span><span class="cx">         return self.testCase.mktemp()
</span><span class="cx"> 
</span><span class="lines">@@ -129,8 +148,9 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a L{UIDsFolder}.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        self.svc = ShellService(store=(yield buildStore(self, None)),
-                                directory=DirectoryStubber(self).service(),
</del><ins>+        directory = DirectoryStubber(self).service()
+        self.svc = ShellService(store=(yield buildStore(self, None, directoryService=directory)),
+                                directory=directory,
</ins><span class="cx">                                 options=None, reactor=None, config=None)
</span><span class="cx">         self.folder = UIDsFolder(self.svc, ())
</span><span class="cx"> 
</span><span class="lines">@@ -156,6 +176,3 @@
</span><span class="cx">               {&quot;Record Type&quot;: &quot;users&quot;, &quot;Short Name&quot;: &quot;dreid&quot;,
</span><span class="cx">               &quot;Full Name&quot;: &quot;David Reid&quot;, &quot;Name&quot;: dreid}]
</span><span class="cx">         )
</span><del>-
-
-
</del></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstestcalverifyaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -47,4 +47,11 @@
</span><span class="cx">     &lt;name&gt;Example User4&lt;/name&gt;
</span><span class="cx">     &lt;email-address&gt;example4@example.com&lt;/email-address&gt;
</span><span class="cx">   &lt;/user&gt;
</span><ins>+  &lt;user&gt;
+    &lt;uid&gt;home1&lt;/uid&gt;
+    &lt;guid&gt;home1&lt;/guid&gt;
+    &lt;password&gt;home1&lt;/password&gt;
+    &lt;name&gt;Home 1&lt;/name&gt;
+    &lt;email-address&gt;home1@example.com&lt;/email-address&gt;
+  &lt;/user&gt;
</ins><span class="cx"> &lt;/accounts&gt;
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstestpurgeaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/purge/accounts.xml (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/test/purge/accounts.xml        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tools/test/purge/accounts.xml        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -26,4 +26,25 @@
</span><span class="cx">     &lt;name&gt;Example User&lt;/name&gt;
</span><span class="cx">     &lt;email-address&gt;example@example.com&lt;/email-address&gt;
</span><span class="cx">   &lt;/user&gt;
</span><ins>+  &lt;user&gt;
+    &lt;uid&gt;example2&lt;/uid&gt;
+    &lt;guid&gt;37DB0C90-4DB1-4932-BC69-3DAB66F374F5&lt;/guid&gt;
+    &lt;password&gt;example2&lt;/password&gt;
+    &lt;name&gt;Example User 2&lt;/name&gt;
+    &lt;email-address&gt;example2@example.com&lt;/email-address&gt;
+  &lt;/user&gt;
+  &lt;user&gt;
+    &lt;uid&gt;home1&lt;/uid&gt;
+    &lt;guid&gt;home1&lt;/guid&gt;
+    &lt;password&gt;home1&lt;/password&gt;
+    &lt;name&gt;Home 1&lt;/name&gt;
+    &lt;email-address&gt;home1@example.com&lt;/email-address&gt;
+  &lt;/user&gt;
+  &lt;user&gt;
+    &lt;uid&gt;home2&lt;/uid&gt;
+    &lt;guid&gt;home2&lt;/guid&gt;
+    &lt;password&gt;home2&lt;/password&gt;
+    &lt;name&gt;Home 2&lt;/name&gt;
+    &lt;email-address&gt;home2@example.com&lt;/email-address&gt;
+  &lt;/user&gt;
</ins><span class="cx"> &lt;/accounts&gt;
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstesttest_exportpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/test_export.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/test/test_export.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tools/test/test_export.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -277,7 +277,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         yield populateCalendarsFrom(
</span><span class="cx">             {
</span><del>-                &quot;home1&quot;: {
</del><ins>+                &quot;user01&quot;: {
</ins><span class="cx">                     &quot;calendar1&quot;: {
</span><span class="cx">                         &quot;valentines-day.ics&quot;: (valentines, {})
</span><span class="cx">                     }
</span><span class="lines">@@ -291,7 +291,7 @@
</span><span class="cx"> 
</span><span class="cx">         io = StringIO()
</span><span class="cx">         yield exportToFile(
</span><del>-            [(yield self.txn().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+            [(yield self.txn().calendarHomeWithUID(&quot;user01&quot;))
</ins><span class="cx">               .calendarWithName(&quot;calendar1&quot;)], io
</span><span class="cx">         )
</span><span class="cx">         self.assertEquals(Component.fromString(io.getvalue()),
</span><span class="lines">@@ -306,7 +306,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         yield populateCalendarsFrom(
</span><span class="cx">             {
</span><del>-                &quot;home1&quot;: {
</del><ins>+                &quot;user01&quot;: {
</ins><span class="cx">                     &quot;calendar1&quot;: {
</span><span class="cx">                         &quot;valentines-day.ics&quot;: (valentines, {}),
</span><span class="cx">                         &quot;new-years-day.ics&quot;: (newYears, {})
</span><span class="lines">@@ -324,7 +324,7 @@
</span><span class="cx"> 
</span><span class="cx">         io = StringIO()
</span><span class="cx">         yield exportToFile(
</span><del>-            [(yield self.txn().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+            [(yield self.txn().calendarHomeWithUID(&quot;user01&quot;))
</ins><span class="cx">               .calendarWithName(&quot;calendar1&quot;)], io
</span><span class="cx">         )
</span><span class="cx">         self.assertEquals(Component.fromString(io.getvalue()),
</span><span class="lines">@@ -342,7 +342,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         yield populateCalendarsFrom(
</span><span class="cx">             {
</span><del>-                &quot;home1&quot;: {
</del><ins>+                &quot;user01&quot;: {
</ins><span class="cx">                     &quot;calendar1&quot;: {
</span><span class="cx">                         &quot;1.ics&quot;: (one, {}), # EST
</span><span class="cx">                         &quot;2.ics&quot;: (another, {}), # EST
</span><span class="lines">@@ -354,7 +354,7 @@
</span><span class="cx"> 
</span><span class="cx">         io = StringIO()
</span><span class="cx">         yield exportToFile(
</span><del>-            [(yield self.txn().calendarHomeWithUID(&quot;home1&quot;))
</del><ins>+            [(yield self.txn().calendarHomeWithUID(&quot;user01&quot;))
</ins><span class="cx">               .calendarWithName(&quot;calendar1&quot;)], io
</span><span class="cx">         )
</span><span class="cx">         result = Component.fromString(io.getvalue())
</span></span></pre></div>
<a id="CalendarServertrunkcalendarservertoolstesttest_purgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/calendarserver/tools/test/test_purge.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/calendarserver/tools/test/test_purge.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/calendarserver/tools/test/test_purge.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -806,16 +806,6 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><del>-        self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;purge&quot;, &quot;accounts.xml&quot;
-            )
-        )
-        self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
-            os.path.join(
-                os.path.dirname(__file__), &quot;purge&quot;, &quot;resources.xml&quot;
-            )
-        )
</del><span class="cx">         yield super(PurgePrincipalTests, self).setUp()
</span><span class="cx"> 
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="lines">@@ -850,6 +840,20 @@
</span><span class="cx">         (yield txn.commit())
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def configure(self):
+        super(PurgePrincipalTests, self).configure()
+        self.patch(config.DirectoryService.params, &quot;xmlFile&quot;,
+            os.path.join(
+                os.path.dirname(__file__), &quot;purge&quot;, &quot;accounts.xml&quot;
+            )
+        )
+        self.patch(config.ResourceService.params, &quot;xmlFile&quot;,
+            os.path.join(
+                os.path.dirname(__file__), &quot;purge&quot;, &quot;resources.xml&quot;
+            )
+        )
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def populate(self):
</span><span class="cx">         yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
</span></span></pre></div>
<a id="CalendarServertrunkconfcaldavdtestpodBplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/conf/caldavd-test-podB.plist (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/conf/caldavd-test-podB.plist        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/conf/caldavd-test-podB.plist        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -43,6 +43,13 @@
</span><span class="cx">     &lt;array&gt;
</span><span class="cx">     &lt;/array&gt;
</span><span class="cx"> 
</span><ins>+        &lt;!--  Work Queue --&gt;
+        &lt;key&gt;WorkQueue&lt;/key&gt;
+        &lt;dict&gt;
+                &lt;key&gt;ampPort&lt;/key&gt;
+                &lt;integer&gt;7655&lt;/integer&gt;
+        &lt;/dict&gt;
+
</ins><span class="cx">     &lt;!-- Server root --&gt;
</span><span class="cx">     &lt;key&gt;ServerRoot&lt;/key&gt;
</span><span class="cx">     &lt;string&gt;./data/podB&lt;/string&gt;
</span><span class="lines">@@ -94,6 +101,19 @@
</span><span class="cx">       &lt;/dict&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><span class="cx"> 
</span><ins>+    &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;
+
</ins><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></pre></div>
<a id="CalendarServertrunksupportbuildsh"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/support/build.sh (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/support/build.sh        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/support/build.sh        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -864,7 +864,7 @@
</span><span class="cx">     &quot;${n}&quot; &quot;twisted&quot; &quot;${p}&quot; \
</span><span class="cx">     &quot;${pypi}/T/${n}/${p}.tar.bz2&quot;;
</span><span class="cx"> 
</span><del>-  local v=&quot;12171&quot;;
</del><ins>+  local v=&quot;12210&quot;;
</ins><span class="cx">   local n=&quot;twext&quot;;
</span><span class="cx">   local p=&quot;${n}-${v}&quot;;
</span><span class="cx">   py_dependency -fe -r &quot;${v}&quot; \
</span></span></pre></div>
<a id="CalendarServertrunktwistedpluginscaldavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twisted/plugins/caldav.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twisted/plugins/caldav.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twisted/plugins/caldav.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -23,7 +23,6 @@
</span><span class="cx"> from twisted.internet.protocol import Factory
</span><span class="cx"> Factory.noisy = False
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> def serviceMakerProperty(propname):
</span><span class="cx">     def getProperty(self):
</span><span class="cx">         return getattr(reflect.namedClass(self.serviceMakerClass), propname)
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectoryopendirectorybackerpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/directory/opendirectorybacker.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -23,46 +23,46 @@
</span><span class="cx">     &quot;OpenDirectoryBackingService&quot;, &quot;VCardRecord&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><del>-import traceback
-import hashlib
</del><ins>+from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
</ins><span class="cx"> 
</span><del>-import os
-import sys
-import time
-
-from os import listdir
-from os.path import join, abspath
-from tempfile import mkstemp, gettempdir
-from random import random
-
</del><ins>+from pycalendar.datetime import DateTime
+from pycalendar.vcard.adr import Adr
</ins><span class="cx"> from pycalendar.vcard.n import N
</span><del>-from pycalendar.vcard.adr import Adr
-from pycalendar.datetime import DateTime
</del><span class="cx"> 
</span><del>-from socket import getfqdn
</del><span class="cx"> 
</span><del>-from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, deferredGenerator, succeed
</del><span class="cx"> from twext.python.filepath import CachingFilePath as FilePath
</span><del>-from txdav.xml import element as davxml
-from txdav.xml.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
</del><ins>+
</ins><span class="cx"> from txweb2.dav.resource import DAVPropertyMixIn
</span><span class="cx"> from txweb2.dav.util import joinURL
</span><span class="cx"> from txweb2.http_headers import MimeType, generateContentType, ETag
</span><span class="cx"> 
</span><ins>+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, returnValue, deferredGenerator, succeed
</ins><span class="cx"> 
</span><span class="cx"> from twistedcaldav import customxml, carddavxml
</span><ins>+from twistedcaldav.config import config
</ins><span class="cx"> from twistedcaldav.customxml import calendarserver_namespace
</span><del>-from twistedcaldav.config import config
</del><span class="cx"> from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
</span><span class="cx"> from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
</span><del>-from twistedcaldav.query import addressbookqueryfilter
</del><span class="cx"> from twistedcaldav.vcard import Component, Property, vCardProductID
</span><span class="cx"> 
</span><ins>+from txdav.carddav.datastore.query.filter import IsNotDefined, ParameterFilter, \
+    TextMatch
+from txdav.xml import element as davxml
+from txdav.xml.base import twisted_dav_namespace, dav_namespace, parse_date, twisted_private_namespace
+
+from os import listdir
+from os.path import join, abspath
+from random import random
+from socket import getfqdn
+from tempfile import mkstemp, gettempdir
</ins><span class="cx"> from xmlrpclib import datetime
</span><ins>+import hashlib
+import os
+import sys
+import time
+import traceback
</ins><span class="cx"> 
</span><del>-from calendarserver.platform.darwin.od import opendirectory, dsattributes, dsquery
-
</del><span class="cx"> class OpenDirectoryBackingService(DirectoryService):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Open Directory implementation of L{IDirectoryService}.
</span><span class="lines">@@ -830,11 +830,11 @@
</span><span class="cx">                 if not constant and not allAttrStrings:
</span><span class="cx">                     return (False, [], [])
</span><span class="cx"> 
</span><del>-                if propFilter.qualifier and isinstance(propFilter.qualifier, addressbookqueryfilter.IsNotDefined):
</del><ins>+                if propFilter.qualifier and isinstance(propFilter.qualifier, IsNotDefined):
</ins><span class="cx">                     return definedExpression(False, filterAllOf, propFilter.filter_name, constant, queryAttributes, allAttrStrings)
</span><span class="cx"> 
</span><del>-                paramFilterElements = [paramFilterElement for paramFilterElement in propFilter.filters if isinstance(paramFilterElement, addressbookqueryfilter.ParameterFilter)]
-                textMatchElements = [textMatchElement for textMatchElement in propFilter.filters if isinstance(textMatchElement, addressbookqueryfilter.TextMatch)]
</del><ins>+                paramFilterElements = [paramFilterElement for paramFilterElement in propFilter.filters if isinstance(paramFilterElement, ParameterFilter)]
+                textMatchElements = [textMatchElement for textMatchElement in propFilter.filters if isinstance(textMatchElement, TextMatch)]
</ins><span class="cx">                 propFilterAllOf = propFilter.propfilter_test == &quot;allof&quot;
</span><span class="cx"> 
</span><span class="cx">                 # handle parameter filter elements
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectoryprincipalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directory/principal.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -922,6 +922,23 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def proxyMode(self, principal):
+        &quot;&quot;&quot;
+        Determine whether what proxy mode this principal has in relation to the one specified.
+        &quot;&quot;&quot;
+
+        read_uids = (yield self.proxyFor(False))
+        if principal in read_uids:
+            returnValue(&quot;read&quot;)
+
+        write_uids = (yield self.proxyFor(True))
+        if principal in write_uids:
+            returnValue(&quot;write&quot;)
+
+        returnValue(&quot;none&quot;)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def proxyFor(self, read_write, resolve_memberships=True):
</span><span class="cx"> 
</span><span class="cx">         proxyFors = set()
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectoryresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/resource.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directory/resource.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/directory/resource.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -22,6 +22,8 @@
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.client.reverseproxy import ReverseProxyResource
</span><span class="cx"> 
</span><ins>+from twisted.internet.defer import succeed
+
</ins><span class="cx"> __all__ = [&quot;DirectoryReverseProxyResource&quot;]
</span><span class="cx"> 
</span><span class="cx"> class DirectoryReverseProxyResource(ReverseProxyResource):
</span><span class="lines">@@ -35,3 +37,32 @@
</span><span class="cx"> 
</span><span class="cx">     def url(self):
</span><span class="cx">         return joinURL(self.parent.url(), self.record.uid)
</span><ins>+
+
+    def hasQuota(self, request):
+        return succeed(False)
+
+
+    def hasQuotaRoot(self, request):
+        return succeed(False)
+
+
+    def quotaRootResource(self, request):
+        &quot;&quot;&quot;
+        Return the quota root for this resource.
+
+        @return: L{DAVResource} or C{None}
+        &quot;&quot;&quot;
+
+        return succeed(None)
+
+
+    def checkPrivileges(
+        self, request, privileges, recurse=False,
+        principal=None, inherited_aces=None
+    ):
+        return succeed(None)
+
+
+    def hasProperty(self, property, request):
+        return succeed(False)
</ins></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectorytestaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/directory/test/accounts.xml        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -139,7 +139,7 @@
</span><span class="cx">       &lt;member type=&quot;users&quot;&gt;delegateviagroup&lt;/member&gt;
</span><span class="cx">     &lt;/members&gt;
</span><span class="cx">   &lt;/group&gt;
</span><del>-  &lt;user repeat=&quot;2&quot;&gt;
</del><ins>+  &lt;user repeat=&quot;100&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><span class="cx">     &lt;guid&gt;user%02d&lt;/guid&gt;
</span><span class="cx">     &lt;password&gt;%02duser&lt;/password&gt;
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectorytestaugmentsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/test/augments.xml (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directory/test/augments.xml        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments.xml        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -57,7 +57,7 @@
</span><span class="cx">     &lt;enable-calendar&gt;false&lt;/enable-calendar&gt;
</span><span class="cx">     &lt;enable-addressbook&gt;false&lt;/enable-addressbook&gt;
</span><span class="cx">   &lt;/record&gt;
</span><del>-  &lt;record repeat=&quot;2&quot;&gt;
</del><ins>+  &lt;record repeat=&quot;100&quot;&gt;
</ins><span class="cx">     &lt;uid&gt;user%02d&lt;/uid&gt;
</span><span class="cx">     &lt;enable&gt;true&lt;/enable&gt;
</span><span class="cx">     &lt;enable-calendar&gt;true&lt;/enable-calendar&gt;
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavdirectorytesttest_directorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -999,8 +999,9 @@
</span><span class="cx">         Exercise the default recordsMatchingTokens implementation
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         records = list((yield self.directoryService.recordsMatchingTokens([&quot;Use&quot;, &quot;01&quot;])))
</span><del>-        self.assertEquals(len(records), 1)
-        self.assertEquals(records[0].shortNames[0], &quot;user01&quot;)
</del><ins>+        self.assertNotEquals(len(records), 0)
+        shorts = [record.shortNames[0] for record in records]
+        self.assertTrue(&quot;user01&quot; in shorts)
</ins><span class="cx"> 
</span><span class="cx">         records = list((yield self.directoryService.recordsMatchingTokens(['&quot;quotey&quot;'],
</span><span class="cx">             context=self.directoryService.searchContext_attendee)))
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethodreport_addressbook_querypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/method/report_addressbook_query.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -27,7 +27,6 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from txweb2 import responsecode
</span><del>-from txdav.xml import element as davxml
</del><span class="cx"> from txweb2.dav.http import ErrorResponse, MultiStatusResponse
</span><span class="cx"> from txweb2.dav.method.report import NumberOfMatchesWithinLimits
</span><span class="cx"> from txweb2.dav.util import joinURL
</span><span class="lines">@@ -37,9 +36,11 @@
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace, NResults
</span><span class="cx"> from twistedcaldav.method import report_common
</span><del>-from twistedcaldav.query import addressbookqueryfilter
</del><span class="cx"> 
</span><del>-from txdav.common.icommondatastore import ConcurrentModification
</del><ins>+from txdav.carddav.datastore.query.filter import Filter
+from txdav.common.icommondatastore import ConcurrentModification, \
+    IndexedSearchException
+from txdav.xml import element as davxml
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -62,7 +63,7 @@
</span><span class="cx">     responses = []
</span><span class="cx"> 
</span><span class="cx">     xmlfilter = addressbook_query.filter
</span><del>-    filter = addressbookqueryfilter.Filter(xmlfilter)
</del><ins>+    filter = Filter(xmlfilter)
</ins><span class="cx">     query = addressbook_query.props
</span><span class="cx">     limit = addressbook_query.limit
</span><span class="cx"> 
</span><span class="lines">@@ -209,7 +210,7 @@
</span><span class="cx">                                                         carddavxml.TextMatch.fromString(resource_name[:-4]),
</span><span class="cx">                                                         name=&quot;UID&quot;, # attributes
</span><span class="cx">                                                         ), ])
</span><del>-                            vCardFilter = addressbookqueryfilter.Filter(vCardFilter)
</del><ins>+                            vCardFilter = Filter(vCardFilter)
</ins><span class="cx"> 
</span><span class="cx">                             directoryAddressBookLock, limited[0] = (yield  directory.cacheVCardsForAddressBookQuery(vCardFilter, query, max_number_of_results[0]))
</span><span class="cx"> 
</span><span class="lines">@@ -230,11 +231,13 @@
</span><span class="cx"> 
</span><span class="cx">                     # Check for disabled access
</span><span class="cx">                     if filteredaces is not None:
</span><del>-                        # See whether the filter is valid for an index only query
-                        index_query_ok = addrresource.index().searchValid(filter)
-
-                        # Get list of children that match the search and have read access
-                        names = [name for name, ignore_uid in (yield addrresource.index().search(filter))] #@UnusedVariable
</del><ins>+                        index_query_ok = True
+                        try:
+                            # Get list of children that match the search and have read access
+                            names = [name for name, ignore_uid in (yield addrresource.search(filter))] #@UnusedVariable
+                        except IndexedSearchException:
+                            names = yield addrresource.listChildren()
+                            index_query_ok = False
</ins><span class="cx">                         if not names:
</span><span class="cx">                             return
</span><span class="cx"> 
</span><span class="lines">@@ -277,7 +280,7 @@
</span><span class="cx">                                                     carddavxml.TextMatch.fromString(resource_name[:-4]),
</span><span class="cx">                                                     name=&quot;UID&quot;, # attributes
</span><span class="cx">                                                     ), ])
</span><del>-                        vCardFilter = addressbookqueryfilter.Filter(vCardFilter)
</del><ins>+                        vCardFilter = Filter(vCardFilter)
</ins><span class="cx"> 
</span><span class="cx">                         yield  maybeDeferred(queryDirectoryBackedAddressBook, parent, vCardFilter)
</span><span class="cx">                         handled = True
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethodreport_calendar_querypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/method/report_calendar_query.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -20,8 +20,7 @@
</span><span class="cx"> 
</span><span class="cx"> __all__ = [&quot;report_urn_ietf_params_xml_ns_caldav_calendar_query&quot;]
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue, \
-    maybeDeferred
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from txweb2 import responsecode
</span><span class="lines">@@ -39,8 +38,8 @@
</span><span class="cx">     ConcurrentModification
</span><span class="cx"> from twistedcaldav.instance import TooManyInstancesError
</span><span class="cx"> from twistedcaldav.method import report_common
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.caldav.icalendarstore import TimeRangeLowerLimit, TimeRangeUpperLimit
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> 
</span><span class="lines">@@ -66,7 +65,7 @@
</span><span class="cx">     responses = []
</span><span class="cx"> 
</span><span class="cx">     xmlfilter = calendar_query.filter
</span><del>-    filter = calendarqueryfilter.Filter(xmlfilter)
</del><ins>+    filter = Filter(xmlfilter)
</ins><span class="cx">     props = calendar_query.props
</span><span class="cx"> 
</span><span class="cx">     assert props is not None
</span><span class="lines">@@ -190,13 +189,11 @@
</span><span class="cx">             if filteredaces is not None:
</span><span class="cx">                 index_query_ok = True
</span><span class="cx">                 try:
</span><del>-                    # Get list of children that match the search and have read
-                    # access
-                    records = yield maybeDeferred(calresource.index().indexedSearch, filter)
</del><ins>+                    # Get list of children that match the search and have read access
+                    names = [name for name, ignore_uid, ignore_type in (yield calresource.search(filter))]
</ins><span class="cx">                 except IndexedSearchException:
</span><del>-                    records = yield maybeDeferred(calresource.index().bruteForceSearch)
</del><ins>+                    names = yield calresource.listChildren()
</ins><span class="cx">                     index_query_ok = False
</span><del>-                names = [name for name, ignore_uid, ignore_type in records]
</del><span class="cx"> 
</span><span class="cx">                 if not names:
</span><span class="cx">                     returnValue(True)
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethodreport_commonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/method/report_common.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/method/report_common.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/method/report_common.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -36,17 +36,16 @@
</span><span class="cx"> except ImportError:
</span><span class="cx">     from md5 import new as md5
</span><span class="cx"> 
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred
</del><ins>+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> from twisted.python.failure import Failure
</span><ins>+
</ins><span class="cx"> from txweb2 import responsecode
</span><del>-
-from txdav.xml import element
</del><span class="cx"> from txweb2.dav.http import statusForFailure
</span><span class="cx"> from txweb2.dav.method.propfind import propertyName
</span><span class="cx"> from txweb2.dav.method.report import NumberOfMatchesWithinLimits
</span><span class="cx"> from txweb2.dav.method.report import max_number_of_matches
</span><span class="cx"> from txweb2.dav.resource import AccessDeniedError
</span><del>-from txweb2.http import HTTPError
</del><ins>+from txweb2.http import HTTPError, StatusResponse
</ins><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><span class="lines">@@ -65,9 +64,9 @@
</span><span class="cx"> from twistedcaldav.instance import InstanceList
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="cx"> 
</span><del>-from twistedcaldav.query import calendarqueryfilter
-
</del><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.common.icommondatastore import IndexedSearchException
</span><ins>+from txdav.xml import element
</ins><span class="cx"> 
</span><span class="cx"> from pycalendar.duration import Duration
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="lines">@@ -583,17 +582,18 @@
</span><span class="cx">                           name=&quot;VCALENDAR&quot;,
</span><span class="cx">                        )
</span><span class="cx">                   )
</span><del>-        filter = calendarqueryfilter.Filter(filter)
</del><ins>+        filter = Filter(filter)
</ins><span class="cx">         tzinfo = filter.settimezone(tz)
</span><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            resources = yield maybeDeferred(calresource.index().indexedSearch,
-                filter, useruid=useruid, fbtype=True
-            )
</del><ins>+            resources = yield calresource.search(filter, useruid=useruid, fbtype=True)
</ins><span class="cx">             if caching:
</span><span class="cx">                 yield FBCacheEntry.makeCacheEntry(calresource, useruid, cache_timerange, resources)
</span><span class="cx">         except IndexedSearchException:
</span><del>-            resources = yield maybeDeferred(calresource.index().bruteForceSearch)
</del><ins>+            raise HTTPError(StatusResponse(
+                responsecode.INTERNAL_SERVER_ERROR,
+                &quot;Failed freebusy query&quot;
+            ))
</ins><span class="cx"> 
</span><span class="cx">     else:
</span><span class="cx">         # Log extended item
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavmethodreport_multiget_commonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/method/report_multiget_common.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -23,9 +23,8 @@
</span><span class="cx"> from urllib import unquote
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><ins>+
</ins><span class="cx"> from txweb2 import responsecode
</span><del>-from txdav.xml import element as davxml
-from txdav.xml.base import dav_namespace
</del><span class="cx"> from txweb2.dav.http import ErrorResponse, MultiStatusResponse
</span><span class="cx"> from txweb2.dav.resource import AccessDeniedError
</span><span class="cx"> from txweb2.http import HTTPError, StatusResponse
</span><span class="lines">@@ -37,11 +36,14 @@
</span><span class="cx"> from twistedcaldav.carddavxml import carddav_namespace
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.method import report_common
</span><del>-from txdav.common.icommondatastore import ConcurrentModification
</del><span class="cx"> from twistedcaldav.method.report_common import COLLECTION_TYPE_CALENDAR, \
</span><span class="cx">     COLLECTION_TYPE_ADDRESSBOOK
</span><del>-from twistedcaldav.query import addressbookqueryfilter
</del><span class="cx"> 
</span><ins>+from txdav.carddav.datastore.query.filter import Filter
+from txdav.common.icommondatastore import ConcurrentModification
+from txdav.xml import element as davxml
+from txdav.xml.base import dav_namespace
+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -265,7 +267,7 @@
</span><span class="cx">                     returnValue(None)
</span><span class="cx"> 
</span><span class="cx">                 addressBookFilter = carddavxml.Filter(*vCardFilters)
</span><del>-                addressBookFilter = addressbookqueryfilter.Filter(addressBookFilter)
</del><ins>+                addressBookFilter = Filter(addressBookFilter)
</ins><span class="cx">                 if self.directory.cacheQuery:
</span><span class="cx">                     # add vcards to directory address book and run &quot;normal case&quot; below
</span><span class="cx">                     limit = config.DirectoryAddressBook.MaxQueryResults
</span><span class="lines">@@ -333,11 +335,11 @@
</span><span class="cx">                     parent = (yield child.locateParent(request, resource_uri))
</span><span class="cx"> 
</span><span class="cx">                     if collection_type == COLLECTION_TYPE_CALENDAR:
</span><del>-                        if not parent.isCalendarCollection() or not (yield parent.index().resourceExists(name)):
</del><ins>+                        if not parent.isCalendarCollection() or not (yield parent.resourceExists(name)):
</ins><span class="cx">                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                             continue
</span><span class="cx">                     elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
</span><del>-                        if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
</del><ins>+                        if not parent.isAddressBookCollection() or not (yield parent.resourceExists(name)):
</ins><span class="cx">                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                             continue
</span><span class="cx"> 
</span><span class="lines">@@ -367,11 +369,11 @@
</span><span class="cx">                     parent = (yield self.locateParent(request, resource_uri))
</span><span class="cx"> 
</span><span class="cx">                     if collection_type == COLLECTION_TYPE_CALENDAR:
</span><del>-                        if not parent.isPseudoCalendarCollection() or not (yield parent.index().resourceExists(name)):
</del><ins>+                        if not parent.isPseudoCalendarCollection() or not (yield parent.resourceExists(name)):
</ins><span class="cx">                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                             continue
</span><span class="cx">                     elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
</span><del>-                        if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
</del><ins>+                        if not parent.isAddressBookCollection() or not (yield parent.resourceExists(name)):
</ins><span class="cx">                             responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
</span><span class="cx">                             continue
</span><span class="cx">                     child = self
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/resource.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/resource.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/resource.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -2667,10 +2667,9 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _indexWhatChanged(self, revision, depth):
</span><span class="cx">         # The newstore implementation supports this directly
</span><del>-        changed, deleted = yield self._newStoreHome.resourceNamesSinceToken(
</del><ins>+        changed, deleted, notallowed = yield self._newStoreHome.resourceNamesSinceToken(
</ins><span class="cx">             revision, depth
</span><span class="cx">         )
</span><del>-        notallowed = []
</del><span class="cx"> 
</span><span class="cx">         # Need to insert some addition items on first sync
</span><span class="cx">         if revision == 0:
</span><span class="lines">@@ -2894,10 +2893,9 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _indexWhatChanged(self, revision, depth):
</span><span class="cx">         # The newstore implementation supports this directly
</span><del>-        changed, deleted = yield self._newStoreHome.resourceNamesSinceToken(
</del><ins>+        changed, deleted, notallowed = yield self._newStoreHome.resourceNamesSinceToken(
</ins><span class="cx">             revision, depth
</span><span class="cx">         )
</span><del>-        notallowed = []
</del><span class="cx"> 
</span><span class="cx">         # Need to insert some addition items on first sync
</span><span class="cx">         if revision == 0:
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavsharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/sharing.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/sharing.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/sharing.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -267,10 +267,10 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if self._newStoreObject.direct():
</span><span class="cx">             owner = self.principalForUID(self._newStoreObject.ownerHome().uid())
</span><ins>+            sharee = self.principalForUID(self._newStoreObject.viewerHome().uid())
</ins><span class="cx">             if owner.record.recordType == WikiDirectoryService.recordType_wikis:
</span><span class="cx">                 # Access level comes from what the wiki has granted to the
</span><span class="cx">                 # sharee
</span><del>-                sharee = self.principalForUID(self._newStoreObject.viewerHome().uid())
</del><span class="cx">                 userID = sharee.record.guid
</span><span class="cx">                 wikiID = owner.record.shortNames[0]
</span><span class="cx">                 access = (yield getWikiAccess(userID, wikiID))
</span><span class="lines">@@ -281,7 +281,12 @@
</span><span class="cx">                 else:
</span><span class="cx">                     returnValue(None)
</span><span class="cx">             else:
</span><del>-                returnValue(&quot;original&quot;)
</del><ins>+                # Check proxy access
+                proxy_mode = yield sharee.proxyMode(owner)
+                if proxy_mode == &quot;none&quot;:
+                    returnValue(&quot;original&quot;)
+                else:
+                    returnValue(&quot;read-write&quot; if proxy_mode == &quot;write&quot; else &quot;read-only&quot;)
</ins><span class="cx">         else:
</span><span class="cx">             # Invited shares use access mode from the invite
</span><span class="cx">             # Get the access for self
</span><span class="lines">@@ -318,7 +323,7 @@
</span><span class="cx">         sharee = self.principalForUID(self._newStoreObject.viewerHome().uid())
</span><span class="cx">         access = yield self._checkAccessControl()
</span><span class="cx"> 
</span><del>-        if access == &quot;original&quot;:
</del><ins>+        if access == &quot;original&quot; and not self._newStoreObject.ownerHome().external():
</ins><span class="cx">             original = (yield request.locateResource(self._share_url))
</span><span class="cx">             result = (yield original.accessControlList(request, *args, **kwargs))
</span><span class="cx">             returnValue(result)
</span><span class="lines">@@ -805,6 +810,12 @@
</span><span class="cx"> 
</span><span class="cx">         # Accept the share
</span><span class="cx">         shareeView = yield self._newStoreHome.acceptShare(inviteUID, summary)
</span><ins>+        if shareeView is None:
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (calendarserver_namespace, &quot;invalid-share&quot;),
+                &quot;Invite UID not valid&quot;,
+            ))
</ins><span class="cx"> 
</span><span class="cx">         # Return the URL of the shared collection
</span><span class="cx">         sharedAsURL = joinURL(self.url(), shareeView.shareName())
</span><span class="lines">@@ -820,7 +831,13 @@
</span><span class="cx">     def declineShare(self, request, inviteUID):
</span><span class="cx"> 
</span><span class="cx">         # Remove it if it is in the DB
</span><del>-        yield self._newStoreHome.declineShare(inviteUID)
</del><ins>+        result = yield self._newStoreHome.declineShare(inviteUID)
+        if not result:
+            raise HTTPError(ErrorResponse(
+                responsecode.FORBIDDEN,
+                (calendarserver_namespace, &quot;invalid-share&quot;),
+                &quot;Invite UID not valid&quot;,
+            ))
</ins><span class="cx">         returnValue(Response(code=responsecode.NO_CONTENT))
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/stdconfig.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -318,6 +318,13 @@
</span><span class="cx">                                     # upgrade.
</span><span class="cx"> 
</span><span class="cx">     #
</span><ins>+    # Work queue configuration information
+    #
+    &quot;WorkQueue&quot; : {
+        &quot;ampPort&quot;: 7654,            # Port used for hosts in a cluster to take to each other
+    },
+
+    #
</ins><span class="cx">     # Types of service provided
</span><span class="cx">     #
</span><span class="cx">     &quot;EnableCalDAV&quot;  : True, # Enable CalDAV service
</span><span class="lines">@@ -361,7 +368,7 @@
</span><span class="cx">     #
</span><span class="cx">     # Directory service
</span><span class="cx">     #
</span><del>-    #    A directory service provides information about principals (eg.
</del><ins>+    #    A directory service provides information about principals (e.g.
</ins><span class="cx">     #    users, groups, locations and resources) to the server.
</span><span class="cx">     #
</span><span class="cx">     &quot;DirectoryService&quot;: {
</span><span class="lines">@@ -825,8 +832,9 @@
</span><span class="cx">     &quot;Servers&quot; : {
</span><span class="cx">         &quot;Enabled&quot;: False,                   # Multiple servers enabled or not
</span><span class="cx">         &quot;ConfigFile&quot;: &quot;localservers.xml&quot;,   # File path for server information
</span><del>-        &quot;MaxClients&quot;: 5,                    # Pool size for connections to between servers
</del><ins>+        &quot;MaxClients&quot;: 5,                    # Pool size for connections between servers
</ins><span class="cx">         &quot;InboxName&quot;: &quot;podding&quot;,             # Name for top-level inbox resource
</span><ins>+        &quot;ConduitName&quot;: &quot;conduit&quot;,           # Name for top-level cross-pod resource
</ins><span class="cx">     },
</span><span class="cx"> 
</span><span class="cx">     #
</span><span class="lines">@@ -1067,8 +1075,11 @@
</span><span class="cx">         def _loadImport(childDict):
</span><span class="cx">             # Look for an import and read that one as the main config and merge the current one into that
</span><span class="cx">             if &quot;ImportConfig&quot; in childDict and childDict.ImportConfig:
</span><del>-                configRoot = os.path.join(childDict.ServerRoot, childDict.ConfigRoot)
-                path = _expandPath(fullServerPath(configRoot, childDict.ImportConfig))
</del><ins>+                if childDict.ImportConfig[0] != &quot;.&quot;:
+                    configRoot = os.path.join(childDict.ServerRoot, childDict.ConfigRoot)
+                    path = _expandPath(fullServerPath(configRoot, childDict.ImportConfig))
+                else:
+                    path = childDict.ImportConfig
</ins><span class="cx">                 if os.path.exists(path):
</span><span class="cx">                     importDict = ConfigDict(self._parseConfigFromFile(path))
</span><span class="cx">                     if importDict:
</span><span class="lines">@@ -1564,6 +1575,7 @@
</span><span class="cx">                         (direction,))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def _updateSharing(configDict, reloading=False):
</span><span class="cx">     #
</span><span class="cx">     # Sharing
</span><span class="lines">@@ -1574,6 +1586,7 @@
</span><span class="cx">         PerUserDataFilter.IGNORE_X_PROPERTIES.append(propertyName)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> def _updateServers(configDict, reloading=False):
</span><span class="cx">     from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers
</span><span class="cx">     if configDict.Servers.Enabled:
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/storebridge.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/storebridge.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -286,13 +286,6 @@
</span><span class="cx">         return self._parentResource
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def index(self):
-        &quot;&quot;&quot;
-        Retrieve the new-style index wrapper.
-        &quot;&quot;&quot;
-        return self._newStoreObject.retrieveOldIndex()
-
-
</del><span class="cx">     def exists(self):
</span><span class="cx">         # FIXME: tests
</span><span class="cx">         return self._newStoreObject is not None
</span><span class="lines">@@ -303,7 +296,6 @@
</span><span class="cx">         # The newstore implementation supports this directly
</span><span class="cx">         returnValue(
</span><span class="cx">             (yield self._newStoreObject.resourceNamesSinceToken(revision))
</span><del>-            + ([],)
</del><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -347,6 +339,18 @@
</span><span class="cx">         return self._newStoreObject.countObjectResources()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def resourceExists(self, name):
+        &quot;&quot;&quot;
+        Indicate whether a resource with the specified name exists.
+
+        @return: C{True} if it exists
+        @rtype: C{bool}
+        &quot;&quot;&quot;
+        allNames = yield self._newStoreObject.listObjectResources()
+        returnValue(name in allNames)
+
+
</ins><span class="cx">     def name(self):
</span><span class="cx">         return self._name
</span><span class="cx"> 
</span><span class="lines">@@ -462,6 +466,8 @@
</span><span class="cx">         if self.isShareeResource():
</span><span class="cx">             log.debug(&quot;Removing shared collection %s&quot; % (self,))
</span><span class="cx">             yield self.removeShareeResource(request)
</span><ins>+            # Re-initialize to get stuff setup again now we have no object
+            self._initializeWithHomeChild(None, self._parentResource)
</ins><span class="cx">             returnValue(NO_CONTENT)
</span><span class="cx"> 
</span><span class="cx">         log.debug(&quot;Deleting collection %s&quot; % (self,))
</span><span class="lines">@@ -488,11 +494,6 @@
</span><span class="cx"> 
</span><span class="cx">         # Now do normal delete
</span><span class="cx"> 
</span><del>-        # Handle sharing
-        wasShared = self.isShared()
-        if wasShared:
-            yield self.downgradeFromShare(request)
-
</del><span class="cx">         # Actually delete it.
</span><span class="cx">         yield self._newStoreObject.remove()
</span><span class="cx"> 
</span><span class="lines">@@ -968,6 +969,10 @@
</span><span class="cx">                     )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def search(self, filter, **kwargs):
+        return self._newStoreObject.search(filter, **kwargs)
+
+
</ins><span class="cx">     def notifierID(self):
</span><span class="cx">         return &quot;%s/%s&quot; % self._newStoreObject.notifierID()
</span><span class="cx"> 
</span><span class="lines">@@ -1121,7 +1126,7 @@
</span><span class="cx">         isowner = (yield self.isOwner(request))
</span><span class="cx">         accessPrincipal = (yield self.resourceOwnerPrincipal(request))
</span><span class="cx"> 
</span><del>-        for name, _ignore_uid, _ignore_type in (yield maybeDeferred(self.index().bruteForceSearch)):
</del><ins>+        for name in (yield self._newStoreObject.listObjectResources()):
</ins><span class="cx">             try:
</span><span class="cx">                 child = yield request.locateChildResource(self, name)
</span><span class="cx">             except TypeError:
</span><span class="lines">@@ -2421,6 +2426,7 @@
</span><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             response = (yield self.storeMove(request, destinationparent, destination.name()))
</span><ins>+            self._newStoreObject = None
</ins><span class="cx">             returnValue(response)
</span><span class="cx"> 
</span><span class="cx">         # Handle the various store errors
</span><span class="lines">@@ -3371,6 +3377,8 @@
</span><span class="cx">         if self.isShareeResource():
</span><span class="cx">             log.debug(&quot;Removing shared resource %s&quot; % (self,))
</span><span class="cx">             yield self.removeShareeResource(request)
</span><ins>+            # Re-initialize to get stuff setup again now we have no object
+            self._initializeWithObject(None, self._newStoreParent)
</ins><span class="cx">             returnValue(NO_CONTENT)
</span><span class="cx">         elif self._newStoreObject.isGroupForSharedAddressBook():
</span><span class="cx">             abCollectionResource = (yield request.locateResource(parentForURL(request.uri)))
</span><span class="lines">@@ -3673,7 +3681,6 @@
</span><span class="cx">         # The newstore implementation supports this directly
</span><span class="cx">         returnValue(
</span><span class="cx">             (yield self._newStoreNotifications.resourceNamesSinceToken(revision))
</span><del>-            + ([],)
</del><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavtesttest_calendarquerypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/test/test_calendarquery.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -27,7 +27,6 @@
</span><span class="cx"> from twistedcaldav import caldavxml
</span><span class="cx"> from twistedcaldav import ical
</span><span class="cx"> 
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase, SimpleStoreRequest
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="lines">@@ -36,6 +35,7 @@
</span><span class="cx"> from twistedcaldav.ical import Component
</span><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState
</span><span class="cx"> from twistedcaldav.directory.directory import DirectoryService
</span><ins>+from txdav.caldav.datastore.query.filter import TimeRange
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><span class="lines">@@ -167,7 +167,7 @@
</span><span class="cx">                             cal = property.calendar()
</span><span class="cx">                             instances = cal.expandTimeRanges(query_timerange.end)
</span><span class="cx">                             vevents = [x for x in cal.subcomponents() if x.name() == &quot;VEVENT&quot;]
</span><del>-                            if not calendarqueryfilter.TimeRange(query_timerange).matchinstance(vevents[0], instances):
</del><ins>+                            if not TimeRange(query_timerange).matchinstance(vevents[0], instances):
</ins><span class="cx">                                 self.fail(&quot;REPORT property %r returned calendar %s outside of request time range %r&quot;
</span><span class="cx">                                           % (property, property.calendar, query_timerange))
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavtesttest_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -30,6 +30,7 @@
</span><span class="cx"> from twistedcaldav.test.test_cache import StubResponseCacheResource
</span><span class="cx"> from twistedcaldav.test.util import norequest, StoreTestCase, SimpleStoreRequest
</span><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.test.util import buildDirectory
</ins><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_DIRECT
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="cx"> from txdav.xml.parser import WebDAVDocument
</span><span class="lines">@@ -738,6 +739,8 @@
</span><span class="cx">         home is at /.  Return the name of the newly shared calendar in the
</span><span class="cx">         sharee's home.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
+        self._sqlCalendarStore._directoryService = buildDirectory(homes=(&quot;wiki-testing&quot;,))
</ins><span class="cx">         wcreate = self._sqlCalendarStore.newTransaction(&quot;create wiki&quot;)
</span><span class="cx">         yield wcreate.calendarHomeWithUID(&quot;wiki-testing&quot;, create=True)
</span><span class="cx">         yield wcreate.commit()
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavtesttest_xmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/test/test_xml.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/test/test_xml.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/twistedcaldav/test/test_xml.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -17,12 +17,15 @@
</span><span class="cx"> import os
</span><span class="cx"> 
</span><span class="cx"> from twisted.trial.unittest import SkipTest
</span><ins>+
</ins><span class="cx"> from twistedcaldav.ical import Component
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> import twistedcaldav.test.util
</span><span class="cx"> from twistedcaldav.caldavxml import ComponentFilter, PropertyFilter, TextMatch, \
</span><span class="cx">     Filter, TimeRange
</span><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.filter import Filter as storeFilter
+from txdav.caldav.datastore.query.filter import ComponentFilter as storeComponentFilter
+
</ins><span class="cx"> class XML (twistedcaldav.test.util.TestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     XML tests
</span><span class="lines">@@ -46,7 +49,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 no = &quot;&quot;
</span><span class="cx"> 
</span><del>-            if has != calendarqueryfilter.ComponentFilter(
</del><ins>+            if has != storeComponentFilter(
</ins><span class="cx">                 ComponentFilter(
</span><span class="cx">                     ComponentFilter(
</span><span class="cx">                         name=component_name
</span><span class="lines">@@ -70,7 +73,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 no = &quot;&quot;
</span><span class="cx"> 
</span><del>-            if has != calendarqueryfilter.ComponentFilter(
</del><ins>+            if has != storeComponentFilter(
</ins><span class="cx">                 ComponentFilter(
</span><span class="cx">                     ComponentFilter(
</span><span class="cx">                         PropertyFilter(
</span><span class="lines">@@ -106,7 +109,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 no = &quot;&quot;
</span><span class="cx"> 
</span><del>-            if has != calendarqueryfilter.ComponentFilter(
</del><ins>+            if has != storeComponentFilter(
</ins><span class="cx">                 ComponentFilter(
</span><span class="cx">                     ComponentFilter(
</span><span class="cx">                         PropertyFilter(
</span><span class="lines">@@ -148,7 +151,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 no = &quot;&quot;
</span><span class="cx"> 
</span><del>-            if has != calendarqueryfilter.Filter(
</del><ins>+            if has != storeFilter(
</ins><span class="cx">                 Filter(
</span><span class="cx">                     ComponentFilter(
</span><span class="cx">                         ComponentFilter(
</span></span></pre></div>
<a id="CalendarServertrunktxdavbasedatastoresubpostgrespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/base/datastore/subpostgres.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/base/datastore/subpostgres.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/base/datastore/subpostgres.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -228,7 +228,7 @@
</span><span class="cx">                 # in /tmp and based on a hash of the data store directory
</span><span class="cx">                 digest = md5(dataStoreDirectory.path).hexdigest()
</span><span class="cx">                 socketDir = &quot;/tmp/ccs_postgres_&quot; + digest
</span><del>-                
</del><ins>+
</ins><span class="cx">             self.socketDir = CachingFilePath(socketDir)
</span><span class="cx">             self.host = self.socketDir.path
</span><span class="cx">             self.port = None
</span></span></pre></div>
<a id="CalendarServertrunktxdavbasedatastoreutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/base/datastore/util.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/base/datastore/util.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/base/datastore/util.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -98,6 +98,12 @@
</span><span class="cx">         return &quot;objectWithResourceID:%s:%s&quot; % (homeResourceID, resourceID)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    # Home child objects by external id
+
+    def keyForObjectWithExternalID(self, homeResourceID, externalID):
+        return &quot;objectWithExternalID:%s:%s&quot; % (homeResourceID, externalID)
+
+
</ins><span class="cx">     # Home metadata (Created/Modified)
</span><span class="cx"> 
</span><span class="cx">     def keyForHomeMetaData(self, homeResourceID):
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreindex_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/index_file.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/index_file.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/index_file.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -15,6 +15,7 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> CalDAV Index.
</span><span class="cx"> 
</span><span class="lines">@@ -43,12 +44,14 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.builder import buildExpression
+from txdav.caldav.datastore.query.filter import Filter
+from txdav.common.datastore.query.filegenerator import sqllitegenerator
</ins><span class="cx"> from txdav.common.icommondatastore import SyncTokenValidException, \
</span><span class="cx">     ReservationError, IndexedSearchException
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.dateops import pyCalendarTodatetime
</span><span class="cx"> from twistedcaldav.ical import Component
</span><del>-from twistedcaldav.query import calendarquery, calendarqueryfilter
</del><span class="cx"> from twistedcaldav.sql import AbstractSQLDatabase
</span><span class="cx"> from twistedcaldav.sql import db_prefix
</span><span class="cx"> from twistedcaldav.instance import InvalidOverriddenInstanceError
</span><span class="lines">@@ -275,6 +278,7 @@
</span><span class="cx"> 
</span><span class="cx">         changed = []
</span><span class="cx">         deleted = []
</span><ins>+        invalid = []
</ins><span class="cx">         for name, wasdeleted in results:
</span><span class="cx">             if name:
</span><span class="cx">                 if wasdeleted == 'Y':
</span><span class="lines">@@ -285,7 +289,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 raise SyncTokenValidException
</span><span class="cx"> 
</span><del>-        return changed, deleted,
</del><ins>+        return (changed, deleted, invalid)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def lastRevision(self):
</span><span class="lines">@@ -320,7 +324,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Make sure we have a proper Filter element and get the partial SQL
</span><span class="cx">         # statement to use.
</span><del>-        if isinstance(filter, calendarqueryfilter.Filter):
</del><ins>+        if isinstance(filter, Filter):
</ins><span class="cx">             if fbtype:
</span><span class="cx">                 # Lookup the useruid - try the empty (default) one if needed
</span><span class="cx">                 dbuseruid = self._db_value_for_sql(
</span><span class="lines">@@ -330,7 +334,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 dbuseruid = &quot;&quot;
</span><span class="cx"> 
</span><del>-            qualifiers = calendarquery.sqlcalendarquery(filter, None, dbuseruid, fbtype)
</del><ins>+            qualifiers = sqlcalendarquery(filter, None, dbuseruid, fbtype)
</ins><span class="cx">             if qualifiers is not None:
</span><span class="cx">                 # Determine how far we need to extend the current expansion of
</span><span class="cx">                 # events. If we have an open-ended time-range we will expand one
</span><span class="lines">@@ -437,6 +441,24 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def sqlcalendarquery(filter, calendarid=None, userid=None, freebusy=False):
+    &quot;&quot;&quot;
+    Convert the supplied calendar-query into a partial SQL statement.
+
+    @param filter: the L{Filter} for the calendar-query to convert.
+    @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+            Or return C{None} if it is not possible to create an SQL query to fully match the calendar-query.
+    &quot;&quot;&quot;
+    try:
+        expression = buildExpression(filter, sqllitegenerator.FIELDS)
+        sql = sqllitegenerator(expression, calendarid, userid, freebusy)
+        return sql.generate()
+    except ValueError:
+        return None
+
+
+
</ins><span class="cx"> class CalendarIndex (AbstractCalendarIndex):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Calendar index - abstract class for indexer that indexes calendar objects in a collection.
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequery__init__py"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/caldav/datastore/query/__init__.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/__init__.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,15 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
</del></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequery__init__pyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequery__init__py"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/caldav/datastore/query/__init__.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/__init__.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/query/__init__.py                                (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequerybuilderpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/caldav/datastore/query/builder.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/builder.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,227 +0,0 @@
</span><del>-##
-# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twistedcaldav.dateops import floatoffset, pyCalendarTodatetime
-
-from txdav.caldav.datastore.query.filter import ComponentFilter, PropertyFilter, TextMatch, TimeRange
-from txdav.common.datastore.query import expression
-
-
-&quot;&quot;&quot;
-SQL statement generator from query expressions.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;buildExpression&quot;,
-]
-
-
-
-# SQL Index column (field) names
-
-def buildExpression(filter, fields):
-    &quot;&quot;&quot;
-    Convert the supplied calendar-query into an expression tree.
-
-    @param filter: the L{Filter} for the calendar-query to convert.
-    @return: a L{baseExpression} for the expression tree.
-    &quot;&quot;&quot;
-
-    # Lets assume we have a valid filter from the outset.
-
-    # Top-level filter contains exactly one comp-filter element
-    assert filter.child is not None
-    vcalfilter = filter.child
-    assert isinstance(vcalfilter, ComponentFilter)
-    assert vcalfilter.filter_name == &quot;VCALENDAR&quot;
-
-    if len(vcalfilter.filters) &gt; 0:
-        # Determine logical expression grouping
-        logical = expression.andExpression if vcalfilter.filter_test == &quot;allof&quot; else expression.orExpression
-
-        # Only comp-filters are handled
-        for _ignore in [x for x in vcalfilter.filters if not isinstance(x, ComponentFilter)]:
-            raise ValueError
-
-        return compfilterListExpression(vcalfilter.filters, fields, logical)
-    else:
-        return expression.allExpression()
-
-
-
-def compfilterListExpression(compfilters, fields, logical):
-    &quot;&quot;&quot;
-    Create an expression for a list of comp-filter elements.
-
-    @param compfilters: the C{list} of L{ComponentFilter} elements.
-    @return: a L{baseExpression} for the expression tree.
-    &quot;&quot;&quot;
-
-    if len(compfilters) == 1:
-        return compfilterExpression(compfilters[0], fields)
-    else:
-        return logical([compfilterExpression(c, fields) for c in compfilters])
-
-
-
-def compfilterExpression(compfilter, fields):
-    &quot;&quot;&quot;
-    Create an expression for a single comp-filter element.
-
-    @param compfilter: the L{ComponentFilter} element.
-    @return: a L{baseExpression} for the expression tree.
-    &quot;&quot;&quot;
-
-    # Handle is-not-defined case
-    if not compfilter.defined:
-        # Test for TYPE != &lt;&lt;component-type name&gt;&gt;
-        return expression.isnotExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True)
-
-    # Determine logical expression grouping
-    logical = expression.andExpression if compfilter.filter_test == &quot;allof&quot; else expression.orExpression
-
-    expressions = []
-    if isinstance(compfilter.filter_name, str) or isinstance(compfilter.filter_name, unicode):
-        expressions.append(expression.isExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True))
-    else:
-        expressions.append(expression.inExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True))
-
-    # Handle time-range
-    if compfilter.qualifier and isinstance(compfilter.qualifier, TimeRange):
-        start, end, startfloat, endfloat = getTimerangeArguments(compfilter.qualifier)
-        expressions.append(expression.timerangeExpression(start, end, startfloat, endfloat))
-
-    # Handle properties - we can only do UID right now
-    props = []
-    for p in [x for x in compfilter.filters if isinstance(x, PropertyFilter)]:
-        props.append(propfilterExpression(p, fields))
-    if len(props) &gt; 1:
-        propsExpression = logical(props)
-    elif len(props) == 1:
-        propsExpression = props[0]
-    else:
-        propsExpression = None
-
-    # Handle embedded components - we do not right now as our Index does not handle them
-    comps = []
-    for _ignore in [x for x in compfilter.filters if isinstance(x, ComponentFilter)]:
-        raise ValueError
-    if len(comps) &gt; 1:
-        compsExpression = logical(comps)
-    elif len(comps) == 1:
-        compsExpression = comps[0]
-    else:
-        compsExpression = None
-
-    # Now build compound expression
-    if ((propsExpression is not None) and (compsExpression is not None)):
-        expressions.append(logical([propsExpression, compsExpression]))
-    elif propsExpression is not None:
-        expressions.append(propsExpression)
-    elif compsExpression is not None:
-        expressions.append(compsExpression)
-
-    # Now build return expression
-    return expression.andExpression(expressions)
-
-
-
-def propfilterExpression(propfilter, fields):
-    &quot;&quot;&quot;
-    Create an expression for a single prop-filter element.
-
-    @param propfilter: the L{PropertyFilter} element.
-    @return: a L{baseExpression} for the expression tree.
-    &quot;&quot;&quot;
-
-    # Only handle UID right now
-    if propfilter.filter_name != &quot;UID&quot;:
-        raise ValueError
-
-    # Handle is-not-defined case
-    if not propfilter.defined:
-        # Test for &lt;&lt;field&gt;&gt; != &quot;*&quot;
-        return expression.isExpression(fields[&quot;UID&quot;], &quot;&quot;, True)
-
-    # Determine logical expression grouping
-    logical = expression.andExpression if propfilter.filter_test == &quot;allof&quot; else expression.orExpression
-
-    # Handle time-range - we cannot do this with our Index right now
-    if propfilter.qualifier and isinstance(propfilter.qualifier, TimeRange):
-        raise ValueError
-
-    # Handle text-match
-    tm = None
-    if propfilter.qualifier and isinstance(propfilter.qualifier, TextMatch):
-        if propfilter.qualifier.match_type == &quot;equals&quot;:
-            tm = expression.isnotExpression if propfilter.qualifier.negate else expression.isExpression
-        elif propfilter.qualifier.match_type == &quot;contains&quot;:
-            tm = expression.notcontainsExpression if propfilter.qualifier.negate else expression.containsExpression
-        elif propfilter.qualifier.match_type == &quot;starts-with&quot;:
-            tm = expression.notstartswithExpression if propfilter.qualifier.negate else expression.startswithExpression
-        elif propfilter.qualifier.match_type == &quot;ends-with&quot;:
-            tm = expression.notendswithExpression if propfilter.qualifier.negate else expression.endswithExpression
-        tm = tm(fields[propfilter.filter_name], propfilter.qualifier.text, propfilter.qualifier.caseless)
-
-    # Handle embedded parameters - we do not right now as our Index does not handle them
-    params = []
-    for _ignore in propfilter.filters:
-        raise ValueError
-    if len(params) &gt; 1:
-        paramsExpression = logical(params)
-    elif len(params) == 1:
-        paramsExpression = params[0]
-    else:
-        paramsExpression = None
-
-    # Now build return expression
-    if (tm is not None) and (paramsExpression is not None):
-        return logical([tm, paramsExpression])
-    elif tm is not None:
-        return tm
-    elif paramsExpression is not None:
-        return paramsExpression
-    else:
-        return None
-
-
-
-def getTimerangeArguments(timerange):
-    &quot;&quot;&quot;
-    Get start/end and floating start/end (adjusted for timezone offset) values from the
-    supplied time-range test.
-
-    @param timerange: the L{TimeRange} used in the query.
-    @return: C{tuple} of C{str} for start, end, startfloat, endfloat
-    &quot;&quot;&quot;
-
-    # Start/end in UTC
-    start = timerange.start
-    end = timerange.end
-
-    # Get timezone
-    tzinfo = timerange.tzinfo
-
-    # Now force to floating UTC
-    startfloat = floatoffset(start, tzinfo) if start else None
-    endfloat = floatoffset(end, tzinfo) if end else None
-
-    return (
-        pyCalendarTodatetime(start) if start else None,
-        pyCalendarTodatetime(end) if end else None,
-        pyCalendarTodatetime(startfloat) if startfloat else None,
-        pyCalendarTodatetime(endfloat) if endfloat else None,
-    )
</del></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequerybuilderpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerybuilderpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/caldav/datastore/query/builder.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/builder.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/query/builder.py                                (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/builder.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,227 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twistedcaldav.dateops import floatoffset, pyCalendarTodatetime
+
+from txdav.caldav.datastore.query.filter import ComponentFilter, PropertyFilter, TextMatch, TimeRange
+from txdav.common.datastore.query import expression
+
+
+&quot;&quot;&quot;
+SQL statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;buildExpression&quot;,
+]
+
+
+
+# SQL Index column (field) names
+
+def buildExpression(filter, fields):
+    &quot;&quot;&quot;
+    Convert the supplied calendar-query into an expression tree.
+
+    @param filter: the L{Filter} for the calendar-query to convert.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    # Lets assume we have a valid filter from the outset.
+
+    # Top-level filter contains exactly one comp-filter element
+    assert filter.child is not None
+    vcalfilter = filter.child
+    assert isinstance(vcalfilter, ComponentFilter)
+    assert vcalfilter.filter_name == &quot;VCALENDAR&quot;
+
+    if len(vcalfilter.filters) &gt; 0:
+        # Determine logical expression grouping
+        logical = expression.andExpression if vcalfilter.filter_test == &quot;allof&quot; else expression.orExpression
+
+        # Only comp-filters are handled
+        for _ignore in [x for x in vcalfilter.filters if not isinstance(x, ComponentFilter)]:
+            raise ValueError
+
+        return compfilterListExpression(vcalfilter.filters, fields, logical)
+    else:
+        return expression.allExpression()
+
+
+
+def compfilterListExpression(compfilters, fields, logical):
+    &quot;&quot;&quot;
+    Create an expression for a list of comp-filter elements.
+
+    @param compfilters: the C{list} of L{ComponentFilter} elements.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    if len(compfilters) == 1:
+        return compfilterExpression(compfilters[0], fields)
+    else:
+        return logical([compfilterExpression(c, fields) for c in compfilters])
+
+
+
+def compfilterExpression(compfilter, fields):
+    &quot;&quot;&quot;
+    Create an expression for a single comp-filter element.
+
+    @param compfilter: the L{ComponentFilter} element.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    # Handle is-not-defined case
+    if not compfilter.defined:
+        # Test for TYPE != &lt;&lt;component-type name&gt;&gt;
+        return expression.isnotExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True)
+
+    # Determine logical expression grouping
+    logical = expression.andExpression if compfilter.filter_test == &quot;allof&quot; else expression.orExpression
+
+    expressions = []
+    if isinstance(compfilter.filter_name, str) or isinstance(compfilter.filter_name, unicode):
+        expressions.append(expression.isExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True))
+    else:
+        expressions.append(expression.inExpression(fields[&quot;TYPE&quot;], compfilter.filter_name, True))
+
+    # Handle time-range
+    if compfilter.qualifier and isinstance(compfilter.qualifier, TimeRange):
+        start, end, startfloat, endfloat = getTimerangeArguments(compfilter.qualifier)
+        expressions.append(expression.timerangeExpression(start, end, startfloat, endfloat))
+
+    # Handle properties - we can only do UID right now
+    props = []
+    for p in [x for x in compfilter.filters if isinstance(x, PropertyFilter)]:
+        props.append(propfilterExpression(p, fields))
+    if len(props) &gt; 1:
+        propsExpression = logical(props)
+    elif len(props) == 1:
+        propsExpression = props[0]
+    else:
+        propsExpression = None
+
+    # Handle embedded components - we do not right now as our Index does not handle them
+    comps = []
+    for _ignore in [x for x in compfilter.filters if isinstance(x, ComponentFilter)]:
+        raise ValueError
+    if len(comps) &gt; 1:
+        compsExpression = logical(comps)
+    elif len(comps) == 1:
+        compsExpression = comps[0]
+    else:
+        compsExpression = None
+
+    # Now build compound expression
+    if ((propsExpression is not None) and (compsExpression is not None)):
+        expressions.append(logical([propsExpression, compsExpression]))
+    elif propsExpression is not None:
+        expressions.append(propsExpression)
+    elif compsExpression is not None:
+        expressions.append(compsExpression)
+
+    # Now build return expression
+    return expression.andExpression(expressions)
+
+
+
+def propfilterExpression(propfilter, fields):
+    &quot;&quot;&quot;
+    Create an expression for a single prop-filter element.
+
+    @param propfilter: the L{PropertyFilter} element.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    # Only handle UID right now
+    if propfilter.filter_name != &quot;UID&quot;:
+        raise ValueError
+
+    # Handle is-not-defined case
+    if not propfilter.defined:
+        # Test for &lt;&lt;field&gt;&gt; != &quot;*&quot;
+        return expression.isExpression(fields[&quot;UID&quot;], &quot;&quot;, True)
+
+    # Determine logical expression grouping
+    logical = expression.andExpression if propfilter.filter_test == &quot;allof&quot; else expression.orExpression
+
+    # Handle time-range - we cannot do this with our Index right now
+    if propfilter.qualifier and isinstance(propfilter.qualifier, TimeRange):
+        raise ValueError
+
+    # Handle text-match
+    tm = None
+    if propfilter.qualifier and isinstance(propfilter.qualifier, TextMatch):
+        if propfilter.qualifier.match_type == &quot;equals&quot;:
+            tm = expression.isnotExpression if propfilter.qualifier.negate else expression.isExpression
+        elif propfilter.qualifier.match_type == &quot;contains&quot;:
+            tm = expression.notcontainsExpression if propfilter.qualifier.negate else expression.containsExpression
+        elif propfilter.qualifier.match_type == &quot;starts-with&quot;:
+            tm = expression.notstartswithExpression if propfilter.qualifier.negate else expression.startswithExpression
+        elif propfilter.qualifier.match_type == &quot;ends-with&quot;:
+            tm = expression.notendswithExpression if propfilter.qualifier.negate else expression.endswithExpression
+        tm = tm(fields[propfilter.filter_name], propfilter.qualifier.text, propfilter.qualifier.caseless)
+
+    # Handle embedded parameters - we do not right now as our Index does not handle them
+    params = []
+    for _ignore in propfilter.filters:
+        raise ValueError
+    if len(params) &gt; 1:
+        paramsExpression = logical(params)
+    elif len(params) == 1:
+        paramsExpression = params[0]
+    else:
+        paramsExpression = None
+
+    # Now build return expression
+    if (tm is not None) and (paramsExpression is not None):
+        return logical([tm, paramsExpression])
+    elif tm is not None:
+        return tm
+    elif paramsExpression is not None:
+        return paramsExpression
+    else:
+        return None
+
+
+
+def getTimerangeArguments(timerange):
+    &quot;&quot;&quot;
+    Get start/end and floating start/end (adjusted for timezone offset) values from the
+    supplied time-range test.
+
+    @param timerange: the L{TimeRange} used in the query.
+    @return: C{tuple} of C{str} for start, end, startfloat, endfloat
+    &quot;&quot;&quot;
+
+    # Start/end in UTC
+    start = timerange.start
+    end = timerange.end
+
+    # Get timezone
+    tzinfo = timerange.tzinfo
+
+    # Now force to floating UTC
+    startfloat = floatoffset(start, tzinfo) if start else None
+    endfloat = floatoffset(end, tzinfo) if end else None
+
+    return (
+        pyCalendarTodatetime(start) if start else None,
+        pyCalendarTodatetime(end) if end else None,
+        pyCalendarTodatetime(startfloat) if startfloat else None,
+        pyCalendarTodatetime(endfloat) if endfloat else None,
+    )
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequeryfilterpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/caldav/datastore/query/filter.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/filter.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,913 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-&quot;&quot;&quot;
-Object model of CALDAV:filter element used in an addressbook-query.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;Filter&quot;,
-]
-
-from twext.python.log import Logger
-
-from twistedcaldav.caldavxml import caldav_namespace, CalDAVTimeZoneElement
-from twistedcaldav.dateops import timeRangesOverlap
-from twistedcaldav.ical import Component, Property
-
-from pycalendar.datetime import DateTime
-from pycalendar.timezone import Timezone
-
-log = Logger()
-
-
-class FilterBase(object):
-    &quot;&quot;&quot;
-    Determines which matching components are returned.
-    &quot;&quot;&quot;
-
-    serialized_name = None
-    deserialize_names = {}
-
-    @classmethod
-    def serialize_register(cls, register):
-        cls.deserialize_names[register.serialized_name] = register
-
-
-    def __init__(self, xml_element):
-        pass
-
-
-    @classmethod
-    def deserialize(cls, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        obj = cls.deserialize_names[data[&quot;type&quot;]](None)
-        obj._deserialize(data)
-        return obj
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        pass
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        return {
-            &quot;type&quot;: self.serialized_name,
-        }
-
-
-    def match(self, item, access=None):
-        raise NotImplementedError
-
-
-    def valid(self, level=0):
-        raise NotImplementedError
-
-
-
-class Filter(FilterBase):
-    &quot;&quot;&quot;
-    Determines which matching components are returned.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;Filter&quot;
-
-    def __init__(self, xml_element):
-
-        super(Filter, self).__init__(xml_element)
-        if xml_element is None:
-            return
-
-        # One comp-filter element must be present
-        if len(xml_element.children) != 1 or xml_element.children[0].qname() != (caldav_namespace, &quot;comp-filter&quot;):
-            raise ValueError(&quot;Invalid CALDAV:filter element: %s&quot; % (xml_element,))
-
-        self.child = ComponentFilter(xml_element.children[0])
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        self.child = FilterBase.deserialize(data[&quot;child&quot;])
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        result = super(Filter, self).serialize()
-        result.update({
-            &quot;child&quot;: self.child.serialize(),
-        })
-        return result
-
-
-    def match(self, component, access=None):
-        &quot;&quot;&quot;
-        Returns True if the given calendar component matches this filter, False
-        otherwise.
-        &quot;&quot;&quot;
-
-        # We only care about certain access restrictions.
-        if access not in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
-            access = None
-
-        # We need to prepare ourselves for a time-range query by pre-calculating
-        # the set of instances up to the latest time-range limit. That way we can
-        # avoid having to do some form of recurrence expansion for each query sub-part.
-        maxend, isStartTime = self.getmaxtimerange()
-        if maxend:
-            if isStartTime:
-                if component.isRecurringUnbounded():
-                    # Unbounded recurrence is always within a start-only time-range
-                    instances = None
-                else:
-                    # Expand the instances up to infinity
-                    instances = component.expandTimeRanges(DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)), ignoreInvalidInstances=True)
-            else:
-                instances = component.expandTimeRanges(maxend, ignoreInvalidInstances=True)
-        else:
-            instances = None
-        self.child.setInstances(instances)
-
-        # &lt;filter&gt; contains exactly one &lt;comp-filter&gt;
-        return self.child.match(component, access)
-
-
-    def valid(self):
-        &quot;&quot;&quot;
-        Indicate whether this filter element's structure is valid wrt iCalendar
-        data object model.
-
-        @return: True if valid, False otherwise
-        &quot;&quot;&quot;
-
-        # Must have one child element for VCALENDAR
-        return self.child.valid(0)
-
-
-    def settimezone(self, tzelement):
-        &quot;&quot;&quot;
-        Set the default timezone to use with this query.
-        @param calendar: a L{Component} for the VCALENDAR containing the one
-            VTIMEZONE that we want
-        @return: the L{Timezone} derived from the VTIMEZONE or utc.
-        &quot;&quot;&quot;
-
-        if tzelement is None:
-            tz = None
-        elif isinstance(tzelement, CalDAVTimeZoneElement):
-            tz = tzelement.gettimezone()
-        elif isinstance(tzelement, Component):
-            tz = tzelement.gettimezone()
-        if tz is None:
-            tz = Timezone(utc=True)
-        self.child.settzinfo(tz)
-        return tz
-
-
-    def getmaxtimerange(self):
-        &quot;&quot;&quot;
-        Get the date farthest into the future in any time-range elements
-        &quot;&quot;&quot;
-
-        return self.child.getmaxtimerange(None, False)
-
-
-    def getmintimerange(self):
-        &quot;&quot;&quot;
-        Get the date farthest into the past in any time-range elements. That is either
-        the start date, or if start is not present, the end date.
-        &quot;&quot;&quot;
-
-        return self.child.getmintimerange(None, False)
-
-FilterBase.serialize_register(Filter)
-
-
-
-class FilterChildBase(FilterBase):
-    &quot;&quot;&quot;
-    CalDAV filter element.
-    &quot;&quot;&quot;
-
-    def __init__(self, xml_element):
-
-        super(FilterChildBase, self).__init__(xml_element)
-        if xml_element is None:
-            return
-
-        qualifier = None
-        filters = []
-
-        for child in xml_element.children:
-            qname = child.qname()
-
-            if qname in (
-                (caldav_namespace, &quot;is-not-defined&quot;),
-                (caldav_namespace, &quot;time-range&quot;),
-                (caldav_namespace, &quot;text-match&quot;),
-            ):
-                if qualifier is not None:
-                    raise ValueError(&quot;Only one of CalDAV:time-range, CalDAV:text-match allowed&quot;)
-
-                if qname == (caldav_namespace, &quot;is-not-defined&quot;):
-                    qualifier = IsNotDefined(child)
-                elif qname == (caldav_namespace, &quot;time-range&quot;):
-                    qualifier = TimeRange(child)
-                elif qname == (caldav_namespace, &quot;text-match&quot;):
-                    qualifier = TextMatch(child)
-
-            elif qname == (caldav_namespace, &quot;comp-filter&quot;):
-                filters.append(ComponentFilter(child))
-            elif qname == (caldav_namespace, &quot;prop-filter&quot;):
-                filters.append(PropertyFilter(child))
-            elif qname == (caldav_namespace, &quot;param-filter&quot;):
-                filters.append(ParameterFilter(child))
-            else:
-                raise ValueError(&quot;Unknown child element: %s&quot; % (qname,))
-
-        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
-            raise ValueError(&quot;No other tests allowed when CalDAV:is-not-defined is present&quot;)
-
-        self.qualifier = qualifier
-        self.filters = filters
-        self.filter_name = xml_element.attributes[&quot;name&quot;]
-        if isinstance(self.filter_name, unicode):
-            self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
-        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
-
-        filter_test = xml_element.attributes.get(&quot;test&quot;, &quot;allof&quot;)
-        if filter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
-            raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
-        self.filter_test = filter_test
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        self.qualifier = FilterBase.deserialize(data[&quot;qualifier&quot;]) if data[&quot;qualifier&quot;] else None
-        self.filters = [FilterBase.deserialize(filter) for filter in data[&quot;filters&quot;]]
-        self.filter_name = data[&quot;filter_name&quot;]
-        self.defined = data[&quot;defined&quot;]
-        self.filter_test = data[&quot;filter_test&quot;]
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        result = super(FilterChildBase, self).serialize()
-        result.update({
-            &quot;qualifier&quot;: self.qualifier.serialize() if self.qualifier else None,
-            &quot;filters&quot;: [filter.serialize() for filter in self.filters],
-            &quot;filter_name&quot;: self.filter_name,
-            &quot;defined&quot;: self.defined,
-            &quot;filter_test&quot;: self.filter_test,
-        })
-        return result
-
-
-    def match(self, item, access=None):
-        &quot;&quot;&quot;
-        Returns True if the given calendar item (either a component, property or parameter value)
-        matches this filter, False otherwise.
-        &quot;&quot;&quot;
-
-        # Always return True for the is-not-defined case as the result of this will
-        # be negated by the caller
-        if not self.defined:
-            return True
-
-        if self.qualifier and not self.qualifier.match(item, access):
-            return False
-
-        if len(self.filters) &gt; 0:
-            allof = self.filter_test == &quot;allof&quot;
-            for filter in self.filters:
-                if allof != filter._match(item, access):
-                    return not allof
-            return allof
-        else:
-            return True
-
-
-
-class ComponentFilter (FilterChildBase):
-    &quot;&quot;&quot;
-    Limits a search to only the chosen component types.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;ComponentFilter&quot;
-
-    def match(self, item, access):
-        &quot;&quot;&quot;
-        Returns True if the given calendar item (which is a component)
-        matches this filter, False otherwise.
-        This specialization uses the instance matching option of the time-range filter
-        to minimize instance expansion.
-        &quot;&quot;&quot;
-
-        # Always return True for the is-not-defined case as the result of this will
-        # be negated by the caller
-        if not self.defined:
-            return True
-
-        if self.qualifier and not self.qualifier.matchinstance(item, self.instances):
-            return False
-
-        if len(self.filters) &gt; 0:
-            allof = self.filter_test == &quot;allof&quot;
-            for filter in self.filters:
-                if allof != filter._match(item, access):
-                    return not allof
-            return allof
-        else:
-            return True
-
-
-    def _match(self, component, access):
-        # At least one subcomponent must match (or is-not-defined is set)
-        for subcomponent in component.subcomponents():
-            # If access restrictions are in force, restrict matching to specific components only.
-            # In particular do not match VALARM.
-            if access and subcomponent.name() not in (&quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VTIMEZONE&quot;,):
-                continue
-
-            # Try to match the component name
-            if isinstance(self.filter_name, str):
-                if subcomponent.name() != self.filter_name:
-                    continue
-            else:
-                if subcomponent.name() not in self.filter_name:
-                    continue
-            if self.match(subcomponent, access):
-                break
-        else:
-            return not self.defined
-        return self.defined
-
-
-    def setInstances(self, instances):
-        &quot;&quot;&quot;
-        Give the list of instances to each comp-filter element.
-        @param instances: the list of instances.
-        &quot;&quot;&quot;
-        self.instances = instances
-        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
-            compfilter.setInstances(instances)
-
-
-    def valid(self, level):
-        &quot;&quot;&quot;
-        Indicate whether this filter element's structure is valid wrt iCalendar
-        data object model.
-
-        @param level: the nesting level of this filter element, 0 being the top comp-filter.
-        @return:      True if valid, False otherwise
-        &quot;&quot;&quot;
-
-        # Check for time-range
-        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
-
-        if level == 0:
-            # Must have VCALENDAR at the top
-            if (self.filter_name != &quot;VCALENDAR&quot;) or timerange:
-                log.info(&quot;Top-level comp-filter must be VCALENDAR, instead: %s&quot; % (self.filter_name,))
-                return False
-        elif level == 1:
-            # Disallow VCALENDAR, VALARM, STANDARD, DAYLIGHT, AVAILABLE at the top, everything else is OK
-            if self.filter_name in (&quot;VCALENDAR&quot;, &quot;VALARM&quot;, &quot;STANDARD&quot;, &quot;DAYLIGHT&quot;, &quot;AVAILABLE&quot;):
-                log.info(&quot;comp-filter wrong component type: %s&quot; % (self.filter_name,))
-                return False
-
-            # time-range only on VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY
-            if timerange and self.filter_name not in (&quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;):
-                log.info(&quot;time-range cannot be used with component %s&quot; % (self.filter_name,))
-                return False
-        elif level == 2:
-            # Disallow VCALENDAR, VTIMEZONE, VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY at the top, everything else is OK
-            if (self.filter_name in (&quot;VCALENDAR&quot;, &quot;VTIMEZONE&quot;, &quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)):
-                log.info(&quot;comp-filter wrong sub-component type: %s&quot; % (self.filter_name,))
-                return False
-
-            # time-range only on VALARM, AVAILABLE
-            if timerange and self.filter_name not in (&quot;VALARM&quot;, &quot;AVAILABLE&quot;,):
-                log.info(&quot;time-range cannot be used with sub-component %s&quot; % (self.filter_name,))
-                return False
-        else:
-            # Disallow all standard iCal components anywhere else
-            if (self.filter_name in (&quot;VCALENDAR&quot;, &quot;VTIMEZONE&quot;, &quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VALARM&quot;, &quot;STANDARD&quot;, &quot;DAYLIGHT&quot;, &quot;AVAILABLE&quot;)) or timerange:
-                log.info(&quot;comp-filter wrong standard component type: %s&quot; % (self.filter_name,))
-                return False
-
-        # Test each property
-        for propfilter in [x for x in self.filters if isinstance(x, PropertyFilter)]:
-            if not propfilter.valid():
-                return False
-
-        # Test each component
-        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
-            if not compfilter.valid(level + 1):
-                return False
-
-        # Test the time-range
-        if timerange:
-            if not self.qualifier.valid():
-                return False
-
-        return True
-
-
-    def settzinfo(self, tzinfo):
-        &quot;&quot;&quot;
-        Set the default timezone to use with this query.
-        @param tzinfo: a L{Timezone} to use.
-        &quot;&quot;&quot;
-
-        # Give tzinfo to any TimeRange we have
-        if isinstance(self.qualifier, TimeRange):
-            self.qualifier.settzinfo(tzinfo)
-
-        # Pass down to sub components/properties
-        for x in self.filters:
-            x.settzinfo(tzinfo)
-
-
-    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
-        &quot;&quot;&quot;
-        Get the date farthest into the future in any time-range elements
-
-        @param currentMaximum: current future value to compare with
-        @type currentMaximum: L{DateTime}
-        &quot;&quot;&quot;
-
-        # Give tzinfo to any TimeRange we have
-        isStartTime = False
-        if isinstance(self.qualifier, TimeRange):
-            isStartTime = self.qualifier.end is None
-            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
-            if currentMaximum is None or currentMaximum &lt; compareWith:
-                currentMaximum = compareWith
-                currentIsStartTime = isStartTime
-
-        # Pass down to sub components/properties
-        for x in self.filters:
-            currentMaximum, currentIsStartTime = x.getmaxtimerange(currentMaximum, currentIsStartTime)
-
-        return currentMaximum, currentIsStartTime
-
-
-    def getmintimerange(self, currentMinimum, currentIsEndTime):
-        &quot;&quot;&quot;
-        Get the date farthest into the past in any time-range elements. That is either
-        the start date, or if start is not present, the end date.
-        &quot;&quot;&quot;
-
-        # Give tzinfo to any TimeRange we have
-        isEndTime = False
-        if isinstance(self.qualifier, TimeRange):
-            isEndTime = self.qualifier.start is None
-            compareWith = self.qualifier.end if isEndTime else self.qualifier.start
-            if currentMinimum is None or currentMinimum &gt; compareWith:
-                currentMinimum = compareWith
-                currentIsEndTime = isEndTime
-
-        # Pass down to sub components/properties
-        for x in self.filters:
-            currentMinimum, currentIsEndTime = x.getmintimerange(currentMinimum, currentIsEndTime)
-
-        return currentMinimum, currentIsEndTime
-
-FilterBase.serialize_register(ComponentFilter)
-
-
-
-class PropertyFilter (FilterChildBase):
-    &quot;&quot;&quot;
-    Limits a search to specific properties.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;PropertyFilter&quot;
-
-    def _match(self, component, access):
-        # When access restriction is in force, we need to only allow matches against the properties
-        # allowed by the access restriction level.
-        if access:
-            allowedProperties = Component.confidentialPropertiesMap.get(component.name(), None)
-            if allowedProperties and access == Component.ACCESS_RESTRICTED:
-                allowedProperties += Component.extraRestrictedProperties
-        else:
-            allowedProperties = None
-
-        # At least one property must match (or is-not-defined is set)
-        for property in component.properties():
-            # Apply access restrictions, if any.
-            if allowedProperties is not None and property.name().upper() not in allowedProperties:
-                continue
-            if property.name().upper() == self.filter_name.upper() and self.match(property, access):
-                break
-        else:
-            return not self.defined
-        return self.defined
-
-
-    def valid(self):
-        &quot;&quot;&quot;
-        Indicate whether this filter element's structure is valid wrt iCalendar
-        data object model.
-
-        @return:      True if valid, False otherwise
-        &quot;&quot;&quot;
-
-        # Check for time-range
-        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
-
-        # time-range only on COMPLETED, CREATED, DTSTAMP, LAST-MODIFIED
-        if timerange and self.filter_name.upper() not in (&quot;COMPLETED&quot;, &quot;CREATED&quot;, &quot;DTSTAMP&quot;, &quot;LAST-MODIFIED&quot;):
-            log.info(&quot;time-range cannot be used with property %s&quot; % (self.filter_name,))
-            return False
-
-        # Test the time-range
-        if timerange:
-            if not self.qualifier.valid():
-                return False
-
-        # No other tests
-        return True
-
-
-    def settzinfo(self, tzinfo):
-        &quot;&quot;&quot;
-        Set the default timezone to use with this query.
-        @param tzinfo: a L{Timezone} to use.
-        &quot;&quot;&quot;
-
-        # Give tzinfo to any TimeRange we have
-        if isinstance(self.qualifier, TimeRange):
-            self.qualifier.settzinfo(tzinfo)
-
-
-    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
-        &quot;&quot;&quot;
-        Get the date farthest into the future in any time-range elements
-
-        @param currentMaximum: current future value to compare with
-        @type currentMaximum: L{DateTime}
-        &quot;&quot;&quot;
-
-        # Give tzinfo to any TimeRange we have
-        isStartTime = False
-        if isinstance(self.qualifier, TimeRange):
-            isStartTime = self.qualifier.end is None
-            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
-            if currentMaximum is None or currentMaximum &lt; compareWith:
-                currentMaximum = compareWith
-                currentIsStartTime = isStartTime
-
-        return currentMaximum, currentIsStartTime
-
-
-    def getmintimerange(self, currentMinimum, currentIsEndTime):
-        &quot;&quot;&quot;
-        Get the date farthest into the past in any time-range elements. That is either
-        the start date, or if start is not present, the end date.
-        &quot;&quot;&quot;
-
-        # Give tzinfo to any TimeRange we have
-        isEndTime = False
-        if isinstance(self.qualifier, TimeRange):
-            isEndTime = self.qualifier.start is None
-            compareWith = self.qualifier.end if isEndTime else self.qualifier.start
-            if currentMinimum is None or currentMinimum &gt; compareWith:
-                currentMinimum = compareWith
-                currentIsEndTime = isEndTime
-
-        return currentMinimum, currentIsEndTime
-
-FilterBase.serialize_register(PropertyFilter)
-
-
-
-class ParameterFilter (FilterChildBase):
-    &quot;&quot;&quot;
-    Limits a search to specific parameters.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;ParameterFilter&quot;
-
-    def _match(self, property, access):
-
-        # At least one parameter must match (or is-not-defined is set)
-        result = not self.defined
-        for parameterName in property.parameterNames():
-            if parameterName.upper() == self.filter_name.upper() and self.match([property.parameterValue(parameterName)], access):
-                result = self.defined
-                break
-
-        return result
-
-FilterBase.serialize_register(ParameterFilter)
-
-
-
-class IsNotDefined (FilterBase):
-    &quot;&quot;&quot;
-    Specifies that the named iCalendar item does not exist.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;IsNotDefined&quot;
-
-    def match(self, component, access=None):
-        # Oddly, this needs always to return True so that it appears there is
-        # a match - but we then &quot;negate&quot; the result if is-not-defined is set.
-        # Actually this method should never be called as we special case the
-        # is-not-defined option.
-        return True
-
-FilterBase.serialize_register(IsNotDefined)
-
-
-class TextMatch (FilterBase):
-    &quot;&quot;&quot;
-    Specifies a substring match on a property or parameter value.
-    (CalDAV-access-09, section 9.6.4)
-    &quot;&quot;&quot;
-    serialized_name = &quot;TextMatch&quot;
-
-    def __init__(self, xml_element):
-
-        super(TextMatch, self).__init__(xml_element)
-        if xml_element is None:
-            return
-
-        self.text = str(xml_element)
-        if &quot;caseless&quot; in xml_element.attributes:
-            caseless = xml_element.attributes[&quot;caseless&quot;]
-            if caseless == &quot;yes&quot;:
-                self.caseless = True
-            elif caseless == &quot;no&quot;:
-                self.caseless = False
-        else:
-            self.caseless = True
-
-        if &quot;negate-condition&quot; in xml_element.attributes:
-            negate = xml_element.attributes[&quot;negate-condition&quot;]
-            if negate == &quot;yes&quot;:
-                self.negate = True
-            elif negate == &quot;no&quot;:
-                self.negate = False
-        else:
-            self.negate = False
-
-        if &quot;match-type&quot; in xml_element.attributes:
-            self.match_type = xml_element.attributes[&quot;match-type&quot;]
-            if self.match_type not in (
-                &quot;equals&quot;,
-                &quot;contains&quot;,
-                &quot;starts-with&quot;,
-                &quot;ends-with&quot;,
-            ):
-                self.match_type = &quot;contains&quot;
-        else:
-            self.match_type = &quot;contains&quot;
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        self.text = data[&quot;text&quot;]
-        self.caseless = data[&quot;caseless&quot;]
-        self.negate = data[&quot;negate&quot;]
-        self.match_type = data[&quot;match_type&quot;]
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        result = super(TextMatch, self).serialize()
-        result.update({
-            &quot;text&quot;: self.text,
-            &quot;caseless&quot;: self.caseless,
-            &quot;negate&quot;: self.negate,
-            &quot;match_type&quot;: self.match_type,
-        })
-        return result
-
-
-    def match(self, item, access):
-        &quot;&quot;&quot;
-        Match the text for the item.
-        If the item is a property, then match the property value,
-        otherwise it may be a list of parameter values - try to match anyone of those
-        &quot;&quot;&quot;
-        if item is None:
-            return False
-
-        if isinstance(item, Property):
-            values = [item.strvalue()]
-        else:
-            values = item
-
-        test = unicode(self.text, &quot;utf-8&quot;)
-        if self.caseless:
-            test = test.lower()
-
-        def _textCompare(s):
-            if self.caseless:
-                s = s.lower()
-
-            if self.match_type == &quot;equals&quot;:
-                return s == test
-            elif self.match_type == &quot;contains&quot;:
-                return s.find(test) != -1
-            elif self.match_type == &quot;starts-with&quot;:
-                return s.startswith(test)
-            elif self.match_type == &quot;ends-with&quot;:
-                return s.endswith(test)
-            else:
-                return False
-
-        for value in values:
-            # NB Its possible that we have a text list value which appears as a Python list,
-            # so we need to check for that and iterate over the list.
-            if isinstance(value, list):
-                for subvalue in value:
-                    if _textCompare(unicode(subvalue, &quot;utf-8&quot;)):
-                        return not self.negate
-            else:
-                if _textCompare(unicode(value, &quot;utf-8&quot;)):
-                    return not self.negate
-
-        return self.negate
-
-FilterBase.serialize_register(TextMatch)
-
-
-
-class TimeRange (FilterBase):
-    &quot;&quot;&quot;
-    Specifies a time for testing components against.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;TimeRange&quot;
-
-    def __init__(self, xml_element):
-
-        super(TimeRange, self).__init__(xml_element)
-        if xml_element is None:
-            return
-
-        # One of start or end must be present
-        if &quot;start&quot; not in xml_element.attributes and &quot;end&quot; not in xml_element.attributes:
-            raise ValueError(&quot;One of 'start' or 'end' must be present in CALDAV:time-range&quot;)
-
-        self.start = DateTime.parseText(xml_element.attributes[&quot;start&quot;]) if &quot;start&quot; in xml_element.attributes else None
-        self.end = DateTime.parseText(xml_element.attributes[&quot;end&quot;]) if &quot;end&quot; in xml_element.attributes else None
-        self.tzinfo = None
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        self.start = DateTime.parseText(data[&quot;start&quot;]) if data[&quot;start&quot;] else None
-        self.end = DateTime.parseText(data[&quot;end&quot;]) if data[&quot;end&quot;] else None
-        self.tzinfo = Timezone(tzid=data[&quot;tzinfo&quot;]) if data[&quot;tzinfo&quot;] else None
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        result = super(TimeRange, self).serialize()
-        result.update({
-            &quot;start&quot;: self.start.getText() if self.start else None,
-            &quot;end&quot;: self.end.getText() if self.end else None,
-            &quot;tzinfo&quot;: self.tzinfo.getTimezoneID() if self.tzinfo else None,
-        })
-        return result
-
-
-    def settzinfo(self, tzinfo):
-        &quot;&quot;&quot;
-        Set the default timezone to use with this query.
-        @param tzinfo: a L{Timezone} to use.
-        &quot;&quot;&quot;
-
-        # Give tzinfo to any TimeRange we have
-        self.tzinfo = tzinfo
-
-
-    def valid(self, level=0):
-        &quot;&quot;&quot;
-        Indicate whether the time-range is valid (must be date-time in UTC).
-
-        @return:      True if valid, False otherwise
-        &quot;&quot;&quot;
-
-        if self.start is not None and self.start.isDateOnly():
-            log.info(&quot;start attribute in &lt;time-range&gt; is not a date-time: %s&quot; % (self.start,))
-            return False
-        if self.end is not None and self.end.isDateOnly():
-            log.info(&quot;end attribute in &lt;time-range&gt; is not a date-time: %s&quot; % (self.end,))
-            return False
-        if self.start is not None and not self.start.utc():
-            log.info(&quot;start attribute in &lt;time-range&gt; is not UTC: %s&quot; % (self.start,))
-            return False
-        if self.end is not None and not self.end.utc():
-            log.info(&quot;end attribute in &lt;time-range&gt; is not UTC: %s&quot; % (self.end,))
-            return False
-
-        # No other tests
-        return True
-
-
-    def match(self, property, access=None):
-        &quot;&quot;&quot;
-        NB This is only called when doing a time-range match on a property.
-        &quot;&quot;&quot;
-        if property is None:
-            return False
-        else:
-            return property.containsTimeRange(self.start, self.end, self.tzinfo)
-
-
-    def matchinstance(self, component, instances):
-        &quot;&quot;&quot;
-        Test whether this time-range element causes a match to the specified component
-        using the specified set of instances to determine the expanded time ranges.
-        @param component: the L{Component} to test.
-        @param instances: the list of expanded instances.
-        @return: True if the time-range query matches, False otherwise.
-        &quot;&quot;&quot;
-        if component is None:
-            return False
-
-        assert instances is not None or self.end is None, &quot;Failure to expand instance for time-range filter: %r&quot; % (self,)
-
-        # Special case open-ended unbounded
-        if instances is None:
-            if component.getRecurrenceIDUTC() is None:
-                return True
-            else:
-                # See if the overridden component's start is past the start
-                start, _ignore_end = component.getEffectiveStartEnd()
-                if start is None:
-                    return True
-                else:
-                    return start &gt;= self.start
-
-        # Handle alarms as a special case
-        alarms = (component.name() == &quot;VALARM&quot;)
-        if alarms:
-            testcomponent = component._parent
-        else:
-            testcomponent = component
-
-        for key in instances:
-            instance = instances[key]
-
-            # First make sure components match
-            if not testcomponent.same(instance.component):
-                continue
-
-            if alarms:
-                # Get all the alarm triggers for this instance and test each one
-                triggers = instance.getAlarmTriggers()
-                for trigger in triggers:
-                    if timeRangesOverlap(trigger, None, self.start, self.end, self.tzinfo):
-                        return True
-            else:
-                # Regular instance overlap test
-                if timeRangesOverlap(instance.start, instance.end, self.start, self.end, self.tzinfo):
-                    return True
-
-        return False
-
-FilterBase.serialize_register(TimeRange)
</del></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequeryfilterpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequeryfilterpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/caldav/datastore/query/filter.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/filter.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/query/filter.py                                (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/filter.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,913 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+&quot;&quot;&quot;
+Object model of CALDAV:filter element used in an addressbook-query.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;Filter&quot;,
+]
+
+from twext.python.log import Logger
+
+from twistedcaldav.caldavxml import caldav_namespace, CalDAVTimeZoneElement
+from twistedcaldav.dateops import timeRangesOverlap
+from twistedcaldav.ical import Component, Property
+
+from pycalendar.datetime import DateTime
+from pycalendar.timezone import Timezone
+
+log = Logger()
+
+
+class FilterBase(object):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    serialized_name = None
+    deserialize_names = {}
+
+    @classmethod
+    def serialize_register(cls, register):
+        cls.deserialize_names[register.serialized_name] = register
+
+
+    def __init__(self, xml_element):
+        pass
+
+
+    @classmethod
+    def deserialize(cls, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        obj = cls.deserialize_names[data[&quot;type&quot;]](None)
+        obj._deserialize(data)
+        return obj
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        pass
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        return {
+            &quot;type&quot;: self.serialized_name,
+        }
+
+
+    def match(self, item, access=None):
+        raise NotImplementedError
+
+
+    def valid(self, level=0):
+        raise NotImplementedError
+
+
+
+class Filter(FilterBase):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;Filter&quot;
+
+    def __init__(self, xml_element):
+
+        super(Filter, self).__init__(xml_element)
+        if xml_element is None:
+            return
+
+        # One comp-filter element must be present
+        if len(xml_element.children) != 1 or xml_element.children[0].qname() != (caldav_namespace, &quot;comp-filter&quot;):
+            raise ValueError(&quot;Invalid CALDAV:filter element: %s&quot; % (xml_element,))
+
+        self.child = ComponentFilter(xml_element.children[0])
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        self.child = FilterBase.deserialize(data[&quot;child&quot;])
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        result = super(Filter, self).serialize()
+        result.update({
+            &quot;child&quot;: self.child.serialize(),
+        })
+        return result
+
+
+    def match(self, component, access=None):
+        &quot;&quot;&quot;
+        Returns True if the given calendar component matches this filter, False
+        otherwise.
+        &quot;&quot;&quot;
+
+        # We only care about certain access restrictions.
+        if access not in (Component.ACCESS_CONFIDENTIAL, Component.ACCESS_RESTRICTED):
+            access = None
+
+        # We need to prepare ourselves for a time-range query by pre-calculating
+        # the set of instances up to the latest time-range limit. That way we can
+        # avoid having to do some form of recurrence expansion for each query sub-part.
+        maxend, isStartTime = self.getmaxtimerange()
+        if maxend:
+            if isStartTime:
+                if component.isRecurringUnbounded():
+                    # Unbounded recurrence is always within a start-only time-range
+                    instances = None
+                else:
+                    # Expand the instances up to infinity
+                    instances = component.expandTimeRanges(DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)), ignoreInvalidInstances=True)
+            else:
+                instances = component.expandTimeRanges(maxend, ignoreInvalidInstances=True)
+        else:
+            instances = None
+        self.child.setInstances(instances)
+
+        # &lt;filter&gt; contains exactly one &lt;comp-filter&gt;
+        return self.child.match(component, access)
+
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+
+        @return: True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # Must have one child element for VCALENDAR
+        return self.child.valid(0)
+
+
+    def settimezone(self, tzelement):
+        &quot;&quot;&quot;
+        Set the default timezone to use with this query.
+        @param calendar: a L{Component} for the VCALENDAR containing the one
+            VTIMEZONE that we want
+        @return: the L{Timezone} derived from the VTIMEZONE or utc.
+        &quot;&quot;&quot;
+
+        if tzelement is None:
+            tz = None
+        elif isinstance(tzelement, CalDAVTimeZoneElement):
+            tz = tzelement.gettimezone()
+        elif isinstance(tzelement, Component):
+            tz = tzelement.gettimezone()
+        if tz is None:
+            tz = Timezone(utc=True)
+        self.child.settzinfo(tz)
+        return tz
+
+
+    def getmaxtimerange(self):
+        &quot;&quot;&quot;
+        Get the date farthest into the future in any time-range elements
+        &quot;&quot;&quot;
+
+        return self.child.getmaxtimerange(None, False)
+
+
+    def getmintimerange(self):
+        &quot;&quot;&quot;
+        Get the date farthest into the past in any time-range elements. That is either
+        the start date, or if start is not present, the end date.
+        &quot;&quot;&quot;
+
+        return self.child.getmintimerange(None, False)
+
+FilterBase.serialize_register(Filter)
+
+
+
+class FilterChildBase(FilterBase):
+    &quot;&quot;&quot;
+    CalDAV filter element.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(FilterChildBase, self).__init__(xml_element)
+        if xml_element is None:
+            return
+
+        qualifier = None
+        filters = []
+
+        for child in xml_element.children:
+            qname = child.qname()
+
+            if qname in (
+                (caldav_namespace, &quot;is-not-defined&quot;),
+                (caldav_namespace, &quot;time-range&quot;),
+                (caldav_namespace, &quot;text-match&quot;),
+            ):
+                if qualifier is not None:
+                    raise ValueError(&quot;Only one of CalDAV:time-range, CalDAV:text-match allowed&quot;)
+
+                if qname == (caldav_namespace, &quot;is-not-defined&quot;):
+                    qualifier = IsNotDefined(child)
+                elif qname == (caldav_namespace, &quot;time-range&quot;):
+                    qualifier = TimeRange(child)
+                elif qname == (caldav_namespace, &quot;text-match&quot;):
+                    qualifier = TextMatch(child)
+
+            elif qname == (caldav_namespace, &quot;comp-filter&quot;):
+                filters.append(ComponentFilter(child))
+            elif qname == (caldav_namespace, &quot;prop-filter&quot;):
+                filters.append(PropertyFilter(child))
+            elif qname == (caldav_namespace, &quot;param-filter&quot;):
+                filters.append(ParameterFilter(child))
+            else:
+                raise ValueError(&quot;Unknown child element: %s&quot; % (qname,))
+
+        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
+            raise ValueError(&quot;No other tests allowed when CalDAV:is-not-defined is present&quot;)
+
+        self.qualifier = qualifier
+        self.filters = filters
+        self.filter_name = xml_element.attributes[&quot;name&quot;]
+        if isinstance(self.filter_name, unicode):
+            self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
+        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
+
+        filter_test = xml_element.attributes.get(&quot;test&quot;, &quot;allof&quot;)
+        if filter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
+            raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
+        self.filter_test = filter_test
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        self.qualifier = FilterBase.deserialize(data[&quot;qualifier&quot;]) if data[&quot;qualifier&quot;] else None
+        self.filters = [FilterBase.deserialize(filter) for filter in data[&quot;filters&quot;]]
+        self.filter_name = data[&quot;filter_name&quot;]
+        self.defined = data[&quot;defined&quot;]
+        self.filter_test = data[&quot;filter_test&quot;]
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        result = super(FilterChildBase, self).serialize()
+        result.update({
+            &quot;qualifier&quot;: self.qualifier.serialize() if self.qualifier else None,
+            &quot;filters&quot;: [filter.serialize() for filter in self.filters],
+            &quot;filter_name&quot;: self.filter_name,
+            &quot;defined&quot;: self.defined,
+            &quot;filter_test&quot;: self.filter_test,
+        })
+        return result
+
+
+    def match(self, item, access=None):
+        &quot;&quot;&quot;
+        Returns True if the given calendar item (either a component, property or parameter value)
+        matches this filter, False otherwise.
+        &quot;&quot;&quot;
+
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined:
+            return True
+
+        if self.qualifier and not self.qualifier.match(item, access):
+            return False
+
+        if len(self.filters) &gt; 0:
+            allof = self.filter_test == &quot;allof&quot;
+            for filter in self.filters:
+                if allof != filter._match(item, access):
+                    return not allof
+            return allof
+        else:
+            return True
+
+
+
+class ComponentFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to only the chosen component types.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;ComponentFilter&quot;
+
+    def match(self, item, access):
+        &quot;&quot;&quot;
+        Returns True if the given calendar item (which is a component)
+        matches this filter, False otherwise.
+        This specialization uses the instance matching option of the time-range filter
+        to minimize instance expansion.
+        &quot;&quot;&quot;
+
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined:
+            return True
+
+        if self.qualifier and not self.qualifier.matchinstance(item, self.instances):
+            return False
+
+        if len(self.filters) &gt; 0:
+            allof = self.filter_test == &quot;allof&quot;
+            for filter in self.filters:
+                if allof != filter._match(item, access):
+                    return not allof
+            return allof
+        else:
+            return True
+
+
+    def _match(self, component, access):
+        # At least one subcomponent must match (or is-not-defined is set)
+        for subcomponent in component.subcomponents():
+            # If access restrictions are in force, restrict matching to specific components only.
+            # In particular do not match VALARM.
+            if access and subcomponent.name() not in (&quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VTIMEZONE&quot;,):
+                continue
+
+            # Try to match the component name
+            if isinstance(self.filter_name, str):
+                if subcomponent.name() != self.filter_name:
+                    continue
+            else:
+                if subcomponent.name() not in self.filter_name:
+                    continue
+            if self.match(subcomponent, access):
+                break
+        else:
+            return not self.defined
+        return self.defined
+
+
+    def setInstances(self, instances):
+        &quot;&quot;&quot;
+        Give the list of instances to each comp-filter element.
+        @param instances: the list of instances.
+        &quot;&quot;&quot;
+        self.instances = instances
+        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
+            compfilter.setInstances(instances)
+
+
+    def valid(self, level):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+
+        @param level: the nesting level of this filter element, 0 being the top comp-filter.
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # Check for time-range
+        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
+
+        if level == 0:
+            # Must have VCALENDAR at the top
+            if (self.filter_name != &quot;VCALENDAR&quot;) or timerange:
+                log.info(&quot;Top-level comp-filter must be VCALENDAR, instead: %s&quot; % (self.filter_name,))
+                return False
+        elif level == 1:
+            # Disallow VCALENDAR, VALARM, STANDARD, DAYLIGHT, AVAILABLE at the top, everything else is OK
+            if self.filter_name in (&quot;VCALENDAR&quot;, &quot;VALARM&quot;, &quot;STANDARD&quot;, &quot;DAYLIGHT&quot;, &quot;AVAILABLE&quot;):
+                log.info(&quot;comp-filter wrong component type: %s&quot; % (self.filter_name,))
+                return False
+
+            # time-range only on VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY
+            if timerange and self.filter_name not in (&quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;):
+                log.info(&quot;time-range cannot be used with component %s&quot; % (self.filter_name,))
+                return False
+        elif level == 2:
+            # Disallow VCALENDAR, VTIMEZONE, VEVENT, VTODO, VJOURNAL, VFREEBUSY, VAVAILABILITY at the top, everything else is OK
+            if (self.filter_name in (&quot;VCALENDAR&quot;, &quot;VTIMEZONE&quot;, &quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)):
+                log.info(&quot;comp-filter wrong sub-component type: %s&quot; % (self.filter_name,))
+                return False
+
+            # time-range only on VALARM, AVAILABLE
+            if timerange and self.filter_name not in (&quot;VALARM&quot;, &quot;AVAILABLE&quot;,):
+                log.info(&quot;time-range cannot be used with sub-component %s&quot; % (self.filter_name,))
+                return False
+        else:
+            # Disallow all standard iCal components anywhere else
+            if (self.filter_name in (&quot;VCALENDAR&quot;, &quot;VTIMEZONE&quot;, &quot;VEVENT&quot;, &quot;VTODO&quot;, &quot;VJOURNAL&quot;, &quot;VFREEBUSY&quot;, &quot;VALARM&quot;, &quot;STANDARD&quot;, &quot;DAYLIGHT&quot;, &quot;AVAILABLE&quot;)) or timerange:
+                log.info(&quot;comp-filter wrong standard component type: %s&quot; % (self.filter_name,))
+                return False
+
+        # Test each property
+        for propfilter in [x for x in self.filters if isinstance(x, PropertyFilter)]:
+            if not propfilter.valid():
+                return False
+
+        # Test each component
+        for compfilter in [x for x in self.filters if isinstance(x, ComponentFilter)]:
+            if not compfilter.valid(level + 1):
+                return False
+
+        # Test the time-range
+        if timerange:
+            if not self.qualifier.valid():
+                return False
+
+        return True
+
+
+    def settzinfo(self, tzinfo):
+        &quot;&quot;&quot;
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{Timezone} to use.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        if isinstance(self.qualifier, TimeRange):
+            self.qualifier.settzinfo(tzinfo)
+
+        # Pass down to sub components/properties
+        for x in self.filters:
+            x.settzinfo(tzinfo)
+
+
+    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
+        &quot;&quot;&quot;
+        Get the date farthest into the future in any time-range elements
+
+        @param currentMaximum: current future value to compare with
+        @type currentMaximum: L{DateTime}
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        isStartTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isStartTime = self.qualifier.end is None
+            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
+            if currentMaximum is None or currentMaximum &lt; compareWith:
+                currentMaximum = compareWith
+                currentIsStartTime = isStartTime
+
+        # Pass down to sub components/properties
+        for x in self.filters:
+            currentMaximum, currentIsStartTime = x.getmaxtimerange(currentMaximum, currentIsStartTime)
+
+        return currentMaximum, currentIsStartTime
+
+
+    def getmintimerange(self, currentMinimum, currentIsEndTime):
+        &quot;&quot;&quot;
+        Get the date farthest into the past in any time-range elements. That is either
+        the start date, or if start is not present, the end date.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        isEndTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isEndTime = self.qualifier.start is None
+            compareWith = self.qualifier.end if isEndTime else self.qualifier.start
+            if currentMinimum is None or currentMinimum &gt; compareWith:
+                currentMinimum = compareWith
+                currentIsEndTime = isEndTime
+
+        # Pass down to sub components/properties
+        for x in self.filters:
+            currentMinimum, currentIsEndTime = x.getmintimerange(currentMinimum, currentIsEndTime)
+
+        return currentMinimum, currentIsEndTime
+
+FilterBase.serialize_register(ComponentFilter)
+
+
+
+class PropertyFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific properties.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;PropertyFilter&quot;
+
+    def _match(self, component, access):
+        # When access restriction is in force, we need to only allow matches against the properties
+        # allowed by the access restriction level.
+        if access:
+            allowedProperties = Component.confidentialPropertiesMap.get(component.name(), None)
+            if allowedProperties and access == Component.ACCESS_RESTRICTED:
+                allowedProperties += Component.extraRestrictedProperties
+        else:
+            allowedProperties = None
+
+        # At least one property must match (or is-not-defined is set)
+        for property in component.properties():
+            # Apply access restrictions, if any.
+            if allowedProperties is not None and property.name().upper() not in allowedProperties:
+                continue
+            if property.name().upper() == self.filter_name.upper() and self.match(property, access):
+                break
+        else:
+            return not self.defined
+        return self.defined
+
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt iCalendar
+        data object model.
+
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # Check for time-range
+        timerange = self.qualifier and isinstance(self.qualifier, TimeRange)
+
+        # time-range only on COMPLETED, CREATED, DTSTAMP, LAST-MODIFIED
+        if timerange and self.filter_name.upper() not in (&quot;COMPLETED&quot;, &quot;CREATED&quot;, &quot;DTSTAMP&quot;, &quot;LAST-MODIFIED&quot;):
+            log.info(&quot;time-range cannot be used with property %s&quot; % (self.filter_name,))
+            return False
+
+        # Test the time-range
+        if timerange:
+            if not self.qualifier.valid():
+                return False
+
+        # No other tests
+        return True
+
+
+    def settzinfo(self, tzinfo):
+        &quot;&quot;&quot;
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{Timezone} to use.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        if isinstance(self.qualifier, TimeRange):
+            self.qualifier.settzinfo(tzinfo)
+
+
+    def getmaxtimerange(self, currentMaximum, currentIsStartTime):
+        &quot;&quot;&quot;
+        Get the date farthest into the future in any time-range elements
+
+        @param currentMaximum: current future value to compare with
+        @type currentMaximum: L{DateTime}
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        isStartTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isStartTime = self.qualifier.end is None
+            compareWith = self.qualifier.start if isStartTime else self.qualifier.end
+            if currentMaximum is None or currentMaximum &lt; compareWith:
+                currentMaximum = compareWith
+                currentIsStartTime = isStartTime
+
+        return currentMaximum, currentIsStartTime
+
+
+    def getmintimerange(self, currentMinimum, currentIsEndTime):
+        &quot;&quot;&quot;
+        Get the date farthest into the past in any time-range elements. That is either
+        the start date, or if start is not present, the end date.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        isEndTime = False
+        if isinstance(self.qualifier, TimeRange):
+            isEndTime = self.qualifier.start is None
+            compareWith = self.qualifier.end if isEndTime else self.qualifier.start
+            if currentMinimum is None or currentMinimum &gt; compareWith:
+                currentMinimum = compareWith
+                currentIsEndTime = isEndTime
+
+        return currentMinimum, currentIsEndTime
+
+FilterBase.serialize_register(PropertyFilter)
+
+
+
+class ParameterFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific parameters.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;ParameterFilter&quot;
+
+    def _match(self, property, access):
+
+        # At least one parameter must match (or is-not-defined is set)
+        result = not self.defined
+        for parameterName in property.parameterNames():
+            if parameterName.upper() == self.filter_name.upper() and self.match([property.parameterValue(parameterName)], access):
+                result = self.defined
+                break
+
+        return result
+
+FilterBase.serialize_register(ParameterFilter)
+
+
+
+class IsNotDefined (FilterBase):
+    &quot;&quot;&quot;
+    Specifies that the named iCalendar item does not exist.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;IsNotDefined&quot;
+
+    def match(self, component, access=None):
+        # Oddly, this needs always to return True so that it appears there is
+        # a match - but we then &quot;negate&quot; the result if is-not-defined is set.
+        # Actually this method should never be called as we special case the
+        # is-not-defined option.
+        return True
+
+FilterBase.serialize_register(IsNotDefined)
+
+
+class TextMatch (FilterBase):
+    &quot;&quot;&quot;
+    Specifies a substring match on a property or parameter value.
+    (CalDAV-access-09, section 9.6.4)
+    &quot;&quot;&quot;
+    serialized_name = &quot;TextMatch&quot;
+
+    def __init__(self, xml_element):
+
+        super(TextMatch, self).__init__(xml_element)
+        if xml_element is None:
+            return
+
+        self.text = str(xml_element)
+        if &quot;caseless&quot; in xml_element.attributes:
+            caseless = xml_element.attributes[&quot;caseless&quot;]
+            if caseless == &quot;yes&quot;:
+                self.caseless = True
+            elif caseless == &quot;no&quot;:
+                self.caseless = False
+        else:
+            self.caseless = True
+
+        if &quot;negate-condition&quot; in xml_element.attributes:
+            negate = xml_element.attributes[&quot;negate-condition&quot;]
+            if negate == &quot;yes&quot;:
+                self.negate = True
+            elif negate == &quot;no&quot;:
+                self.negate = False
+        else:
+            self.negate = False
+
+        if &quot;match-type&quot; in xml_element.attributes:
+            self.match_type = xml_element.attributes[&quot;match-type&quot;]
+            if self.match_type not in (
+                &quot;equals&quot;,
+                &quot;contains&quot;,
+                &quot;starts-with&quot;,
+                &quot;ends-with&quot;,
+            ):
+                self.match_type = &quot;contains&quot;
+        else:
+            self.match_type = &quot;contains&quot;
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        self.text = data[&quot;text&quot;]
+        self.caseless = data[&quot;caseless&quot;]
+        self.negate = data[&quot;negate&quot;]
+        self.match_type = data[&quot;match_type&quot;]
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        result = super(TextMatch, self).serialize()
+        result.update({
+            &quot;text&quot;: self.text,
+            &quot;caseless&quot;: self.caseless,
+            &quot;negate&quot;: self.negate,
+            &quot;match_type&quot;: self.match_type,
+        })
+        return result
+
+
+    def match(self, item, access):
+        &quot;&quot;&quot;
+        Match the text for the item.
+        If the item is a property, then match the property value,
+        otherwise it may be a list of parameter values - try to match anyone of those
+        &quot;&quot;&quot;
+        if item is None:
+            return False
+
+        if isinstance(item, Property):
+            values = [item.strvalue()]
+        else:
+            values = item
+
+        test = unicode(self.text, &quot;utf-8&quot;)
+        if self.caseless:
+            test = test.lower()
+
+        def _textCompare(s):
+            if self.caseless:
+                s = s.lower()
+
+            if self.match_type == &quot;equals&quot;:
+                return s == test
+            elif self.match_type == &quot;contains&quot;:
+                return s.find(test) != -1
+            elif self.match_type == &quot;starts-with&quot;:
+                return s.startswith(test)
+            elif self.match_type == &quot;ends-with&quot;:
+                return s.endswith(test)
+            else:
+                return False
+
+        for value in values:
+            # NB Its possible that we have a text list value which appears as a Python list,
+            # so we need to check for that and iterate over the list.
+            if isinstance(value, list):
+                for subvalue in value:
+                    if _textCompare(unicode(subvalue, &quot;utf-8&quot;)):
+                        return not self.negate
+            else:
+                if _textCompare(unicode(value, &quot;utf-8&quot;)):
+                    return not self.negate
+
+        return self.negate
+
+FilterBase.serialize_register(TextMatch)
+
+
+
+class TimeRange (FilterBase):
+    &quot;&quot;&quot;
+    Specifies a time for testing components against.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;TimeRange&quot;
+
+    def __init__(self, xml_element):
+
+        super(TimeRange, self).__init__(xml_element)
+        if xml_element is None:
+            return
+
+        # One of start or end must be present
+        if &quot;start&quot; not in xml_element.attributes and &quot;end&quot; not in xml_element.attributes:
+            raise ValueError(&quot;One of 'start' or 'end' must be present in CALDAV:time-range&quot;)
+
+        self.start = DateTime.parseText(xml_element.attributes[&quot;start&quot;]) if &quot;start&quot; in xml_element.attributes else None
+        self.end = DateTime.parseText(xml_element.attributes[&quot;end&quot;]) if &quot;end&quot; in xml_element.attributes else None
+        self.tzinfo = None
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        self.start = DateTime.parseText(data[&quot;start&quot;]) if data[&quot;start&quot;] else None
+        self.end = DateTime.parseText(data[&quot;end&quot;]) if data[&quot;end&quot;] else None
+        self.tzinfo = Timezone(tzid=data[&quot;tzinfo&quot;]) if data[&quot;tzinfo&quot;] else None
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        result = super(TimeRange, self).serialize()
+        result.update({
+            &quot;start&quot;: self.start.getText() if self.start else None,
+            &quot;end&quot;: self.end.getText() if self.end else None,
+            &quot;tzinfo&quot;: self.tzinfo.getTimezoneID() if self.tzinfo else None,
+        })
+        return result
+
+
+    def settzinfo(self, tzinfo):
+        &quot;&quot;&quot;
+        Set the default timezone to use with this query.
+        @param tzinfo: a L{Timezone} to use.
+        &quot;&quot;&quot;
+
+        # Give tzinfo to any TimeRange we have
+        self.tzinfo = tzinfo
+
+
+    def valid(self, level=0):
+        &quot;&quot;&quot;
+        Indicate whether the time-range is valid (must be date-time in UTC).
+
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        if self.start is not None and self.start.isDateOnly():
+            log.info(&quot;start attribute in &lt;time-range&gt; is not a date-time: %s&quot; % (self.start,))
+            return False
+        if self.end is not None and self.end.isDateOnly():
+            log.info(&quot;end attribute in &lt;time-range&gt; is not a date-time: %s&quot; % (self.end,))
+            return False
+        if self.start is not None and not self.start.utc():
+            log.info(&quot;start attribute in &lt;time-range&gt; is not UTC: %s&quot; % (self.start,))
+            return False
+        if self.end is not None and not self.end.utc():
+            log.info(&quot;end attribute in &lt;time-range&gt; is not UTC: %s&quot; % (self.end,))
+            return False
+
+        # No other tests
+        return True
+
+
+    def match(self, property, access=None):
+        &quot;&quot;&quot;
+        NB This is only called when doing a time-range match on a property.
+        &quot;&quot;&quot;
+        if property is None:
+            return False
+        else:
+            return property.containsTimeRange(self.start, self.end, self.tzinfo)
+
+
+    def matchinstance(self, component, instances):
+        &quot;&quot;&quot;
+        Test whether this time-range element causes a match to the specified component
+        using the specified set of instances to determine the expanded time ranges.
+        @param component: the L{Component} to test.
+        @param instances: the list of expanded instances.
+        @return: True if the time-range query matches, False otherwise.
+        &quot;&quot;&quot;
+        if component is None:
+            return False
+
+        assert instances is not None or self.end is None, &quot;Failure to expand instance for time-range filter: %r&quot; % (self,)
+
+        # Special case open-ended unbounded
+        if instances is None:
+            if component.getRecurrenceIDUTC() is None:
+                return True
+            else:
+                # See if the overridden component's start is past the start
+                start, _ignore_end = component.getEffectiveStartEnd()
+                if start is None:
+                    return True
+                else:
+                    return start &gt;= self.start
+
+        # Handle alarms as a special case
+        alarms = (component.name() == &quot;VALARM&quot;)
+        if alarms:
+            testcomponent = component._parent
+        else:
+            testcomponent = component
+
+        for key in instances:
+            instance = instances[key]
+
+            # First make sure components match
+            if not testcomponent.same(instance.component):
+                continue
+
+            if alarms:
+                # Get all the alarm triggers for this instance and test each one
+                triggers = instance.getAlarmTriggers()
+                for trigger in triggers:
+                    if timeRangesOverlap(trigger, None, self.start, self.end, self.tzinfo):
+                        return True
+            else:
+                # Regular instance overlap test
+                if timeRangesOverlap(instance.start, instance.end, self.start, self.end, self.tzinfo):
+                    return True
+
+        return False
+
+FilterBase.serialize_register(TimeRange)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequerygeneratorpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/caldav/datastore/query/generator.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/generator.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/generator.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,207 +0,0 @@
</span><del>-##
-# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twext.enterprise.dal.syntax import Select
-
-from txdav.common.datastore.query import expression
-from txdav.common.datastore.query.generator import SQLQueryGenerator
-from txdav.common.datastore.sql_tables import schema
-
-&quot;&quot;&quot;
-SQL statement generator from query expressions.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;CalDAVSQLQueryGenerator&quot;,
-]
-
-class CalDAVSQLQueryGenerator(SQLQueryGenerator):
-
-    _timerange = schema.TIME_RANGE
-    _transparency = schema.TRANSPARENCY
-
-    def __init__(self, expr, collection, whereid, userid=None, freebusy=False):
-        &quot;&quot;&quot;
-
-        @param expr: the query expression object model
-        @type expr: L{expression}
-        @param collection: the resource targeted by the query
-        @type collection: L{CommonHomeChild}
-        @param userid: user for whom query is being done - query will be scoped to that user's privileges and their transparency
-        @type userid: C{str}
-        @param freebusy: whether or not a freebusy query is being done - if it is, additional time range and transparency information is returned
-        @type freebusy: C{bool}
-        &quot;&quot;&quot;
-        super(CalDAVSQLQueryGenerator, self).__init__(expr, collection, whereid)
-        self.userid = userid if userid else &quot;&quot;
-        self.freebusy = freebusy
-        self.usedtimerange = False
-
-
-    def generate(self):
-        &quot;&quot;&quot;
-        Generate the actual SQL statement from the passed in expression tree.
-
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
-            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
-        &quot;&quot;&quot;
-
-        # Init state
-        self.arguments = {}
-        self.argcount = 0
-        obj = self.collection._objectSchema
-
-        columns = [obj.RESOURCE_NAME, obj.ICALENDAR_UID, obj.ICALENDAR_TYPE]
-        if self.freebusy:
-            columns.extend([
-                obj.ORGANIZER,
-                self._timerange.FLOATING,
-                self._timerange.START_DATE,
-                self._timerange.END_DATE,
-                self._timerange.FBTYPE,
-                self._timerange.TRANSPARENT,
-                self._transparency.TRANSPARENT,
-            ])
-
-        # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
-        if self.whereid:
-
-            test = expression.isExpression(obj.CALENDAR_RESOURCE_ID, self.whereid, True)
-
-            # Since timerange expression already have the calendar resource-id test in them, do not
-            # add the additional term to those. When the additional term is added, add it as the first
-            # component in the AND expression to hopefully get the DB to use its index first
-
-            # Top-level timerange expression already has calendar resource-id restriction in it
-            if isinstance(self.expression, expression.timerangeExpression):
-                pass
-
-            # Top-level OR - check each component
-            elif isinstance(self.expression, expression.orExpression):
-
-                def _hasTopLevelTimerange(testexpr):
-                    if isinstance(testexpr, expression.timerangeExpression):
-                        return True
-                    elif isinstance(testexpr, expression.andExpression):
-                        return any([isinstance(expr, expression.timerangeExpression) for expr in testexpr.expressions])
-                    else:
-                        return False
-
-                hasTimerange = any([_hasTopLevelTimerange(expr) for expr in self.expression.expressions])
-
-                if hasTimerange:
-                    # timerange expression forces a join on calendarid
-                    pass
-                else:
-                    # AND the whole thing with calendarid
-                    self.expression = test.andWith(self.expression)
-
-            # Top-level AND - only add additional expression if timerange not present
-            elif isinstance(self.expression, expression.andExpression):
-                hasTimerange = any([isinstance(expr, expression.timerangeExpression) for expr in self.expression.expressions])
-                if not hasTimerange:
-                    # AND the whole thing
-                    self.expression = test.andWith(self.expression)
-
-            # Just use the id test
-            elif isinstance(self.expression, expression.allExpression):
-                self.expression = test
-
-            # Just AND the entire thing
-            else:
-                self.expression = test.andWith(self.expression)
-
-        # Generate ' where ...' partial statement
-        where = self.generateExpression(self.expression)
-
-        if self.usedtimerange:
-            where = where.And(self._timerange.CALENDAR_OBJECT_RESOURCE_ID == obj.RESOURCE_ID).And(self._timerange.CALENDAR_RESOURCE_ID == self.whereid)
-
-        # Set of tables depends on use of timespan and fb use
-        if self.usedtimerange:
-            if self.freebusy:
-                tables = obj.join(
-                    self._timerange.join(
-                        self._transparency,
-                        on=(self._timerange.INSTANCE_ID == self._transparency.TIME_RANGE_INSTANCE_ID).And(self._transparency.USER_ID == self.userid),
-                        type=&quot;left outer&quot;
-                    ),
-                    type=&quot;,&quot;
-                )
-            else:
-                tables = obj.join(self._timerange, type=&quot;,&quot;)
-        else:
-            tables = obj
-
-        select = Select(
-            columns,
-            From=tables,
-            Where=where,
-            Distinct=True,
-        )
-
-        return select, self.arguments, self.usedtimerange
-
-
-    def generateExpression(self, expr):
-        &quot;&quot;&quot;
-        Generate an expression and all it's subexpressions.
-
-        @param expr: the L{baseExpression} derived class to write out.
-        &quot;&quot;&quot;
-
-        # Generate based on each type of expression we might encounter
-        partial = None
-
-        # time-range
-        if isinstance(expr, expression.timerangeExpression):
-            if expr.start and expr.end:
-                partial = (
-                    (self._timerange.FLOATING == False).And(self._timerange.START_DATE &lt; expr.end).And(self._timerange.END_DATE &gt; expr.start)
-                ).Or(
-                    (self._timerange.FLOATING == True).And(self._timerange.START_DATE &lt; expr.endfloat).And(self._timerange.END_DATE &gt; expr.startfloat)
-                )
-            elif expr.start and expr.end is None:
-                partial = (
-                    (self._timerange.FLOATING == False).And(self._timerange.END_DATE &gt; expr.start)
-                ).Or(
-                    (self._timerange.FLOATING == True).And(self._timerange.END_DATE &gt; expr.startfloat)
-                )
-            elif not expr.start and expr.end:
-                partial = (
-                    (self._timerange.FLOATING == False).And(self._timerange.START_DATE &lt; expr.end)
-                ).Or(
-                    (self._timerange.FLOATING == True).And(self._timerange.START_DATE &lt; expr.endfloat)
-                )
-            self.usedtimerange = True
-
-        else:
-            partial = super(CalDAVSQLQueryGenerator, self).generateExpression(expr)
-
-        return partial
-
-
-    def addArgument(self, arg):
-        &quot;&quot;&quot;
-
-        @param arg: the C{str} of the argument to add
-        &quot;&quot;&quot;
-
-        # Append argument to the list and add the appropriate substitution string to the output stream.
-        self.argcount += 1
-        argname = &quot;arg{}&quot;.format(self.argcount)
-        self.arguments[argname] = arg
-        return argname
</del></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequerygeneratorpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerygeneratorpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/caldav/datastore/query/generator.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/generator.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/query/generator.py                                (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/generator.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,207 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.enterprise.dal.syntax import Select
+
+from txdav.common.datastore.query import expression
+from txdav.common.datastore.query.generator import SQLQueryGenerator
+from txdav.common.datastore.sql_tables import schema
+
+&quot;&quot;&quot;
+SQL statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;CalDAVSQLQueryGenerator&quot;,
+]
+
+class CalDAVSQLQueryGenerator(SQLQueryGenerator):
+
+    _timerange = schema.TIME_RANGE
+    _transparency = schema.TRANSPARENCY
+
+    def __init__(self, expr, collection, whereid, userid=None, freebusy=False):
+        &quot;&quot;&quot;
+
+        @param expr: the query expression object model
+        @type expr: L{expression}
+        @param collection: the resource targeted by the query
+        @type collection: L{CommonHomeChild}
+        @param userid: user for whom query is being done - query will be scoped to that user's privileges and their transparency
+        @type userid: C{str}
+        @param freebusy: whether or not a freebusy query is being done - if it is, additional time range and transparency information is returned
+        @type freebusy: C{bool}
+        &quot;&quot;&quot;
+        super(CalDAVSQLQueryGenerator, self).__init__(expr, collection, whereid)
+        self.userid = userid if userid else &quot;&quot;
+        self.freebusy = freebusy
+        self.usedtimerange = False
+
+
+    def generate(self):
+        &quot;&quot;&quot;
+        Generate the actual SQL statement from the passed in expression tree.
+
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+        &quot;&quot;&quot;
+
+        # Init state
+        self.arguments = {}
+        self.argcount = 0
+        obj = self.collection._objectSchema
+
+        columns = [obj.RESOURCE_NAME, obj.ICALENDAR_UID, obj.ICALENDAR_TYPE]
+        if self.freebusy:
+            columns.extend([
+                obj.ORGANIZER,
+                self._timerange.FLOATING,
+                self._timerange.START_DATE,
+                self._timerange.END_DATE,
+                self._timerange.FBTYPE,
+                self._timerange.TRANSPARENT,
+                self._transparency.TRANSPARENT,
+            ])
+
+        # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
+        if self.whereid:
+
+            test = expression.isExpression(obj.CALENDAR_RESOURCE_ID, self.whereid, True)
+
+            # Since timerange expression already have the calendar resource-id test in them, do not
+            # add the additional term to those. When the additional term is added, add it as the first
+            # component in the AND expression to hopefully get the DB to use its index first
+
+            # Top-level timerange expression already has calendar resource-id restriction in it
+            if isinstance(self.expression, expression.timerangeExpression):
+                pass
+
+            # Top-level OR - check each component
+            elif isinstance(self.expression, expression.orExpression):
+
+                def _hasTopLevelTimerange(testexpr):
+                    if isinstance(testexpr, expression.timerangeExpression):
+                        return True
+                    elif isinstance(testexpr, expression.andExpression):
+                        return any([isinstance(expr, expression.timerangeExpression) for expr in testexpr.expressions])
+                    else:
+                        return False
+
+                hasTimerange = any([_hasTopLevelTimerange(expr) for expr in self.expression.expressions])
+
+                if hasTimerange:
+                    # timerange expression forces a join on calendarid
+                    pass
+                else:
+                    # AND the whole thing with calendarid
+                    self.expression = test.andWith(self.expression)
+
+            # Top-level AND - only add additional expression if timerange not present
+            elif isinstance(self.expression, expression.andExpression):
+                hasTimerange = any([isinstance(expr, expression.timerangeExpression) for expr in self.expression.expressions])
+                if not hasTimerange:
+                    # AND the whole thing
+                    self.expression = test.andWith(self.expression)
+
+            # Just use the id test
+            elif isinstance(self.expression, expression.allExpression):
+                self.expression = test
+
+            # Just AND the entire thing
+            else:
+                self.expression = test.andWith(self.expression)
+
+        # Generate ' where ...' partial statement
+        where = self.generateExpression(self.expression)
+
+        if self.usedtimerange:
+            where = where.And(self._timerange.CALENDAR_OBJECT_RESOURCE_ID == obj.RESOURCE_ID).And(self._timerange.CALENDAR_RESOURCE_ID == self.whereid)
+
+        # Set of tables depends on use of timespan and fb use
+        if self.usedtimerange:
+            if self.freebusy:
+                tables = obj.join(
+                    self._timerange.join(
+                        self._transparency,
+                        on=(self._timerange.INSTANCE_ID == self._transparency.TIME_RANGE_INSTANCE_ID).And(self._transparency.USER_ID == self.userid),
+                        type=&quot;left outer&quot;
+                    ),
+                    type=&quot;,&quot;
+                )
+            else:
+                tables = obj.join(self._timerange, type=&quot;,&quot;)
+        else:
+            tables = obj
+
+        select = Select(
+            columns,
+            From=tables,
+            Where=where,
+            Distinct=True,
+        )
+
+        return select, self.arguments, self.usedtimerange
+
+
+    def generateExpression(self, expr):
+        &quot;&quot;&quot;
+        Generate an expression and all it's subexpressions.
+
+        @param expr: the L{baseExpression} derived class to write out.
+        &quot;&quot;&quot;
+
+        # Generate based on each type of expression we might encounter
+        partial = None
+
+        # time-range
+        if isinstance(expr, expression.timerangeExpression):
+            if expr.start and expr.end:
+                partial = (
+                    (self._timerange.FLOATING == False).And(self._timerange.START_DATE &lt; expr.end).And(self._timerange.END_DATE &gt; expr.start)
+                ).Or(
+                    (self._timerange.FLOATING == True).And(self._timerange.START_DATE &lt; expr.endfloat).And(self._timerange.END_DATE &gt; expr.startfloat)
+                )
+            elif expr.start and expr.end is None:
+                partial = (
+                    (self._timerange.FLOATING == False).And(self._timerange.END_DATE &gt; expr.start)
+                ).Or(
+                    (self._timerange.FLOATING == True).And(self._timerange.END_DATE &gt; expr.startfloat)
+                )
+            elif not expr.start and expr.end:
+                partial = (
+                    (self._timerange.FLOATING == False).And(self._timerange.START_DATE &lt; expr.end)
+                ).Or(
+                    (self._timerange.FLOATING == True).And(self._timerange.START_DATE &lt; expr.endfloat)
+                )
+            self.usedtimerange = True
+
+        else:
+            partial = super(CalDAVSQLQueryGenerator, self).generateExpression(expr)
+
+        return partial
+
+
+    def addArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.argcount += 1
+        argname = &quot;arg{}&quot;.format(self.argcount)
+        self.arguments[argname] = arg
+        return argname
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequerytest__init__py"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/caldav/datastore/query/test/__init__.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/__init__.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/test/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,15 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
</del></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequerytest__init__pyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerytest__init__py"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/caldav/datastore/query/test/__init__.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/__init__.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/query/test/__init__.py                                (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/test/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequerytesttest_filterpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/caldav/datastore/query/test/test_filter.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/test/test_filter.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,435 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from pycalendar.timezone import Timezone
-
-from twext.enterprise.dal.syntax import SQLFragment, Parameter
-
-from twistedcaldav.test.util import TestCase
-from twistedcaldav import caldavxml
-from twistedcaldav.timezones import TimezoneCache
-
-from txdav.caldav.datastore.index_file import sqlcalendarquery
-from txdav.caldav.datastore.query.builder import buildExpression
-from txdav.caldav.datastore.query.filter import Filter, FilterBase, TimeRange, \
-    PropertyFilter, TextMatch
-from txdav.caldav.datastore.query.generator import CalDAVSQLQueryGenerator
-from txdav.common.datastore.sql_tables import schema
-
-from dateutil.tz import tzutc
-import datetime
-from twistedcaldav.ical import Component
-
-class TestQueryFilter(TestCase):
-
-    _objectSchema = schema.CALENDAR_OBJECT
-    _queryFields = {
-        &quot;UID&quot;: _objectSchema.UID,
-        &quot;TYPE&quot;: _objectSchema.ICALENDAR_TYPE,
-    }
-
-    def setUp(self):
-        super(TestQueryFilter, self).setUp()
-        TimezoneCache.create()
-
-
-    def test_query(self):
-        &quot;&quot;&quot;
-        Basic query test - no time range
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[caldavxml.ComponentFilter(
-                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
-                )],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-
-        expression = buildExpression(filter, self._queryFields)
-        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
-        select, args, usedtimerange = sql.generate()
-
-        self.assertEqual(select.toSQL(), SQLFragment(
-            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE in (?, ?, ?)&quot;,
-            [1234, Parameter('arg1', 3)]
-        ))
-        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
-        self.assertEqual(usedtimerange, False)
-
-
-    def test_query_timerange(self):
-        &quot;&quot;&quot;
-        Basic query test - with time range
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[caldavxml.ComponentFilter(
-                    *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, &quot;end&quot;:&quot;20060605T170000Z&quot;})],
-                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
-                )],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-
-        expression = buildExpression(filter, self._queryFields)
-        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
-        select, args, usedtimerange = sql.generate()
-
-        self.assertEqual(select.toSQL(), SQLFragment(
-            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ? or FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
-            [Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 13, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 1234]
-        ))
-        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
-        self.assertEqual(usedtimerange, True)
-
-
-    def test_query_freebusy(self):
-        &quot;&quot;&quot;
-        Basic query test - with time range
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[caldavxml.ComponentFilter(
-                    *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, &quot;end&quot;:&quot;20060605T170000Z&quot;})],
-                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
-                )],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-
-        expression = buildExpression(filter, self._queryFields)
-        sql = CalDAVSQLQueryGenerator(expression, self, 1234, &quot;user01&quot;, True)
-        select, args, usedtimerange = sql.generate()
-
-        self.assertEqual(select.toSQL(), SQLFragment(
-            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE, ORGANIZER, FLOATING, START_DATE, END_DATE, FBTYPE, TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT from CALENDAR_OBJECT, TIME_RANGE left outer join TRANSPARENCY on INSTANCE_ID = TIME_RANGE_INSTANCE_ID and USER_ID = ? where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ? or FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
-            ['user01', Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 13, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 1234]
-        ))
-        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
-        self.assertEqual(usedtimerange, True)
-
-
-    def test_query_not_extended(self):
-        &quot;&quot;&quot;
-        Query test - two terms not anyof
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[
-                    caldavxml.ComponentFilter(
-                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
-                    ),
-                    caldavxml.ComponentFilter(
-                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
-                    ),
-                ],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-
-        expression = buildExpression(filter, self._queryFields)
-        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
-        select, args, usedtimerange = sql.generate()
-
-        self.assertEqual(select.toSQL(), SQLFragment(
-            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE = ? and ICALENDAR_TYPE = ?&quot;,
-            [1234, &quot;VEVENT&quot;, &quot;VTODO&quot;]
-        ))
-        self.assertEqual(args, {})
-        self.assertEqual(usedtimerange, False)
-
-
-    def test_query_extended(self):
-        &quot;&quot;&quot;
-        Extended query test - two terms with anyof
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[
-                    caldavxml.ComponentFilter(
-                        *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, })],
-                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
-                    ),
-                    caldavxml.ComponentFilter(
-                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
-                    ),
-                ],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;, &quot;test&quot;: &quot;anyof&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-
-        expression = buildExpression(filter, self._queryFields)
-        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
-        select, args, usedtimerange = sql.generate()
-
-        self.assertEqual(select.toSQL(), SQLFragment(
-            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where (ICALENDAR_TYPE = ? and (FLOATING = ? and END_DATE &gt; ? or FLOATING = ? and END_DATE &gt; ?) or ICALENDAR_TYPE = ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
-            ['VEVENT', False, datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 'VTODO', 1234]
-        ))
-        self.assertEqual(args, {})
-        self.assertEqual(usedtimerange, True)
-
-
-    def test_sqllite_query(self):
-        &quot;&quot;&quot;
-        Basic query test - single term.
-        Only UID can be queried via sql.
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[caldavxml.ComponentFilter(
-                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
-                )],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        sql, args = sqlcalendarquery(filter, 1234)
-
-        self.assertTrue(sql.find(&quot;RESOURCE&quot;) != -1)
-        self.assertTrue(sql.find(&quot;TIMESPAN&quot;) == -1)
-        self.assertTrue(sql.find(&quot;TRANSPARENCY&quot;) == -1)
-        self.assertTrue(&quot;VEVENT&quot; in args)
-
-
-
-class TestQueryFilterSerialize(TestCase):
-
-    def setUp(self):
-        super(TestQueryFilterSerialize, self).setUp()
-        TimezoneCache.create()
-
-
-    def test_query(self):
-        &quot;&quot;&quot;
-        Basic query test - no time range
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[caldavxml.ComponentFilter(
-                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
-                )],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-        j = filter.serialize()
-        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
-
-        f = FilterBase.deserialize(j)
-        self.assertTrue(isinstance(f, Filter))
-
-
-    def test_timerange_query(self):
-        &quot;&quot;&quot;
-        Basic query test with time range
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[caldavxml.ComponentFilter(
-                    *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, &quot;end&quot;:&quot;20060605T170000Z&quot;})],
-                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
-                )],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-        j = filter.serialize()
-        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
-
-        f = FilterBase.deserialize(j)
-        self.assertTrue(isinstance(f, Filter))
-        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
-        self.assertTrue(isinstance(f.child.filters[0].qualifier.tzinfo, Timezone))
-        self.assertEqual(f.child.filters[0].qualifier.tzinfo.getTimezoneID(), &quot;America/New_York&quot;)
-
-
-    def test_query_not_extended(self):
-        &quot;&quot;&quot;
-        Basic query test with time range
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[
-                    caldavxml.ComponentFilter(
-                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
-                    ),
-                    caldavxml.ComponentFilter(
-                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
-                    ),
-                ],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-        j = filter.serialize()
-        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
-
-        f = FilterBase.deserialize(j)
-        self.assertTrue(isinstance(f, Filter))
-        self.assertEqual(len(f.child.filters), 2)
-
-
-    def test_query_extended(self):
-        &quot;&quot;&quot;
-        Basic query test with time range
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[
-                    caldavxml.ComponentFilter(
-                        *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, })],
-                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
-                    ),
-                    caldavxml.ComponentFilter(
-                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
-                    ),
-                ],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;, &quot;test&quot;: &quot;anyof&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-        j = filter.serialize()
-        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
-
-        f = FilterBase.deserialize(j)
-        self.assertTrue(isinstance(f, Filter))
-        self.assertEqual(len(f.child.filters), 2)
-        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
-
-
-    def test_query_text(self):
-        &quot;&quot;&quot;
-        Basic query test with time range
-        &quot;&quot;&quot;
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[
-                    caldavxml.ComponentFilter(
-                        caldavxml.PropertyFilter(
-                            caldavxml.TextMatch.fromString(&quot;1234&quot;, False),
-                            name=&quot;UID&quot;,
-                        ),
-                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
-                    ),
-                ],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;, &quot;test&quot;: &quot;anyof&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-        j = filter.serialize()
-        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
-
-        f = FilterBase.deserialize(j)
-        self.assertTrue(isinstance(f, Filter))
-        self.assertTrue(isinstance(f.child.filters[0].filters[0], PropertyFilter))
-        self.assertTrue(isinstance(f.child.filters[0].filters[0].qualifier, TextMatch))
-        self.assertEqual(f.child.filters[0].filters[0].qualifier.text, &quot;1234&quot;)
-
-
-
-class TestQueryFilterMatch(TestCase):
-
-    def setUp(self):
-        super(TestQueryFilterMatch, self).setUp()
-        TimezoneCache.create()
-
-
-    def test_vlarm_undefined(self):
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[caldavxml.ComponentFilter(
-                    *[caldavxml.ComponentFilter(
-                        caldavxml.IsNotDefined(),
-                        **{&quot;name&quot;:&quot;VALARM&quot;}
-                    )],
-                    **{&quot;name&quot;:&quot;VEVENT&quot;}
-                )],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
-
-        self.assertFalse(filter.match(
-            Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-PRODID:-//Example Inc.//Example Calendar//EN
-VERSION:2.0
-BEGIN:VTIMEZONE
-LAST-MODIFIED:20040110T032845Z
-TZID:US/Eastern
-BEGIN:DAYLIGHT
-DTSTART:20000404T020000
-RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
-TZNAME:EDT
-TZOFFSETFROM:-0500
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20001026T020000
-RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
-TZNAME:EST
-TZOFFSETFROM:-0400
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-DTSTAMP:20051222T210412Z
-CREATED:20060102T150000Z
-DTSTART;TZID=US/Eastern:20130102T100000
-DURATION:PT1H
-RRULE:FREQ=DAILY;COUNT=5
-SUMMARY:event 5
-UID:945113826375CBB89184DC36@ninevah.local
-CATEGORIES:cool,hot
-CATEGORIES:warm
-BEGIN:VALARM
-ACTION:AUDIO
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;
-        )))
</del></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastorequerytesttest_filterpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastorequerytesttest_filterpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/caldav/datastore/query/test/test_filter.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/query/test/test_filter.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/query/test/test_filter.py                                (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/query/test/test_filter.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,435 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from pycalendar.timezone import Timezone
+
+from twext.enterprise.dal.syntax import SQLFragment, Parameter
+
+from twistedcaldav.test.util import TestCase
+from twistedcaldav import caldavxml
+from twistedcaldav.timezones import TimezoneCache
+
+from txdav.caldav.datastore.index_file import sqlcalendarquery
+from txdav.caldav.datastore.query.builder import buildExpression
+from txdav.caldav.datastore.query.filter import Filter, FilterBase, TimeRange, \
+    PropertyFilter, TextMatch
+from txdav.caldav.datastore.query.generator import CalDAVSQLQueryGenerator
+from txdav.common.datastore.sql_tables import schema
+
+from dateutil.tz import tzutc
+import datetime
+from twistedcaldav.ical import Component
+
+class TestQueryFilter(TestCase):
+
+    _objectSchema = schema.CALENDAR_OBJECT
+    _queryFields = {
+        &quot;UID&quot;: _objectSchema.UID,
+        &quot;TYPE&quot;: _objectSchema.ICALENDAR_TYPE,
+    }
+
+    def setUp(self):
+        super(TestQueryFilter, self).setUp()
+        TimezoneCache.create()
+
+
+    def test_query(self):
+        &quot;&quot;&quot;
+        Basic query test - no time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE in (?, ?, ?)&quot;,
+            [1234, Parameter('arg1', 3)]
+        ))
+        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
+        self.assertEqual(usedtimerange, False)
+
+
+    def test_query_timerange(self):
+        &quot;&quot;&quot;
+        Basic query test - with time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, &quot;end&quot;:&quot;20060605T170000Z&quot;})],
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ? or FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
+            [Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 13, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 1234]
+        ))
+        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
+        self.assertEqual(usedtimerange, True)
+
+
+    def test_query_freebusy(self):
+        &quot;&quot;&quot;
+        Basic query test - with time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, &quot;end&quot;:&quot;20060605T170000Z&quot;})],
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234, &quot;user01&quot;, True)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE, ORGANIZER, FLOATING, START_DATE, END_DATE, FBTYPE, TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT from CALENDAR_OBJECT, TIME_RANGE left outer join TRANSPARENCY on INSTANCE_ID = TIME_RANGE_INSTANCE_ID and USER_ID = ? where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ? or FLOATING = ? and START_DATE &lt; ? and END_DATE &gt; ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
+            ['user01', Parameter('arg1', 3), False, datetime.datetime(2006, 6, 5, 17, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 13, 0, tzinfo=tzutc()), datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 1234]
+        ))
+        self.assertEqual(args, {&quot;arg1&quot;: (&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)})
+        self.assertEqual(usedtimerange, True)
+
+
+    def test_query_not_extended(self):
+        &quot;&quot;&quot;
+        Query test - two terms not anyof
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
+                    ),
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
+                    ),
+                ],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_TYPE = ? and ICALENDAR_TYPE = ?&quot;,
+            [1234, &quot;VEVENT&quot;, &quot;VTODO&quot;]
+        ))
+        self.assertEqual(args, {})
+        self.assertEqual(usedtimerange, False)
+
+
+    def test_query_extended(self):
+        &quot;&quot;&quot;
+        Extended query test - two terms with anyof
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, })],
+                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
+                    ),
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
+                    ),
+                ],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;, &quot;test&quot;: &quot;anyof&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
+        select, args, usedtimerange = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(
+            &quot;select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where (ICALENDAR_TYPE = ? and (FLOATING = ? and END_DATE &gt; ? or FLOATING = ? and END_DATE &gt; ?) or ICALENDAR_TYPE = ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?&quot;,
+            ['VEVENT', False, datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True, datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()), 'VTODO', 1234]
+        ))
+        self.assertEqual(args, {})
+        self.assertEqual(usedtimerange, True)
+
+
+    def test_sqllite_query(self):
+        &quot;&quot;&quot;
+        Basic query test - single term.
+        Only UID can be queried via sql.
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        sql, args = sqlcalendarquery(filter, 1234)
+
+        self.assertTrue(sql.find(&quot;RESOURCE&quot;) != -1)
+        self.assertTrue(sql.find(&quot;TIMESPAN&quot;) == -1)
+        self.assertTrue(sql.find(&quot;TRANSPARENCY&quot;) == -1)
+        self.assertTrue(&quot;VEVENT&quot; in args)
+
+
+
+class TestQueryFilterSerialize(TestCase):
+
+    def setUp(self):
+        super(TestQueryFilterSerialize, self).setUp()
+        TimezoneCache.create()
+
+
+    def test_query(self):
+        &quot;&quot;&quot;
+        Basic query test - no time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+        j = filter.serialize()
+        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+
+
+    def test_timerange_query(self):
+        &quot;&quot;&quot;
+        Basic query test with time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, &quot;end&quot;:&quot;20060605T170000Z&quot;})],
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+        j = filter.serialize()
+        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
+        self.assertTrue(isinstance(f.child.filters[0].qualifier.tzinfo, Timezone))
+        self.assertEqual(f.child.filters[0].qualifier.tzinfo.getTimezoneID(), &quot;America/New_York&quot;)
+
+
+    def test_query_not_extended(self):
+        &quot;&quot;&quot;
+        Basic query test with time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
+                    ),
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
+                    ),
+                ],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+        j = filter.serialize()
+        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+        self.assertEqual(len(f.child.filters), 2)
+
+
+    def test_query_extended(self):
+        &quot;&quot;&quot;
+        Basic query test with time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        *[caldavxml.TimeRange(**{&quot;start&quot;:&quot;20060605T160000Z&quot;, })],
+                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
+                    ),
+                    caldavxml.ComponentFilter(
+                        **{&quot;name&quot;:(&quot;VTODO&quot;)}
+                    ),
+                ],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;, &quot;test&quot;: &quot;anyof&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+        j = filter.serialize()
+        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+        self.assertEqual(len(f.child.filters), 2)
+        self.assertTrue(isinstance(f.child.filters[0].qualifier, TimeRange))
+
+
+    def test_query_text(self):
+        &quot;&quot;&quot;
+        Basic query test with time range
+        &quot;&quot;&quot;
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[
+                    caldavxml.ComponentFilter(
+                        caldavxml.PropertyFilter(
+                            caldavxml.TextMatch.fromString(&quot;1234&quot;, False),
+                            name=&quot;UID&quot;,
+                        ),
+                        **{&quot;name&quot;:(&quot;VEVENT&quot;)}
+                    ),
+                ],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;, &quot;test&quot;: &quot;anyof&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+        j = filter.serialize()
+        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
+        self.assertTrue(isinstance(f.child.filters[0].filters[0], PropertyFilter))
+        self.assertTrue(isinstance(f.child.filters[0].filters[0].qualifier, TextMatch))
+        self.assertEqual(f.child.filters[0].filters[0].qualifier.text, &quot;1234&quot;)
+
+
+
+class TestQueryFilterMatch(TestCase):
+
+    def setUp(self):
+        super(TestQueryFilterMatch, self).setUp()
+        TimezoneCache.create()
+
+
+    def test_vlarm_undefined(self):
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    *[caldavxml.ComponentFilter(
+                        caldavxml.IsNotDefined(),
+                        **{&quot;name&quot;:&quot;VALARM&quot;}
+                    )],
+                    **{&quot;name&quot;:&quot;VEVENT&quot;}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+        filter.child.settzinfo(Timezone(tzid=&quot;America/New_York&quot;))
+
+        self.assertFalse(filter.match(
+            Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20051222T210412Z
+CREATED:20060102T150000Z
+DTSTART;TZID=US/Eastern:20130102T100000
+DURATION:PT1H
+RRULE:FREQ=DAILY;COUNT=5
+SUMMARY:event 5
+UID:945113826375CBB89184DC36@ninevah.local
+CATEGORIES:cool,hot
+CATEGORIES:warm
+BEGIN:VALARM
+ACTION:AUDIO
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+        )))
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingfreebusypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/freebusy.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/freebusy.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/freebusy.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -32,10 +32,11 @@
</span><span class="cx"> from twistedcaldav.ical import Component, Property, iCalendarProductID
</span><span class="cx"> from twistedcaldav.instance import InstanceList
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.caldav.icalendarstore import QueryMaxResources
</span><del>-from txdav.common.icommondatastore import IndexedSearchException
</del><ins>+from txdav.common.icommondatastore import IndexedSearchException, \
+    InternalDataStoreError
</ins><span class="cx"> 
</span><span class="cx"> import uuid
</span><span class="cx"> 
</span><span class="lines">@@ -92,7 +93,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-@inlineCallbacks
</del><span class="cx"> def generateFreeBusyInfo(
</span><span class="cx">     calresource,
</span><span class="cx">     fbinfo,
</span><span class="lines">@@ -107,6 +107,86 @@
</span><span class="cx">     logItems=None,
</span><span class="cx"> ):
</span><span class="cx">     &quot;&quot;&quot;
</span><ins>+    Get freebusy information for a calendar. Different behavior for internal vs external calendars.
+
+    See L{_internalGenerateFreeBusyInfo} for argument description.
+    &quot;&quot;&quot;
+
+    # TODO: this method really should be moved to L{CalendarObject} so the internal/external pieces
+    # can be split across L{CalendarObject} and L{CalendarObjectExternal}
+    if calresource.external():
+        return _externalGenerateFreeBusyInfo(
+            calresource,
+            fbinfo,
+            timerange,
+            matchtotal,
+            excludeuid,
+            organizer,
+            organizerPrincipal,
+            same_calendar_user,
+            servertoserver,
+            event_details,
+            logItems
+        )
+    else:
+        return _internalGenerateFreeBusyInfo(
+            calresource,
+            fbinfo,
+            timerange,
+            matchtotal,
+            excludeuid,
+            organizer,
+            organizerPrincipal,
+            same_calendar_user,
+            servertoserver,
+            event_details,
+            logItems
+        )
+
+
+
+@inlineCallbacks
+def _externalGenerateFreeBusyInfo(
+    calresource,
+    fbinfo,
+    timerange,
+    matchtotal,
+    excludeuid=None,
+    organizer=None,
+    organizerPrincipal=None,
+    same_calendar_user=False,
+    servertoserver=False,
+    event_details=None,
+    logItems=None,
+):
+    &quot;&quot;&quot;
+    Generate a freebusy response for an external (cross-pod) calendar by making a cross-pod call. This will bypass
+    any type of smart caching on this pod in favor of using caching on the pod hosting the actual calendar data.
+
+    See L{_internalGenerateFreeBusyInfo} for argument description.
+    &quot;&quot;&quot;
+    fbresults, matchtotal = yield calresource._txn.store().conduit.send_freebusy(calresource, timerange, matchtotal, excludeuid, organizer, organizerPrincipal, same_calendar_user, servertoserver, event_details)
+    for i in range(3):
+        fbinfo[i].extend([Period.parseText(p) for p in fbresults[i]])
+    returnValue(matchtotal)
+
+
+
+@inlineCallbacks
+def _internalGenerateFreeBusyInfo(
+    calresource,
+    fbinfo,
+    timerange,
+    matchtotal,
+    excludeuid=None,
+    organizer=None,
+    organizerPrincipal=None,
+    same_calendar_user=False,
+    servertoserver=False,
+    event_details=None,
+    logItems=None,
+):
+    &quot;&quot;&quot;
</ins><span class="cx">     Run a free busy report on the specified calendar collection
</span><span class="cx">     accumulating the free busy info for later processing.
</span><span class="cx">     @param calresource: the L{Calendar} for a calendar collection.
</span><span class="lines">@@ -203,23 +283,23 @@
</span><span class="cx"> 
</span><span class="cx">         # Create fake filter element to match time-range
</span><span class="cx">         filter = caldavxml.Filter(
</span><del>-                      caldavxml.ComponentFilter(
-                          caldavxml.ComponentFilter(
-                              cache_timerange if caching else timerange,
-                              name=(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;),
-                          ),
-                          name=&quot;VCALENDAR&quot;,
-                       )
-                  )
-        filter = calendarqueryfilter.Filter(filter)
</del><ins>+            caldavxml.ComponentFilter(
+                caldavxml.ComponentFilter(
+                    cache_timerange if caching else timerange,
+                    name=(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;),
+                ),
+                name=&quot;VCALENDAR&quot;,
+            )
+        )
+        filter = Filter(filter)
</ins><span class="cx">         tzinfo = filter.settimezone(tz)
</span><span class="cx"> 
</span><span class="cx">         try:
</span><del>-            resources = yield calresource._index.indexedSearch(filter, useruid=attendee_uid, fbtype=True)
</del><ins>+            resources = yield calresource.search(filter, useruid=attendee_uid, fbtype=True)
</ins><span class="cx">             if caching:
</span><span class="cx">                 yield FBCacheEntry.makeCacheEntry(calresource, attendee_uid, cache_timerange, resources)
</span><span class="cx">         except IndexedSearchException:
</span><del>-            resources = yield calresource._index.bruteForceSearch()
</del><ins>+            raise InternalDataStoreError(&quot;Invalid indexedSearch query&quot;)
</ins><span class="cx"> 
</span><span class="cx">     else:
</span><span class="cx">         # Log extended item
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingischedulelocalserverspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/localservers.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -84,6 +84,12 @@
</span><span class="cx">         self._thisServer = None
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def addServer(self, server):
+        self._servers[server.id] = server
+        if server.thisServer:
+            self._thisServer = server
+
+
</ins><span class="cx">     def getServerById(self, id):
</span><span class="cx">         return self._servers.get(id)
</span><span class="cx"> 
</span><span class="lines">@@ -125,16 +131,22 @@
</span><span class="cx">     Represents a server.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    def __init__(self):
-        self.id = None
-        self.uri = None
-        self.thisServer = False
</del><ins>+    def __init__(self, id=None, uri=None, sharedSecret=None, thisServer=False):
+        self.id = id
+        self.uri = uri
+        self.thisServer = thisServer
</ins><span class="cx">         self.ips = set()
</span><span class="cx">         self.allowed_from_ips = set()
</span><del>-        self.shared_secret = None
</del><ins>+        self.shared_secret = sharedSecret
</ins><span class="cx">         self.isImplicit = True
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def details(self):
+        if not hasattr(self, &quot;ssl&quot;):
+            self._parseDetails()
+        return (self.ssl, self.host, self.port, self.path,)
+
+
</ins><span class="cx">     def check(self, ignoreIPLookupFailures=False):
</span><span class="cx">         # Check whether this matches the current server
</span><span class="cx">         parsed_uri = urlparse.urlparse(self.uri)
</span><span class="lines">@@ -215,7 +227,28 @@
</span><span class="cx">         return (SERVER_SECRET_HEADER, self.shared_secret,)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def _parseDetails(self):
+        # Extract scheme, host, port and path
+        if self.uri.startswith(&quot;http://&quot;):
+            self.ssl = False
+            rest = self.uri[7:]
+        elif self.uri.startswith(&quot;https://&quot;):
+            self.ssl = True
+            rest = self.uri[8:]
</ins><span class="cx"> 
</span><ins>+        splits = rest.split(&quot;/&quot;, 1)
+        hostport = splits[0].split(&quot;:&quot;)
+        self.host = hostport[0]
+        if len(hostport) &gt; 1:
+            self.port = int(hostport[1])
+        else:
+            self.port = {False: 80, True: 443}[self.ssl]
+        self.path = &quot;/&quot;
+        if len(splits) &gt; 1:
+            self.path += splits[1]
+
+
+
</ins><span class="cx"> ELEMENT_SERVERS = &quot;servers&quot;
</span><span class="cx"> ELEMENT_SERVER = &quot;server&quot;
</span><span class="cx"> ELEMENT_ID = &quot;id&quot;
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingischedulexmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/xml.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/xml.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/xml.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -259,14 +259,18 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def fromCalendar(clazz, calendar):
</del><ins>+    def fromCalendar(clazz, calendar, format=None):
+        attrs = {}
+        if format is not None and format != &quot;text/calendar&quot;:
+            attrs[&quot;content-type&quot;] = format
+
</ins><span class="cx">         if isinstance(calendar, str):
</span><span class="cx">             if not calendar:
</span><span class="cx">                 raise ValueError(&quot;Missing calendar data&quot;)
</span><span class="cx">             return clazz(PCDATAElement(calendar))
</span><span class="cx">         elif isinstance(calendar, iComponent):
</span><span class="cx">             assert calendar.name() == &quot;VCALENDAR&quot;, &quot;Not a calendar: %r&quot; % (calendar,)
</span><del>-            return clazz(PCDATAElement(calendar.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference)))
</del><ins>+            return clazz(PCDATAElement(calendar.getTextWithTimezones(includeTimezones=not config.EnableTimezonesByReference, format=format)))
</ins><span class="cx">         else:
</span><span class="cx">             raise ValueError(&quot;Not a calendar: %s&quot; % (calendar,))
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/sql.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -57,6 +57,9 @@
</span><span class="cx"> from twistedcaldav.memcacher import Memcacher
</span><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><ins>+from txdav.caldav.datastore.query.builder import buildExpression
+from txdav.caldav.datastore.query.filter import Filter
+from txdav.caldav.datastore.query.generator import CalDAVSQLQueryGenerator
</ins><span class="cx"> from txdav.caldav.datastore.scheduling.icalsplitter import iCalSplitter
</span><span class="cx"> from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler
</span><span class="cx"> from txdav.caldav.datastore.util import AttachmentRetrievalTransport, \
</span><span class="lines">@@ -72,12 +75,11 @@
</span><span class="cx">     AttendeeAllowedError, InvalidPerUserDataMerge, ComponentUpdateState, \
</span><span class="cx">     ValidOrganizerError, ShareeAllowedError, ComponentRemoveState, \
</span><span class="cx">     InvalidDefaultCalendar, \
</span><del>-    InvalidAttachmentOperation, DuplicatePrivateCommentsError
</del><ins>+    InvalidAttachmentOperation, DuplicatePrivateCommentsError, \
+    TimeRangeUpperLimit, TimeRangeLowerLimit
</ins><span class="cx"> from txdav.caldav.icalendarstore import QuotaExceeded
</span><span class="cx"> from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
</span><span class="cx">     CommonObjectResource, ECALENDARTYPE
</span><del>-from txdav.common.datastore.sql_legacy import PostgresLegacyIndexEmulator, \
-    PostgresLegacyInboxIndexEmulator
</del><span class="cx"> from txdav.common.datastore.sql_tables import _ATTACHMENTS_MODE_NONE, \
</span><span class="cx">     _ATTACHMENTS_MODE_WRITE, schema, _BIND_MODE_OWN, \
</span><span class="cx">     _ATTACHMENTS_MODE_READ, _TRANSP_OPAQUE, _TRANSP_TRANSPARENT
</span><span class="lines">@@ -87,7 +89,8 @@
</span><span class="cx">     InvalidObjectResourceError, ObjectResourceNameAlreadyExistsError, \
</span><span class="cx">     ObjectResourceNameNotAllowedError, TooManyObjectResourcesError, \
</span><span class="cx">     InvalidUIDError, UIDExistsError, UIDExistsElsewhereError, \
</span><del>-    InvalidResourceMove, InvalidComponentForStoreError
</del><ins>+    InvalidResourceMove, InvalidComponentForStoreError, \
+    NoSuchObjectResourceError
</ins><span class="cx"> from txdav.xml import element
</span><span class="cx"> 
</span><span class="cx"> from txdav.idav import ChangeCategory
</span><span class="lines">@@ -433,12 +436,6 @@
</span><span class="cx">         &quot;VPOLL&quot;: &quot;_default_polls&quot;,
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    def __init__(self, transaction, ownerUID):
-
-        self._childClass = Calendar
-        super(CalendarHome, self).__init__(transaction, ownerUID)
-
-
</del><span class="cx">     @classmethod
</span><span class="cx">     def metadataColumns(cls):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -957,6 +954,12 @@
</span><span class="cx">     _objectSchema = schema.CALENDAR_OBJECT
</span><span class="cx">     _timeRangeSchema = schema.TIME_RANGE
</span><span class="cx"> 
</span><ins>+    # Mapping of iCalendar property name to DB column name
+    _queryFields = {
+        &quot;UID&quot;: _objectSchema.UID,
+        &quot;TYPE&quot;: _objectSchema.ICALENDAR_TYPE,
+    }
+
</ins><span class="cx">     _supportedComponents = None
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, *args, **kw):
</span><span class="lines">@@ -964,10 +967,6 @@
</span><span class="cx">         Initialize a calendar pointing at a record in a database.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         super(Calendar, self).__init__(*args, **kw)
</span><del>-        if self.isInbox():
-            self._index = PostgresLegacyInboxIndexEmulator(self)
-        else:
-            self._index = PostgresLegacyIndexEmulator(self)
</del><span class="cx">         self._transp = _TRANSP_OPAQUE
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1085,6 +1084,45 @@
</span><span class="cx">         self.viewerHome().removedCalendarResource(child.uid())
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def moveObjectResourceHere(self, name, component):
+        &quot;&quot;&quot;
+        Create a new child in this collection as part of a move operation. This needs to be split out because
+        behavior differs for sub-classes and cross-pod operations.
+
+        @param name: new name to use in new parent
+        @type name: C{str} or C{None} for existing name
+        @param component: data for new resource
+        @type component: L{Component}
+        &quot;&quot;&quot;
+
+        # Cross-pod calls come in with component as str or unicode
+        if isinstance(component, str) or isinstance(component, unicode):
+            try:
+                component = self._objectResourceClass._componentClass.fromString(component)
+            except InvalidICalendarDataError as e:
+                raise InvalidComponentForStoreError(str(e))
+
+        yield self._createCalendarObjectWithNameInternal(name, component, internal_state=ComponentUpdateState.RAW)
+
+
+    @inlineCallbacks
+    def moveObjectResourceAway(self, rid, child=None):
+        &quot;&quot;&quot;
+        Remove the child as the result of a move operation. This needs to be split out because
+        behavior differs for sub-classes and cross-pod operations.
+
+        @param rid: the child resource-id to move
+        @type rid: C{int}
+        @param child: the child resource to move - might be C{None} for cross-pod
+        @type child: L{CommonObjectResource}
+        &quot;&quot;&quot;
+
+        if child is None:
+            child = yield self.objectResourceWithID(rid)
+        yield child._removeInternal(internal_state=ComponentRemoveState.INTERNAL)
+
+
</ins><span class="cx">     def calendarObjectsInTimeRange(self, start, end, timeZone):
</span><span class="cx">         raise NotImplementedError()
</span><span class="cx"> 
</span><span class="lines">@@ -1302,6 +1340,37 @@
</span><span class="cx">         yield self.setDefaultAlarm(&quot;empty&quot;, False, False)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getInviteCopyProperties(self):
+        &quot;&quot;&quot;
+        Get a dictionary of property name/values (as strings) for properties that are shadowable and
+        need to be copied to a sharee's collection when an external (cross-pod) share is created.
+        Sub-classes should override to expose the properties they care about.
+        &quot;&quot;&quot;
+        props = {}
+        for elem in (element.DisplayName, caldavxml.CalendarDescription, caldavxml.CalendarTimeZone, customxml.CalendarColor,):
+            if PropertyName.fromElement(elem) in self.properties():
+                props[elem.sname()] = str(self.properties()[PropertyName.fromElement(elem)])
+        return props
+
+
+    def setInviteCopyProperties(self, props):
+        &quot;&quot;&quot;
+        Copy a set of shadowable properties (as name/value strings) onto this shared resource when
+        a cross-pod invite is processed. Sub-classes should override to expose the properties they
+        care about.
+        &quot;&quot;&quot;
+        # Initialize these for all shares
+        for elem in (caldavxml.CalendarDescription, caldavxml.CalendarTimeZone,):
+            if PropertyName.fromElement(elem) not in self.properties() and elem.sname() in props:
+                self.properties()[PropertyName.fromElement(elem)] = elem.fromString(props[elem.sname()])
+
+        # Only initialize these for direct shares
+        if self.direct():
+            for elem in (element.DisplayName, customxml.CalendarColor,):
+                if PropertyName.fromElement(elem) not in self.properties() and elem.sname() in props:
+                    self.properties()[PropertyName.fromElement(elem)] = elem.fromString(props[elem.sname()])
+
+
</ins><span class="cx">     # FIXME: this is DAV-ish.  Data store calendar objects don't have
</span><span class="cx">     # mime types.  -wsv
</span><span class="cx">     def contentType(self):
</span><span class="lines">@@ -1312,6 +1381,196 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def search(self, filter, useruid=None, fbtype=False):
+        &quot;&quot;&quot;
+        Finds resources matching the given qualifiers.
+        @param filter: the L{Filter} for the calendar-query to execute.
+        @return: an iterable of tuples for each resource matching the
+            given C{qualifiers}. The tuples are C{(name, uid)}, where
+            C{name} is the resource name, C{uid} is the resource UID.
+        &quot;&quot;&quot;
+
+        # We might be passed an L{Filter} or a serialization of one
+        if isinstance(filter, dict):
+            try:
+                filter = Filter.deserialize(filter)
+            except Exception:
+                file = None
+
+        # Make sure we have a proper Filter element and get the partial SQL statement to use.
+        sql_stmt = self._sqlquery(filter, useruid, fbtype)
+
+        # No result means it is too complex for us
+        if sql_stmt is None:
+            raise IndexedSearchException()
+        sql_stmt, args, usedtimerange = sql_stmt
+
+        # Check for time-range re-expand
+        if usedtimerange is not None:
+
+            today = DateTime.getToday()
+
+            # Determine how far we need to extend the current expansion of
+            # events. If we have an open-ended time-range we will expand
+            # one year past the start. That should catch bounded
+            # recurrences - unbounded will have been indexed with an
+            # &quot;infinite&quot; value always included.
+            maxDate, isStartDate = filter.getmaxtimerange()
+            if maxDate:
+                maxDate = maxDate.duplicate()
+                maxDate.offsetDay(1)
+                maxDate.setDateOnly(True)
+                upperLimit = today + Duration(days=config.FreeBusyIndexExpandMaxDays)
+                if maxDate &gt; upperLimit:
+                    raise TimeRangeUpperLimit(upperLimit)
+                if isStartDate:
+                    maxDate += Duration(days=365)
+
+            # Determine if the start date is too early for the restricted range we
+            # are applying. If it is today or later we don't need to worry about truncation
+            # in the past.
+            minDate, _ignore_isEndDate = filter.getmintimerange()
+            if minDate &gt;= today:
+                minDate = None
+            if minDate is not None and config.FreeBusyIndexLowerLimitDays:
+                truncateLowerLimit = today - Duration(days=config.FreeBusyIndexLowerLimitDays)
+                if minDate &lt; truncateLowerLimit:
+                    raise TimeRangeLowerLimit(truncateLowerLimit)
+
+            if maxDate is not None or minDate is not None:
+                yield self.testAndUpdateIndex(minDate, maxDate)
+
+        rowiter = yield sql_stmt.on(self._txn, **args)
+
+        # Check result for missing resources
+        results = []
+        for row in rowiter:
+            if fbtype:
+                row = list(row)
+                row[4] = 'Y' if row[4] else 'N'
+                row[7] = indexfbtype_to_icalfbtype[row[7]]
+                if row[9] is not None:
+                    row[8] = row[9]
+                row[8] = 'T' if row[8] else 'F'
+                del row[9]
+            results.append(row)
+
+        returnValue(results)
+
+
+    def _sqlquery(self, filter, useruid, fbtype):
+        &quot;&quot;&quot;
+        Convert the supplied addressbook-query into a partial SQL statement.
+
+        @param filter: the L{Filter} for the addressbook-query to convert.
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+                and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+                Or return C{None} if it is not possible to create an SQL query to fully match the addressbook-query.
+        &quot;&quot;&quot;
+
+        if not isinstance(filter, Filter):
+            return None
+
+        try:
+            expression = buildExpression(filter, self._queryFields)
+            sql = CalDAVSQLQueryGenerator(expression, self, self.id(), useruid, fbtype)
+            return sql.generate()
+        except ValueError:
+            return None
+
+
+    @classproperty
+    def _notExpandedWithinQuery(cls): #@NoSelf
+        &quot;&quot;&quot;
+        Query to find resources that need to be re-expanded
+        &quot;&quot;&quot;
+        co = schema.CALENDAR_OBJECT
+        return Select(
+            [co.RESOURCE_NAME],
+            From=co,
+            Where=((co.RECURRANCE_MIN &gt; Parameter(&quot;minDate&quot;))
+                .Or(co.RECURRANCE_MAX &lt; Parameter(&quot;maxDate&quot;)))
+                .And(co.CALENDAR_RESOURCE_ID == Parameter(&quot;resourceID&quot;))
+        )
+
+
+    @inlineCallbacks
+    def notExpandedWithin(self, minDate, maxDate):
+        &quot;&quot;&quot;
+        Gives all resources which have not been expanded beyond a given date
+        in the database.  (Unused; see above L{postgresqlgenerator}.
+        &quot;&quot;&quot;
+        returnValue([row[0] for row in (
+            yield self._notExpandedWithinQuery.on(
+                self._txn,
+                minDate=pyCalendarTodatetime(normalizeForIndex(minDate)) if minDate is not None else None,
+                maxDate=pyCalendarTodatetime(normalizeForIndex(maxDate)),
+                resourceID=self._resourceID))]
+        )
+
+
+    @inlineCallbacks
+    def reExpandResource(self, name, expand_start, expand_end):
+        &quot;&quot;&quot;
+        Given a resource name, remove it from the database and re-add it
+        with a longer expansion.
+        &quot;&quot;&quot;
+        obj = yield self.calendarObjectWithName(name)
+
+        # Use a new transaction to do this update quickly without locking the row for too long. However, the original
+        # transaction may have the row locked, so use wait=False and if that fails, fall back to using the original txn.
+
+        newTxn = obj.transaction().store().newTransaction()
+        try:
+            yield obj.lock(wait=False, txn=newTxn)
+        except NoSuchObjectResourceError:
+            yield newTxn.commit()
+            returnValue(None)
+        except:
+            yield newTxn.abort()
+            newTxn = None
+
+        # Now do the re-expand using the appropriate transaction
+        try:
+            doExpand = False
+            if newTxn is None:
+                doExpand = True
+            else:
+                # We repeat this check because the resource may have been re-expanded by someone else
+                rmin, rmax = (yield obj.recurrenceMinMax(txn=newTxn))
+
+                # If the resource is not fully expanded, see if within the required range or not.
+                # Note that expand_start could be None if no lower limit is applied, but expand_end will
+                # never be None
+                if rmax is not None and rmax &lt; expand_end:
+                    doExpand = True
+                if rmin is not None and expand_start is not None and rmin &gt; expand_start:
+                    doExpand = True
+
+            if doExpand:
+                yield obj.updateDatabase(
+                    (yield obj.component()),
+                    expand_until=expand_end,
+                    reCreate=True,
+                    txn=newTxn,
+                )
+        finally:
+            if newTxn is not None:
+                yield newTxn.commit()
+
+
+    @inlineCallbacks
+    def testAndUpdateIndex(self, minDate, maxDate):
+        # Find out if the index is expanded far enough
+        names = yield self.notExpandedWithin(minDate, maxDate)
+
+        # Actually expand recurrence max
+        for name in names:
+            self.log.info(&quot;Search falls outside range of index for %s %s to %s&quot; % (name, minDate, maxDate))
+            yield self.reExpandResource(name, minDate, maxDate)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def splitCollectionByComponentTypes(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         If the calendar contains iCalendar data with different component types, then split it into separate collections
</span><span class="lines">@@ -1524,6 +1783,7 @@
</span><span class="cx">     implements(ICalendarObject)
</span><span class="cx"> 
</span><span class="cx">     _objectSchema = schema.CALENDAR_OBJECT
</span><ins>+    _componentClass = VComponent
</ins><span class="cx"> 
</span><span class="cx">     def __init__(self, calendar, name, uid, resourceID=None, options=None):
</span><span class="cx"> 
</span><span class="lines">@@ -1542,36 +1802,20 @@
</span><span class="cx">         self._cachedComponent = None
</span><span class="cx">         self._cachedCommponentPerUser = {}
</span><span class="cx"> 
</span><del>-    _allColumns = [
-        _objectSchema.RESOURCE_ID,
-        _objectSchema.RESOURCE_NAME,
-        _objectSchema.UID,
-        _objectSchema.MD5,
-        Len(_objectSchema.TEXT),
-        _objectSchema.ATTACHMENTS_MODE,
-        _objectSchema.DROPBOX_ID,
-        _objectSchema.ACCESS,
-        _objectSchema.SCHEDULE_OBJECT,
-        _objectSchema.SCHEDULE_TAG,
-        _objectSchema.SCHEDULE_ETAGS,
-        _objectSchema.PRIVATE_COMMENTS,
-        _objectSchema.CREATED,
-        _objectSchema.MODIFIED
-    ]
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _createInternal(cls, parent, name, component, internal_state, options=None, split_details=None):
</span><span class="cx"> 
</span><del>-        child = (yield cls.objectWithName(parent, name, None))
</del><ins>+        child = (yield cls.objectWithName(parent, name))
</ins><span class="cx">         if child:
</span><span class="cx">             raise ObjectResourceNameAlreadyExistsError(name)
</span><span class="cx"> 
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise ObjectResourceNameNotAllowedError(name)
</span><span class="cx"> 
</span><del>-        objectResource = cls(parent, name, None, None, options=options)
</del><ins>+        c = cls._externalClass if parent.external() else cls
+        objectResource = c(parent, name, None, None, options=options)
</ins><span class="cx">         yield objectResource._setComponentInternal(component, inserting=True, internal_state=internal_state, split_details=split_details)
</span><span class="cx">         yield objectResource._loadPropertyStore(created=True)
</span><span class="cx"> 
</span><span class="lines">@@ -1581,27 +1825,51 @@
</span><span class="cx">         returnValue(objectResource)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _initFromRow(self, row):
</del><ins>+    @classmethod
+    def _allColumns(cls): #@NoSelf
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Given a select result using the columns from L{_allColumns}, initialize
-        the calendar object resource state.
</del><ins>+        Full set of columns in the object table that need to be loaded to
+        initialize the object resource state.
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        (self._resourceID,
-         self._name,
-         self._uid,
-         self._md5,
-         self._size,
-         self._attachment,
-         self._dropboxID,
-         self._access,
-         self._schedule_object,
-         self._schedule_tag,
-         self._schedule_etags,
-         self._private_comments,
-         self._created,
-         self._modified,) = tuple(row)
</del><ins>+        obj = cls._objectSchema
+        return [
+            obj.RESOURCE_ID,
+            obj.RESOURCE_NAME,
+            obj.UID,
+            obj.MD5,
+            Len(obj.TEXT),
+            obj.ATTACHMENTS_MODE,
+            obj.DROPBOX_ID,
+            obj.ACCESS,
+            obj.SCHEDULE_OBJECT,
+            obj.SCHEDULE_TAG,
+            obj.SCHEDULE_ETAGS,
+            obj.PRIVATE_COMMENTS,
+            obj.CREATED,
+            obj.MODIFIED
+        ]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
+    def _rowAttributes(cls): #@NoSelf
+        return (
+            &quot;_resourceID&quot;,
+            &quot;_name&quot;,
+            &quot;_uid&quot;,
+            &quot;_md5&quot;,
+            &quot;_size&quot;,
+            &quot;_attachment&quot;,
+            &quot;_dropboxID&quot;,
+            &quot;_access&quot;,
+            &quot;_schedule_object&quot;,
+            &quot;_schedule_tag&quot;,
+            &quot;_schedule_etags&quot;,
+            &quot;_private_comments&quot;,
+            &quot;_created&quot;,
+            &quot;_modified&quot;,
+         )
+
+
</ins><span class="cx">     @property
</span><span class="cx">     def _calendar(self):
</span><span class="cx">         return self._parentCollection
</span><span class="lines">@@ -2183,6 +2451,12 @@
</span><span class="cx">         Scheduling will be done automatically.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        # Cross-pod calls come in with component as str or unicode
+        if isinstance(component, str) or isinstance(component, unicode):
+            try:
+                component = self._componentClass.fromString(component)
+            except InvalidICalendarDataError as e:
+                raise InvalidComponentForStoreError(str(e))
</ins><span class="cx">         return self._setComponentInternal(component, inserting, ComponentUpdateState.NORMAL, smart_merge)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -2734,9 +3008,10 @@
</span><span class="cx">                 yield NamedLock.acquire(self._txn, &quot;ImplicitUIDLock:%s&quot; % (hashlib.md5(calendar.resourceUID()).hexdigest(),))
</span><span class="cx"> 
</span><span class="cx">         # Need to also remove attachments
</span><del>-        if self._dropboxID:
-            yield DropBoxAttachment.resourceRemoved(self._txn, self._resourceID, self._dropboxID)
-        yield ManagedAttachment.resourceRemoved(self._txn, self._resourceID)
</del><ins>+        if internal_state != ComponentRemoveState.INTERNAL:
+            if self._dropboxID:
+                yield DropBoxAttachment.resourceRemoved(self._txn, self._resourceID, self._dropboxID)
+            yield ManagedAttachment.resourceRemoved(self._txn, self._resourceID)
</ins><span class="cx">         yield super(CalendarObject, self).remove()
</span><span class="cx"> 
</span><span class="cx">         # Do scheduling
</span><span class="lines">@@ -4419,4 +4694,10 @@
</span><span class="cx"> 
</span><span class="cx">         returnValue(location)
</span><span class="cx"> 
</span><ins>+# Hook-up class relationships at the end after they have all been defined
+from txdav.caldav.datastore.sql_external import CalendarHomeExternal, CalendarExternal, CalendarObjectExternal
+CalendarHome._externalClass = CalendarHomeExternal
+CalendarHome._childClass = Calendar
+Calendar._externalClass = CalendarExternal
</ins><span class="cx"> Calendar._objectResourceClass = CalendarObject
</span><ins>+CalendarObject._externalClass = CalendarObjectExternal
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoresql_externalpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoresql_externalpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/caldav/datastore/sql_external.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/sql_external.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/sql_external.py                                (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql_external.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,217 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+&quot;&quot;&quot;
+SQL backend for CalDAV storage when resources are external.
+&quot;&quot;&quot;
+
+from twisted.internet.defer import succeed, inlineCallbacks, returnValue
+
+from twext.python.log import Logger
+
+from txdav.caldav.datastore.sql import CalendarHome, Calendar, CalendarObject
+from txdav.caldav.icalendarstore import ComponentUpdateState, ComponentRemoveState
+from txdav.common.datastore.sql_external import CommonHomeExternal, CommonHomeChildExternal, \
+    CommonObjectResourceExternal
+
+log = Logger()
+
+class CalendarHomeExternal(CommonHomeExternal, CalendarHome):
+    &quot;&quot;&quot;
+    Wrapper for a CalendarHome that is external and only supports a limited set of operations.
+    &quot;&quot;&quot;
+
+    def __init__(self, transaction, ownerUID, resourceID):
+
+        CalendarHome.__init__(self, transaction, ownerUID)
+        CommonHomeExternal.__init__(self, transaction, ownerUID, resourceID)
+
+
+    def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, mode):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getCalendarResourcesForUID(self, uid):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def calendarObjectWithDropboxID(self, dropboxID):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAllDropboxIDs(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAllAttachmentNames(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAllManagedIDs(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def createdHome(self):
+        &quot;&quot;&quot;
+        No children - make this a no-op.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def splitCalendars(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def ensureDefaultCalendarsExist(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def setDefaultCalendar(self, calendar, componentType):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def defaultCalendar(self, componentType, create=True):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def isDefaultCalendar(self, calendar):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getDefaultAlarm(self, vevent, timed):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def setDefaultAlarm(self, alarm, vevent, timed):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAvailability(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def setAvailability(self, availability):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+
+class CalendarExternal(CommonHomeChildExternal, Calendar):
+    &quot;&quot;&quot;
+    SQL-based implementation of L{ICalendar}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class CalendarObjectExternal(CommonObjectResourceExternal, CalendarObject):
+    &quot;&quot;&quot;
+    SQL-based implementation of L{ICalendarObject}.
+    &quot;&quot;&quot;
+
+    @classmethod
+    def _createInternal(cls, parent, name, component, internal_state, options=None, split_details=None):
+        raise AssertionError(&quot;CalendarObjectExternal: not supported&quot;)
+
+
+    def _setComponentInternal(self, component, inserting=False, internal_state=ComponentUpdateState.NORMAL, smart_merge=False, split_details=None):
+        raise AssertionError(&quot;CalendarObjectExternal: not supported&quot;)
+
+
+    def _removeInternal(self, internal_state=ComponentRemoveState.NORMAL):
+        raise AssertionError(&quot;CalendarObjectExternal: not supported&quot;)
+
+
+    @inlineCallbacks
+    def addAttachment(self, rids, content_type, filename, stream):
+        result = yield self._txn.store().conduit.send_add_attachment(self, rids, content_type, filename, stream)
+        managedID, location = result
+        returnValue((ManagedAttachmentExternal(str(managedID)), str(location),))
+
+
+    @inlineCallbacks
+    def updateAttachment(self, managed_id, content_type, filename, stream):
+        result = yield self._txn.store().conduit.send_update_attachment(self, managed_id, content_type, filename, stream)
+        managedID, location = result
+        returnValue((ManagedAttachmentExternal(str(managedID)), str(location),))
+
+
+    @inlineCallbacks
+    def removeAttachment(self, rids, managed_id):
+        yield self._txn.store().conduit.send_remove_attachment(self, rids, managed_id)
+        returnValue(None)
+
+
+
+class ManagedAttachmentExternal(object):
+    &quot;&quot;&quot;
+    Fake managed attachment object returned from L{CalendarObjectExternal.addAttachment} and
+    L{CalendarObjectExternal.updateAttachment}.
+    &quot;&quot;&quot;
+
+    def __init__(self, managedID):
+        self._managedID = managedID
+
+
+    def managedID(self):
+        return self._managedID
+
+
+CalendarExternal._objectResourceClass = CalendarObjectExternal
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretestcommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -416,12 +416,14 @@
</span><span class="cx">         yield coll.removeNotificationObjectWithUID(&quot;1&quot;)
</span><span class="cx">         st2 = yield coll.syncToken()
</span><span class="cx">         rev2 = self.token2revision(st2)
</span><del>-        changed, deleted = yield coll.resourceNamesSinceToken(rev)
</del><ins>+        changed, deleted, invalid = yield coll.resourceNamesSinceToken(rev)
</ins><span class="cx">         self.assertEquals(set(changed), set([&quot;2.xml&quot;]))
</span><span class="cx">         self.assertEquals(set(deleted), set([&quot;1.xml&quot;]))
</span><del>-        changed, deleted = yield coll.resourceNamesSinceToken(rev2)
</del><ins>+        self.assertEquals(len(invalid), 0)
+        changed, deleted, invalid = yield coll.resourceNamesSinceToken(rev2)
</ins><span class="cx">         self.assertEquals(set(changed), set([]))
</span><span class="cx">         self.assertEquals(set(deleted), set([]))
</span><ins>+        self.assertEquals(len(invalid), 0)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1602,7 +1604,7 @@
</span><span class="cx"> 
</span><span class="cx">         home = yield self.homeUnderTest()
</span><span class="cx"> 
</span><del>-        changed, deleted = yield home.resourceNamesSinceToken(
</del><ins>+        changed, deleted, invalid = yield home.resourceNamesSinceToken(
</ins><span class="cx">             self.token2revision(st), &quot;infinity&quot;)
</span><span class="cx"> 
</span><span class="cx">         self.assertEquals(set(changed), set([&quot;calendar_1/&quot;,
</span><span class="lines">@@ -1610,11 +1612,13 @@
</span><span class="cx">                                              &quot;calendar_1/2.ics&quot;,
</span><span class="cx">                                              &quot;other-calendar/&quot;]))
</span><span class="cx">         self.assertEquals(set(deleted), set([&quot;calendar_1/2.ics&quot;]))
</span><ins>+        self.assertEquals(invalid, [])
</ins><span class="cx"> 
</span><del>-        changed, deleted = yield home.resourceNamesSinceToken(
</del><ins>+        changed, deleted, invalid = yield home.resourceNamesSinceToken(
</ins><span class="cx">             self.token2revision(st2), &quot;infinity&quot;)
</span><span class="cx">         self.assertEquals(changed, [])
</span><span class="cx">         self.assertEquals(deleted, [])
</span><ins>+        self.assertEquals(invalid, [])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1634,12 +1638,14 @@
</span><span class="cx">         yield obj1.remove()
</span><span class="cx">         st2 = yield cal.syncToken()
</span><span class="cx">         rev2 = self.token2revision(st2)
</span><del>-        changed, deleted = yield cal.resourceNamesSinceToken(rev)
</del><ins>+        changed, deleted, invalid = yield cal.resourceNamesSinceToken(rev)
</ins><span class="cx">         self.assertEquals(set(changed), set([&quot;new.ics&quot;]))
</span><span class="cx">         self.assertEquals(set(deleted), set([&quot;2.ics&quot;]))
</span><del>-        changed, deleted = yield cal.resourceNamesSinceToken(rev2)
</del><ins>+        self.assertEquals(len(invalid), 0)
+        changed, deleted, invalid = yield cal.resourceNamesSinceToken(rev2)
</ins><span class="cx">         self.assertEquals(set(changed), set([]))
</span><span class="cx">         self.assertEquals(set(deleted), set([]))
</span><ins>+        self.assertEquals(len(invalid), 0)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1703,7 +1709,7 @@
</span><span class="cx">         L{ICalendarStore.withEachCalendarHomeDo} executes its C{action}
</span><span class="cx">         argument repeatedly with all homes that have been created.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        additionalUIDs = set('alpha-uid home2 home3 beta-uid'.split())
</del><ins>+        additionalUIDs = set('user01 home2 home3 uid1'.split())
</ins><span class="cx">         txn = self.transactionUnderTest()
</span><span class="cx">         for name in additionalUIDs:
</span><span class="cx">             yield txn.calendarHomeWithUID(name, create=True)
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_index_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -19,6 +19,7 @@
</span><span class="cx"> from twisted.internet.task import deferLater
</span><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.index_file import Index, MemcachedUIDReserver
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.common.icommondatastore import ReservationError, \
</span><span class="cx">     InternalDataStoreError
</span><span class="cx"> 
</span><span class="lines">@@ -26,7 +27,6 @@
</span><span class="cx"> from twistedcaldav.caldavxml import TimeRange
</span><span class="cx"> from twistedcaldav.ical import Component, InvalidICalendarDataError
</span><span class="cx"> from twistedcaldav.instance import InvalidOverriddenInstanceError
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><span class="cx"> from twistedcaldav.test.util import InMemoryMemcacheProtocol
</span><span class="cx"> import twistedcaldav.test.util
</span><span class="cx"> 
</span><span class="lines">@@ -480,7 +480,7 @@
</span><span class="cx">                       name=&quot;VCALENDAR&quot;,
</span><span class="cx">                    )
</span><span class="cx">               )
</span><del>-            filter = calendarqueryfilter.Filter(filter)
</del><ins>+            filter = Filter(filter)
</ins><span class="cx"> 
</span><span class="cx">             resources = yield self.db.indexedSearch(filter)
</span><span class="cx">             index_results = set()
</span><span class="lines">@@ -666,7 +666,7 @@
</span><span class="cx">                       name=&quot;VCALENDAR&quot;,
</span><span class="cx">                    )
</span><span class="cx">               )
</span><del>-            filter = calendarqueryfilter.Filter(filter)
</del><ins>+            filter = Filter(filter)
</ins><span class="cx"> 
</span><span class="cx">             resources = yield self.db.indexedSearch(filter, fbtype=True)
</span><span class="cx">             index_results = set()
</span><span class="lines">@@ -1073,7 +1073,7 @@
</span><span class="cx">                       name=&quot;VCALENDAR&quot;,
</span><span class="cx">                    )
</span><span class="cx">               )
</span><del>-            filter = calendarqueryfilter.Filter(filter)
</del><ins>+            filter = Filter(filter)
</ins><span class="cx"> 
</span><span class="cx">             for useruid, instances in peruserinstances:
</span><span class="cx">                 resources = yield self.db.indexedSearch(filter, useruid=useruid, fbtype=True)
</span><span class="lines">@@ -1142,12 +1142,12 @@
</span><span class="cx">         self.db.deleteResource(&quot;data3.ics&quot;)
</span><span class="cx"> 
</span><span class="cx">         tests = (
</span><del>-            (0, ([&quot;data1.ics&quot;, &quot;data2.ics&quot;, ], [],)),
-            (1, ([&quot;data2.ics&quot;, ], [&quot;data3.ics&quot;, ],)),
-            (2, ([], [&quot;data3.ics&quot;, ],)),
-            (3, ([], [&quot;data3.ics&quot;, ],)),
-            (4, ([], [],)),
-            (5, ([], [],)),
</del><ins>+            (0, ([&quot;data1.ics&quot;, &quot;data2.ics&quot;, ], [], [],)),
+            (1, ([&quot;data2.ics&quot;, ], [&quot;data3.ics&quot;, ], [],)),
+            (2, ([], [&quot;data3.ics&quot;, ], [],)),
+            (3, ([], [&quot;data3.ics&quot;, ], [],)),
+            (4, ([], [], [],)),
+            (5, ([], [], [],)),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         for revision, results in tests:
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -13,14 +13,6 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><del>-from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
-from txdav.caldav.datastore.scheduling.cuaddress import RemoteCalendarUser, \
-    LocalCalendarUser
-from txdav.caldav.datastore.scheduling.caldav.scheduler import CalDAVScheduler
-from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
-from txweb2 import responsecode
-from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
-from twistedcaldav.instance import InvalidOverriddenInstanceError
</del><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Tests for txdav.caldav.datastore.postgres, mostly based on
</span><span class="lines">@@ -32,7 +24,8 @@
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.dal.syntax import Select, Parameter, Insert, Delete, \
</span><span class="cx">     Update
</span><del>-from twistedcaldav.ical import Component as VComponent
</del><ins>+
+from txweb2 import responsecode
</ins><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="cx"> from txweb2.stream import MemoryStream
</span><span class="cx"> 
</span><span class="lines">@@ -47,9 +40,16 @@
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.dateops import datetimeMktime
</span><span class="cx"> from twistedcaldav.ical import Component, normalize_iCalStr, diff_iCalStrs
</span><del>-from twistedcaldav.query import calendarqueryfilter
</del><ins>+from twistedcaldav.instance import InvalidOverriddenInstanceError
</ins><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><ins>+from txdav.caldav.datastore.query.filter import Filter
+from txdav.caldav.datastore.scheduling.caldav.scheduler import CalDAVScheduler
+from txdav.caldav.datastore.scheduling.cuaddress import RemoteCalendarUser, \
+    LocalCalendarUser
+from txdav.caldav.datastore.scheduling.itip import iTIPRequestStatus
+from txdav.caldav.datastore.scheduling.processing import ImplicitProcessor
+from txdav.caldav.datastore.scheduling.scheduler import ScheduleResponseQueue
</ins><span class="cx"> from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests, \
</span><span class="cx">     test_event_text
</span><span class="cx"> from txdav.caldav.datastore.test.test_file import setUpCalendarStore
</span><span class="lines">@@ -57,14 +57,13 @@
</span><span class="cx"> from txdav.caldav.datastore.util import _migrateCalendar, migrateHome
</span><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState, InvalidDefaultCalendar
</span><span class="cx"> from txdav.common.datastore.sql import ECALENDARTYPE, CommonObjectResource
</span><del>-from txdav.common.datastore.sql_legacy import PostgresLegacyIndexEmulator
</del><span class="cx"> from txdav.common.datastore.sql_tables import schema, _BIND_MODE_DIRECT, \
</span><span class="cx">     _BIND_STATUS_ACCEPTED
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom, \
</span><span class="cx">     CommonCommonTests
</span><span class="cx"> from txdav.common.icommondatastore import NoSuchObjectResourceError
</span><del>-from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
</del><span class="cx"> from txdav.idav import ChangeCategory
</span><ins>+from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
</ins><span class="cx"> 
</span><span class="cx"> import datetime
</span><span class="cx"> 
</span><span class="lines">@@ -407,10 +406,10 @@
</span><span class="cx">                           name=&quot;VCALENDAR&quot;,
</span><span class="cx">                        )
</span><span class="cx">                   )
</span><del>-        filter = calendarqueryfilter.Filter(filter)
</del><ins>+        filter = Filter(filter)
</ins><span class="cx">         filter.settimezone(None)
</span><span class="cx"> 
</span><del>-        results = yield toCalendar._index.indexedSearch(filter, 'user01', True)
</del><ins>+        results = yield toCalendar.search(filter, 'user01', True)
</ins><span class="cx">         self.assertEquals(len(results), 1)
</span><span class="cx">         _ignore_name, uid, _ignore_type, _ignore_organizer, _ignore_float, _ignore_start, _ignore_end, _ignore_fbtype, transp = results[0]
</span><span class="cx">         self.assertEquals(uid, &quot;uid4&quot;)
</span><span class="lines">@@ -618,7 +617,7 @@
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def _defer1():
</span><del>-            yield cal1.createObjectResourceWithName(&quot;1.ics&quot;, VComponent.fromString(
</del><ins>+            yield cal1.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(
</ins><span class="cx"> &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="lines">@@ -664,7 +663,7 @@
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def _defer2():
</span><del>-            yield cal2.createObjectResourceWithName(&quot;2.ics&quot;, VComponent.fromString(
</del><ins>+            yield cal2.createObjectResourceWithName(&quot;2.ics&quot;, Component.fromString(
</ins><span class="cx"> &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="lines">@@ -825,7 +824,7 @@
</span><span class="cx">         # Create calendar object
</span><span class="cx">         calendar1 = yield self.calendarUnderTest()
</span><span class="cx">         name = &quot;test.ics&quot;
</span><del>-        component = VComponent.fromString(test_event_text)
</del><ins>+        component = Component.fromString(test_event_text)
</ins><span class="cx">         metadata = {
</span><span class="cx">             &quot;accessMode&quot;: &quot;PUBLIC&quot;,
</span><span class="cx">             &quot;isScheduleObject&quot;: True,
</span><span class="lines">@@ -873,7 +872,7 @@
</span><span class="cx">         inbox = yield home.createCalendarWithName(&quot;inbox&quot;)
</span><span class="cx"> 
</span><span class="cx">         name = &quot;test.ics&quot;
</span><del>-        component = VComponent.fromString(test_event_text)
</del><ins>+        component = Component.fromString(test_event_text)
</ins><span class="cx">         metadata = {
</span><span class="cx">             &quot;accessMode&quot;: &quot;PUBLIC&quot;,
</span><span class="cx">             &quot;isScheduleObject&quot;: True,
</span><span class="lines">@@ -922,7 +921,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         home = yield self.homeUnderTest()
</span><span class="cx">         inbox = yield home.createCalendarWithName(&quot;inbox&quot;)
</span><del>-        component = VComponent.fromString(test_event_text)
</del><ins>+        component = Component.fromString(test_event_text)
</ins><span class="cx">         inboxItem = yield inbox.createCalendarObjectWithName(&quot;inbox.ics&quot;, component)
</span><span class="cx">         self.assertEquals(ChangeCategory.inbox, inboxItem.removeNotifyCategory())
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -935,7 +934,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         home = yield self.homeUnderTest()
</span><span class="cx">         nonInbox = yield home.createCalendarWithName(&quot;noninbox&quot;)
</span><del>-        component = VComponent.fromString(test_event_text)
</del><ins>+        component = Component.fromString(test_event_text)
</ins><span class="cx">         nonInboxItem = yield nonInbox.createCalendarObjectWithName(&quot;inbox.ics&quot;, component)
</span><span class="cx">         self.assertEquals(ChangeCategory.default, nonInboxItem.removeNotifyCategory())
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -1105,9 +1104,10 @@
</span><span class="cx">         self.assertTrue(calendar2_vtodo is not None)
</span><span class="cx">         children = yield calendar2_vtodo.listCalendarObjects()
</span><span class="cx">         self.assertEqual(len(children), 2)
</span><del>-        changed, deleted = yield calendar2_vtodo.resourceNamesSinceToken(None)
</del><ins>+        changed, deleted, invalid = yield calendar2_vtodo.resourceNamesSinceToken(None)
</ins><span class="cx">         self.assertEqual(sorted(changed), [&quot;3.ics&quot;, &quot;5.ics&quot;])
</span><span class="cx">         self.assertEqual(len(deleted), 0)
</span><ins>+        self.assertEqual(len(invalid), 0)
</ins><span class="cx">         result = yield calendar2_vtodo.getSupportedComponents()
</span><span class="cx">         self.assertEquals(result, &quot;VTODO&quot;)
</span><span class="cx">         self.assertTrue(pkey in calendar2_vtodo.properties())
</span><span class="lines">@@ -1118,9 +1118,10 @@
</span><span class="cx">         self.assertEqual(len(children), 3)
</span><span class="cx">         new_sync_token2 = yield calendar2.syncToken()
</span><span class="cx">         self.assertNotEqual(new_sync_token2, original_sync_token2)
</span><del>-        changed, deleted = yield calendar2.resourceNamesSinceToken(original_sync_token2)
</del><ins>+        changed, deleted, invalid = yield calendar2.resourceNamesSinceToken(original_sync_token2)
</ins><span class="cx">         self.assertEqual(len(changed), 0)
</span><span class="cx">         self.assertEqual(sorted(deleted), [&quot;3.ics&quot;, &quot;5.ics&quot;])
</span><ins>+        self.assertEqual(len(invalid), 0)
</ins><span class="cx">         result = yield calendar2.getSupportedComponents()
</span><span class="cx">         self.assertEquals(result, &quot;VEVENT&quot;)
</span><span class="cx">         self.assertTrue(pkey in calendar2.properties())
</span><span class="lines">@@ -1369,7 +1370,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_notExpandedWithin(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Test PostgresLegacyIndexEmulator.notExpandedWithin to make sure it returns the correct
</del><ins>+        Test Calendar.notExpandedWithin to make sure it returns the correct
</ins><span class="cx">         result based on the ranges passed in.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="lines">@@ -1378,7 +1379,6 @@
</span><span class="cx">         # Create the index on a new calendar
</span><span class="cx">         home = yield self.homeUnderTest()
</span><span class="cx">         newcalendar = yield home.createCalendarWithName(&quot;index_testing&quot;)
</span><del>-        index = PostgresLegacyIndexEmulator(newcalendar)
</del><span class="cx"> 
</span><span class="cx">         # Create the calendar object to use for testing
</span><span class="cx">         nowYear = self.nowYear[&quot;now&quot;]
</span><span class="lines">@@ -1406,37 +1406,37 @@
</span><span class="cx">         # Fully within range
</span><span class="cx">         testMin = DateTime(nowYear, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><span class="cx">         testMax = DateTime(nowYear + 1, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [])
</span><span class="cx"> 
</span><span class="cx">         # Upper bound exceeded
</span><span class="cx">         testMin = DateTime(nowYear, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><span class="cx">         testMax = DateTime(nowYear + 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [&quot;indexing.ics&quot;])
</span><span class="cx"> 
</span><span class="cx">         # Lower bound exceeded
</span><span class="cx">         testMin = DateTime(nowYear - 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><span class="cx">         testMax = DateTime(nowYear + 1, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [&quot;indexing.ics&quot;])
</span><span class="cx"> 
</span><span class="cx">         # Lower and upper bounds exceeded
</span><span class="cx">         testMin = DateTime(nowYear - 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><span class="cx">         testMax = DateTime(nowYear + 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [&quot;indexing.ics&quot;])
</span><span class="cx"> 
</span><span class="cx">         # Lower none within range
</span><span class="cx">         testMin = None
</span><span class="cx">         testMax = DateTime(nowYear + 1, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [])
</span><span class="cx"> 
</span><span class="cx">         # Lower none and upper bounds exceeded
</span><span class="cx">         testMin = None
</span><span class="cx">         testMax = DateTime(nowYear + 5, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
</span><del>-        result = yield index.notExpandedWithin(testMin, testMax)
</del><ins>+        result = yield newcalendar.notExpandedWithin(testMin, testMax)
</ins><span class="cx">         self.assertEqual(result, [&quot;indexing.ics&quot;])
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1542,8 +1542,8 @@
</span><span class="cx"> 
</span><span class="cx">         # Tests on inbox - resources with properties
</span><span class="cx">         txn = self.transactionUnderTest()
</span><del>-        yield txn.homeWithUID(ECALENDARTYPE, &quot;byNameTest&quot;, create=True)
-        inbox = yield self.calendarUnderTest(txn=txn, name=&quot;inbox&quot;, home=&quot;byNameTest&quot;)
</del><ins>+        yield txn.homeWithUID(ECALENDARTYPE, &quot;user01&quot;, create=True)
+        inbox = yield self.calendarUnderTest(txn=txn, name=&quot;inbox&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         caldata = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="lines">@@ -1574,7 +1574,7 @@
</span><span class="cx">         yield _createInboxItem(&quot;4.ics&quot;, &quot;p4&quot;)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        inbox = yield self.calendarUnderTest(name=&quot;inbox&quot;, home=&quot;byNameTest&quot;)
</del><ins>+        inbox = yield self.calendarUnderTest(name=&quot;inbox&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         yield _tests(inbox)
</span><span class="cx"> 
</span><span class="cx">         resources = yield inbox.objectResourcesWithNames((&quot;1.ics&quot;,))
</span><span class="lines">@@ -2152,7 +2152,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> class CalendarObjectSplitting(CommonCommonTests, unittest.TestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     CalendarObject splitting tests
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_sql_externalpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcaldavdatastoretesttest_sql_externalpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_external.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/caldav/datastore/test/test_sql_external.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_external.py                                (rev 0)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_external.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,682 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+from twisted.internet.defer import inlineCallbacks
+
+from twext.python.clsprop import classproperty
+from txdav.common.datastore.test.util import populateCalendarsFrom
+from txdav.common.datastore.sql_tables import _BIND_MODE_READ, \
+    _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_ACCEPTED
+from txdav.common.datastore.podding.test.util import MultiStoreConduitTest
+
+
+class BaseSharingTests(MultiStoreConduitTest):
+
+    &quot;&quot;&quot;
+    Test store-based calendar sharing.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(BaseSharingTests, self).setUp()
+        yield self.populate()
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+    cal1 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid1
+DTSTART:20131122T140000
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:event 1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+    @classproperty(cache=False)
+    def requirements(cls): #@NoSelf
+        return {
+        &quot;user01&quot;: {
+            &quot;calendar&quot;: {
+                &quot;cal1.ics&quot;: (cls.cal1, None,),
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+        &quot;user02&quot;: {
+            &quot;calendar&quot;: {
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+        &quot;user03&quot;: {
+            &quot;calendar&quot;: {
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+    }
+
+
+
+class CalendarSharing(BaseSharingTests):
+
+    @inlineCallbacks
+    def test_no_shares(self):
+        &quot;&quot;&quot;
+        Test that initially there are no shares.
+        &quot;&quot;&quot;
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+
+    @inlineCallbacks
+    def test_invite_sharee(self):
+        &quot;&quot;&quot;
+        Test invite/uninvite creates/removes shares and notifications.
+        &quot;&quot;&quot;
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeView = yield calendar.inviteUserToShare(&quot;puser02&quot;, _BIND_MODE_READ, &quot;summary&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        self.assertEqual(invites[0].uid, shareeView.shareUID())
+        self.assertEqual(invites[0].ownerUID, &quot;user01&quot;)
+        self.assertEqual(invites[0].shareeUID, &quot;puser02&quot;)
+        self.assertEqual(invites[0].mode, _BIND_MODE_READ)
+        self.assertEqual(invites[0].status, _BIND_STATUS_INVITED)
+        self.assertEqual(invites[0].summary, &quot;summary&quot;)
+
+        inviteUID = shareeView.shareUID()
+        sharedName = shareeView.name()
+
+        self.assertTrue(calendar.isShared())
+
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+
+        notifyHome = yield self.otherTransactionUnderTest().notificationsWithUID(&quot;puser02&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID, ])
+        yield self.otherCommit()
+
+        # Uninvite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+
+        yield calendar.uninviteUserFromShare(&quot;puser02&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+
+        self.assertTrue(calendar.isShared())
+
+        yield self.commit()
+
+        notifyHome = yield self.otherTransactionUnderTest().notificationsWithUID(&quot;puser02&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [])
+        yield self.otherCommit()
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertTrue(calendar.isShared())
+        yield calendar.setShared(False)
+        self.assertFalse(calendar.isShared())
+
+
+    @inlineCallbacks
+    def test_accept_share(self):
+        &quot;&quot;&quot;
+        Test that invite+accept creates shares and notifications.
+        &quot;&quot;&quot;
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeView = yield calendar.inviteUserToShare(&quot;puser02&quot;, _BIND_MODE_READ, &quot;summary&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+
+        inviteUID = shareeView.shareUID()
+        sharedName = shareeView.name()
+
+        self.assertTrue(calendar.isShared())
+
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+
+        notifyHome = yield self.otherTransactionUnderTest().notificationsWithUID(&quot;puser02&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(len(notifications), 1)
+        yield self.otherCommit()
+
+        # Accept
+        txn2 = self.newOtherTransaction()
+        shareeHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        yield shareeHome.acceptShare(inviteUID)
+
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is not None)
+        yield self.otherCommit()
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID + &quot;-reply&quot;, ])
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertTrue(calendar.isShared())
+
+        yield self.commit()
+
+        # Re-accept
+        txn2 = self.newOtherTransaction()
+        shareeHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        yield shareeHome.acceptShare(inviteUID)
+
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is not None)
+        yield self.otherCommit()
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID + &quot;-reply&quot;, ])
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertTrue(calendar.isShared())
+
+
+    @inlineCallbacks
+    def test_decline_share(self):
+        &quot;&quot;&quot;
+        Test that invite+decline does not create shares but does create notifications.
+        &quot;&quot;&quot;
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeView = yield calendar.inviteUserToShare(&quot;puser02&quot;, _BIND_MODE_READ, &quot;summary&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+
+        inviteUID = shareeView.shareUID()
+        sharedName = shareeView.name()
+
+        self.assertTrue(calendar.isShared())
+
+        yield self.commit()
+
+        txn2 = self.newOtherTransaction()
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+
+        notifyHome = yield txn2.notificationsWithUID(&quot;puser02&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(len(notifications), 1)
+        yield self.otherCommit()
+
+        # Decline
+        txn2 = self.newOtherTransaction()
+        shareeHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        yield shareeHome.declineShare(inviteUID)
+
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+        yield self.otherCommit()
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID + &quot;-reply&quot;, ])
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertTrue(calendar.isShared())
+
+        yield self.commit()
+
+        # Redecline
+        txn2 = self.newOtherTransaction()
+        shareeHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        yield shareeHome.declineShare(inviteUID)
+
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+        yield self.otherCommit()
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID + &quot;-reply&quot;, ])
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertTrue(calendar.isShared())
+
+
+    @inlineCallbacks
+    def test_accept_decline_share(self):
+        &quot;&quot;&quot;
+        Test that invite+accept/decline creates/removes shares and notifications.
+        Decline via the home.
+        &quot;&quot;&quot;
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeView = yield calendar.inviteUserToShare(&quot;puser02&quot;, _BIND_MODE_READ, &quot;summary&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        inviteUID = shareeView.shareUID()
+
+        sharedName = shareeView.name()
+
+        self.assertTrue(calendar.isShared())
+
+        yield self.commit()
+
+        txn2 = self.newOtherTransaction()
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+
+        notifyHome = yield txn2.notificationsWithUID(&quot;puser02&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(len(notifications), 1)
+        yield self.otherCommit()
+
+        # Accept
+        txn2 = self.newOtherTransaction()
+        shareeHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        yield shareeHome.acceptShare(inviteUID)
+
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is not None)
+        yield self.otherCommit()
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID + &quot;-reply&quot;, ])
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertTrue(calendar.isShared())
+
+        yield self.commit()
+
+        # Decline
+        txn2 = self.newOtherTransaction()
+        shareeHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        yield shareeHome.declineShare(inviteUID)
+
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+        yield self.otherCommit()
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID + &quot;-reply&quot;, ])
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertTrue(calendar.isShared())
+
+
+    @inlineCallbacks
+    def test_accept_remove_share(self):
+        &quot;&quot;&quot;
+        Test that invite+accept/decline creates/removes shares and notifications.
+        Decline via the shared collection (removal).
+        &quot;&quot;&quot;
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+
+        shareeView = yield calendar.inviteUserToShare(&quot;puser02&quot;, _BIND_MODE_READ, &quot;summary&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+
+        inviteUID = shareeView.shareUID()
+        sharedName = shareeView.name()
+
+        yield self.commit()
+
+        txn2 = self.newOtherTransaction()
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+
+        notifyHome = yield txn2.notificationsWithUID(&quot;puser02&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(len(notifications), 1)
+        yield self.otherCommit()
+
+        # Accept
+        txn2 = self.newOtherTransaction()
+        shareeHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        yield shareeHome.acceptShare(inviteUID)
+
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is not None)
+        yield self.otherCommit()
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID + &quot;-reply&quot;, ])
+
+        yield self.commit()
+
+        # Delete
+        txn2 = self.newOtherTransaction()
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        yield shared.deleteShare()
+        yield self.otherCommit()
+
+        txn2 = self.newOtherTransaction()
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertTrue(shared is None)
+        yield self.otherCommit()
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(notifications, [inviteUID + &quot;-reply&quot;, ])
+
+
+    @inlineCallbacks
+    def test_accept_remove_accept(self):
+        yield self.createShare()
+        yield self.removeShare()
+        shared_name = yield self.createShare()
+
+        txn2 = self.newOtherTransaction()
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=shared_name)
+        self.assertTrue(otherCal is not None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_accept_remove_accept_newcalendar(self):
+        &quot;&quot;&quot;
+        Test that deleting and re-creating a share with the same sharer name works.
+        &quot;&quot;&quot;
+
+        home = yield self.homeUnderTest(name=&quot;user01&quot;, create=True)
+        yield home.createCalendarWithName(&quot;shared&quot;)
+        yield self.commit()
+
+        shared_name = yield self.createShare(name=&quot;shared&quot;)
+
+        txn2 = self.newOtherTransaction()
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=shared_name)
+        self.assertTrue(otherCal is not None)
+        yield self.otherCommit()
+
+        yield self.removeShare(name=&quot;shared&quot;)
+        home = yield self.homeUnderTest(name=&quot;user01&quot;, create=True)
+        yield home.removeCalendarWithName(&quot;shared&quot;)
+        yield self.commit()
+
+        txn2 = self.newOtherTransaction()
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=shared_name)
+        self.assertTrue(otherCal is None)
+        yield self.otherCommit()
+
+        home = yield self.homeUnderTest(name=&quot;user01&quot;, create=True)
+        yield home.createCalendarWithName(&quot;shared&quot;)
+        yield self.commit()
+
+        shared_name = yield self.createShare(name=&quot;shared&quot;)
+
+        txn2 = self.newOtherTransaction()
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=shared_name)
+        self.assertTrue(otherCal is not None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_inviteProperties(self):
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield calendar.setUsedForFreeBusy(True)
+        yield self.commit()
+
+        shared_name = yield self.createShare()
+
+        txn2 = self.newOtherTransaction()
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=shared_name)
+        self.assertFalse(shared.isUsedForFreeBusy())
+
+
+    @inlineCallbacks
+    def test_direct_sharee(self):
+        &quot;&quot;&quot;
+        Test invite/uninvite creates/removes shares and notifications.
+        &quot;&quot;&quot;
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeView = yield calendar.directShareWithUser(&quot;puser02&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        self.assertEqual(invites[0].uid, shareeView.shareUID())
+        self.assertEqual(invites[0].ownerUID, &quot;user01&quot;)
+        self.assertEqual(invites[0].shareeUID, &quot;puser02&quot;)
+        self.assertEqual(invites[0].mode, _BIND_MODE_DIRECT)
+        self.assertEqual(invites[0].status, _BIND_STATUS_ACCEPTED)
+
+        sharedName = shareeView.name()
+
+        yield self.commit()
+
+        txn2 = self.newOtherTransaction()
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;user02&quot;, name=sharedName)
+        self.assertTrue(shared is not None)
+
+        notifyHome = yield txn2.notificationsWithUID(&quot;user02&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(len(notifications), 0)
+        yield self.otherCommit()
+
+        # Remove
+        txn2 = self.newOtherTransaction()
+        shared = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        yield shared.deleteShare()
+        yield self.otherCommit()
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+
+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
+        notifications = yield notifyHome.listNotificationObjects()
+        self.assertEqual(len(notifications), 0)
+
+    test_direct_sharee.skip = True
+
+    @inlineCallbacks
+    def test_sharedNotifierID(self):
+        shared_name = yield self.createShare()
+
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        self.assertEquals(home.notifierID(), (&quot;CalDAV&quot;, &quot;user01&quot;,))
+        calendar = yield home.calendarWithName(&quot;calendar&quot;)
+        self.assertEquals(calendar.notifierID(), (&quot;CalDAV&quot;, &quot;user01/calendar&quot;,))
+        yield self.commit()
+
+        txn2 = self.newOtherTransaction()
+        home = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        self.assertEquals(home.notifierID(), (&quot;CalDAV&quot;, &quot;puser02&quot;,))
+        calendar = yield home.calendarWithName(shared_name)
+        self.assertEquals(calendar.notifierID(), (&quot;CalDAV&quot;, &quot;user01/calendar&quot;,))
+
+
+    @inlineCallbacks
+    def test_sharedWithTwo(self):
+        shared_name1 = yield self.createShare(shareeGUID=&quot;puser02&quot;)
+        shared_name2 = yield self.createShare(shareeGUID=&quot;puser03&quot;)
+
+        txn2 = self.newOtherTransaction()
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=shared_name1)
+        self.assertTrue(otherCal is not None)
+        yield self.otherCommit()
+
+        txn2 = self.newOtherTransaction()
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser03&quot;, name=shared_name2)
+        self.assertTrue(otherCal is not None)
+        yield self.otherCommit()
+
+
+
+class SharingRevisions(BaseSharingTests):
+    &quot;&quot;&quot;
+    Test store-based sharing and interaction with revision table.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def test_shareWithRevision(self):
+        &quot;&quot;&quot;
+        Verify that bindRevision on calendars and shared calendars has the correct value.
+        &quot;&quot;&quot;
+        sharedName = yield self.createShare()
+
+        normalCal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertEqual(normalCal._bindRevision, 0)
+
+        txn2 = self.newOtherTransaction()
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertNotEqual(otherCal._bindRevision, 0)
+
+
+    @inlineCallbacks
+    def test_updateShareRevision(self):
+        &quot;&quot;&quot;
+        Verify that bindRevision on calendars and shared calendars has the correct value.
+        &quot;&quot;&quot;
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+
+        shareeView = yield calendar.inviteUserToShare(&quot;puser02&quot;, _BIND_MODE_READ, &quot;summary&quot;)
+        newCalName = shareeView.shareUID()
+        yield self.commit()
+
+        normalCal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertEqual(normalCal._bindRevision, 0)
+        yield self.commit()
+
+        txn2 = self.newOtherTransaction()
+        otherHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        otherCal = yield otherHome.anyObjectWithShareUID(newCalName)
+        self.assertEqual(otherCal._bindRevision, 0)
+        yield self.otherCommit()
+
+        txn2 = self.newOtherTransaction()
+        shareeHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        shareeView = yield shareeHome.acceptShare(newCalName)
+        sharedName = shareeView.name()
+        yield self.otherCommit()
+
+        normalCal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertEqual(normalCal._bindRevision, 0)
+
+        txn2 = self.newOtherTransaction()
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertNotEqual(otherCal._bindRevision, 0)
+
+
+    @inlineCallbacks
+    def test_sharedRevisions(self):
+        &quot;&quot;&quot;
+        Verify that resourceNamesSinceRevision returns all resources after initial bind and sync.
+        &quot;&quot;&quot;
+        sharedName = yield self.createShare()
+
+        normalCal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        self.assertEqual(normalCal._bindRevision, 0)
+
+        txn2 = self.newOtherTransaction()
+        otherHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+        otherCal = yield self.calendarUnderTest(txn=txn2, home=&quot;puser02&quot;, name=sharedName)
+        self.assertNotEqual(otherCal._bindRevision, 0)
+
+        sync_token = yield otherCal.syncToken()
+        revision = otherCal.revisionFromToken(sync_token)
+
+        changed, deleted, invalid = yield otherCal.resourceNamesSinceRevision(0)
+        self.assertNotEqual(len(changed), 0)
+        self.assertEqual(len(deleted), 0)
+        self.assertEqual(len(invalid), 0)
+
+        changed, deleted, invalid = yield otherCal.resourceNamesSinceRevision(revision)
+        self.assertEqual(len(changed), 0)
+        self.assertEqual(len(deleted), 0)
+        self.assertEqual(len(invalid), 0)
+
+        sync_token = yield otherHome.syncToken()
+        revision = otherHome.revisionFromToken(sync_token)
+
+        for depth in (&quot;1&quot;, &quot;infinity&quot;,):
+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(revision - 1, depth)
+            self.assertEqual(len(changed), 0 if depth == &quot;infinity&quot; else 1)
+            self.assertEqual(len(deleted), 0)
+            self.assertEqual(len(invalid), 1 if depth == &quot;infinity&quot; else 0)
+
+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(revision, depth)
+            self.assertEqual(len(changed), 0)
+            self.assertEqual(len(deleted), 0)
+            self.assertEqual(len(invalid), 1 if depth == &quot;infinity&quot; else 0)
+
+        yield self.otherCommit()
+
+        yield self.removeShare()
+
+        txn2 = self.newOtherTransaction()
+        otherHome = yield self.homeUnderTest(txn=txn2, name=&quot;puser02&quot;)
+
+        for depth in (&quot;1&quot;, &quot;infinity&quot;,):
+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(revision, depth)
+            self.assertEqual(len(changed), 0)
+            self.assertEqual(len(deleted), 1)
+            self.assertEqual(len(invalid), 0)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_sql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_sql_sharing.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -554,19 +554,23 @@
</span><span class="cx">         otherCal = yield self.calendarUnderTest(home=&quot;user02&quot;, name=sharedName)
</span><span class="cx">         self.assertNotEqual(otherCal._bindRevision, 0)
</span><span class="cx"> 
</span><del>-        changed, deleted = yield otherCal.resourceNamesSinceRevision(0)
</del><ins>+        changed, deleted, invalid = yield otherCal.resourceNamesSinceRevision(0)
</ins><span class="cx">         self.assertNotEqual(len(changed), 0)
</span><span class="cx">         self.assertEqual(len(deleted), 0)
</span><ins>+        self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><del>-        changed, deleted = yield otherCal.resourceNamesSinceRevision(otherCal._bindRevision)
</del><ins>+        changed, deleted, invalid = yield otherCal.resourceNamesSinceRevision(otherCal._bindRevision)
</ins><span class="cx">         self.assertEqual(len(changed), 0)
</span><span class="cx">         self.assertEqual(len(deleted), 0)
</span><ins>+        self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><span class="cx">         for depth in (&quot;1&quot;, &quot;infinity&quot;,):
</span><del>-            changed, deleted = yield otherHome.resourceNamesSinceRevision(0, depth)
</del><ins>+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(0, depth)
</ins><span class="cx">             self.assertNotEqual(len(changed), 0)
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+            self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><del>-            changed, deleted = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision, depth)
</del><ins>+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(otherCal._bindRevision, depth)
</ins><span class="cx">             self.assertEqual(len(changed), 0)
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+            self.assertEqual(len(invalid), 0)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretesttest_utilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/test_util.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -13,7 +13,6 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><del>-from txdav.caldav.datastore.test.util import buildCalendarStore
</del><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Tests for txdav.caldav.datastore.util.
</span><span class="lines">@@ -30,6 +29,7 @@
</span><span class="cx"> from twistedcaldav.test.util import TestCase
</span><span class="cx"> 
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
</span><ins>+from txdav.caldav.datastore.test.util import buildCalendarStore
</ins><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.util import dropboxIDFromCalendarObject, \
</span><span class="cx">     StorageTransportBase, migrateHome
</span><span class="lines">@@ -323,7 +323,12 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(HomeMigrationTests, self).setUp()
</span><del>-        self.theStore = yield buildCalendarStore(self, self.notifierFactory, homes=(&quot;conflict1&quot;, &quot;conflict2&quot;,))
</del><ins>+        self.theStore = yield buildCalendarStore(self, self.notifierFactory, homes=(
+            &quot;conflict1&quot;,
+            &quot;conflict2&quot;,
+            &quot;empty_home&quot;,
+            &quot;non_empty_home&quot;,
+        ))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def storeUnderTest(self):
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/test/util.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/test/util.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/util.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -62,18 +62,13 @@
</span><span class="cx">         calendarUserAddresses,
</span><span class="cx">         cutype=&quot;INDIVIDUAL&quot;,
</span><span class="cx">         thisServer=True,
</span><ins>+        server=None,
</ins><span class="cx">         extras={},
</span><span class="cx">     ):
</span><span class="cx"> 
</span><del>-        super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames,
-            fullName, extras=extras)
-        self.uid = uid
-        self.shortNames = shortNames
-        self.fullName = fullName
-        self.displayName = self.fullName if self.fullName else self.shortNames[0]
</del><ins>+        super(TestCalendarStoreDirectoryRecord, self).__init__(uid, shortNames, fullName, thisServer, server, extras=extras)
</ins><span class="cx">         self.calendarUserAddresses = calendarUserAddresses
</span><span class="cx">         self.cutype = cutype
</span><del>-        self._thisServer = thisServer
</del><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def canonicalCalendarUserAddress(self):
</span><span class="lines">@@ -92,10 +87,6 @@
</span><span class="cx">         return cua
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def thisServer(self):
-        return self._thisServer
-
-
</del><span class="cx">     def calendarsEnabled(self):
</span><span class="cx">         return True
</span><span class="cx"> 
</span><span class="lines">@@ -150,12 +141,19 @@
</span><span class="cx">     homes.update((
</span><span class="cx">         &quot;home1&quot;,
</span><span class="cx">         &quot;home2&quot;,
</span><del>-        &quot;Home_attachments&quot;,
</del><ins>+        &quot;home3&quot;,
+        &quot;home_attachments&quot;,
</ins><span class="cx">         &quot;home_bad&quot;,
</span><span class="cx">         &quot;home_defaults&quot;,
</span><span class="cx">         &quot;home_no_splits&quot;,
</span><ins>+        &quot;home_provision1&quot;,
+        &quot;home_provision2&quot;,
</ins><span class="cx">         &quot;home_splits&quot;,
</span><span class="cx">         &quot;home_splits_shared&quot;,
</span><ins>+        &quot;uid1&quot;,
+        &quot;uid2&quot;,
+        &quot;new-home&quot;,
+        &quot;xyzzy&quot;,
</ins><span class="cx">     ))
</span><span class="cx">     for uid in homes:
</span><span class="cx">         directory.addRecord(buildDirectoryRecord(uid))
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavicalendardirectoryservicepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/icalendardirectoryservice.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/icalendardirectoryservice.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/icalendardirectoryservice.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -60,14 +60,6 @@
</span><span class="cx">         @rtype: C{str}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    def thisServer(): #@NoSelf
-        &quot;&quot;&quot;
-        Indicates whether the record is hosted on this server &quot;pod&quot;.
-
-        @return: C{True} if hosted by this service.
-        @rtype: C{bool}
-        &quot;&quot;&quot;
-
</del><span class="cx">     def calendarsEnabled(): #@NoSelf
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Indicates whether the record enabled for using the calendar service.
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavicalendarstorepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/icalendarstore.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/icalendarstore.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/caldav/icalendarstore.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -907,7 +907,7 @@
</span><span class="cx">     NORMAL_NO_IMPLICIT -    this is an application layer (user) generated remove that deliberately turns
</span><span class="cx">                             off implicit scheduling operations.
</span><span class="cx"> 
</span><del>-    INTERNAL -              remove the resource without implicit scheduling.
</del><ins>+    INTERNAL -              remove the resource without implicit scheduling or attachment processing.
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     NORMAL = NamedConstant()
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoreindex_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/index_file.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/index_file.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/carddav/datastore/index_file.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -37,10 +37,6 @@
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import maybeDeferred
</span><span class="cx"> 
</span><del>-from twistedcaldav import carddavxml
-from txdav.common.icommondatastore import SyncTokenValidException, \
-    ReservationError
-from twistedcaldav.query import addressbookquery
</del><span class="cx"> from twistedcaldav.sql import AbstractSQLDatabase
</span><span class="cx"> from twistedcaldav.sql import db_prefix
</span><span class="cx"> from twistedcaldav.vcard import Component
</span><span class="lines">@@ -49,6 +45,12 @@
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.memcachepool import CachePoolUserMixIn
</span><span class="cx"> 
</span><ins>+from txdav.carddav.datastore.query.builder import buildExpression
+from txdav.carddav.datastore.query.filter import Filter
+from txdav.common.datastore.query.filegenerator import sqllitegenerator
+from txdav.common.icommondatastore import SyncTokenValidException, \
+    ReservationError
+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> db_basename = db_prefix + &quot;sqlite&quot;
</span><span class="lines">@@ -218,6 +220,24 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def sqladdressbookquery(filter, addressbookid=None):
+    &quot;&quot;&quot;
+    Convert the supplied addressbook-query into a partial SQL statement.
+
+    @param filter: the L{Filter} for the addressbook-query to convert.
+    @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+            Or return C{None} if it is not possible to create an SQL query to fully match the addressbook-query.
+    &quot;&quot;&quot;
+    try:
+        expression = buildExpression(filter, sqllitegenerator.FIELDS)
+        sql = sqllitegenerator(expression, addressbookid, None)
+        return sql.generate()
+    except ValueError:
+        return None
+
+
+
</ins><span class="cx"> class AddressBookIndex(AbstractSQLDatabase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     AddressBook collection index abstract base class that defines the apis for the index.
</span><span class="lines">@@ -410,6 +430,7 @@
</span><span class="cx"> 
</span><span class="cx">         changed = []
</span><span class="cx">         deleted = []
</span><ins>+        invalid = []
</ins><span class="cx">         for name, wasdeleted in results:
</span><span class="cx">             if name:
</span><span class="cx">                 if wasdeleted == 'Y':
</span><span class="lines">@@ -420,7 +441,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 raise SyncTokenValidException
</span><span class="cx"> 
</span><del>-        return changed, deleted,
</del><ins>+        return (changed, deleted, invalid)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def lastRevision(self):
</span><span class="lines">@@ -445,8 +466,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def searchValid(self, filter):
</span><del>-        if isinstance(filter, carddavxml.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
</del><ins>+        if isinstance(filter, Filter):
+            qualifiers = sqladdressbookquery(filter)
</ins><span class="cx">         else:
</span><span class="cx">             qualifiers = None
</span><span class="cx"> 
</span><span class="lines">@@ -466,8 +487,8 @@
</span><span class="cx">         # start caching...
</span><span class="cx"> 
</span><span class="cx">         # Make sure we have a proper Filter element and get the partial SQL statement to use.
</span><del>-        if isinstance(filter, carddavxml.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
</del><ins>+        if isinstance(filter, Filter):
+            qualifiers = sqladdressbookquery(filter)
</ins><span class="cx">         else:
</span><span class="cx">             qualifiers = None
</span><span class="cx">         if qualifiers is not None:
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequery__init__py"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/carddav/datastore/query/__init__.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/__init__.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,15 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
</del></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequery__init__pyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequery__init__py"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/carddav/datastore/query/__init__.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/__init__.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/query/__init__.py                                (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequerybuilderpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/carddav/datastore/query/builder.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/builder.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/builder.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,107 +0,0 @@
</span><del>-##
-# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from txdav.common.datastore.query import expression
-from txdav.carddav.datastore.query.filter import TextMatch
-
-&quot;&quot;&quot;
-SQL statement generator from query expressions.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;buildExpression&quot;,
-]
-
-
-
-# SQL Index column (field) names
-
-def buildExpression(filter, fields):
-    &quot;&quot;&quot;
-    Convert the supplied addressbook-query into an expression tree.
-
-    @param filter: the L{Filter} for the addressbook-query to convert.
-    @return: a L{baseExpression} for the expression tree.
-    &quot;&quot;&quot;
-    # Lets assume we have a valid filter from the outset.
-
-    # Top-level filter contains zero or more prop-filter element
-    if len(filter.children) &gt; 0:
-        return propfilterListExpression(filter.children, fields)
-    else:
-        return expression.allExpression()
-
-
-
-def propfilterListExpression(propfilters, fields):
-    &quot;&quot;&quot;
-    Create an expression for a list of prop-filter elements.
-
-    @param propfilters: the C{list} of L{ComponentFilter} elements.
-    @return: a L{baseExpression} for the expression tree.
-    &quot;&quot;&quot;
-
-    if len(propfilters) == 1:
-        return propfilterExpression(propfilters[0], fields)
-    else:
-        return expression.orExpression([propfilterExpression(c, fields) for c in propfilters])
-
-
-
-def propfilterExpression(propfilter, fields):
-    &quot;&quot;&quot;
-    Create an expression for a single prop-filter element.
-
-    @param propfilter: the L{PropertyFilter} element.
-    @return: a L{baseExpression} for the expression tree.
-    &quot;&quot;&quot;
-
-    # Only handle UID right now
-    if propfilter.filter_name != &quot;UID&quot;:
-        raise ValueError
-
-    # Handle is-not-defined case
-    if not propfilter.defined:
-        # Test for &lt;&lt;field&gt;&gt; != &quot;*&quot;
-        return expression.isExpression(fields[&quot;UID&quot;], &quot;&quot;, True)
-
-    # Handle embedded parameters/text-match
-    params = []
-    for filter in propfilter.filters:
-        if isinstance(filter, TextMatch):
-            if filter.match_type == &quot;equals&quot;:
-                tm = expression.isnotExpression if filter.negate else expression.isExpression
-            elif filter.match_type == &quot;contains&quot;:
-                tm = expression.notcontainsExpression if filter.negate else expression.containsExpression
-            elif filter.match_type == &quot;starts-with&quot;:
-                tm = expression.notstartswithExpression if filter.negate else expression.startswithExpression
-            elif filter.match_type == &quot;ends-with&quot;:
-                tm = expression.notendswithExpression if filter.negate else expression.endswithExpression
-            params.append(tm(fields[propfilter.filter_name], str(filter.text), True))
-        else:
-            # No embedded parameters - not right now as our Index does not handle them
-            raise ValueError
-
-    # Now build return expression
-    if len(params) &gt; 1:
-        if propfilter.propfilter_test == &quot;anyof&quot;:
-            return expression.orExpression(params)
-        else:
-            return expression.andExpression(params)
-    elif len(params) == 1:
-        return params[0]
-    else:
-        return None
</del></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequerybuilderpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerybuilderpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/carddav/datastore/query/builder.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/builder.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/query/builder.py                                (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/builder.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,107 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from txdav.common.datastore.query import expression
+from txdav.carddav.datastore.query.filter import TextMatch
+
+&quot;&quot;&quot;
+SQL statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;buildExpression&quot;,
+]
+
+
+
+# SQL Index column (field) names
+
+def buildExpression(filter, fields):
+    &quot;&quot;&quot;
+    Convert the supplied addressbook-query into an expression tree.
+
+    @param filter: the L{Filter} for the addressbook-query to convert.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+    # Lets assume we have a valid filter from the outset.
+
+    # Top-level filter contains zero or more prop-filter element
+    if len(filter.children) &gt; 0:
+        return propfilterListExpression(filter.children, fields)
+    else:
+        return expression.allExpression()
+
+
+
+def propfilterListExpression(propfilters, fields):
+    &quot;&quot;&quot;
+    Create an expression for a list of prop-filter elements.
+
+    @param propfilters: the C{list} of L{ComponentFilter} elements.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    if len(propfilters) == 1:
+        return propfilterExpression(propfilters[0], fields)
+    else:
+        return expression.orExpression([propfilterExpression(c, fields) for c in propfilters])
+
+
+
+def propfilterExpression(propfilter, fields):
+    &quot;&quot;&quot;
+    Create an expression for a single prop-filter element.
+
+    @param propfilter: the L{PropertyFilter} element.
+    @return: a L{baseExpression} for the expression tree.
+    &quot;&quot;&quot;
+
+    # Only handle UID right now
+    if propfilter.filter_name != &quot;UID&quot;:
+        raise ValueError
+
+    # Handle is-not-defined case
+    if not propfilter.defined:
+        # Test for &lt;&lt;field&gt;&gt; != &quot;*&quot;
+        return expression.isExpression(fields[&quot;UID&quot;], &quot;&quot;, True)
+
+    # Handle embedded parameters/text-match
+    params = []
+    for filter in propfilter.filters:
+        if isinstance(filter, TextMatch):
+            if filter.match_type == &quot;equals&quot;:
+                tm = expression.isnotExpression if filter.negate else expression.isExpression
+            elif filter.match_type == &quot;contains&quot;:
+                tm = expression.notcontainsExpression if filter.negate else expression.containsExpression
+            elif filter.match_type == &quot;starts-with&quot;:
+                tm = expression.notstartswithExpression if filter.negate else expression.startswithExpression
+            elif filter.match_type == &quot;ends-with&quot;:
+                tm = expression.notendswithExpression if filter.negate else expression.endswithExpression
+            params.append(tm(fields[propfilter.filter_name], str(filter.text), True))
+        else:
+            # No embedded parameters - not right now as our Index does not handle them
+            raise ValueError
+
+    # Now build return expression
+    if len(params) &gt; 1:
+        if propfilter.propfilter_test == &quot;anyof&quot;:
+            return expression.orExpression(params)
+        else:
+            return expression.andExpression(params)
+    elif len(params) == 1:
+        return params[0]
+    else:
+        return None
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequeryfilterpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/carddav/datastore/query/filter.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/filter.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,444 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-&quot;&quot;&quot;
-Object model of CARDDAV:filter element used in an addressbook-query.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;Filter&quot;,
-]
-
-from twext.python.log import Logger
-
-from twistedcaldav.carddavxml import carddav_namespace
-from twistedcaldav.vcard import Property
-
-log = Logger()
-
-class FilterBase(object):
-    &quot;&quot;&quot;
-    Determines which matching components are returned.
-    &quot;&quot;&quot;
-
-    serialized_name = None
-    deserialize_names = {}
-
-    @classmethod
-    def serialize_register(cls, register):
-        cls.deserialize_names[register.serialized_name] = register
-
-
-    def __init__(self, xml_element):
-        pass
-
-
-    @classmethod
-    def deserialize(cls, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        obj = cls.deserialize_names[data[&quot;type&quot;]](None)
-        obj._deserialize(data)
-        return obj
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        pass
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        return {
-            &quot;type&quot;: self.serialized_name,
-        }
-
-
-    def match(self, item, access=None):
-        raise NotImplementedError
-
-
-    def valid(self, level=0):
-        raise NotImplementedError
-
-
-
-class Filter(FilterBase):
-    &quot;&quot;&quot;
-    Determines which matching components are returned.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;Filter&quot;
-
-    def __init__(self, xml_element):
-
-        super(Filter, self).__init__(xml_element)
-        if xml_element is None:
-            return
-
-        filter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
-        if filter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
-            raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
-
-        self.filter_test = filter_test
-
-        self.children = [PropertyFilter(child) for child in xml_element.children]
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        self.filter_test = data[&quot;filter_test&quot;]
-        self.child = [FilterBase.deserialize(child) for child in data[&quot;children&quot;]]
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        result = super(Filter, self).serialize()
-        result.update({
-            &quot;filter_test&quot;: self.filter_test,
-            &quot;children&quot;: [child.serialize() for child in self.children],
-        })
-        return result
-
-
-    def match(self, vcard):
-        &quot;&quot;&quot;
-        Returns True if the given address property matches this filter, False
-        otherwise. Empty element means always match.
-        &quot;&quot;&quot;
-
-        if len(self.children) &gt; 0:
-            allof = self.filter_test == &quot;allof&quot;
-            for propfilter in self.children:
-                if allof != propfilter._match(vcard):
-                    return not allof
-            return allof
-        else:
-            return True
-
-
-    def valid(self):
-        &quot;&quot;&quot;
-        Indicate whether this filter element's structure is valid wrt vCard
-        data object model.
-
-        @return: True if valid, False otherwise
-        &quot;&quot;&quot;
-
-        # Test each property
-        for propfilter in self.children:
-            if not propfilter.valid():
-                return False
-        else:
-            return True
-
-FilterBase.serialize_register(Filter)
-
-
-
-class FilterChildBase(FilterBase):
-    &quot;&quot;&quot;
-    CardDAV filter element.
-    &quot;&quot;&quot;
-
-    def __init__(self, xml_element):
-
-        super(FilterChildBase, self).__init__(xml_element)
-        if xml_element is None:
-            return
-
-        qualifier = None
-        filters = []
-
-        for child in xml_element.children:
-            qname = child.qname()
-
-            if qname in (
-                (carddav_namespace, &quot;is-not-defined&quot;),
-            ):
-                if qualifier is not None:
-                    raise ValueError(&quot;Only one of CardDAV:is-not-defined allowed&quot;)
-                qualifier = IsNotDefined(child)
-
-            elif qname == (carddav_namespace, &quot;text-match&quot;):
-                filters.append(TextMatch(child))
-
-            elif qname == (carddav_namespace, &quot;param-filter&quot;):
-                filters.append(ParameterFilter(child))
-            else:
-                raise ValueError(&quot;Unknown child element: %s&quot; % (qname,))
-
-        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
-            raise ValueError(&quot;No other tests allowed when CardDAV:is-not-defined is present&quot;)
-
-        if xml_element.qname() == (carddav_namespace, &quot;prop-filter&quot;):
-            propfilter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
-            if propfilter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
-                raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
-        else:
-            propfilter_test = &quot;anyof&quot;
-
-        self.propfilter_test = propfilter_test
-        self.qualifier = qualifier
-        self.filters = filters
-        self.filter_name = xml_element.attributes[&quot;name&quot;]
-        if isinstance(self.filter_name, unicode):
-            self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
-        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        self.propfilter_test = data[&quot;propfilter_test&quot;]
-        self.qualifier = FilterBase.deserialize(data[&quot;qualifier&quot;]) if data[&quot;qualifier&quot;] else None
-        self.filters = [FilterBase.deserialize(filter) for filter in data[&quot;filters&quot;]]
-        self.filter_name = data[&quot;filter_name&quot;]
-        self.defined = data[&quot;defined&quot;]
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        result = super(FilterChildBase, self).serialize()
-        result.update({
-            &quot;propfilter_test&quot;: self.propfilter_test,
-            &quot;qualifier&quot;: self.qualifier.serialize() if self.qualifier else None,
-            &quot;filters&quot;: [filter.serialize() for filter in self.filters],
-            &quot;filter_name&quot;: self.filter_name,
-            &quot;defined&quot;: self.defined,
-        })
-        return result
-
-
-    def match(self, item):
-        &quot;&quot;&quot;
-        Returns True if the given address book item (either a property or parameter value)
-        matches this filter, False otherwise.
-        &quot;&quot;&quot;
-
-        # Always return True for the is-not-defined case as the result of this will
-        # be negated by the caller
-        if not self.defined:
-            return True
-
-        if self.qualifier and not self.qualifier.match(item):
-            return False
-
-        if len(self.filters) &gt; 0:
-            allof = self.propfilter_test == &quot;allof&quot;
-            for filter in self.filters:
-                if allof != filter._match(item):
-                    return not allof
-            return allof
-        else:
-            return True
-
-
-
-class PropertyFilter (FilterChildBase):
-    &quot;&quot;&quot;
-    Limits a search to specific properties.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;PropertyFilter&quot;
-
-    def _match(self, vcard):
-        # At least one property must match (or is-not-defined is set)
-        for property in vcard.properties():
-            if property.name().upper() == self.filter_name.upper() and self.match(property):
-                break
-        else:
-            return not self.defined
-        return self.defined
-
-
-    def valid(self):
-        &quot;&quot;&quot;
-        Indicate whether this filter element's structure is valid wrt vCard
-        data object model.
-
-        @return:      True if valid, False otherwise
-        &quot;&quot;&quot;
-
-        # No tests
-        return True
-
-FilterBase.serialize_register(PropertyFilter)
-
-
-
-class ParameterFilter (FilterChildBase):
-    &quot;&quot;&quot;
-    Limits a search to specific parameters.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;ParameterFilter&quot;
-
-    def _match(self, property):
-
-        # At least one parameter must match (or is-not-defined is set)
-        result = not self.defined
-        for parameterName in property.parameterNames():
-            if parameterName.upper() == self.filter_name.upper() and self.match([property.parameterValues(parameterName)]):
-                result = self.defined
-                break
-
-        return result
-
-FilterBase.serialize_register(ParameterFilter)
-
-
-
-class IsNotDefined (FilterBase):
-    &quot;&quot;&quot;
-    Specifies that the named iCalendar item does not exist.
-    &quot;&quot;&quot;
-
-    serialized_name = &quot;IsNotDefined&quot;
-
-    def match(self, component, access=None):
-        # Oddly, this needs always to return True so that it appears there is
-        # a match - but we then &quot;negate&quot; the result if is-not-defined is set.
-        # Actually this method should never be called as we special case the
-        # is-not-defined option.
-        return True
-
-FilterBase.serialize_register(IsNotDefined)
-
-
-
-class TextMatch (FilterBase):
-    &quot;&quot;&quot;
-    Specifies a substring match on a property or parameter value.
-    &quot;&quot;&quot;
-    serialized_name = &quot;TextMatch&quot;
-
-    def __init__(self, xml_element):
-
-        super(TextMatch, self).__init__(xml_element)
-        if xml_element is None:
-            return
-
-        self.text = str(xml_element)
-
-        if &quot;collation&quot; in xml_element.attributes:
-            self.collation = xml_element.attributes[&quot;collation&quot;]
-        else:
-            self.collation = &quot;i;unicode-casemap&quot;
-
-        if &quot;negate-condition&quot; in xml_element.attributes:
-            self.negate = xml_element.attributes[&quot;negate-condition&quot;]
-            if self.negate not in (&quot;yes&quot;, &quot;no&quot;):
-                self.negate = &quot;no&quot;
-            self.negate = {&quot;yes&quot;: True, &quot;no&quot;: False}[self.negate]
-        else:
-            self.negate = False
-
-        if &quot;match-type&quot; in xml_element.attributes:
-            self.match_type = xml_element.attributes[&quot;match-type&quot;]
-            if self.match_type not in (
-                &quot;equals&quot;,
-                &quot;contains&quot;,
-                &quot;starts-with&quot;,
-                &quot;ends-with&quot;,
-            ):
-                self.match_type = &quot;contains&quot;
-        else:
-            self.match_type = &quot;contains&quot;
-
-
-    def _deserialize(self, data):
-        &quot;&quot;&quot;
-        Convert a JSON compatible serialization of this object into the actual object.
-        &quot;&quot;&quot;
-        self.text = data[&quot;text&quot;]
-        self.collation = data[&quot;collation&quot;]
-        self.negate = data[&quot;negate&quot;]
-        self.match_type = data[&quot;match_type&quot;]
-
-
-    def serialize(self):
-        &quot;&quot;&quot;
-        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
-        &quot;&quot;&quot;
-        result = super(TextMatch, self).serialize()
-        result.update({
-            &quot;text&quot;: self.text,
-            &quot;collation&quot;: self.collation,
-            &quot;negate&quot;: self.negate,
-            &quot;match_type&quot;: self.match_type,
-        })
-        return result
-
-
-    def _match(self, item):
-        &quot;&quot;&quot;
-        Match the text for the item.
-        If the item is a property, then match the property value,
-        otherwise it may be a list of parameter values - try to match anyone of those
-        &quot;&quot;&quot;
-        if item is None:
-            return False
-
-        if isinstance(item, Property):
-            values = [item.strvalue()]
-        else:
-            values = item
-
-        test = unicode(self.text, &quot;utf-8&quot;).lower()
-
-
-        def _textCompare(s):
-            # Currently ignores the collation and does caseless matching
-            s = s.lower()
-
-            if self.match_type == &quot;equals&quot;:
-                return s == test
-            elif self.match_type == &quot;contains&quot;:
-                return s.find(test) != -1
-            elif self.match_type == &quot;starts-with&quot;:
-                return s.startswith(test)
-            elif self.match_type == &quot;ends-with&quot;:
-                return s.endswith(test)
-            else:
-                return False
-
-        for value in values:
-            # NB Its possible that we have a text list value which appears as a Python list,
-            # so we need to check for that and iterate over the list.
-            if isinstance(value, list):
-                for subvalue in value:
-                    if _textCompare(unicode(subvalue, &quot;utf-8&quot;)):
-                        return not self.negate
-            else:
-                if _textCompare(unicode(value, &quot;utf-8&quot;)):
-                    return not self.negate
-
-        return self.negate
-
-FilterBase.serialize_register(TextMatch)
</del></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequeryfilterpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequeryfilterpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/carddav/datastore/query/filter.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/filter.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/query/filter.py                                (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/filter.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,444 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+&quot;&quot;&quot;
+Object model of CARDDAV:filter element used in an addressbook-query.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;Filter&quot;,
+]
+
+from twext.python.log import Logger
+
+from twistedcaldav.carddavxml import carddav_namespace
+from twistedcaldav.vcard import Property
+
+log = Logger()
+
+class FilterBase(object):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    serialized_name = None
+    deserialize_names = {}
+
+    @classmethod
+    def serialize_register(cls, register):
+        cls.deserialize_names[register.serialized_name] = register
+
+
+    def __init__(self, xml_element):
+        pass
+
+
+    @classmethod
+    def deserialize(cls, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        obj = cls.deserialize_names[data[&quot;type&quot;]](None)
+        obj._deserialize(data)
+        return obj
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        pass
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        return {
+            &quot;type&quot;: self.serialized_name,
+        }
+
+
+    def match(self, item, access=None):
+        raise NotImplementedError
+
+
+    def valid(self, level=0):
+        raise NotImplementedError
+
+
+
+class Filter(FilterBase):
+    &quot;&quot;&quot;
+    Determines which matching components are returned.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;Filter&quot;
+
+    def __init__(self, xml_element):
+
+        super(Filter, self).__init__(xml_element)
+        if xml_element is None:
+            return
+
+        filter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
+        if filter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
+            raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
+
+        self.filter_test = filter_test
+
+        self.children = [PropertyFilter(child) for child in xml_element.children]
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        self.filter_test = data[&quot;filter_test&quot;]
+        self.child = [FilterBase.deserialize(child) for child in data[&quot;children&quot;]]
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        result = super(Filter, self).serialize()
+        result.update({
+            &quot;filter_test&quot;: self.filter_test,
+            &quot;children&quot;: [child.serialize() for child in self.children],
+        })
+        return result
+
+
+    def match(self, vcard):
+        &quot;&quot;&quot;
+        Returns True if the given address property matches this filter, False
+        otherwise. Empty element means always match.
+        &quot;&quot;&quot;
+
+        if len(self.children) &gt; 0:
+            allof = self.filter_test == &quot;allof&quot;
+            for propfilter in self.children:
+                if allof != propfilter._match(vcard):
+                    return not allof
+            return allof
+        else:
+            return True
+
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt vCard
+        data object model.
+
+        @return: True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # Test each property
+        for propfilter in self.children:
+            if not propfilter.valid():
+                return False
+        else:
+            return True
+
+FilterBase.serialize_register(Filter)
+
+
+
+class FilterChildBase(FilterBase):
+    &quot;&quot;&quot;
+    CardDAV filter element.
+    &quot;&quot;&quot;
+
+    def __init__(self, xml_element):
+
+        super(FilterChildBase, self).__init__(xml_element)
+        if xml_element is None:
+            return
+
+        qualifier = None
+        filters = []
+
+        for child in xml_element.children:
+            qname = child.qname()
+
+            if qname in (
+                (carddav_namespace, &quot;is-not-defined&quot;),
+            ):
+                if qualifier is not None:
+                    raise ValueError(&quot;Only one of CardDAV:is-not-defined allowed&quot;)
+                qualifier = IsNotDefined(child)
+
+            elif qname == (carddav_namespace, &quot;text-match&quot;):
+                filters.append(TextMatch(child))
+
+            elif qname == (carddav_namespace, &quot;param-filter&quot;):
+                filters.append(ParameterFilter(child))
+            else:
+                raise ValueError(&quot;Unknown child element: %s&quot; % (qname,))
+
+        if qualifier and isinstance(qualifier, IsNotDefined) and (len(filters) != 0):
+            raise ValueError(&quot;No other tests allowed when CardDAV:is-not-defined is present&quot;)
+
+        if xml_element.qname() == (carddav_namespace, &quot;prop-filter&quot;):
+            propfilter_test = xml_element.attributes.get(&quot;test&quot;, &quot;anyof&quot;)
+            if propfilter_test not in (&quot;anyof&quot;, &quot;allof&quot;):
+                raise ValueError(&quot;Test must be only one of anyof, allof&quot;)
+        else:
+            propfilter_test = &quot;anyof&quot;
+
+        self.propfilter_test = propfilter_test
+        self.qualifier = qualifier
+        self.filters = filters
+        self.filter_name = xml_element.attributes[&quot;name&quot;]
+        if isinstance(self.filter_name, unicode):
+            self.filter_name = self.filter_name.encode(&quot;utf-8&quot;)
+        self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        self.propfilter_test = data[&quot;propfilter_test&quot;]
+        self.qualifier = FilterBase.deserialize(data[&quot;qualifier&quot;]) if data[&quot;qualifier&quot;] else None
+        self.filters = [FilterBase.deserialize(filter) for filter in data[&quot;filters&quot;]]
+        self.filter_name = data[&quot;filter_name&quot;]
+        self.defined = data[&quot;defined&quot;]
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        result = super(FilterChildBase, self).serialize()
+        result.update({
+            &quot;propfilter_test&quot;: self.propfilter_test,
+            &quot;qualifier&quot;: self.qualifier.serialize() if self.qualifier else None,
+            &quot;filters&quot;: [filter.serialize() for filter in self.filters],
+            &quot;filter_name&quot;: self.filter_name,
+            &quot;defined&quot;: self.defined,
+        })
+        return result
+
+
+    def match(self, item):
+        &quot;&quot;&quot;
+        Returns True if the given address book item (either a property or parameter value)
+        matches this filter, False otherwise.
+        &quot;&quot;&quot;
+
+        # Always return True for the is-not-defined case as the result of this will
+        # be negated by the caller
+        if not self.defined:
+            return True
+
+        if self.qualifier and not self.qualifier.match(item):
+            return False
+
+        if len(self.filters) &gt; 0:
+            allof = self.propfilter_test == &quot;allof&quot;
+            for filter in self.filters:
+                if allof != filter._match(item):
+                    return not allof
+            return allof
+        else:
+            return True
+
+
+
+class PropertyFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific properties.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;PropertyFilter&quot;
+
+    def _match(self, vcard):
+        # At least one property must match (or is-not-defined is set)
+        for property in vcard.properties():
+            if property.name().upper() == self.filter_name.upper() and self.match(property):
+                break
+        else:
+            return not self.defined
+        return self.defined
+
+
+    def valid(self):
+        &quot;&quot;&quot;
+        Indicate whether this filter element's structure is valid wrt vCard
+        data object model.
+
+        @return:      True if valid, False otherwise
+        &quot;&quot;&quot;
+
+        # No tests
+        return True
+
+FilterBase.serialize_register(PropertyFilter)
+
+
+
+class ParameterFilter (FilterChildBase):
+    &quot;&quot;&quot;
+    Limits a search to specific parameters.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;ParameterFilter&quot;
+
+    def _match(self, property):
+
+        # At least one parameter must match (or is-not-defined is set)
+        result = not self.defined
+        for parameterName in property.parameterNames():
+            if parameterName.upper() == self.filter_name.upper() and self.match([property.parameterValues(parameterName)]):
+                result = self.defined
+                break
+
+        return result
+
+FilterBase.serialize_register(ParameterFilter)
+
+
+
+class IsNotDefined (FilterBase):
+    &quot;&quot;&quot;
+    Specifies that the named iCalendar item does not exist.
+    &quot;&quot;&quot;
+
+    serialized_name = &quot;IsNotDefined&quot;
+
+    def match(self, component, access=None):
+        # Oddly, this needs always to return True so that it appears there is
+        # a match - but we then &quot;negate&quot; the result if is-not-defined is set.
+        # Actually this method should never be called as we special case the
+        # is-not-defined option.
+        return True
+
+FilterBase.serialize_register(IsNotDefined)
+
+
+
+class TextMatch (FilterBase):
+    &quot;&quot;&quot;
+    Specifies a substring match on a property or parameter value.
+    &quot;&quot;&quot;
+    serialized_name = &quot;TextMatch&quot;
+
+    def __init__(self, xml_element):
+
+        super(TextMatch, self).__init__(xml_element)
+        if xml_element is None:
+            return
+
+        self.text = str(xml_element)
+
+        if &quot;collation&quot; in xml_element.attributes:
+            self.collation = xml_element.attributes[&quot;collation&quot;]
+        else:
+            self.collation = &quot;i;unicode-casemap&quot;
+
+        if &quot;negate-condition&quot; in xml_element.attributes:
+            self.negate = xml_element.attributes[&quot;negate-condition&quot;]
+            if self.negate not in (&quot;yes&quot;, &quot;no&quot;):
+                self.negate = &quot;no&quot;
+            self.negate = {&quot;yes&quot;: True, &quot;no&quot;: False}[self.negate]
+        else:
+            self.negate = False
+
+        if &quot;match-type&quot; in xml_element.attributes:
+            self.match_type = xml_element.attributes[&quot;match-type&quot;]
+            if self.match_type not in (
+                &quot;equals&quot;,
+                &quot;contains&quot;,
+                &quot;starts-with&quot;,
+                &quot;ends-with&quot;,
+            ):
+                self.match_type = &quot;contains&quot;
+        else:
+            self.match_type = &quot;contains&quot;
+
+
+    def _deserialize(self, data):
+        &quot;&quot;&quot;
+        Convert a JSON compatible serialization of this object into the actual object.
+        &quot;&quot;&quot;
+        self.text = data[&quot;text&quot;]
+        self.collation = data[&quot;collation&quot;]
+        self.negate = data[&quot;negate&quot;]
+        self.match_type = data[&quot;match_type&quot;]
+
+
+    def serialize(self):
+        &quot;&quot;&quot;
+        Create a JSON compatible serialization of this object - will be used in a cross-pod request.
+        &quot;&quot;&quot;
+        result = super(TextMatch, self).serialize()
+        result.update({
+            &quot;text&quot;: self.text,
+            &quot;collation&quot;: self.collation,
+            &quot;negate&quot;: self.negate,
+            &quot;match_type&quot;: self.match_type,
+        })
+        return result
+
+
+    def _match(self, item):
+        &quot;&quot;&quot;
+        Match the text for the item.
+        If the item is a property, then match the property value,
+        otherwise it may be a list of parameter values - try to match anyone of those
+        &quot;&quot;&quot;
+        if item is None:
+            return False
+
+        if isinstance(item, Property):
+            values = [item.strvalue()]
+        else:
+            values = item
+
+        test = unicode(self.text, &quot;utf-8&quot;).lower()
+
+
+        def _textCompare(s):
+            # Currently ignores the collation and does caseless matching
+            s = s.lower()
+
+            if self.match_type == &quot;equals&quot;:
+                return s == test
+            elif self.match_type == &quot;contains&quot;:
+                return s.find(test) != -1
+            elif self.match_type == &quot;starts-with&quot;:
+                return s.startswith(test)
+            elif self.match_type == &quot;ends-with&quot;:
+                return s.endswith(test)
+            else:
+                return False
+
+        for value in values:
+            # NB Its possible that we have a text list value which appears as a Python list,
+            # so we need to check for that and iterate over the list.
+            if isinstance(value, list):
+                for subvalue in value:
+                    if _textCompare(unicode(subvalue, &quot;utf-8&quot;)):
+                        return not self.negate
+            else:
+                if _textCompare(unicode(value, &quot;utf-8&quot;)):
+                    return not self.negate
+
+        return self.negate
+
+FilterBase.serialize_register(TextMatch)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequerytest__init__py"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/carddav/datastore/query/test/__init__.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/__init__.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/test/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,15 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
</del></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequerytest__init__pyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerytest__init__py"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/carddav/datastore/query/test/__init__.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/__init__.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/query/test/__init__.py                                (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/test/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequerytesttest_filterpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/carddav/datastore/query/test/test_filter.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/test/test_filter.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,96 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twext.enterprise.dal.syntax import SQLFragment
-
-from twisted.trial.unittest import TestCase
-
-from twistedcaldav import carddavxml
-
-from txdav.carddav.datastore.query.filter import Filter, FilterBase
-from txdav.common.datastore.sql_tables import schema
-from txdav.carddav.datastore.query.builder import buildExpression
-from txdav.common.datastore.query.generator import SQLQueryGenerator
-from txdav.carddav.datastore.index_file import sqladdressbookquery
-
-class TestQueryFilter(TestCase):
-
-    _objectSchema = schema.ADDRESSBOOK_OBJECT
-    _queryFields = {
-        &quot;UID&quot;: _objectSchema.UID
-    }
-
-    def test_query(self):
-        &quot;&quot;&quot;
-        Basic query test - single term.
-        Only UID can be queried via sql.
-        &quot;&quot;&quot;
-
-        filter = carddavxml.Filter(
-            *[carddavxml.PropertyFilter(
-                carddavxml.TextMatch.fromString(&quot;Example&quot;),
-                **{&quot;name&quot;:&quot;UID&quot;}
-            )]
-        )
-        filter = Filter(filter)
-
-        expression = buildExpression(filter, self._queryFields)
-        sql = SQLQueryGenerator(expression, self, 1234)
-        select, args = sql.generate()
-
-        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, VCARD_UID from ADDRESSBOOK_OBJECT where ADDRESSBOOK_HOME_RESOURCE_ID = ? and VCARD_UID like (? || (? || ?))&quot;, [1234, &quot;%&quot;, &quot;Example&quot;, &quot;%&quot;]))
-        self.assertEqual(args, {})
-
-
-    def test_sqllite_query(self):
-        &quot;&quot;&quot;
-        Basic query test - single term.
-        Only UID can be queried via sql.
-        &quot;&quot;&quot;
-
-        filter = carddavxml.Filter(
-            *[carddavxml.PropertyFilter(
-                carddavxml.TextMatch.fromString(&quot;Example&quot;),
-                **{&quot;name&quot;:&quot;UID&quot;}
-            )]
-        )
-        filter = Filter(filter)
-        sql, args = sqladdressbookquery(filter, 1234)
-
-        self.assertEqual(sql, &quot; from RESOURCE where RESOURCE.UID GLOB :1&quot;)
-        self.assertEqual(args, [&quot;*Example*&quot;])
-
-
-
-class TestQueryFilterSerialize(TestCase):
-
-    def test_query(self):
-        &quot;&quot;&quot;
-        Basic query test - no time range
-        &quot;&quot;&quot;
-
-        filter = carddavxml.Filter(
-            *[carddavxml.PropertyFilter(
-                carddavxml.TextMatch.fromString(&quot;Example&quot;),
-                **{&quot;name&quot;:&quot;UID&quot;}
-            )]
-        )
-        filter = Filter(filter)
-        j = filter.serialize()
-        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
-
-        f = FilterBase.deserialize(j)
-        self.assertTrue(isinstance(f, Filter))
</del></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastorequerytesttest_filterpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastorequerytesttest_filterpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/carddav/datastore/query/test/test_filter.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/query/test/test_filter.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/query/test/test_filter.py                                (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/query/test/test_filter.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,96 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.enterprise.dal.syntax import SQLFragment
+
+from twisted.trial.unittest import TestCase
+
+from twistedcaldav import carddavxml
+
+from txdav.carddav.datastore.query.filter import Filter, FilterBase
+from txdav.common.datastore.sql_tables import schema
+from txdav.carddav.datastore.query.builder import buildExpression
+from txdav.common.datastore.query.generator import SQLQueryGenerator
+from txdav.carddav.datastore.index_file import sqladdressbookquery
+
+class TestQueryFilter(TestCase):
+
+    _objectSchema = schema.ADDRESSBOOK_OBJECT
+    _queryFields = {
+        &quot;UID&quot;: _objectSchema.UID
+    }
+
+    def test_query(self):
+        &quot;&quot;&quot;
+        Basic query test - single term.
+        Only UID can be queried via sql.
+        &quot;&quot;&quot;
+
+        filter = carddavxml.Filter(
+            *[carddavxml.PropertyFilter(
+                carddavxml.TextMatch.fromString(&quot;Example&quot;),
+                **{&quot;name&quot;:&quot;UID&quot;}
+            )]
+        )
+        filter = Filter(filter)
+
+        expression = buildExpression(filter, self._queryFields)
+        sql = SQLQueryGenerator(expression, self, 1234)
+        select, args = sql.generate()
+
+        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, VCARD_UID from ADDRESSBOOK_OBJECT where ADDRESSBOOK_HOME_RESOURCE_ID = ? and VCARD_UID like (? || (? || ?))&quot;, [1234, &quot;%&quot;, &quot;Example&quot;, &quot;%&quot;]))
+        self.assertEqual(args, {})
+
+
+    def test_sqllite_query(self):
+        &quot;&quot;&quot;
+        Basic query test - single term.
+        Only UID can be queried via sql.
+        &quot;&quot;&quot;
+
+        filter = carddavxml.Filter(
+            *[carddavxml.PropertyFilter(
+                carddavxml.TextMatch.fromString(&quot;Example&quot;),
+                **{&quot;name&quot;:&quot;UID&quot;}
+            )]
+        )
+        filter = Filter(filter)
+        sql, args = sqladdressbookquery(filter, 1234)
+
+        self.assertEqual(sql, &quot; from RESOURCE where RESOURCE.UID GLOB :1&quot;)
+        self.assertEqual(args, [&quot;*Example*&quot;])
+
+
+
+class TestQueryFilterSerialize(TestCase):
+
+    def test_query(self):
+        &quot;&quot;&quot;
+        Basic query test - no time range
+        &quot;&quot;&quot;
+
+        filter = carddavxml.Filter(
+            *[carddavxml.PropertyFilter(
+                carddavxml.TextMatch.fromString(&quot;Example&quot;),
+                **{&quot;name&quot;:&quot;UID&quot;}
+            )]
+        )
+        filter = Filter(filter)
+        j = filter.serialize()
+        self.assertEqual(j[&quot;type&quot;], &quot;Filter&quot;)
+
+        f = FilterBase.deserialize(j)
+        self.assertTrue(isinstance(f, Filter))
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/sql.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -14,8 +14,8 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> # #
</span><del>-from txdav.xml import element
</del><span class="cx"> 
</span><ins>+
</ins><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> SQL backend for CardDAV storage.
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -47,12 +47,14 @@
</span><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><span class="cx"> from txdav.base.propertystore.sql import PropertyStore
</span><ins>+from txdav.carddav.datastore.query.builder import buildExpression
+from txdav.carddav.datastore.query.filter import Filter
</ins><span class="cx"> from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook, \
</span><span class="cx">     IAddressBookObject, GroupWithUnsharedAddressNotAllowedError, \
</span><span class="cx">     KindChangeNotAllowedError
</span><ins>+from txdav.common.datastore.query.generator import SQLQueryGenerator
</ins><span class="cx"> from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
</span><span class="cx">     CommonObjectResource, EADDRESSBOOKTYPE, SharingMixIn, SharingInvitation
</span><del>-from txdav.common.datastore.sql_legacy import PostgresLegacyABIndexEmulator
</del><span class="cx"> from txdav.common.datastore.sql_tables import _ABO_KIND_PERSON, \
</span><span class="cx">     _ABO_KIND_GROUP, _ABO_KIND_RESOURCE, _ABO_KIND_LOCATION, schema, \
</span><span class="cx">     _BIND_MODE_OWN, _BIND_MODE_WRITE, _BIND_STATUS_ACCEPTED, \
</span><span class="lines">@@ -61,7 +63,8 @@
</span><span class="cx">     InvalidUIDError, UIDExistsError, ObjectResourceTooBigError, \
</span><span class="cx">     InvalidObjectResourceError, InvalidComponentForStoreError, \
</span><span class="cx">     AllRetriesFailed, ObjectResourceNameAlreadyExistsError, \
</span><del>-    SyncTokenValidException
</del><ins>+    SyncTokenValidException, IndexedSearchException
+from txdav.xml import element
</ins><span class="cx"> 
</span><span class="cx"> from zope.interface.declarations import implements
</span><span class="cx"> 
</span><span class="lines">@@ -86,7 +89,6 @@
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, transaction, ownerUID):
</span><span class="cx"> 
</span><del>-        self._childClass = AddressBook
</del><span class="cx">         super(AddressBookHome, self).__init__(transaction, ownerUID)
</span><span class="cx">         self._addressbookPropertyStoreID = None
</span><span class="cx">         self._addressbook = None
</span><span class="lines">@@ -450,6 +452,8 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     implements(IAddressBook)
</span><span class="cx"> 
</span><ins>+    _homeType = EADDRESSBOOKTYPE
+
</ins><span class="cx">     # structured tables.  (new, preferred)
</span><span class="cx">     _homeSchema = schema.ADDRESSBOOK_HOME
</span><span class="cx">     _bindSchema = schema.SHARED_ADDRESSBOOK_BIND
</span><span class="lines">@@ -458,14 +462,87 @@
</span><span class="cx">     _revisionsSchema = schema.ADDRESSBOOK_OBJECT_REVISIONS
</span><span class="cx">     _objectSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx"> 
</span><ins>+    # Mapping of vCard property name to DB column name
+    _queryFields = {
+        &quot;UID&quot;: _objectSchema.UID,
+    }
</ins><span class="cx"> 
</span><del>-    def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None):
</del><ins>+
+    @classmethod
+    @inlineCallbacks
+    def _getDBDataIndirect(cls, home, name, resourceID, externalID):
+
+        # Get the bind row data
+        row = None
+
+        # TODO: query cacher
+
+        rows = None
+        ownerHome = None
+
+        # TODO: add queryCacher support
+
+        if rows is None:
+            # No cached copy
+            if name:
+                ownerHome = yield home._txn.addressbookHomeWithUID(name)
+                if ownerHome is None:
+                    returnValue(None)
+                resourceID = ownerHome.addressbook()._resourceID
+            rows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
+                home._txn, homeID=home._resourceID, addressbookID=resourceID
+            )
+
+        if not rows:
+            returnValue(None)
+
+        groupID = None
+        overallBindStatus = _BIND_STATUS_INVITED
+        minBindRevision = None
+        for row in rows:
+            bindMode, homeID, resourceGroupID, externalID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
+            if groupID is None:
+                groupID = resourceGroupID
+            minBindRevision = min(minBindRevision, bindRevision) if minBindRevision is not None else bindRevision
+            if bindStatus == _BIND_STATUS_ACCEPTED:
+                overallBindStatus = _BIND_STATUS_ACCEPTED
+
+        if ownerHome is None:
+            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, groupID)
+            ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
+
+        bindData = row[:cls.bindColumnCount]
+        additionalBindData = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+
+        # Adjust for aggregate values
+        bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)] = resourceID
+        bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] = ownerHome.uid()
+        bindData[cls.bindColumns().index(cls._bindSchema.BIND_MODE)] = _BIND_MODE_INDIRECT
+        bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)] = overallBindStatus
+        bindData[cls.bindColumns().index(cls._bindSchema.BIND_REVISION)] = minBindRevision
+        bindData[cls.bindColumns().index(cls._bindSchema.MESSAGE)] = &quot;&quot;
+
+        # Get the matching metadata data
+        metadataData = None
+        queryCacher = home._txn._queryCacher
+        if queryCacher:
+            # Retrieve from cache
+            cacheKey = queryCacher.keyForHomeChildMetaData(resourceID)
+            metadataData = yield queryCacher.get(cacheKey)
+
+        if metadataData is None:
+            # No cached copy
+            metadataData = (yield cls._metadataByIDQuery.on(home._txn, resourceID=resourceID))[0]
+            if queryCacher:
+                # Cache the results
+                yield queryCacher.setAfterCommit(home._txn, cacheKey, metadataData)
+
+        returnValue((bindData, additionalBindData, metadataData, ownerHome,))
+
+
+    def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
</ins><span class="cx">         ownerName = ownerHome.addressbook().name() if ownerHome else None
</span><del>-        super(AddressBook, self).__init__(
-            home, name, resourceID, mode, status, revision=revision,
-            message=message, ownerHome=ownerHome, ownerName=ownerName
-        )
-        self._index = PostgresLegacyABIndexEmulator(self)
</del><ins>+        super(AddressBook, self).__init__(home, name, resourceID, mode, status, revision=revision, message=message, ownerHome=ownerHome, ownerName=ownerName, externalID=externalID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def __repr__(self):
</span><span class="lines">@@ -568,13 +645,14 @@
</span><span class="cx">             returnValue((yield super(AddressBook, self).resourceNamesSinceRevision(revision)))
</span><span class="cx"> 
</span><span class="cx">         # call sharedChildResourceNamesSinceRevision() and filter results
</span><del>-        sharedChildChanged, sharedChildDeleted = yield self.sharedChildResourceNamesSinceRevision(revision, &quot;infinity&quot;)
</del><ins>+        sharedChildChanged, sharedChildDeleted, sharedChildInvalid = yield self.sharedChildResourceNamesSinceRevision(revision, &quot;infinity&quot;)
</ins><span class="cx"> 
</span><span class="cx">         selfPath = self.name() + '/'
</span><span class="cx">         lenpath = len(selfPath)
</span><span class="cx">         changed = [item[lenpath:] for item in sharedChildChanged if item.startswith(selfPath) and item != selfPath]
</span><span class="cx">         deleted = [item[lenpath:] for item in sharedChildDeleted if item.startswith(selfPath) and item != selfPath]
</span><del>-        returnValue((changed, deleted,))
</del><ins>+        invalid = [item[lenpath:] for item in sharedChildInvalid if item.startswith(selfPath) and item != selfPath]
+        returnValue((changed, deleted, invalid))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -611,7 +689,7 @@
</span><span class="cx">                 self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</span><span class="cx">         )
</span><span class="cx">         if groupBindRows:
</span><del>-            bindRevisions += [groupBindRow[5] for groupBindRow in groupBindRows]
</del><ins>+            bindRevisions += [groupBindRow[self.bindColumns().index(self._bindSchema.BIND_REVISION)] for groupBindRow in groupBindRows]
</ins><span class="cx"> 
</span><span class="cx">         if revision != 0 and revision &lt; max(bindRevisions):
</span><span class="cx">             if depth != '1':
</span><span class="lines">@@ -623,16 +701,17 @@
</span><span class="cx"> 
</span><span class="cx">         if self.fullyShared():
</span><span class="cx">             # add change for addressbook group
</span><del>-            changed, deleted = yield super(AddressBook, self).sharedChildResourceNamesSinceRevision(revision, depth)
</del><ins>+            changed, deleted, invalid = yield super(AddressBook, self).sharedChildResourceNamesSinceRevision(revision, depth)
</ins><span class="cx"> 
</span><span class="cx">             if revision == 0 and depth != &quot;1&quot;:
</span><span class="cx">                 changed.add(&quot;%s/%s&quot; % (path, self._groupForSharedAddressBookName(),))
</span><span class="cx"> 
</span><del>-            returnValue((changed, deleted))
</del><ins>+            returnValue((changed, deleted, invalid))
</ins><span class="cx"> 
</span><span class="cx">         changed = set()
</span><span class="cx">         deleted = set()
</span><del>-        acceptedGroupIDs = set([groupBindRow[2] for groupBindRow in groupBindRows])
</del><ins>+        invalid = set()
+        acceptedGroupIDs = set([groupBindRow[self.bindColumns().index(self._bindSchema.RESOURCE_ID)] for groupBindRow in groupBindRows])
</ins><span class="cx"> 
</span><span class="cx">         allowedObjectIDs = set((yield self.expandGroupIDs(self._txn, acceptedGroupIDs)))
</span><span class="cx">         oldAllowedObjectIDs = set((yield self.expandGroupIDs(self._txn, acceptedGroupIDs, revision)))
</span><span class="lines">@@ -700,7 +779,7 @@
</span><span class="cx">                 for addedObjectID in allowedObjectIDs:
</span><span class="cx">                     changed.add(&quot;%s/%s&quot; % (path, idToNameMap[addedObjectID],))
</span><span class="cx"> 
</span><del>-        returnValue((changed, deleted))
</del><ins>+        returnValue((changed, deleted, invalid,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -728,6 +807,37 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getInviteCopyProperties(self):
+        &quot;&quot;&quot;
+        Get a dictionary of property name/values (as strings) for properties that are shadowable and
+        need to be copied to a sharee's collection when an external (cross-pod) share is created.
+        Sub-classes should override to expose the properties they care about.
+        &quot;&quot;&quot;
+        props = {}
+        for elem in (element.DisplayName, carddavxml.AddressBookDescription,):
+            if PropertyName.fromElement(elem) in self.properties():
+                props[elem.sname()] = str(self.properties()[PropertyName.fromElement(elem)])
+        return props
+
+
+    def setInviteCopyProperties(self, props):
+        &quot;&quot;&quot;
+        Copy a set of shadowable properties (as name/value strings) onto this shared resource when
+        a cross-pod invite is processed. Sub-classes should override to expose the properties they
+        care about.
+        &quot;&quot;&quot;
+        # Initialize these for all shares
+        for elem in (carddavxml.AddressBookDescription,):
+            if PropertyName.fromElement(elem) not in self.properties() and elem.sname() in props:
+                self.properties()[PropertyName.fromElement(elem)] = elem.fromString(props[elem.sname()])
+
+        # Only initialize these for direct shares
+        if self.direct():
+            for elem in (element.DisplayName,):
+                if PropertyName.fromElement(elem) not in self.properties() and elem.sname() in props:
+                    self.properties()[PropertyName.fromElement(elem)] = elem.fromString(props[elem.sname()])
+
+
</ins><span class="cx">     def contentType(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         The content type of addressbook objects is text/vcard.
</span><span class="lines">@@ -736,7 +846,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def create(cls, home, name):
</del><ins>+    def create(cls, home, name, externalID=None):
</ins><span class="cx">         if name == home.addressbook().name():
</span><span class="cx">             # raise HomeChildNameAlreadyExistsError
</span><span class="cx">             pass
</span><span class="lines">@@ -762,13 +872,15 @@
</span><span class="cx">     def remove(self):
</span><span class="cx"> 
</span><span class="cx">         if self._resourceID == self._home._resourceID:
</span><ins>+
+            # Stop sharing first
+            yield self.ownerDeleteShare()
+
</ins><span class="cx">             # Allow remove, as a way to reset the address book to an empty state
</span><span class="cx">             for abo in (yield self.objectResources()):
</span><span class="cx">                 yield abo.remove()
</span><span class="cx">                 yield self.removedObjectResource(abo)
</span><span class="cx"> 
</span><del>-            yield self.unshare()  # storebridge should already have done this
-
</del><span class="cx">             yield self.properties()._removeResource()
</span><span class="cx">             yield self._loadPropertyStore()
</span><span class="cx"> 
</span><span class="lines">@@ -925,6 +1037,57 @@
</span><span class="cx">             returnValue((yield super(AddressBook, self).bumpModified()))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def search(self, filter):
+        &quot;&quot;&quot;
+        Finds resources matching the given qualifiers.
+        @param filter: the L{Filter} for the addressbook-query to execute.
+        @return: an iterable of tuples for each resource matching the
+            given C{qualifiers}. The tuples are C{(name, uid)}, where
+            C{name} is the resource name, C{uid} is the resource UID.
+        &quot;&quot;&quot;
+
+        # We might be passed an L{Filter} or a serialization of one
+        if isinstance(filter, dict):
+            try:
+                filter = Filter.deserialize(filter)
+            except Exception:
+                filter = None
+
+        # Make sure we have a proper Filter element and get the partial SQL statement to use.
+        sql_stmt = self._sqlquery(filter)
+
+        # No result means it is too complex for us
+        if sql_stmt is None:
+            raise IndexedSearchException()
+
+        sql_stmt, args = sql_stmt
+        rowiter = yield sql_stmt.on(self._txn, **args)
+
+        returnValue(list(rowiter))
+
+
+    def _sqlquery(self, filter):
+        &quot;&quot;&quot;
+        Convert the supplied addressbook-query into a partial SQL statement.
+
+        @param filter: the L{Filter} for the addressbook-query to convert.
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+                and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+                Or return C{None} if it is not possible to create an SQL query to fully match the addressbook-query.
+        &quot;&quot;&quot;
+
+        if not isinstance(filter, Filter):
+            return None
+
+        try:
+            expression = buildExpression(filter, self._queryFields)
+            sql = SQLQueryGenerator(expression, self, self.id())
+            return sql.generate()
+        except ValueError:
+            return None
+
+
</ins><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def listObjects(cls, home):
</span><span class="lines">@@ -946,7 +1109,7 @@
</span><span class="cx">             home._txn, homeID=home._resourceID
</span><span class="cx">         )
</span><span class="cx">         for groupRow in groupRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
</span><span class="cx">             ownerHome = yield home._txn.homeWithResourceID(home._homeType, ownerAddressBookID, create=True)
</span><span class="cx">             names |= set([ownerHome.uid()])
</span><span class="lines">@@ -974,7 +1137,7 @@
</span><span class="cx">         )
</span><span class="cx">         # get ownerHomeIDs
</span><span class="cx">         for dataRow in dataRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             ownerHome = yield home.ownerHomeWithChildID(resourceID)
</span><span class="cx">             ownerHomeToDataRowMap[ownerHome] = dataRow
</span><span class="cx"> 
</span><span class="lines">@@ -983,7 +1146,7 @@
</span><span class="cx">             home._txn, homeID=home._resourceID
</span><span class="cx">         )
</span><span class="cx">         for groupBindRow in groupBindRows:
</span><del>-            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, resourceID)
</span><span class="cx">             ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
</span><span class="cx">             if ownerHome not in ownerHomeToDataRowMap:
</span><span class="lines">@@ -1004,32 +1167,17 @@
</span><span class="cx"> 
</span><span class="cx">             # Create the actual objects merging in properties
</span><span class="cx">             for ownerHome, dataRow in ownerHomeToDataRowMap.iteritems():
</span><del>-                bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
-                additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
-                metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
</del><ins>+                bindData = dataRow[:cls.bindColumnCount]
+                additionalBindData = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+                metadataData = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
+                propstore = propertyStores.get(ownerHome._addressbookPropertyStoreID, None)
</ins><span class="cx"> 
</span><del>-                child = cls(
-                    home=home,
-                    name=ownerHome.uid(),
-                    resourceID=ownerHome._resourceID,
-                    mode=bindMode,
-                    status=bindStatus,
-                    revision=bindRevision,
-                    message=bindMessage,
-                    ownerHome=ownerHome,
-                )
</del><ins>+                # Some adjustments for addressbook share model
+                bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)] = ownerHome._resourceID
+                bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] = ownerHome.uid()
</ins><span class="cx"> 
</span><del>-                for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
-                    setattr(child, attr, value)
-                for attr, value in zip(cls.metadataAttributes(), metadata):
-                    setattr(child, attr, value)
</del><ins>+                child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, propstore, ownerHome)
</ins><span class="cx">                 child._syncTokenRevision = revisions[child._resourceID]
</span><del>-                propstore = propertyStores.get(ownerHome._addressbookPropertyStoreID, None)
-                # We have to re-adjust the property store object to account for possible shared
-                # collections as previously we loaded them all as if they were owned
-                if propstore:
-                    propstore._setDefaultUserUID(ownerHome.uid())
-                yield child._loadPropertyStore(propstore)
</del><span class="cx">                 results.append(child)
</span><span class="cx"> 
</span><span class="cx">         returnValue(results)
</span><span class="lines">@@ -1092,7 +1240,7 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, accepted=True):
</del><ins>+    def _indirectObjectWithNameOrID(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
</ins><span class="cx">         # replaces objectWithName()
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Synthesize and indirect child for matching name or id based on whether shared groups exist.
</span><span class="lines">@@ -1104,56 +1252,17 @@
</span><span class="cx">         @return: an L{CommonHomeChild} or C{None} if no such child
</span><span class="cx">             exists.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        rows = None
-        ownerHome = None
</del><span class="cx"> 
</span><del>-        # TODO: add queryCacher support
-
-        if rows is None:
-            # No cached copy
-            if name:
-                ownerHome = yield home._txn.addressbookHomeWithUID(name)
-                if ownerHome is None:
-                    returnValue(None)
-                resourceID = ownerHome.addressbook()._resourceID
-            rows = yield AddressBookObject._bindForHomeIDAndAddressBookID.on(
-                home._txn, homeID=home._resourceID, addressbookID=resourceID
-            )
-
-        if not rows:
</del><ins>+        dbData = yield cls._getDBDataIndirect(home, name, resourceID, externalID)
+        if dbData is None:
</ins><span class="cx">             returnValue(None)
</span><ins>+        bindData, additionalBindData, metadataData, ownerHome = dbData
</ins><span class="cx"> 
</span><del>-        groupID = None
-        overallBindStatus = _BIND_STATUS_INVITED
-        minBindRevision = None
-        for row in rows:
-            bindMode, homeID, resourceGroupID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
-            if groupID is None:
-                groupID = resourceGroupID
-            minBindRevision = min(minBindRevision, bindRevision) if minBindRevision is not None else bindRevision
-            if bindStatus == _BIND_STATUS_ACCEPTED:
-                overallBindStatus = _BIND_STATUS_ACCEPTED
-
-        if accepted is not None and (overallBindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</del><ins>+        bindStatus = bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)]
+        if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</ins><span class="cx">             returnValue(None)
</span><del>-        additionalBind = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</del><span class="cx"> 
</span><del>-        if ownerHome is None:
-            ownerAddressBookID = yield AddressBookObject.ownerAddressBookIDFromGroupID(home._txn, groupID)
-            ownerHome = yield home.ownerHomeWithChildID(ownerAddressBookID)
-
-        child = cls(
-            home=home,
-            name=ownerHome.uid(),
-            resourceID=resourceID,
-            mode=_BIND_MODE_INDIRECT,
-            status=overallBindStatus,
-            revision=minBindRevision,
-            message=&quot;&quot;,
-            ownerHome=ownerHome,
-            ownerName=ownerHome.uid()
-        )
-        yield child.initFromStore(additionalBind)
</del><ins>+        child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, None, ownerHome)
</ins><span class="cx">         returnValue(child)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1247,7 +1356,7 @@
</span><span class="cx">             groupBindRows = yield AddressBookObject._unacceptedBindForHomeIDAndAddressBookID.on(
</span><span class="cx">                 self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</span><span class="cx">             )
</span><del>-            returnValue([groupBindRow[2] for groupBindRow in groupBindRows])
</del><ins>+            returnValue([groupBindRow[self.bindColumns().index(self._bindSchema.RESOURCE_ID)] for groupBindRow in groupBindRows])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1261,7 +1370,7 @@
</span><span class="cx">             groupBindRows = yield AddressBookObject._acceptedBindForHomeIDAndAddressBookID.on(
</span><span class="cx">                 self._txn, homeID=self._home._resourceID, addressbookID=self._resourceID
</span><span class="cx">             )
</span><del>-            returnValue([groupBindRow[2] for groupBindRow in groupBindRows])
</del><ins>+            returnValue([groupBindRow[self.bindColumns().index(self._bindSchema.RESOURCE_ID)] for groupBindRow in groupBindRows])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1280,7 +1389,7 @@
</span><span class="cx">             readWriteGroupIDs = set()
</span><span class="cx">             readOnlyGroupIDs = set()
</span><span class="cx">             for groupBindRow in groupBindRows:
</span><del>-                bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+                bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx">                 if bindMode == _BIND_MODE_WRITE:
</span><span class="cx">                     readWriteGroupIDs.add(resourceID)
</span><span class="cx">                 else:
</span><span class="lines">@@ -1341,7 +1450,7 @@
</span><span class="cx">         readWriteGroupIDs = []
</span><span class="cx">         readOnlyGroupIDs = []
</span><span class="cx">         for groupBindRow in groupBindRows:
</span><del>-            bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx">             if bindMode == _BIND_MODE_WRITE:
</span><span class="cx">                 readWriteGroupIDs.append(resourceID)
</span><span class="cx">             else:
</span><span class="lines">@@ -1426,7 +1535,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def shareWith(self, shareeHome, mode, status=None, summary=None):
</del><ins>+    def shareWith(self, shareeHome, mode, status=None, summary=None, shareName=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Share this (owned) L{AddressBookObject} with another home.
</span><span class="cx"> 
</span><span class="lines">@@ -1454,11 +1563,12 @@
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def doInsert(subt):
</span><del>-            newName = self.newShareName()
</del><ins>+            newName = shareName if shareName is not None else self.newShareName()
</ins><span class="cx">             yield self._bindInsertQuery.on(
</span><span class="cx">                 subt,
</span><span class="cx">                 homeID=shareeHome._resourceID,
</span><span class="cx">                 resourceID=self._resourceID,
</span><ins>+                externalID=None,
</ins><span class="cx">                 name=newName,
</span><span class="cx">                 mode=mode,
</span><span class="cx">                 bindStatus=status,
</span><span class="lines">@@ -1495,14 +1605,14 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def createShare(self, shareeUID, mode, summary=None):
</del><ins>+    def createShare(self, shareeUID, mode, summary=None, shareName=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a new shared resource. If the mode is direct, the share is created in accepted state,
</span><span class="cx">         otherwise the share is created in invited state.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if self._kind == _ABO_KIND_GROUP:
</span><del>-            shareeView = yield super(AddressBookObjectSharingMixIn, self).createShare(shareeUID, mode, summary)
</del><ins>+            shareeView = yield super(AddressBookObjectSharingMixIn, self).createShare(shareeUID, mode, summary, shareName)
</ins><span class="cx">             returnValue(shareeView)
</span><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="lines">@@ -1598,8 +1708,9 @@
</span><span class="cx">                         # update revision in all remaining bind table rows for this address book
</span><span class="cx">                         yield shareeView.addressbook().notifyPropertyChanged()
</span><span class="cx">                         for groupBindRow in groupBindRows:
</span><del>-                            if groupBindRow[2] != shareeView._resourceID:
-                                groupObject = yield shareeView.addressbook().objectResourceWithID(groupBindRow[2])
</del><ins>+                            resid = groupBindRow[self.bindColumns().index(self._bindSchema.RESOURCE_ID)]
+                            if resid != shareeView._resourceID:
+                                groupObject = yield shareeView.addressbook().objectResourceWithID(resid)
</ins><span class="cx">                                 yield groupObject._initBindRevision()
</span><span class="cx">                         if shareeView.addressbook().fullyShared():
</span><span class="cx">                             yield shareeView.addressbook()._initBindRevision()
</span><span class="lines">@@ -1651,7 +1762,9 @@
</span><span class="cx">                 yield addressbookAsShared.notifyPropertyChanged()
</span><span class="cx">                 #update revision in all remaining bind table rows for this address book
</span><span class="cx">                 for groupBindRow in groupBindRows:
</span><del>-                    groupObject = yield addressbookAsShared.objectResourceWithID(groupBindRow[2])
</del><ins>+                    groupObject = yield addressbookAsShared.objectResourceWithID(
+                        groupBindRow[self.bindColumns().index(self._bindSchema.RESOURCE_ID)]
+                    )
</ins><span class="cx">                     yield groupObject._initBindRevision()
</span><span class="cx">                 addressbookAsShared._objects = {}
</span><span class="cx">                 addressbookAsShared._objectNames = None
</span><span class="lines">@@ -1731,25 +1844,147 @@
</span><span class="cx">     _objectSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx">     _bindSchema = schema.SHARED_GROUP_BIND
</span><span class="cx"> 
</span><ins>+    _componentClass = VCard
+
</ins><span class="cx">     # used by CommonHomeChild._childrenAndMetadataForHomeID() only
</span><span class="cx">     # _homeChildSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx">     # _homeChildMetaDataSchema = schema.ADDRESSBOOK_OBJECT
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
+    @inlineCallbacks
+    def makeClass(cls, parent, objectData, groupBindData=None, propstore=None):
+        &quot;&quot;&quot;
+        Given the various database rows, build the actual class.
+
+        @param parent: the parent collection object
+        @type parent: L{AddressBook}
+        @param objectData: the standard set of object columns
+        @type objectData: C{list}
+        @param groupBindData: additional group bind data
+        @type groupBindData: C{list}
+        @param propstore: a property store to use, or C{None} to load it automatically
+        @type propstore: L{PropertyStore}
+
+        @return: the constructed child class
+        @rtype: L{CommonHomeChild}
+        &quot;&quot;&quot;
+
+        c = cls._externalClass if parent.external() else cls
+        child = c(
+            parent,
+            objectData[cls._allColumns().index(cls._objectSchema.RESOURCE_NAME)],
+            objectData[cls._allColumns().index(cls._objectSchema.UID)],
+        )
+
+        for attr, value in zip(child._rowAttributes(), objectData):
+            setattr(child, attr, value)
+
+        yield child._loadPropertyStore(propstore)
+
+        if groupBindData:
+            bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindData[:AddressBookObject.bindColumnCount] #@UnusedVariable
+            child._bindMode = bindMode
+            child._bindStatus = bindStatus
+            child._bindMessage = bindMessage
+            child._bindName = bindName
+            child._bindRevision = bindRevision
+        else:
+            invites = yield child.sharingInvites()
+            if len(invites):
+                child._bindMessage = &quot;shared&quot;
+
+        returnValue(child)
+
+
+    @classmethod
+    @inlineCallbacks
+    def _getDBData(cls, parent, name, uid, resourceID):
+        &quot;&quot;&quot;
+        Given a set of identifying information, load the data rows for the object. Only one of
+        L{name}, L{uid} or L{resourceID} is specified - others are C{None}.
+
+        @param parent: the parent collection object
+        @type parent: L{AddressBook}
+        @param name: the resource name
+        @type name: C{str}
+        @param uid: the UID of the data
+        @type uid: C{str}
+        @param resourceID: the resource ID
+        @type resourceID: C{int}
+        &quot;&quot;&quot;
+
+        row = None
+        groupBindRow = None
+
+        if parent.owned() or parent.fullyShared():  # owned or fully shared
+            row = yield super(AddressBookObject, cls)._getDBData(parent, name, uid, resourceID)
+
+            # Might be special group
+            if row is None and parent.fullyShared():
+                if name:
+                    if name == parent._groupForSharedAddressBookName():
+                        row = parent._groupForSharedAddressBookRow()
+                elif uid:
+                    if uid == (yield parent._groupForSharedAddressBookUID()):
+                        row = parent._groupForSharedAddressBookRow()
+                elif resourceID:
+                    if resourceID == parent.id():
+                        rows = parent._groupForSharedAddressBookRow()
+
+        else:
+            acceptedGroupIDs = yield parent.acceptedGroupIDs()
+            allowedObjectIDs = yield parent.expandGroupIDs(parent._txn, acceptedGroupIDs)
+            rows = None
+            if name:
+                if allowedObjectIDs:
+                    rows = (yield cls._allColumnsWithResourceIDsAndName(allowedObjectIDs).on(
+                        parent._txn,
+                        name=name,
+                        resourceIDs=allowedObjectIDs,
+                    ))
+            elif uid:
+                if allowedObjectIDs:
+                    rows = (yield cls._allColumnsWithResourceIDsAndUID(allowedObjectIDs).on(
+                        parent._txn,
+                        uid=uid,
+                        resourceIDs=allowedObjectIDs,
+                    ))
+            elif resourceID:
+                # Also allow invited groups
+                if resourceID in allowedObjectIDs or resourceID in (yield parent.unacceptedGroupIDs()):
+                    rows = (yield cls._allColumnsWithResourceID.on(
+                        parent._txn,
+                        resourceID=resourceID,
+                    ))
+            if rows:
+                row = rows[0]
+
+        if row is not None:
+            if row[cls._allColumns().index(cls._objectSchema.KIND)] == _ABO_KIND_GROUP:
+
+                resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+                groupBindRows = yield AddressBookObject._bindForResourceIDAndHomeID.on(
+                    parent._txn, resourceID=resourceID, homeID=parent._home._resourceID
+                )
+
+                if groupBindRows:
+                    groupBindRow = groupBindRows[0]
+
+        returnValue((row, groupBindRow,))
+
+
</ins><span class="cx">     def __init__(self, addressbook, name, uid, resourceID=None, options=None):
</span><span class="cx"> 
</span><span class="cx">         self._kind = None
</span><span class="cx">         self._ownerAddressBookResourceID = None
</span><del>-        # _self._component is the cached, current component
-        # super._objectText now contains the text as read of the database only,
-        #     not including group member text
-        self._component = None
</del><span class="cx">         self._bindMode = None
</span><span class="cx">         self._bindStatus = None
</span><span class="cx">         self._bindMessage = None
</span><span class="cx">         self._bindName = None
</span><span class="cx">         self._bindRevision = None
</span><span class="cx">         super(AddressBookObject, self).__init__(addressbook, name, uid, resourceID, options)
</span><ins>+        self._externalID = None
</ins><span class="cx">         self._options = {} if options is None else options
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1774,6 +2009,15 @@
</span><span class="cx">         return self._resourceID == self.addressbook()._resourceID
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def external(self):
+        &quot;&quot;&quot;
+        Is this an external object.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return self.addressbook().external()
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def remove(self):
</span><span class="cx"> 
</span><span class="lines">@@ -1872,7 +2116,8 @@
</span><span class="cx">         yield super(AddressBookObject, self).remove()
</span><span class="cx">         self._kind = None
</span><span class="cx">         self._ownerAddressBookResourceID = None
</span><del>-        self._component = None
</del><ins>+        self._objectText = None
+        self._cachedComponent = None
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1901,7 +2146,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         obj = cls._objectSchema
</span><span class="cx">         return Select(
</span><del>-            cls._allColumns, From=obj,
</del><ins>+            cls._allColumns(), From=obj,
</ins><span class="cx">             Where=(column == Parameter(paramName)).And(
</span><span class="cx">                 obj.RESOURCE_ID.In(Parameter(&quot;resourceIDs&quot;, len(resourceIDs)))),
</span><span class="cx">         )
</span><span class="lines">@@ -1921,7 +2166,7 @@
</span><span class="cx">     def _allColumnsWithResourceID(cls): #@NoSelf
</span><span class="cx">         obj = cls._objectSchema
</span><span class="cx">         return Select(
</span><del>-            cls._allColumns, From=obj,
</del><ins>+            cls._allColumns(), From=obj,
</ins><span class="cx">             Where=obj.RESOURCE_ID == Parameter(&quot;resourceID&quot;),)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1944,7 +2189,7 @@
</span><span class="cx">         )
</span><span class="cx">         if groupBindRows:
</span><span class="cx">             groupBindRow = groupBindRows[0]
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</del><ins>+            bindMode, homeID, resourceID, externalID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
</ins><span class="cx"> 
</span><span class="cx">             if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx">                 returnValue(None)
</span><span class="lines">@@ -1957,93 +2202,20 @@
</span><span class="cx">         returnValue(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def initFromStore(self):
-        &quot;&quot;&quot;
-        Initialise this object from the store. We read in and cache all the
-        extra metadata from the DB to avoid having to do DB queries for those
-        individually later. Either the name or uid is present, so we have to
-        tweak the query accordingly.
</del><ins>+    def objectWith(cls, parent, name=None, uid=None, resourceID=None):
</ins><span class="cx"> 
</span><del>-        @return: L{self} if object exists in the DB, else C{None}
-        &quot;&quot;&quot;
-        abo = None
-        if self.owned() or self.addressbook().fullyShared():  # owned or fully shared
-            abo = yield super(AddressBookObject, self).initFromStore()
</del><ins>+        row, groupBindRow = yield cls._getDBData(parent, name, uid, resourceID)
</ins><span class="cx"> 
</span><del>-            # Might be special group
-            if abo is None and self.addressbook().fullyShared():
-                rows = None
-                if self._name:
-                    if self._name == self.addressbook()._groupForSharedAddressBookName():
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
-                elif self._uid:
-                    if self._uid == (yield self.addressbook()._groupForSharedAddressBookUID()):
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
-                elif self._resourceID:
-                    if self.isGroupForSharedAddressBook():
-                        rows = [self.addressbook()._groupForSharedAddressBookRow()]
-
-                if rows:
-                    self._initFromRow(tuple(rows[0]))
-                    yield self._loadPropertyStore()
-                    abo = self
-
</del><ins>+        if row:
+            child = yield cls.makeClass(parent, row, groupBindRow)
+            returnValue(child)
</ins><span class="cx">         else:
</span><del>-            acceptedGroupIDs = yield self.addressbook().acceptedGroupIDs()
-            allowedObjectIDs = yield self.addressbook().expandGroupIDs(self._txn, acceptedGroupIDs)
-            rows = None
-            if self._name:
-                if allowedObjectIDs:
-                    rows = (yield self._allColumnsWithResourceIDsAndName(allowedObjectIDs).on(
-                        self._txn, name=self._name,
-                        resourceIDs=allowedObjectIDs,
-                    ))
-            elif self._uid:
-                if allowedObjectIDs:
-                    rows = (yield self._allColumnsWithResourceIDsAndUID(allowedObjectIDs).on(
-                        self._txn, uid=self._uid,
-                        resourceIDs=allowedObjectIDs,
-                    ))
-            elif self._resourceID:
-                if (self._resourceID in allowedObjectIDs or
-                        self._resourceID in (yield self.addressbook().unacceptedGroupIDs())): # allow invited groups
-                    rows = (yield self._allColumnsWithResourceID.on(
-                        self._txn, resourceID=self._resourceID,
-                    ))
-            if rows:
-                self._initFromRow(tuple(rows[0]))
-                yield self._loadPropertyStore()
-                abo = self
-
-        if abo is not None:
-            if self._kind == _ABO_KIND_GROUP:
-
-                groupBindRows = yield AddressBookObject._bindForResourceIDAndHomeID.on(
-                    self._txn, resourceID=self._resourceID, homeID=self._home._resourceID
-                )
-
-                if groupBindRows:
-                    groupBindRow = groupBindRows[0]
-                    bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = groupBindRow[:AddressBookObject.bindColumnCount] #@UnusedVariable
-                    self._bindMode = bindMode
-                    self._bindStatus = bindStatus
-                    self._bindMessage = bindMessage
-                    self._bindName = bindName
-                    self._bindRevision = bindRevision
-                else:
-                    invites = yield self.sharingInvites()
-                    if len(invites):
-                        self._bindMessage = &quot;shared&quot;
-
-            yield self._loadPropertyStore()
-
-            returnValue(self)
-        else:
</del><span class="cx">             returnValue(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @classproperty
</del><ins>+    @classmethod
</ins><span class="cx">     def _allColumns(cls): #@NoSelf
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Full set of columns in the object table that need to be loaded to
</span><span class="lines">@@ -2059,24 +2231,23 @@
</span><span class="cx">             obj.MD5,
</span><span class="cx">             Len(obj.TEXT),
</span><span class="cx">             obj.CREATED,
</span><del>-            obj.MODIFIED,
</del><ins>+            obj.MODIFIED
</ins><span class="cx">         ]
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _initFromRow(self, row):
-        &quot;&quot;&quot;
-        Given a select result using the columns from L{_allColumns}, initialize
-        the object resource state.
-        &quot;&quot;&quot;
-        (self._ownerAddressBookResourceID,
-         self._resourceID,
-         self._name,
-         self._uid,
-         self._kind,
-         self._md5,
-         self._size,
-         self._created,
-         self._modified,) = tuple(row)
</del><ins>+    @classmethod
+    def _rowAttributes(cls): #@NoSelf
+        return (
+            &quot;_ownerAddressBookResourceID&quot;,
+            &quot;_resourceID&quot;,
+            &quot;_name&quot;,
+            &quot;_uid&quot;,
+            &quot;_kind&quot;,
+            &quot;_md5&quot;,
+            &quot;_size&quot;,
+            &quot;_created&quot;,
+            &quot;_modified&quot;,
+         )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="lines">@@ -2099,7 +2270,7 @@
</span><span class="cx">         else:
</span><span class="cx">             acceptedGroupIDs = yield addressbook.acceptedGroupIDs()
</span><span class="cx">             allowedObjectIDs = yield addressbook.expandGroupIDs(addressbook._txn, acceptedGroupIDs)
</span><del>-            rows = yield cls._columnsWithResourceIDsQuery(cls._allColumns, allowedObjectIDs).on(
</del><ins>+            rows = yield cls._columnsWithResourceIDsQuery(cls._allColumns(), allowedObjectIDs).on(
</ins><span class="cx">                 addressbook._txn, resourceIDs=allowedObjectIDs
</span><span class="cx">             )
</span><span class="cx">         returnValue(rows)
</span><span class="lines">@@ -2108,7 +2279,7 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     def _allColumnsWithResourceIDsAndNamesQuery(cls, resourceIDs, names):
</span><span class="cx">         obj = cls._objectSchema
</span><del>-        return Select(cls._allColumns, From=obj,
</del><ins>+        return Select(cls._allColumns(), From=obj,
</ins><span class="cx">                       Where=(obj.RESOURCE_ID.In(Parameter(&quot;resourceIDs&quot;, len(resourceIDs))).And(
</span><span class="cx">                           obj.RESOURCE_NAME.In(Parameter(&quot;names&quot;, len(names))))),)
</span><span class="cx"> 
</span><span class="lines">@@ -2224,6 +2395,13 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setComponent(self, component, inserting=False):
</span><span class="cx"> 
</span><ins>+        if isinstance(component, str) or isinstance(component, unicode):
+            component = self._componentClass.fromString(component)
+            try:
+                component = self._componentClass.fromString(component)
+            except InvalidVCardDataError as e:
+                raise InvalidComponentForStoreError(str(e))
+
</ins><span class="cx">         self._componentChanged = False
</span><span class="cx"> 
</span><span class="cx">         if &quot;coaddedUIDs&quot; not in self._options:
</span><span class="lines">@@ -2407,7 +2585,7 @@
</span><span class="cx">             self._objectText = componentText
</span><span class="cx"> 
</span><span class="cx">         self._size = len(self._objectText)
</span><del>-        self._component = component
</del><ins>+        self._cachedComponent = component
</ins><span class="cx">         self._md5 = hashlib.md5(componentText).hexdigest()
</span><span class="cx">         self._componentChanged = originalComponentText != componentText
</span><span class="cx"> 
</span><span class="lines">@@ -2548,7 +2726,7 @@
</span><span class="cx">         only allowed in good data.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        if self._component is None:
</del><ins>+        if self._cachedComponent is None:
</ins><span class="cx"> 
</span><span class="cx">             if self.isGroupForSharedAddressBook():
</span><span class="cx">                 component = yield self.addressbook()._groupForSharedAddressBookComponent()
</span><span class="lines">@@ -2608,9 +2786,9 @@
</span><span class="cx">                     component.addProperty(Property(&quot;X-ADDRESSBOOKSERVER-KIND&quot;, &quot;group&quot;))
</span><span class="cx">                     component.addProperty(Property(&quot;UID&quot;, self._uid))
</span><span class="cx"> 
</span><del>-            self._component = component
</del><ins>+            self._cachedComponent = component
</ins><span class="cx"> 
</span><del>-        returnValue(self._component)
</del><ins>+        returnValue(self._cachedComponent)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def moveValidation(self, destination, name):
</span><span class="lines">@@ -2725,4 +2903,10 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+# Hook-up class relationships at the end after they have all been defined
+from txdav.carddav.datastore.sql_external import AddressBookHomeExternal, AddressBookExternal, AddressBookObjectExternal
+AddressBookHome._externalClass = AddressBookHomeExternal
+AddressBookHome._childClass = AddressBook
+AddressBook._externalClass = AddressBookExternal
</ins><span class="cx"> AddressBook._objectResourceClass = AddressBookObject
</span><ins>+AddressBookObject._externalClass = AddressBookObjectExternal
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoresql_externalpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcarddavdatastoresql_externalpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/carddav/datastore/sql_external.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/carddav/datastore/sql_external.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/sql_external.py                                (rev 0)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql_external.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,83 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+&quot;&quot;&quot;
+SQL backend for CardDAV storage when resources are external.
+&quot;&quot;&quot;
+
+from twisted.internet.defer import succeed
+
+from twext.python.log import Logger
+
+from txdav.carddav.datastore.sql import AddressBookHome, AddressBook, \
+    AddressBookObject
+from txdav.common.datastore.sql_external import CommonHomeExternal, CommonHomeChildExternal, \
+    CommonObjectResourceExternal
+
+log = Logger()
+
+class AddressBookHomeExternal(CommonHomeExternal, AddressBookHome):
+
+    def __init__(self, transaction, ownerUID, resourceID):
+
+        AddressBookHome.__init__(self, transaction, ownerUID)
+        CommonHomeExternal.__init__(self, transaction, ownerUID, resourceID)
+
+
+    def hasAddressBookResourceUIDSomewhereElse(self, uid, ok_object, mode):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def getAddressBookResourcesForUID(self, uid):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def createdHome(self):
+        &quot;&quot;&quot;
+        No children - make this a no-op.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def addressbook(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+
+class AddressBookExternal(CommonHomeChildExternal, AddressBook):
+    &quot;&quot;&quot;
+    SQL-based implementation of L{IAddressBook}.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class AddressBookObjectExternal(CommonObjectResourceExternal, AddressBookObject):
+    &quot;&quot;&quot;
+    SQL-based implementation of L{IAddressBookObject}.
+    &quot;&quot;&quot;
+    pass
+
+AddressBookExternal._objectResourceClass = AddressBookObjectExternal
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoretesttest_index_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -189,12 +189,12 @@
</span><span class="cx">         self.db.deleteResource(&quot;data3.vcf&quot;)
</span><span class="cx"> 
</span><span class="cx">         tests = (
</span><del>-            (0, ([&quot;data1.vcf&quot;, &quot;data2.vcf&quot;, ], [],)),
-            (1, ([&quot;data2.vcf&quot;, ], [&quot;data3.vcf&quot;, ],)),
-            (2, ([], [&quot;data3.vcf&quot;, ],)),
-            (3, ([], [&quot;data3.vcf&quot;, ],)),
-            (4, ([], [],)),
-            (5, ([], [],)),
</del><ins>+            (0, ([&quot;data1.vcf&quot;, &quot;data2.vcf&quot;, ], [], [],)),
+            (1, ([&quot;data2.vcf&quot;, ], [&quot;data3.vcf&quot;, ], [],)),
+            (2, ([], [&quot;data3.vcf&quot;, ], [],)),
+            (3, ([], [&quot;data3.vcf&quot;, ], [],)),
+            (4, ([], [], [],)),
+            (5, ([], [], [],)),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         for revision, results in tests:
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -41,7 +41,7 @@
</span><span class="cx"> from txdav.common.icommondatastore import NoSuchObjectResourceError
</span><span class="cx"> from txdav.common.datastore.sql import EADDRESSBOOKTYPE, CommonObjectResource
</span><span class="cx"> from txdav.common.datastore.sql_tables import  _ABO_KIND_PERSON, _ABO_KIND_GROUP, schema
</span><del>-from txdav.common.datastore.test.util import buildStore
</del><ins>+from txdav.common.datastore.test.util import buildStore, cleanStore
</ins><span class="cx"> from txdav.carddav.datastore.sql import AddressBook
</span><span class="cx"> 
</span><span class="cx"> from txdav.xml.rfc2518 import GETContentLanguage, ResourceType
</span><span class="lines">@@ -56,7 +56,22 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(AddressBookSQLStorageTests, self).setUp()
</span><del>-        self._sqlStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        self._sqlStore = yield buildStore(
+            self,
+            self.notifierFactory,
+            homes=(
+                &quot;home1&quot;,
+                &quot;home2&quot;,
+                &quot;home3&quot;,
+                &quot;home_bad&quot;,
+                &quot;home_empty&quot;,
+                &quot;homeNew&quot;,
+                &quot;new-home&quot;,
+                &quot;uid1&quot;,
+                &quot;uid2&quot;,
+                &quot;xyzzy&quot;,
+            )
+        )
</ins><span class="cx">         yield self.populate()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -289,7 +304,7 @@
</span><span class="cx">         Test that two concurrent attempts to PUT different address book object resources to the
</span><span class="cx">         same address book home does not cause a deadlock.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        addressbookStore = self._sqlStore
</ins><span class="cx"> 
</span><span class="cx">         # Provision the home and addressbook now
</span><span class="cx">         txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -395,7 +410,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that kind property UID is stored correctly in database
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        addressbookStore = self._sqlStore
</ins><span class="cx"> 
</span><span class="cx">         # Provision the home and addressbook, one user and one group
</span><span class="cx">         txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -441,7 +456,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that kind property vCard is stored correctly in database
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        addressbookStore = self._sqlStore
</ins><span class="cx"> 
</span><span class="cx">         # Provision the home and addressbook, one user and one group
</span><span class="cx">         txn = addressbookStore.newTransaction()
</span><span class="lines">@@ -532,7 +547,8 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that kind property vCard is stored correctly in database
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        addressbookStore = yield buildStore(self, self.notifierFactory)
</del><ins>+        addressbookStore = self._sqlStore
+        cleanStore(self, addressbookStore)
</ins><span class="cx"> 
</span><span class="cx">         # Provision the home and addressbook, one user and one group
</span><span class="cx">         txn = addressbookStore.newTransaction()
</span></span></pre></div>
<a id="CalendarServertrunktxdavcarddavdatastoretesttest_sql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/carddav/datastore/test/test_sql_sharing.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/carddav/datastore/test/test_sql_sharing.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/test_sql_sharing.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1134,23 +1134,27 @@
</span><span class="cx">         otherAB = yield self.addressbookUnderTest(home=&quot;user02&quot;, name=&quot;user01&quot;)
</span><span class="cx">         self.assertNotEqual(otherAB._bindRevision, 0)
</span><span class="cx"> 
</span><del>-        changed, deleted = yield otherAB.resourceNamesSinceRevision(0)
</del><ins>+        changed, deleted, invalid = yield otherAB.resourceNamesSinceRevision(0)
</ins><span class="cx">         self.assertNotEqual(len(changed), 0)
</span><span class="cx">         self.assertEqual(len(deleted), 0)
</span><ins>+        self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><del>-        changed, deleted = yield otherAB.resourceNamesSinceRevision(otherAB._bindRevision)
</del><ins>+        changed, deleted, invalid = yield otherAB.resourceNamesSinceRevision(otherAB._bindRevision)
</ins><span class="cx">         self.assertEqual(len(changed), 0)
</span><span class="cx">         self.assertEqual(len(deleted), 0)
</span><ins>+        self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><span class="cx">         otherHome = yield self.addressbookHomeUnderTest(name=&quot;user02&quot;)
</span><span class="cx">         for depth in (&quot;1&quot;, &quot;infinity&quot;,):
</span><del>-            changed, deleted = yield otherHome.resourceNamesSinceRevision(0, depth)
</del><ins>+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(0, depth)
</ins><span class="cx">             self.assertNotEqual(len(changed), 0)
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+            self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><del>-            changed, deleted = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision, depth)
</del><ins>+            changed, deleted, invalid = yield otherHome.resourceNamesSinceRevision(otherAB._bindRevision, depth)
</ins><span class="cx">             self.assertEqual(len(changed), 0)
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+            self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1166,13 +1170,15 @@
</span><span class="cx">         otherAB = yield self.addressbookUnderTest(home=&quot;user02&quot;, name=&quot;user01&quot;)
</span><span class="cx">         self.assertNotEqual(otherAB._bindRevision, 0)
</span><span class="cx"> 
</span><del>-        changed, deleted = yield otherAB.resourceNamesSinceRevision(0)
</del><ins>+        changed, deleted, invalid = yield otherAB.resourceNamesSinceRevision(0)
</ins><span class="cx">         self.assertEqual(set(changed), set(['card1.vcf', 'card2.vcf', 'group1.vcf']))
</span><span class="cx">         self.assertEqual(len(deleted), 0)
</span><ins>+        self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><del>-        changed, deleted = yield otherAB.resourceNamesSinceRevision(otherAB._bindRevision)
</del><ins>+        changed, deleted, invalid = yield otherAB.resourceNamesSinceRevision(otherAB._bindRevision)
</ins><span class="cx">         self.assertEqual(len(changed), 0)
</span><span class="cx">         self.assertEqual(len(deleted), 0)
</span><ins>+        self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><span class="cx">         for depth, result in (
</span><span class="cx">             (&quot;1&quot;, ['addressbook/',
</span><span class="lines">@@ -1184,13 +1190,15 @@
</span><span class="cx">                              'user01/card2.vcf',
</span><span class="cx">                              'user01/group1.vcf']
</span><span class="cx">              )):
</span><del>-            changed, deleted = yield otherAB.viewerHome().resourceNamesSinceRevision(0, depth)
</del><ins>+            changed, deleted, invalid = yield otherAB.viewerHome().resourceNamesSinceRevision(0, depth)
</ins><span class="cx">             self.assertEqual(set(changed), set(result))
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+            self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><del>-            changed, deleted = yield otherAB.viewerHome().resourceNamesSinceRevision(otherAB._bindRevision, depth)
</del><ins>+            changed, deleted, invalid = yield otherAB.viewerHome().resourceNamesSinceRevision(otherAB._bindRevision, depth)
</ins><span class="cx">             self.assertEqual(len(changed), 0)
</span><span class="cx">             self.assertEqual(len(deleted), 0)
</span><ins>+            self.assertEqual(len(invalid), 0)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorefilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/file.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/file.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/file.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -852,7 +852,8 @@
</span><span class="cx">     def resourceNamesSinceToken(self, token, depth):
</span><span class="cx">         deleted = []
</span><span class="cx">         changed = []
</span><del>-        return succeed((changed, deleted))
</del><ins>+        invalid = []
+        return succeed((changed, deleted, invalid))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     # @cached
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepodding__init__py"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/__init__.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/__init__.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,15 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepodding__init__pyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepodding__init__py"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/__init__.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/__init__.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/__init__.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingconduitpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/conduit.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/conduit.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,845 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twext.python.log import Logger
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-
-from txdav.common.datastore.podding.request import ConduitRequest
-from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
-from txdav.common.icommondatastore import ExternalShareFailed
-from twisted.python.reflect import namedClass
-from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
-from twistedcaldav.caldavxml import TimeRange
-
-
-__all__ = [
-    &quot;PoddingConduitResource&quot;,
-]
-
-log = Logger()
-
-
-class FailedCrossPodRequestError(RuntimeError):
-    &quot;&quot;&quot;
-    Request returned an error.
-    &quot;&quot;&quot;
-    pass
-
-
-
-class PoddingConduit(object):
-    &quot;&quot;&quot;
-    This class is the API/RPC bridge between cross-pod requests and the store.
-
-    Each cross-pod request/response is described by a Python C{dict} that is serialized
-    to JSON for the HTTP request/response.
-
-    Each request C{dict} has an &quot;action&quot; key that indicates what call is being made, and
-    the other keys are arguments to that call.
-
-    Each response C{dict} has a &quot;result&quot; key that indicates the call result, and other
-    optional keys for any values returned by the call.
-
-    The conduit provides two methods for each action: one for the sending side and one for
-    the receiving side, called &quot;send_{action}&quot; and &quot;recv_{action}&quot;, respectively, where
-    {action} is the action value.
-
-    The &quot;send_{action}&quot; calls each have a set of arguments specific to the call itself. The
-    code takes care of packing that into a C{dict} and sending to the appropriate pod.
-
-    The &quot;recv_{action}&quot; calls take a single C{dict} argument that is the deserialized JSON
-    data from the incoming request. The return value is a C{dict} with the result.
-
-    Some simple forms of send_/recv_ methods can be auto-generated to simplify coding.
-
-    Right now this conduit is used for cross-pod sharing operations. In the future we will
-    likely use it for cross-pod migration.
-    &quot;&quot;&quot;
-
-    conduitRequestClass = ConduitRequest
-
-    def __init__(self, store):
-        &quot;&quot;&quot;
-        @param store: the L{CommonDataStore} in use.
-        &quot;&quot;&quot;
-        self.store = store
-
-
-    def validRequst(self, source_guid, destination_guid):
-        &quot;&quot;&quot;
-        Verify that the specified GUIDs are valid for the request and return the
-        matching directory records.
-
-        @param source_guid: GUID for the user on whose behalf the request is being made
-        @type source_guid: C{str}
-        @param destination_guid: GUID for the user to whom the request is being sent
-        @type destination_guid: C{str}
-
-        @return: C{tuple} of L{IStoreDirectoryRecord}
-        &quot;&quot;&quot;
-
-        source = self.store.directoryService().recordWithUID(source_guid)
-        if source is None:
-            raise DirectoryRecordNotFoundError(&quot;Cross-pod source: {}&quot;.format(source_guid))
-        if not source.thisServer():
-            raise FailedCrossPodRequestError(&quot;Cross-pod source not on this server: {}&quot;.format(source_guid))
-
-        destination = self.store.directoryService().recordWithUID(destination_guid)
-        if destination is None:
-            raise DirectoryRecordNotFoundError(&quot;Cross-pod destination: {}&quot;.format(destination_guid))
-        if destination.thisServer():
-            raise FailedCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(destination_guid))
-
-        return (source, destination,)
-
-
-    @inlineCallbacks
-    def sendRequest(self, txn, recipient, data, stream=None, streamType=None):
-
-        request = self.conduitRequestClass(recipient.server(), data, stream, streamType)
-        try:
-            response = (yield request.doRequest(txn))
-        except Exception as e:
-            raise FailedCrossPodRequestError(&quot;Failed cross-pod request: {}&quot;.format(e))
-        returnValue(response)
-
-
-    @inlineCallbacks
-    def processRequest(self, data):
-        &quot;&quot;&quot;
-        Process the request.
-
-        @param data: the JSON data to process
-        @type data: C{dict}
-        &quot;&quot;&quot;
-        # Must have a dict with an &quot;action&quot; key
-        try:
-            action = data[&quot;action&quot;]
-        except (KeyError, TypeError) as e:
-            log.error(&quot;JSON data must have an object as its root with an 'action' attribute: {ex}\n{json}&quot;, ex=e, json=data)
-            raise FailedCrossPodRequestError(&quot;JSON data must have an object as its root with an 'action' attribute: {}\n{}&quot;.format(e, data,))
-
-        if action == &quot;ping&quot;:
-            result = {&quot;result&quot;: &quot;ok&quot;}
-            returnValue(result)
-
-        method = &quot;recv_{}&quot;.format(action.replace(&quot;-&quot;, &quot;_&quot;))
-        if not hasattr(self, method):
-            log.error(&quot;Unsupported action: {action}&quot;, action=action)
-            raise FailedCrossPodRequestError(&quot;Unsupported action: {}&quot;.format(action))
-
-        # Need a transaction to work with
-        txn = self.store.newTransaction(repr(&quot;Conduit request&quot;))
-
-        # Do the actual request processing
-        try:
-            result = (yield getattr(self, method)(txn, data))
-        except Exception as e:
-            yield txn.abort()
-            log.error(&quot;Failed action: {action}, {ex}&quot;, action=action, ex=e)
-            raise FailedCrossPodRequestError(&quot;Failed action: {}, {}&quot;.format(action, e))
-
-        yield txn.commit()
-
-        returnValue(result)
-
-
-    #
-    # Invite related apis
-    #
-
-    @inlineCallbacks
-    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: GUID 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: GUID 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 = self.validRequst(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: GUID of the sharer.
-        @type ownerUID: C{str}
-        @param ownerID: resource ID of the sharer calendar
-        @type ownerID: C{int}
-        @param shareeUID: GUID of the sharee
-        @type shareeUID: C{str}
-        @param shareUID: Resource/invite ID for sharee
-        @type shareUID: C{str}
-        &quot;&quot;&quot;
-
-        _ignore_sender, recipient = self.validRequst(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: GUID of the sharer.
-        @type ownerUID: C{str}
-        @param shareeUID: GUID 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 = self.validRequst(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 = 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 = 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 = 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
-    #
-
-    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 = self.validRequst(shareeUID, ownerUID)
-
-        result = {
-            &quot;action&quot;: action,
-            &quot;type&quot;: homeType,
-            &quot;owner&quot;: ownerUID,
-            &quot;owner_id&quot;: ownerID,
-            &quot;sharee&quot;: shareeUID,
-        }
-        if child is not None:
-            result[&quot;resource_id&quot;] = child.id()
-        return result, recipient
-
-
-    @inlineCallbacks
-    def _recv(self, txn, message, expected_action):
-        &quot;&quot;&quot;
-        Base behavior for sharer data access.
-
-        @param message: message arguments
-        @type message: C{dict}
-        &quot;&quot;&quot;
-
-        if message[&quot;action&quot;] != expected_action:
-            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_{}&quot;.format(message[&quot;action&quot;], expected_action))
-
-        # 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 = 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 = 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="CalendarServertrunktxdavcommondatastorepoddingconduitpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingconduitpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/conduit.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/conduit.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/conduit.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/conduit.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,846 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.python.log import Logger
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.reflect import namedClass
+
+from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
+from txdav.common.datastore.podding.request import ConduitRequest
+from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
+from txdav.common.icommondatastore import ExternalShareFailed
+
+from twistedcaldav.caldavxml import TimeRange
+
+
+__all__ = [
+    &quot;PoddingConduitResource&quot;,
+]
+
+log = Logger()
+
+
+class FailedCrossPodRequestError(RuntimeError):
+    &quot;&quot;&quot;
+    Request returned an error.
+    &quot;&quot;&quot;
+    pass
+
+
+
+class PoddingConduit(object):
+    &quot;&quot;&quot;
+    This class is the API/RPC bridge between cross-pod requests and the store.
+
+    Each cross-pod request/response is described by a Python C{dict} that is serialized
+    to JSON for the HTTP request/response.
+
+    Each request C{dict} has an &quot;action&quot; key that indicates what call is being made, and
+    the other keys are arguments to that call.
+
+    Each response C{dict} has a &quot;result&quot; key that indicates the call result, and other
+    optional keys for any values returned by the call.
+
+    The conduit provides two methods for each action: one for the sending side and one for
+    the receiving side, called &quot;send_{action}&quot; and &quot;recv_{action}&quot;, respectively, where
+    {action} is the action value.
+
+    The &quot;send_{action}&quot; calls each have a set of arguments specific to the call itself. The
+    code takes care of packing that into a C{dict} and sending to the appropriate pod.
+
+    The &quot;recv_{action}&quot; calls take a single C{dict} argument that is the deserialized JSON
+    data from the incoming request. The return value is a C{dict} with the result.
+
+    Some simple forms of send_/recv_ methods can be auto-generated to simplify coding.
+
+    Right now this conduit is used for cross-pod sharing operations. In the future we will
+    likely use it for cross-pod migration.
+    &quot;&quot;&quot;
+
+    conduitRequestClass = ConduitRequest
+
+    def __init__(self, store):
+        &quot;&quot;&quot;
+        @param store: the L{CommonDataStore} in use.
+        &quot;&quot;&quot;
+        self.store = store
+
+
+    def validRequst(self, source_guid, destination_guid):
+        &quot;&quot;&quot;
+        Verify that the specified GUIDs are valid for the request and return the
+        matching directory records.
+
+        @param source_guid: GUID for the user on whose behalf the request is being made
+        @type source_guid: C{str}
+        @param destination_guid: GUID for the user to whom the request is being sent
+        @type destination_guid: C{str}
+
+        @return: C{tuple} of L{IStoreDirectoryRecord}
+        &quot;&quot;&quot;
+
+        source = self.store.directoryService().recordWithUID(source_guid)
+        if source is None:
+            raise DirectoryRecordNotFoundError(&quot;Cross-pod source: {}&quot;.format(source_guid))
+        if not source.thisServer():
+            raise FailedCrossPodRequestError(&quot;Cross-pod source not on this server: {}&quot;.format(source_guid))
+
+        destination = self.store.directoryService().recordWithUID(destination_guid)
+        if destination is None:
+            raise DirectoryRecordNotFoundError(&quot;Cross-pod destination: {}&quot;.format(destination_guid))
+        if destination.thisServer():
+            raise FailedCrossPodRequestError(&quot;Cross-pod destination on this server: {}&quot;.format(destination_guid))
+
+        return (source, destination,)
+
+
+    @inlineCallbacks
+    def sendRequest(self, txn, recipient, data, stream=None, streamType=None):
+
+        request = self.conduitRequestClass(recipient.server(), data, stream, streamType)
+        try:
+            response = (yield request.doRequest(txn))
+        except Exception as e:
+            raise FailedCrossPodRequestError(&quot;Failed cross-pod request: {}&quot;.format(e))
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def processRequest(self, data):
+        &quot;&quot;&quot;
+        Process the request.
+
+        @param data: the JSON data to process
+        @type data: C{dict}
+        &quot;&quot;&quot;
+        # Must have a dict with an &quot;action&quot; key
+        try:
+            action = data[&quot;action&quot;]
+        except (KeyError, TypeError) as e:
+            log.error(&quot;JSON data must have an object as its root with an 'action' attribute: {ex}\n{json}&quot;, ex=e, json=data)
+            raise FailedCrossPodRequestError(&quot;JSON data must have an object as its root with an 'action' attribute: {}\n{}&quot;.format(e, data,))
+
+        if action == &quot;ping&quot;:
+            result = {&quot;result&quot;: &quot;ok&quot;}
+            returnValue(result)
+
+        method = &quot;recv_{}&quot;.format(action.replace(&quot;-&quot;, &quot;_&quot;))
+        if not hasattr(self, method):
+            log.error(&quot;Unsupported action: {action}&quot;, action=action)
+            raise FailedCrossPodRequestError(&quot;Unsupported action: {}&quot;.format(action))
+
+        # Need a transaction to work with
+        txn = self.store.newTransaction(repr(&quot;Conduit request&quot;))
+
+        # Do the actual request processing
+        try:
+            result = (yield getattr(self, method)(txn, data))
+        except Exception as e:
+            yield txn.abort()
+            log.error(&quot;Failed action: {action}, {ex}&quot;, action=action, ex=e)
+            raise FailedCrossPodRequestError(&quot;Failed action: {}, {}&quot;.format(action, e))
+
+        yield txn.commit()
+
+        returnValue(result)
+
+
+    #
+    # Invite related apis
+    #
+
+    @inlineCallbacks
+    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: GUID 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: GUID 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 = self.validRequst(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: GUID of the sharer.
+        @type ownerUID: C{str}
+        @param ownerID: resource ID of the sharer calendar
+        @type ownerID: C{int}
+        @param shareeUID: GUID of the sharee
+        @type shareeUID: C{str}
+        @param shareUID: Resource/invite ID for sharee
+        @type shareUID: C{str}
+        &quot;&quot;&quot;
+
+        _ignore_sender, recipient = self.validRequst(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: GUID of the sharer.
+        @type ownerUID: C{str}
+        @param shareeUID: GUID 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 = self.validRequst(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 = 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 = 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 = 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
+    #
+
+    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 = self.validRequst(shareeUID, ownerUID)
+
+        result = {
+            &quot;action&quot;: action,
+            &quot;type&quot;: homeType,
+            &quot;owner&quot;: ownerUID,
+            &quot;owner_id&quot;: ownerID,
+            &quot;sharee&quot;: shareeUID,
+        }
+        if child is not None:
+            result[&quot;resource_id&quot;] = child.id()
+        return result, recipient
+
+
+    @inlineCallbacks
+    def _recv(self, txn, message, expected_action):
+        &quot;&quot;&quot;
+        Base behavior for sharer data access.
+
+        @param message: message arguments
+        @type message: C{dict}
+        &quot;&quot;&quot;
+
+        if message[&quot;action&quot;] != expected_action:
+            raise FailedCrossPodRequestError(&quot;Wrong action '{}' for recv_{}&quot;.format(message[&quot;action&quot;], expected_action))
+
+        # 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 = 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 = 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;)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingrequestpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/request.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/request.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,191 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from calendarserver.version import version
-
-from twext.internet.gaiendpoint import GAIEndpoint
-from twext.python.log import Logger
-from twext.web2 import responsecode
-from twext.web2.client.http import HTTPClientProtocol, ClientRequest
-from twext.web2.dav.util import allDataFromStream
-from twext.web2.http_headers import Headers, MimeType
-from twext.web2.stream import MemoryStream
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.internet.protocol import Factory
-
-from twistedcaldav.accounting import accountingEnabledForCategory, \
-    emitAccounting
-from twistedcaldav.client.pool import _configuredClientContextFactory
-from twistedcaldav.config import config
-from twistedcaldav.util import utf8String
-
-from cStringIO import StringIO
-import base64
-import json
-
-
-log = Logger()
-
-
-
-class ConduitRequest(object):
-    &quot;&quot;&quot;
-    An HTTP request between pods. This is typically used to send and receive JSON data. However,
-    for attachments, we need to send the actual attachment data as the request body, so in that
-    case the JSON data is sent in an HTTP header.
-    &quot;&quot;&quot;
-
-    def __init__(self, server, data, stream=None, stream_type=None):
-        self.server = server
-        self.data = json.dumps(data)
-        self.stream = stream
-        self.streamType = stream_type
-
-
-    @inlineCallbacks
-    def doRequest(self, txn):
-
-        # Generate an HTTP client request
-        try:
-            if &quot;xpod&quot; not in txn.logItems:
-                txn.logItems[&quot;xpod&quot;] = 0
-            txn.logItems[&quot;xpod&quot;] += 1
-
-            response = (yield self._processRequest())
-
-            if accountingEnabledForCategory(&quot;xPod&quot;):
-                self.loggedResponse = yield self.logResponse(response)
-                emitAccounting(&quot;xPod&quot;, &quot;&quot;, self.loggedRequest + &quot;\n&quot; + self.loggedResponse, &quot;POST&quot;)
-
-            if response.code in (responsecode.OK,):
-                data = (yield allDataFromStream(response.stream))
-                data = json.loads(data)
-            else:
-                raise ValueError(&quot;Incorrect cross-pod response status code: {}&quot;.format(response.code))
-
-        except Exception as e:
-            # Request failed
-            log.error(&quot;Could not do cross-pod request : {request} {ex}&quot;, request=self, ex=e)
-            raise ValueError(&quot;Failed cross-pod request: {}&quot;.format(e))
-
-        returnValue(data)
-
-
-    @inlineCallbacks
-    def logRequest(self, request):
-        &quot;&quot;&quot;
-        Log an HTTP request.
-        &quot;&quot;&quot;
-
-        iostr = StringIO()
-        iostr.write(&quot;&gt;&gt;&gt;&gt; Request start\n\n&quot;)
-        if hasattr(request, &quot;clientproto&quot;):
-            protocol = &quot;HTTP/{:d}.{:d}&quot;.format(request.clientproto[0], request.clientproto[1])
-        else:
-            protocol = &quot;HTTP/1.1&quot;
-        iostr.write(&quot;{} {} {}\n&quot;.format(request.method, request.uri, protocol))
-        for name, valuelist in request.headers.getAllRawHeaders():
-            for value in valuelist:
-                # Do not log authorization details
-                if name not in (&quot;Authorization&quot;,):
-                    iostr.write(&quot;{}: {}\n&quot;.format(name, value))
-                else:
-                    iostr.write(&quot;{}: xxxxxxxxx\n&quot;.format(name))
-        iostr.write(&quot;\n&quot;)
-
-        # We need to play a trick with the request stream as we can only read it once. So we
-        # read it, store the value in a MemoryStream, and replace the request's stream with that,
-        # so the data can be read again. Note if we are sending an attachment, we won't log
-        # the attachment data as we do not want to read it all into memory.
-        if self.stream is None:
-            data = (yield allDataFromStream(request.stream))
-            iostr.write(data)
-            request.stream = MemoryStream(data if data is not None else &quot;&quot;)
-            request.stream.doStartReading = None
-        else:
-            iostr.write(&quot;&lt;&lt;Stream Type: {}&gt;&gt;\n&quot;.format(self.streamType))
-
-        iostr.write(&quot;\n\n&gt;&gt;&gt;&gt; Request end\n&quot;)
-        returnValue(iostr.getvalue())
-
-
-    @inlineCallbacks
-    def logResponse(self, response):
-        &quot;&quot;&quot;
-        Log an HTTP request.
-        &quot;&quot;&quot;
-        iostr = StringIO()
-        iostr.write(&quot;&gt;&gt;&gt;&gt; Response start\n\n&quot;)
-        code_message = responsecode.RESPONSES.get(response.code, &quot;Unknown Status&quot;)
-        iostr.write(&quot;HTTP/1.1 {:d} {}\n&quot;.format(response.code, code_message))
-        for name, valuelist in response.headers.getAllRawHeaders():
-            for value in valuelist:
-                # Do not log authorization details
-                if name not in (&quot;WWW-Authenticate&quot;,):
-                    iostr.write(&quot;{}: {}\n&quot;.format(name, value))
-                else:
-                    iostr.write(&quot;{}: xxxxxxxxx\n&quot;.format(name))
-        iostr.write(&quot;\n&quot;)
-
-        # We need to play a trick with the response stream to ensure we don't mess it up. So we
-        # read it, store the value in a MemoryStream, and replace the response's stream with that,
-        # so the data can be read again.
-        data = (yield allDataFromStream(response.stream))
-        iostr.write(data)
-        response.stream = MemoryStream(data if data is not None else &quot;&quot;)
-        response.stream.doStartReading = None
-
-        iostr.write(&quot;\n\n&gt;&gt;&gt;&gt; Response end\n&quot;)
-        returnValue(iostr.getvalue())
-
-
-    @inlineCallbacks
-    def _processRequest(self):
-        &quot;&quot;&quot;
-        Process the request by sending it to the relevant server.
-
-        @return: the HTTP response.
-        @rtype: L{Response}
-        &quot;&quot;&quot;
-        ssl, host, port, _ignore_path = self.server.details()
-        path = &quot;/&quot; + config.Servers.ConduitName
-
-        headers = Headers()
-        headers.setHeader(&quot;Host&quot;, utf8String(host + &quot;:{}&quot;.format(port)))
-        if self.streamType:
-            # For attachments we put the base64-encoded JSON data into a header
-            headers.setHeader(&quot;Content-Type&quot;, self.streamType)
-            headers.addRawHeader(&quot;XPOD&quot;, base64.b64encode(self.data))
-        else:
-            headers.setHeader(&quot;Content-Type&quot;, MimeType(&quot;application&quot;, &quot;json&quot;, params={&quot;charset&quot;: &quot;utf-8&quot;, }))
-        headers.setHeader(&quot;User-Agent&quot;, &quot;CalendarServer/{}&quot;.format(version))
-        headers.addRawHeader(*self.server.secretHeader())
-
-        from twisted.internet import reactor
-        f = Factory()
-        f.protocol = HTTPClientProtocol
-        ep = GAIEndpoint(reactor, host, port, _configuredClientContextFactory() if ssl else None)
-        proto = (yield ep.connect(f))
-
-        request = ClientRequest(&quot;POST&quot;, path, headers, self.stream if self.stream is not None else self.data)
-
-        if accountingEnabledForCategory(&quot;xPod&quot;):
-            self.loggedRequest = yield self.logRequest(request)
-
-        response = (yield proto.submitRequest(request))
-
-        returnValue(response)
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingrequestpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingrequestpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/request.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/request.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/request.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/request.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,192 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from calendarserver.version import version
+
+from twext.internet.gaiendpoint import GAIEndpoint
+from twext.python.log import Logger
+
+from txweb2 import responsecode
+from txweb2.client.http import HTTPClientProtocol, ClientRequest
+from txweb2.dav.util import allDataFromStream
+from txweb2.http_headers import Headers, MimeType
+from txweb2.stream import MemoryStream
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.protocol import Factory
+
+from twistedcaldav.accounting import accountingEnabledForCategory, \
+    emitAccounting
+from twistedcaldav.client.pool import _configuredClientContextFactory
+from twistedcaldav.config import config
+from twistedcaldav.util import utf8String
+
+from cStringIO import StringIO
+import base64
+import json
+
+
+log = Logger()
+
+
+
+class ConduitRequest(object):
+    &quot;&quot;&quot;
+    An HTTP request between pods. This is typically used to send and receive JSON data. However,
+    for attachments, we need to send the actual attachment data as the request body, so in that
+    case the JSON data is sent in an HTTP header.
+    &quot;&quot;&quot;
+
+    def __init__(self, server, data, stream=None, stream_type=None):
+        self.server = server
+        self.data = json.dumps(data)
+        self.stream = stream
+        self.streamType = stream_type
+
+
+    @inlineCallbacks
+    def doRequest(self, txn):
+
+        # Generate an HTTP client request
+        try:
+            if &quot;xpod&quot; not in txn.logItems:
+                txn.logItems[&quot;xpod&quot;] = 0
+            txn.logItems[&quot;xpod&quot;] += 1
+
+            response = (yield self._processRequest())
+
+            if accountingEnabledForCategory(&quot;xPod&quot;):
+                self.loggedResponse = yield self.logResponse(response)
+                emitAccounting(&quot;xPod&quot;, &quot;&quot;, self.loggedRequest + &quot;\n&quot; + self.loggedResponse, &quot;POST&quot;)
+
+            if response.code in (responsecode.OK,):
+                data = (yield allDataFromStream(response.stream))
+                data = json.loads(data)
+            else:
+                raise ValueError(&quot;Incorrect cross-pod response status code: {}&quot;.format(response.code))
+
+        except Exception as e:
+            # Request failed
+            log.error(&quot;Could not do cross-pod request : {request} {ex}&quot;, request=self, ex=e)
+            raise ValueError(&quot;Failed cross-pod request: {}&quot;.format(e))
+
+        returnValue(data)
+
+
+    @inlineCallbacks
+    def logRequest(self, request):
+        &quot;&quot;&quot;
+        Log an HTTP request.
+        &quot;&quot;&quot;
+
+        iostr = StringIO()
+        iostr.write(&quot;&gt;&gt;&gt;&gt; Request start\n\n&quot;)
+        if hasattr(request, &quot;clientproto&quot;):
+            protocol = &quot;HTTP/{:d}.{:d}&quot;.format(request.clientproto[0], request.clientproto[1])
+        else:
+            protocol = &quot;HTTP/1.1&quot;
+        iostr.write(&quot;{} {} {}\n&quot;.format(request.method, request.uri, protocol))
+        for name, valuelist in request.headers.getAllRawHeaders():
+            for value in valuelist:
+                # Do not log authorization details
+                if name not in (&quot;Authorization&quot;,):
+                    iostr.write(&quot;{}: {}\n&quot;.format(name, value))
+                else:
+                    iostr.write(&quot;{}: xxxxxxxxx\n&quot;.format(name))
+        iostr.write(&quot;\n&quot;)
+
+        # We need to play a trick with the request stream as we can only read it once. So we
+        # read it, store the value in a MemoryStream, and replace the request's stream with that,
+        # so the data can be read again. Note if we are sending an attachment, we won't log
+        # the attachment data as we do not want to read it all into memory.
+        if self.stream is None:
+            data = (yield allDataFromStream(request.stream))
+            iostr.write(data)
+            request.stream = MemoryStream(data if data is not None else &quot;&quot;)
+            request.stream.doStartReading = None
+        else:
+            iostr.write(&quot;&lt;&lt;Stream Type: {}&gt;&gt;\n&quot;.format(self.streamType))
+
+        iostr.write(&quot;\n\n&gt;&gt;&gt;&gt; Request end\n&quot;)
+        returnValue(iostr.getvalue())
+
+
+    @inlineCallbacks
+    def logResponse(self, response):
+        &quot;&quot;&quot;
+        Log an HTTP request.
+        &quot;&quot;&quot;
+        iostr = StringIO()
+        iostr.write(&quot;&gt;&gt;&gt;&gt; Response start\n\n&quot;)
+        code_message = responsecode.RESPONSES.get(response.code, &quot;Unknown Status&quot;)
+        iostr.write(&quot;HTTP/1.1 {:d} {}\n&quot;.format(response.code, code_message))
+        for name, valuelist in response.headers.getAllRawHeaders():
+            for value in valuelist:
+                # Do not log authorization details
+                if name not in (&quot;WWW-Authenticate&quot;,):
+                    iostr.write(&quot;{}: {}\n&quot;.format(name, value))
+                else:
+                    iostr.write(&quot;{}: xxxxxxxxx\n&quot;.format(name))
+        iostr.write(&quot;\n&quot;)
+
+        # We need to play a trick with the response stream to ensure we don't mess it up. So we
+        # read it, store the value in a MemoryStream, and replace the response's stream with that,
+        # so the data can be read again.
+        data = (yield allDataFromStream(response.stream))
+        iostr.write(data)
+        response.stream = MemoryStream(data if data is not None else &quot;&quot;)
+        response.stream.doStartReading = None
+
+        iostr.write(&quot;\n\n&gt;&gt;&gt;&gt; Response end\n&quot;)
+        returnValue(iostr.getvalue())
+
+
+    @inlineCallbacks
+    def _processRequest(self):
+        &quot;&quot;&quot;
+        Process the request by sending it to the relevant server.
+
+        @return: the HTTP response.
+        @rtype: L{Response}
+        &quot;&quot;&quot;
+        ssl, host, port, _ignore_path = self.server.details()
+        path = &quot;/&quot; + config.Servers.ConduitName
+
+        headers = Headers()
+        headers.setHeader(&quot;Host&quot;, utf8String(host + &quot;:{}&quot;.format(port)))
+        if self.streamType:
+            # For attachments we put the base64-encoded JSON data into a header
+            headers.setHeader(&quot;Content-Type&quot;, self.streamType)
+            headers.addRawHeader(&quot;XPOD&quot;, base64.b64encode(self.data))
+        else:
+            headers.setHeader(&quot;Content-Type&quot;, MimeType(&quot;application&quot;, &quot;json&quot;, params={&quot;charset&quot;: &quot;utf-8&quot;, }))
+        headers.setHeader(&quot;User-Agent&quot;, &quot;CalendarServer/{}&quot;.format(version))
+        headers.addRawHeader(*self.server.secretHeader())
+
+        from twisted.internet import reactor
+        f = Factory()
+        f.protocol = HTTPClientProtocol
+        ep = GAIEndpoint(reactor, host, port, _configuredClientContextFactory() if ssl else None)
+        proto = (yield ep.connect(f))
+
+        request = ClientRequest(&quot;POST&quot;, path, headers, self.stream if self.stream is not None else self.data)
+
+        if accountingEnabledForCategory(&quot;xPod&quot;):
+            self.loggedRequest = yield self.logRequest(request)
+
+        response = (yield proto.submitRequest(request))
+
+        returnValue(response)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingresourcepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/resource.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/resource.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,193 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twext.web2 import responsecode
-from twext.web2.dav.noneprops import NonePropertyStore
-from twext.web2.dav.util import allDataFromStream
-from twext.web2.http import Response, HTTPError, StatusResponse, JSONResponse
-from twext.web2.http_headers import MimeType
-
-from twisted.internet.defer import succeed, returnValue, inlineCallbacks
-
-from twistedcaldav.extensions import DAVResource, \
-    DAVResourceWithoutChildrenMixin
-from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
-from twistedcaldav.scheduling_store.caldav.resource import \
-    deliverSchedulePrivilegeSet
-
-from txdav.xml import element as davxml
-from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers
-from txdav.common.datastore.podding.conduit import FailedCrossPodRequestError
-
-import base64
-import json
-
-__all__ = [
-    &quot;ConduitResource&quot;,
-]
-
-class ConduitResource(ReadOnlyNoCopyResourceMixIn, DAVResourceWithoutChildrenMixin, DAVResource):
-    &quot;&quot;&quot;
-    Podding cross-pod RPC conduit resource.
-
-    Extends L{DAVResource} to provide cross-pod RPC functionality.
-    &quot;&quot;&quot;
-
-    def __init__(self, parent, store):
-        &quot;&quot;&quot;
-        @param parent: the parent resource of this one.
-        &quot;&quot;&quot;
-        assert parent is not None
-
-        DAVResource.__init__(self, principalCollections=parent.principalCollections())
-
-        self.parent = parent
-        self.store = store
-
-
-    def deadProperties(self):
-        if not hasattr(self, &quot;_dead_properties&quot;):
-            self._dead_properties = NonePropertyStore(self)
-        return self._dead_properties
-
-
-    def etag(self):
-        return succeed(None)
-
-
-    def checkPreconditions(self, request):
-        return None
-
-
-    def resourceType(self):
-        return davxml.ResourceType.ischeduleinbox
-
-
-    def contentType(self):
-        return MimeType.fromString(&quot;text/html; charset=utf-8&quot;)
-
-
-    def isCollection(self):
-        return False
-
-
-    def isCalendarCollection(self):
-        return False
-
-
-    def isPseudoCalendarCollection(self):
-        return False
-
-
-    def principalForCalendarUserAddress(self, address):
-        for principalCollection in self.principalCollections():
-            principal = principalCollection.principalForCalendarUserAddress(address)
-            if principal is not None:
-                return principal
-        return None
-
-
-    def render(self, request):
-        output = &quot;&quot;&quot;&lt;html&gt;
-&lt;head&gt;
-&lt;title&gt;Podding Conduit Resource&lt;/title&gt;
-&lt;/head&gt;
-&lt;body&gt;
-&lt;h1&gt;Podding Conduit Resource.&lt;/h1&gt;
-&lt;/body
-&lt;/html&gt;&quot;&quot;&quot;
-
-        response = Response(200, {}, output)
-        response.headers.setHeader(&quot;content-type&quot;, MimeType(&quot;text&quot;, &quot;html&quot;))
-        return response
-
-
-    @inlineCallbacks
-    def http_POST(self, request):
-        &quot;&quot;&quot;
-        The server-to-server POST method.
-        &quot;&quot;&quot;
-
-        # Check shared secret
-        if not Servers.getThisServer().checkSharedSecret(request.headers):
-            self.log.error(&quot;Invalid shared secret header in cross-pod request&quot;)
-            raise HTTPError(StatusResponse(responsecode.FORBIDDEN, &quot;Not authorized to make this request&quot;))
-
-        # Look for XPOD header
-        xpod = request.headers.getRawHeaders(&quot;XPOD&quot;)
-        contentType = request.headers.getHeader(&quot;content-type&quot;)
-        if xpod is not None:
-            # Attachments are sent in the request body with the JSON data in a header. We
-            # decode the header and add the request.stream as an attribute of the JSON object.
-            xpod = xpod[0]
-            try:
-                j = json.loads(base64.b64decode(xpod))
-            except (TypeError, ValueError) as e:
-                self.log.error(&quot;Invalid JSON header in request: {ex}\n{xpod}&quot;, ex=e, xpod=xpod)
-                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;Invalid JSON header in request: {}\n{}&quot;.format(e, xpod)))
-            j[&quot;stream&quot;] = request.stream
-            j[&quot;streamType&quot;] = contentType
-        else:
-            # Check content first
-            if &quot;{}/{}&quot;.format(contentType.mediaType, contentType.mediaSubtype) != &quot;application/json&quot;:
-                self.log.error(&quot;MIME type {mime} not allowed in request&quot;, mime=contentType)
-                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;MIME type {} not allowed in request&quot;.format(contentType)))
-
-            body = (yield allDataFromStream(request.stream))
-            try:
-                j = json.loads(body)
-            except ValueError as e:
-                self.log.error(&quot;Invalid JSON data in request: {ex}\n{body}&quot;, ex=e, body=body)
-                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;Invalid JSON data in request: {}\n{}&quot;.format(e, body)))
-
-        # Log extended item
-        if not hasattr(request, &quot;extendedLogItems&quot;):
-            request.extendedLogItems = {}
-        request.extendedLogItems[&quot;xpod&quot;] = j[&quot;action&quot;] if &quot;action&quot; in j else &quot;unknown&quot;
-
-        # Get the conduit to process the data
-        try:
-            result = yield self.store.conduit.processRequest(j)
-        except FailedCrossPodRequestError as e:
-            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
-        except Exception as e:
-            raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, str(e)))
-
-        response = JSONResponse(responsecode.OK, result)
-        returnValue(response)
-
-
-    ##
-    # ACL
-    ##
-
-    def supportedPrivileges(self, request):
-        return succeed(deliverSchedulePrivilegeSet)
-
-
-    def defaultAccessControlList(self):
-        privs = (
-            davxml.Privilege(davxml.Read()),
-        )
-
-        return davxml.ACL(
-            # DAV:Read for all principals (includes anonymous)
-            davxml.ACE(
-                davxml.Principal(davxml.All()),
-                davxml.Grant(*privs),
-                davxml.Protected(),
-            ),
-        )
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingresourcepyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingresourcepy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/resource.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/resource.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/resource.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/resource.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,193 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from txweb2 import responsecode
+from txweb2.dav.noneprops import NonePropertyStore
+from txweb2.dav.util import allDataFromStream
+from txweb2.http import Response, HTTPError, StatusResponse, JSONResponse
+from txweb2.http_headers import MimeType
+
+from twisted.internet.defer import succeed, returnValue, inlineCallbacks
+
+from twistedcaldav.extensions import DAVResource, \
+    DAVResourceWithoutChildrenMixin
+from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn
+from twistedcaldav.scheduling_store.caldav.resource import \
+    deliverSchedulePrivilegeSet
+
+from txdav.xml import element as davxml
+from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers
+from txdav.common.datastore.podding.conduit import FailedCrossPodRequestError
+
+import base64
+import json
+
+__all__ = [
+    &quot;ConduitResource&quot;,
+]
+
+class ConduitResource(ReadOnlyNoCopyResourceMixIn, DAVResourceWithoutChildrenMixin, DAVResource):
+    &quot;&quot;&quot;
+    Podding cross-pod RPC conduit resource.
+
+    Extends L{DAVResource} to provide cross-pod RPC functionality.
+    &quot;&quot;&quot;
+
+    def __init__(self, parent, store):
+        &quot;&quot;&quot;
+        @param parent: the parent resource of this one.
+        &quot;&quot;&quot;
+        assert parent is not None
+
+        DAVResource.__init__(self, principalCollections=parent.principalCollections())
+
+        self.parent = parent
+        self.store = store
+
+
+    def deadProperties(self):
+        if not hasattr(self, &quot;_dead_properties&quot;):
+            self._dead_properties = NonePropertyStore(self)
+        return self._dead_properties
+
+
+    def etag(self):
+        return succeed(None)
+
+
+    def checkPreconditions(self, request):
+        return None
+
+
+    def resourceType(self):
+        return davxml.ResourceType.ischeduleinbox
+
+
+    def contentType(self):
+        return MimeType.fromString(&quot;text/html; charset=utf-8&quot;)
+
+
+    def isCollection(self):
+        return False
+
+
+    def isCalendarCollection(self):
+        return False
+
+
+    def isPseudoCalendarCollection(self):
+        return False
+
+
+    def principalForCalendarUserAddress(self, address):
+        for principalCollection in self.principalCollections():
+            principal = principalCollection.principalForCalendarUserAddress(address)
+            if principal is not None:
+                return principal
+        return None
+
+
+    def render(self, request):
+        output = &quot;&quot;&quot;&lt;html&gt;
+&lt;head&gt;
+&lt;title&gt;Podding Conduit Resource&lt;/title&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;h1&gt;Podding Conduit Resource.&lt;/h1&gt;
+&lt;/body
+&lt;/html&gt;&quot;&quot;&quot;
+
+        response = Response(200, {}, output)
+        response.headers.setHeader(&quot;content-type&quot;, MimeType(&quot;text&quot;, &quot;html&quot;))
+        return response
+
+
+    @inlineCallbacks
+    def http_POST(self, request):
+        &quot;&quot;&quot;
+        The server-to-server POST method.
+        &quot;&quot;&quot;
+
+        # Check shared secret
+        if not Servers.getThisServer().checkSharedSecret(request.headers):
+            self.log.error(&quot;Invalid shared secret header in cross-pod request&quot;)
+            raise HTTPError(StatusResponse(responsecode.FORBIDDEN, &quot;Not authorized to make this request&quot;))
+
+        # Look for XPOD header
+        xpod = request.headers.getRawHeaders(&quot;XPOD&quot;)
+        contentType = request.headers.getHeader(&quot;content-type&quot;)
+        if xpod is not None:
+            # Attachments are sent in the request body with the JSON data in a header. We
+            # decode the header and add the request.stream as an attribute of the JSON object.
+            xpod = xpod[0]
+            try:
+                j = json.loads(base64.b64decode(xpod))
+            except (TypeError, ValueError) as e:
+                self.log.error(&quot;Invalid JSON header in request: {ex}\n{xpod}&quot;, ex=e, xpod=xpod)
+                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;Invalid JSON header in request: {}\n{}&quot;.format(e, xpod)))
+            j[&quot;stream&quot;] = request.stream
+            j[&quot;streamType&quot;] = contentType
+        else:
+            # Check content first
+            if &quot;{}/{}&quot;.format(contentType.mediaType, contentType.mediaSubtype) != &quot;application/json&quot;:
+                self.log.error(&quot;MIME type {mime} not allowed in request&quot;, mime=contentType)
+                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;MIME type {} not allowed in request&quot;.format(contentType)))
+
+            body = (yield allDataFromStream(request.stream))
+            try:
+                j = json.loads(body)
+            except ValueError as e:
+                self.log.error(&quot;Invalid JSON data in request: {ex}\n{body}&quot;, ex=e, body=body)
+                raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, &quot;Invalid JSON data in request: {}\n{}&quot;.format(e, body)))
+
+        # Log extended item
+        if not hasattr(request, &quot;extendedLogItems&quot;):
+            request.extendedLogItems = {}
+        request.extendedLogItems[&quot;xpod&quot;] = j[&quot;action&quot;] if &quot;action&quot; in j else &quot;unknown&quot;
+
+        # Get the conduit to process the data
+        try:
+            result = yield self.store.conduit.processRequest(j)
+        except FailedCrossPodRequestError as e:
+            raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, str(e)))
+        except Exception as e:
+            raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, str(e)))
+
+        response = JSONResponse(responsecode.OK, result)
+        returnValue(response)
+
+
+    ##
+    # ACL
+    ##
+
+    def supportedPrivileges(self, request):
+        return succeed(deliverSchedulePrivilegeSet)
+
+
+    def defaultAccessControlList(self):
+        privs = (
+            davxml.Privilege(davxml.Read()),
+        )
+
+        return davxml.ACL(
+            # DAV:Read for all principals (includes anonymous)
+            davxml.ACE(
+                davxml.Principal(davxml.All()),
+                davxml.Grant(*privs),
+                davxml.Protected(),
+            ),
+        )
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtest__init__py"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/test/__init__.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/__init__.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,15 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtest__init__pyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtest__init__py"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/test/__init__.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/__init__.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/test/__init__.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtesttest_conduitpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,1082 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from pycalendar.datetime import DateTime
-from pycalendar.period import Period
-
-from twext.python.clsprop import classproperty
-import twext.web2.dav.test.util
-from twext.web2.http_headers import MimeType
-from twext.web2.stream import MemoryStream
-
-from twisted.internet.defer import inlineCallbacks, succeed, returnValue
-
-from twistedcaldav import caldavxml
-from twistedcaldav.caldavxml import TimeRange
-from twistedcaldav.ical import Component, normalize_iCalStr
-
-from txdav.caldav.datastore.query.filter import Filter
-from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
-from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, Server
-from txdav.caldav.datastore.sql import ManagedAttachment
-from txdav.caldav.datastore.test.common import CaptureProtocol
-from txdav.caldav.datastore.test.util import buildCalendarStore, \
-    TestCalendarStoreDirectoryRecord
-from txdav.common.datastore.podding.conduit import PoddingConduit, \
-    FailedCrossPodRequestError
-from txdav.common.datastore.podding.resource import ConduitResource
-from txdav.common.datastore.podding.test.util import MultiStoreConduitTest, \
-    FakeConduitRequest
-from txdav.common.datastore.sql_tables import _BIND_STATUS_ACCEPTED
-from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
-from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
-    ObjectResourceNameNotAllowedError
-from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
-
-
-class TestConduit (CommonCommonTests, twext.web2.dav.test.util.TestCase):
-
-    class FakeConduit(object):
-
-        def recv_fake(self, j):
-            return succeed({
-                &quot;result&quot;: &quot;ok&quot;,
-                &quot;back2u&quot;: j[&quot;echo&quot;],
-                &quot;more&quot;: &quot;bits&quot;,
-            })
-
-
-    @inlineCallbacks
-    def setUp(self):
-        yield super(TestConduit, self).setUp()
-        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
-        self.directory = self._sqlCalendarStore.directoryService()
-
-        for ctr in range(1, 100):
-            self.directory.addRecord(TestCalendarStoreDirectoryRecord(
-                &quot;puser{:02d}&quot;.format(ctr),
-                (&quot;puser{:02d}&quot;.format(ctr),),
-                &quot;Puser {:02d}&quot;.format(ctr),
-                frozenset((
-                    &quot;urn:uuid:puser{:02d}&quot;.format(ctr),
-                    &quot;mailto:puser{:02d}@example.com&quot;.format(ctr),
-                )),
-                thisServer=False,
-            ))
-
-        self.site.resource.putChild(&quot;conduit&quot;, ConduitResource(self.site.resource, self.storeUnderTest()))
-
-        self.thisServer = Server(&quot;A&quot;, &quot;http://127.0.0.1&quot;, &quot;A&quot;, True)
-        Servers.addServer(self.thisServer)
-
-        yield self.populate()
-
-
-    def storeUnderTest(self):
-        &quot;&quot;&quot;
-        Return a store for testing.
-        &quot;&quot;&quot;
-        return self._sqlCalendarStore
-
-
-    @inlineCallbacks
-    def populate(self):
-        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
-        self.notifierFactory.reset()
-
-
-    @classproperty(cache=False)
-    def requirements(cls): #@NoSelf
-        return {
-        &quot;user01&quot;: {
-            &quot;calendar_1&quot;: {
-            },
-            &quot;inbox&quot;: {
-            },
-        },
-        &quot;user02&quot;: {
-            &quot;calendar_1&quot;: {
-            },
-            &quot;inbox&quot;: {
-            },
-        },
-        &quot;user03&quot;: {
-            &quot;calendar_1&quot;: {
-            },
-            &quot;inbox&quot;: {
-            },
-        },
-    }
-
-
-    def test_validRequst(self):
-        &quot;&quot;&quot;
-        Cross-pod request fails when there is no shared secret header present.
-        &quot;&quot;&quot;
-
-        conduit = PoddingConduit(self.storeUnderTest())
-        r1, r2 = conduit.validRequst(&quot;user01&quot;, &quot;puser02&quot;)
-        self.assertTrue(r1 is not None)
-        self.assertTrue(r2 is not None)
-
-        self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, &quot;bogus01&quot;, &quot;user02&quot;)
-        self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, &quot;user01&quot;, &quot;bogus02&quot;)
-        self.assertRaises(FailedCrossPodRequestError, conduit.validRequst, &quot;user01&quot;, &quot;user02&quot;)
-
-
-
-class TestConduitToConduit(MultiStoreConduitTest):
-
-    class FakeConduit(PoddingConduit):
-
-        @inlineCallbacks
-        def send_fake(self, txn, ownerUID, shareeUID):
-            _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
-            action = {
-                &quot;action&quot;: &quot;fake&quot;,
-                &quot;echo&quot;: &quot;bravo&quot;
-            }
-
-            result = yield self.sendRequest(txn, sharee, action)
-            returnValue(result)
-
-
-        def recv_fake(self, txn, j):
-            return succeed({
-                &quot;result&quot;: &quot;ok&quot;,
-                &quot;back2u&quot;: j[&quot;echo&quot;],
-                &quot;more&quot;: &quot;bits&quot;,
-            })
-
-
-    def makeConduit(self, store):
-        &quot;&quot;&quot;
-        Use our own variant.
-        &quot;&quot;&quot;
-        conduit = self.FakeConduit(store)
-        conduit.conduitRequestClass = FakeConduitRequest
-        return conduit
-
-
-    @inlineCallbacks
-    def test_fake_action(self):
-        &quot;&quot;&quot;
-        Cross-pod request works when conduit does support the action.
-        &quot;&quot;&quot;
-
-        txn = self.transactionUnderTest()
-        store1 = self.storeUnderTest()
-        response = yield store1.conduit.send_fake(txn, &quot;user01&quot;, &quot;puser01&quot;)
-        self.assertTrue(&quot;result&quot; in response)
-        self.assertEqual(response[&quot;result&quot;], &quot;ok&quot;)
-        self.assertTrue(&quot;back2u&quot; in response)
-        self.assertEqual(response[&quot;back2u&quot;], &quot;bravo&quot;)
-        self.assertTrue(&quot;more&quot; in response)
-        self.assertEqual(response[&quot;more&quot;], &quot;bits&quot;)
-        yield txn.commit()
-
-        store2 = self.otherStoreUnderTest()
-        txn = store2.newTransaction()
-        response = yield store2.conduit.send_fake(txn, &quot;puser01&quot;, &quot;user01&quot;)
-        self.assertTrue(&quot;result&quot; in response)
-        self.assertEqual(response[&quot;result&quot;], &quot;ok&quot;)
-        self.assertTrue(&quot;back2u&quot; in response)
-        self.assertEqual(response[&quot;back2u&quot;], &quot;bravo&quot;)
-        self.assertTrue(&quot;more&quot; in response)
-        self.assertEqual(response[&quot;more&quot;], &quot;bits&quot;)
-        yield txn.commit()
-
-
-
-class TestConduitAPI(MultiStoreConduitTest):
-    &quot;&quot;&quot;
-    Test that the conduit api works.
-    &quot;&quot;&quot;
-
-    nowYear = {&quot;now&quot;: DateTime.getToday().getYear()}
-
-    caldata1 = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:uid1
-DTSTART:{now:04d}0102T140000Z
-DURATION:PT1H
-CREATED:20060102T190000Z
-DTSTAMP:20051222T210507Z
-RRULE:FREQ=WEEKLY
-SUMMARY:instance
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
-
-    caldata1_changed = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:uid1
-DTSTART:{now:04d}0102T150000Z
-DURATION:PT1H
-CREATED:20060102T190000Z
-DTSTAMP:20051222T210507Z
-RRULE:FREQ=WEEKLY
-SUMMARY:instance changed
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
-
-    caldata2 = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:uid2
-DTSTART:{now:04d}0102T160000Z
-DURATION:PT1H
-CREATED:20060102T190000Z
-DTSTAMP:20051222T210507Z
-RRULE:FREQ=WEEKLY
-SUMMARY:instance
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
-
-    caldata3 = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:uid3
-DTSTART:{now:04d}0102T160000Z
-DURATION:PT1H
-CREATED:20060102T190000Z
-DTSTAMP:20051222T210507Z
-RRULE:FREQ=WEEKLY
-SUMMARY:instance
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
-
-    @inlineCallbacks
-    def test_basic_share(self):
-        &quot;&quot;&quot;
-        Test that basic invite/uninvite works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        shared = yield  calendar1.shareeView(&quot;puser01&quot;)
-        self.assertEqual(shared.shareStatus(), _BIND_STATUS_ACCEPTED)
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        self.assertTrue(shared is not None)
-        self.assertTrue(shared.external())
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield calendar1.uninviteUserFromShare(&quot;puser01&quot;)
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        self.assertTrue(shared is None)
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_countobjects(self):
-        &quot;&quot;&quot;
-        Test that action=countobjects works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        count = yield shared.countObjectResources()
-        self.assertEqual(count, 0)
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        count = yield calendar1.countObjectResources()
-        self.assertEqual(count, 1)
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        count = yield shared.countObjectResources()
-        self.assertEqual(count, 1)
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        yield  object1.remove()
-        count = yield calendar1.countObjectResources()
-        self.assertEqual(count, 0)
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        count = yield shared.countObjectResources()
-        self.assertEqual(count, 0)
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_listobjects(self):
-        &quot;&quot;&quot;
-        Test that action=listobjects works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        objects = yield shared.listObjectResources()
-        self.assertEqual(set(objects), set())
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
-        objects = yield calendar1.listObjectResources()
-        self.assertEqual(set(objects), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        objects = yield shared.listObjectResources()
-        self.assertEqual(set(objects), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        yield  object1.remove()
-        objects = yield calendar1.listObjectResources()
-        self.assertEqual(set(objects), set((&quot;2.ics&quot;,)))
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        objects = yield shared.listObjectResources()
-        self.assertEqual(set(objects), set((&quot;2.ics&quot;,)))
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_synctoken(self):
-        &quot;&quot;&quot;
-        Test that action=synctoken works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        token1_1 = yield calendar1.syncToken()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        token2_1 = yield shared.syncToken()
-        yield self.otherCommit()
-
-        self.assertEqual(token1_1, token2_1)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        token1_2 = yield calendar1.syncToken()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        token2_2 = yield shared.syncToken()
-        yield self.otherCommit()
-
-        self.assertNotEqual(token1_1, token1_2)
-        self.assertEqual(token1_2, token2_2)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        yield  object1.remove()
-        count = yield calendar1.countObjectResources()
-        self.assertEqual(count, 0)
-        yield self.commit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        token1_3 = yield calendar1.syncToken()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        token2_3 = yield shared.syncToken()
-        yield self.otherCommit()
-
-        self.assertNotEqual(token1_1, token1_3)
-        self.assertNotEqual(token1_2, token1_3)
-        self.assertEqual(token1_3, token2_3)
-
-
-    @inlineCallbacks
-    def test_resourcenamessincerevision(self):
-        &quot;&quot;&quot;
-        Test that action=synctoken works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        token1_1 = yield calendar1.syncToken()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        token2_1 = yield shared.syncToken()
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        token1_2 = yield calendar1.syncToken()
-        names1 = yield calendar1.resourceNamesSinceToken(token1_1)
-        self.assertEqual(names1, ([u&quot;1.ics&quot;], [],))
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        token2_2 = yield shared.syncToken()
-        names2 = yield shared.resourceNamesSinceToken(token2_1)
-        self.assertEqual(names2, ([u&quot;1.ics&quot;], [],))
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        yield  object1.remove()
-        count = yield calendar1.countObjectResources()
-        self.assertEqual(count, 0)
-        yield self.commit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        token1_3 = yield calendar1.syncToken()
-        names1 = yield calendar1.resourceNamesSinceToken(token1_2)
-        self.assertEqual(names1, ([], [u&quot;1.ics&quot;],))
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        token2_3 = yield shared.syncToken()
-        names2 = yield shared.resourceNamesSinceToken(token2_2)
-        self.assertEqual(names2, ([], [u&quot;1.ics&quot;],))
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        names1 = yield calendar1.resourceNamesSinceToken(token1_3)
-        self.assertEqual(names1, ([], [],))
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        names2 = yield shared.resourceNamesSinceToken(token2_3)
-        self.assertEqual(names2, ([], [],))
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_resourceuidforname(self):
-        &quot;&quot;&quot;
-        Test that action=resourceuidforname works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        uid = yield calendar1.resourceUIDForName(&quot;1.ics&quot;)
-        self.assertEqual(uid, &quot;uid1&quot;)
-        uid = yield calendar1.resourceUIDForName(&quot;2.ics&quot;)
-        self.assertTrue(uid is None)
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        uid = yield shared.resourceUIDForName(&quot;1.ics&quot;)
-        self.assertEqual(uid, &quot;uid1&quot;)
-        uid = yield shared.resourceUIDForName(&quot;2.ics&quot;)
-        self.assertTrue(uid is None)
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_resourcenameforuid(self):
-        &quot;&quot;&quot;
-        Test that action=resourcenameforuid works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        name = yield calendar1.resourceNameForUID(&quot;uid1&quot;)
-        self.assertEqual(name, &quot;1.ics&quot;)
-        name = yield calendar1.resourceNameForUID(&quot;uid2&quot;)
-        self.assertTrue(name is None)
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        name = yield shared.resourceNameForUID(&quot;uid1&quot;)
-        self.assertEqual(name, &quot;1.ics&quot;)
-        name = yield shared.resourceNameForUID(&quot;uid2&quot;)
-        self.assertTrue(name is None)
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_search(self):
-        &quot;&quot;&quot;
-        Test that action=resourcenameforuid works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        filter = caldavxml.Filter(
-            caldavxml.ComponentFilter(
-                *[caldavxml.ComponentFilter(
-                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
-                )],
-                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
-            )
-        )
-        filter = Filter(filter)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        names = [item[0] for item in (yield calendar1.search(filter))]
-        self.assertEqual(names, [&quot;1.ics&quot;, ])
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        names = [item[0] for item in (yield shared.search(filter))]
-        self.assertEqual(names, [&quot;1.ics&quot;, ])
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_loadallobjects(self):
-        &quot;&quot;&quot;
-        Test that action=loadallobjects works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        resource1 = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        resource_id1 = resource1.id()
-        resource2 = yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
-        resource_id2 = resource2.id()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resources = yield shared.objectResources()
-        byname = dict([(resource.name(), resource) for resource in resources])
-        byuid = dict([(resource.uid(), resource) for resource in resources])
-        self.assertEqual(len(resources), 2)
-        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
-        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid1&quot;, &quot;uid2&quot;,)))
-        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id1, resource_id2,)))
-        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
-        self.assertTrue(resource is byname[&quot;1.ics&quot;])
-        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
-        self.assertTrue(resource is byname[&quot;2.ics&quot;])
-        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
-        self.assertTrue(resource is byuid[&quot;uid1&quot;])
-        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
-        self.assertTrue(resource is byuid[&quot;uid2&quot;])
-        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithID(resource_id1)
-        self.assertTrue(resource is byname[&quot;1.ics&quot;])
-        resource = yield shared.objectResourceWithID(resource_id2)
-        self.assertTrue(resource is byname[&quot;2.ics&quot;])
-        resource = yield shared.objectResourceWithID(0)
-        self.assertTrue(resource is None)
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        yield  object1.remove()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resources = yield shared.objectResources()
-        byname = dict([(resource.name(), resource) for resource in resources])
-        byuid = dict([(resource.uid(), resource) for resource in resources])
-        self.assertEqual(len(resources), 1)
-        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;2.ics&quot;,)))
-        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid2&quot;,)))
-        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id2,)))
-        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
-        self.assertTrue(resource is byname[&quot;2.ics&quot;])
-        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
-        self.assertTrue(resource is byuid[&quot;uid2&quot;])
-        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithID(resource_id1)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithID(resource_id2)
-        self.assertTrue(resource is byname[&quot;2.ics&quot;])
-        resource = yield shared.objectResourceWithID(0)
-        self.assertTrue(resource is None)
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_loadallobjectswithnames(self):
-        &quot;&quot;&quot;
-        Test that action=loadallobjectswithnames works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        resource1 = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        resource_id1 = resource1.id()
-        yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
-        resource3 = yield  calendar1.createCalendarObjectWithName(&quot;3.ics&quot;, Component.fromString(self.caldata3))
-        resource_id3 = resource3.id()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resources = yield shared.objectResources()
-        self.assertEqual(len(resources), 3)
-        yield self.otherCommit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resources = yield shared.objectResourcesWithNames((&quot;1.ics&quot;, &quot;3.ics&quot;,))
-        byname = dict([(resource.name(), resource) for resource in resources])
-        byuid = dict([(resource.uid(), resource) for resource in resources])
-        self.assertEqual(len(resources), 2)
-        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;1.ics&quot;, &quot;3.ics&quot;,)))
-        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid1&quot;, &quot;uid3&quot;,)))
-        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id1, resource_id3,)))
-        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
-        self.assertTrue(resource is byname[&quot;1.ics&quot;])
-        resource = yield shared.objectResourceWithName(&quot;3.ics&quot;)
-        self.assertTrue(resource is byname[&quot;3.ics&quot;])
-        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
-        self.assertTrue(resource is byuid[&quot;uid1&quot;])
-        resource = yield shared.objectResourceWithUID(&quot;uid3&quot;)
-        self.assertTrue(resource is byuid[&quot;uid3&quot;])
-        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithID(resource_id1)
-        self.assertTrue(resource is byname[&quot;1.ics&quot;])
-        resource = yield shared.objectResourceWithID(resource_id3)
-        self.assertTrue(resource is byname[&quot;3.ics&quot;])
-        resource = yield shared.objectResourceWithID(0)
-        self.assertTrue(resource is None)
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        yield  object1.remove()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resources = yield shared.objectResourcesWithNames((&quot;1.ics&quot;, &quot;3.ics&quot;,))
-        byname = dict([(resource.name(), resource) for resource in resources])
-        byuid = dict([(resource.uid(), resource) for resource in resources])
-        self.assertEqual(len(resources), 1)
-        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;3.ics&quot;,)))
-        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid3&quot;,)))
-        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id3,)))
-        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithName(&quot;3.ics&quot;)
-        self.assertTrue(resource is byname[&quot;3.ics&quot;])
-        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithUID(&quot;uid3&quot;)
-        self.assertTrue(resource is byuid[&quot;uid3&quot;])
-        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithID(resource_id1)
-        self.assertTrue(resource is None)
-        resource = yield shared.objectResourceWithID(resource_id3)
-        self.assertTrue(resource is byname[&quot;3.ics&quot;])
-        resource = yield shared.objectResourceWithID(0)
-        self.assertTrue(resource is None)
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_objectwith(self):
-        &quot;&quot;&quot;
-        Test that action=objectwith works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        resource = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        resource_id = resource.id()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
-        self.assertTrue(resource is not None)
-        self.assertEqual(resource.name(), &quot;1.ics&quot;)
-        self.assertEqual(resource.uid(), &quot;uid1&quot;)
-
-        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
-        self.assertTrue(resource is None)
-
-        yield self.otherCommit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
-        self.assertTrue(resource is not None)
-        self.assertEqual(resource.name(), &quot;1.ics&quot;)
-        self.assertEqual(resource.uid(), &quot;uid1&quot;)
-
-        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
-        self.assertTrue(resource is None)
-
-        yield self.otherCommit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resource = yield shared.objectResourceWithID(resource_id)
-        self.assertTrue(resource is not None)
-        self.assertEqual(resource.name(), &quot;1.ics&quot;)
-        self.assertEqual(resource.uid(), &quot;uid1&quot;)
-
-        resource = yield shared.objectResourceWithID(0)
-        self.assertTrue(resource is None)
-
-        yield self.otherCommit()
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        yield  object1.remove()
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
-        self.assertTrue(resource is None)
-        yield self.otherCommit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
-        self.assertTrue(resource is None)
-        yield self.otherCommit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resource = yield shared.objectResourceWithID(resource_id)
-        self.assertTrue(resource is None)
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_create(self):
-        &quot;&quot;&quot;
-        Test that action=create works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resource = yield shared.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        resource_id = resource.id()
-        self.assertTrue(resource is not None)
-        self.assertEqual(resource.name(), &quot;1.ics&quot;)
-        self.assertEqual(resource.uid(), &quot;uid1&quot;)
-        self.assertFalse(resource._componentChanged)
-        yield self.otherCommit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
-        self.assertTrue(resource is not None)
-        self.assertEqual(resource.name(), &quot;1.ics&quot;)
-        self.assertEqual(resource.uid(), &quot;uid1&quot;)
-        self.assertEqual(resource.id(), resource_id)
-        yield self.otherCommit()
-
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        self.assertTrue(object1 is not None)
-        self.assertEqual(object1.name(), &quot;1.ics&quot;)
-        self.assertEqual(object1.uid(), &quot;uid1&quot;)
-        self.assertEqual(object1.id(), resource_id)
-        yield self.commit()
-
-
-    @inlineCallbacks
-    def test_create_exception(self):
-        &quot;&quot;&quot;
-        Test that action=create fails when a duplicate name is used.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        yield self.failUnlessFailure(shared.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1)), ObjectResourceNameAlreadyExistsError)
-        yield self.otherAbort()
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-        yield self.failUnlessFailure(shared.createCalendarObjectWithName(&quot;.2.ics&quot;, Component.fromString(self.caldata2)), ObjectResourceNameNotAllowedError)
-        yield self.otherAbort()
-
-
-    @inlineCallbacks
-    def test_setcomponent(self):
-        &quot;&quot;&quot;
-        Test that action=setcomponent works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
-        ical = yield shared_object.component()
-        self.assertTrue(isinstance(ical, Component))
-        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
-        yield self.otherCommit()
-
-        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
-        changed = yield shared_object.setComponent(Component.fromString(self.caldata1_changed))
-        self.assertFalse(changed)
-        ical = yield shared_object.component()
-        self.assertTrue(isinstance(ical, Component))
-        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
-        yield self.otherCommit()
-
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        ical = yield object1.component()
-        self.assertTrue(isinstance(ical, Component))
-        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
-        yield self.commit()
-
-
-    @inlineCallbacks
-    def test_component(self):
-        &quot;&quot;&quot;
-        Test that action=component works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
-        ical = yield shared_object.component()
-        self.assertTrue(isinstance(ical, Component))
-        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
-        yield self.otherCommit()
-
-
-    @inlineCallbacks
-    def test_remove(self):
-        &quot;&quot;&quot;
-        Test that action=create works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
-        yield shared_object.remove()
-        yield self.otherCommit()
-
-        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
-        self.assertTrue(shared_object is None)
-        yield self.otherCommit()
-
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        self.assertTrue(object1 is None)
-        yield self.commit()
-
-
-    @inlineCallbacks
-    def test_freebusy(self):
-        &quot;&quot;&quot;
-        Test that action=component works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        fbstart = &quot;{now:04d}0102T000000Z&quot;.format(**self.nowYear)
-        fbend = &quot;{now:04d}0103T000000Z&quot;.format(**self.nowYear)
-
-        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
-
-        fbinfo = [[], [], []]
-        matchtotal = yield generateFreeBusyInfo(
-            shared,
-            fbinfo,
-            TimeRange(start=fbstart, end=fbend),
-            0,
-            excludeuid=None,
-            organizer=None,
-            organizerPrincipal=None,
-            same_calendar_user=False,
-            servertoserver=False,
-            event_details=False,
-            logItems=None
-        )
-
-        self.assertEqual(matchtotal, 1)
-        self.assertEqual(fbinfo[0], [Period.parseText(&quot;{now:04d}0102T140000Z/PT1H&quot;.format(**self.nowYear)), ])
-        self.assertEqual(len(fbinfo[1]), 0)
-        self.assertEqual(len(fbinfo[2]), 0)
-        yield self.otherCommit()
-
-
-    def attachmentToString(self, attachment):
-        &quot;&quot;&quot;
-        Convenience to convert an L{IAttachment} to a string.
-
-        @param attachment: an L{IAttachment} provider to convert into a string.
-
-        @return: a L{Deferred} that fires with the contents of the attachment.
-
-        @rtype: L{Deferred} firing C{bytes}
-        &quot;&quot;&quot;
-        capture = CaptureProtocol()
-        attachment.retrieve(capture)
-        return capture.deferred
-
-
-    @inlineCallbacks
-    def test_add_attachment(self):
-        &quot;&quot;&quot;
-        Test that action=add-attachment works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        object1 = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        resourceID = object1.id()
-        yield self.commit()
-
-        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
-        attachment, location = yield shared_object.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some text.&quot;))
-        managedID = attachment.managedID()
-        from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
-        self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
-        self.assertTrue(&quot;user01/attachments/test&quot; in location)
-        yield self.otherCommit()
-
-        cobjs = yield ManagedAttachment.referencesTo(self.transactionUnderTest(), managedID)
-        self.assertEqual(cobjs, set((resourceID,)))
-        attachment = yield ManagedAttachment.load(self.transactionUnderTest(), resourceID, managedID)
-        self.assertEqual(attachment.name(), &quot;test.txt&quot;)
-        data = yield self.attachmentToString(attachment)
-        self.assertEqual(data, &quot;Here is some text.&quot;)
-        yield self.commit()
-
-
-    @inlineCallbacks
-    def test_update_attachment(self):
-        &quot;&quot;&quot;
-        Test that action=update-attachment works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        resourceID = object1.id()
-        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some text.&quot;))
-        managedID = attachment.managedID()
-        yield self.commit()
-
-        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
-        attachment, location = yield shared_object.updateAttachment(managedID, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some more text.&quot;))
-        managedID = attachment.managedID()
-        from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
-        self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
-        self.assertTrue(&quot;user01/attachments/test&quot; in location)
-        yield self.otherCommit()
-
-        cobjs = yield ManagedAttachment.referencesTo(self.transactionUnderTest(), managedID)
-        self.assertEqual(cobjs, set((resourceID,)))
-        attachment = yield ManagedAttachment.load(self.transactionUnderTest(), resourceID, managedID)
-        self.assertEqual(attachment.name(), &quot;test.txt&quot;)
-        data = yield self.attachmentToString(attachment)
-        self.assertEqual(data, &quot;Here is some more text.&quot;)
-        yield self.commit()
-
-
-    @inlineCallbacks
-    def test_remove_attachment(self):
-        &quot;&quot;&quot;
-        Test that action=remove-attachment works.
-        &quot;&quot;&quot;
-
-        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
-
-        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
-        yield self.commit()
-
-        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
-        resourceID = object1.id()
-        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some text.&quot;))
-        managedID = attachment.managedID()
-        yield self.commit()
-
-        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
-        yield shared_object.removeAttachment(None, managedID)
-        yield self.otherCommit()
-
-        cobjs = yield ManagedAttachment.referencesTo(self.transactionUnderTest(), managedID)
-        self.assertEqual(cobjs, set())
-        attachment = yield ManagedAttachment.load(self.transactionUnderTest(), resourceID, managedID)
-        self.assertTrue(attachment is None)
-        yield self.commit()
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtesttest_conduitpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_conduitpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_conduit.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/test_conduit.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,1083 @@
</span><ins>+##
+# Copyright (c) 2005-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from pycalendar.datetime import DateTime
+from pycalendar.period import Period
+
+from twext.python.clsprop import classproperty
+
+import txweb2.dav.test.util
+from txweb2.http_headers import MimeType
+from txweb2.stream import MemoryStream
+
+from twisted.internet.defer import inlineCallbacks, succeed, returnValue
+
+from twistedcaldav import caldavxml
+from twistedcaldav.caldavxml import TimeRange
+from twistedcaldav.ical import Component, normalize_iCalStr
+
+from txdav.caldav.datastore.query.filter import Filter
+from txdav.caldav.datastore.scheduling.freebusy import generateFreeBusyInfo
+from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, Server
+from txdav.caldav.datastore.sql import ManagedAttachment
+from txdav.caldav.datastore.test.common import CaptureProtocol
+from txdav.caldav.datastore.test.util import buildCalendarStore, \
+    TestCalendarStoreDirectoryRecord
+from txdav.common.datastore.podding.conduit import PoddingConduit, \
+    FailedCrossPodRequestError
+from txdav.common.datastore.podding.resource import ConduitResource
+from txdav.common.datastore.podding.test.util import MultiStoreConduitTest, \
+    FakeConduitRequest
+from txdav.common.datastore.sql_tables import _BIND_STATUS_ACCEPTED
+from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
+from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError, \
+    ObjectResourceNameNotAllowedError
+from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
+
+
+class TestConduit (CommonCommonTests, txweb2.dav.test.util.TestCase):
+
+    class FakeConduit(object):
+
+        def recv_fake(self, j):
+            return succeed({
+                &quot;result&quot;: &quot;ok&quot;,
+                &quot;back2u&quot;: j[&quot;echo&quot;],
+                &quot;more&quot;: &quot;bits&quot;,
+            })
+
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(TestConduit, self).setUp()
+        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
+        self.directory = self._sqlCalendarStore.directoryService()
+
+        for ctr in range(1, 100):
+            self.directory.addRecord(TestCalendarStoreDirectoryRecord(
+                &quot;puser{:02d}&quot;.format(ctr),
+                (&quot;puser{:02d}&quot;.format(ctr),),
+                &quot;Puser {:02d}&quot;.format(ctr),
+                frozenset((
+                    &quot;urn:uuid:puser{:02d}&quot;.format(ctr),
+                    &quot;mailto:puser{:02d}@example.com&quot;.format(ctr),
+                )),
+                thisServer=False,
+            ))
+
+        self.site.resource.putChild(&quot;conduit&quot;, ConduitResource(self.site.resource, self.storeUnderTest()))
+
+        self.thisServer = Server(&quot;A&quot;, &quot;http://127.0.0.1&quot;, &quot;A&quot;, True)
+        Servers.addServer(self.thisServer)
+
+        yield self.populate()
+
+
+    def storeUnderTest(self):
+        &quot;&quot;&quot;
+        Return a store for testing.
+        &quot;&quot;&quot;
+        return self._sqlCalendarStore
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+
+    @classproperty(cache=False)
+    def requirements(cls): #@NoSelf
+        return {
+        &quot;user01&quot;: {
+            &quot;calendar_1&quot;: {
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+        &quot;user02&quot;: {
+            &quot;calendar_1&quot;: {
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+        &quot;user03&quot;: {
+            &quot;calendar_1&quot;: {
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+    }
+
+
+    def test_validRequst(self):
+        &quot;&quot;&quot;
+        Cross-pod request fails when there is no shared secret header present.
+        &quot;&quot;&quot;
+
+        conduit = PoddingConduit(self.storeUnderTest())
+        r1, r2 = conduit.validRequst(&quot;user01&quot;, &quot;puser02&quot;)
+        self.assertTrue(r1 is not None)
+        self.assertTrue(r2 is not None)
+
+        self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, &quot;bogus01&quot;, &quot;user02&quot;)
+        self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, &quot;user01&quot;, &quot;bogus02&quot;)
+        self.assertRaises(FailedCrossPodRequestError, conduit.validRequst, &quot;user01&quot;, &quot;user02&quot;)
+
+
+
+class TestConduitToConduit(MultiStoreConduitTest):
+
+    class FakeConduit(PoddingConduit):
+
+        @inlineCallbacks
+        def send_fake(self, txn, ownerUID, shareeUID):
+            _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
+            action = {
+                &quot;action&quot;: &quot;fake&quot;,
+                &quot;echo&quot;: &quot;bravo&quot;
+            }
+
+            result = yield self.sendRequest(txn, sharee, action)
+            returnValue(result)
+
+
+        def recv_fake(self, txn, j):
+            return succeed({
+                &quot;result&quot;: &quot;ok&quot;,
+                &quot;back2u&quot;: j[&quot;echo&quot;],
+                &quot;more&quot;: &quot;bits&quot;,
+            })
+
+
+    def makeConduit(self, store):
+        &quot;&quot;&quot;
+        Use our own variant.
+        &quot;&quot;&quot;
+        conduit = self.FakeConduit(store)
+        conduit.conduitRequestClass = FakeConduitRequest
+        return conduit
+
+
+    @inlineCallbacks
+    def test_fake_action(self):
+        &quot;&quot;&quot;
+        Cross-pod request works when conduit does support the action.
+        &quot;&quot;&quot;
+
+        txn = self.transactionUnderTest()
+        store1 = self.storeUnderTest()
+        response = yield store1.conduit.send_fake(txn, &quot;user01&quot;, &quot;puser01&quot;)
+        self.assertTrue(&quot;result&quot; in response)
+        self.assertEqual(response[&quot;result&quot;], &quot;ok&quot;)
+        self.assertTrue(&quot;back2u&quot; in response)
+        self.assertEqual(response[&quot;back2u&quot;], &quot;bravo&quot;)
+        self.assertTrue(&quot;more&quot; in response)
+        self.assertEqual(response[&quot;more&quot;], &quot;bits&quot;)
+        yield txn.commit()
+
+        store2 = self.otherStoreUnderTest()
+        txn = store2.newTransaction()
+        response = yield store2.conduit.send_fake(txn, &quot;puser01&quot;, &quot;user01&quot;)
+        self.assertTrue(&quot;result&quot; in response)
+        self.assertEqual(response[&quot;result&quot;], &quot;ok&quot;)
+        self.assertTrue(&quot;back2u&quot; in response)
+        self.assertEqual(response[&quot;back2u&quot;], &quot;bravo&quot;)
+        self.assertTrue(&quot;more&quot; in response)
+        self.assertEqual(response[&quot;more&quot;], &quot;bits&quot;)
+        yield txn.commit()
+
+
+
+class TestConduitAPI(MultiStoreConduitTest):
+    &quot;&quot;&quot;
+    Test that the conduit api works.
+    &quot;&quot;&quot;
+
+    nowYear = {&quot;now&quot;: DateTime.getToday().getYear()}
+
+    caldata1 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid1
+DTSTART:{now:04d}0102T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
+
+    caldata1_changed = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid1
+DTSTART:{now:04d}0102T150000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance changed
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
+
+    caldata2 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid2
+DTSTART:{now:04d}0102T160000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
+
+    caldata3 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid3
+DTSTART:{now:04d}0102T160000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:instance
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**nowYear)
+
+    @inlineCallbacks
+    def test_basic_share(self):
+        &quot;&quot;&quot;
+        Test that basic invite/uninvite works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        shared = yield  calendar1.shareeView(&quot;puser01&quot;)
+        self.assertEqual(shared.shareStatus(), _BIND_STATUS_ACCEPTED)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        self.assertTrue(shared is not None)
+        self.assertTrue(shared.external())
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield calendar1.uninviteUserFromShare(&quot;puser01&quot;)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        self.assertTrue(shared is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_countobjects(self):
+        &quot;&quot;&quot;
+        Test that action=countobjects works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        count = yield shared.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        count = yield calendar1.countObjectResources()
+        self.assertEqual(count, 1)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        count = yield shared.countObjectResources()
+        self.assertEqual(count, 1)
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        count = yield calendar1.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        count = yield shared.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_listobjects(self):
+        &quot;&quot;&quot;
+        Test that action=listobjects works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        objects = yield shared.listObjectResources()
+        self.assertEqual(set(objects), set())
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
+        objects = yield calendar1.listObjectResources()
+        self.assertEqual(set(objects), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        objects = yield shared.listObjectResources()
+        self.assertEqual(set(objects), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        objects = yield calendar1.listObjectResources()
+        self.assertEqual(set(objects), set((&quot;2.ics&quot;,)))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        objects = yield shared.listObjectResources()
+        self.assertEqual(set(objects), set((&quot;2.ics&quot;,)))
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_synctoken(self):
+        &quot;&quot;&quot;
+        Test that action=synctoken works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_1 = yield calendar1.syncToken()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_1 = yield shared.syncToken()
+        yield self.otherCommit()
+
+        self.assertEqual(token1_1, token2_1)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_2 = yield calendar1.syncToken()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_2 = yield shared.syncToken()
+        yield self.otherCommit()
+
+        self.assertNotEqual(token1_1, token1_2)
+        self.assertEqual(token1_2, token2_2)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        count = yield calendar1.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_3 = yield calendar1.syncToken()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_3 = yield shared.syncToken()
+        yield self.otherCommit()
+
+        self.assertNotEqual(token1_1, token1_3)
+        self.assertNotEqual(token1_2, token1_3)
+        self.assertEqual(token1_3, token2_3)
+
+
+    @inlineCallbacks
+    def test_resourcenamessincerevision(self):
+        &quot;&quot;&quot;
+        Test that action=synctoken works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_1 = yield calendar1.syncToken()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_1 = yield shared.syncToken()
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_2 = yield calendar1.syncToken()
+        names1 = yield calendar1.resourceNamesSinceToken(token1_1)
+        self.assertEqual(names1, ([u&quot;1.ics&quot;], [], [],))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_2 = yield shared.syncToken()
+        names2 = yield shared.resourceNamesSinceToken(token2_1)
+        self.assertEqual(names2, ([u&quot;1.ics&quot;], [], [],))
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        count = yield calendar1.countObjectResources()
+        self.assertEqual(count, 0)
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        token1_3 = yield calendar1.syncToken()
+        names1 = yield calendar1.resourceNamesSinceToken(token1_2)
+        self.assertEqual(names1, ([], [u&quot;1.ics&quot;], [],))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        token2_3 = yield shared.syncToken()
+        names2 = yield shared.resourceNamesSinceToken(token2_2)
+        self.assertEqual(names2, ([], [u&quot;1.ics&quot;], [],))
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        names1 = yield calendar1.resourceNamesSinceToken(token1_3)
+        self.assertEqual(names1, ([], [], [],))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        names2 = yield shared.resourceNamesSinceToken(token2_3)
+        self.assertEqual(names2, ([], [], [],))
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_resourceuidforname(self):
+        &quot;&quot;&quot;
+        Test that action=resourceuidforname works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        uid = yield calendar1.resourceUIDForName(&quot;1.ics&quot;)
+        self.assertEqual(uid, &quot;uid1&quot;)
+        uid = yield calendar1.resourceUIDForName(&quot;2.ics&quot;)
+        self.assertTrue(uid is None)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        uid = yield shared.resourceUIDForName(&quot;1.ics&quot;)
+        self.assertEqual(uid, &quot;uid1&quot;)
+        uid = yield shared.resourceUIDForName(&quot;2.ics&quot;)
+        self.assertTrue(uid is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_resourcenameforuid(self):
+        &quot;&quot;&quot;
+        Test that action=resourcenameforuid works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        name = yield calendar1.resourceNameForUID(&quot;uid1&quot;)
+        self.assertEqual(name, &quot;1.ics&quot;)
+        name = yield calendar1.resourceNameForUID(&quot;uid2&quot;)
+        self.assertTrue(name is None)
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        name = yield shared.resourceNameForUID(&quot;uid1&quot;)
+        self.assertEqual(name, &quot;1.ics&quot;)
+        name = yield shared.resourceNameForUID(&quot;uid2&quot;)
+        self.assertTrue(name is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_search(self):
+        &quot;&quot;&quot;
+        Test that action=resourcenameforuid works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        filter = caldavxml.Filter(
+            caldavxml.ComponentFilter(
+                *[caldavxml.ComponentFilter(
+                    **{&quot;name&quot;:(&quot;VEVENT&quot;, &quot;VFREEBUSY&quot;, &quot;VAVAILABILITY&quot;)}
+                )],
+                **{&quot;name&quot;: &quot;VCALENDAR&quot;}
+            )
+        )
+        filter = Filter(filter)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        names = [item[0] for item in (yield calendar1.search(filter))]
+        self.assertEqual(names, [&quot;1.ics&quot;, ])
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        names = [item[0] for item in (yield shared.search(filter))]
+        self.assertEqual(names, [&quot;1.ics&quot;, ])
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_loadallobjects(self):
+        &quot;&quot;&quot;
+        Test that action=loadallobjects works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        resource1 = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resource_id1 = resource1.id()
+        resource2 = yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
+        resource_id2 = resource2.id()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResources()
+        byname = dict([(resource.name(), resource) for resource in resources])
+        byuid = dict([(resource.uid(), resource) for resource in resources])
+        self.assertEqual(len(resources), 2)
+        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;1.ics&quot;, &quot;2.ics&quot;,)))
+        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid1&quot;, &quot;uid2&quot;,)))
+        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id1, resource_id2,)))
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is byname[&quot;1.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
+        self.assertTrue(resource is byname[&quot;2.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is byuid[&quot;uid1&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
+        self.assertTrue(resource is byuid[&quot;uid2&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id1)
+        self.assertTrue(resource is byname[&quot;1.ics&quot;])
+        resource = yield shared.objectResourceWithID(resource_id2)
+        self.assertTrue(resource is byname[&quot;2.ics&quot;])
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResources()
+        byname = dict([(resource.name(), resource) for resource in resources])
+        byuid = dict([(resource.uid(), resource) for resource in resources])
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;2.ics&quot;,)))
+        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid2&quot;,)))
+        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id2,)))
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
+        self.assertTrue(resource is byname[&quot;2.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
+        self.assertTrue(resource is byuid[&quot;uid2&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id1)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id2)
+        self.assertTrue(resource is byname[&quot;2.ics&quot;])
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_loadallobjectswithnames(self):
+        &quot;&quot;&quot;
+        Test that action=loadallobjectswithnames works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        resource1 = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resource_id1 = resource1.id()
+        yield  calendar1.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
+        resource3 = yield  calendar1.createCalendarObjectWithName(&quot;3.ics&quot;, Component.fromString(self.caldata3))
+        resource_id3 = resource3.id()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResources()
+        self.assertEqual(len(resources), 3)
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResourcesWithNames((&quot;1.ics&quot;, &quot;3.ics&quot;,))
+        byname = dict([(resource.name(), resource) for resource in resources])
+        byuid = dict([(resource.uid(), resource) for resource in resources])
+        self.assertEqual(len(resources), 2)
+        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;1.ics&quot;, &quot;3.ics&quot;,)))
+        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid1&quot;, &quot;uid3&quot;,)))
+        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id1, resource_id3,)))
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is byname[&quot;1.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;3.ics&quot;)
+        self.assertTrue(resource is byname[&quot;3.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is byuid[&quot;uid1&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid3&quot;)
+        self.assertTrue(resource is byuid[&quot;uid3&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id1)
+        self.assertTrue(resource is byname[&quot;1.ics&quot;])
+        resource = yield shared.objectResourceWithID(resource_id3)
+        self.assertTrue(resource is byname[&quot;3.ics&quot;])
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resources = yield shared.objectResourcesWithNames((&quot;1.ics&quot;, &quot;3.ics&quot;,))
+        byname = dict([(resource.name(), resource) for resource in resources])
+        byuid = dict([(resource.uid(), resource) for resource in resources])
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(set([resource.name() for resource in resources]), set((&quot;3.ics&quot;,)))
+        self.assertEqual(set([resource.uid() for resource in resources]), set((&quot;uid3&quot;,)))
+        self.assertEqual(set([resource.id() for resource in resources]), set((resource_id3,)))
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithName(&quot;3.ics&quot;)
+        self.assertTrue(resource is byname[&quot;3.ics&quot;])
+        resource = yield shared.objectResourceWithName(&quot;Missing.ics&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithUID(&quot;uid3&quot;)
+        self.assertTrue(resource is byuid[&quot;uid3&quot;])
+        resource = yield shared.objectResourceWithUID(&quot;uid-missing&quot;)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id1)
+        self.assertTrue(resource is None)
+        resource = yield shared.objectResourceWithID(resource_id3)
+        self.assertTrue(resource is byname[&quot;3.ics&quot;])
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_objectwith(self):
+        &quot;&quot;&quot;
+        Test that action=objectwith works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        resource = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resource_id = resource.id()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+
+        resource = yield shared.objectResourceWithName(&quot;2.ics&quot;)
+        self.assertTrue(resource is None)
+
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+
+        resource = yield shared.objectResourceWithUID(&quot;uid2&quot;)
+        self.assertTrue(resource is None)
+
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithID(resource_id)
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+
+        resource = yield shared.objectResourceWithID(0)
+        self.assertTrue(resource is None)
+
+        yield self.otherCommit()
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield  object1.remove()
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithName(&quot;1.ics&quot;)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithID(resource_id)
+        self.assertTrue(resource is None)
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_create(self):
+        &quot;&quot;&quot;
+        Test that action=create works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resource_id = resource.id()
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+        self.assertFalse(resource._componentChanged)
+        yield self.otherCommit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        resource = yield shared.objectResourceWithUID(&quot;uid1&quot;)
+        self.assertTrue(resource is not None)
+        self.assertEqual(resource.name(), &quot;1.ics&quot;)
+        self.assertEqual(resource.uid(), &quot;uid1&quot;)
+        self.assertEqual(resource.id(), resource_id)
+        yield self.otherCommit()
+
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        self.assertTrue(object1 is not None)
+        self.assertEqual(object1.name(), &quot;1.ics&quot;)
+        self.assertEqual(object1.uid(), &quot;uid1&quot;)
+        self.assertEqual(object1.id(), resource_id)
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_create_exception(self):
+        &quot;&quot;&quot;
+        Test that action=create fails when a duplicate name is used.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        yield self.failUnlessFailure(shared.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1)), ObjectResourceNameAlreadyExistsError)
+        yield self.otherAbort()
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+        yield self.failUnlessFailure(shared.createCalendarObjectWithName(&quot;.2.ics&quot;, Component.fromString(self.caldata2)), ObjectResourceNameNotAllowedError)
+        yield self.otherAbort()
+
+
+    @inlineCallbacks
+    def test_setcomponent(self):
+        &quot;&quot;&quot;
+        Test that action=setcomponent works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        ical = yield shared_object.component()
+        self.assertTrue(isinstance(ical, Component))
+        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
+        yield self.otherCommit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        changed = yield shared_object.setComponent(Component.fromString(self.caldata1_changed))
+        self.assertFalse(changed)
+        ical = yield shared_object.component()
+        self.assertTrue(isinstance(ical, Component))
+        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
+        yield self.otherCommit()
+
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        ical = yield object1.component()
+        self.assertTrue(isinstance(ical, Component))
+        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1_changed))
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_component(self):
+        &quot;&quot;&quot;
+        Test that action=component works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        ical = yield shared_object.component()
+        self.assertTrue(isinstance(ical, Component))
+        self.assertEqual(normalize_iCalStr(str(ical)), normalize_iCalStr(self.caldata1))
+        yield self.otherCommit()
+
+
+    @inlineCallbacks
+    def test_remove(self):
+        &quot;&quot;&quot;
+        Test that action=create works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        yield shared_object.remove()
+        yield self.otherCommit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        self.assertTrue(shared_object is None)
+        yield self.otherCommit()
+
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        self.assertTrue(object1 is None)
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_freebusy(self):
+        &quot;&quot;&quot;
+        Test that action=component works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        fbstart = &quot;{now:04d}0102T000000Z&quot;.format(**self.nowYear)
+        fbend = &quot;{now:04d}0103T000000Z&quot;.format(**self.nowYear)
+
+        shared = yield self.calendarUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, name=&quot;shared-calendar&quot;)
+
+        fbinfo = [[], [], []]
+        matchtotal = yield generateFreeBusyInfo(
+            shared,
+            fbinfo,
+            TimeRange(start=fbstart, end=fbend),
+            0,
+            excludeuid=None,
+            organizer=None,
+            organizerPrincipal=None,
+            same_calendar_user=False,
+            servertoserver=False,
+            event_details=False,
+            logItems=None
+        )
+
+        self.assertEqual(matchtotal, 1)
+        self.assertEqual(fbinfo[0], [Period.parseText(&quot;{now:04d}0102T140000Z/PT1H&quot;.format(**self.nowYear)), ])
+        self.assertEqual(len(fbinfo[1]), 0)
+        self.assertEqual(len(fbinfo[2]), 0)
+        yield self.otherCommit()
+
+
+    def attachmentToString(self, attachment):
+        &quot;&quot;&quot;
+        Convenience to convert an L{IAttachment} to a string.
+
+        @param attachment: an L{IAttachment} provider to convert into a string.
+
+        @return: a L{Deferred} that fires with the contents of the attachment.
+
+        @rtype: L{Deferred} firing C{bytes}
+        &quot;&quot;&quot;
+        capture = CaptureProtocol()
+        attachment.retrieve(capture)
+        return capture.deferred
+
+
+    @inlineCallbacks
+    def test_add_attachment(self):
+        &quot;&quot;&quot;
+        Test that action=add-attachment works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        object1 = yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        resourceID = object1.id()
+        yield self.commit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        attachment, location = yield shared_object.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some text.&quot;))
+        managedID = attachment.managedID()
+        from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
+        self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
+        self.assertTrue(&quot;user01/attachments/test&quot; in location)
+        yield self.otherCommit()
+
+        cobjs = yield ManagedAttachment.referencesTo(self.transactionUnderTest(), managedID)
+        self.assertEqual(cobjs, set((resourceID,)))
+        attachment = yield ManagedAttachment.load(self.transactionUnderTest(), resourceID, managedID)
+        self.assertEqual(attachment.name(), &quot;test.txt&quot;)
+        data = yield self.attachmentToString(attachment)
+        self.assertEqual(data, &quot;Here is some text.&quot;)
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_update_attachment(self):
+        &quot;&quot;&quot;
+        Test that action=update-attachment works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        resourceID = object1.id()
+        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some text.&quot;))
+        managedID = attachment.managedID()
+        yield self.commit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        attachment, location = yield shared_object.updateAttachment(managedID, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some more text.&quot;))
+        managedID = attachment.managedID()
+        from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
+        self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
+        self.assertTrue(&quot;user01/attachments/test&quot; in location)
+        yield self.otherCommit()
+
+        cobjs = yield ManagedAttachment.referencesTo(self.transactionUnderTest(), managedID)
+        self.assertEqual(cobjs, set((resourceID,)))
+        attachment = yield ManagedAttachment.load(self.transactionUnderTest(), resourceID, managedID)
+        self.assertEqual(attachment.name(), &quot;test.txt&quot;)
+        data = yield self.attachmentToString(attachment)
+        self.assertEqual(data, &quot;Here is some more text.&quot;)
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_remove_attachment(self):
+        &quot;&quot;&quot;
+        Test that action=remove-attachment works.
+        &quot;&quot;&quot;
+
+        yield self.createShare(&quot;user01&quot;, &quot;puser01&quot;)
+
+        calendar1 = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield  calendar1.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
+        yield self.commit()
+
+        object1 = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        resourceID = object1.id()
+        attachment, _ignore_location = yield object1.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some text.&quot;))
+        managedID = attachment.managedID()
+        yield self.commit()
+
+        shared_object = yield self.calendarObjectUnderTest(txn=self.newOtherTransaction(), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
+        yield shared_object.removeAttachment(None, managedID)
+        yield self.otherCommit()
+
+        cobjs = yield ManagedAttachment.referencesTo(self.transactionUnderTest(), managedID)
+        self.assertEqual(cobjs, set())
+        attachment = yield ManagedAttachment.load(self.transactionUnderTest(), resourceID, managedID)
+        self.assertTrue(attachment is None)
+        yield self.commit()
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtesttest_external_homepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/test/test_external_home.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/test_external_home.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,99 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet.defer import inlineCallbacks
-
-from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, \
-    Server
-from txdav.caldav.datastore.test.util import buildCalendarStore, \
-    TestCalendarStoreDirectoryRecord
-from txdav.common.datastore.podding.resource import ConduitResource
-from txdav.common.datastore.sql_tables import _HOME_STATUS_NORMAL, \
-    _HOME_STATUS_EXTERNAL
-from txdav.common.datastore.test.util import CommonCommonTests
-from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
-
-import twext.web2.dav.test.util
-
-
-class ExternalHome(CommonCommonTests, twext.web2.dav.test.util.TestCase):
-
-    @inlineCallbacks
-    def setUp(self):
-        yield super(ExternalHome, self).setUp()
-        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
-        self.directory = self._sqlCalendarStore.directoryService()
-
-        for ctr in range(1, 100):
-            self.directory.addRecord(TestCalendarStoreDirectoryRecord(
-                &quot;puser{:02d}&quot;.format(ctr),
-                (&quot;puser{:02d}&quot;.format(ctr),),
-                &quot;Puser {:02d}&quot;.format(ctr),
-                frozenset((
-                    &quot;urn:uuid:puser{:02d}&quot;.format(ctr),
-                    &quot;mailto:puser{:02d}@example.com&quot;.format(ctr),
-                )),
-                thisServer=False,
-            ))
-
-        self.site.resource.putChild(&quot;conduit&quot;, ConduitResource(self.site.resource, self.storeUnderTest()))
-
-        self.thisServer = Server(&quot;A&quot;, &quot;http://127.0.0.1&quot;, &quot;A&quot;, True)
-        Servers.addServer(self.thisServer)
-
-
-    def storeUnderTest(self):
-        &quot;&quot;&quot;
-        Return a store for testing.
-        &quot;&quot;&quot;
-        return self._sqlCalendarStore
-
-
-    @inlineCallbacks
-    def test_validNormalHome(self):
-        &quot;&quot;&quot;
-        Locally hosted homes are valid.
-        &quot;&quot;&quot;
-
-        for i in range(1, 100):
-            home = yield self.transactionUnderTest().calendarHomeWithUID(&quot;user{:02d}&quot;.format(i), create=True)
-            self.assertTrue(home is not None)
-            self.assertEqual(home._status, _HOME_STATUS_NORMAL)
-            calendar = yield home.childWithName(&quot;calendar&quot;)
-            self.assertTrue(calendar is not None)
-
-
-    @inlineCallbacks
-    def test_validExternalHome(self):
-        &quot;&quot;&quot;
-        Externally hosted homes are valid.
-        &quot;&quot;&quot;
-
-        for i in range(1, 100):
-            home = yield self.transactionUnderTest().calendarHomeWithUID(&quot;puser{:02d}&quot;.format(i), create=True)
-            self.assertTrue(home is not None)
-            self.assertEqual(home._status, _HOME_STATUS_EXTERNAL)
-            calendar = yield home.childWithName(&quot;calendar&quot;)
-            self.assertTrue(calendar is None)
-
-
-    @inlineCallbacks
-    def test_invalidHome(self):
-        &quot;&quot;&quot;
-        Homes are invalid.
-        &quot;&quot;&quot;
-
-        yield self.assertFailure(self.transactionUnderTest().calendarHomeWithUID(&quot;buser01&quot;, create=True), DirectoryRecordNotFoundError)
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtesttest_external_homepyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_external_homepy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/test/test_external_home.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_external_home.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/test/test_external_home.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/test_external_home.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,99 @@
</span><ins>+##
+# Copyright (c) 2005-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks
+
+from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, \
+    Server
+from txdav.caldav.datastore.test.util import buildCalendarStore, \
+    TestCalendarStoreDirectoryRecord
+from txdav.common.datastore.podding.resource import ConduitResource
+from txdav.common.datastore.sql_tables import _HOME_STATUS_NORMAL, \
+    _HOME_STATUS_EXTERNAL
+from txdav.common.datastore.test.util import CommonCommonTests
+from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
+
+import txweb2.dav.test.util
+
+
+class ExternalHome(CommonCommonTests, txweb2.dav.test.util.TestCase):
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(ExternalHome, self).setUp()
+        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
+        self.directory = self._sqlCalendarStore.directoryService()
+
+        for ctr in range(1, 100):
+            self.directory.addRecord(TestCalendarStoreDirectoryRecord(
+                &quot;puser{:02d}&quot;.format(ctr),
+                (&quot;puser{:02d}&quot;.format(ctr),),
+                &quot;Puser {:02d}&quot;.format(ctr),
+                frozenset((
+                    &quot;urn:uuid:puser{:02d}&quot;.format(ctr),
+                    &quot;mailto:puser{:02d}@example.com&quot;.format(ctr),
+                )),
+                thisServer=False,
+            ))
+
+        self.site.resource.putChild(&quot;conduit&quot;, ConduitResource(self.site.resource, self.storeUnderTest()))
+
+        self.thisServer = Server(&quot;A&quot;, &quot;http://127.0.0.1&quot;, &quot;A&quot;, True)
+        Servers.addServer(self.thisServer)
+
+
+    def storeUnderTest(self):
+        &quot;&quot;&quot;
+        Return a store for testing.
+        &quot;&quot;&quot;
+        return self._sqlCalendarStore
+
+
+    @inlineCallbacks
+    def test_validNormalHome(self):
+        &quot;&quot;&quot;
+        Locally hosted homes are valid.
+        &quot;&quot;&quot;
+
+        for i in range(1, 100):
+            home = yield self.transactionUnderTest().calendarHomeWithUID(&quot;user{:02d}&quot;.format(i), create=True)
+            self.assertTrue(home is not None)
+            self.assertEqual(home._status, _HOME_STATUS_NORMAL)
+            calendar = yield home.childWithName(&quot;calendar&quot;)
+            self.assertTrue(calendar is not None)
+
+
+    @inlineCallbacks
+    def test_validExternalHome(self):
+        &quot;&quot;&quot;
+        Externally hosted homes are valid.
+        &quot;&quot;&quot;
+
+        for i in range(1, 100):
+            home = yield self.transactionUnderTest().calendarHomeWithUID(&quot;puser{:02d}&quot;.format(i), create=True)
+            self.assertTrue(home is not None)
+            self.assertEqual(home._status, _HOME_STATUS_EXTERNAL)
+            calendar = yield home.childWithName(&quot;calendar&quot;)
+            self.assertTrue(calendar is None)
+
+
+    @inlineCallbacks
+    def test_invalidHome(self):
+        &quot;&quot;&quot;
+        Homes are invalid.
+        &quot;&quot;&quot;
+
+        yield self.assertFailure(self.transactionUnderTest().calendarHomeWithUID(&quot;buser01&quot;, create=True), DirectoryRecordNotFoundError)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtesttest_resourcepy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/test/test_resource.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/test_resource.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,275 +0,0 @@
</span><del>-##
-# Copyright (c) 2005-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twext.python.clsprop import classproperty
-from twext.web2 import http_headers, responsecode
-import twext.web2.dav.test.util
-from twext.web2.dav.util import allDataFromStream
-from twext.web2.test.test_server import SimpleRequest
-from twisted.internet.defer import inlineCallbacks, succeed
-from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, Server
-from txdav.caldav.datastore.test.util import buildCalendarStore
-from txdav.common.datastore.podding.resource import ConduitResource
-from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
-import json
-from txdav.common.datastore.podding.conduit import PoddingConduit
-
-class ConduitPOST (CommonCommonTests, twext.web2.dav.test.util.TestCase):
-
-    class FakeConduit(PoddingConduit):
-
-        def recv_fake(self, txn, j):
-            return succeed({
-                &quot;result&quot;: &quot;ok&quot;,
-                &quot;back2u&quot;: j[&quot;echo&quot;],
-                &quot;more&quot;: &quot;bits&quot;,
-            })
-
-
-    @inlineCallbacks
-    def setUp(self):
-        yield super(ConduitPOST, self).setUp()
-        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
-        self.directory = self._sqlCalendarStore.directoryService()
-
-        self.site.resource.putChild(&quot;conduit&quot;, ConduitResource(self.site.resource, self.storeUnderTest()))
-
-        self.thisServer = Server(&quot;A&quot;, &quot;http://127.0.0.1&quot;, &quot;A&quot;, True)
-        Servers.addServer(self.thisServer)
-
-        yield self.populate()
-
-
-    def storeUnderTest(self):
-        &quot;&quot;&quot;
-        Return a store for testing.
-        &quot;&quot;&quot;
-        return self._sqlCalendarStore
-
-
-    @inlineCallbacks
-    def populate(self):
-        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
-        self.notifierFactory.reset()
-
-
-    @classproperty(cache=False)
-    def requirements(cls): #@NoSelf
-        return {
-        &quot;user01&quot;: {
-            &quot;calendar_1&quot;: {
-            },
-            &quot;inbox&quot;: {
-            },
-        },
-        &quot;user02&quot;: {
-            &quot;calendar_1&quot;: {
-            },
-            &quot;inbox&quot;: {
-            },
-        },
-        &quot;user03&quot;: {
-            &quot;calendar_1&quot;: {
-            },
-            &quot;inbox&quot;: {
-            },
-        },
-    }
-
-
-    @inlineCallbacks
-    def test_receive_no_secret(self):
-        &quot;&quot;&quot;
-        Cross-pod request fails when there is no shared secret header present.
-        &quot;&quot;&quot;
-
-        request = SimpleRequest(
-            self.site,
-            &quot;POST&quot;,
-            &quot;/conduit&quot;,
-            headers=http_headers.Headers(rawHeaders={
-                &quot;Content-Type&quot;: (&quot;text/plain&quot;,)
-            }),
-            content=&quot;&quot;&quot;Hello, World!
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        )
-
-        response = (yield self.send(request))
-        self.assertEqual(response.code, responsecode.FORBIDDEN)
-
-
-    @inlineCallbacks
-    def test_receive_wrong_mime(self):
-        &quot;&quot;&quot;
-        Cross-pod request fails when Content-Type header is wrong.
-        &quot;&quot;&quot;
-
-        request = SimpleRequest(
-            self.site,
-            &quot;POST&quot;,
-            &quot;/conduit&quot;,
-            headers=http_headers.Headers(rawHeaders={
-                &quot;Content-Type&quot;: (&quot;text/plain&quot;,),
-                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
-            }),
-            content=&quot;&quot;&quot;Hello, World!
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        )
-
-        response = (yield self.send(request))
-        self.assertEqual(response.code, responsecode.BAD_REQUEST)
-
-
-    @inlineCallbacks
-    def test_receive_invalid_json(self):
-        &quot;&quot;&quot;
-        Cross-pod request fails when request data is not JSON.
-        &quot;&quot;&quot;
-
-        request = SimpleRequest(
-            self.site,
-            &quot;POST&quot;,
-            &quot;/conduit&quot;,
-            headers=http_headers.Headers(rawHeaders={
-                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
-                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
-            }),
-            content=&quot;&quot;&quot;Hello, World!
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        )
-
-        response = (yield self.send(request))
-        self.assertEqual(response.code, responsecode.BAD_REQUEST)
-
-
-    @inlineCallbacks
-    def test_receive_bad_json(self):
-        &quot;&quot;&quot;
-        Cross-pod request fails when JSON data does not have an &quot;action&quot;.
-        &quot;&quot;&quot;
-
-        request = SimpleRequest(
-            self.site,
-            &quot;POST&quot;,
-            &quot;/conduit&quot;,
-            headers=http_headers.Headers(rawHeaders={
-                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
-                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
-            }),
-            content=&quot;&quot;&quot;
-{
-    &quot;foo&quot;:&quot;bar&quot;
-}
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        )
-
-        response = (yield self.send(request))
-        self.assertEqual(response.code, responsecode.BAD_REQUEST)
-
-
-    @inlineCallbacks
-    def test_receive_ping(self):
-        &quot;&quot;&quot;
-        Cross-pod request works with the &quot;ping&quot; action.
-        &quot;&quot;&quot;
-
-        request = SimpleRequest(
-            self.site,
-            &quot;POST&quot;,
-            &quot;/conduit&quot;,
-            headers=http_headers.Headers(rawHeaders={
-                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
-                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
-            }),
-            content=&quot;&quot;&quot;
-{
-    &quot;action&quot;:&quot;ping&quot;
-}
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        )
-
-        response = (yield self.send(request))
-        self.assertEqual(response.code, responsecode.OK)
-        data = (yield allDataFromStream(response.stream))
-        j = json.loads(data)
-        self.assertTrue(&quot;result&quot; in j)
-        self.assertEqual(j[&quot;result&quot;], &quot;ok&quot;)
-
-
-    @inlineCallbacks
-    def test_receive_fake_conduit_no_action(self):
-        &quot;&quot;&quot;
-        Cross-pod request fails when conduit does not support the action.
-        &quot;&quot;&quot;
-
-        store = self.storeUnderTest()
-        self.patch(store, &quot;conduit&quot;, self.FakeConduit(store))
-
-        request = SimpleRequest(
-            self.site,
-            &quot;POST&quot;,
-            &quot;/conduit&quot;,
-            headers=http_headers.Headers(rawHeaders={
-                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
-                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
-            }),
-            content=&quot;&quot;&quot;
-{
-    &quot;action&quot;:&quot;bogus&quot;,
-    &quot;echo&quot;:&quot;bravo&quot;
-}
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        )
-
-        response = (yield self.send(request))
-        self.assertEqual(response.code, responsecode.BAD_REQUEST)
-
-
-    @inlineCallbacks
-    def test_receive_fake_conduit(self):
-        &quot;&quot;&quot;
-        Cross-pod request works when conduit does support the action.
-        &quot;&quot;&quot;
-
-        store = self.storeUnderTest()
-        self.patch(store, &quot;conduit&quot;, self.FakeConduit(store))
-
-        request = SimpleRequest(
-            self.site,
-            &quot;POST&quot;,
-            &quot;/conduit&quot;,
-            headers=http_headers.Headers(rawHeaders={
-                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
-                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
-            }),
-            content=&quot;&quot;&quot;
-{
-    &quot;action&quot;:&quot;fake&quot;,
-    &quot;echo&quot;:&quot;bravo&quot;
-}
-&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
-        )
-
-        response = (yield self.send(request))
-        self.assertEqual(response.code, responsecode.OK)
-        data = (yield allDataFromStream(response.stream))
-        j = json.loads(data)
-        self.assertTrue(&quot;result&quot; in j)
-        self.assertEqual(j[&quot;result&quot;], &quot;ok&quot;)
-        self.assertTrue(&quot;back2u&quot; in j)
-        self.assertEqual(j[&quot;back2u&quot;], &quot;bravo&quot;)
-        self.assertTrue(&quot;more&quot; in j)
-        self.assertEqual(j[&quot;more&quot;], &quot;bits&quot;)
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtesttest_resourcepyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtesttest_resourcepy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/test/test_resource.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/test_resource.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/test/test_resource.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/test_resource.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,278 @@
</span><ins>+##
+# Copyright (c) 2005-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.python.clsprop import classproperty
+
+import txweb2.dav.test.util
+from txweb2 import http_headers, responsecode
+from txweb2.dav.util import allDataFromStream
+from txweb2.test.test_server import SimpleRequest
+
+from twisted.internet.defer import inlineCallbacks, succeed
+
+from txdav.caldav.datastore.scheduling.ischedule.localservers import Servers, Server
+from txdav.caldav.datastore.test.util import buildCalendarStore
+from txdav.common.datastore.podding.resource import ConduitResource
+from txdav.common.datastore.test.util import populateCalendarsFrom, CommonCommonTests
+import json
+from txdav.common.datastore.podding.conduit import PoddingConduit
+
+class ConduitPOST (CommonCommonTests, txweb2.dav.test.util.TestCase):
+
+    class FakeConduit(PoddingConduit):
+
+        def recv_fake(self, txn, j):
+            return succeed({
+                &quot;result&quot;: &quot;ok&quot;,
+                &quot;back2u&quot;: j[&quot;echo&quot;],
+                &quot;more&quot;: &quot;bits&quot;,
+            })
+
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(ConduitPOST, self).setUp()
+        self._sqlCalendarStore = yield buildCalendarStore(self, self.notifierFactory)
+        self.directory = self._sqlCalendarStore.directoryService()
+
+        self.site.resource.putChild(&quot;conduit&quot;, ConduitResource(self.site.resource, self.storeUnderTest()))
+
+        self.thisServer = Server(&quot;A&quot;, &quot;http://127.0.0.1&quot;, &quot;A&quot;, True)
+        Servers.addServer(self.thisServer)
+
+        yield self.populate()
+
+
+    def storeUnderTest(self):
+        &quot;&quot;&quot;
+        Return a store for testing.
+        &quot;&quot;&quot;
+        return self._sqlCalendarStore
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+
+    @classproperty(cache=False)
+    def requirements(cls): #@NoSelf
+        return {
+        &quot;user01&quot;: {
+            &quot;calendar_1&quot;: {
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+        &quot;user02&quot;: {
+            &quot;calendar_1&quot;: {
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+        &quot;user03&quot;: {
+            &quot;calendar_1&quot;: {
+            },
+            &quot;inbox&quot;: {
+            },
+        },
+    }
+
+
+    @inlineCallbacks
+    def test_receive_no_secret(self):
+        &quot;&quot;&quot;
+        Cross-pod request fails when there is no shared secret header present.
+        &quot;&quot;&quot;
+
+        request = SimpleRequest(
+            self.site,
+            &quot;POST&quot;,
+            &quot;/conduit&quot;,
+            headers=http_headers.Headers(rawHeaders={
+                &quot;Content-Type&quot;: (&quot;text/plain&quot;,)
+            }),
+            content=&quot;&quot;&quot;Hello, World!
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        )
+
+        response = (yield self.send(request))
+        self.assertEqual(response.code, responsecode.FORBIDDEN)
+
+
+    @inlineCallbacks
+    def test_receive_wrong_mime(self):
+        &quot;&quot;&quot;
+        Cross-pod request fails when Content-Type header is wrong.
+        &quot;&quot;&quot;
+
+        request = SimpleRequest(
+            self.site,
+            &quot;POST&quot;,
+            &quot;/conduit&quot;,
+            headers=http_headers.Headers(rawHeaders={
+                &quot;Content-Type&quot;: (&quot;text/plain&quot;,),
+                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
+            }),
+            content=&quot;&quot;&quot;Hello, World!
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        )
+
+        response = (yield self.send(request))
+        self.assertEqual(response.code, responsecode.BAD_REQUEST)
+
+
+    @inlineCallbacks
+    def test_receive_invalid_json(self):
+        &quot;&quot;&quot;
+        Cross-pod request fails when request data is not JSON.
+        &quot;&quot;&quot;
+
+        request = SimpleRequest(
+            self.site,
+            &quot;POST&quot;,
+            &quot;/conduit&quot;,
+            headers=http_headers.Headers(rawHeaders={
+                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
+                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
+            }),
+            content=&quot;&quot;&quot;Hello, World!
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        )
+
+        response = (yield self.send(request))
+        self.assertEqual(response.code, responsecode.BAD_REQUEST)
+
+
+    @inlineCallbacks
+    def test_receive_bad_json(self):
+        &quot;&quot;&quot;
+        Cross-pod request fails when JSON data does not have an &quot;action&quot;.
+        &quot;&quot;&quot;
+
+        request = SimpleRequest(
+            self.site,
+            &quot;POST&quot;,
+            &quot;/conduit&quot;,
+            headers=http_headers.Headers(rawHeaders={
+                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
+                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
+            }),
+            content=&quot;&quot;&quot;
+{
+    &quot;foo&quot;:&quot;bar&quot;
+}
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        )
+
+        response = (yield self.send(request))
+        self.assertEqual(response.code, responsecode.BAD_REQUEST)
+
+
+    @inlineCallbacks
+    def test_receive_ping(self):
+        &quot;&quot;&quot;
+        Cross-pod request works with the &quot;ping&quot; action.
+        &quot;&quot;&quot;
+
+        request = SimpleRequest(
+            self.site,
+            &quot;POST&quot;,
+            &quot;/conduit&quot;,
+            headers=http_headers.Headers(rawHeaders={
+                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
+                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
+            }),
+            content=&quot;&quot;&quot;
+{
+    &quot;action&quot;:&quot;ping&quot;
+}
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        )
+
+        response = (yield self.send(request))
+        self.assertEqual(response.code, responsecode.OK)
+        data = (yield allDataFromStream(response.stream))
+        j = json.loads(data)
+        self.assertTrue(&quot;result&quot; in j)
+        self.assertEqual(j[&quot;result&quot;], &quot;ok&quot;)
+
+
+    @inlineCallbacks
+    def test_receive_fake_conduit_no_action(self):
+        &quot;&quot;&quot;
+        Cross-pod request fails when conduit does not support the action.
+        &quot;&quot;&quot;
+
+        store = self.storeUnderTest()
+        self.patch(store, &quot;conduit&quot;, self.FakeConduit(store))
+
+        request = SimpleRequest(
+            self.site,
+            &quot;POST&quot;,
+            &quot;/conduit&quot;,
+            headers=http_headers.Headers(rawHeaders={
+                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
+                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
+            }),
+            content=&quot;&quot;&quot;
+{
+    &quot;action&quot;:&quot;bogus&quot;,
+    &quot;echo&quot;:&quot;bravo&quot;
+}
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        )
+
+        response = (yield self.send(request))
+        self.assertEqual(response.code, responsecode.BAD_REQUEST)
+
+
+    @inlineCallbacks
+    def test_receive_fake_conduit(self):
+        &quot;&quot;&quot;
+        Cross-pod request works when conduit does support the action.
+        &quot;&quot;&quot;
+
+        store = self.storeUnderTest()
+        self.patch(store, &quot;conduit&quot;, self.FakeConduit(store))
+
+        request = SimpleRequest(
+            self.site,
+            &quot;POST&quot;,
+            &quot;/conduit&quot;,
+            headers=http_headers.Headers(rawHeaders={
+                &quot;Content-Type&quot;: (&quot;application/json&quot;,),
+                self.thisServer.secretHeader()[0]: self.thisServer.secretHeader()[1],
+            }),
+            content=&quot;&quot;&quot;
+{
+    &quot;action&quot;:&quot;fake&quot;,
+    &quot;echo&quot;:&quot;bravo&quot;
+}
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+        )
+
+        response = (yield self.send(request))
+        self.assertEqual(response.code, responsecode.OK)
+        data = (yield allDataFromStream(response.stream))
+        j = json.loads(data)
+        self.assertTrue(&quot;result&quot; in j)
+        self.assertEqual(j[&quot;result&quot;], &quot;ok&quot;)
+        self.assertTrue(&quot;back2u&quot; in j)
+        self.assertEqual(j[&quot;back2u&quot;], &quot;bravo&quot;)
+        self.assertTrue(&quot;more&quot; in j)
+        self.assertEqual(j[&quot;more&quot;], &quot;bits&quot;)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtestutilpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/podding/test/util.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/util.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,237 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twisted.internet.defer import inlineCallbacks, returnValue
-
-from txdav.caldav.datastore.scheduling.ischedule.localservers import Server, \
-    Servers
-from txdav.caldav.datastore.test.util import \
-    TestCalendarStoreDirectoryRecord, TestCalendarStoreDirectoryService
-from txdav.common.datastore.podding.conduit import PoddingConduit
-from txdav.common.datastore.test.util import CommonCommonTests, SQLStoreBuilder,\
-    theStoreBuilder
-
-import twext.web2.dav.test.util
-from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
-from twext.enterprise.ienterprise import AlreadyFinishedError
-import json
-
-class FakeConduitRequest(object):
-    &quot;&quot;&quot;
-    A conduit request that sends messages internally rather than using HTTP
-    &quot;&quot;&quot;
-
-    storeMap = {}
-
-    @classmethod
-    def addServerStore(cls, server, store):
-        &quot;&quot;&quot;
-        Add a store mapped to a server. These mappings are used to &quot;deliver&quot; conduit
-        requests to the appropriate store.
-
-        @param uri: the server
-        @type uri: L{Server}
-        @param store: the store
-        @type store: L{ICommonDataStore}
-        &quot;&quot;&quot;
-
-        cls.storeMap[server.details()] = store
-
-
-    def __init__(self, server, data, stream=None, stream_type=None):
-
-        self.server = server
-        self.data = json.dumps(data)
-        self.stream = stream
-        self.streamType = stream_type
-
-
-    @inlineCallbacks
-    def doRequest(self, txn):
-
-        # Generate an HTTP client request
-        try:
-            response = (yield self._processRequest())
-            response = json.loads(response)
-        except Exception as e:
-            raise ValueError(&quot;Failed cross-pod request: {}&quot;.format(e))
-
-        returnValue(response)
-
-
-    @inlineCallbacks
-    def _processRequest(self):
-        &quot;&quot;&quot;
-        Process the request by sending it to the relevant server.
-
-        @return: the HTTP response.
-        @rtype: L{Response}
-        &quot;&quot;&quot;
-
-        store = self.storeMap[self.server.details()]
-        j = json.loads(self.data)
-        if self.stream is not None:
-            j[&quot;stream&quot;] = self.stream
-            j[&quot;streamType&quot;] = self.streamType
-        result = yield store.conduit.processRequest(j)
-        result = json.dumps(result)
-        returnValue(result)
-
-
-
-class MultiStoreConduitTest(CommonCommonTests, twext.web2.dav.test.util.TestCase):
-
-    theStoreBuilder2 = SQLStoreBuilder(secondary=True)
-    otherTransaction = None
-
-    @inlineCallbacks
-    def setUp(self):
-        yield super(MultiStoreConduitTest, self).setUp()
-
-        server1 = Server(&quot;A&quot;, &quot;http://127.0.0.1:8008&quot;, &quot;A&quot;, True)
-        Servers.addServer(server1)
-
-        server2 = Server(&quot;B&quot;, &quot;http://127.0.0.1:8108&quot;, &quot;B&quot;, False)
-        Servers.addServer(server2)
-
-        self._sqlCalendarStore1 = yield self.makeStore(theStoreBuilder, True, server1, server2)
-        self._sqlCalendarStore2 = yield self.makeStore(self.theStoreBuilder2, False, server1, server2)
-
-        FakeConduitRequest.addServerStore(server1, self._sqlCalendarStore1)
-        FakeConduitRequest.addServerStore(server2, self._sqlCalendarStore2)
-
-
-    def storeUnderTest(self):
-        &quot;&quot;&quot;
-        Return a store for testing.
-        &quot;&quot;&quot;
-        return self._sqlCalendarStore1
-
-
-    def otherStoreUnderTest(self):
-        &quot;&quot;&quot;
-        Return a store for testing.
-        &quot;&quot;&quot;
-        return self._sqlCalendarStore2
-
-
-    def newOtherTransaction(self):
-        assert self.otherTransaction is None
-        store2 = self.otherStoreUnderTest()
-        txn = store2.newTransaction()
-        @inlineCallbacks
-        def maybeCommitThis():
-            try:
-                yield txn.commit()
-            except AlreadyFinishedError:
-                pass
-        self.addCleanup(maybeCommitThis)
-        self.otherTransaction = txn
-        return self.otherTransaction
-
-
-    def otherTransactionUnderTest(self):
-        if self.otherTransaction is None:
-            self.newOtherTransaction()
-        return self.otherTransaction
-
-
-    @inlineCallbacks
-    def otherCommit(self):
-        assert self.otherTransaction is not None
-        yield self.otherTransaction.commit()
-        self.otherTransaction = None
-
-
-    @inlineCallbacks
-    def otherAbort(self):
-        assert self.otherTransaction is not None
-        yield self.otherTransaction.abort()
-        self.otherTransaction = None
-
-
-    @inlineCallbacks
-    def makeStore(self, builder, internal, server1, server2):
-
-        directory = self.makeDirectory(internal, server1, server2)
-        store = yield builder.buildStore(self, self.notifierFactory, directory)
-        store.queryCacher = None     # Cannot use query caching
-        store.conduit = self.makeConduit(store)
-        returnValue(store)
-
-
-    def makeDirectory(self, internal, server1, server2):
-
-        directory = TestCalendarStoreDirectoryService()
-
-        # User accounts
-        for ctr in range(1, 100):
-            directory.addRecord(TestCalendarStoreDirectoryRecord(
-                &quot;user%02d&quot; % (ctr,),
-                (&quot;user%02d&quot; % (ctr,),),
-                &quot;User %02d&quot; % (ctr,),
-                frozenset((
-                    &quot;urn:uuid:user%02d&quot; % (ctr,),
-                    &quot;mailto:user%02d@example.com&quot; % (ctr,),
-                )),
-                thisServer=internal,
-                server=server1
-            ))
-
-        for ctr in range(1, 100):
-            directory.addRecord(TestCalendarStoreDirectoryRecord(
-                &quot;puser{:02d}&quot;.format(ctr),
-                (&quot;puser{:02d}&quot;.format(ctr),),
-                &quot;Puser {:02d}&quot;.format(ctr),
-                frozenset((
-                    &quot;urn:uuid:puser{:02d}&quot;.format(ctr),
-                    &quot;mailto:puser{:02d}@example.com&quot;.format(ctr),
-                )),
-                thisServer=not internal,
-                server=server2
-            ))
-
-        return directory
-
-
-    def makeConduit(self, store):
-        conduit = PoddingConduit(store)
-        conduit.conduitRequestClass = FakeConduitRequest
-        return conduit
-
-
-    @inlineCallbacks
-    def createShare(self, ownerGUID=&quot;user01&quot;, shareeGUID=&quot;puser02&quot;, name=&quot;calendar&quot;):
-
-        home = yield self.homeUnderTest(name=ownerGUID, create=True)
-        calendar = yield home.calendarWithName(name)
-        yield calendar.inviteUserToShare(shareeGUID, _BIND_MODE_WRITE, &quot;shared&quot;, shareName=&quot;shared-calendar&quot;)
-        yield self.commit()
-
-        home2 = yield self.homeUnderTest(txn=self.newOtherTransaction(), name=shareeGUID)
-        yield home2.acceptShare(&quot;shared-calendar&quot;)
-        yield self.otherCommit()
-
-        returnValue(&quot;shared-calendar&quot;)
-
-
-    @inlineCallbacks
-    def removeShare(self, ownerGUID=&quot;user01&quot;, shareeGUID=&quot;puser02&quot;, name=&quot;calendar&quot;):
-
-        home = yield self.homeUnderTest(name=ownerGUID)
-        calendar = yield home.calendarWithName(name)
-        yield calendar.uninviteUserFromShare(shareeGUID)
-        yield self.commit()
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorepoddingtestutilpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorepoddingtestutilpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/podding/test/util.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/podding/test/util.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/podding/test/util.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/podding/test/util.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,239 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from txdav.caldav.datastore.scheduling.ischedule.localservers import Server, \
+    Servers
+from txdav.caldav.datastore.test.util import \
+    TestCalendarStoreDirectoryRecord, TestCalendarStoreDirectoryService
+from txdav.common.datastore.podding.conduit import PoddingConduit
+from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
+from txdav.common.datastore.test.util import CommonCommonTests, SQLStoreBuilder,\
+    theStoreBuilder
+
+import txweb2.dav.test.util
+
+from twext.enterprise.ienterprise import AlreadyFinishedError
+
+import json
+
+class FakeConduitRequest(object):
+    &quot;&quot;&quot;
+    A conduit request that sends messages internally rather than using HTTP
+    &quot;&quot;&quot;
+
+    storeMap = {}
+
+    @classmethod
+    def addServerStore(cls, server, store):
+        &quot;&quot;&quot;
+        Add a store mapped to a server. These mappings are used to &quot;deliver&quot; conduit
+        requests to the appropriate store.
+
+        @param uri: the server
+        @type uri: L{Server}
+        @param store: the store
+        @type store: L{ICommonDataStore}
+        &quot;&quot;&quot;
+
+        cls.storeMap[server.details()] = store
+
+
+    def __init__(self, server, data, stream=None, stream_type=None):
+
+        self.server = server
+        self.data = json.dumps(data)
+        self.stream = stream
+        self.streamType = stream_type
+
+
+    @inlineCallbacks
+    def doRequest(self, txn):
+
+        # Generate an HTTP client request
+        try:
+            response = (yield self._processRequest())
+            response = json.loads(response)
+        except Exception as e:
+            raise ValueError(&quot;Failed cross-pod request: {}&quot;.format(e))
+
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def _processRequest(self):
+        &quot;&quot;&quot;
+        Process the request by sending it to the relevant server.
+
+        @return: the HTTP response.
+        @rtype: L{Response}
+        &quot;&quot;&quot;
+
+        store = self.storeMap[self.server.details()]
+        j = json.loads(self.data)
+        if self.stream is not None:
+            j[&quot;stream&quot;] = self.stream
+            j[&quot;streamType&quot;] = self.streamType
+        result = yield store.conduit.processRequest(j)
+        result = json.dumps(result)
+        returnValue(result)
+
+
+
+class MultiStoreConduitTest(CommonCommonTests, txweb2.dav.test.util.TestCase):
+
+    theStoreBuilder2 = SQLStoreBuilder(secondary=True)
+    otherTransaction = None
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(MultiStoreConduitTest, self).setUp()
+
+        server1 = Server(&quot;A&quot;, &quot;http://127.0.0.1:8008&quot;, &quot;A&quot;, True)
+        Servers.addServer(server1)
+
+        server2 = Server(&quot;B&quot;, &quot;http://127.0.0.1:8108&quot;, &quot;B&quot;, False)
+        Servers.addServer(server2)
+
+        self._sqlCalendarStore1 = yield self.makeStore(theStoreBuilder, True, server1, server2)
+        self._sqlCalendarStore2 = yield self.makeStore(self.theStoreBuilder2, False, server1, server2)
+
+        FakeConduitRequest.addServerStore(server1, self._sqlCalendarStore1)
+        FakeConduitRequest.addServerStore(server2, self._sqlCalendarStore2)
+
+
+    def storeUnderTest(self):
+        &quot;&quot;&quot;
+        Return a store for testing.
+        &quot;&quot;&quot;
+        return self._sqlCalendarStore1
+
+
+    def otherStoreUnderTest(self):
+        &quot;&quot;&quot;
+        Return a store for testing.
+        &quot;&quot;&quot;
+        return self._sqlCalendarStore2
+
+
+    def newOtherTransaction(self):
+        assert self.otherTransaction is None
+        store2 = self.otherStoreUnderTest()
+        txn = store2.newTransaction()
+        @inlineCallbacks
+        def maybeCommitThis():
+            try:
+                yield txn.commit()
+            except AlreadyFinishedError:
+                pass
+        self.addCleanup(maybeCommitThis)
+        self.otherTransaction = txn
+        return self.otherTransaction
+
+
+    def otherTransactionUnderTest(self):
+        if self.otherTransaction is None:
+            self.newOtherTransaction()
+        return self.otherTransaction
+
+
+    @inlineCallbacks
+    def otherCommit(self):
+        assert self.otherTransaction is not None
+        yield self.otherTransaction.commit()
+        self.otherTransaction = None
+
+
+    @inlineCallbacks
+    def otherAbort(self):
+        assert self.otherTransaction is not None
+        yield self.otherTransaction.abort()
+        self.otherTransaction = None
+
+
+    @inlineCallbacks
+    def makeStore(self, builder, internal, server1, server2):
+
+        directory = self.makeDirectory(internal, server1, server2)
+        store = yield builder.buildStore(self, self.notifierFactory, directory)
+        store.queryCacher = None     # Cannot use query caching
+        store.conduit = self.makeConduit(store)
+        returnValue(store)
+
+
+    def makeDirectory(self, internal, server1, server2):
+
+        directory = TestCalendarStoreDirectoryService()
+
+        # User accounts
+        for ctr in range(1, 100):
+            directory.addRecord(TestCalendarStoreDirectoryRecord(
+                &quot;user%02d&quot; % (ctr,),
+                (&quot;user%02d&quot; % (ctr,),),
+                &quot;User %02d&quot; % (ctr,),
+                frozenset((
+                    &quot;urn:uuid:user%02d&quot; % (ctr,),
+                    &quot;mailto:user%02d@example.com&quot; % (ctr,),
+                )),
+                thisServer=internal,
+                server=server1
+            ))
+
+        for ctr in range(1, 100):
+            directory.addRecord(TestCalendarStoreDirectoryRecord(
+                &quot;puser{:02d}&quot;.format(ctr),
+                (&quot;puser{:02d}&quot;.format(ctr),),
+                &quot;Puser {:02d}&quot;.format(ctr),
+                frozenset((
+                    &quot;urn:uuid:puser{:02d}&quot;.format(ctr),
+                    &quot;mailto:puser{:02d}@example.com&quot;.format(ctr),
+                )),
+                thisServer=not internal,
+                server=server2
+            ))
+
+        return directory
+
+
+    def makeConduit(self, store):
+        conduit = PoddingConduit(store)
+        conduit.conduitRequestClass = FakeConduitRequest
+        return conduit
+
+
+    @inlineCallbacks
+    def createShare(self, ownerGUID=&quot;user01&quot;, shareeGUID=&quot;puser02&quot;, name=&quot;calendar&quot;):
+
+        home = yield self.homeUnderTest(name=ownerGUID, create=True)
+        calendar = yield home.calendarWithName(name)
+        yield calendar.inviteUserToShare(shareeGUID, _BIND_MODE_WRITE, &quot;shared&quot;, shareName=&quot;shared-calendar&quot;)
+        yield self.commit()
+
+        home2 = yield self.homeUnderTest(txn=self.newOtherTransaction(), name=shareeGUID)
+        yield home2.acceptShare(&quot;shared-calendar&quot;)
+        yield self.otherCommit()
+
+        returnValue(&quot;shared-calendar&quot;)
+
+
+    @inlineCallbacks
+    def removeShare(self, ownerGUID=&quot;user01&quot;, shareeGUID=&quot;puser02&quot;, name=&quot;calendar&quot;):
+
+        home = yield self.homeUnderTest(name=ownerGUID)
+        calendar = yield home.calendarWithName(name)
+        yield calendar.uninviteUserFromShare(shareeGUID)
+        yield self.commit()
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequery__init__py"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/query/__init__.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/__init__.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/query/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,15 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequery__init__pyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequery__init__py"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/query/__init__.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/__init__.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/query/__init__.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/query/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequeryexpressionpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/query/expression.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/expression.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/query/expression.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,382 +0,0 @@
</span><del>-##
-# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-&quot;&quot;&quot;
-Query Expression Elements. These are used to build a 'generic' query
-expression tree that can then be used by different query language
-generators to produce the actual query syntax required (SQL, xpath
-etc).
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;allExpression&quot;,
-    &quot;notExpression&quot;,
-    &quot;andExpression&quot;,
-    &quot;orExpression&quot;,
-    &quot;timerangeExpression&quot;,
-    &quot;textcompareExpression&quot;,
-    &quot;containsExpression&quot;,
-    &quot;notcontainsExpression&quot;,
-    &quot;isExpression&quot;,
-    &quot;isnotExpression&quot;,
-    &quot;startswithExpression&quot;,
-    &quot;notstartswithExpression&quot;,
-    &quot;endswithExpression&quot;,
-    &quot;notendswithExpression&quot;,
-    &quot;inExpression&quot;,
-    &quot;notinExpression&quot;,
-]
-
-class baseExpression(object):
-    &quot;&quot;&quot;
-    The base class for all types of expression.
-    &quot;&quot;&quot;
-
-    def __init__(self):
-        pass
-
-
-    def multi(self):
-        &quot;&quot;&quot;
-        Indicate whether this expression is composed of multiple sub-expressions.
-
-        @return: C{True} if this expressions contains multiple sub-expressions,
-            C{False} otherwise.
-        &quot;&quot;&quot;
-
-        return False
-
-
-    def _collapsedExpression(self):
-        return self
-
-
-    def andWith(self, other):
-        if isinstance(other, andExpression):
-            return andExpression((self._collapsedExpression(),) + tuple(other.expressions))
-        else:
-            return andExpression((self._collapsedExpression(), other._collapsedExpression(),))
-
-
-    def orWith(self, other):
-        if isinstance(other, orExpression):
-            return orExpression((self._collapsedExpression(),) + tuple(other.expressions))
-        else:
-            return orExpression((self._collapsedExpression(), other._collapsedExpression(),))
-
-
-
-class allExpression(baseExpression):
-    &quot;&quot;&quot;
-    Match everything.
-    &quot;&quot;&quot;
-
-    def __init__(self):
-        pass
-
-
-
-class logicExpression(baseExpression):
-    &quot;&quot;&quot;
-    An expression representing a logical operation (boolean).
-    &quot;&quot;&quot;
-
-    def __init__(self, expressions):
-        self.expressions = expressions
-
-
-    def __str__(self):
-        &quot;&quot;&quot;
-        Generate a suitable text descriptor of this expression.
-
-        @return: a C{str} of the text for this expression.
-        &quot;&quot;&quot;
-
-        result = &quot;&quot;
-        for e in self.expressions:
-            if len(result) != 0:
-                result += &quot; &quot; + self.operator() + &quot; &quot;
-            result += str(e)
-        if len(result):
-            result = &quot;(&quot; + result + &quot;)&quot;
-        return result
-
-
-    def multi(self):
-        &quot;&quot;&quot;
-        Indicate whether this expression is composed of multiple expressions.
-
-        @return: C{True} if this expressions contains multiple sub-expressions,
-            C{False} otherwise.
-        &quot;&quot;&quot;
-
-        return True
-
-
-    def _collapsedExpression(self):
-        if self.multi() and len(self.expressions) == 1:
-            return self.expressions[0]._collapsedExpression()
-        else:
-            return self
-
-
-
-class notExpression(logicExpression):
-    &quot;&quot;&quot;
-    Logical NOT operation.
-    &quot;&quot;&quot;
-
-    def __init__(self, expression):
-        super(notExpression, self).__init__([expression])
-
-
-    def operator(self):
-        return &quot;NOT&quot;
-
-
-    def __str__(self):
-        result = self.operator() + &quot; &quot; + str(self.expressions[0])
-        return result
-
-
-    def multi(self):
-        &quot;&quot;&quot;
-        Indicate whether this expression is composed of multiple expressions.
-
-        @return: C{True} if this expressions contains multiple sub-expressions,
-            C{False} otherwise.
-        &quot;&quot;&quot;
-
-        return False
-
-
-
-class andExpression(logicExpression):
-    &quot;&quot;&quot;
-    Logical AND operation.
-    &quot;&quot;&quot;
-
-    def __init__(self, expressions):
-        super(andExpression, self).__init__(expressions)
-
-
-    def operator(self):
-        return &quot;AND&quot;
-
-
-    def andWith(self, other):
-        self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
-        return self
-
-
-
-class orExpression(logicExpression):
-    &quot;&quot;&quot;
-    Logical OR operation.
-    &quot;&quot;&quot;
-
-    def __init__(self, expressions):
-        super(orExpression, self).__init__(expressions)
-
-
-    def operator(self):
-        return &quot;OR&quot;
-
-
-    def orWith(self, other):
-        self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
-        return self
-
-
-
-class timerangeExpression(baseExpression):
-    &quot;&quot;&quot;
-    CalDAV time-range comparison expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, start, end, startfloat, endfloat):
-        self.start = start
-        self.end = end
-        self.startfloat = startfloat
-        self.endfloat = endfloat
-
-
-    def __str__(self):
-        return &quot;timerange(&quot; + str(self.start) + &quot;, &quot; + str(self.end) + &quot;)&quot;
-
-
-
-class textcompareExpression(baseExpression):
-    &quot;&quot;&quot;
-    Base class for text comparison expressions.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        self.field = field
-        self.text = text
-        self.caseless = caseless
-
-
-    def __str__(self):
-        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + self.text + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
-
-
-
-class containsExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text CONTAINS (sub-string match) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(containsExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;contains&quot;
-
-
-
-class notcontainsExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text NOT CONTAINS (sub-string match) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(notcontainsExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;does not contain&quot;
-
-
-
-class isExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text IS (exact string match) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(isExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;is&quot;
-
-
-
-class isnotExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text IS NOT (exact string match) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(isnotExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;is not&quot;
-
-
-
-class startswithExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text STARTSWITH (sub-string match) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(startswithExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;starts with&quot;
-
-
-
-class notstartswithExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text NOT STARTSWITH (sub-string match) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(notstartswithExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;does not start with&quot;
-
-
-
-class endswithExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text STARTSWITH (sub-string match) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(endswithExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;ends with&quot;
-
-
-
-class notendswithExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text NOT STARTSWITH (sub-string match) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(notendswithExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;does not end with&quot;
-
-
-
-class inExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text IN (exact string match to one of the supplied items) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text_list, caseless):
-        super(inExpression, self).__init__(field, text_list, caseless)
-
-
-    def operator(self):
-        return &quot;in&quot;
-
-
-    def __str__(self):
-        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + str(self.text) + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
-
-
-
-class notinExpression(textcompareExpression):
-    &quot;&quot;&quot;
-    Text NOT IN (exact string match to none of the supplied items) expression.
-    &quot;&quot;&quot;
-
-    def __init__(self, field, text, caseless):
-        super(notinExpression, self).__init__(field, text, caseless)
-
-
-    def operator(self):
-        return &quot;not in&quot;
-
-
-    def __str__(self):
-        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + str(self.text) + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequeryexpressionpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequeryexpressionpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/query/expression.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/expression.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/query/expression.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/query/expression.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,382 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+&quot;&quot;&quot;
+Query Expression Elements. These are used to build a 'generic' query
+expression tree that can then be used by different query language
+generators to produce the actual query syntax required (SQL, xpath
+etc).
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;allExpression&quot;,
+    &quot;notExpression&quot;,
+    &quot;andExpression&quot;,
+    &quot;orExpression&quot;,
+    &quot;timerangeExpression&quot;,
+    &quot;textcompareExpression&quot;,
+    &quot;containsExpression&quot;,
+    &quot;notcontainsExpression&quot;,
+    &quot;isExpression&quot;,
+    &quot;isnotExpression&quot;,
+    &quot;startswithExpression&quot;,
+    &quot;notstartswithExpression&quot;,
+    &quot;endswithExpression&quot;,
+    &quot;notendswithExpression&quot;,
+    &quot;inExpression&quot;,
+    &quot;notinExpression&quot;,
+]
+
+class baseExpression(object):
+    &quot;&quot;&quot;
+    The base class for all types of expression.
+    &quot;&quot;&quot;
+
+    def __init__(self):
+        pass
+
+
+    def multi(self):
+        &quot;&quot;&quot;
+        Indicate whether this expression is composed of multiple sub-expressions.
+
+        @return: C{True} if this expressions contains multiple sub-expressions,
+            C{False} otherwise.
+        &quot;&quot;&quot;
+
+        return False
+
+
+    def _collapsedExpression(self):
+        return self
+
+
+    def andWith(self, other):
+        if isinstance(other, andExpression):
+            return andExpression((self._collapsedExpression(),) + tuple(other.expressions))
+        else:
+            return andExpression((self._collapsedExpression(), other._collapsedExpression(),))
+
+
+    def orWith(self, other):
+        if isinstance(other, orExpression):
+            return orExpression((self._collapsedExpression(),) + tuple(other.expressions))
+        else:
+            return orExpression((self._collapsedExpression(), other._collapsedExpression(),))
+
+
+
+class allExpression(baseExpression):
+    &quot;&quot;&quot;
+    Match everything.
+    &quot;&quot;&quot;
+
+    def __init__(self):
+        pass
+
+
+
+class logicExpression(baseExpression):
+    &quot;&quot;&quot;
+    An expression representing a logical operation (boolean).
+    &quot;&quot;&quot;
+
+    def __init__(self, expressions):
+        self.expressions = expressions
+
+
+    def __str__(self):
+        &quot;&quot;&quot;
+        Generate a suitable text descriptor of this expression.
+
+        @return: a C{str} of the text for this expression.
+        &quot;&quot;&quot;
+
+        result = &quot;&quot;
+        for e in self.expressions:
+            if len(result) != 0:
+                result += &quot; &quot; + self.operator() + &quot; &quot;
+            result += str(e)
+        if len(result):
+            result = &quot;(&quot; + result + &quot;)&quot;
+        return result
+
+
+    def multi(self):
+        &quot;&quot;&quot;
+        Indicate whether this expression is composed of multiple expressions.
+
+        @return: C{True} if this expressions contains multiple sub-expressions,
+            C{False} otherwise.
+        &quot;&quot;&quot;
+
+        return True
+
+
+    def _collapsedExpression(self):
+        if self.multi() and len(self.expressions) == 1:
+            return self.expressions[0]._collapsedExpression()
+        else:
+            return self
+
+
+
+class notExpression(logicExpression):
+    &quot;&quot;&quot;
+    Logical NOT operation.
+    &quot;&quot;&quot;
+
+    def __init__(self, expression):
+        super(notExpression, self).__init__([expression])
+
+
+    def operator(self):
+        return &quot;NOT&quot;
+
+
+    def __str__(self):
+        result = self.operator() + &quot; &quot; + str(self.expressions[0])
+        return result
+
+
+    def multi(self):
+        &quot;&quot;&quot;
+        Indicate whether this expression is composed of multiple expressions.
+
+        @return: C{True} if this expressions contains multiple sub-expressions,
+            C{False} otherwise.
+        &quot;&quot;&quot;
+
+        return False
+
+
+
+class andExpression(logicExpression):
+    &quot;&quot;&quot;
+    Logical AND operation.
+    &quot;&quot;&quot;
+
+    def __init__(self, expressions):
+        super(andExpression, self).__init__(expressions)
+
+
+    def operator(self):
+        return &quot;AND&quot;
+
+
+    def andWith(self, other):
+        self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
+        return self
+
+
+
+class orExpression(logicExpression):
+    &quot;&quot;&quot;
+    Logical OR operation.
+    &quot;&quot;&quot;
+
+    def __init__(self, expressions):
+        super(orExpression, self).__init__(expressions)
+
+
+    def operator(self):
+        return &quot;OR&quot;
+
+
+    def orWith(self, other):
+        self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
+        return self
+
+
+
+class timerangeExpression(baseExpression):
+    &quot;&quot;&quot;
+    CalDAV time-range comparison expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, start, end, startfloat, endfloat):
+        self.start = start
+        self.end = end
+        self.startfloat = startfloat
+        self.endfloat = endfloat
+
+
+    def __str__(self):
+        return &quot;timerange(&quot; + str(self.start) + &quot;, &quot; + str(self.end) + &quot;)&quot;
+
+
+
+class textcompareExpression(baseExpression):
+    &quot;&quot;&quot;
+    Base class for text comparison expressions.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        self.field = field
+        self.text = text
+        self.caseless = caseless
+
+
+    def __str__(self):
+        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + self.text + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
+
+
+
+class containsExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text CONTAINS (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(containsExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;contains&quot;
+
+
+
+class notcontainsExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text NOT CONTAINS (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(notcontainsExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;does not contain&quot;
+
+
+
+class isExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text IS (exact string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(isExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;is&quot;
+
+
+
+class isnotExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text IS NOT (exact string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(isnotExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;is not&quot;
+
+
+
+class startswithExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text STARTSWITH (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(startswithExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;starts with&quot;
+
+
+
+class notstartswithExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text NOT STARTSWITH (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(notstartswithExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;does not start with&quot;
+
+
+
+class endswithExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text STARTSWITH (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(endswithExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;ends with&quot;
+
+
+
+class notendswithExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text NOT STARTSWITH (sub-string match) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(notendswithExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;does not end with&quot;
+
+
+
+class inExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text IN (exact string match to one of the supplied items) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text_list, caseless):
+        super(inExpression, self).__init__(field, text_list, caseless)
+
+
+    def operator(self):
+        return &quot;in&quot;
+
+
+    def __str__(self):
+        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + str(self.text) + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
+
+
+
+class notinExpression(textcompareExpression):
+    &quot;&quot;&quot;
+    Text NOT IN (exact string match to none of the supplied items) expression.
+    &quot;&quot;&quot;
+
+    def __init__(self, field, text, caseless):
+        super(notinExpression, self).__init__(field, text, caseless)
+
+
+    def operator(self):
+        return &quot;not in&quot;
+
+
+    def __str__(self):
+        return self.operator() + &quot;(&quot; + self.field + &quot;, &quot; + str(self.text) + &quot;, &quot; + str(self.caseless) + &quot;)&quot;
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequeryfilegeneratorpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/query/filegenerator.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/filegenerator.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/query/filegenerator.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,322 +0,0 @@
</span><del>-##
-# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from txdav.common.datastore.query import expression
-
-&quot;&quot;&quot;
-SQLLite statement generator from query expressions.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;sqllitegenerator&quot;,
-]
-
-import cStringIO as StringIO
-
-class sqllitegenerator(object):
-
-    FROM = &quot; from &quot;
-    WHERE = &quot; where &quot;
-    RESOURCEDB = &quot;RESOURCE&quot;
-    TIMESPANDB = &quot;TIMESPAN&quot;
-    TRANSPARENCYDB = &quot;TRANSPARENCY&quot;
-    PERUSERDB = &quot;PERUSER&quot;
-    NOTOP = &quot;NOT &quot;
-    ANDOP = &quot; AND &quot;
-    OROP = &quot; OR &quot;
-    CONTAINSOP = &quot; GLOB &quot;
-    NOTCONTAINSOP = &quot; NOT GLOB &quot;
-    ISOP = &quot; == &quot;
-    ISNOTOP = &quot; != &quot;
-    STARTSWITHOP = &quot; GLOB &quot;
-    NOTSTARTSWITHOP = &quot; NOT GLOB &quot;
-    ENDSWITHOP = &quot; GLOB &quot;
-    NOTENDSWITHOP = &quot; NOT GLOB &quot;
-    INOP = &quot; IN &quot;
-    NOTINOP = &quot; NOT IN &quot;
-
-    FIELDS = {
-        &quot;TYPE&quot;: &quot;RESOURCE.TYPE&quot;,
-        &quot;UID&quot;: &quot;RESOURCE.UID&quot;,
-    }
-
-    TIMESPANTEST = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START &lt; %s AND TIMESPAN.END &gt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START &lt; %s AND TIMESPAN.END &gt; %s))&quot;
-    TIMESPANTEST_NOEND = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.END &gt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.END &gt; %s))&quot;
-    TIMESPANTEST_NOSTART = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START &lt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START &lt; %s))&quot;
-    TIMESPANTEST_TAIL_PIECE = &quot; AND TIMESPAN.RESOURCEID == RESOURCE.RESOURCEID&quot;
-    TIMESPANTEST_JOIN_ON_PIECE = &quot;TIMESPAN.INSTANCEID == TRANSPARENCY.INSTANCEID AND TRANSPARENCY.PERUSERID == %s&quot;
-
-    def __init__(self, expr, calendarid, userid, freebusy=False):
-        &quot;&quot;&quot;
-
-        @param expr: the query expression object model
-        @type expr: L{Filter}
-        @param calendarid: resource ID - not used for file-based per-calendar indexes
-        @type calendarid: C{int}
-        @param userid: user for whom query is being done - query will be scoped to that user's privileges and their transparency
-        @type userid: C{str}
-        @param freebusy: whether or not a freebusy query is being done - if it is, additional time range and transparency information is returned
-        @type freebusy: C{bool}
-        &quot;&quot;&quot;
-        self.expression = expr
-        self.calendarid = calendarid
-        self.userid = userid if userid else &quot;&quot;
-        self.freebusy = freebusy
-        self.usedtimespan = False
-
-
-    def generate(self):
-        &quot;&quot;&quot;
-        Generate the actual SQL 'where ...' expression from the passed in expression tree.
-
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
-            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
-        &quot;&quot;&quot;
-
-        # Init state
-        self.sout = StringIO.StringIO()
-        self.arguments = []
-        self.substitutions = []
-        self.usedtimespan = False
-
-        # Generate ' where ...' partial statement
-        self.generateExpression(self.expression)
-
-        # Prefix with ' from ...' partial statement
-        select = self.FROM + self.RESOURCEDB
-        if self.usedtimespan:
-
-            # Free busy needs transparency join
-            if self.freebusy:
-                self.frontArgument(self.userid)
-                select += &quot;, %s LEFT OUTER JOIN %s ON (%s)&quot; % (
-                    self.TIMESPANDB,
-                    self.TRANSPARENCYDB,
-                    self.TIMESPANTEST_JOIN_ON_PIECE
-                )
-            else:
-                select += &quot;, %s&quot; % (
-                    self.TIMESPANDB,
-                )
-        select += self.WHERE
-        if self.usedtimespan:
-            select += &quot;(&quot;
-        select += self.sout.getvalue()
-        if self.usedtimespan:
-            if self.calendarid:
-                self.setArgument(self.calendarid)
-            select += &quot;)%s&quot; % (self.TIMESPANTEST_TAIL_PIECE,)
-
-        select = select % tuple(self.substitutions)
-
-        return select, self.arguments
-
-
-    def generateExpression(self, expr):
-        &quot;&quot;&quot;
-        Generate an expression and all it's subexpressions.
-
-        @param expr: the L{baseExpression} derived class to write out.
-        @return: C{True} if the TIMESPAN table is used, C{False} otherwise.
-        &quot;&quot;&quot;
-
-        # Generate based on each type of expression we might encounter
-
-        # ALL
-        if isinstance(expr, expression.allExpression):
-            # Wipe out the ' where ...' clause so everything is matched
-            self.sout.truncate(0)
-            self.arguments = []
-            self.substitutions = []
-            self.usedtimespan = False
-
-        # NOT
-        elif isinstance(expr, expression.notExpression):
-            self.sout.write(self.NOTOP)
-            self.generateSubExpression(expr.expressions[0])
-
-        # AND
-        elif isinstance(expr, expression.andExpression):
-            first = True
-            for e in expr.expressions:
-                if first:
-                    first = False
-                else:
-                    self.sout.write(self.ANDOP)
-                self.generateSubExpression(e)
-
-        # OR
-        elif isinstance(expr, expression.orExpression):
-            first = True
-            for e in expr.expressions:
-                if first:
-                    first = False
-                else:
-                    self.sout.write(self.OROP)
-                self.generateSubExpression(e)
-
-        # time-range
-        elif isinstance(expr, expression.timerangeExpression):
-            if expr.start and expr.end:
-                self.setArgument(expr.end)
-                self.setArgument(expr.start)
-                self.setArgument(expr.endfloat)
-                self.setArgument(expr.startfloat)
-                test = self.TIMESPANTEST
-            elif expr.start and expr.end is None:
-                self.setArgument(expr.start)
-                self.setArgument(expr.startfloat)
-                test = self.TIMESPANTEST_NOEND
-            elif not expr.start and expr.end:
-                self.setArgument(expr.end)
-                self.setArgument(expr.endfloat)
-                test = self.TIMESPANTEST_NOSTART
-
-            self.sout.write(test)
-            self.usedtimespan = True
-
-        # CONTAINS
-        elif isinstance(expr, expression.containsExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.CONTAINSOP)
-            self.addArgument(self.containsArgument(expr.text))
-
-        # NOT CONTAINS
-        elif isinstance(expr, expression.notcontainsExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.NOTCONTAINSOP)
-            self.addArgument(self.containsArgument(expr.text))
-
-        # IS
-        elif isinstance(expr, expression.isExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.ISOP)
-            self.addArgument(expr.text)
-
-        # IS NOT
-        elif isinstance(expr, expression.isnotExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.ISNOTOP)
-            self.addArgument(expr.text)
-
-        # STARTSWITH
-        elif isinstance(expr, expression.startswithExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.STARTSWITHOP)
-            self.addArgument(self.startswithArgument(expr.text))
-
-        # NOT STARTSWITH
-        elif isinstance(expr, expression.notstartswithExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.NOTSTARTSWITHOP)
-            self.addArgument(self.startswithArgument(expr.text))
-
-        # ENDSWITH
-        elif isinstance(expr, expression.endswithExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.ENDSWITHOP)
-            self.addArgument(self.endswithArgument(expr.text))
-
-        # NOT ENDSWITH
-        elif isinstance(expr, expression.notendswithExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.NOTENDSWITHOP)
-            self.addArgument(self.endswithArgument(expr.text))
-
-        # IN
-        elif isinstance(expr, expression.inExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.INOP)
-            self.sout.write(&quot;(&quot;)
-            for count, item in enumerate(expr.text):
-                if count != 0:
-                    self.sout.write(&quot;, &quot;)
-                self.addArgument(item)
-            self.sout.write(&quot;)&quot;)
-
-        # NOT IN
-        elif isinstance(expr, expression.notinExpression):
-            self.sout.write(expr.field)
-            self.sout.write(self.NOTINOP)
-            self.sout.write(&quot;(&quot;)
-            for count, item in enumerate(expr.text):
-                if count != 0:
-                    self.sout.write(&quot;, &quot;)
-                self.addArgument(item)
-            self.sout.write(&quot;)&quot;)
-
-
-    def generateSubExpression(self, expression):
-        &quot;&quot;&quot;
-        Generate an SQL expression possibly in parenthesis if its a compound expression.
-
-        @param expression: the L{baseExpression} to write out.
-        @return: C{True} if the TIMESPAN table is used, C{False} otherwise.
-        &quot;&quot;&quot;
-
-        if expression.multi():
-            self.sout.write(&quot;(&quot;)
-        self.generateExpression(expression)
-        if expression.multi():
-            self.sout.write(&quot;)&quot;)
-
-
-    def addArgument(self, arg):
-        &quot;&quot;&quot;
-
-        @param arg: the C{str} of the argument to add
-        &quot;&quot;&quot;
-
-        # Append argument to the list and add the appropriate substitution string to the output stream.
-        self.arguments.append(arg)
-        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
-        self.sout.write(&quot;%s&quot;)
-
-
-    def setArgument(self, arg):
-        &quot;&quot;&quot;
-
-        @param arg: the C{str} of the argument to add
-        @return: C{str} for argument substitution text
-        &quot;&quot;&quot;
-
-        # Append argument to the list and add the appropriate substitution string to the output stream.
-        self.arguments.append(arg)
-        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
-
-
-    def frontArgument(self, arg):
-        &quot;&quot;&quot;
-
-        @param arg: the C{str} of the argument to add
-        @return: C{str} for argument substitution text
-        &quot;&quot;&quot;
-
-        # Append argument to the list and add the appropriate substitution string to the output stream.
-        self.arguments.insert(0, arg)
-        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
-
-
-    def containsArgument(self, arg):
-        return &quot;*%s*&quot; % (arg,)
-
-
-    def startswithArgument(self, arg):
-        return &quot;%s*&quot; % (arg,)
-
-
-    def endswithArgument(self, arg):
-        return &quot;*%s&quot; % (arg,)
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequeryfilegeneratorpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequeryfilegeneratorpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/query/filegenerator.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/filegenerator.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/query/filegenerator.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/query/filegenerator.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,322 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from txdav.common.datastore.query import expression
+
+&quot;&quot;&quot;
+SQLLite statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;sqllitegenerator&quot;,
+]
+
+import cStringIO as StringIO
+
+class sqllitegenerator(object):
+
+    FROM = &quot; from &quot;
+    WHERE = &quot; where &quot;
+    RESOURCEDB = &quot;RESOURCE&quot;
+    TIMESPANDB = &quot;TIMESPAN&quot;
+    TRANSPARENCYDB = &quot;TRANSPARENCY&quot;
+    PERUSERDB = &quot;PERUSER&quot;
+    NOTOP = &quot;NOT &quot;
+    ANDOP = &quot; AND &quot;
+    OROP = &quot; OR &quot;
+    CONTAINSOP = &quot; GLOB &quot;
+    NOTCONTAINSOP = &quot; NOT GLOB &quot;
+    ISOP = &quot; == &quot;
+    ISNOTOP = &quot; != &quot;
+    STARTSWITHOP = &quot; GLOB &quot;
+    NOTSTARTSWITHOP = &quot; NOT GLOB &quot;
+    ENDSWITHOP = &quot; GLOB &quot;
+    NOTENDSWITHOP = &quot; NOT GLOB &quot;
+    INOP = &quot; IN &quot;
+    NOTINOP = &quot; NOT IN &quot;
+
+    FIELDS = {
+        &quot;TYPE&quot;: &quot;RESOURCE.TYPE&quot;,
+        &quot;UID&quot;: &quot;RESOURCE.UID&quot;,
+    }
+
+    TIMESPANTEST = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START &lt; %s AND TIMESPAN.END &gt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START &lt; %s AND TIMESPAN.END &gt; %s))&quot;
+    TIMESPANTEST_NOEND = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.END &gt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.END &gt; %s))&quot;
+    TIMESPANTEST_NOSTART = &quot;((TIMESPAN.FLOAT == 'N' AND TIMESPAN.START &lt; %s) OR (TIMESPAN.FLOAT == 'Y' AND TIMESPAN.START &lt; %s))&quot;
+    TIMESPANTEST_TAIL_PIECE = &quot; AND TIMESPAN.RESOURCEID == RESOURCE.RESOURCEID&quot;
+    TIMESPANTEST_JOIN_ON_PIECE = &quot;TIMESPAN.INSTANCEID == TRANSPARENCY.INSTANCEID AND TRANSPARENCY.PERUSERID == %s&quot;
+
+    def __init__(self, expr, calendarid, userid, freebusy=False):
+        &quot;&quot;&quot;
+
+        @param expr: the query expression object model
+        @type expr: L{Filter}
+        @param calendarid: resource ID - not used for file-based per-calendar indexes
+        @type calendarid: C{int}
+        @param userid: user for whom query is being done - query will be scoped to that user's privileges and their transparency
+        @type userid: C{str}
+        @param freebusy: whether or not a freebusy query is being done - if it is, additional time range and transparency information is returned
+        @type freebusy: C{bool}
+        &quot;&quot;&quot;
+        self.expression = expr
+        self.calendarid = calendarid
+        self.userid = userid if userid else &quot;&quot;
+        self.freebusy = freebusy
+        self.usedtimespan = False
+
+
+    def generate(self):
+        &quot;&quot;&quot;
+        Generate the actual SQL 'where ...' expression from the passed in expression tree.
+
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+        &quot;&quot;&quot;
+
+        # Init state
+        self.sout = StringIO.StringIO()
+        self.arguments = []
+        self.substitutions = []
+        self.usedtimespan = False
+
+        # Generate ' where ...' partial statement
+        self.generateExpression(self.expression)
+
+        # Prefix with ' from ...' partial statement
+        select = self.FROM + self.RESOURCEDB
+        if self.usedtimespan:
+
+            # Free busy needs transparency join
+            if self.freebusy:
+                self.frontArgument(self.userid)
+                select += &quot;, %s LEFT OUTER JOIN %s ON (%s)&quot; % (
+                    self.TIMESPANDB,
+                    self.TRANSPARENCYDB,
+                    self.TIMESPANTEST_JOIN_ON_PIECE
+                )
+            else:
+                select += &quot;, %s&quot; % (
+                    self.TIMESPANDB,
+                )
+        select += self.WHERE
+        if self.usedtimespan:
+            select += &quot;(&quot;
+        select += self.sout.getvalue()
+        if self.usedtimespan:
+            if self.calendarid:
+                self.setArgument(self.calendarid)
+            select += &quot;)%s&quot; % (self.TIMESPANTEST_TAIL_PIECE,)
+
+        select = select % tuple(self.substitutions)
+
+        return select, self.arguments
+
+
+    def generateExpression(self, expr):
+        &quot;&quot;&quot;
+        Generate an expression and all it's subexpressions.
+
+        @param expr: the L{baseExpression} derived class to write out.
+        @return: C{True} if the TIMESPAN table is used, C{False} otherwise.
+        &quot;&quot;&quot;
+
+        # Generate based on each type of expression we might encounter
+
+        # ALL
+        if isinstance(expr, expression.allExpression):
+            # Wipe out the ' where ...' clause so everything is matched
+            self.sout.truncate(0)
+            self.arguments = []
+            self.substitutions = []
+            self.usedtimespan = False
+
+        # NOT
+        elif isinstance(expr, expression.notExpression):
+            self.sout.write(self.NOTOP)
+            self.generateSubExpression(expr.expressions[0])
+
+        # AND
+        elif isinstance(expr, expression.andExpression):
+            first = True
+            for e in expr.expressions:
+                if first:
+                    first = False
+                else:
+                    self.sout.write(self.ANDOP)
+                self.generateSubExpression(e)
+
+        # OR
+        elif isinstance(expr, expression.orExpression):
+            first = True
+            for e in expr.expressions:
+                if first:
+                    first = False
+                else:
+                    self.sout.write(self.OROP)
+                self.generateSubExpression(e)
+
+        # time-range
+        elif isinstance(expr, expression.timerangeExpression):
+            if expr.start and expr.end:
+                self.setArgument(expr.end)
+                self.setArgument(expr.start)
+                self.setArgument(expr.endfloat)
+                self.setArgument(expr.startfloat)
+                test = self.TIMESPANTEST
+            elif expr.start and expr.end is None:
+                self.setArgument(expr.start)
+                self.setArgument(expr.startfloat)
+                test = self.TIMESPANTEST_NOEND
+            elif not expr.start and expr.end:
+                self.setArgument(expr.end)
+                self.setArgument(expr.endfloat)
+                test = self.TIMESPANTEST_NOSTART
+
+            self.sout.write(test)
+            self.usedtimespan = True
+
+        # CONTAINS
+        elif isinstance(expr, expression.containsExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.CONTAINSOP)
+            self.addArgument(self.containsArgument(expr.text))
+
+        # NOT CONTAINS
+        elif isinstance(expr, expression.notcontainsExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.NOTCONTAINSOP)
+            self.addArgument(self.containsArgument(expr.text))
+
+        # IS
+        elif isinstance(expr, expression.isExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.ISOP)
+            self.addArgument(expr.text)
+
+        # IS NOT
+        elif isinstance(expr, expression.isnotExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.ISNOTOP)
+            self.addArgument(expr.text)
+
+        # STARTSWITH
+        elif isinstance(expr, expression.startswithExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.STARTSWITHOP)
+            self.addArgument(self.startswithArgument(expr.text))
+
+        # NOT STARTSWITH
+        elif isinstance(expr, expression.notstartswithExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.NOTSTARTSWITHOP)
+            self.addArgument(self.startswithArgument(expr.text))
+
+        # ENDSWITH
+        elif isinstance(expr, expression.endswithExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.ENDSWITHOP)
+            self.addArgument(self.endswithArgument(expr.text))
+
+        # NOT ENDSWITH
+        elif isinstance(expr, expression.notendswithExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.NOTENDSWITHOP)
+            self.addArgument(self.endswithArgument(expr.text))
+
+        # IN
+        elif isinstance(expr, expression.inExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.INOP)
+            self.sout.write(&quot;(&quot;)
+            for count, item in enumerate(expr.text):
+                if count != 0:
+                    self.sout.write(&quot;, &quot;)
+                self.addArgument(item)
+            self.sout.write(&quot;)&quot;)
+
+        # NOT IN
+        elif isinstance(expr, expression.notinExpression):
+            self.sout.write(expr.field)
+            self.sout.write(self.NOTINOP)
+            self.sout.write(&quot;(&quot;)
+            for count, item in enumerate(expr.text):
+                if count != 0:
+                    self.sout.write(&quot;, &quot;)
+                self.addArgument(item)
+            self.sout.write(&quot;)&quot;)
+
+
+    def generateSubExpression(self, expression):
+        &quot;&quot;&quot;
+        Generate an SQL expression possibly in parenthesis if its a compound expression.
+
+        @param expression: the L{baseExpression} to write out.
+        @return: C{True} if the TIMESPAN table is used, C{False} otherwise.
+        &quot;&quot;&quot;
+
+        if expression.multi():
+            self.sout.write(&quot;(&quot;)
+        self.generateExpression(expression)
+        if expression.multi():
+            self.sout.write(&quot;)&quot;)
+
+
+    def addArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.arguments.append(arg)
+        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
+        self.sout.write(&quot;%s&quot;)
+
+
+    def setArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        @return: C{str} for argument substitution text
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.arguments.append(arg)
+        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
+
+
+    def frontArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        @return: C{str} for argument substitution text
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.arguments.insert(0, arg)
+        self.substitutions.append(&quot;:&quot; + str(len(self.arguments)))
+
+
+    def containsArgument(self, arg):
+        return &quot;*%s*&quot; % (arg,)
+
+
+    def startswithArgument(self, arg):
+        return &quot;%s*&quot; % (arg,)
+
+
+    def endswithArgument(self, arg):
+        return &quot;*%s&quot; % (arg,)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequerygeneratorpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/query/generator.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/generator.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/query/generator.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,164 +0,0 @@
</span><del>-##
-# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twext.enterprise.dal.syntax import Select, Parameter, Not
-from txdav.common.datastore.query import expression
-
-&quot;&quot;&quot;
-SQL statement generator from query expressions.
-&quot;&quot;&quot;
-
-__all__ = [
-    &quot;SQLQueryGenerator&quot;,
-]
-
-class SQLQueryGenerator(object):
-
-    def __init__(self, expr, collection, whereid):
-        &quot;&quot;&quot;
-
-        @param expr: the query expression object model
-        @type expr: L{expression}
-        @param collection: the resource targeted by the query
-        @type collection: L{CommonHomeChild}
-        &quot;&quot;&quot;
-        self.expression = expr
-        self.collection = collection
-        self.whereid = whereid
-
-
-    def generate(self):
-        &quot;&quot;&quot;
-        Generate the actual SQL statement from the passed in expression tree.
-
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
-            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
-        &quot;&quot;&quot;
-
-        # Init state
-        self.arguments = {}
-        self.argcount = 0
-        obj = self.collection._objectSchema
-
-        columns = [obj.RESOURCE_NAME, obj.UID]
-
-        # For SQL data DB we need to restrict the query to just the targeted collection resource-id if provided
-        if self.whereid:
-            # AND the whole thing
-            test = expression.isExpression(obj.PARENT_RESOURCE_ID, self.whereid, True)
-            self.expression = test if isinstance(self.expression, expression.allExpression) else test.andWith(self.expression)
-
-        # Generate ' where ...' partial statement
-        where = self.generateExpression(self.expression)
-
-        select = Select(
-            columns,
-            From=obj,
-            Where=where,
-            Distinct=True,
-        )
-
-        return select, self.arguments
-
-
-    def generateExpression(self, expr):
-        &quot;&quot;&quot;
-        Generate an expression and all it's subexpressions.
-
-        @param expr: the L{baseExpression} derived class to write out.
-        &quot;&quot;&quot;
-
-        # Generate based on each type of expression we might encounter
-        partial = None
-
-        # ALL
-        if isinstance(expr, expression.allExpression):
-            # Everything is matched
-            partial = None
-            self.arguments = {}
-
-        # NOT
-        elif isinstance(expr, expression.notExpression):
-            partial = Not(self.generateExpression(expr.expressions[0]))
-
-        # AND
-        elif isinstance(expr, expression.andExpression):
-            for e in expr.expressions:
-                next = self.generateExpression(e)
-                partial = partial.And(next) if partial is not None else next
-
-        # OR
-        elif isinstance(expr, expression.orExpression):
-            for e in expr.expressions:
-                next = self.generateExpression(e)
-                partial = partial.Or(next) if partial is not None else next
-
-        # CONTAINS
-        elif isinstance(expr, expression.containsExpression):
-            partial = expr.field.Contains(expr.text)
-
-        # NOT CONTAINS
-        elif isinstance(expr, expression.notcontainsExpression):
-            partial = expr.field.NotContains(expr.text)
-
-        # IS
-        elif isinstance(expr, expression.isExpression):
-            partial = expr.field == expr.text
-
-        # IS NOT
-        elif isinstance(expr, expression.isnotExpression):
-            partial = expr.field != expr.text
-
-        # STARTSWITH
-        elif isinstance(expr, expression.startswithExpression):
-            partial = expr.field.StartsWith(expr.text)
-
-        # NOT STARTSWITH
-        elif isinstance(expr, expression.notstartswithExpression):
-            partial = expr.field.NotStartsWith(expr.text)
-
-        # ENDSWITH
-        elif isinstance(expr, expression.endswithExpression):
-            partial = expr.field.EndsWith(expr.text)
-
-        # NOT ENDSWITH
-        elif isinstance(expr, expression.notendswithExpression):
-            partial = expr.field.NotEndsWith(expr.text)
-
-        # IN
-        elif isinstance(expr, expression.inExpression):
-            argname = self.addArgument(expr.text)
-            partial = expr.field.In(Parameter(argname, len(expr.text)))
-
-        # NOT IN
-        elif isinstance(expr, expression.notinExpression):
-            argname = self.addArgument(expr.text)
-            partial = expr.field.NotIn(Parameter(argname, len(expr.text)))
-
-        return partial
-
-
-    def addArgument(self, arg):
-        &quot;&quot;&quot;
-
-        @param arg: the C{str} of the argument to add
-        &quot;&quot;&quot;
-
-        # Append argument to the list and add the appropriate substitution string to the output stream.
-        self.argcount += 1
-        argname = &quot;arg{}&quot;.format(self.argcount)
-        self.arguments[argname] = arg
-        return argname
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequerygeneratorpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerygeneratorpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/query/generator.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/generator.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/query/generator.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/query/generator.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,164 @@
</span><ins>+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.enterprise.dal.syntax import Select, Parameter, Not
+from txdav.common.datastore.query import expression
+
+&quot;&quot;&quot;
+SQL statement generator from query expressions.
+&quot;&quot;&quot;
+
+__all__ = [
+    &quot;SQLQueryGenerator&quot;,
+]
+
+class SQLQueryGenerator(object):
+
+    def __init__(self, expr, collection, whereid):
+        &quot;&quot;&quot;
+
+        @param expr: the query expression object model
+        @type expr: L{expression}
+        @param collection: the resource targeted by the query
+        @type collection: L{CommonHomeChild}
+        &quot;&quot;&quot;
+        self.expression = expr
+        self.collection = collection
+        self.whereid = whereid
+
+
+    def generate(self):
+        &quot;&quot;&quot;
+        Generate the actual SQL statement from the passed in expression tree.
+
+        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the partial SQL statement,
+            and the C{list} is the list of argument substitutions to use with the SQL API execute method.
+        &quot;&quot;&quot;
+
+        # Init state
+        self.arguments = {}
+        self.argcount = 0
+        obj = self.collection._objectSchema
+
+        columns = [obj.RESOURCE_NAME, obj.UID]
+
+        # For SQL data DB we need to restrict the query to just the targeted collection resource-id if provided
+        if self.whereid:
+            # AND the whole thing
+            test = expression.isExpression(obj.PARENT_RESOURCE_ID, self.whereid, True)
+            self.expression = test if isinstance(self.expression, expression.allExpression) else test.andWith(self.expression)
+
+        # Generate ' where ...' partial statement
+        where = self.generateExpression(self.expression)
+
+        select = Select(
+            columns,
+            From=obj,
+            Where=where,
+            Distinct=True,
+        )
+
+        return select, self.arguments
+
+
+    def generateExpression(self, expr):
+        &quot;&quot;&quot;
+        Generate an expression and all it's subexpressions.
+
+        @param expr: the L{baseExpression} derived class to write out.
+        &quot;&quot;&quot;
+
+        # Generate based on each type of expression we might encounter
+        partial = None
+
+        # ALL
+        if isinstance(expr, expression.allExpression):
+            # Everything is matched
+            partial = None
+            self.arguments = {}
+
+        # NOT
+        elif isinstance(expr, expression.notExpression):
+            partial = Not(self.generateExpression(expr.expressions[0]))
+
+        # AND
+        elif isinstance(expr, expression.andExpression):
+            for e in expr.expressions:
+                next = self.generateExpression(e)
+                partial = partial.And(next) if partial is not None else next
+
+        # OR
+        elif isinstance(expr, expression.orExpression):
+            for e in expr.expressions:
+                next = self.generateExpression(e)
+                partial = partial.Or(next) if partial is not None else next
+
+        # CONTAINS
+        elif isinstance(expr, expression.containsExpression):
+            partial = expr.field.Contains(expr.text)
+
+        # NOT CONTAINS
+        elif isinstance(expr, expression.notcontainsExpression):
+            partial = expr.field.NotContains(expr.text)
+
+        # IS
+        elif isinstance(expr, expression.isExpression):
+            partial = expr.field == expr.text
+
+        # IS NOT
+        elif isinstance(expr, expression.isnotExpression):
+            partial = expr.field != expr.text
+
+        # STARTSWITH
+        elif isinstance(expr, expression.startswithExpression):
+            partial = expr.field.StartsWith(expr.text)
+
+        # NOT STARTSWITH
+        elif isinstance(expr, expression.notstartswithExpression):
+            partial = expr.field.NotStartsWith(expr.text)
+
+        # ENDSWITH
+        elif isinstance(expr, expression.endswithExpression):
+            partial = expr.field.EndsWith(expr.text)
+
+        # NOT ENDSWITH
+        elif isinstance(expr, expression.notendswithExpression):
+            partial = expr.field.NotEndsWith(expr.text)
+
+        # IN
+        elif isinstance(expr, expression.inExpression):
+            argname = self.addArgument(expr.text)
+            partial = expr.field.In(Parameter(argname, len(expr.text)))
+
+        # NOT IN
+        elif isinstance(expr, expression.notinExpression):
+            argname = self.addArgument(expr.text)
+            partial = expr.field.NotIn(Parameter(argname, len(expr.text)))
+
+        return partial
+
+
+    def addArgument(self, arg):
+        &quot;&quot;&quot;
+
+        @param arg: the C{str} of the argument to add
+        &quot;&quot;&quot;
+
+        # Append argument to the list and add the appropriate substitution string to the output stream.
+        self.argcount += 1
+        argname = &quot;arg{}&quot;.format(self.argcount)
+        self.arguments[argname] = arg
+        return argname
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequerytest__init__py"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/query/test/__init__.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/__init__.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/query/test/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,15 +0,0 @@
</span><del>-##
-# Copyright (c) 2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequerytest__init__pyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytest__init__py"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/query/test/__init__.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/__init__.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/query/test/__init__.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/query/test/__init__.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequerytesttest_expressionpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/query/test/test_expression.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_expression.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/query/test/test_expression.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,167 +0,0 @@
</span><del>-##
-# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from txdav.common.datastore.query import expression
-from twisted.trial.unittest import TestCase
-
-class Tests(TestCase):
-
-    def test_andWith(self):
-
-        tests = (
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
-            ),
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.andExpression((
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                )),
-                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
-            ),
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.andExpression((
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
-                )),
-                &quot;(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))&quot;
-            ),
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.orExpression((
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                )),
-                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
-            ),
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.orExpression((
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
-                )),
-                &quot;(is(A, 1, True) AND (is(B, 2, True) OR is(C, 3, True)))&quot;
-            ),
-            (
-                expression.andExpression((
-                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                )),
-                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
-            ),
-            (
-                expression.andExpression((
-                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                )),
-                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
-                &quot;(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))&quot;
-            ),
-            (
-                expression.orExpression((
-                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                )),
-                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
-            ),
-            (
-                expression.orExpression((
-                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                )),
-                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
-                &quot;((is(A, 1, True) OR is(B, 2, True)) AND is(C, 3, True))&quot;
-            ),
-        )
-
-        for expr1, expr2, result in tests:
-            self.assertEqual(str(expr1.andWith(expr2)), result, msg=&quot;Failed on %s&quot; % (result,))
-
-
-    def test_orWith(self):
-
-        tests = (
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
-            ),
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.andExpression((
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                )),
-                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
-            ),
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.andExpression((
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
-                )),
-                &quot;(is(A, 1, True) OR (is(B, 2, True) AND is(C, 3, True)))&quot;
-            ),
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.orExpression((
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                )),
-                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
-            ),
-            (
-                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                expression.orExpression((
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
-                )),
-                &quot;(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))&quot;
-            ),
-            (
-                expression.andExpression((
-                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                )),
-                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
-            ),
-            (
-                expression.andExpression((
-                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                )),
-                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
-                &quot;((is(A, 1, True) AND is(B, 2, True)) OR is(C, 3, True))&quot;
-            ),
-            (
-                expression.orExpression((
-                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                )),
-                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
-            ),
-            (
-                expression.orExpression((
-                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
-                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
-                )),
-                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
-                &quot;(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))&quot;
-            ),
-        )
-
-        for expr1, expr2, result in tests:
-            self.assertEqual(str(expr1.orWith(expr2)), result, msg=&quot;Failed on %s&quot; % (result,))
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequerytesttest_expressionpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytesttest_expressionpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/query/test/test_expression.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_expression.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/query/test/test_expression.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/query/test/test_expression.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,167 @@
</span><ins>+##
+# Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from txdav.common.datastore.query import expression
+from twisted.trial.unittest import TestCase
+
+class Tests(TestCase):
+
+    def test_andWith(self):
+
+        tests = (
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.andExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.andExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                )),
+                &quot;(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.orExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.orExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                )),
+                &quot;(is(A, 1, True) AND (is(B, 2, True) OR is(C, 3, True)))&quot;
+            ),
+            (
+                expression.andExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                )),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.andExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                &quot;(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))&quot;
+            ),
+            (
+                expression.orExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                )),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) AND is(B, 2, True))&quot;
+            ),
+            (
+                expression.orExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                &quot;((is(A, 1, True) OR is(B, 2, True)) AND is(C, 3, True))&quot;
+            ),
+        )
+
+        for expr1, expr2, result in tests:
+            self.assertEqual(str(expr1.andWith(expr2)), result, msg=&quot;Failed on %s&quot; % (result,))
+
+
+    def test_orWith(self):
+
+        tests = (
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.andExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.andExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                )),
+                &quot;(is(A, 1, True) OR (is(B, 2, True) AND is(C, 3, True)))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.orExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                expression.orExpression((
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                    expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                )),
+                &quot;(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))&quot;
+            ),
+            (
+                expression.andExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                )),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.andExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                &quot;((is(A, 1, True) AND is(B, 2, True)) OR is(C, 3, True))&quot;
+            ),
+            (
+                expression.orExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                )),
+                expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                &quot;(is(A, 1, True) OR is(B, 2, True))&quot;
+            ),
+            (
+                expression.orExpression((
+                    expression.isExpression(&quot;A&quot;, &quot;1&quot;, True),
+                    expression.isExpression(&quot;B&quot;, &quot;2&quot;, True),
+                )),
+                expression.isExpression(&quot;C&quot;, &quot;3&quot;, True),
+                &quot;(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))&quot;
+            ),
+        )
+
+        for expr1, expr2, result in tests:
+            self.assertEqual(str(expr1.orWith(expr2)), result, msg=&quot;Failed on %s&quot; % (result,))
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequerytesttest_generatorpy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/query/test/test_generator.py (12191 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_generator.py        2013-12-23 16:55:28 UTC (rev 12191)
+++ CalendarServer/trunk/txdav/common/datastore/query/test/test_generator.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,127 +0,0 @@
</span><del>-##
-# Copyright (c) 2012-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-from twext.enterprise.dal.syntax import SQLFragment, Parameter
-from txdav.common.datastore.query.generator import SQLQueryGenerator
-from txdav.common.datastore.query import expression
-
-&quot;&quot;&quot;
-Tests for L{txdav.common.datastore.sql}.
-&quot;&quot;&quot;
-
-from twisted.trial.unittest import TestCase
-
-from txdav.common.datastore.sql_tables import schema
-
-class SQLQueryGeneratorTests(TestCase):
-    &quot;&quot;&quot;
-    Tests for shared functionality in L{txdav.common.datastore.sql}.
-    &quot;&quot;&quot;
-
-    class FakeHomeChild(object):
-        _objectSchema = schema.CALENDAR_OBJECT
-
-        def id(self):
-            return 1234
-
-
-    def test_all_query(self):
-
-        expr = expression.allExpression()
-        resource = self.FakeHomeChild()
-        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
-        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ?&quot;, [1234]))
-        self.assertEqual(args, {})
-
-
-    def test_uid_query(self):
-
-        resource = self.FakeHomeChild()
-        obj = resource._objectSchema
-        expr = expression.isExpression(obj.UID, 5678, False)
-        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
-        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_UID = ?&quot;, [1234, 5678]))
-        self.assertEqual(args, {})
-
-
-    def test_or_query(self):
-
-        resource = self.FakeHomeChild()
-        obj = resource._objectSchema
-        expr = expression.orExpression((
-            expression.isExpression(obj.UID, 5678, False),
-            expression.isnotExpression(obj.RESOURCE_NAME, &quot;foobar.ics&quot;, False),
-        ))
-        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
-        self.assertEqual(
-            select.toSQL(),
-            SQLFragment(
-                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and (ICALENDAR_UID = ? or RESOURCE_NAME != ?)&quot;,
-                [1234, 5678, &quot;foobar.ics&quot;]
-            )
-        )
-        self.assertEqual(args, {})
-
-
-    def test_and_query(self):
-
-        resource = self.FakeHomeChild()
-        obj = resource._objectSchema
-        expr = expression.andExpression((
-            expression.isExpression(obj.UID, 5678, False),
-            expression.isnotExpression(obj.RESOURCE_NAME, &quot;foobar.ics&quot;, False),
-        ))
-        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
-        self.assertEqual(
-            select.toSQL(),
-            SQLFragment(
-                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_UID = ? and RESOURCE_NAME != ?&quot;,
-                [1234, 5678, &quot;foobar.ics&quot;]
-            )
-        )
-        self.assertEqual(args, {})
-
-
-    def test_not_query(self):
-
-        resource = self.FakeHomeChild()
-        obj = resource._objectSchema
-        expr = expression.notExpression(expression.isExpression(obj.UID, 5678, False))
-        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
-        self.assertEqual(
-            select.toSQL(),
-            SQLFragment(
-                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and not ICALENDAR_UID = ?&quot;,
-                [1234, 5678]
-            )
-        )
-        self.assertEqual(args, {})
-
-
-    def test_in_query(self):
-
-        resource = self.FakeHomeChild()
-        obj = resource._objectSchema
-        expr = expression.inExpression(obj.RESOURCE_NAME, [&quot;1.ics&quot;, &quot;2.ics&quot;, &quot;3.ics&quot;], False)
-        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
-        self.assertEqual(
-            select.toSQL(),
-            SQLFragment(
-                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and RESOURCE_NAME in (?, ?, ?)&quot;,
-                [1234, Parameter('arg1', 3)]
-            )
-        )
-        self.assertEqual(args, {&quot;arg1&quot;: [&quot;1.ics&quot;, &quot;2.ics&quot;, &quot;3.ics&quot;]})
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastorequerytesttest_generatorpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastorequerytesttest_generatorpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/query/test/test_generator.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/query/test/test_generator.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/query/test/test_generator.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/query/test/test_generator.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,127 @@
</span><ins>+##
+# Copyright (c) 2012-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twext.enterprise.dal.syntax import SQLFragment, Parameter
+from txdav.common.datastore.query.generator import SQLQueryGenerator
+from txdav.common.datastore.query import expression
+
+&quot;&quot;&quot;
+Tests for L{txdav.common.datastore.sql}.
+&quot;&quot;&quot;
+
+from twisted.trial.unittest import TestCase
+
+from txdav.common.datastore.sql_tables import schema
+
+class SQLQueryGeneratorTests(TestCase):
+    &quot;&quot;&quot;
+    Tests for shared functionality in L{txdav.common.datastore.sql}.
+    &quot;&quot;&quot;
+
+    class FakeHomeChild(object):
+        _objectSchema = schema.CALENDAR_OBJECT
+
+        def id(self):
+            return 1234
+
+
+    def test_all_query(self):
+
+        expr = expression.allExpression()
+        resource = self.FakeHomeChild()
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ?&quot;, [1234]))
+        self.assertEqual(args, {})
+
+
+    def test_uid_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.isExpression(obj.UID, 5678, False)
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(select.toSQL(), SQLFragment(&quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_UID = ?&quot;, [1234, 5678]))
+        self.assertEqual(args, {})
+
+
+    def test_or_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.orExpression((
+            expression.isExpression(obj.UID, 5678, False),
+            expression.isnotExpression(obj.RESOURCE_NAME, &quot;foobar.ics&quot;, False),
+        ))
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(
+            select.toSQL(),
+            SQLFragment(
+                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and (ICALENDAR_UID = ? or RESOURCE_NAME != ?)&quot;,
+                [1234, 5678, &quot;foobar.ics&quot;]
+            )
+        )
+        self.assertEqual(args, {})
+
+
+    def test_and_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.andExpression((
+            expression.isExpression(obj.UID, 5678, False),
+            expression.isnotExpression(obj.RESOURCE_NAME, &quot;foobar.ics&quot;, False),
+        ))
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(
+            select.toSQL(),
+            SQLFragment(
+                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and ICALENDAR_UID = ? and RESOURCE_NAME != ?&quot;,
+                [1234, 5678, &quot;foobar.ics&quot;]
+            )
+        )
+        self.assertEqual(args, {})
+
+
+    def test_not_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.notExpression(expression.isExpression(obj.UID, 5678, False))
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(
+            select.toSQL(),
+            SQLFragment(
+                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and not ICALENDAR_UID = ?&quot;,
+                [1234, 5678]
+            )
+        )
+        self.assertEqual(args, {})
+
+
+    def test_in_query(self):
+
+        resource = self.FakeHomeChild()
+        obj = resource._objectSchema
+        expr = expression.inExpression(obj.RESOURCE_NAME, [&quot;1.ics&quot;, &quot;2.ics&quot;, &quot;3.ics&quot;], False)
+        select, args = SQLQueryGenerator(expr, resource, resource.id()).generate()
+        self.assertEqual(
+            select.toSQL(),
+            SQLFragment(
+                &quot;select distinct RESOURCE_NAME, ICALENDAR_UID from CALENDAR_OBJECT where CALENDAR_RESOURCE_ID = ? and RESOURCE_NAME in (?, ?, ?)&quot;,
+                [1234, Parameter('arg1', 3)]
+            )
+        )
+        self.assertEqual(args, {&quot;arg1&quot;: [&quot;1.ics&quot;, &quot;2.ics&quot;, &quot;3.ics&quot;]})
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -14,9 +14,6 @@
</span><span class="cx"> # See the License for the specific language governing permissions and
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><del>-from collections import namedtuple
-from txdav.xml import element
-from txdav.base.propertystore.base import PropertyName
</del><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> SQL data store.
</span><span class="lines">@@ -57,32 +54,40 @@
</span><span class="cx"> 
</span><span class="cx"> from txdav.base.datastore.util import QueryCacher
</span><span class="cx"> from txdav.base.datastore.util import normalizeUUIDOrNot
</span><ins>+from txdav.base.propertystore.base import PropertyName
</ins><span class="cx"> from txdav.base.propertystore.none import PropertyStore as NonePropertyStore
</span><span class="cx"> from txdav.base.propertystore.sql import PropertyStore
</span><span class="cx"> from txdav.caldav.icalendarstore import ICalendarTransaction, ICalendarStore
</span><span class="cx"> from txdav.carddav.iaddressbookstore import IAddressBookTransaction
</span><span class="cx"> from txdav.common.datastore.common import HomeChildBase
</span><ins>+from txdav.common.datastore.podding.conduit import PoddingConduit
</ins><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_OWN, \
</span><span class="cx">     _BIND_STATUS_ACCEPTED, _BIND_STATUS_DECLINED, _BIND_STATUS_INVALID, \
</span><span class="cx">     _BIND_STATUS_INVITED, _BIND_MODE_DIRECT, _BIND_STATUS_DELETED, \
</span><del>-    _BIND_MODE_INDIRECT
</del><ins>+    _BIND_MODE_INDIRECT, _HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL
</ins><span class="cx"> from txdav.common.datastore.sql_tables import schema, splitSQLString
</span><del>-from txdav.common.icommondatastore import ConcurrentModification
</del><ins>+from txdav.common.icommondatastore import ConcurrentModification, \
+    RecordNotAllowedError, ExternalShareFailed, ShareNotAllowed, \
+    IndexedSearchException
</ins><span class="cx"> from txdav.common.icommondatastore import HomeChildNameNotAllowedError, \
</span><span class="cx">     HomeChildNameAlreadyExistsError, NoSuchHomeChildError, \
</span><span class="cx">     ObjectResourceNameNotAllowedError, ObjectResourceNameAlreadyExistsError, \
</span><span class="cx">     NoSuchObjectResourceError, AllRetriesFailed, InvalidSubscriptionValues, \
</span><span class="cx">     InvalidIMIPTokenValues, TooManyObjectResourcesError, \
</span><span class="cx">     SyncTokenValidException
</span><del>-from txdav.common.idirectoryservice import IStoreDirectoryService
</del><ins>+from txdav.common.idirectoryservice import IStoreDirectoryService, \
+    DirectoryRecordNotFoundError
</ins><span class="cx"> from txdav.common.inotifications import INotificationCollection, \
</span><span class="cx">     INotificationObject
</span><span class="cx"> from txdav.idav import ChangeCategory
</span><ins>+from txdav.xml import element
</ins><span class="cx"> 
</span><span class="cx"> from uuid import uuid4, UUID
</span><span class="cx"> 
</span><span class="cx"> from zope.interface import implements, directlyProvides
</span><span class="cx"> 
</span><ins>+from collections import namedtuple
+import itertools
</ins><span class="cx"> import json
</span><span class="cx"> import sys
</span><span class="cx"> import time
</span><span class="lines">@@ -189,6 +194,8 @@
</span><span class="cx">         else:
</span><span class="cx">             self.queryCacher = None
</span><span class="cx"> 
</span><ins>+        self.conduit = PoddingConduit(self)
+
</ins><span class="cx">         # Always import these here to trigger proper &quot;registration&quot; of the calendar and address book
</span><span class="cx">         # home classes
</span><span class="cx">         __import__(&quot;txdav.caldav.datastore.sql&quot;)
</span><span class="lines">@@ -210,16 +217,18 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _withEachHomeDo(self, homeTable, homeFromTxn, action, batchSize): #@UnusedVariable
</del><ins>+    def _withEachHomeDo(self, homeTable, homeFromTxn, action, batchSize, processExternal=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Implementation of L{ICalendarStore.withEachCalendarHomeDo} and
</span><span class="cx">         L{IAddressbookStore.withEachAddressbookHomeDo}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         txn = yield self.newTransaction()
</span><span class="cx">         try:
</span><del>-            allUIDs = yield (Select([homeTable.OWNER_UID], From=homeTable)
-                             .on(txn))
</del><ins>+            allUIDs = yield (Select([homeTable.OWNER_UID], From=homeTable).on(txn))
</ins><span class="cx">             for [uid] in allUIDs:
</span><ins>+                home = yield homeFromTxn(txn, uid)
+                if not processExternal and home.external():
+                    continue
</ins><span class="cx">                 yield action(txn, (yield homeFromTxn(txn, uid)))
</span><span class="cx">         except:
</span><span class="cx">             a, b, c = sys.exc_info()
</span><span class="lines">@@ -229,25 +238,25 @@
</span><span class="cx">             yield txn.commit()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def withEachCalendarHomeDo(self, action, batchSize=None):
</del><ins>+    def withEachCalendarHomeDo(self, action, batchSize=None, processExternal=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Implementation of L{ICalendarStore.withEachCalendarHomeDo}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self._withEachHomeDo(
</span><span class="cx">             schema.CALENDAR_HOME,
</span><span class="cx">             lambda txn, uid: txn.calendarHomeWithUID(uid),
</span><del>-            action, batchSize
</del><ins>+            action, batchSize, processExternal
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def withEachAddressbookHomeDo(self, action, batchSize=None):
</del><ins>+    def withEachAddressbookHomeDo(self, action, batchSize=None, processExternal=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Implementation of L{IAddressbookStore.withEachAddressbookHomeDo}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return self._withEachHomeDo(
</span><span class="cx">             schema.ADDRESSBOOK_HOME,
</span><span class="cx">             lambda txn, uid: txn.addressbookHomeWithUID(uid),
</span><del>-            action, batchSize
</del><ins>+            action, batchSize, processExternal
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -473,7 +482,7 @@
</span><span class="cx">         self._label = label
</span><span class="cx">         self._migrating = migrating
</span><span class="cx">         self._primaryHomeType = None
</span><del>-        self._disableCache = disableCache
</del><ins>+        self._disableCache = disableCache or not store.queryCachingEnabled()
</ins><span class="cx">         if disableCache:
</span><span class="cx">             self._queryCacher = None
</span><span class="cx">         else:
</span><span class="lines">@@ -1467,10 +1476,108 @@
</span><span class="cx">         if shareeView is not None:
</span><span class="cx">             yield shareeView.declineShare()
</span><span class="cx"> 
</span><del>-        returnValue(shareeView)
</del><ins>+        returnValue(shareeView is not None)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    #
+    # External (cross-pod) sharing - entry point is the sharee's home collection.
+    #
+    @inlineCallbacks
+    def processExternalInvite(self, ownerUID, ownerRID, ownerName, shareUID, bindMode, summary, copy_invite_properties, supported_components=None):
+        &quot;&quot;&quot;
+        External invite received.
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+        # Get the owner home - create external one if not present
+        ownerHome = yield self._txn.homeWithUID(self._homeType, ownerUID, create=True)
+        if ownerHome is None or not ownerHome.external():
+            raise ExternalShareFailed(&quot;Invalid owner UID: {}&quot;.format(ownerUID))
+
+        # Try to find owner calendar via its external id
+        ownerView = yield ownerHome.childWithExternalID(ownerRID)
+        if ownerView is None:
+            try:
+                ownerView = yield ownerHome.createChildWithName(ownerName, externalID=ownerRID)
+            except HomeChildNameAlreadyExistsError:
+                # This is odd - it means we possibly have a left over sharer collection which the sharer likely removed
+                # and re-created with the same name but now it has a different externalID and is not found by the initial
+                # query. What we do is check to see whether any shares still reference the old ID - if they do we are hosed.
+                # If not, we can remove the old item and create a new one.
+                oldOwnerView = yield ownerHome.childWithName(ownerName)
+                invites = yield oldOwnerView.sharingInvites()
+                if len(invites) != 0:
+                    log.error(&quot;External invite collection name is present with a different externalID and still has shares&quot;)
+                    raise
+                log.error(&quot;External invite collection name is present with a different externalID - trying to fix&quot;)
+                yield ownerHome.removeExternalChild(oldOwnerView)
+                ownerView = yield ownerHome.createChildWithName(ownerName, externalID=ownerRID)
+
+            if supported_components is not None and hasattr(ownerView, &quot;setSupportedComponents&quot;):
+                yield ownerView.setSupportedComponents(supported_components)
+
+        # Now carry out the share operation
+        if bindMode == _BIND_MODE_DIRECT:
+            shareeView = yield ownerView.directShareWithUser(self.uid(), shareName=shareUID)
+        else:
+            shareeView = yield ownerView.inviteUserToShare(self.uid(), bindMode, summary, shareName=shareUID)
+
+        shareeView.setInviteCopyProperties(copy_invite_properties)
+
+
+    @inlineCallbacks
+    def processExternalUninvite(self, ownerUID, ownerRID, shareUID):
+        &quot;&quot;&quot;
+        External invite received.
+        &quot;&quot;&quot;
+
+        # Get the owner home
+        ownerHome = yield self._txn.homeWithUID(self._homeType, ownerUID)
+        if ownerHome is None or not ownerHome.external():
+            raise ExternalShareFailed(&quot;Invalid owner UID: {}&quot;.format(ownerUID))
+
+        # Try to find owner calendar via its external id
+        ownerView = yield ownerHome.childWithExternalID(ownerRID)
+        if ownerView is None:
+            raise ExternalShareFailed(&quot;Invalid share ID: {}&quot;.format(shareUID))
+
+        # Now carry out the share operation
+        yield ownerView.uninviteUserFromShare(self.uid())
+
+        # See if there are any references to the external share - if not remove it
+        invites = yield ownerView.sharingInvites()
+        if len(invites) == 0:
+            yield ownerHome.removeExternalChild(ownerView)
+
+
+    @inlineCallbacks
+    def processExternalReply(self, ownerUID, shareeUID, shareUID, bindStatus, summary=None):
+        &quot;&quot;&quot;
+        External invite received.
+        &quot;&quot;&quot;
+
+        # Make sure the shareeUID and shareUID match
+
+        # Get the owner home - create external one if not present
+        shareeHome = yield self._txn.homeWithUID(self._homeType, shareeUID)
+        if shareeHome is None or not shareeHome.external():
+            raise ExternalShareFailed(&quot;Invalid sharee UID: {}&quot;.format(shareeUID))
+
+        # Try to find owner calendar via its external id
+        shareeView = yield shareeHome.anyObjectWithShareUID(shareUID)
+        if shareeView is None:
+            raise ExternalShareFailed(&quot;Invalid share UID: {}&quot;.format(shareUID))
+
+        # Now carry out the share operation
+        if bindStatus == _BIND_STATUS_ACCEPTED:
+            yield shareeHome.acceptShare(shareUID, summary)
+        elif bindStatus == _BIND_STATUS_DECLINED:
+            if shareeView.direct():
+                yield shareeView.deleteShare()
+            else:
+                yield shareeHome.declineShare(shareUID)
+
+
+
</ins><span class="cx"> class CommonHome(SharingHomeMixIn):
</span><span class="cx">     log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -1478,6 +1585,7 @@
</span><span class="cx">     _homeType = None
</span><span class="cx">     _homeTable = None
</span><span class="cx">     _homeMetaDataTable = None
</span><ins>+    _externalClass = None
</ins><span class="cx">     _childClass = None
</span><span class="cx">     _childTable = None
</span><span class="cx">     _notifierPrefix = None
</span><span class="lines">@@ -1487,10 +1595,30 @@
</span><span class="cx"> 
</span><span class="cx">     _cacher = None  # Initialize in derived classes
</span><span class="cx"> 
</span><ins>+    @classmethod
+    @inlineCallbacks
+    def makeClass(cls, transaction, ownerUID, no_cache=False):
+        &quot;&quot;&quot;
+        Build the actual home class taking into account the possibility that we might need to
+        switch in the external version of the class.
+
+        @param transaction: transaction
+        @type transaction: L{CommonStoreTransaction}
+        @param ownerUID: owner UID of home to load
+        @type ownerUID: C{str}
+        @param no_cache: should cached query be used
+        @type no_cache: C{bool}
+        &quot;&quot;&quot;
+        home = cls(transaction, ownerUID)
+        actualHome = yield home.initFromStore(no_cache)
+        returnValue(actualHome)
+
+
</ins><span class="cx">     def __init__(self, transaction, ownerUID):
</span><span class="cx">         self._txn = transaction
</span><span class="cx">         self._ownerUID = ownerUID
</span><span class="cx">         self._resourceID = None
</span><ins>+        self._status = _HOME_STATUS_NORMAL
</ins><span class="cx">         self._dataVersion = None
</span><span class="cx">         self._childrenLoaded = False
</span><span class="cx">         self._children = {}
</span><span class="lines">@@ -1554,6 +1682,7 @@
</span><span class="cx">         return (
</span><span class="cx">             cls._homeSchema.RESOURCE_ID,
</span><span class="cx">             cls._homeSchema.OWNER_UID,
</span><ins>+            cls._homeSchema.STATUS,
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1568,6 +1697,7 @@
</span><span class="cx">         return (
</span><span class="cx">             &quot;_resourceID&quot;,
</span><span class="cx">             &quot;_ownerUID&quot;,
</span><ins>+            &quot;_status&quot;,
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1612,39 +1742,57 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         result = yield self._cacher.get(self._ownerUID)
</span><span class="cx">         if result is None:
</span><del>-            result = yield self._homeColumnsFromOwnerQuery.on(
-                self._txn, ownerUID=self._ownerUID)
-            if result and not no_cache:
-                yield self._cacher.set(self._ownerUID, result)
</del><ins>+            result = yield self._homeColumnsFromOwnerQuery.on(self._txn, ownerUID=self._ownerUID)
+            if result:
+                result = result[0]
+                if not no_cache:
+                    yield self._cacher.set(self._ownerUID, result)
</ins><span class="cx"> 
</span><span class="cx">         if result:
</span><del>-            for attr, value in zip(self.homeAttributes(), result[0]):
</del><ins>+            for attr, value in zip(self.homeAttributes(), result):
</ins><span class="cx">                 setattr(self, attr, value)
</span><span class="cx"> 
</span><del>-            queryCacher = self._txn._queryCacher
-            if queryCacher:
-                # Get cached copy
-                cacheKey = queryCacher.keyForHomeMetaData(self._resourceID)
-                data = yield queryCacher.get(cacheKey)
</del><ins>+            # STOP! If the status is external we need to convert this object to a CommonHomeExternal class which will
+            # have the right behavior for non-hosted external users.
+            if self._status == _HOME_STATUS_EXTERNAL:
+                actualHome = self._externalClass(self._txn, self._ownerUID, self._resourceID)
</ins><span class="cx">             else:
</span><del>-                data = None
-            if data is None:
-                # Don't have a cached copy
-                data = (yield self._metaDataQuery.on(
-                    self._txn, resourceID=self._resourceID))[0]
-                if queryCacher:
-                    # Cache the data
-                    yield queryCacher.setAfterCommit(self._txn, cacheKey, data)
</del><ins>+                actualHome = self
+            yield actualHome.initMetaDataFromStore()
+            yield actualHome._loadPropertyStore()
</ins><span class="cx"> 
</span><del>-            for attr, value in zip(self.metadataAttributes(), data):
-                setattr(self, attr, value)
</del><ins>+            for factory_type, factory in self._txn._notifierFactories.items():
+                actualHome.addNotifier(factory_type, factory.newNotifier(actualHome))
</ins><span class="cx"> 
</span><del>-            yield self._loadPropertyStore()
-            returnValue(self)
</del><ins>+            returnValue(actualHome)
</ins><span class="cx">         else:
</span><span class="cx">             returnValue(None)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def initMetaDataFromStore(self):
+        &quot;&quot;&quot;
+        Load up the metadata and property store
+        &quot;&quot;&quot;
+
+        queryCacher = self._txn._queryCacher
+        if queryCacher:
+            # Get cached copy
+            cacheKey = queryCacher.keyForHomeMetaData(self._resourceID)
+            data = yield queryCacher.get(cacheKey)
+        else:
+            data = None
+        if data is None:
+            # Don't have a cached copy
+            data = (yield self._metaDataQuery.on(self._txn, resourceID=self._resourceID))[0]
+            if queryCacher:
+                # Cache the data
+                yield queryCacher.setAfterCommit(self._txn, cacheKey, data)
+
+        for attr, value in zip(self.metadataAttributes(), data):
+            setattr(self, attr, value)
+
+
</ins><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def listHomes(cls, txn):
</span><span class="lines">@@ -1664,16 +1812,20 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def homeWithUID(cls, txn, uid, create=False):
</span><del>-        homeObject = cls(txn, uid)
-        for factory_type, factory in txn._notifierFactories.items():
-            homeObject.addNotifier(factory_type, factory.newNotifier(homeObject))
-        homeObject = (yield homeObject.initFromStore())
</del><ins>+        homeObject = yield cls.makeClass(txn, uid)
</ins><span class="cx">         if homeObject is not None:
</span><span class="cx">             returnValue(homeObject)
</span><span class="cx">         else:
</span><span class="cx">             if not create:
</span><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><ins>+            # Determine if the user is local or external
+            record = txn.directoryService().recordWithUID(uid)
+            if record is None:
+                raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {}&quot;.format(uid))
+
+            state = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
+
</ins><span class="cx">             # Use savepoint so we can do a partial rollback if there is a race condition
</span><span class="cx">             # where this row has already been inserted
</span><span class="cx">             savepoint = SavepointAction(&quot;homeWithUID&quot;)
</span><span class="lines">@@ -1685,19 +1837,17 @@
</span><span class="cx">                 resourceid = (yield Insert(
</span><span class="cx">                     {
</span><span class="cx">                         cls._homeSchema.OWNER_UID: uid,
</span><ins>+                        cls._homeSchema.STATUS: state,
</ins><span class="cx">                         cls._homeSchema.DATAVERSION: cls._dataVersionValue,
</span><span class="cx">                     },
</span><del>-                    Return=cls._homeSchema.RESOURCE_ID).on(txn))[0][0]
-                yield Insert(
-                    {cls._homeMetaDataSchema.RESOURCE_ID: resourceid}).on(txn)
</del><ins>+                    Return=cls._homeSchema.RESOURCE_ID
+                ).on(txn))[0][0]
+                yield Insert({cls._homeMetaDataSchema.RESOURCE_ID: resourceid}).on(txn)
</ins><span class="cx">             except Exception:  # FIXME: Really want to trap the pg.DatabaseError but in a non-DB specific manner
</span><span class="cx">                 yield savepoint.rollback(txn)
</span><span class="cx"> 
</span><span class="cx">                 # Retry the query - row may exist now, if not re-raise
</span><del>-                homeObject = cls(txn, uid)
-                for factory_type, factory in txn._notifierFactories.items():
-                    homeObject.addNotifier(factory_type, factory.newNotifier(homeObject))
-                homeObject = (yield homeObject.initFromStore())
</del><ins>+                homeObject = yield cls.makeClass(txn, uid)
</ins><span class="cx">                 if homeObject:
</span><span class="cx">                     returnValue(homeObject)
</span><span class="cx">                 else:
</span><span class="lines">@@ -1708,10 +1858,7 @@
</span><span class="cx">                 # Note that we must not cache the owner_uid-&gt;resource_id
</span><span class="cx">                 # mapping in _cacher when creating as we don't want that to appear
</span><span class="cx">                 # until AFTER the commit
</span><del>-                home = cls(txn, uid)
-                for factory_type, factory in txn._notifierFactories.items():
-                    home.addNotifier(factory_type, factory.newNotifier(home))
-                home = (yield home.initFromStore(no_cache=True))
</del><ins>+                home = yield cls.makeClass(txn, uid, no_cache=True)
</ins><span class="cx">                 yield home.createdHome()
</span><span class="cx">                 returnValue(home)
</span><span class="cx"> 
</span><span class="lines">@@ -1727,7 +1874,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def __repr__(self):
</span><del>-        return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self._resourceID)
</del><ins>+        return &quot;&lt;%s: %s, %s&gt;&quot; % (self.__class__.__name__, self._resourceID, self._ownerUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def id(self):
</span><span class="lines">@@ -1749,6 +1896,15 @@
</span><span class="cx">         return self._ownerUID
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def external(self):
+        &quot;&quot;&quot;
+        Is this an external home.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return False
+
+
</ins><span class="cx">     def transaction(self):
</span><span class="cx">         return self._txn
</span><span class="cx"> 
</span><span class="lines">@@ -1866,6 +2022,17 @@
</span><span class="cx">         return self._childClass.objectWithID(self, resourceID)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def childWithExternalID(self, externalID):
+        &quot;&quot;&quot;
+        Retrieve the child with the given C{externalID} contained in this
+        home.
+
+        @param name: a string.
+        @return: an L{ICalendar} or C{None} if no such child exists.
+        &quot;&quot;&quot;
+        return self._childClass.objectWithExternalID(self, externalID)
+
+
</ins><span class="cx">     def allChildWithID(self, resourceID):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Retrieve the child with the given C{resourceID} contained in this
</span><span class="lines">@@ -1878,12 +2045,11 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def createChildWithName(self, name):
</del><ins>+    def createChildWithName(self, name, externalID=None):
</ins><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise HomeChildNameNotAllowedError(name)
</span><span class="cx"> 
</span><del>-        yield self._childClass.create(self, name)
-        child = (yield self.childWithName(name))
</del><ins>+        child = yield self._childClass.create(self, name, externalID=externalID)
</ins><span class="cx">         returnValue(child)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -2025,6 +2191,10 @@
</span><span class="cx">         record a revision for the sharee home and sharee collection name with the &quot;deleted&quot; flag set. That way
</span><span class="cx">         the shared collection can be reported as removed.
</span><span class="cx"> 
</span><ins>+        For external shared collections we need to report them as invalid as we cannot aggregate the sync token
+        for this home with the sync token from the external share which is under the control of the other pod.
+        Reporting it as invalid means that clients should do requests directly on the share itself to sync it.
+
</ins><span class="cx">         @param revision: the sync revision to compare to
</span><span class="cx">         @type revision: C{str}
</span><span class="cx">         @param depth: depth for determine what changed
</span><span class="lines">@@ -2043,6 +2213,7 @@
</span><span class="cx"> 
</span><span class="cx">         changed = set()
</span><span class="cx">         deleted = set()
</span><ins>+        invalid = set()
</ins><span class="cx">         deleted_collections = set()
</span><span class="cx">         for path, name, wasdeleted in results:
</span><span class="cx">             if wasdeleted:
</span><span class="lines">@@ -2071,13 +2242,17 @@
</span><span class="cx">         # TODO: think about whether this can be done in one query rather than looping over each share
</span><span class="cx">         for share in (yield self.children()):
</span><span class="cx">             if not share.owned():
</span><del>-                sharedChanged, sharedDeleted = yield share.sharedChildResourceNamesSinceRevision(revision, depth)
</del><ins>+                sharedChanged, sharedDeleted, sharedInvalid = yield share.sharedChildResourceNamesSinceRevision(revision, depth)
</ins><span class="cx">                 changed |= sharedChanged
</span><ins>+                changed -= sharedInvalid
</ins><span class="cx">                 deleted |= sharedDeleted
</span><ins>+                deleted -= sharedInvalid
+                invalid |= sharedInvalid
</ins><span class="cx"> 
</span><span class="cx">         changed = sorted(changed)
</span><span class="cx">         deleted = sorted(deleted)
</span><del>-        returnValue((changed, deleted))
</del><ins>+        invalid = sorted(invalid)
+        returnValue((changed, deleted, invalid,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2400,7 +2575,7 @@
</span><span class="cx">     def revisionFromToken(self, token):
</span><span class="cx">         if token is None:
</span><span class="cx">             return 0
</span><del>-        elif isinstance(token, str):
</del><ins>+        elif isinstance(token, str) or isinstance(token, unicode):
</ins><span class="cx">             _ignore_uuid, revision = token.split(&quot;_&quot;, 1)
</span><span class="cx">             return int(revision)
</span><span class="cx">         else:
</span><span class="lines">@@ -2466,6 +2641,7 @@
</span><span class="cx"> 
</span><span class="cx">         changed = []
</span><span class="cx">         deleted = []
</span><ins>+        invalid = []
</ins><span class="cx">         for name, wasdeleted in results:
</span><span class="cx">             if name:
</span><span class="cx">                 if wasdeleted:
</span><span class="lines">@@ -2474,7 +2650,7 @@
</span><span class="cx">                 else:
</span><span class="cx">                     changed.append(name)
</span><span class="cx"> 
</span><del>-        returnValue((changed, deleted))
</del><ins>+        returnValue((changed, deleted, invalid))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><span class="lines">@@ -2759,6 +2935,7 @@
</span><span class="cx">         return Insert({
</span><span class="cx">             bind.HOME_RESOURCE_ID: Parameter(&quot;homeID&quot;),
</span><span class="cx">             bind.RESOURCE_ID: Parameter(&quot;resourceID&quot;),
</span><ins>+            bind.EXTERNAL_ID: Parameter(&quot;externalID&quot;),
</ins><span class="cx">             bind.RESOURCE_NAME: Parameter(&quot;name&quot;),
</span><span class="cx">             bind.BIND_MODE: Parameter(&quot;mode&quot;),
</span><span class="cx">             bind.BIND_STATUS: Parameter(&quot;bindStatus&quot;),
</span><span class="lines">@@ -2840,11 +3017,21 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         bind = cls._bindSchema
</span><span class="cx">         return cls._bindFor((bind.RESOURCE_ID == Parameter(&quot;resourceID&quot;))
</span><del>-                               .And(bind.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;))
-                               )
</del><ins>+                            .And(bind.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><ins>+    def _bindForExternalIDAndHomeID(cls): #@NoSelf
+        &quot;&quot;&quot;
+        DAL query that looks up home bind rows by home child
+        resource ID and home resource ID.
+        &quot;&quot;&quot;
+        bind = cls._bindSchema
+        return cls._bindFor((bind.EXTERNAL_ID == Parameter(&quot;externalID&quot;))
+                            .And(bind.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)))
+
+
+    @classproperty
</ins><span class="cx">     def _bindForNameAndHomeID(cls): #@NoSelf
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         DAL query that looks up any bind rows by home child
</span><span class="lines">@@ -2852,15 +3039,14 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         bind = cls._bindSchema
</span><span class="cx">         return cls._bindFor((bind.RESOURCE_NAME == Parameter(&quot;name&quot;))
</span><del>-                               .And(bind.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;))
-                               )
</del><ins>+                            .And(bind.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     #
</span><span class="cx">     # Higher level API
</span><span class="cx">     #
</span><span class="cx">     @inlineCallbacks
</span><del>-    def inviteUserToShare(self, shareeUID, mode, summary):
</del><ins>+    def inviteUserToShare(self, shareeUID, mode, summary, shareName=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Invite a user to share this collection - either create the share if it does not exist, or
</span><span class="cx">         update the existing share with new values. Make sure a notification is sent as well.
</span><span class="lines">@@ -2879,15 +3065,19 @@
</span><span class="cx">             status = _BIND_STATUS_INVITED if shareeView.shareStatus() in (_BIND_STATUS_DECLINED, _BIND_STATUS_INVALID) else None
</span><span class="cx">             yield self.updateShare(shareeView, mode=mode, status=status, summary=summary)
</span><span class="cx">         else:
</span><del>-            shareeView = yield self.createShare(shareeUID=shareeUID, mode=mode, summary=summary)
</del><ins>+            shareeView = yield self.createShare(shareeUID=shareeUID, mode=mode, summary=summary, shareName=shareName)
</ins><span class="cx"> 
</span><del>-        # Send invite notification
-        yield self._sendInviteNotification(shareeView)
</del><ins>+        # Check for external
+        if shareeView.viewerHome().external():
+            yield self._sendExternalInvite(shareeView)
+        else:
+            # Send invite notification
+            yield self._sendInviteNotification(shareeView)
</ins><span class="cx">         returnValue(shareeView)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def directShareWithUser(self, shareeUID):
</del><ins>+    def directShareWithUser(self, shareeUID, shareName=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a direct share with the specified user. Note it is currently up to the app layer
</span><span class="cx">         to enforce access control - this is not ideal as we really should have control of that in
</span><span class="lines">@@ -2902,8 +3092,13 @@
</span><span class="cx">         # Ignore if it already exists
</span><span class="cx">         shareeView = yield self.shareeView(shareeUID)
</span><span class="cx">         if shareeView is None:
</span><del>-            shareeView = yield self.createShare(shareeUID=shareeUID, mode=_BIND_MODE_DIRECT)
</del><ins>+            shareeView = yield self.createShare(shareeUID=shareeUID, mode=_BIND_MODE_DIRECT, shareName=shareName)
</ins><span class="cx">             yield shareeView.newShare()
</span><ins>+
+            # Check for external
+            if shareeView.viewerHome().external():
+                yield self._sendExternalInvite(shareeView)
+
</ins><span class="cx">         returnValue(shareeView)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -2919,13 +3114,16 @@
</span><span class="cx"> 
</span><span class="cx">         shareeView = yield self.shareeView(shareeUID)
</span><span class="cx">         if shareeView is not None:
</span><del>-            # If current user state is accepted then we send an invite with the new state, otherwise
-            # we cancel any existing invites for the user
-            if not shareeView.direct():
-                if shareeView.shareStatus() != _BIND_STATUS_ACCEPTED:
-                    yield self._removeInviteNotification(shareeView)
-                else:
-                    yield self._sendInviteNotification(shareeView, notificationState=_BIND_STATUS_DELETED)
</del><ins>+            if shareeView.viewerHome().external():
+                yield self._sendExternalUninvite(shareeView)
+            else:
+                # If current user state is accepted then we send an invite with the new state, otherwise
+                # we cancel any existing invites for the user
+                if not shareeView.direct():
+                    if shareeView.shareStatus() != _BIND_STATUS_ACCEPTED:
+                        yield self._removeInviteNotification(shareeView)
+                    else:
+                        yield self._sendInviteNotification(shareeView, notificationState=_BIND_STATUS_DELETED)
</ins><span class="cx"> 
</span><span class="cx">             # Remove the bind
</span><span class="cx">             yield self.removeShare(shareeView)
</span><span class="lines">@@ -2938,10 +3136,13 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if not self.direct() and self.shareStatus() != _BIND_STATUS_ACCEPTED:
</span><ins>+            if self.external():
+                yield self._replyExternalInvite(_BIND_STATUS_ACCEPTED, summary)
</ins><span class="cx">             ownerView = yield self.ownerView()
</span><span class="cx">             yield ownerView.updateShare(self, status=_BIND_STATUS_ACCEPTED)
</span><span class="cx">             yield self.newShare(displayname=summary)
</span><del>-            yield self._sendReplyNotification(ownerView, summary)
</del><ins>+            if not ownerView.external():
+                yield self._sendReplyNotification(ownerView, summary)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -2951,24 +3152,43 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         if not self.direct() and self.shareStatus() != _BIND_STATUS_DECLINED:
</span><ins>+            if self.external():
+                yield self._replyExternalInvite(_BIND_STATUS_DECLINED)
</ins><span class="cx">             ownerView = yield self.ownerView()
</span><span class="cx">             yield ownerView.updateShare(self, status=_BIND_STATUS_DECLINED)
</span><del>-            yield self._sendReplyNotification(ownerView)
</del><ins>+            if not ownerView.external():
+                yield self._sendReplyNotification(ownerView)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def deleteShare(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        This share is being deleted - either decline or remove (for direct shares).
</del><ins>+        This share is being deleted (by the sharee) - either decline or remove (for direct shares).
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         ownerView = yield self.ownerView()
</span><span class="cx">         if self.direct():
</span><span class="cx">             yield ownerView.removeShare(self)
</span><ins>+            if ownerView.external():
+                yield self._replyExternalInvite(_BIND_STATUS_DECLINED)
</ins><span class="cx">         else:
</span><span class="cx">             yield self.declineShare()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def ownerDeleteShare(self):
+        &quot;&quot;&quot;
+        This share is being deleted (by the owner) - either decline or remove (for direct shares).
+        &quot;&quot;&quot;
+
+        # Change status on store object
+        yield self.setShared(False)
+
+        # Remove all sharees (direct and invited)
+        for invitation in (yield self.sharingInvites()):
+            yield self.uninviteUserFromShare(invitation.shareeUID)
+
+
</ins><span class="cx">     def newShare(self, displayname=None):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Override in derived classes to do any specific operations needed when a share
</span><span class="lines">@@ -3068,10 +3288,57 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     #
</span><del>-    # Lower level API
</del><ins>+    # External/cross-pod API
</ins><span class="cx">     #
</span><ins>+    @inlineCallbacks
+    def _sendExternalInvite(self, shareeView):
</ins><span class="cx"> 
</span><ins>+        yield self._txn.store().conduit.send_shareinvite(
+            self._txn,
+            shareeView.ownerHome()._homeType,
+            shareeView.ownerHome().uid(),
+            self.id(),
+            self.shareName(),
+            shareeView.viewerHome().uid(),
+            shareeView.shareUID(),
+            shareeView.shareMode(),
+            shareeView.shareMessage(),
+            self.getInviteCopyProperties(),
+            supported_components=self.getSupportedComponents() if hasattr(self, &quot;getSupportedComponents&quot;) else None,
+        )
+
+
</ins><span class="cx">     @inlineCallbacks
</span><ins>+    def _sendExternalUninvite(self, shareeView):
+
+        yield self._txn.store().conduit.send_shareuninvite(
+            self._txn,
+            shareeView.ownerHome()._homeType,
+            shareeView.ownerHome().uid(),
+            self.id(),
+            shareeView.viewerHome().uid(),
+            shareeView.shareUID(),
+        )
+
+
+    @inlineCallbacks
+    def _replyExternalInvite(self, status, summary=None):
+
+        yield self._txn.store().conduit.send_sharereply(
+            self._txn,
+            self.viewerHome()._homeType,
+            self.ownerHome().uid(),
+            self.viewerHome().uid(),
+            self.shareUID(),
+            status,
+            summary,
+        )
+
+
+    #
+    # Lower level API
+    #
+    @inlineCallbacks
</ins><span class="cx">     def ownerView(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Return the owner resource counterpart of this shared resource.
</span><span class="lines">@@ -3094,7 +3361,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def shareWith(self, shareeHome, mode, status=None, summary=None):
</del><ins>+    def shareWith(self, shareeHome, mode, status=None, summary=None, shareName=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Share this (owned) L{CommonHomeChild} with another home.
</span><span class="cx"> 
</span><span class="lines">@@ -3122,11 +3389,12 @@
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def doInsert(subt):
</span><del>-            newName = self.newShareName()
</del><ins>+            newName = shareName if shareName is not None else self.newShareName()
</ins><span class="cx">             yield self._bindInsertQuery.on(
</span><span class="cx">                 subt,
</span><span class="cx">                 homeID=shareeHome._resourceID,
</span><span class="cx">                 resourceID=self._resourceID,
</span><ins>+                externalID=self._externalID,
</ins><span class="cx">                 name=newName,
</span><span class="cx">                 mode=mode,
</span><span class="cx">                 bindStatus=status,
</span><span class="lines">@@ -3160,7 +3428,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def createShare(self, shareeUID, mode, summary=None):
</del><ins>+    def createShare(self, shareeUID, mode, summary=None, shareName=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a new shared resource. If the mode is direct, the share is created in accepted state,
</span><span class="cx">         otherwise the share is created in invited state.
</span><span class="lines">@@ -3172,6 +3440,7 @@
</span><span class="cx">             mode=mode,
</span><span class="cx">             status=_BIND_STATUS_INVITED if mode != _BIND_MODE_DIRECT else _BIND_STATUS_ACCEPTED,
</span><span class="cx">             summary=summary,
</span><ins>+            shareName=shareName,
</ins><span class="cx">         )
</span><span class="cx">         shareeView = yield self.shareeView(shareeUID)
</span><span class="cx">         returnValue(shareeView)
</span><span class="lines">@@ -3232,12 +3501,7 @@
</span><span class="cx">             if summary is not None:
</span><span class="cx">                 shareeView._bindMessage = columnMap[bind.MESSAGE]
</span><span class="cx"> 
</span><del>-            queryCacher = self._txn._queryCacher
-            if queryCacher:
-                cacheKey = queryCacher.keyForObjectWithName(shareeView._home._resourceID, shareeView._name)
-                yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
-                cacheKey = queryCacher.keyForObjectWithResourceID(shareeView._home._resourceID, shareeView._resourceID)
-                yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
</del><ins>+            yield shareeView.invalidateQueryCache()
</ins><span class="cx"> 
</span><span class="cx">             # Must send notification to ensure cache invalidation occurs
</span><span class="cx">             yield self.notifyPropertyChanged()
</span><span class="lines">@@ -3291,12 +3555,7 @@
</span><span class="cx">             homeID=shareeHome._resourceID,
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        queryCacher = self._txn._queryCacher
-        if queryCacher:
-            cacheKey = queryCacher.keyForObjectWithName(shareeHome._resourceID, shareeView._name)
-            yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
-            cacheKey = queryCacher.keyForObjectWithResourceID(shareeHome._resourceID, shareeView._resourceID)
-            yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
</del><ins>+        yield shareeView.invalidateQueryCache()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -3496,6 +3755,24 @@
</span><span class="cx">         return self._bindMessage
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getInviteCopyProperties(self):
+        &quot;&quot;&quot;
+        Get a dictionary of property name/values (as strings) for properties that are shadowable and
+        need to be copied to a sharee's collection when an external (cross-pod) share is created.
+        Sub-classes should override to expose the properties they care about.
+        &quot;&quot;&quot;
+        return {}
+
+
+    def setInviteCopyProperties(self, props):
+        &quot;&quot;&quot;
+        Copy a set of shadowable properties (as name/value strings) onto this shared resource when
+        a cross-pod invite is processed. Sub-classes should override to expose the properties they
+        care about.
+        &quot;&quot;&quot;
+        pass
+
+
</ins><span class="cx">     @classmethod
</span><span class="cx">     def metadataColumns(cls):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -3540,13 +3817,14 @@
</span><span class="cx">             cls._bindSchema.BIND_MODE,
</span><span class="cx">             cls._bindSchema.HOME_RESOURCE_ID,
</span><span class="cx">             cls._bindSchema.RESOURCE_ID,
</span><ins>+            cls._bindSchema.EXTERNAL_ID,
</ins><span class="cx">             cls._bindSchema.RESOURCE_NAME,
</span><span class="cx">             cls._bindSchema.BIND_STATUS,
</span><span class="cx">             cls._bindSchema.BIND_REVISION,
</span><span class="cx">             cls._bindSchema.MESSAGE
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-    bindColumnCount = 7
</del><ins>+    bindColumnCount = 8
</ins><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def additionalBindColumns(cls):
</span><span class="lines">@@ -3606,6 +3884,7 @@
</span><span class="cx">             yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForHomeChildMetaData(self._resourceID))
</span><span class="cx">             yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithName(self._home._resourceID, self._name))
</span><span class="cx">             yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithResourceID(self._home._resourceID, self._resourceID))
</span><ins>+            yield queryCacher.invalidateAfterCommit(self._txn, queryCacher.keyForObjectWithExternalID(self._home._resourceID, self._externalID))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -3621,6 +3900,7 @@
</span><span class="cx">         &quot;_resourceID&quot;,
</span><span class="cx">     )
</span><span class="cx"> 
</span><ins>+    _externalClass = None
</ins><span class="cx">     _objectResourceClass = None
</span><span class="cx"> 
</span><span class="cx">     _bindSchema = None
</span><span class="lines">@@ -3631,11 +3911,148 @@
</span><span class="cx">     _objectSchema = None
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None):
</del><ins>+    @classmethod
+    @inlineCallbacks
+    def makeClass(cls, home, bindData, additionalBindData, metadataData, propstore=None, ownerHome=None):
+        &quot;&quot;&quot;
+        Given the various database rows, build the actual class.
</ins><span class="cx"> 
</span><ins>+        @param home: the parent home object
+        @type home: L{CommonHome}
+        @param bindData: the standard set of bind columns
+        @type bindData: C{list}
+        @param additionalBindData: additional bind data specific to sub-classes
+        @type additionalBindData: C{list}
+        @param metadataData: metadata data
+        @type metadataData: C{list}
+        @param propstore: a property store to use, or C{None} to load it automatically
+        @type propstore: L{PropertyStore}
+        @param ownerHome: the home of the owner, or C{None} to figure it out automatically
+        @type ownerHome: L{CommonHome}
+
+        @return: the constructed child class
+        @rtype: L{CommonHomeChild}
+        &quot;&quot;&quot;
+
+        bindMode, _ignore_homeID, resourceID, externalID, name, bindStatus, bindRevision, bindMessage = bindData
+
+        if ownerHome is None:
+            if bindMode == _BIND_MODE_OWN:
+                ownerHome = home
+                ownerName = name
+            else:
+                ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
+        else:
+            ownerName = None
+
+        c = cls._externalClass if ownerHome.external() else cls
+        child = c(
+            home=home,
+            name=name,
+            resourceID=resourceID,
+            mode=bindMode,
+            status=bindStatus,
+            revision=bindRevision,
+            message=bindMessage,
+            ownerHome=ownerHome,
+            ownerName=ownerName,
+            externalID=externalID,
+        )
+
+        if additionalBindData:
+            for attr, value in zip(child.additionalBindAttributes(), additionalBindData):
+                setattr(child, attr, value)
+
+        if metadataData:
+            for attr, value in zip(child.metadataAttributes(), metadataData):
+                setattr(child, attr, value)
+
+        # We have to re-adjust the property store object to account for possible shared
+        # collections as previously we loaded them all as if they were owned
+        if propstore and bindMode != _BIND_MODE_OWN:
+            propstore._setDefaultUserUID(ownerHome.uid())
+        yield child._loadPropertyStore(propstore)
+
+        returnValue(child)
+
+
+    @classmethod
+    @inlineCallbacks
+    def _getDBData(cls, home, name, resourceID, externalID):
+        &quot;&quot;&quot;
+        Given a set of identifying information, load the data rows for the object. Only one of
+        L{name}, L{resourceID} or L{externalID} is specified - others are C{None}.
+
+        @param home: the parent home object
+        @type home: L{CommonHome}
+        @param name: the resource name
+        @type name: C{str}
+        @param resourceID: the resource ID
+        @type resourceID: C{int}
+        @param externalID: the resource ID of the external (cross-pod) referenced item
+        @type externalID: C{int}
+        &quot;&quot;&quot;
+
+        # Get the bind row data
+        row = None
+        queryCacher = home._txn._queryCacher
+
+        if queryCacher:
+            # Retrieve data from cache
+            if name:
+                cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
+            elif resourceID:
+                cacheKey = queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID)
+            elif externalID:
+                cacheKey = queryCacher.keyForObjectWithExternalID(home._resourceID, externalID)
+            row = yield queryCacher.get(cacheKey)
+
+        if row is None:
+            # No cached copy
+            if name:
+                rows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
+            elif resourceID:
+                rows = yield cls._bindForResourceIDAndHomeID.on(home._txn, resourceID=resourceID, homeID=home._resourceID)
+            elif externalID:
+                rows = yield cls._bindForExternalIDAndHomeID.on(home._txn, externalID=externalID, homeID=home._resourceID)
+            row = rows[0] if rows else None
+
+        if not row:
+            returnValue(None)
+
+        if queryCacher:
+            # Cache the result
+            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithName(home._resourceID, name), row)
+            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID), row)
+            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithExternalID(home._resourceID, externalID), row)
+
+        bindData = row[:cls.bindColumnCount]
+        additionalBindData = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+        resourceID = bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)]
+
+        # Get the matching metadata data
+        metadataData = None
+        if queryCacher:
+            # Retrieve from cache
+            cacheKey = queryCacher.keyForHomeChildMetaData(resourceID)
+            metadataData = yield queryCacher.get(cacheKey)
+
+        if metadataData is None:
+            # No cached copy
+            metadataData = (yield cls._metadataByIDQuery.on(home._txn, resourceID=resourceID))[0]
+            if queryCacher:
+                # Cache the results
+                yield queryCacher.setAfterCommit(home._txn, cacheKey, metadataData)
+
+        returnValue((bindData, additionalBindData, metadataData,))
+
+
+    def __init__(self, home, name, resourceID, mode, status, revision=0, message=None, ownerHome=None, ownerName=None, externalID=None):
+
</ins><span class="cx">         self._home = home
</span><span class="cx">         self._name = name
</span><span class="cx">         self._resourceID = resourceID
</span><ins>+        self._externalID = externalID
</ins><span class="cx">         self._bindMode = mode
</span><span class="cx">         self._bindStatus = status
</span><span class="cx">         self._bindRevision = revision
</span><span class="lines">@@ -3657,9 +4074,7 @@
</span><span class="cx">         else:
</span><span class="cx">             self._notifiers = None
</span><span class="cx"> 
</span><del>-        self._index = None  # Derived classes need to set this
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def memoMe(self, key, memo): #@UnusedVariable
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Add this object to the memo dictionary in whatever fashion is appropriate.
</span><span class="lines">@@ -3685,7 +4100,7 @@
</span><span class="cx">         rows = yield cls._acceptedBindForHomeID.on(
</span><span class="cx">             home._txn, homeID=home._resourceID
</span><span class="cx">         )
</span><del>-        names = [row[3] for row in rows]
</del><ins>+        names = [row[cls.bindColumns().index(cls._bindSchema.RESOURCE_NAME)] for row in rows]
</ins><span class="cx">         returnValue(names)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -3718,119 +4133,66 @@
</span><span class="cx"> 
</span><span class="cx">         # Create the actual objects merging in properties
</span><span class="cx">         for dataRow in dataRows:
</span><del>-            bindMode, homeID, resourceID, bindName, bindStatus, bindRevision, bindMessage = dataRow[:cls.bindColumnCount] #@UnusedVariable
-            additionalBind = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
-            metadata = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
</del><ins>+            bindData = dataRow[:cls.bindColumnCount] #@UnusedVariable
+            resourceID = bindData[cls.bindColumns().index(cls._bindSchema.RESOURCE_ID)]
+            additionalBindData = dataRow[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
+            metadataData = dataRow[cls.bindColumnCount + len(cls.additionalBindColumns()):]
+            propstore = propertyStores.get(resourceID, None)
</ins><span class="cx"> 
</span><del>-            if bindMode == _BIND_MODE_OWN:
-                ownerHome = home
-                ownerName = bindName
-            else:
-                #TODO: get all ownerHomeIDs at once
-                ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
-
-            child = cls(
-                home=home,
-                name=bindName,
-                resourceID=resourceID,
-                mode=bindMode,
-                status=bindStatus,
-                revision=bindRevision,
-                message=bindMessage,
-                ownerHome=ownerHome,
-                ownerName=ownerName,
-            )
-            for attr, value in zip(cls.additionalBindAttributes(), additionalBind):
-                setattr(child, attr, value)
-            for attr, value in zip(cls.metadataAttributes(), metadata):
-                setattr(child, attr, value)
</del><ins>+            child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, propstore)
</ins><span class="cx">             child._syncTokenRevision = revisions[resourceID]
</span><del>-            propstore = propertyStores.get(resourceID, None)
-            # We have to re-adjust the property store object to account for possible shared
-            # collections as previously we loaded them all as if they were owned
-            if propstore and bindMode != _BIND_MODE_OWN:
-                propstore._setDefaultUserUID(ownerHome.uid())
-            yield child._loadPropertyStore(propstore)
</del><span class="cx">             results.append(child)
</span><ins>+
</ins><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def objectWithName(cls, home, name, accepted=True):
</span><del>-        return cls._objectWithNameOrID(home, name=name, accepted=accepted)
</del><ins>+        return cls.objectWith(home, name=name, accepted=accepted)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def objectWithID(cls, home, resourceID, accepted=True):
</span><del>-        return cls._objectWithNameOrID(home, resourceID=resourceID, accepted=accepted)
</del><ins>+        return cls.objectWith(home, resourceID=resourceID, accepted=accepted)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><ins>+    def objectWithExternalID(cls, home, externalID, accepted=True):
+        return cls.objectWith(home, externalID=externalID, accepted=accepted)
+
+
+    @classmethod
</ins><span class="cx">     @inlineCallbacks
</span><del>-    def _objectWithNameOrID(cls, home, name=None, resourceID=None, accepted=True):
-        # replaces objectWithName()
</del><ins>+    def objectWith(cls, home, name=None, resourceID=None, externalID=None, accepted=True):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Retrieve the child with the given C{name} or C{resourceID} contained in the given
-        C{home}.
</del><ins>+        Create the object using one of the specified arguments as the key to load it. One
+        and only one of the keyword arguments must be set.
</ins><span class="cx"> 
</span><del>-        @param home: a L{CommonHome}.
</del><ins>+        @param parent: parent collection
+        @type parent: L{CommonHomeChild}
+        @param name: name of the resource, or C{None}
+        @type name: C{str}
+        @param uid: resource data UID, or C{None}
+        @type uid: C{str}
+        @param resourceID: resource id
+        @type resourceID: C{int}
+        @param accepted: if C{True} only load owned or accepted share items
+        @type accepted: C{bool}
</ins><span class="cx"> 
</span><del>-        @param name: a string; the name of the L{CommonHomeChild} to retrieve.
-
-        @return: an L{CommonHomeChild} or C{None} if no such child
-            exists.
</del><ins>+        @return: the new object or C{None} if not found
+        @rtype: C{CommonHomeChild}
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        rows = None
-        queryCacher = home._txn._queryCacher
</del><span class="cx"> 
</span><del>-        if queryCacher:
-            # Retrieve data from cache
-            if name:
-                cacheKey = queryCacher.keyForObjectWithName(home._resourceID, name)
-            else:
-                cacheKey = queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID)
-            rows = yield queryCacher.get(cacheKey)
-
-        if rows is None:
-            # No cached copy
-            if name:
-                rows = yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)
-            else:
-                rows = yield cls._bindForResourceIDAndHomeID.on(home._txn, resourceID=resourceID, homeID=home._resourceID)
-
-        if not rows:
</del><ins>+        dbData = yield cls._getDBData(home, name, resourceID, externalID)
+        if dbData is None:
</ins><span class="cx">             returnValue(None)
</span><ins>+        bindData, additionalBindData, metadataData = dbData
</ins><span class="cx"> 
</span><del>-        row = rows[0]
-        bindMode, homeID, resourceID, name, bindStatus, bindRevision, bindMessage = row[:cls.bindColumnCount] #@UnusedVariable
-
-        if queryCacher:
-            # Cache the result
-            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithName(home._resourceID, name), rows)
-            queryCacher.setAfterCommit(home._txn, queryCacher.keyForObjectWithResourceID(home._resourceID, resourceID), rows)
-
</del><ins>+        bindStatus = bindData[cls.bindColumns().index(cls._bindSchema.BIND_STATUS)]
</ins><span class="cx">         if accepted is not None and (bindStatus == _BIND_STATUS_ACCEPTED) != bool(accepted):
</span><span class="cx">             returnValue(None)
</span><del>-        additionalBind = row[cls.bindColumnCount:cls.bindColumnCount + len(cls.additionalBindColumns())]
</del><span class="cx"> 
</span><del>-        if bindMode == _BIND_MODE_OWN:
-            ownerHome = home
-            ownerName = name
-        else:
-            ownerHome, ownerName = yield home.ownerHomeAndChildNameForChildID(resourceID)
-
-        child = cls(
-            home=home,
-            name=name,
-            resourceID=resourceID,
-            mode=bindMode,
-            status=bindStatus,
-            revision=bindRevision,
-            message=bindMessage,
-            ownerHome=ownerHome,
-            ownerName=ownerName
-        )
-        yield child.initFromStore(additionalBind)
</del><ins>+        child = yield cls.makeClass(home, bindData, additionalBindData, metadataData)
</ins><span class="cx">         returnValue(child)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -3858,35 +4220,28 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def create(cls, home, name):
</del><ins>+    def create(cls, home, name, externalID=None):
</ins><span class="cx"> 
</span><del>-        if (yield cls._bindForNameAndHomeID.on(home._txn,
-            name=name, homeID=home._resourceID)):
</del><ins>+        if (yield cls._bindForNameAndHomeID.on(home._txn, name=name, homeID=home._resourceID)):
</ins><span class="cx">             raise HomeChildNameAlreadyExistsError(name)
</span><span class="cx"> 
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise HomeChildNameNotAllowedError(name)
</span><span class="cx"> 
</span><span class="cx">         # Create this object
</span><del>-        resourceID = (
-            yield cls._insertHomeChild.on(home._txn))[0][0]
</del><ins>+        resourceID = (yield cls._insertHomeChild.on(home._txn))[0][0]
</ins><span class="cx"> 
</span><span class="cx">         # Initialize this object
</span><del>-        _created, _modified = (
-            yield cls._insertHomeChildMetaData.on(home._txn,
-                                                  resourceID=resourceID))[0]
</del><ins>+        _created, _modified = (yield cls._insertHomeChildMetaData.on(home._txn, resourceID=resourceID))[0]
</ins><span class="cx">         # Bind table needs entry
</span><span class="cx">         yield cls._bindInsertQuery.on(
</span><del>-            home._txn, homeID=home._resourceID, resourceID=resourceID,
</del><ins>+            home._txn, homeID=home._resourceID, resourceID=resourceID, externalID=externalID,
</ins><span class="cx">             name=name, mode=_BIND_MODE_OWN, bindStatus=_BIND_STATUS_ACCEPTED,
</span><span class="cx">             message=None,
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         # Initialize other state
</span><del>-        child = cls(home, name, resourceID, _BIND_MODE_OWN, _BIND_STATUS_ACCEPTED)
-        child._created = _created
-        child._modified = _modified
-        yield child._loadPropertyStore()
</del><ins>+        child = yield cls.objectWithID(home, resourceID)
</ins><span class="cx"> 
</span><span class="cx">         yield child._initSyncToken()
</span><span class="cx"> 
</span><span class="lines">@@ -3907,44 +4262,32 @@
</span><span class="cx">                       Where=child.RESOURCE_ID == Parameter(&quot;resourceID&quot;))
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def initFromStore(self, additionalBind=None):
</del><ins>+    def id(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Initialise this object from the store, based on its already-populated
-        resource ID. We read in and cache all the extra metadata from the DB to
-        avoid having to do DB queries for those individually later.
</del><ins>+        Retrieve the store identifier for this collection.
+
+        @return: store identifier.
+        @rtype: C{int}
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        queryCacher = self._txn._queryCacher
-        if queryCacher:
-            # Retrieve from cache
-            cacheKey = queryCacher.keyForHomeChildMetaData(self._resourceID)
-            dataRows = yield queryCacher.get(cacheKey)
-        else:
-            dataRows = None
-        if dataRows is None:
-            # No cached copy
-            dataRows = (yield self._metadataByIDQuery.on(self._txn, resourceID=self._resourceID))[0]
-            if queryCacher:
-                # Cache the results
-                yield queryCacher.setAfterCommit(self._txn, cacheKey, dataRows)
</del><ins>+        return self._resourceID
</ins><span class="cx"> 
</span><del>-        if additionalBind:
-            for attr, value in zip(self.additionalBindAttributes(), additionalBind):
-                setattr(self, attr, value)
</del><span class="cx"> 
</span><del>-        for attr, value in zip(self.metadataAttributes(), dataRows):
-            setattr(self, attr, value)
-        yield self._loadPropertyStore()
</del><ins>+    def external_id(self):
+        &quot;&quot;&quot;
+        Retrieve the external store identifier for this collection.
</ins><span class="cx"> 
</span><ins>+        @return: a string.
+        &quot;&quot;&quot;
+        return self._externalID
</ins><span class="cx"> 
</span><del>-    def id(self):
</del><ins>+
+    def external(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Retrieve the store identifier for this collection.
</del><ins>+        Is this an external home.
</ins><span class="cx"> 
</span><del>-        @return: store identifier.
-        @rtype: C{int}
</del><ins>+        @return: a string.
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        return self._resourceID
</del><ins>+        return self.ownerHome().external()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @property
</span><span class="lines">@@ -3956,10 +4299,6 @@
</span><span class="cx">         return self._txn.store().directoryService()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def retrieveOldIndex(self):
-        return self._index
-
-
</del><span class="cx">     def __repr__(self):
</span><span class="cx">         return &quot;&lt;%s: %s&gt;&quot; % (self.__class__.__name__, self._resourceID)
</span><span class="cx"> 
</span><span class="lines">@@ -3994,14 +4333,13 @@
</span><span class="cx"> 
</span><span class="cx">         @return: a L{Deferred} which fires when the modification is complete.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
+        if self.isShared() or self.external():
+            raise ShareNotAllowed(&quot;Cannot rename a shared collection&quot;)
+
</ins><span class="cx">         oldName = self._name
</span><span class="cx"> 
</span><del>-        queryCacher = self._home._txn._queryCacher
-        if queryCacher:
-            cacheKey = queryCacher.keyForObjectWithName(self._home._resourceID, oldName)
-            yield queryCacher.invalidateAfterCommit(self._home._txn, cacheKey)
-            cacheKey = queryCacher.keyForObjectWithResourceID(self._home._resourceID, self._resourceID)
-            yield queryCacher.invalidateAfterCommit(self._home._txn, cacheKey)
</del><ins>+        yield self.invalidateQueryCache()
</ins><span class="cx"> 
</span><span class="cx">         yield self._renameQuery.on(self._txn, name=name,
</span><span class="cx">                                    resourceID=self._resourceID,
</span><span class="lines">@@ -4028,19 +4366,16 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def remove(self):
</span><span class="cx"> 
</span><ins>+        # Stop sharing first
+        yield self.ownerDeleteShare()
+
</ins><span class="cx">         # Do before setting _resourceID making changes
</span><span class="cx">         yield self.notifyPropertyChanged()
</span><span class="cx"> 
</span><del>-        queryCacher = self._home._txn._queryCacher
-        if queryCacher:
-            cacheKey = queryCacher.keyForObjectWithName(self._home._resourceID, self._name)
-            yield queryCacher.invalidateAfterCommit(self._home._txn, cacheKey)
-            cacheKey = queryCacher.keyForObjectWithResourceID(self._home._resourceID, self._resourceID)
-            yield queryCacher.invalidateAfterCommit(self._home._txn, cacheKey)
</del><ins>+        yield self.invalidateQueryCache()
</ins><span class="cx"> 
</span><span class="cx">         yield self._deletedSyncToken()
</span><del>-        yield self._deleteQuery.on(self._txn, NoSuchHomeChildError,
-                                   resourceID=self._resourceID)
</del><ins>+        yield self._deleteQuery.on(self._txn, NoSuchHomeChildError, resourceID=self._resourceID)
</ins><span class="cx">         yield self.properties()._removeResource()
</span><span class="cx"> 
</span><span class="cx">         # Set to non-existent state
</span><span class="lines">@@ -4092,6 +4427,7 @@
</span><span class="cx">         for result in results:
</span><span class="cx">             self._objects[result.name()] = result
</span><span class="cx">             self._objects[result.uid()] = result
</span><ins>+            self._objects[result.id()] = result
</ins><span class="cx">         self._objectNames = sorted([result.name() for result in results])
</span><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="lines">@@ -4105,6 +4441,7 @@
</span><span class="cx">         for result in results:
</span><span class="cx">             self._objects[result.name()] = result
</span><span class="cx">             self._objects[result.uid()] = result
</span><ins>+            self._objects[result.id()] = result
</ins><span class="cx">         self._objectNames = sorted([result.name() for result in results])
</span><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="lines">@@ -4141,8 +4478,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def countObjectResources(self):
</span><span class="cx">         if self._objectNames is None:
</span><del>-            rows = yield self._objectCountQuery.on(
-                self._txn, resourceID=self._resourceID)
</del><ins>+            rows = yield self._objectCountQuery.on(self._txn, resourceID=self._resourceID)
</ins><span class="cx">             returnValue(rows[0][0])
</span><span class="cx">         returnValue(len(self._objectNames))
</span><span class="cx"> 
</span><span class="lines">@@ -4174,18 +4510,13 @@
</span><span class="cx">         We create the empty object first then have it initialize itself from the
</span><span class="cx">         store.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        if resourceID:
-            objectResource = (
-                yield self._objectResourceClass.objectWithID(self, resourceID)
-            )
-        else:
-            objectResource = (
-                yield self._objectResourceClass.objectWithName(self, name, uid)
-            )
</del><ins>+        objectResource = (
+            yield self._objectResourceClass.objectWith(self, name=name, uid=uid, resourceID=resourceID)
+        )
</ins><span class="cx">         if objectResource:
</span><span class="cx">             self._objects[objectResource.name()] = objectResource
</span><span class="cx">             self._objects[objectResource.uid()] = objectResource
</span><del>-            self._objects[objectResource._resourceID] = objectResource
</del><ins>+            self._objects[objectResource.id()] = objectResource
</ins><span class="cx">         else:
</span><span class="cx">             if resourceID:
</span><span class="cx">                 self._objects[resourceID] = None
</span><span class="lines">@@ -4232,7 +4563,7 @@
</span><span class="cx">         obj = cls._objectSchema
</span><span class="cx">         return Select(
</span><span class="cx">             [obj.UID], From=obj,
</span><del>-            Where=(obj.UID == Parameter(&quot;name&quot;)
</del><ins>+            Where=(obj.RESOURCE_NAME == Parameter(&quot;name&quot;)
</ins><span class="cx">                   ).And(obj.PARENT_RESOURCE_ID == Parameter(&quot;resourceID&quot;)))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -4275,6 +4606,7 @@
</span><span class="cx">         )
</span><span class="cx">         self._objects[objectResource.name()] = objectResource
</span><span class="cx">         self._objects[objectResource.uid()] = objectResource
</span><ins>+        self._objects[objectResource.id()] = objectResource
</ins><span class="cx"> 
</span><span class="cx">         # Note: create triggers a notification when the component is set, so we
</span><span class="cx">         # don't need to call notify() here like we do for object removal.
</span><span class="lines">@@ -4285,6 +4617,7 @@
</span><span class="cx">     def removedObjectResource(self, child):
</span><span class="cx">         self._objects.pop(child.name(), None)
</span><span class="cx">         self._objects.pop(child.uid(), None)
</span><ins>+        self._objects.pop(child.id(), None)
</ins><span class="cx">         if self._objectNames and child.name() in self._objectNames:
</span><span class="cx">             self._objectNames.remove(child.name())
</span><span class="cx">         yield self._deleteRevision(child.name())
</span><span class="lines">@@ -4317,10 +4650,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def moveObjectResource(self, child, newparent, newname=None):
</del><ins>+    def _validObjectResource(self, child, newparent, newname=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Move a child of this collection into another collection without actually removing/re-inserting the data.
-        Make sure sync and cache details for both collections are updated.
</del><ins>+        Check that the move operation is valid
</ins><span class="cx"> 
</span><span class="cx">         TODO: check that the resource name does not exist in the new parent, or that the UID
</span><span class="cx">         does not exist there too.
</span><span class="lines">@@ -4334,7 +4666,6 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         name = child.name()
</span><del>-        uid = child.uid()
</del><span class="cx"> 
</span><span class="cx">         if newname is None:
</span><span class="cx">             newname = name
</span><span class="lines">@@ -4353,10 +4684,34 @@
</span><span class="cx">             if child_count &gt;= config.MaxResourcesPerCollection:
</span><span class="cx">                 raise TooManyObjectResourcesError()
</span><span class="cx"> 
</span><ins>+        returnValue(newname)
+
+
+    @inlineCallbacks
+    def moveObjectResource(self, child, newparent, newname=None):
+        &quot;&quot;&quot;
+        Move a child of this collection into another collection without actually removing/re-inserting the data.
+        Make sure sync and cache details for both collections are updated.
+
+        TODO: check that the resource name does not exist in the new parent, or that the UID
+        does not exist there too.
+
+        @param child: the child resource to move
+        @type child: L{CommonObjectResource}
+        @param newparent: the parent to move to
+        @type newparent: L{CommonHomeChild}
+        @param newname: new name to use in new parent
+        @type newname: C{str} or C{None} for existing name
+        &quot;&quot;&quot;
+
+        name = child.name()
+        newname = yield self._validObjectResource(child, newparent, newname)
+        uid = child.uid()
+
</ins><span class="cx">         # Clean this collections cache and signal sync change
</span><span class="cx">         self._objects.pop(name, None)
</span><span class="cx">         self._objects.pop(uid, None)
</span><del>-        self._objects.pop(child._resourceID, None)
</del><ins>+        self._objects.pop(child.id(), None)
</ins><span class="cx">         yield self._deleteRevision(name)
</span><span class="cx">         yield self.notifyChanged()
</span><span class="cx"> 
</span><span class="lines">@@ -4387,11 +4742,68 @@
</span><span class="cx">         # Signal sync change on new collection
</span><span class="cx">         newparent._objects.pop(name, None)
</span><span class="cx">         newparent._objects.pop(uid, None)
</span><del>-        newparent._objects.pop(child._resourceID, None)
</del><ins>+        newparent._objects.pop(child.id(), None)
</ins><span class="cx">         yield newparent._insertRevision(newname)
</span><span class="cx">         yield newparent.notifyChanged()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def moveObjectResourceCreateDelete(self, child, newparent, newname=None):
+        &quot;&quot;&quot;
+        Move a child of this collection into another collection by doing a create/delete.
+
+        TODO: check that the resource name does not exist in the new parent, or that the UID
+        does not exist there too.
+
+        @param child: the child resource to move
+        @type child: L{CommonObjectResource}
+        @param newparent: the parent to move to
+        @type newparent: L{CommonHomeChild}
+        @param newname: new name to use in new parent
+        @type newname: C{str} or C{None} for existing name
+        &quot;&quot;&quot;
+
+        name = child.name()
+        newname = yield self._validObjectResource(child, newparent, newname)
+
+        # Do a move as a create/delete
+        component = yield child.component()
+        yield newparent.moveObjectResourceHere(name, component)
+        yield self.moveObjectResourceAway(child.id(), child)
+
+
+    @inlineCallbacks
+    def moveObjectResourceHere(self, name, component):
+        &quot;&quot;&quot;
+        Create a new child in this collection as part of a move operation. This needs to be split out because
+        behavior differs for sub-classes and cross-pod operations.
+
+        @param name: new name to use in new parent
+        @type name: C{str} or C{None} for existing name
+        @param component: data for new resource
+        @type component: L{Component}
+        &quot;&quot;&quot;
+
+        yield self.createObjectResourceWithName(name, component)
+
+
+    @inlineCallbacks
+    def moveObjectResourceAway(self, rid, child=None):
+        &quot;&quot;&quot;
+        Remove the child as the result of a move operation. This needs to be split out because
+        behavior differs for sub-classes and cross-pod operations.
+
+        @param rid: the child resource-id to move
+        @type rid: C{int}
+        @param child: the child resource to move - might be C{None} for cross-pod
+        @type child: L{CommonObjectResource}
+        &quot;&quot;&quot;
+
+        if child is None:
+            child = yield self.objectResourceWithID(rid)
+        yield child.remove()
+
+
</ins><span class="cx">     def objectResourcesHaveProperties(self):
</span><span class="cx">         return False
</span><span class="cx"> 
</span><span class="lines">@@ -4407,12 +4819,26 @@
</span><span class="cx">         @type revision: C{int}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        if revision != 0 and revision &lt; self._bindRevision:
</del><ins>+        if revision != 0 and revision &lt; self._bindRevision and not self.external():
</ins><span class="cx">             raise SyncTokenValidException
</span><del>-
</del><span class="cx">         return super(CommonHomeChild, self).resourceNamesSinceRevision(revision)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def search(self, filter):
+        &quot;&quot;&quot;
+        Do a query of the contents of this collection.
+
+        @param filter: the query filter to use
+        @type filter: L{Filter}
+
+        @return: the names of the matching resources
+        @rtype: C{list}
+        &quot;&quot;&quot;
+
+        # This implementation raises - sub-classes override to do the actual query
+        raise IndexedSearchException()
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def sharedChildResourceNamesSinceRevision(self, revision, depth):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -4436,47 +4862,56 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         assert not self.owned()
</span><span class="cx"> 
</span><del>-        if revision != 0 and revision &lt; self._bindRevision:
-            if depth != '1':
-                raise SyncTokenValidException
-            else:
-                revision = 0
-
</del><span class="cx">         changed = set()
</span><span class="cx">         deleted = set()
</span><del>-        rev = self._revisionsSchema
-        results = [
-            (
-                self.name(),
-                name if name else &quot;&quot;,
-                wasdeleted
-            )
-            for name, wasdeleted in
-            (yield Select([rev.RESOURCE_NAME, rev.DELETED],
-                             From=rev,
-                            Where=(rev.REVISION &gt; revision).And(
-                            rev.RESOURCE_ID == self._resourceID)).on(self._txn))
-            if name
-        ]
</del><ins>+        invalid = set()
+        if self.external():
+            if depth == &quot;1&quot;:
+                pass
+            else:
+                name = self.name() + &quot;/&quot;
+                invalid.add(name)
+        else:
+            if revision != 0 and revision &lt; self._bindRevision:
+                if depth != &quot;1&quot;:
+                    raise SyncTokenValidException
+                else:
+                    revision = 0
</ins><span class="cx"> 
</span><del>-        for path, name, wasdeleted in results:
-            if wasdeleted:
-                if revision:
-                    if depth == &quot;1&quot;:
-                        changed.add(&quot;%s/&quot; % (path,))
-                    else:
-                        deleted.add(&quot;%s/%s&quot; % (path, name,))
</del><ins>+            rev = self._revisionsSchema
+            results = [
+                (
+                    self.name(),
+                    name if name else &quot;&quot;,
+                    wasdeleted
+                )
+                for name, wasdeleted in
+                (yield Select([rev.RESOURCE_NAME, rev.DELETED],
+                                 From=rev,
+                                Where=(rev.REVISION &gt; revision).And(
+                                rev.RESOURCE_ID == self._resourceID)).on(self._txn))
+                if name
+            ]
</ins><span class="cx"> 
</span><del>-            # Always report collection as changed
-            changed.add(&quot;%s/&quot; % (path,))
</del><ins>+            for path, name, wasdeleted in results:
+                if wasdeleted:
+                    if revision:
+                        if depth == &quot;1&quot;:
+                            changed.add(&quot;%s/&quot; % (path,))
+                        else:
+                            deleted.add(&quot;%s/%s&quot; % (path, name,))
</ins><span class="cx"> 
</span><del>-            # Resource changed - for depth &quot;infinity&quot; report resource as changed
-            if depth != &quot;1&quot;:
-                changed.add(&quot;%s/%s&quot; % (path, name,))
</del><ins>+                # Always report collection as changed
+                changed.add(&quot;%s/&quot; % (path,))
</ins><span class="cx"> 
</span><del>-        returnValue((changed, deleted))
</del><ins>+                if name:
+                    # Resource changed - for depth &quot;infinity&quot; report resource as changed
+                    if depth != &quot;1&quot;:
+                        changed.add(&quot;%s/%s&quot; % (path, name,))
</ins><span class="cx"> 
</span><ins>+        returnValue((changed, deleted, invalid,))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _loadPropertyStore(self, props=None):
</span><span class="cx">         if props is None:
</span><span class="lines">@@ -4656,10 +5091,85 @@
</span><span class="cx">         &quot;_parentCollection&quot;,
</span><span class="cx">     )
</span><span class="cx"> 
</span><ins>+    _externalClass = None
</ins><span class="cx">     _objectSchema = None
</span><ins>+    _componentClass = None
</ins><span class="cx"> 
</span><span class="cx">     BATCH_LOAD_SIZE = 50
</span><span class="cx"> 
</span><ins>+
+    @classmethod
+    @inlineCallbacks
+    def makeClass(cls, parent, objectData, propstore=None):
+        &quot;&quot;&quot;
+        Given the various database rows, build the actual class.
+
+        @param parent: the parent collection object
+        @type parent: L{CommonHomeChild}
+        @param objectData: the standard set of object columns
+        @type objectData: C{list}
+        @param propstore: a property store to use, or C{None} to load it automatically
+        @type propstore: L{PropertyStore}
+
+        @return: the constructed child class
+        @rtype: L{CommonHomeChild}
+        &quot;&quot;&quot;
+
+        c = cls._externalClass if parent.external() else cls
+        child = c(
+            parent,
+            objectData[cls._allColumns().index(cls._objectSchema.RESOURCE_NAME)],
+            objectData[cls._allColumns().index(cls._objectSchema.UID)],
+        )
+
+        for attr, value in zip(child._rowAttributes(), objectData):
+            setattr(child, attr, value)
+
+        yield child._loadPropertyStore(propstore)
+
+        returnValue(child)
+
+
+    @classmethod
+    @inlineCallbacks
+    def _getDBData(cls, parent, name, uid, resourceID):
+        &quot;&quot;&quot;
+        Given a set of identifying information, load the data rows for the object. Only one of
+        L{name}, L{uid} or L{resourceID} is specified - others are C{None}.
+
+        @param parent: the parent collection object
+        @type parent: L{CommonHomeChild}
+        @param name: the resource name
+        @type name: C{str}
+        @param uid: the UID of the data
+        @type uid: C{str}
+        @param resourceID: the resource ID
+        @type resourceID: C{int}
+        &quot;&quot;&quot;
+
+        rows = None
+        if name:
+            rows = yield cls._allColumnsWithParentAndName.on(
+                parent._txn,
+                name=name,
+                parentID=parent._resourceID
+            )
+        elif uid:
+            rows = yield cls._allColumnsWithParentAndUID.on(
+                parent._txn,
+                uid=uid,
+                parentID=parent._resourceID
+            )
+        elif resourceID:
+            rows = yield cls._allColumnsWithParentAndID.on(
+                parent._txn,
+                resourceID=resourceID,
+                parentID=parent._resourceID
+            )
+
+        returnValue(rows[0] if rows else None)
+
+
</ins><span class="cx">     def __init__(self, parent, name, uid, resourceID=None, options=None): #@UnusedVariable
</span><span class="cx">         self._parentCollection = parent
</span><span class="cx">         self._resourceID = resourceID
</span><span class="lines">@@ -4669,7 +5179,8 @@
</span><span class="cx">         self._size = None
</span><span class="cx">         self._created = None
</span><span class="cx">         self._modified = None
</span><del>-        self._notificationData = None
</del><ins>+        self._textData = None
+        self._cachedComponent = None
</ins><span class="cx"> 
</span><span class="cx">         self._locked = False
</span><span class="cx"> 
</span><span class="lines">@@ -4677,7 +5188,7 @@
</span><span class="cx">     @classproperty
</span><span class="cx">     def _allColumnsWithParentQuery(cls): #@NoSelf
</span><span class="cx">         obj = cls._objectSchema
</span><del>-        return Select(cls._allColumns, From=obj,
</del><ins>+        return Select(cls._allColumns(), From=obj,
</ins><span class="cx">                       Where=obj.PARENT_RESOURCE_ID == Parameter(&quot;parentID&quot;))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -4718,11 +5229,10 @@
</span><span class="cx"> 
</span><span class="cx">         # Create the actual objects merging in properties
</span><span class="cx">         for row in dataRows:
</span><del>-            child = cls(parent, &quot;&quot;, None)
-            child._initFromRow(tuple(row))
-            yield child._loadPropertyStore(
-                props=propertyStores.get(child._resourceID, None)
-            )
</del><ins>+            resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+            propstore = propertyStores.get(resourceID, None)
+
+            child = yield cls.makeClass(parent, row, propstore=propstore)
</ins><span class="cx">             results.append(child)
</span><span class="cx"> 
</span><span class="cx">         returnValue(results)
</span><span class="lines">@@ -4732,7 +5242,9 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def loadAllObjectsWithNames(cls, parent, names):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Load all child objects with the specified names, doing so in batches.
</del><ins>+        Load all child objects with the specified names, doing so in batches (because we need to match
+        using SQL &quot;resource_name in (...)&quot; where there might be a character length limit on the number
+        of items in the set).
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         names = tuple(names)
</span><span class="cx">         results = []
</span><span class="lines">@@ -4747,7 +5259,7 @@
</span><span class="cx">     @classmethod
</span><span class="cx">     def _allColumnsWithParentAndNamesQuery(cls, names):
</span><span class="cx">         obj = cls._objectSchema
</span><del>-        return Select(cls._allColumns, From=obj,
</del><ins>+        return Select(cls._allColumns(), From=obj,
</ins><span class="cx">                       Where=(obj.PARENT_RESOURCE_ID == Parameter(&quot;parentID&quot;)).And(
</span><span class="cx">                           obj.RESOURCE_NAME.In(Parameter(&quot;names&quot;, len(names)))))
</span><span class="cx"> 
</span><span class="lines">@@ -4771,7 +5283,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Optimize case of single name to load
</span><span class="cx">         if len(names) == 1:
</span><del>-            obj = yield cls.objectWithName(parent, names[0], None)
</del><ins>+            obj = yield cls.objectWithName(parent, names[0])
</ins><span class="cx">             returnValue([obj] if obj else [])
</span><span class="cx"> 
</span><span class="cx">         results = []
</span><span class="lines">@@ -4792,30 +5304,61 @@
</span><span class="cx"> 
</span><span class="cx">         # Create the actual objects merging in properties
</span><span class="cx">         for row in dataRows:
</span><del>-            child = cls(parent, &quot;&quot;, None)
-            child._initFromRow(tuple(row))
-            yield child._loadPropertyStore(
-                props=propertyStores.get(child._resourceID, None)
-            )
</del><ins>+            resourceID = row[cls._allColumns().index(cls._objectSchema.RESOURCE_ID)]
+            propstore = propertyStores.get(resourceID, None)
+
+            child = yield cls.makeClass(parent, row, propstore=propstore)
</ins><span class="cx">             results.append(child)
</span><span class="cx"> 
</span><span class="cx">         returnValue(results)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def objectWithName(cls, parent, name, uid):
-        objectResource = cls(parent, name, uid, None)
-        return objectResource.initFromStore()
</del><ins>+    def objectWithName(cls, parent, name):
+        return cls.objectWith(parent, name=name)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><ins>+    def objectWithUID(cls, parent, uid):
+        return cls.objectWith(parent, uid=uid)
+
+
+    @classmethod
</ins><span class="cx">     def objectWithID(cls, parent, resourceID):
</span><del>-        objectResource = cls(parent, None, None, resourceID)
-        return objectResource.initFromStore()
</del><ins>+        return cls.objectWith(parent, resourceID=resourceID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def objectWith(cls, parent, name=None, uid=None, resourceID=None):
+        &quot;&quot;&quot;
+        Create the object using one of the specified arguments as the key to load it. One
+        and only one of the keyword arguments must be set.
+
+        @param parent: parent collection
+        @type parent: L{CommonHomeChild}
+        @param name: name of the resource, or C{None}
+        @type name: C{str}
+        @param uid: resource data UID, or C{None}
+        @type uid: C{str}
+        @param resourceID: resource id
+        @type resourceID: C{int}
+
+        @return: the new object or C{None} if not found
+        @rtype: C{CommonObjectResource}
+        &quot;&quot;&quot;
+
+        row = yield cls._getDBData(parent, name, uid, resourceID)
+
+        if row:
+            child = yield cls.makeClass(parent, row)
+            returnValue(child)
+        else:
+            returnValue(None)
+
+
+    @classmethod
+    @inlineCallbacks
</ins><span class="cx">     def create(cls, parent, name, component, options=None):
</span><span class="cx"> 
</span><span class="cx">         child = (yield parent.objectResourceWithName(name))
</span><span class="lines">@@ -4825,13 +5368,13 @@
</span><span class="cx">         if name.startswith(&quot;.&quot;):
</span><span class="cx">             raise ObjectResourceNameNotAllowedError(name)
</span><span class="cx"> 
</span><del>-        objectResource = cls(parent, name, None, None, options=options)
</del><ins>+        c = cls._externalClass if parent.external() else cls
+        objectResource = c(parent, name, None, None, options=options)
</ins><span class="cx">         yield objectResource.setComponent(component, inserting=True)
</span><span class="cx">         yield objectResource._loadPropertyStore(created=True)
</span><span class="cx"> 
</span><span class="cx">         # Note: setComponent triggers a notification, so we don't need to
</span><span class="cx">         # call notify( ) here like we do for object removal.
</span><del>-
</del><span class="cx">         returnValue(objectResource)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -4842,7 +5385,7 @@
</span><span class="cx">         parameter and a given instance column matches a given parameter name.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         return Select(
</span><del>-            cls._allColumns, From=cls._objectSchema,
</del><ins>+            cls._allColumns(), From=cls._objectSchema,
</ins><span class="cx">             Where=(column == Parameter(paramName)).And(
</span><span class="cx">                 cls._objectSchema.PARENT_RESOURCE_ID == Parameter(&quot;parentID&quot;))
</span><span class="cx">         )
</span><span class="lines">@@ -4863,38 +5406,7 @@
</span><span class="cx">         return cls._allColumnsWithParentAnd(cls._objectSchema.RESOURCE_ID, &quot;resourceID&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @inlineCallbacks
-    def initFromStore(self):
-        &quot;&quot;&quot;
-        Initialise this object from the store. We read in and cache all the
-        extra metadata from the DB to avoid having to do DB queries for those
-        individually later. Either the name or uid is present, so we have to
-        tweak the query accordingly.
-
-        @return: L{self} if object exists in the DB, else C{None}
-        &quot;&quot;&quot;
-
-        if self._name:
-            rows = yield self._allColumnsWithParentAndName.on(
-                self._txn, name=self._name,
-                parentID=self._parentCollection._resourceID)
-        elif self._uid:
-            rows = yield self._allColumnsWithParentAndUID.on(
-                self._txn, uid=self._uid,
-                parentID=self._parentCollection._resourceID)
-        elif self._resourceID:
-            rows = yield self._allColumnsWithParentAndID.on(
-                self._txn, resourceID=self._resourceID,
-                parentID=self._parentCollection._resourceID)
-        if rows:
-            self._initFromRow(tuple(rows[0]))
-            yield self._loadPropertyStore()
-            returnValue(self)
-        else:
-            returnValue(None)
-
-
-    @classproperty
</del><ins>+    @classmethod
</ins><span class="cx">     def _allColumns(cls): #@NoSelf
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Full set of columns in the object table that need to be loaded to
</span><span class="lines">@@ -4912,21 +5424,52 @@
</span><span class="cx">         ]
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _initFromRow(self, row):
</del><ins>+    @classmethod
+    def _rowAttributes(cls): #@NoSelf
+        return (
+            &quot;_resourceID&quot;,
+            &quot;_name&quot;,
+            &quot;_uid&quot;,
+            &quot;_md5&quot;,
+            &quot;_size&quot;,
+            &quot;_created&quot;,
+            &quot;_modified&quot;,
+        )
+
+
+    @classmethod
+    def _otherSerializedAttributes(cls): #@NoSelf
+        return (
+            &quot;_componentChanged&quot;,
+        )
+
+
+    def externalize(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Given a select result using the columns from L{_allColumns}, initialize
-        the object resource state.
</del><ins>+        Create a dictionary mapping key attributes so this object can be sent over a cross-pod call
+        and reconstituted at the other end. Note that the other end may have a different schema so
+        the attributes may not match exactly and will need to be processed accordingly.
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        (self._resourceID,
-         self._name,
-         self._uid,
-         self._md5,
-         self._size,
-         self._created,
-         self._modified,) = tuple(row)
</del><ins>+        return dict([(attr[1:], getattr(self, attr, None)) for attr in itertools.chain(self._rowAttributes(), self._otherSerializedAttributes())])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
</ins><span class="cx">     @inlineCallbacks
</span><ins>+    def internalize(cls, parent, mapping):
+        &quot;&quot;&quot;
+        Given a mapping generated by L{externalize}, convert the values into an array of database
+        like items that conforms to the ordering of L{_allColumns} so it can be fed into L{makeClass}.
+        Note that there may be a schema mismatch with the external data, so treat missing items as
+        C{None} and ignore extra items.
+        &quot;&quot;&quot;
+
+        child = yield cls.makeClass(parent, [mapping.get(row[1:]) for row in cls._rowAttributes()])
+        for attr in cls._otherSerializedAttributes():
+            setattr(child, attr, mapping.get(attr[1:]))
+        returnValue(child)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def _loadPropertyStore(self, props=None, created=False):
</span><span class="cx">         if props is None:
</span><span class="cx">             if self._parentCollection.objectResourcesHaveProperties():
</span><span class="lines">@@ -5036,7 +5579,7 @@
</span><span class="cx">         self._locked = True
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def setComponent(self, component, inserting=False, options=None):
</del><ins>+    def setComponent(self, component, inserting=False):
</ins><span class="cx">         raise NotImplementedError
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -5069,9 +5612,15 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         yield self.moveValidation(destination, name)
</span><del>-        yield self._parentCollection.moveObjectResource(self, destination, name)
</del><span class="cx"> 
</span><ins>+        # If possible we do a &quot;fast&quot; move by simply fixing up the database information directly rather than
+        # re-writing any data. That is only possible when the source and destination are on this pod.
+        if not self._parentCollection.external() and not destination.external():
+            yield self._parentCollection.moveObjectResource(self, destination, name)
+        else:
+            yield self._parentCollection.moveObjectResourceCreateDelete(self, destination, name)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def moveValidation(self, destination, name):
</span><span class="cx">         raise NotImplementedError
</span><span class="cx"> 
</span><span class="lines">@@ -5092,7 +5641,8 @@
</span><span class="cx">         self._size = None
</span><span class="cx">         self._created = None
</span><span class="cx">         self._modified = None
</span><del>-        self._notificationData = None
</del><ins>+        self._textData = None
+        self._cachedComponent = None
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def removeNotifyCategory(self):
</span><span class="lines">@@ -5148,19 +5698,19 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _text(self):
</span><del>-        if self._notificationData is None:
</del><ins>+        if self._textData is None:
</ins><span class="cx">             texts = (
</span><span class="cx">                 yield self._textByIDQuery.on(self._txn,
</span><span class="cx">                                              resourceID=self._resourceID)
</span><span class="cx">             )
</span><span class="cx">             if texts:
</span><span class="cx">                 text = texts[0][0]
</span><del>-                self._notificationData = text
</del><ins>+                self._textData = text
</ins><span class="cx">                 returnValue(text)
</span><span class="cx">             else:
</span><span class="cx">                 raise ConcurrentModification()
</span><span class="cx">         else:
</span><del>-            returnValue(self._notificationData)
</del><ins>+            returnValue(self._textData)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -5224,6 +5774,15 @@
</span><span class="cx">             resourceID = rows[0][0]
</span><span class="cx">             created = False
</span><span class="cx">         elif create:
</span><ins>+            # Determine if the user is local or external
+            record = txn.directoryService().recordWithUID(uid)
+            if record is None:
+                raise DirectoryRecordNotFoundError(&quot;Cannot create home for UID since no directory record exists: {}&quot;.format(uid))
+
+            state = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
+            if state == _HOME_STATUS_EXTERNAL:
+                raise RecordNotAllowedError(&quot;Cannot store notifications for external user: {}&quot;.format(uid))
+
</ins><span class="cx">             # Use savepoint so we can do a partial rollback if there is a race
</span><span class="cx">             # condition where this row has already been inserted
</span><span class="cx">             savepoint = SavepointAction(&quot;notificationsWithUID&quot;)
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_externalpyfromrev12191CalendarServerbranchesuserscdaboocrosspodsharingtxdavcommondatastoresql_externalpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/trunk/txdav/common/datastore/sql_external.py (from rev 12191, CalendarServer/branches/users/cdaboo/cross-pod-sharing/txdav/common/datastore/sql_external.py) (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_external.py                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_external.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,470 @@
</span><ins>+# -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+&quot;&quot;&quot;
+SQL data store.
+&quot;&quot;&quot;
+
+from twext.internet.decorate import memoizedKey
+from twext.python.log import Logger
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+from txdav.base.propertystore.sql import PropertyStore
+from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
+    CommonObjectResource
+from txdav.common.datastore.sql_tables import _HOME_STATUS_EXTERNAL
+from txdav.common.icommondatastore import NonExistentExternalShare, \
+    ExternalShareFailed
+
+
+log = Logger()
+
+class CommonHomeExternal(CommonHome):
+    &quot;&quot;&quot;
+    A CommonHome for a user not hosted on this system, but on another pod. This is needed to provide a
+    &quot;reference&quot; to the external user so we can share with them. Actual operations to list child resources, etc
+    are all stubbed out since no data for the user is actually hosted in this store.
+    &quot;&quot;&quot;
+
+    def __init__(self, transaction, ownerUID, resourceID):
+        super(CommonHomeExternal, self).__init__(transaction, ownerUID)
+        self._resourceID = resourceID
+        self._status = _HOME_STATUS_EXTERNAL
+
+
+    def initFromStore(self, no_cache=False):
+        &quot;&quot;&quot;
+        Never called - this should be done by CommonHome.initFromStore only.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def external(self):
+        &quot;&quot;&quot;
+        Is this an external home.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return True
+
+
+    def children(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def loadChildren(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def listChildren(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def objectWithShareUID(self, shareUID):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def invitedObjectWithShareUID(self, shareUID):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    @memoizedKey(&quot;name&quot;, &quot;_children&quot;)
+    @inlineCallbacks
+    def createChildWithName(self, name, externalID=None):
+        &quot;&quot;&quot;
+        No real children - only external ones.
+        &quot;&quot;&quot;
+        if externalID is None:
+            raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+        child = yield super(CommonHomeExternal, self).createChildWithName(name, externalID)
+        returnValue(child)
+
+
+    def removeChildWithName(self, name):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    @inlineCallbacks
+    def removeExternalChild(self, child):
+        &quot;&quot;&quot;
+        Remove an external child. Check that it is invalid or unused before calling this because if there
+        are valid references to it, removing will break things.
+        &quot;&quot;&quot;
+        if child._externalID is None:
+            raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+        yield super(CommonHomeExternal, self).removeChildWithName(child.name())
+
+
+    def syncToken(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def resourceNamesSinceRevision(self, revision, depth):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    @inlineCallbacks
+    def _loadPropertyStore(self):
+        &quot;&quot;&quot;
+        No property store - stub to a NonePropertyStore.
+        &quot;&quot;&quot;
+        props = yield PropertyStore.load(
+            self.uid(),
+            self.uid(),
+            self._txn,
+            self._resourceID,
+            notifyCallback=self.notifyChanged
+        )
+        self._propertyStore = props
+
+
+    def properties(self):
+        return self._propertyStore
+
+
+    def objectResourcesWithUID(self, uid, ignore_children=[], allowShared=True):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def objectResourceWithID(self, rid):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+    def notifyChanged(self):
+        &quot;&quot;&quot;
+        Notifications are not handled for external homes - make this a no-op.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def bumpModified(self):
+        &quot;&quot;&quot;
+        No changes recorded for external homes - make this a no-op.
+        &quot;&quot;&quot;
+        return succeed(None)
+
+
+    def removeUnacceptedShares(self):
+        &quot;&quot;&quot;
+        No children.
+        &quot;&quot;&quot;
+        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+#    def ownerHomeAndChildNameForChildID(self, resourceID):
+#        &quot;&quot;&quot;
+#        No children.
+#        &quot;&quot;&quot;
+#        raise AssertionError(&quot;CommonHomeExternal: not supported&quot;)
+
+
+
+class CommonHomeChildExternal(CommonHomeChild):
+    &quot;&quot;&quot;
+    A CommonHomeChild for a collection not hosted on this system, but on another pod. This will forward
+    specific apis to the other pod using cross-pod requests.
+    &quot;&quot;&quot;
+
+    def external(self):
+        &quot;&quot;&quot;
+        Is this an external home.
+
+        @return: a string.
+        &quot;&quot;&quot;
+        return True
+
+
+    def fixNonExistentExternalShare(self):
+        &quot;&quot;&quot;
+        An external request has returned and indicates the external share no longer exists. That
+        means this shared resource is an &quot;orphan&quot; and needs to be remove (uninvited) to clean things up.
+        &quot;&quot;&quot;
+        log.error(&quot;Non-existent share detected and removed for {share}&quot;, share=self)
+        ownerView = yield self.ownerView()
+        yield ownerView.removeShare(self)
+
+
+    @inlineCallbacks
+    def remove(self):
+        &quot;&quot;&quot;
+        External shares are never removed directly - instead they must be &quot;uninvited&quot;. However,
+        the owner's external calendar can be removed.
+        &quot;&quot;&quot;
+        if self.owned():
+            yield super(CommonHomeChildExternal, self).remove()
+        else:
+            raise AssertionError(&quot;CommonHomeChildExternal: not supported&quot;)
+
+
+    @inlineCallbacks
+    def listObjectResources(self):
+        if self._objectNames is None:
+            try:
+                self._objectNames = yield self._txn.store().conduit.send_listobjects(self)
+            except NonExistentExternalShare:
+                yield self.fixNonExistentExternalShare()
+                raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        returnValue(self._objectNames)
+
+
+    @inlineCallbacks
+    def countObjectResources(self):
+        if self._objectNames is None:
+            try:
+                count = yield self._txn.store().conduit.send_countobjects(self)
+            except NonExistentExternalShare:
+                yield self.fixNonExistentExternalShare()
+                raise ExternalShareFailed(&quot;External share does not exist&quot;)
+            returnValue(count)
+        returnValue(len(self._objectNames))
+
+
+    @inlineCallbacks
+    def resourceNameForUID(self, uid):
+        try:
+            resource = self._objects[uid]
+            returnValue(resource.name() if resource else None)
+        except KeyError:
+            pass
+
+        try:
+            name = yield self._txn.store().conduit.send_resourcenameforuid(self, uid)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        if name:
+            returnValue(name)
+        else:
+            self._objects[uid] = None
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def resourceUIDForName(self, name):
+        try:
+            resource = self._objects[name]
+            returnValue(resource.uid() if resource else None)
+        except KeyError:
+            pass
+
+        try:
+            uid = yield self._txn.store().conduit.send_resourceuidforname(self, name)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        if uid:
+            returnValue(uid)
+        else:
+            self._objects[name] = None
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def moveObjectResource(self, child, newparent, newname=None):
+        &quot;&quot;&quot;
+        The base class does an optimization to avoid removing/re-creating
+        the actual object resource data. That might not always be possible
+        with external shares if the shared resource is moved to a collection
+        that is not shared or shared by someone else on a different (third)
+        pod. The best bet here is to treat the move as a delete/create.
+        &quot;&quot;&quot;
+        raise NotImplementedError(&quot;TODO: external resource&quot;)
+
+
+    @inlineCallbacks
+    def moveObjectResourceHere(self, name, component):
+        &quot;&quot;&quot;
+        Create a new child in this collection as part of a move operation. This needs to be split out because
+        behavior differs for sub-classes and cross-pod operations.
+
+        @param name: new name to use in new parent
+        @type name: C{str} or C{None} for existing name
+        @param component: data for new resource
+        @type component: L{Component}
+        &quot;&quot;&quot;
+
+        try:
+            result = yield self._txn.store().conduit.send_movehere(self, name, str(component))
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def moveObjectResourceAway(self, rid, child=None):
+        &quot;&quot;&quot;
+        Remove the child as the result of a move operation. This needs to be split out because
+        behavior differs for sub-classes and cross-pod operations.
+
+        @param rid: the child resource-id to move
+        @type rid: C{int}
+        @param child: the child resource to move - might be C{None} for cross-pod
+        @type child: L{CommonObjectResource}
+        &quot;&quot;&quot;
+
+        try:
+            result = yield self._txn.store().conduit.send_moveaway(self, rid)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def syncToken(self):
+        if self._syncTokenRevision is None:
+            try:
+                token = yield self._txn.store().conduit.send_synctoken(self)
+                self._syncTokenRevision = self.revisionFromToken(token)
+            except NonExistentExternalShare:
+                yield self.fixNonExistentExternalShare()
+                raise ExternalShareFailed(&quot;External share does not exist&quot;)
+        returnValue((&quot;%s_%s&quot; % (self._externalID, self._syncTokenRevision,)))
+
+
+    @inlineCallbacks
+    def resourceNamesSinceRevision(self, revision):
+        try:
+            names = yield self._txn.store().conduit.send_resourcenamessincerevision(self, revision)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        returnValue(names)
+
+
+    @inlineCallbacks
+    def search(self, filter, **kwargs):
+        try:
+            results = yield self._txn.store().conduit.send_search(self, filter.serialize(), **kwargs)
+        except NonExistentExternalShare:
+            yield self.fixNonExistentExternalShare()
+            raise ExternalShareFailed(&quot;External share does not exist&quot;)
+
+        returnValue(results)
+
+
+
+class CommonObjectResourceExternal(CommonObjectResource):
+    &quot;&quot;&quot;
+    A CommonObjectResource for a resource not hosted on this system, but on another pod. This will forward
+    specific apis to the other pod using cross-pod requests.
+    &quot;&quot;&quot;
+
+    @classmethod
+    @inlineCallbacks
+    def loadAllObjects(cls, parent):
+        mapping_list = yield parent._txn.store().conduit.send_loadallobjects(parent, None)
+
+        results = []
+        if mapping_list:
+            for mapping in mapping_list:
+                child = yield cls.internalize(parent, mapping)
+                results.append(child)
+        returnValue(results)
+
+
+    @classmethod
+    @inlineCallbacks
+    def loadAllObjectsWithNames(cls, parent, names):
+        mapping_list = yield parent._txn.store().conduit.send_loadallobjectswithnames(parent, None, names)
+
+        results = []
+        if mapping_list:
+            for mapping in mapping_list:
+                child = yield cls.internalize(parent, mapping)
+                results.append(child)
+        returnValue(results)
+
+
+    @classmethod
+    @inlineCallbacks
+    def objectWith(cls, parent, name=None, uid=None, resourceID=None):
+        mapping = yield parent._txn.store().conduit.send_objectwith(parent, None, name, uid, resourceID)
+
+        if mapping:
+            child = yield cls.internalize(parent, mapping)
+            returnValue(child)
+        else:
+            returnValue(None)
+
+
+    @classmethod
+    @inlineCallbacks
+    def create(cls, parent, name, component, options=None):
+        mapping = yield parent._txn.store().conduit.send_create(parent, None, name, str(component), options=options)
+
+        if mapping:
+            child = yield cls.internalize(parent, mapping)
+            returnValue(child)
+        else:
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def setComponent(self, component, **kwargs):
+        self._componentChanged = yield self._txn.store().conduit.send_setcomponent(self.parentCollection(), self, str(component), **kwargs)
+        self._cachedComponent = None
+        returnValue(self._componentChanged)
+
+
+    @inlineCallbacks
+    def component(self):
+        if self._cachedComponent is None:
+            text = yield self._txn.store().conduit.send_component(self.parentCollection(), self)
+            self._cachedComponent = self._componentClass.fromString(text)
+
+        returnValue(self._cachedComponent)
+
+
+    @inlineCallbacks
+    def remove(self):
+        yield self._txn.store().conduit.send_remove(self.parentCollection(), self)
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_legacypy"></a>
<div class="delfile"><h4>Deleted: CalendarServer/trunk/txdav/common/datastore/sql_legacy.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_legacy.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/sql_legacy.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -1,886 +0,0 @@
</span><del>-# -*- test-case-name: twistedcaldav.test.test_sharing,twistedcaldav.test.test_calendarquery -*-
-##
-# Copyright (c) 2010-2013 Apple Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-##
-
-
-&quot;&quot;&quot;
-PostgreSQL data store.
-&quot;&quot;&quot;
-
-import StringIO
-
-
-from twisted.python import hashlib
-from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-
-from twistedcaldav.config import config
-from twistedcaldav.dateops import normalizeForIndex, pyCalendarTodatetime
-from twistedcaldav.memcachepool import CachePoolUserMixIn
-from twistedcaldav.query import \
-    calendarqueryfilter, calendarquery, addressbookquery, expression, \
-    addressbookqueryfilter
-from twistedcaldav.query.sqlgenerator import sqlgenerator
-
-from txdav.caldav.icalendarstore import TimeRangeLowerLimit, TimeRangeUpperLimit
-from txdav.common.icommondatastore import IndexedSearchException, \
-    ReservationError, NoSuchObjectResourceError
-
-from txdav.common.datastore.sql_tables import schema
-from twext.enterprise.dal.syntax import Parameter, Select
-from twext.python.clsprop import classproperty
-from twext.python.log import Logger
-
-from pycalendar.datetime import DateTime
-from pycalendar.duration import Duration
-
-log = Logger()
-
-indexfbtype_to_icalfbtype = {
-    0: '?',
-    1: 'F',
-    2: 'B',
-    3: 'U',
-    4: 'T',
-}
-
-class MemcachedUIDReserver(CachePoolUserMixIn):
-    log = Logger()
-
-    def __init__(self, index, cachePool=None):
-        self.index = index
-        self._cachePool = cachePool
-
-
-    def _key(self, uid):
-        return 'reservation:%s' % (
-            hashlib.md5('%s:%s' % (uid,
-                                   self.index.resource._resourceID)).hexdigest())
-
-
-    def reserveUID(self, uid):
-        self.log.debug(&quot;Reserving UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    &quot;UID %s already reserved for calendar collection %s.&quot;
-                    % (uid, self.index.resource._name)
-                    )
-
-        d = self.getCachePool().add(self._key(uid),
-                                    'reserved',
-                                    expireTime=config.UIDReservationTimeOut)
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def unreserveUID(self, uid):
-        self.log.debug(&quot;Unreserving UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        def _handleFalse(result):
-            if result is False:
-                raise ReservationError(
-                    &quot;UID %s is not reserved for calendar collection %s.&quot;
-                    % (uid, self.index.resource._resourceID)
-                    )
-
-        d = self.getCachePool().delete(self._key(uid))
-        d.addCallback(_handleFalse)
-        return d
-
-
-    def isReservedUID(self, uid):
-        self.log.debug(&quot;Is reserved UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        def _checkValue((flags, value)):
-            if value is None:
-                return False
-            else:
-                return True
-
-        d = self.getCachePool().get(self._key(uid))
-        d.addCallback(_checkValue)
-        return d
-
-
-
-class DummyUIDReserver(object):
-    log = Logger()
-
-    def __init__(self, index):
-        self.index = index
-        self.reservations = set()
-
-
-    def _key(self, uid):
-        return 'reservation:%s' % (
-            hashlib.md5('%s:%s' % (uid,
-                                   self.index.resource._resourceID)).hexdigest())
-
-
-    def reserveUID(self, uid):
-        self.log.debug(&quot;Reserving UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        key = self._key(uid)
-        if key in self.reservations:
-            raise ReservationError(
-                &quot;UID %s already reserved for calendar collection %s.&quot;
-                % (uid, self.index.resource._name)
-                )
-        self.reservations.add(key)
-        return succeed(None)
-
-
-    def unreserveUID(self, uid):
-        self.log.debug(&quot;Unreserving UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-
-        key = self._key(uid)
-        if key in self.reservations:
-            self.reservations.remove(key)
-        return succeed(None)
-
-
-    def isReservedUID(self, uid):
-        self.log.debug(&quot;Is reserved UID %r @ %r&quot; % (
-                uid,
-                self.index.resource))
-        key = self._key(uid)
-        return succeed(key in self.reservations)
-
-
-
-class RealSQLBehaviorMixin(object):
-    &quot;&quot;&quot;
-    Class attributes for 'real' SQL behavior; avoid idiosyncracies of SQLite,
-    use standard SQL constructions, and depend on the full schema in
-    sql_schema/current.sql rather than the partial one in twistedcaldav which depends
-    on the placement of the database in the filesystem for some information.
-    &quot;&quot;&quot;
-
-    ISOP = &quot; = &quot;
-    STARTSWITHOP = ENDSWITHOP = CONTAINSOP = &quot; LIKE &quot;
-    NOTSTARTSWITHOP = NOTENDSWITHOP = NOTCONTAINSOP = &quot; NOT LIKE &quot;
-
-    def containsArgument(self, arg):
-        return &quot;%%%s%%&quot; % (arg,)
-
-
-    def startswithArgument(self, arg):
-        return &quot;%s%%&quot; % (arg,)
-
-
-    def endswithArgument(self, arg):
-        return &quot;%%%s&quot; % (arg,)
-
-
-
-class CalDAVSQLBehaviorMixin(RealSQLBehaviorMixin):
-    &quot;&quot;&quot;
-    Query generator for CalDAV indexed searches.
-    &quot;&quot;&quot;
-
-    FIELDS = {
-        &quot;TYPE&quot;: &quot;CALENDAR_OBJECT.ICALENDAR_TYPE&quot;,
-        &quot;UID&quot;: &quot;CALENDAR_OBJECT.ICALENDAR_UID&quot;,
-    }
-    RESOURCEDB = &quot;CALENDAR_OBJECT&quot;
-    TIMESPANDB = &quot;TIME_RANGE&quot;
-
-    TIMESPANTEST = &quot;((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.START_DATE &lt; %s AND TIME_RANGE.END_DATE &gt; %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.START_DATE &lt; %s AND TIME_RANGE.END_DATE &gt; %s))&quot;
-    TIMESPANTEST_NOEND = &quot;((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.END_DATE &gt; %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.END_DATE &gt; %s))&quot;
-    TIMESPANTEST_NOSTART = &quot;((TIME_RANGE.FLOATING = FALSE AND TIME_RANGE.START_DATE &lt; %s) OR (TIME_RANGE.FLOATING = TRUE AND TIME_RANGE.START_DATE &lt; %s))&quot;
-    TIMESPANTEST_TAIL_PIECE = &quot; AND TIME_RANGE.CALENDAR_OBJECT_RESOURCE_ID = CALENDAR_OBJECT.RESOURCE_ID AND TIME_RANGE.CALENDAR_RESOURCE_ID = %s&quot;
-    TIMESPANTEST_JOIN_ON_PIECE = &quot;TIME_RANGE.INSTANCE_ID = TRANSPARENCY.TIME_RANGE_INSTANCE_ID AND TRANSPARENCY.USER_ID = %s&quot;
-
-    def generate(self):
-        &quot;&quot;&quot;
-        Generate the actual SQL 'where ...' expression from the passed in
-        expression tree.
-
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the
-            partial SQL statement, and the C{list} is the list of argument
-            substitutions to use with the SQL API execute method.
-        &quot;&quot;&quot;
-
-        # Init state
-        self.sout = StringIO.StringIO()
-        self.arguments = []
-        self.substitutions = []
-        self.usedtimespan = False
-
-        # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
-        if self.calendarid:
-
-            test = expression.isExpression(&quot;CALENDAR_OBJECT.CALENDAR_RESOURCE_ID&quot;, str(self.calendarid), True)
-
-            # Since timerange expression already have the calendar resource-id test in them, do not
-            # add the additional term to those. When the additional term is added, add it as the first
-            # component in the AND expression to hopefully get the DB to use its index first
-
-            # Top-level timerange expression already has calendar resource-id restriction in it
-            if isinstance(self.expression, expression.timerangeExpression):
-                pass
-
-            # Top-level OR - check each component
-            elif isinstance(self.expression, expression.orExpression):
-
-                def _hasTopLevelTimerange(testexpr):
-                    if isinstance(testexpr, expression.timerangeExpression):
-                        return True
-                    elif isinstance(testexpr, expression.andExpression):
-                        return any([isinstance(expr, expression.timerangeExpression) for expr in testexpr.expressions])
-                    else:
-                        return False
-
-                hasTimerange = any([_hasTopLevelTimerange(expr) for expr in self.expression.expressions])
-
-                if hasTimerange:
-                    # timerange expression forces a join on calendarid
-                    pass
-                else:
-                    # AND the whole thing with calendarid
-                    self.expression = test.andWith(self.expression)
-
-            # Top-level AND - only add additional expression if timerange not present
-            elif isinstance(self.expression, expression.andExpression):
-                hasTimerange = any([isinstance(expr, expression.timerangeExpression) for expr in self.expression.expressions])
-                if not hasTimerange:
-                    # AND the whole thing
-                    self.expression = test.andWith(self.expression)
-
-            # Just AND the entire thing
-            else:
-                self.expression = test.andWith(self.expression)
-
-        # Generate ' where ...' partial statement
-        self.generateExpression(self.expression)
-
-        # Prefix with ' from ...' partial statement
-        select = self.FROM + self.RESOURCEDB
-        if self.usedtimespan:
-
-            # Free busy needs transparency join
-            if self.freebusy:
-                self.frontArgument(self.userid)
-                select += &quot;, %s LEFT OUTER JOIN %s ON (%s)&quot; % (
-                    self.TIMESPANDB,
-                    self.TRANSPARENCYDB,
-                    self.TIMESPANTEST_JOIN_ON_PIECE
-                )
-            else:
-                select += &quot;, %s&quot; % (
-                    self.TIMESPANDB,
-                )
-        select += self.WHERE
-        if self.usedtimespan:
-            select += &quot;(&quot;
-        select += self.sout.getvalue()
-        if self.usedtimespan:
-            if self.calendarid:
-                self.setArgument(self.calendarid)
-            select += &quot;)%s&quot; % (self.TIMESPANTEST_TAIL_PIECE,)
-
-        select = select % tuple(self.substitutions)
-
-        return select, self.arguments
-
-
-
-class FormatParamStyleMixin(object):
-    &quot;&quot;&quot;
-    Mixin for overriding methods on sqlgenerator that generate arguments
-    according to format/pyformat rules rather than the base class's 'numeric'
-    rules.
-    &quot;&quot;&quot;
-
-    def addArgument(self, arg):
-        self.arguments.append(arg)
-        self.substitutions.append(&quot;%s&quot;)
-        self.sout.write(&quot;%s&quot;)
-
-
-    def setArgument(self, arg):
-        self.arguments.append(arg)
-        self.substitutions.append(&quot;%s&quot;)
-
-
-    def frontArgument(self, arg):
-        self.arguments.insert(0, arg)
-        self.substitutions.insert(0, &quot;%s&quot;)
-
-
-
-class postgresqlgenerator(FormatParamStyleMixin, CalDAVSQLBehaviorMixin,
-                          sqlgenerator):
-    &quot;&quot;&quot;
-    Query generator for PostgreSQL indexed searches.
-    &quot;&quot;&quot;
-
-
-
-def fixbools(sqltext):
-    return sqltext.replace(&quot;TRUE&quot;, &quot;1&quot;).replace(&quot;FALSE&quot;, &quot;0&quot;)
-
-
-
-class oraclesqlgenerator(CalDAVSQLBehaviorMixin, sqlgenerator):
-    &quot;&quot;&quot;
-    Query generator for Oracle indexed searches.
-    &quot;&quot;&quot;
-    TIMESPANTEST = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST)
-    TIMESPANTEST_NOEND = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST_NOEND)
-    TIMESPANTEST_NOSTART = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST_NOSTART)
-    TIMESPANTEST_TAIL_PIECE = fixbools(
-        CalDAVSQLBehaviorMixin.TIMESPANTEST_TAIL_PIECE)
-    TIMESPANTEST_JOIN_ON_PIECE = fixbools(
-        CalDAVSQLBehaviorMixin.TIMESPANTEST_JOIN_ON_PIECE)
-
-
-
-class LegacyIndexHelper(object):
-    log = Logger()
-
-    @inlineCallbacks
-    def isAllowedUID(self, uid, *names):
-        &quot;&quot;&quot;
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        &quot;&quot;&quot;
-        rname = yield self.resourceNameForUID(uid)
-        returnValue(rname is None or rname in names)
-
-
-    def reserveUID(self, uid):
-        return self.reserver.reserveUID(uid)
-
-
-    def unreserveUID(self, uid):
-        return self.reserver.unreserveUID(uid)
-
-
-    def isReservedUID(self, uid):
-        return self.reserver.isReservedUID(uid)
-
-
-
-class PostgresLegacyIndexEmulator(LegacyIndexHelper):
-    &quot;&quot;&quot;
-    Emulator for L{twistedcaldv.index.Index} and
-    L{twistedcaldv.index.IndexSchedule}.
-    &quot;&quot;&quot;
-
-    def __init__(self, calendar):
-        self.resource = self.calendar = calendar
-        if (
-            hasattr(config, &quot;Memcached&quot;) and
-            config.Memcached.Pools.Default.ClientEnabled
-        ):
-            self.reserver = MemcachedUIDReserver(self)
-        else:
-            # This is only used with unit tests
-            self.reserver = DummyUIDReserver(self)
-
-    _objectSchema = schema.CALENDAR_OBJECT
-
-    @property
-    def _txn(self):
-        return self.calendar._txn
-
-
-    @inlineCallbacks
-    def isAllowedUID(self, uid, *names):
-        &quot;&quot;&quot;
-        Checks to see whether to allow an operation which would add the
-        specified UID to the index.  Specifically, the operation may not
-        violate the constraint that UIDs must be unique.
-        @param uid: the UID to check
-        @param names: the names of resources being replaced or deleted by the
-            operation; UIDs associated with these resources are not checked.
-        @return: True if the UID is not in the index and is not reserved,
-            False otherwise.
-        &quot;&quot;&quot;
-        rname = yield self.resourceNameForUID(uid)
-        returnValue(rname is None or rname in names)
-
-
-    @inlineCallbacks
-    def resourceUIDForName(self, name):
-        uid = yield self.calendar.resourceUIDForName(name)
-        returnValue(uid)
-
-
-    @inlineCallbacks
-    def resourceNameForUID(self, uid):
-        name = yield self.calendar.resourceNameForUID(uid)
-        returnValue(name)
-
-
-    @classproperty
-    def _notExpandedWithinQuery(cls): #@NoSelf
-        &quot;&quot;&quot;
-        DAL query to satisfy L{PostgresLegacyIndexEmulator.notExpandedBeyond}.
-        &quot;&quot;&quot;
-        co = schema.CALENDAR_OBJECT
-        return Select(
-            [co.RESOURCE_NAME],
-            From=co,
-            Where=((co.RECURRANCE_MIN &gt; Parameter(&quot;minDate&quot;))
-                .Or(co.RECURRANCE_MAX &lt; Parameter(&quot;maxDate&quot;)))
-                .And(co.CALENDAR_RESOURCE_ID == Parameter(&quot;resourceID&quot;))
-        )
-
-
-    @inlineCallbacks
-    def notExpandedWithin(self, minDate, maxDate):
-        &quot;&quot;&quot;
-        Gives all resources which have not been expanded beyond a given date
-        in the database.  (Unused; see above L{postgresqlgenerator}.
-        &quot;&quot;&quot;
-        returnValue([row[0] for row in (
-            yield self._notExpandedWithinQuery.on(
-                self._txn,
-                minDate=pyCalendarTodatetime(normalizeForIndex(minDate)) if minDate is not None else None,
-                maxDate=pyCalendarTodatetime(normalizeForIndex(maxDate)),
-                resourceID=self.calendar._resourceID))]
-        )
-
-
-    @inlineCallbacks
-    def reExpandResource(self, name, expand_start, expand_end):
-        &quot;&quot;&quot;
-        Given a resource name, remove it from the database and re-add it
-        with a longer expansion.
-        &quot;&quot;&quot;
-        obj = yield self.calendar.calendarObjectWithName(name)
-
-        # Use a new transaction to do this update quickly without locking the row for too long. However, the original
-        # transaction may have the row locked, so use wait=False and if that fails, fall back to using the original txn.
-
-        newTxn = obj.transaction().store().newTransaction()
-        try:
-            yield obj.lock(wait=False, txn=newTxn)
-        except NoSuchObjectResourceError:
-            yield newTxn.commit()
-            returnValue(None)
-        except:
-            yield newTxn.abort()
-            newTxn = None
-
-        # Now do the re-expand using the appropriate transaction
-        try:
-            doExpand = False
-            if newTxn is None:
-                doExpand = True
-            else:
-                # We repeat this check because the resource may have been re-expanded by someone else
-                rmin, rmax = (yield obj.recurrenceMinMax(txn=newTxn))
-
-                # If the resource is not fully expanded, see if within the required range or not.
-                # Note that expand_start could be None if no lower limit is applied, but expand_end will
-                # never be None
-                if rmax is not None and rmax &lt; expand_end:
-                    doExpand = True
-                if rmin is not None and expand_start is not None and rmin &gt; expand_start:
-                    doExpand = True
-
-            if doExpand:
-                yield obj.updateDatabase(
-                    (yield obj.component()),
-                    expand_until=expand_end,
-                    reCreate=True,
-                    txn=newTxn,
-                )
-        finally:
-            if newTxn is not None:
-                yield newTxn.commit()
-
-
-    @inlineCallbacks
-    def testAndUpdateIndex(self, minDate, maxDate):
-        # Find out if the index is expanded far enough
-        names = yield self.notExpandedWithin(minDate, maxDate)
-
-        # Actually expand recurrence max
-        for name in names:
-            self.log.info(&quot;Search falls outside range of index for %s %s to %s&quot; %
-                          (name, minDate, maxDate))
-            yield self.reExpandResource(name, minDate, maxDate)
-
-
-    @inlineCallbacks
-    def indexedSearch(self, filter, useruid='', fbtype=False):
-        &quot;&quot;&quot;
-        Finds resources matching the given qualifiers.
-
-        @param filter: the L{Filter} for the calendar-query to execute.
-
-        @return: a L{Deferred} which fires with an iterable of tuples for each
-            resource matching the given C{qualifiers}. The tuples are C{(name,
-            uid, type)}, where C{name} is the resource name, C{uid} is the
-            resource UID, and C{type} is the resource iCalendar component type.
-        &quot;&quot;&quot;
-        # Detect which style of parameter-generation we're using.  Naming is a
-        # little off here, because the reason we're using the numeric one is
-        # that it happens to be used by the oracle binding that we're using,
-        # whereas the postgres binding happens to use the 'pyformat' (e.g. %s)
-        # parameter style.
-        if self.calendar._txn.paramstyle == 'numeric':
-            generator = oraclesqlgenerator
-        else:
-            generator = postgresqlgenerator
-        # Make sure we have a proper Filter element and get the partial SQL
-        # statement to use.
-        if isinstance(filter, calendarqueryfilter.Filter):
-            qualifiers = calendarquery.sqlcalendarquery(
-                filter, self.calendar._resourceID, useruid, fbtype,
-                generator=generator
-            )
-            if qualifiers is not None:
-
-                today = DateTime.getToday()
-
-                # Determine how far we need to extend the current expansion of
-                # events. If we have an open-ended time-range we will expand
-                # one year past the start. That should catch bounded
-                # recurrences - unbounded will have been indexed with an
-                # &quot;infinite&quot; value always included.
-                maxDate, isStartDate = filter.getmaxtimerange()
-                if maxDate:
-                    maxDate = maxDate.duplicate()
-                    maxDate.offsetDay(1)
-                    maxDate.setDateOnly(True)
-                    upperLimit = today + Duration(days=config.FreeBusyIndexExpandMaxDays)
-                    if maxDate &gt; upperLimit:
-                        raise TimeRangeUpperLimit(upperLimit)
-                    if isStartDate:
-                        maxDate += Duration(days=365)
-
-                # Determine if the start date is too early for the restricted range we
-                # are applying. If it is today or later we don't need to worry about truncation
-                # in the past.
-                minDate, _ignore_isEndDate = filter.getmintimerange()
-                if minDate &gt;= today:
-                    minDate = None
-                if minDate is not None and config.FreeBusyIndexLowerLimitDays:
-                    truncateLowerLimit = today - Duration(days=config.FreeBusyIndexLowerLimitDays)
-                    if minDate &lt; truncateLowerLimit:
-                        raise TimeRangeLowerLimit(truncateLowerLimit)
-
-                if maxDate is not None or minDate is not None:
-                    yield self.testAndUpdateIndex(minDate, maxDate)
-
-            else:
-                # We cannot handle this filter in an indexed search
-                raise IndexedSearchException()
-        else:
-            qualifiers = None
-
-        # Perform the search
-        if qualifiers is None:
-            rowiter = yield self.bruteForceSearch()
-        else:
-            if fbtype:
-                # For a free-busy time-range query we return all instances
-                rowiter = yield self._txn.execSQL(
-                    &quot;&quot;&quot;
-                    select DISTINCT
-                        CALENDAR_OBJECT.RESOURCE_NAME,
-                        CALENDAR_OBJECT.ICALENDAR_UID,
-                        CALENDAR_OBJECT.ICALENDAR_TYPE,
-                        CALENDAR_OBJECT.ORGANIZER,
-                        TIME_RANGE.FLOATING, TIME_RANGE.START_DATE,
-                        TIME_RANGE.END_DATE, TIME_RANGE.FBTYPE,
-                        TIME_RANGE.TRANSPARENT, TRANSPARENCY.TRANSPARENT
-                    &quot;&quot;&quot; +
-                    qualifiers[0],
-                    qualifiers[1]
-                )
-            else:
-                rowiter = yield self._txn.execSQL(
-                    &quot;&quot;&quot;
-                    select
-                        DISTINCT CALENDAR_OBJECT.RESOURCE_NAME,
-                        CALENDAR_OBJECT.ICALENDAR_UID,
-                        CALENDAR_OBJECT.ICALENDAR_TYPE
-                    &quot;&quot;&quot; +
-                    qualifiers[0],
-                    qualifiers[1]
-                )
-
-        # Check result for missing resources
-
-        results = []
-        for row in rowiter:
-            if fbtype:
-                row = list(row)
-                row[4] = 'Y' if row[4] else 'N'
-                row[7] = indexfbtype_to_icalfbtype[row[7]]
-                if row[9] is not None:
-                    row[8] = row[9]
-                row[8] = 'T' if row[8] else 'F'
-                del row[9]
-            results.append(row)
-        returnValue(results)
-
-
-    @classproperty
-    def _bruteForceQuery(cls): #@NoSelf
-        &quot;&quot;&quot;
-        DAL query for all C{CALENDAR_OBJECT} rows in the calendar represented by
-        this index.
-        &quot;&quot;&quot;
-        obj = cls._objectSchema
-        return Select(
-            [obj.RESOURCE_NAME, obj.ICALENDAR_UID, obj.ICALENDAR_TYPE],
-            From=obj, Where=obj.PARENT_RESOURCE_ID == Parameter(&quot;resourceID&quot;)
-        )
-
-
-    def bruteForceSearch(self):
-        return self._bruteForceQuery.on(
-            self._txn, resourceID=self.resource._resourceID)
-
-
-    @inlineCallbacks
-    def resourcesExist(self, names):
-        returnValue(list(set(names).intersection(
-            set((yield self.calendar.listCalendarObjects())))))
-
-
-    @classproperty
-    def _resourceExistsQuery(cls): #@NoSelf
-        &quot;&quot;&quot;
-        DAL query to determine whether a calendar object exists in the
-        collection represented by this index.
-        &quot;&quot;&quot;
-        obj = cls._objectSchema
-        return Select(
-            [obj.RESOURCE_NAME], From=obj,
-            Where=(obj.RESOURCE_NAME == Parameter(&quot;name&quot;))
-            .And(obj.PARENT_RESOURCE_ID == Parameter(&quot;resourceID&quot;))
-        )
-
-
-    @inlineCallbacks
-    def resourceExists(self, name):
-        returnValue((bool(
-            (yield self._resourceExistsQuery.on(
-                self._txn, name=name, resourceID=self.resource._resourceID))
-        )))
-
-
-
-class PostgresLegacyInboxIndexEmulator(PostgresLegacyIndexEmulator):
-    &quot;&quot;&quot;
-    UIDs need not be unique in the 'inbox' calendar, so override those
-    behaviors intended to ensure that.
-    &quot;&quot;&quot;
-
-    def isAllowedUID(self):
-        return succeed(True)
-
-
-    def reserveUID(self, uid):
-        return succeed(None)
-
-
-    def unreserveUID(self, uid):
-        return succeed(None)
-
-
-    def isReservedUID(self, uid):
-        return succeed(False)
-
-
-
-# CARDDAV
-
-class CardDAVSQLBehaviorMixin(RealSQLBehaviorMixin):
-    &quot;&quot;&quot;
-    Query generator for CardDAV indexed searches.
-    &quot;&quot;&quot;
-
-    FIELDS = {
-        &quot;UID&quot;: &quot;ADDRESSBOOK_OBJECT.VCARD_UID&quot;,
-    }
-    RESOURCEDB = &quot;ADDRESSBOOK_OBJECT&quot;
-
-    def generate(self):
-        &quot;&quot;&quot;
-        Generate the actual SQL 'where ...' expression from the passed in
-        expression tree.
-
-        @return: a C{tuple} of (C{str}, C{list}), where the C{str} is the
-            partial SQL statement, and the C{list} is the list of argument
-            substitutions to use with the SQL API execute method.
-        &quot;&quot;&quot;
-
-        # Init state
-        self.sout = StringIO.StringIO()
-        self.arguments = []
-        self.substitutions = []
-
-        # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
-        if self.calendarid:
-
-            # AND the whole thing
-            test = expression.isExpression(&quot;ADDRESSBOOK_OBJECT.ADDRESSBOOK_HOME_RESOURCE_ID&quot;, str(self.calendarid), True)
-            self.expression = test.andWith(self.expression)
-
-        # Generate ' where ...' partial statement
-        self.sout.write(self.WHERE)
-        self.generateExpression(self.expression)
-
-        # Prefix with ' from ...' partial statement
-        select = self.FROM + self.RESOURCEDB
-        select += self.sout.getvalue()
-
-        select = select % tuple(self.substitutions)
-
-        return select, self.arguments
-
-
-
-class postgresqladbkgenerator(FormatParamStyleMixin, CardDAVSQLBehaviorMixin, sqlgenerator):
-    &quot;&quot;&quot;
-    Query generator for PostgreSQL indexed searches.
-    &quot;&quot;&quot;
-
-
-
-class oraclesqladbkgenerator(CardDAVSQLBehaviorMixin, sqlgenerator):
-    &quot;&quot;&quot;
-    Query generator for Oracle indexed searches.
-    &quot;&quot;&quot;
-
-
-
-class PostgresLegacyABIndexEmulator(LegacyIndexHelper):
-    &quot;&quot;&quot;
-    Emulator for L{twistedcaldv.index.Index} and
-    L{twistedcaldv.index.IndexSchedule}.
-    &quot;&quot;&quot;
-
-    _objectSchema = schema.ADDRESSBOOK_OBJECT
-
-    def __init__(self, addressbook):
-        self.resource = self.addressbook = addressbook
-        if (
-            hasattr(config, &quot;Memcached&quot;) and
-            config.Memcached.Pools.Default.ClientEnabled
-        ):
-            self.reserver = MemcachedUIDReserver(self)
-        else:
-            # This is only used with unit tests
-            self.reserver = DummyUIDReserver(self)
-
-
-    @property
-    def _txn(self):
-        return self.addressbook._txn
-
-
-    @inlineCallbacks
-    def resourceUIDForName(self, name):
-        obj = yield self.addressbook.addressbookObjectWithName(name)
-        if obj is None:
-            returnValue(None)
-        returnValue(obj.uid())
-
-
-    @inlineCallbacks
-    def resourceNameForUID(self, uid):
-        obj = yield self.addressbook.addressbookObjectWithUID(uid)
-        if obj is None:
-            returnValue(None)
-        returnValue(obj.name())
-
-
-    def searchValid(self, filter):
-        if isinstance(filter, addressbookqueryfilter.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(filter)
-        else:
-            qualifiers = None
-
-        return qualifiers is not None
-
-
-    @inlineCallbacks
-    def search(self, filter):
-        &quot;&quot;&quot;
-        Finds resources matching the given qualifiers.
-        @param filter: the L{Filter} for the addressbook-query to execute.
-        @return: an iterable of tuples for each resource matching the
-            given C{qualifiers}. The tuples are C{(name, uid, type)}, where
-            C{name} is the resource name, C{uid} is the resource UID, and
-            C{type} is the resource iCalendar component type.x
-        &quot;&quot;&quot;
-        if self.addressbook._txn.paramstyle == 'numeric':
-            generator = oraclesqladbkgenerator
-        else:
-            generator = postgresqladbkgenerator
-        # Make sure we have a proper Filter element and get the partial SQL statement to use.
-        if isinstance(filter, addressbookqueryfilter.Filter):
-            qualifiers = addressbookquery.sqladdressbookquery(
-                filter, self.addressbook._resourceID, generator=generator)
-        else:
-            qualifiers = None
-        if qualifiers is not None:
-            rowiter = yield self._txn.execSQL(
-                &quot;select DISTINCT ADDRESSBOOK_OBJECT.RESOURCE_NAME, ADDRESSBOOK_OBJECT.VCARD_UID&quot; +
-                qualifiers[0],
-                qualifiers[1]
-            )
-        else:
-            rowiter = yield Select(
-                [self._objectSchema.RESOURCE_NAME,
-                 self._objectSchema.VCARD_UID],
-                From=self._objectSchema,
-                Where=self._objectSchema.ADDRESSBOOK_HOME_RESOURCE_ID ==
-                self.addressbook._resourceID
-            ).on(self.addressbook._txn)
-
-        returnValue(list(rowiter))
-
-
-    def indexedSearch(self, filter, useruid='', fbtype=False):
-        &quot;&quot;&quot;
-        Always raise L{IndexedSearchException}, since these indexes are not
-        fully implemented yet.
-        &quot;&quot;&quot;
-        raise IndexedSearchException()
-
-
-    @inlineCallbacks
-    def resourcesExist(self, names):
-        returnValue(list(set(names).intersection(
-            set((yield self.addressbook.listAddressBookObjects())))))
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_schemacurrentoracledialectsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -18,9 +18,17 @@
</span><span class="cx"> create table CALENDAR_HOME (
</span><span class="cx">     &quot;RESOURCE_ID&quot; integer primary key,
</span><span class="cx">     &quot;OWNER_UID&quot; nvarchar2(255) unique,
</span><ins>+    &quot;STATUS&quot; integer default 0 not null,
</ins><span class="cx">     &quot;DATAVERSION&quot; integer default 0 not null
</span><span class="cx"> );
</span><span class="cx"> 
</span><ins>+create table HOME_STATUS (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into HOME_STATUS (DESCRIPTION, ID) values ('normal', 0);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('external', 1);
</ins><span class="cx"> create table CALENDAR (
</span><span class="cx">     &quot;RESOURCE_ID&quot; integer primary key
</span><span class="cx"> );
</span><span class="lines">@@ -50,6 +58,7 @@
</span><span class="cx"> create table NOTIFICATION_HOME (
</span><span class="cx">     &quot;RESOURCE_ID&quot; integer primary key,
</span><span class="cx">     &quot;OWNER_UID&quot; nvarchar2(255) unique,
</span><ins>+    &quot;STATUS&quot; integer default 0 not null,
</ins><span class="cx">     &quot;DATAVERSION&quot; integer default 0 not null
</span><span class="cx"> );
</span><span class="cx"> 
</span><span class="lines">@@ -210,6 +219,7 @@
</span><span class="cx">     &quot;RESOURCE_ID&quot; integer primary key,
</span><span class="cx">     &quot;ADDRESSBOOK_PROPERTY_STORE_ID&quot; integer not null,
</span><span class="cx">     &quot;OWNER_UID&quot; nvarchar2(255) unique,
</span><ins>+    &quot;STATUS&quot; integer default 0 not null,
</ins><span class="cx">     &quot;DATAVERSION&quot; integer default 0 not null
</span><span class="cx"> );
</span><span class="cx"> 
</span><span class="lines">@@ -373,7 +383,7 @@
</span><span class="cx">     &quot;VALUE&quot; nvarchar2(255)
</span><span class="cx"> );
</span><span class="cx"> 
</span><del>-insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '30');
</del><ins>+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '31');
</ins><span class="cx"> insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '5');
</span><span class="cx"> insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
</span><span class="cx"> insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_schemacurrentsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -53,10 +53,22 @@
</span><span class="cx"> 
</span><span class="cx"> create table CALENDAR_HOME (
</span><span class="cx">   RESOURCE_ID      integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
</span><del>-  OWNER_UID        varchar(255) not null unique,                                 -- implicit index
</del><ins>+  OWNER_UID        varchar(255) not null unique,                                -- implicit index
+  STATUS           integer      default 0 not null,                             -- enum HOME_STATUS
</ins><span class="cx">   DATAVERSION      integer      default 0 not null
</span><span class="cx"> );
</span><span class="cx"> 
</span><ins>+-- Enumeration of statuses
+
+create table HOME_STATUS (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into HOME_STATUS values (0, 'normal' );
+insert into HOME_STATUS values (1, 'external');
+
+
</ins><span class="cx"> --------------
</span><span class="cx"> -- Calendar --
</span><span class="cx"> --------------
</span><span class="lines">@@ -65,6 +77,7 @@
</span><span class="cx">   RESOURCE_ID integer   primary key default nextval('RESOURCE_ID_SEQ') -- implicit index
</span><span class="cx"> );
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> ----------------------------
</span><span class="cx"> -- Calendar Home Metadata --
</span><span class="cx"> ----------------------------
</span><span class="lines">@@ -91,6 +104,7 @@
</span><span class="cx"> create index CALENDAR_HOME_METADATA_DEFAULT_POLLS on
</span><span class="cx">         CALENDAR_HOME_METADATA(DEFAULT_POLLS);
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> -----------------------
</span><span class="cx"> -- Calendar Metadata --
</span><span class="cx"> -----------------------
</span><span class="lines">@@ -110,6 +124,7 @@
</span><span class="cx"> create table NOTIFICATION_HOME (
</span><span class="cx">   RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
</span><span class="cx">   OWNER_UID   varchar(255) not null unique,                                -- implicit index
</span><ins>+  STATUS      integer      default 0 not null,                             -- enum HOME_STATUS
</ins><span class="cx">   DATAVERSION integer      default 0 not null
</span><span class="cx"> );
</span><span class="cx"> 
</span><span class="lines">@@ -139,6 +154,7 @@
</span><span class="cx"> create table CALENDAR_BIND (
</span><span class="cx">   CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
</span><span class="cx">   CALENDAR_RESOURCE_ID      integer      not null references CALENDAR on delete cascade,
</span><ins>+  EXTERNAL_ID                            integer      default null,
</ins><span class="cx">   CALENDAR_RESOURCE_NAME    varchar(255) not null,
</span><span class="cx">   BIND_MODE                 integer      not null, -- enum CALENDAR_BIND_MODE
</span><span class="cx">   BIND_STATUS               integer      not null, -- enum CALENDAR_BIND_STATUS
</span><span class="lines">@@ -380,6 +396,7 @@
</span><span class="cx">   RESOURCE_ID                                      integer                        primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
</span><span class="cx">   ADDRESSBOOK_PROPERTY_STORE_ID        integer              default nextval('RESOURCE_ID_SEQ') not null,         -- implicit index
</span><span class="cx">   OWNER_UID                                        varchar(255)         not null unique,                                -- implicit index
</span><ins>+  STATUS                                           integer              default 0 not null,                             -- enum HOME_STATUS
</ins><span class="cx">   DATAVERSION                                      integer              default 0 not null
</span><span class="cx"> );
</span><span class="cx"> 
</span><span class="lines">@@ -405,6 +422,7 @@
</span><span class="cx"> create table SHARED_ADDRESSBOOK_BIND (
</span><span class="cx">   ADDRESSBOOK_HOME_RESOURCE_ID                        integer                        not null references ADDRESSBOOK_HOME,
</span><span class="cx">   OWNER_HOME_RESOURCE_ID                            integer              not null references ADDRESSBOOK_HOME on delete cascade,
</span><ins>+  EXTERNAL_ID                                        integer         default null,
</ins><span class="cx">   ADDRESSBOOK_RESOURCE_NAME                            varchar(255)         not null,
</span><span class="cx">   BIND_MODE                                            integer              not null,        -- enum CALENDAR_BIND_MODE
</span><span class="cx">   BIND_STATUS                                          integer              not null,        -- enum CALENDAR_BIND_STATUS
</span><span class="lines">@@ -503,6 +521,7 @@
</span><span class="cx"> create table SHARED_GROUP_BIND (        
</span><span class="cx">   ADDRESSBOOK_HOME_RESOURCE_ID                 integer      not null references ADDRESSBOOK_HOME,
</span><span class="cx">   GROUP_RESOURCE_ID                              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,
</span><ins>+  EXTERNAL_ID                                    integer      default null,
</ins><span class="cx">   GROUP_ADDRESSBOOK_NAME                        varchar(255) not null,
</span><span class="cx">   BIND_MODE                                    integer      not null, -- enum CALENDAR_BIND_MODE
</span><span class="cx">   BIND_STATUS                                  integer      not null, -- enum CALENDAR_BIND_STATUS
</span><span class="lines">@@ -711,7 +730,7 @@
</span><span class="cx">   VALUE                         varchar(255)
</span><span class="cx"> );
</span><span class="cx"> 
</span><del>-insert into CALENDARSERVER values ('VERSION', '30');
</del><ins>+insert into CALENDARSERVER values ('VERSION', '31');
</ins><span class="cx"> insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
</span><span class="cx"> insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
</span><span class="cx"> insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_schemaoldoracledialectv30sql"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v30.sql (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v30.sql                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v30.sql        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,508 @@
</span><ins>+create sequence RESOURCE_ID_SEQ;
+create sequence INSTANCE_ID_SEQ;
+create sequence ATTACHMENT_ID_SEQ;
+create sequence REVISION_SEQ;
+create sequence WORKITEM_SEQ;
+create table NODE_INFO (
+    &quot;HOSTNAME&quot; nvarchar2(255),
+    &quot;PID&quot; integer not null,
+    &quot;PORT&quot; integer not null,
+    &quot;TIME&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC' not null, 
+    primary key(&quot;HOSTNAME&quot;, &quot;PORT&quot;)
+);
+
+create table NAMED_LOCK (
+    &quot;LOCK_NAME&quot; nvarchar2(255) primary key
+);
+
+create table CALENDAR_HOME (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;OWNER_UID&quot; nvarchar2(255) unique,
+    &quot;DATAVERSION&quot; integer default 0 not null
+);
+
+create table CALENDAR (
+    &quot;RESOURCE_ID&quot; integer primary key
+);
+
+create table CALENDAR_HOME_METADATA (
+    &quot;RESOURCE_ID&quot; integer primary key references CALENDAR_HOME on delete cascade,
+    &quot;QUOTA_USED_BYTES&quot; integer default 0 not null,
+    &quot;DEFAULT_EVENTS&quot; integer default null references CALENDAR on delete set null,
+    &quot;DEFAULT_TASKS&quot; integer default null references CALENDAR on delete set null,
+    &quot;DEFAULT_POLLS&quot; integer default null references CALENDAR on delete set null,
+    &quot;ALARM_VEVENT_TIMED&quot; nclob default null,
+    &quot;ALARM_VEVENT_ALLDAY&quot; nclob default null,
+    &quot;ALARM_VTODO_TIMED&quot; nclob default null,
+    &quot;ALARM_VTODO_ALLDAY&quot; nclob default null,
+    &quot;AVAILABILITY&quot; nclob default null,
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table CALENDAR_METADATA (
+    &quot;RESOURCE_ID&quot; integer primary key references CALENDAR on delete cascade,
+    &quot;SUPPORTED_COMPONENTS&quot; nvarchar2(255) default null,
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table NOTIFICATION_HOME (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;OWNER_UID&quot; nvarchar2(255) unique,
+    &quot;DATAVERSION&quot; integer default 0 not null
+);
+
+create table NOTIFICATION (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;NOTIFICATION_HOME_RESOURCE_ID&quot; integer not null references NOTIFICATION_HOME,
+    &quot;NOTIFICATION_UID&quot; nvarchar2(255),
+    &quot;NOTIFICATION_TYPE&quot; nvarchar2(255),
+    &quot;NOTIFICATION_DATA&quot; nclob,
+    &quot;MD5&quot; nchar(32),
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique(&quot;NOTIFICATION_UID&quot;, &quot;NOTIFICATION_HOME_RESOURCE_ID&quot;)
+);
+
+create table CALENDAR_BIND (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME,
+    &quot;CALENDAR_RESOURCE_ID&quot; integer not null references CALENDAR on delete cascade,
+    &quot;CALENDAR_RESOURCE_NAME&quot; nvarchar2(255),
+    &quot;BIND_MODE&quot; integer not null,
+    &quot;BIND_STATUS&quot; integer not null,
+    &quot;BIND_REVISION&quot; integer default 0 not null,
+    &quot;MESSAGE&quot; nclob,
+    &quot;TRANSP&quot; integer default 0 not null,
+    &quot;ALARM_VEVENT_TIMED&quot; nclob default null,
+    &quot;ALARM_VEVENT_ALLDAY&quot; nclob default null,
+    &quot;ALARM_VTODO_TIMED&quot; nclob default null,
+    &quot;ALARM_VTODO_ALLDAY&quot; nclob default null,
+    &quot;TIMEZONE&quot; nclob default null, 
+    primary key(&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;CALENDAR_RESOURCE_ID&quot;), 
+    unique(&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;CALENDAR_RESOURCE_NAME&quot;)
+);
+
+create table CALENDAR_BIND_MODE (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('own', 0);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('write', 2);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('direct', 3);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('indirect', 4);
+create table CALENDAR_BIND_STATUS (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('invited', 0);
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('accepted', 1);
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('declined', 2);
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('invalid', 3);
+insert into CALENDAR_BIND_STATUS (DESCRIPTION, ID) values ('deleted', 4);
+create table CALENDAR_TRANSP (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into CALENDAR_TRANSP (DESCRIPTION, ID) values ('opaque', 0);
+insert into CALENDAR_TRANSP (DESCRIPTION, ID) values ('transparent', 1);
+create table CALENDAR_OBJECT (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;CALENDAR_RESOURCE_ID&quot; integer not null references CALENDAR on delete cascade,
+    &quot;RESOURCE_NAME&quot; nvarchar2(255),
+    &quot;ICALENDAR_TEXT&quot; nclob,
+    &quot;ICALENDAR_UID&quot; nvarchar2(255),
+    &quot;ICALENDAR_TYPE&quot; nvarchar2(255),
+    &quot;ATTACHMENTS_MODE&quot; integer default 0 not null,
+    &quot;DROPBOX_ID&quot; nvarchar2(255),
+    &quot;ORGANIZER&quot; nvarchar2(255),
+    &quot;RECURRANCE_MIN&quot; date,
+    &quot;RECURRANCE_MAX&quot; date,
+    &quot;ACCESS&quot; integer default 0 not null,
+    &quot;SCHEDULE_OBJECT&quot; integer default 0,
+    &quot;SCHEDULE_TAG&quot; nvarchar2(36) default null,
+    &quot;SCHEDULE_ETAGS&quot; nclob default null,
+    &quot;PRIVATE_COMMENTS&quot; integer default 0 not null,
+    &quot;MD5&quot; nchar(32),
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique(&quot;CALENDAR_RESOURCE_ID&quot;, &quot;RESOURCE_NAME&quot;)
+);
+
+create table CALENDAR_OBJECT_ATTACHMENTS_MO (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('none', 0);
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_OBJECT_ATTACHMENTS_MO (DESCRIPTION, ID) values ('write', 2);
+create table CALENDAR_ACCESS_TYPE (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(32) unique
+);
+
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('', 0);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('public', 1);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('private', 2);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('confidential', 3);
+insert into CALENDAR_ACCESS_TYPE (DESCRIPTION, ID) values ('restricted', 4);
+create table TIME_RANGE (
+    &quot;INSTANCE_ID&quot; integer primary key,
+    &quot;CALENDAR_RESOURCE_ID&quot; integer not null references CALENDAR on delete cascade,
+    &quot;CALENDAR_OBJECT_RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;FLOATING&quot; integer not null,
+    &quot;START_DATE&quot; timestamp not null,
+    &quot;END_DATE&quot; timestamp not null,
+    &quot;FBTYPE&quot; integer not null,
+    &quot;TRANSPARENT&quot; integer not null
+);
+
+create table FREE_BUSY_TYPE (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('unknown', 0);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('free', 1);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy', 2);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy-unavailable', 3);
+insert into FREE_BUSY_TYPE (DESCRIPTION, ID) values ('busy-tentative', 4);
+create table TRANSPARENCY (
+    &quot;TIME_RANGE_INSTANCE_ID&quot; integer not null references TIME_RANGE on delete cascade,
+    &quot;USER_ID&quot; nvarchar2(255),
+    &quot;TRANSPARENT&quot; integer not null
+);
+
+create table ATTACHMENT (
+    &quot;ATTACHMENT_ID&quot; integer primary key,
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME,
+    &quot;DROPBOX_ID&quot; nvarchar2(255),
+    &quot;CONTENT_TYPE&quot; nvarchar2(255),
+    &quot;SIZE&quot; integer not null,
+    &quot;MD5&quot; nchar(32),
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;PATH&quot; nvarchar2(1024)
+);
+
+create table ATTACHMENT_CALENDAR_OBJECT (
+    &quot;ATTACHMENT_ID&quot; integer not null references ATTACHMENT on delete cascade,
+    &quot;MANAGED_ID&quot; nvarchar2(255),
+    &quot;CALENDAR_OBJECT_RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade, 
+    primary key(&quot;ATTACHMENT_ID&quot;, &quot;CALENDAR_OBJECT_RESOURCE_ID&quot;), 
+    unique(&quot;MANAGED_ID&quot;, &quot;CALENDAR_OBJECT_RESOURCE_ID&quot;)
+);
+
+create table RESOURCE_PROPERTY (
+    &quot;RESOURCE_ID&quot; integer not null,
+    &quot;NAME&quot; nvarchar2(255),
+    &quot;VALUE&quot; nclob,
+    &quot;VIEWER_UID&quot; nvarchar2(255), 
+    primary key(&quot;RESOURCE_ID&quot;, &quot;NAME&quot;, &quot;VIEWER_UID&quot;)
+);
+
+create table ADDRESSBOOK_HOME (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;ADDRESSBOOK_PROPERTY_STORE_ID&quot; integer not null,
+    &quot;OWNER_UID&quot; nvarchar2(255) unique,
+    &quot;DATAVERSION&quot; integer default 0 not null
+);
+
+create table ADDRESSBOOK_HOME_METADATA (
+    &quot;RESOURCE_ID&quot; integer primary key references ADDRESSBOOK_HOME on delete cascade,
+    &quot;QUOTA_USED_BYTES&quot; integer default 0 not null,
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table SHARED_ADDRESSBOOK_BIND (
+    &quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_HOME,
+    &quot;OWNER_HOME_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_HOME on delete cascade,
+    &quot;ADDRESSBOOK_RESOURCE_NAME&quot; nvarchar2(255),
+    &quot;BIND_MODE&quot; integer not null,
+    &quot;BIND_STATUS&quot; integer not null,
+    &quot;BIND_REVISION&quot; integer default 0 not null,
+    &quot;MESSAGE&quot; nclob, 
+    primary key(&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;OWNER_HOME_RESOURCE_ID&quot;), 
+    unique(&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;ADDRESSBOOK_RESOURCE_NAME&quot;)
+);
+
+create table ADDRESSBOOK_OBJECT (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_HOME on delete cascade,
+    &quot;RESOURCE_NAME&quot; nvarchar2(255),
+    &quot;VCARD_TEXT&quot; nclob,
+    &quot;VCARD_UID&quot; nvarchar2(255),
+    &quot;KIND&quot; integer not null,
+    &quot;MD5&quot; nchar(32),
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique(&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;RESOURCE_NAME&quot;), 
+    unique(&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;VCARD_UID&quot;)
+);
+
+create table ADDRESSBOOK_OBJECT_KIND (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('person', 0);
+insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('group', 1);
+insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('resource', 2);
+insert into ADDRESSBOOK_OBJECT_KIND (DESCRIPTION, ID) values ('location', 3);
+create table ABO_MEMBERS (
+    &quot;GROUP_ID&quot; integer not null,
+    &quot;ADDRESSBOOK_ID&quot; integer not null references ADDRESSBOOK_HOME on delete cascade,
+    &quot;MEMBER_ID&quot; integer not null,
+    &quot;REVISION&quot; integer not null,
+    &quot;REMOVED&quot; integer default 0 not null, 
+    primary key(&quot;GROUP_ID&quot;, &quot;MEMBER_ID&quot;, &quot;REVISION&quot;)
+);
+
+create table ABO_FOREIGN_MEMBERS (
+    &quot;GROUP_ID&quot; integer not null references ADDRESSBOOK_OBJECT on delete cascade,
+    &quot;ADDRESSBOOK_ID&quot; integer not null references ADDRESSBOOK_HOME on delete cascade,
+    &quot;MEMBER_ADDRESS&quot; nvarchar2(255), 
+    primary key(&quot;GROUP_ID&quot;, &quot;MEMBER_ADDRESS&quot;)
+);
+
+create table SHARED_GROUP_BIND (
+    &quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_HOME,
+    &quot;GROUP_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_OBJECT on delete cascade,
+    &quot;GROUP_ADDRESSBOOK_NAME&quot; nvarchar2(255),
+    &quot;BIND_MODE&quot; integer not null,
+    &quot;BIND_STATUS&quot; integer not null,
+    &quot;BIND_REVISION&quot; integer default 0 not null,
+    &quot;MESSAGE&quot; nclob, 
+    primary key(&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;GROUP_RESOURCE_ID&quot;), 
+    unique(&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;GROUP_ADDRESSBOOK_NAME&quot;)
+);
+
+create table CALENDAR_OBJECT_REVISIONS (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME,
+    &quot;CALENDAR_RESOURCE_ID&quot; integer references CALENDAR,
+    &quot;CALENDAR_NAME&quot; nvarchar2(255) default null,
+    &quot;RESOURCE_NAME&quot; nvarchar2(255),
+    &quot;REVISION&quot; integer not null,
+    &quot;DELETED&quot; integer not null
+);
+
+create table ADDRESSBOOK_OBJECT_REVISIONS (
+    &quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot; integer not null references ADDRESSBOOK_HOME,
+    &quot;OWNER_HOME_RESOURCE_ID&quot; integer references ADDRESSBOOK_HOME,
+    &quot;ADDRESSBOOK_NAME&quot; nvarchar2(255) default null,
+    &quot;OBJECT_RESOURCE_ID&quot; integer default 0,
+    &quot;RESOURCE_NAME&quot; nvarchar2(255),
+    &quot;REVISION&quot; integer not null,
+    &quot;DELETED&quot; integer not null
+);
+
+create table NOTIFICATION_OBJECT_REVISIONS (
+    &quot;NOTIFICATION_HOME_RESOURCE_ID&quot; integer not null references NOTIFICATION_HOME on delete cascade,
+    &quot;RESOURCE_NAME&quot; nvarchar2(255),
+    &quot;REVISION&quot; integer not null,
+    &quot;DELETED&quot; integer not null, 
+    unique(&quot;NOTIFICATION_HOME_RESOURCE_ID&quot;, &quot;RESOURCE_NAME&quot;)
+);
+
+create table APN_SUBSCRIPTIONS (
+    &quot;TOKEN&quot; nvarchar2(255),
+    &quot;RESOURCE_KEY&quot; nvarchar2(255),
+    &quot;MODIFIED&quot; integer not null,
+    &quot;SUBSCRIBER_GUID&quot; nvarchar2(255),
+    &quot;USER_AGENT&quot; nvarchar2(255) default null,
+    &quot;IP_ADDR&quot; nvarchar2(255) default null, 
+    primary key(&quot;TOKEN&quot;, &quot;RESOURCE_KEY&quot;)
+);
+
+create table IMIP_TOKENS (
+    &quot;TOKEN&quot; nvarchar2(255),
+    &quot;ORGANIZER&quot; nvarchar2(255),
+    &quot;ATTENDEE&quot; nvarchar2(255),
+    &quot;ICALUID&quot; nvarchar2(255),
+    &quot;ACCESSED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    primary key(&quot;ORGANIZER&quot;, &quot;ATTENDEE&quot;, &quot;ICALUID&quot;)
+);
+
+create table IMIP_INVITATION_WORK (
+    &quot;WORK_ID&quot; integer primary key not null,
+    &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;FROM_ADDR&quot; nvarchar2(255),
+    &quot;TO_ADDR&quot; nvarchar2(255),
+    &quot;ICALENDAR_TEXT&quot; nclob
+);
+
+create table IMIP_POLLING_WORK (
+    &quot;WORK_ID&quot; integer primary key not null,
+    &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table IMIP_REPLY_WORK (
+    &quot;WORK_ID&quot; integer primary key not null,
+    &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;ORGANIZER&quot; nvarchar2(255),
+    &quot;ATTENDEE&quot; nvarchar2(255),
+    &quot;ICALENDAR_TEXT&quot; nclob
+);
+
+create table PUSH_NOTIFICATION_WORK (
+    &quot;WORK_ID&quot; integer primary key not null,
+    &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;PUSH_ID&quot; nvarchar2(255),
+    &quot;PRIORITY&quot; integer not null
+);
+
+create table GROUP_CACHER_POLLING_WORK (
+    &quot;WORK_ID&quot; integer primary key not null,
+    &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+    &quot;WORK_ID&quot; integer primary key not null,
+    &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table CALENDARSERVER (
+    &quot;NAME&quot; nvarchar2(255) primary key,
+    &quot;VALUE&quot; nvarchar2(255)
+);
+
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '30');
+insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '5');
+insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
+create index CALENDAR_HOME_METADAT_3cb9049e on CALENDAR_HOME_METADATA (
+    DEFAULT_EVENTS
+);
+
+create index CALENDAR_HOME_METADAT_d55e5548 on CALENDAR_HOME_METADATA (
+    DEFAULT_TASKS
+);
+
+create index CALENDAR_HOME_METADAT_910264ce on CALENDAR_HOME_METADATA (
+    DEFAULT_POLLS
+);
+
+create index NOTIFICATION_NOTIFICA_f891f5f9 on NOTIFICATION (
+    NOTIFICATION_HOME_RESOURCE_ID
+);
+
+create index CALENDAR_BIND_RESOURC_e57964d4 on CALENDAR_BIND (
+    CALENDAR_RESOURCE_ID
+);
+
+create index CALENDAR_OBJECT_CALEN_a9a453a9 on CALENDAR_OBJECT (
+    CALENDAR_RESOURCE_ID,
+    ICALENDAR_UID
+);
+
+create index CALENDAR_OBJECT_CALEN_96e83b73 on CALENDAR_OBJECT (
+    CALENDAR_RESOURCE_ID,
+    RECURRANCE_MAX
+);
+
+create index CALENDAR_OBJECT_ICALE_82e731d5 on CALENDAR_OBJECT (
+    ICALENDAR_UID
+);
+
+create index CALENDAR_OBJECT_DROPB_de041d80 on CALENDAR_OBJECT (
+    DROPBOX_ID
+);
+
+create index TIME_RANGE_CALENDAR_R_beb6e7eb on TIME_RANGE (
+    CALENDAR_RESOURCE_ID
+);
+
+create index TIME_RANGE_CALENDAR_O_acf37bd1 on TIME_RANGE (
+    CALENDAR_OBJECT_RESOURCE_ID
+);
+
+create index TRANSPARENCY_TIME_RAN_5f34467f on TRANSPARENCY (
+    TIME_RANGE_INSTANCE_ID
+);
+
+create index ATTACHMENT_CALENDAR_H_0078845c on ATTACHMENT (
+    CALENDAR_HOME_RESOURCE_ID
+);
+
+create index ATTACHMENT_CALENDAR_O_81508484 on ATTACHMENT_CALENDAR_OBJECT (
+    CALENDAR_OBJECT_RESOURCE_ID
+);
+
+create index SHARED_ADDRESSBOOK_BI_e9a2e6d4 on SHARED_ADDRESSBOOK_BIND (
+    OWNER_HOME_RESOURCE_ID
+);
+
+create index ABO_MEMBERS_ADDRESSBO_4effa879 on ABO_MEMBERS (
+    ADDRESSBOOK_ID
+);
+
+create index ABO_MEMBERS_MEMBER_ID_8d66adcf on ABO_MEMBERS (
+    MEMBER_ID
+);
+
+create index ABO_FOREIGN_MEMBERS_A_1fd2c5e9 on ABO_FOREIGN_MEMBERS (
+    ADDRESSBOOK_ID
+);
+
+create index SHARED_GROUP_BIND_RES_cf52f95d on SHARED_GROUP_BIND (
+    GROUP_RESOURCE_ID
+);
+
+create index CALENDAR_OBJECT_REVIS_3a3956c4 on CALENDAR_OBJECT_REVISIONS (
+    CALENDAR_HOME_RESOURCE_ID,
+    CALENDAR_RESOURCE_ID
+);
+
+create index CALENDAR_OBJECT_REVIS_6d9d929c on CALENDAR_OBJECT_REVISIONS (
+    CALENDAR_RESOURCE_ID,
+    RESOURCE_NAME,
+    DELETED,
+    REVISION
+);
+
+create index CALENDAR_OBJECT_REVIS_265c8acf on CALENDAR_OBJECT_REVISIONS (
+    CALENDAR_RESOURCE_ID,
+    REVISION
+);
+
+create index ADDRESSBOOK_OBJECT_RE_2bfcf757 on ADDRESSBOOK_OBJECT_REVISIONS (
+    ADDRESSBOOK_HOME_RESOURCE_ID,
+    OWNER_HOME_RESOURCE_ID
+);
+
+create index ADDRESSBOOK_OBJECT_RE_00fe8288 on ADDRESSBOOK_OBJECT_REVISIONS (
+    OWNER_HOME_RESOURCE_ID,
+    RESOURCE_NAME,
+    DELETED,
+    REVISION
+);
+
+create index ADDRESSBOOK_OBJECT_RE_45004780 on ADDRESSBOOK_OBJECT_REVISIONS (
+    OWNER_HOME_RESOURCE_ID,
+    REVISION
+);
+
+create index NOTIFICATION_OBJECT_R_036a9cee on NOTIFICATION_OBJECT_REVISIONS (
+    NOTIFICATION_HOME_RESOURCE_ID,
+    REVISION
+);
+
+create index APN_SUBSCRIPTIONS_RES_9610d78e on APN_SUBSCRIPTIONS (
+    RESOURCE_KEY
+);
+
+create index IMIP_TOKENS_TOKEN_e94b918f on IMIP_TOKENS (
+    TOKEN
+);
+
+create index CALENDAR_OBJECT_SPLIT_af71dcda on CALENDAR_OBJECT_SPLITTER_WORK (
+    RESOURCE_ID
+);
+
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_schemaoldpostgresdialectv30sql"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v30.sql (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v30.sql                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v30.sql        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,717 @@
</span><ins>+-- -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+
+----
+-- Copyright (c) 2010-2013 Apple Inc. All rights reserved.
+--
+-- Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+----
+
+
+-----------------
+-- Resource ID --
+-----------------
+
+create sequence RESOURCE_ID_SEQ;
+
+
+-------------------------
+-- Cluster Bookkeeping --
+-------------------------
+
+-- Information about a process connected to this database.
+
+-- Note that this must match the node info schema in twext.enterprise.queue.
+create table NODE_INFO (
+  HOSTNAME  varchar(255) not null,
+  PID       integer      not null,
+  PORT      integer      not null,
+  TIME      timestamp    not null default timezone('UTC', CURRENT_TIMESTAMP),
+
+  primary key (HOSTNAME, PORT)
+);
+
+-- Unique named locks.  This table should always be empty, but rows are
+-- temporarily created in order to prevent undesirable concurrency.
+create table NAMED_LOCK (
+    LOCK_NAME varchar(255) primary key
+);
+
+
+-------------------
+-- Calendar Home --
+-------------------
+
+create table CALENDAR_HOME (
+  RESOURCE_ID      integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID        varchar(255) not null unique,                                 -- implicit index
+  DATAVERSION      integer      default 0 not null
+);
+
+--------------
+-- Calendar --
+--------------
+
+create table CALENDAR (
+  RESOURCE_ID integer   primary key default nextval('RESOURCE_ID_SEQ') -- implicit index
+);
+
+----------------------------
+-- Calendar Home Metadata --
+----------------------------
+
+create table CALENDAR_HOME_METADATA (
+  RESOURCE_ID              integer     primary key references CALENDAR_HOME on delete cascade, -- implicit index
+  QUOTA_USED_BYTES         integer     default 0 not null,
+  DEFAULT_EVENTS           integer     default null references CALENDAR on delete set null,
+  DEFAULT_TASKS            integer     default null references CALENDAR on delete set null,
+  DEFAULT_POLLS            integer     default null references CALENDAR on delete set null,
+  ALARM_VEVENT_TIMED       text        default null,
+  ALARM_VEVENT_ALLDAY      text        default null,
+  ALARM_VTODO_TIMED        text        default null,
+  ALARM_VTODO_ALLDAY       text        default null,
+  AVAILABILITY             text        default null,
+  CREATED                  timestamp   default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                 timestamp   default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+create index CALENDAR_HOME_METADATA_DEFAULT_EVENTS on
+        CALENDAR_HOME_METADATA(DEFAULT_EVENTS);
+create index CALENDAR_HOME_METADATA_DEFAULT_TASKS on
+        CALENDAR_HOME_METADATA(DEFAULT_TASKS);
+create index CALENDAR_HOME_METADATA_DEFAULT_POLLS on
+        CALENDAR_HOME_METADATA(DEFAULT_POLLS);
+
+-----------------------
+-- Calendar Metadata --
+-----------------------
+
+create table CALENDAR_METADATA (
+  RESOURCE_ID           integer      primary key references CALENDAR on delete cascade, -- implicit index
+  SUPPORTED_COMPONENTS  varchar(255) default null,
+  CREATED               timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+---------------------------
+-- Sharing Notifications --
+---------------------------
+
+create table NOTIFICATION_HOME (
+  RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID   varchar(255) not null unique,                                -- implicit index
+  DATAVERSION integer      default 0 not null
+);
+
+create table NOTIFICATION (
+  RESOURCE_ID                   integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  NOTIFICATION_HOME_RESOURCE_ID integer      not null references NOTIFICATION_HOME,
+  NOTIFICATION_UID              varchar(255) not null,
+  NOTIFICATION_TYPE             varchar(255) not null,
+  NOTIFICATION_DATA             text         not null,
+  MD5                           char(32)     not null,
+  CREATED                       timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique(NOTIFICATION_UID, NOTIFICATION_HOME_RESOURCE_ID) -- implicit index
+);
+
+create index NOTIFICATION_NOTIFICATION_HOME_RESOURCE_ID on
+        NOTIFICATION(NOTIFICATION_HOME_RESOURCE_ID);
+
+
+-------------------
+-- Calendar Bind --
+-------------------
+
+-- Joins CALENDAR_HOME and CALENDAR
+
+create table CALENDAR_BIND (
+  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
+  CALENDAR_RESOURCE_ID      integer      not null references CALENDAR on delete cascade,
+  CALENDAR_RESOURCE_NAME    varchar(255) not null,
+  BIND_MODE                 integer      not null, -- enum CALENDAR_BIND_MODE
+  BIND_STATUS               integer      not null, -- enum CALENDAR_BIND_STATUS
+  BIND_REVISION                                integer      default 0 not null,
+  MESSAGE                   text,
+  TRANSP                    integer      default 0 not null, -- enum CALENDAR_TRANSP
+  ALARM_VEVENT_TIMED        text         default null,
+  ALARM_VEVENT_ALLDAY       text         default null,
+  ALARM_VTODO_TIMED         text         default null,
+  ALARM_VTODO_ALLDAY        text         default null,
+  TIMEZONE                  text         default null,
+
+  primary key(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID), -- implicit index
+  unique(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_NAME)     -- implicit index
+);
+
+create index CALENDAR_BIND_RESOURCE_ID on
+        CALENDAR_BIND(CALENDAR_RESOURCE_ID);
+
+-- Enumeration of calendar bind modes
+
+create table CALENDAR_BIND_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_BIND_MODE values (0, 'own'  );
+insert into CALENDAR_BIND_MODE values (1, 'read' );
+insert into CALENDAR_BIND_MODE values (2, 'write');
+insert into CALENDAR_BIND_MODE values (3, 'direct');
+insert into CALENDAR_BIND_MODE values (4, 'indirect');
+
+-- Enumeration of statuses
+
+create table CALENDAR_BIND_STATUS (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_BIND_STATUS values (0, 'invited' );
+insert into CALENDAR_BIND_STATUS values (1, 'accepted');
+insert into CALENDAR_BIND_STATUS values (2, 'declined');
+insert into CALENDAR_BIND_STATUS values (3, 'invalid');
+insert into CALENDAR_BIND_STATUS values (4, 'deleted');
+
+
+-- Enumeration of transparency
+
+create table CALENDAR_TRANSP (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_TRANSP values (0, 'opaque' );
+insert into CALENDAR_TRANSP values (1, 'transparent');
+
+
+---------------------
+-- Calendar Object --
+---------------------
+
+create table CALENDAR_OBJECT (
+  RESOURCE_ID          integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  CALENDAR_RESOURCE_ID integer      not null references CALENDAR on delete cascade,
+  RESOURCE_NAME        varchar(255) not null,
+  ICALENDAR_TEXT       text         not null,
+  ICALENDAR_UID        varchar(255) not null,
+  ICALENDAR_TYPE       varchar(255) not null,
+  ATTACHMENTS_MODE     integer      default 0 not null, -- enum CALENDAR_OBJECT_ATTACHMENTS_MODE
+  DROPBOX_ID           varchar(255),
+  ORGANIZER            varchar(255),
+  RECURRANCE_MIN       date,        -- minimum date that recurrences have been expanded to.
+  RECURRANCE_MAX       date,        -- maximum date that recurrences have been expanded to.
+  ACCESS               integer      default 0 not null,
+  SCHEDULE_OBJECT      boolean      default false,
+  SCHEDULE_TAG         varchar(36)  default null,
+  SCHEDULE_ETAGS       text         default null,
+  PRIVATE_COMMENTS     boolean      default false not null,
+  MD5                  char(32)     not null,
+  CREATED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED             timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique (CALENDAR_RESOURCE_ID, RESOURCE_NAME) -- implicit index
+
+  -- since the 'inbox' is a 'calendar resource' for the purpose of storing
+  -- calendar objects, this constraint has to be selectively enforced by the
+  -- application layer.
+
+  -- unique(CALENDAR_RESOURCE_ID, ICALENDAR_UID)
+);
+
+create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_AND_ICALENDAR_UID on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, ICALENDAR_UID);
+
+create index CALENDAR_OBJECT_CALENDAR_RESOURCE_ID_RECURRANCE_MAX on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, RECURRANCE_MAX);
+
+create index CALENDAR_OBJECT_ICALENDAR_UID on
+  CALENDAR_OBJECT(ICALENDAR_UID);
+
+create index CALENDAR_OBJECT_DROPBOX_ID on
+  CALENDAR_OBJECT(DROPBOX_ID);
+
+-- Enumeration of attachment modes
+
+create table CALENDAR_OBJECT_ATTACHMENTS_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (0, 'none' );
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (1, 'read' );
+insert into CALENDAR_OBJECT_ATTACHMENTS_MODE values (2, 'write');
+
+
+-- Enumeration of calendar access types
+
+create table CALENDAR_ACCESS_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(32) not null unique
+);
+
+insert into CALENDAR_ACCESS_TYPE values (0, ''             );
+insert into CALENDAR_ACCESS_TYPE values (1, 'public'       );
+insert into CALENDAR_ACCESS_TYPE values (2, 'private'      );
+insert into CALENDAR_ACCESS_TYPE values (3, 'confidential' );
+insert into CALENDAR_ACCESS_TYPE values (4, 'restricted'   );
+
+
+-----------------
+-- Instance ID --
+-----------------
+
+create sequence INSTANCE_ID_SEQ;
+
+
+----------------
+-- Time Range --
+----------------
+
+create table TIME_RANGE (
+  INSTANCE_ID                 integer        primary key default nextval('INSTANCE_ID_SEQ'), -- implicit index
+  CALENDAR_RESOURCE_ID        integer        not null references CALENDAR on delete cascade,
+  CALENDAR_OBJECT_RESOURCE_ID integer        not null references CALENDAR_OBJECT on delete cascade,
+  FLOATING                    boolean        not null,
+  START_DATE                  timestamp      not null,
+  END_DATE                    timestamp      not null,
+  FBTYPE                      integer        not null,
+  TRANSPARENT                 boolean        not null
+);
+
+create index TIME_RANGE_CALENDAR_RESOURCE_ID on
+  TIME_RANGE(CALENDAR_RESOURCE_ID);
+create index TIME_RANGE_CALENDAR_OBJECT_RESOURCE_ID on
+  TIME_RANGE(CALENDAR_OBJECT_RESOURCE_ID);
+
+
+-- Enumeration of free/busy types
+
+create table FREE_BUSY_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into FREE_BUSY_TYPE values (0, 'unknown'         );
+insert into FREE_BUSY_TYPE values (1, 'free'            );
+insert into FREE_BUSY_TYPE values (2, 'busy'            );
+insert into FREE_BUSY_TYPE values (3, 'busy-unavailable');
+insert into FREE_BUSY_TYPE values (4, 'busy-tentative'  );
+
+
+------------------
+-- Transparency --
+------------------
+
+create table TRANSPARENCY (
+  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
+  USER_ID                     varchar(255) not null,
+  TRANSPARENT                 boolean      not null
+);
+
+create index TRANSPARENCY_TIME_RANGE_INSTANCE_ID on
+  TRANSPARENCY(TIME_RANGE_INSTANCE_ID);
+
+
+----------------
+-- Attachment --
+----------------
+
+create sequence ATTACHMENT_ID_SEQ;
+
+create table ATTACHMENT (
+  ATTACHMENT_ID               integer           primary key default nextval('ATTACHMENT_ID_SEQ'), -- implicit index
+  CALENDAR_HOME_RESOURCE_ID   integer           not null references CALENDAR_HOME,
+  DROPBOX_ID                  varchar(255),
+  CONTENT_TYPE                varchar(255)      not null,
+  SIZE                        integer           not null,
+  MD5                         char(32)          not null,
+  CREATED                     timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                    timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  PATH                        varchar(1024)     not null
+);
+
+create index ATTACHMENT_CALENDAR_HOME_RESOURCE_ID on
+  ATTACHMENT(CALENDAR_HOME_RESOURCE_ID);
+
+-- Many-to-many relationship between attachments and calendar objects
+create table ATTACHMENT_CALENDAR_OBJECT (
+  ATTACHMENT_ID                  integer      not null references ATTACHMENT on delete cascade,
+  MANAGED_ID                     varchar(255) not null,
+  CALENDAR_OBJECT_RESOURCE_ID    integer      not null references CALENDAR_OBJECT on delete cascade,
+
+  primary key (ATTACHMENT_ID, CALENDAR_OBJECT_RESOURCE_ID), -- implicit index
+  unique (MANAGED_ID, CALENDAR_OBJECT_RESOURCE_ID) --implicit index
+);
+
+create index ATTACHMENT_CALENDAR_OBJECT_CALENDAR_OBJECT_RESOURCE_ID on
+        ATTACHMENT_CALENDAR_OBJECT(CALENDAR_OBJECT_RESOURCE_ID);
+
+-----------------------
+-- Resource Property --
+-----------------------
+
+create table RESOURCE_PROPERTY (
+  RESOURCE_ID integer      not null, -- foreign key: *.RESOURCE_ID
+  NAME        varchar(255) not null,
+  VALUE       text         not null, -- FIXME: xml?
+  VIEWER_UID  varchar(255),
+
+  primary key (RESOURCE_ID, NAME, VIEWER_UID) -- implicit index
+);
+
+
+----------------------
+-- AddressBook Home --
+----------------------
+
+create table ADDRESSBOOK_HOME (
+  RESOURCE_ID                                      integer                        primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  ADDRESSBOOK_PROPERTY_STORE_ID        integer              default nextval('RESOURCE_ID_SEQ') not null,         -- implicit index
+  OWNER_UID                                        varchar(255)         not null unique,                                -- implicit index
+  DATAVERSION                                      integer              default 0 not null
+);
+
+
+-------------------------------
+-- AddressBook Home Metadata --
+-------------------------------
+
+create table ADDRESSBOOK_HOME_METADATA (
+  RESOURCE_ID      integer      primary key references ADDRESSBOOK_HOME on delete cascade, -- implicit index
+  QUOTA_USED_BYTES integer      default 0 not null,
+  CREATED          timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED         timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+-----------------------------
+-- Shared AddressBook Bind --
+-----------------------------
+
+-- Joins sharee ADDRESSBOOK_HOME and owner ADDRESSBOOK_HOME
+
+create table SHARED_ADDRESSBOOK_BIND (
+  ADDRESSBOOK_HOME_RESOURCE_ID                        integer                        not null references ADDRESSBOOK_HOME,
+  OWNER_HOME_RESOURCE_ID                            integer              not null references ADDRESSBOOK_HOME on delete cascade,
+  ADDRESSBOOK_RESOURCE_NAME                            varchar(255)         not null,
+  BIND_MODE                                            integer              not null,        -- enum CALENDAR_BIND_MODE
+  BIND_STATUS                                          integer              not null,        -- enum CALENDAR_BIND_STATUS
+  BIND_REVISION                                                           integer              default 0 not null,
+  MESSAGE                                              text,                                  -- FIXME: xml?
+
+  primary key (ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, ADDRESSBOOK_RESOURCE_NAME)     -- implicit index
+);
+
+create index SHARED_ADDRESSBOOK_BIND_RESOURCE_ID on
+  SHARED_ADDRESSBOOK_BIND(OWNER_HOME_RESOURCE_ID);
+
+
+------------------------
+-- AddressBook Object --
+------------------------
+
+create table ADDRESSBOOK_OBJECT (
+  RESOURCE_ID                             integer                   primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
+  ADDRESSBOOK_HOME_RESOURCE_ID         integer              not null references ADDRESSBOOK_HOME on delete cascade,
+  RESOURCE_NAME                           varchar(255)         not null,
+  VCARD_TEXT                              text                 not null,
+  VCARD_UID                               varchar(255)         not null,
+  KIND                                                             integer              not null,  -- enum ADDRESSBOOK_OBJECT_KIND
+  MD5                                     char(32)             not null,
+  CREATED                                 timestamp            default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                                timestamp            default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, RESOURCE_NAME), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, VCARD_UID)      -- implicit index
+);
+
+
+-----------------------------
+-- AddressBook Object kind --
+-----------------------------
+
+create table ADDRESSBOOK_OBJECT_KIND (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into ADDRESSBOOK_OBJECT_KIND values (0, 'person');
+insert into ADDRESSBOOK_OBJECT_KIND values (1, 'group' );
+insert into ADDRESSBOOK_OBJECT_KIND values (2, 'resource');
+insert into ADDRESSBOOK_OBJECT_KIND values (3, 'location');
+
+
+----------------------------------
+-- Revisions, forward reference --
+----------------------------------
+
+create sequence REVISION_SEQ;
+
+---------------------------------
+-- Address Book Object Members --
+---------------------------------
+
+create table ABO_MEMBERS (
+    GROUP_ID              integer      not null, -- references ADDRESSBOOK_OBJECT on delete cascade,        -- AddressBook Object's (kind=='group') RESOURCE_ID
+         ADDRESSBOOK_ID                  integer      not null references ADDRESSBOOK_HOME on delete cascade,
+    MEMBER_ID             integer      not null, -- references ADDRESSBOOK_OBJECT,                                                -- member AddressBook Object's RESOURCE_ID
+          REVISION              integer      default nextval('REVISION_SEQ') not null,
+          REMOVED               boolean      default false not null,
+
+    primary key (GROUP_ID, MEMBER_ID, REVISION) -- implicit index
+);
+
+create index ABO_MEMBERS_ADDRESSBOOK_ID on
+        ABO_MEMBERS(ADDRESSBOOK_ID);
+create index ABO_MEMBERS_MEMBER_ID on
+        ABO_MEMBERS(MEMBER_ID);
+
+------------------------------------------
+-- Address Book Object Foreign Members  --
+------------------------------------------
+
+create table ABO_FOREIGN_MEMBERS (
+    GROUP_ID              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,        -- AddressBook Object's (kind=='group') RESOURCE_ID
+         ADDRESSBOOK_ID                  integer      not null references ADDRESSBOOK_HOME on delete cascade,
+    MEMBER_ADDRESS            varchar(255) not null,                                                                                                         -- member AddressBook Object's 'calendar' address
+
+    primary key (GROUP_ID, MEMBER_ADDRESS) -- implicit index
+);
+
+create index ABO_FOREIGN_MEMBERS_ADDRESSBOOK_ID on
+        ABO_FOREIGN_MEMBERS(ADDRESSBOOK_ID);
+
+-----------------------
+-- Shared Group Bind --
+-----------------------
+
+-- Joins ADDRESSBOOK_HOME and ADDRESSBOOK_OBJECT (kind == group)
+
+create table SHARED_GROUP_BIND (        
+  ADDRESSBOOK_HOME_RESOURCE_ID                 integer      not null references ADDRESSBOOK_HOME,
+  GROUP_RESOURCE_ID                              integer      not null references ADDRESSBOOK_OBJECT on delete cascade,
+  GROUP_ADDRESSBOOK_NAME                        varchar(255) not null,
+  BIND_MODE                                    integer      not null, -- enum CALENDAR_BIND_MODE
+  BIND_STATUS                                  integer      not null, -- enum CALENDAR_BIND_STATUS
+  BIND_REVISION                                                   integer      default 0 not null,
+  MESSAGE                                      text,                  -- FIXME: xml?
+
+  primary key (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_RESOURCE_ID), -- implicit index
+  unique (ADDRESSBOOK_HOME_RESOURCE_ID, GROUP_ADDRESSBOOK_NAME)  -- implicit index
+);
+
+create index SHARED_GROUP_BIND_RESOURCE_ID on
+  SHARED_GROUP_BIND(GROUP_RESOURCE_ID);
+
+
+---------------
+-- Revisions --
+---------------
+
+-- create sequence REVISION_SEQ;
+
+
+-------------------------------
+-- Calendar Object Revisions --
+-------------------------------
+
+create table CALENDAR_OBJECT_REVISIONS (
+  CALENDAR_HOME_RESOURCE_ID integer      not null references CALENDAR_HOME,
+  CALENDAR_RESOURCE_ID      integer      references CALENDAR,
+  CALENDAR_NAME             varchar(255) default null,
+  RESOURCE_NAME             varchar(255),
+  REVISION                  integer      default nextval('REVISION_SEQ') not null,
+  DELETED                   boolean      not null
+);
+
+create index CALENDAR_OBJECT_REVISIONS_HOME_RESOURCE_ID_CALENDAR_RESOURCE_ID
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID);
+
+create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_RESOURCE_NAME_DELETED_REVISION
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, RESOURCE_NAME, DELETED, REVISION);
+
+create index CALENDAR_OBJECT_REVISIONS_RESOURCE_ID_REVISION
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_RESOURCE_ID, REVISION);
+
+
+----------------------------------
+-- AddressBook Object Revisions --
+----------------------------------
+
+create table ADDRESSBOOK_OBJECT_REVISIONS (
+  ADDRESSBOOK_HOME_RESOURCE_ID                         integer                        not null references ADDRESSBOOK_HOME,
+  OWNER_HOME_RESOURCE_ID                            integer             references ADDRESSBOOK_HOME,
+  ADDRESSBOOK_NAME                                     varchar(255)         default null,
+  OBJECT_RESOURCE_ID                                        integer                        default 0,
+  RESOURCE_NAME                                        varchar(255),
+  REVISION                                             integer             default nextval('REVISION_SEQ') not null,
+  DELETED                                              boolean              not null
+);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_HOME_RESOURCE_ID_OWNER_HOME_RESOURCE_ID
+  on ADDRESSBOOK_OBJECT_REVISIONS(ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_RESOURCE_NAME_DELETED_REVISION
+  on ADDRESSBOOK_OBJECT_REVISIONS(OWNER_HOME_RESOURCE_ID, RESOURCE_NAME, DELETED, REVISION);
+
+create index ADDRESSBOOK_OBJECT_REVISIONS_OWNER_HOME_RESOURCE_ID_REVISION
+  on ADDRESSBOOK_OBJECT_REVISIONS(OWNER_HOME_RESOURCE_ID, REVISION);
+
+
+-----------------------------------
+-- Notification Object Revisions --
+-----------------------------------
+
+create table NOTIFICATION_OBJECT_REVISIONS (
+  NOTIFICATION_HOME_RESOURCE_ID integer      not null references NOTIFICATION_HOME on delete cascade,
+  RESOURCE_NAME                 varchar(255),
+  REVISION                      integer      default nextval('REVISION_SEQ') not null,
+  DELETED                       boolean      not null,
+
+  unique(NOTIFICATION_HOME_RESOURCE_ID, RESOURCE_NAME) -- implicit index
+);
+
+create index NOTIFICATION_OBJECT_REVISIONS_RESOURCE_ID_REVISION
+  on NOTIFICATION_OBJECT_REVISIONS(NOTIFICATION_HOME_RESOURCE_ID, REVISION);
+
+
+-------------------------------------------
+-- Apple Push Notification Subscriptions --
+-------------------------------------------
+
+create table APN_SUBSCRIPTIONS (
+  TOKEN                         varchar(255) not null,
+  RESOURCE_KEY                  varchar(255) not null,
+  MODIFIED                      integer      not null,
+  SUBSCRIBER_GUID               varchar(255) not null,
+  USER_AGENT                    varchar(255) default null,
+  IP_ADDR                       varchar(255) default null,
+
+  primary key (TOKEN, RESOURCE_KEY) -- implicit index
+);
+
+create index APN_SUBSCRIPTIONS_RESOURCE_KEY
+   on APN_SUBSCRIPTIONS(RESOURCE_KEY);
+
+   
+-----------------
+-- IMIP Tokens --
+-----------------
+
+create table IMIP_TOKENS (
+  TOKEN                         varchar(255) not null,
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALUID                       varchar(255) not null,
+  ACCESSED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  primary key (ORGANIZER, ATTENDEE, ICALUID) -- implicit index
+);
+
+create index IMIP_TOKENS_TOKEN
+   on IMIP_TOKENS(TOKEN);
+
+   
+----------------
+-- Work Items --
+----------------
+
+create sequence WORKITEM_SEQ;
+
+
+---------------------------
+-- IMIP Inivitation Work --
+---------------------------
+
+create table IMIP_INVITATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  FROM_ADDR                     varchar(255) not null,
+  TO_ADDR                       varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+
+-----------------------
+-- IMIP Polling Work --
+-----------------------
+
+create table IMIP_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+---------------------
+-- IMIP Reply Work --
+---------------------
+
+create table IMIP_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+
+------------------------
+-- Push Notifications --
+------------------------
+
+create table PUSH_NOTIFICATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  PUSH_ID                       varchar(255) not null,
+  PRIORITY                      integer      not null -- 1:low 5:medium 10:high
+);
+
+-----------------
+-- GroupCacher --
+-----------------
+
+create table GROUP_CACHER_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
+--------------------------
+-- Object Splitter Work --
+--------------------------
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
+  NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
+);
+
+create index CALENDAR_OBJECT_SPLITTER_WORK_RESOURCE_ID on
+        CALENDAR_OBJECT_SPLITTER_WORK(RESOURCE_ID);
+
+--------------------
+-- Schema Version --
+--------------------
+
+create table CALENDARSERVER (
+  NAME                          varchar(255) primary key, -- implicit index
+  VALUE                         varchar(255)
+);
+
+insert into CALENDARSERVER values ('VERSION', '30');
+insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
+insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_30_to_31sql"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_30_to_31.sql (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_30_to_31.sql                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_30_to_31.sql        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,53 @@
</span><ins>+----
+-- Copyright (c) 2012-2013 Apple Inc. All rights reserved.
+--
+-- Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+----
+
+---------------------------------------------------
+-- Upgrade database schema from VERSION 30 to 31 --
+---------------------------------------------------
+
+-- Home related updates
+
+alter table CALENDAR_HOME
+ add (&quot;STATUS&quot; integer default 0 not null);
+
+alter table NOTIFICATION_HOME
+ add (&quot;STATUS&quot; integer default 0 not null);
+
+alter table ADDRESSBOOK_HOME
+ add (&quot;STATUS&quot; integer default 0 not null);
+
+create table HOME_STATUS (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into HOME_STATUS (DESCRIPTION, ID) values ('normal', 0);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('external', 1);
+
+-- Bind changes
+alter table CALENDAR_BIND
+ add (&quot;EXTERNAL_ID&quot; integer default null);
+
+alter table SHARED_ADDRESSBOOK_BIND
+ add (&quot;EXTERNAL_ID&quot; integer default null);
+
+alter table SHARED_GROUP_BIND
+ add (&quot;EXTERNAL_ID&quot; integer default null);
+
+
+-- Now update the version
+-- No data upgrades
+update CALENDARSERVER set VALUE = '31' where NAME = 'VERSION';
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_30_to_31sql"></a>
<div class="addfile"><h4>Added: CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_30_to_31.sql (0 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_30_to_31.sql                                (rev 0)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_30_to_31.sql        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -0,0 +1,55 @@
</span><ins>+----
+-- Copyright (c) 2012-2013 Apple Inc. All rights reserved.
+--
+-- Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+----
+
+---------------------------------------------------
+-- Upgrade database schema from VERSION 30 to 31 --
+---------------------------------------------------
+
+-- Home related updates
+
+alter table CALENDAR_HOME
+ add column STATUS integer default 0 not null;
+
+alter table NOTIFICATION_HOME
+ add column STATUS integer default 0 not null;
+
+alter table ADDRESSBOOK_HOME
+ add column STATUS integer default 0 not null;
+
+-- Enumeration of statuses
+
+create table HOME_STATUS (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into HOME_STATUS values (0, 'normal' );
+insert into HOME_STATUS values (1, 'external');
+
+-- Bind changes
+alter table CALENDAR_BIND
+ add column EXTERNAL_ID integer default null;
+
+alter table SHARED_ADDRESSBOOK_BIND
+ add column EXTERNAL_ID integer default null;
+
+alter table SHARED_GROUP_BIND
+ add column EXTERNAL_ID integer default null;
+
+
+-- Now update the version
+-- No data upgrades
+update CALENDARSERVER set VALUE = '31' where NAME = 'VERSION';
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoresql_tablespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/sql_tables.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/sql_tables.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/sql_tables.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -133,6 +133,15 @@
</span><span class="cx"> 
</span><span class="cx"> # Various constants
</span><span class="cx"> 
</span><ins>+_homeStatus = _schemaConstants(
+    schema.HOME_STATUS.DESCRIPTION,
+    schema.HOME_STATUS.ID
+)
+
+
+_HOME_STATUS_NORMAL = _homeStatus('normal')
+_HOME_STATUS_EXTERNAL = _homeStatus('external')
+
</ins><span class="cx"> _bindStatus = _schemaConstants(
</span><span class="cx">     schema.CALENDAR_BIND_STATUS.DESCRIPTION,
</span><span class="cx">     schema.CALENDAR_BIND_STATUS.ID
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/test/test_sql.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/test/test_sql.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/test/test_sql.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -19,21 +19,28 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.dal.syntax import Select
</span><del>-from txdav.xml import element as davxml
</del><ins>+from twext.enterprise.dal.syntax import Insert
</ins><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.internet.task import Clock
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><span class="cx"> from twisted.internet.defer import Deferred
</span><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.test.util import buildDirectoryRecord
</ins><span class="cx"> from txdav.common.datastore.sql import log, CommonStoreTransactionMonitor, \
</span><span class="cx">     CommonHome, CommonHomeChild, ECALENDARTYPE
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema
</span><span class="cx"> from txdav.common.datastore.test.util import CommonCommonTests, buildStore
</span><span class="cx"> from txdav.common.icommondatastore import AllRetriesFailed
</span><del>-from twext.enterprise.dal.syntax import Insert
</del><span class="cx"> from txdav.common.datastore.sql import fixUUIDNormalization
</span><ins>+from txdav.xml import element as davxml
</ins><span class="cx"> 
</span><ins>+from uuid import UUID
+
+exampleUID = UUID(&quot;a&quot; * 32)
+denormalizedUID = str(exampleUID)
+normalizedUID = denormalizedUID.upper()
+
</ins><span class="cx"> class CommonSQLStoreTests(CommonCommonTests, TestCase):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Tests for shared functionality in L{txdav.common.datastore.sql}.
</span><span class="lines">@@ -46,6 +53,9 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         yield super(CommonSQLStoreTests, self).setUp()
</span><span class="cx">         self._sqlStore = yield buildStore(self, self.notifierFactory)
</span><ins>+        self._sqlStore.directoryService().addRecord(buildDirectoryRecord(denormalizedUID))
+        self._sqlStore.directoryService().addRecord(buildDirectoryRecord(normalizedUID))
+        self._sqlStore.directoryService().addRecord(buildDirectoryRecord(&quot;uid&quot;))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def storeUnderTest(self):
</span><span class="lines">@@ -308,31 +318,31 @@
</span><span class="cx">         token = yield homeChild.syncToken()
</span><span class="cx">         yield homeChild._changeRevision(&quot;insert&quot;, &quot;C&quot;)
</span><span class="cx">         changed = yield homeChild.resourceNamesSinceToken(token)
</span><del>-        self.assertEqual(changed, ([&quot;C&quot;], [],))
</del><ins>+        self.assertEqual(changed, ([&quot;C&quot;], [], [],))
</ins><span class="cx"> 
</span><span class="cx">         # update test
</span><span class="cx">         token = yield homeChild.syncToken()
</span><span class="cx">         yield homeChild._changeRevision(&quot;update&quot;, &quot;C&quot;)
</span><span class="cx">         changed = yield homeChild.resourceNamesSinceToken(token)
</span><del>-        self.assertEqual(changed, ([&quot;C&quot;], [],))
</del><ins>+        self.assertEqual(changed, ([&quot;C&quot;], [], [],))
</ins><span class="cx"> 
</span><span class="cx">         # delete test
</span><span class="cx">         token = yield homeChild.syncToken()
</span><span class="cx">         yield homeChild._changeRevision(&quot;delete&quot;, &quot;C&quot;)
</span><span class="cx">         changed = yield homeChild.resourceNamesSinceToken(token)
</span><del>-        self.assertEqual(changed, ([], [&quot;C&quot;],))
</del><ins>+        self.assertEqual(changed, ([], [&quot;C&quot;], [],))
</ins><span class="cx"> 
</span><span class="cx">         # missing update test
</span><span class="cx">         token = yield homeChild.syncToken()
</span><span class="cx">         yield homeChild._changeRevision(&quot;update&quot;, &quot;D&quot;)
</span><span class="cx">         changed = yield homeChild.resourceNamesSinceToken(token)
</span><del>-        self.assertEqual(changed, ([&quot;D&quot;], [],))
</del><ins>+        self.assertEqual(changed, ([&quot;D&quot;], [], [],))
</ins><span class="cx"> 
</span><span class="cx">         # missing delete test
</span><span class="cx">         token = yield homeChild.syncToken()
</span><span class="cx">         yield homeChild._changeRevision(&quot;delete&quot;, &quot;E&quot;)
</span><span class="cx">         changed = yield homeChild.resourceNamesSinceToken(token)
</span><del>-        self.assertEqual(changed, ([], [],))
</del><ins>+        self.assertEqual(changed, ([], [], [],))
</ins><span class="cx"> 
</span><span class="cx">         yield txn.abort()
</span><span class="cx"> 
</span><span class="lines">@@ -421,10 +431,3 @@
</span><span class="cx">         yield fixUUIDNormalization(self.storeUnderTest())
</span><span class="cx">         self.assertEqual((yield self.allHomeUIDs(schema.ADDRESSBOOK_HOME)),
</span><span class="cx">                          [[normalizedUID]])
</span><del>-
-
-
-from uuid import UUID
-exampleUID = UUID(&quot;a&quot; * 32)
-denormalizedUID = str(exampleUID)
-normalizedUID = denormalizedUID.upper()
</del></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/test/util.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -112,12 +112,14 @@
</span><span class="cx">     def recordWithUID(self, uid):
</span><span class="cx">         return self.records.get(uid)
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def recordWithGUID(self, guid):
</span><span class="cx">         for record in self.records.itervalues():
</span><span class="cx">             if record.guid == guid:
</span><span class="cx">                 return record
</span><span class="cx">         return None
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def addRecord(self, record):
</span><span class="cx">         self.records[record.uid] = record
</span><span class="cx"> 
</span><span class="lines">@@ -127,32 +129,70 @@
</span><span class="cx"> 
</span><span class="cx">     implements(IStoreDirectoryRecord)
</span><span class="cx"> 
</span><del>-    def __init__(self, uid, shortNames, fullName, extras={}):
</del><ins>+    def __init__(self, uid, shortNames, fullName, thisServer=True, server=None, extras={}):
</ins><span class="cx">         self.uid = uid
</span><span class="cx">         self.guid = uid
</span><span class="cx">         self.shortNames = shortNames
</span><span class="cx">         self.fullName = fullName
</span><span class="cx">         self.displayName = self.fullName if self.fullName else self.shortNames[0]
</span><ins>+        self._thisServer = thisServer
+        self._server = server
</ins><span class="cx">         self.extras = extras
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def thisServer(self):
+        return self._thisServer
</ins><span class="cx"> 
</span><ins>+
+    def server(self):
+        return self._server
+
+
+
+def buildDirectory(homes=None):
+
+    directory = TestStoreDirectoryService()
+
+    # User accounts
+    for ctr in range(1, 100):
+        directory.addRecord(TestStoreDirectoryRecord(
+            &quot;user%02d&quot; % (ctr,),
+            (&quot;user%02d&quot; % (ctr,),),
+            &quot;User %02d&quot; % (ctr,),
+        ))
+
+    homes = set(homes) if homes is not None else set()
+    for uid in homes:
+        directory.addRecord(buildDirectoryRecord(uid))
+
+    return directory
+
+
+
+def buildDirectoryRecord(uid):
+    return TestStoreDirectoryRecord(
+        uid,
+        (uid,),
+        uid.capitalize(),
+    )
+
+
+
</ins><span class="cx"> class SQLStoreBuilder(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test-fixture-builder which can construct a PostgresStore.
</span><span class="cx">     &quot;&quot;&quot;
</span><del>-    sharedService = None
-    currentTestID = None
</del><ins>+    def __init__(self, secondary=False):
+        self.sharedService = None
+        self.currentTestID = None
+        self.sharedDBPath = &quot;_test_sql_db&quot; + str(os.getpid()) + (&quot;-2&quot; if secondary else &quot;&quot;)
</ins><span class="cx"> 
</span><del>-    SHARED_DB_PATH = &quot;_test_sql_db&quot; + str(os.getpid())
</del><span class="cx"> 
</span><del>-
-    @classmethod
-    def createService(cls, serviceFactory):
</del><ins>+    def createService(self, serviceFactory):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a L{PostgresService} to use for building a store.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        dbRoot = CachingFilePath(cls.SHARED_DB_PATH)
</del><ins>+        dbRoot = CachingFilePath(self.sharedDBPath)
</ins><span class="cx">         return PostgresService(
</span><span class="cx">             dbRoot, serviceFactory, current_sql_schema, resetSchema=True,
</span><span class="cx">             databaseName=&quot;caldav&quot;,
</span><span class="lines">@@ -168,17 +208,15 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @classmethod
-    def childStore(cls):
</del><ins>+    def childStore(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Create a store suitable for use in a child process, that is hooked up
</span><span class="cx">         to the store that a parent test process is managing.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         disableMemcacheForTest(TestCase())
</span><span class="cx">         staticQuota = 3000
</span><del>-        attachmentRoot = (CachingFilePath(cls.SHARED_DB_PATH)
-                          .child(&quot;attachments&quot;))
-        stubsvc = cls.createService(lambda cf: Service())
</del><ins>+        attachmentRoot = (CachingFilePath(self.sharedDBPath).child(&quot;attachments&quot;))
+        stubsvc = self.createService(lambda cf: Service())
</ins><span class="cx"> 
</span><span class="cx">         cp = ConnectionPool(stubsvc.produceConnection, maxConnections=1)
</span><span class="cx">         # Attach the service to the running reactor.
</span><span class="lines">@@ -194,17 +232,17 @@
</span><span class="cx">         return cds
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def buildStore(self, testCase, notifierFactory, directoryService=None):
</del><ins>+    def buildStore(self, testCase, notifierFactory, directoryService=None, homes=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Do the necessary work to build a store for a particular test case.
</span><span class="cx"> 
</span><span class="cx">         @return: a L{Deferred} which fires with an L{IDataStore}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         disableMemcacheForTest(testCase)
</span><del>-        dbRoot = CachingFilePath(self.SHARED_DB_PATH)
</del><ins>+        dbRoot = CachingFilePath(self.sharedDBPath)
</ins><span class="cx">         attachmentRoot = dbRoot.child(&quot;attachments&quot;)
</span><span class="cx">         if directoryService is None:
</span><del>-            directoryService = TestStoreDirectoryService()
</del><ins>+            directoryService = buildDirectory(homes=homes)
</ins><span class="cx">         if self.sharedService is None:
</span><span class="cx">             ready = Deferred()
</span><span class="cx">             def getReady(connectionFactory, storageService):
</span><span class="lines">@@ -251,8 +289,7 @@
</span><span class="cx">         attachmentRoot.createDirectory()
</span><span class="cx"> 
</span><span class="cx">         currentTestID = testCase.id()
</span><del>-        cp = ConnectionPool(self.sharedService.produceConnection,
-                            maxConnections=5)
</del><ins>+        cp = ConnectionPool(self.sharedService.produceConnection, maxConnections=5)
</ins><span class="cx">         quota = deriveQuota(testCase)
</span><span class="cx">         store = CommonDataStore(
</span><span class="cx">             cp.connection,
</span><span class="lines">@@ -314,6 +351,7 @@
</span><span class="cx"> 
</span><span class="cx"> theStoreBuilder = SQLStoreBuilder()
</span><span class="cx"> buildStore = theStoreBuilder.buildStore
</span><ins>+cleanStore = theStoreBuilder.cleanStore
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> _notSet = object()
</span><span class="lines">@@ -685,13 +723,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def homeUnderTest(self, txn=None, name=&quot;home1&quot;):
</del><ins>+    def homeUnderTest(self, txn=None, name=&quot;home1&quot;, create=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get the calendar home detailed by C{requirements['home1']}.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         if txn is None:
</span><span class="cx">             txn = self.transactionUnderTest()
</span><del>-        returnValue((yield txn.calendarHomeWithUID(name)))
</del><ins>+        returnValue((yield txn.calendarHomeWithUID(name, create=create)))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoreupgradesqlupgradesutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/util.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/util.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/sql/upgrades/util.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -19,7 +19,7 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><span class="cx"> from txdav.base.propertystore.sql import PropertyStore
</span><del>-from txdav.common.datastore.sql_tables import schema
</del><ins>+from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_EXTERNAL
</ins><span class="cx"> from twisted.python.failure import Failure
</span><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="lines">@@ -134,7 +134,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> @inlineCallbacks
</span><del>-def doToEachHomeNotAtVersion(store, homeSchema, version, doIt, logStr, filterOwnerUID=None):
</del><ins>+def doToEachHomeNotAtVersion(store, homeSchema, version, doIt, logStr, filterOwnerUID=None, processExternal=False):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Do something to each home whose version column indicates it is older
</span><span class="cx">     than the specified version. Do this in batches as there may be a lot of work to do. Also,
</span><span class="lines">@@ -161,7 +161,7 @@
</span><span class="cx">         txn = store.newTransaction(&quot;updateDataVersion&quot;)
</span><span class="cx">         try:
</span><span class="cx">             rows = yield Select(
</span><del>-                [homeSchema.RESOURCE_ID, homeSchema.OWNER_UID, ],
</del><ins>+                [homeSchema.RESOURCE_ID, homeSchema.OWNER_UID, homeSchema.STATUS, ],
</ins><span class="cx">                 From=homeSchema,
</span><span class="cx">                 Where=where,
</span><span class="cx">                 OrderBy=homeSchema.OWNER_UID,
</span><span class="lines">@@ -173,9 +173,10 @@
</span><span class="cx">                 logUpgradeStatus(&quot;End {}&quot;.format(logStr), count, total)
</span><span class="cx">                 returnValue(None)
</span><span class="cx"> 
</span><del>-            # Apply to the home
-            homeResourceID, _ignore_owner_uid = rows[0]
-            yield doIt(txn, homeResourceID)
</del><ins>+            # Apply to the home if not external
+            homeResourceID, _ignore_owner_uid, homeStatus = rows[0]
+            if homeStatus != _HOME_STATUS_EXTERNAL or processExternal:
+                yield doIt(txn, homeResourceID)
</ins><span class="cx"> 
</span><span class="cx">             # Update the home to the current version
</span><span class="cx">             yield Update(
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommondatastoreupgradetesttest_migratepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/datastore/upgrade/test/test_migrate.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/datastore/upgrade/test/test_migrate.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/datastore/upgrade/test/test_migrate.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -143,7 +143,17 @@
</span><span class="cx">             self.filesPath, {&quot;push&quot;: StubNotifierFactory()}, TestStoreDirectoryService(), True, True
</span><span class="cx">         )
</span><span class="cx">         self.sqlStore = yield theStoreBuilder.buildStore(
</span><del>-            self, StubNotifierFactory()
</del><ins>+            self,
+            StubNotifierFactory(),
+            homes=(
+                &quot;home1&quot;,
+                &quot;home2&quot;,
+                &quot;home3&quot;,
+                &quot;home_defaults&quot;,
+                &quot;home_no_splits&quot;,
+                &quot;home_splits&quot;,
+                &quot;home_splits_shared&quot;,
+            )
</ins><span class="cx">         )
</span><span class="cx">         self.upgrader = UpgradeToDatabaseStep(self.fileStore, self.sqlStore)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommonicommondatastorepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/icommondatastore.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/icommondatastore.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/icommondatastore.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -51,6 +51,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class RecordNotAllowedError(CommonStoreError):
+    &quot;&quot;&quot;
+    User not allowed.
+    &quot;&quot;&quot;
+
+
+
</ins><span class="cx"> class NameNotAllowedError(CommonStoreError):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Attempt to create an object with a name that is not allowed.
</span><span class="lines">@@ -205,6 +212,29 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class ShareNotAllowed(CommonStoreError):
+    &quot;&quot;&quot;
+    An operation on a shared resource is not allowed.
+    &quot;&quot;&quot;
+
+
+
+class ExternalShareFailed(CommonStoreError):
+    &quot;&quot;&quot;
+    An external sharee operation failed.
+    &quot;&quot;&quot;
+
+
+
+class NonExistentExternalShare(CommonStoreError):
+    &quot;&quot;&quot;
+    An external sharee operation failed because the share does not exist on the
+    other pod. The caller of the external request receiving this exception should
+    remove the local external share to &quot;heal&quot; this mismatch.
+    &quot;&quot;&quot;
+
+
</ins><span class="cx"> # Indexing / sync tokens
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcommonidirectoryservicepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/common/idirectoryservice.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/common/idirectoryservice.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txdav/common/idirectoryservice.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -26,6 +26,20 @@
</span><span class="cx">     &quot;IStoreDirectoryRecord&quot;,
</span><span class="cx"> ]
</span><span class="cx"> 
</span><ins>+class IStoreDirectoryError(Exception):
+    &quot;&quot;&quot;
+    Base class for directory related errors.
+    &quot;&quot;&quot;
+
+
+
+class DirectoryRecordNotFoundError(Exception):
+    &quot;&quot;&quot;
+    Directory record not found.
+    &quot;&quot;&quot;
+
+
+
</ins><span class="cx"> class IStoreDirectoryService(Interface):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Directory Service for looking up users.
</span><span class="lines">@@ -48,6 +62,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class IStoreDirectoryRecord(Interface):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Directory record object
</span><span class="lines">@@ -62,3 +77,27 @@
</span><span class="cx">     fullName = Attribute(&quot;Full name for the entity associated with the record: C{str}&quot;)
</span><span class="cx"> 
</span><span class="cx">     displayName = Attribute(&quot;Display name for entity associated with the record: C{str}&quot;)
</span><ins>+
+    def serverURI(): #@NoSelf
+        &quot;&quot;&quot;
+        Return the URI for the record's server &quot;pod&quot;.
+
+        @return: a URI.
+        @rtype: C{str}
+        &quot;&quot;&quot;
+
+    def server(): #@NoSelf
+        &quot;&quot;&quot;
+        Return the L{txdav.caldav.datastore.scheduling.localservers.Server} for the record's server &quot;pod&quot;.
+
+        @return: a pod server record.
+        @rtype: L{txdav.caldav.datastore.scheduling.localservers.Server}
+        &quot;&quot;&quot;
+
+    def thisServer(): #@NoSelf
+        &quot;&quot;&quot;
+        Indicates whether the record is hosted on this server &quot;pod&quot;.
+
+        @return: C{True} if hosted by this service.
+        @rtype: C{bool}
+        &quot;&quot;&quot;
</ins></span></pre></div>
<a id="CalendarServertrunktxweb2httppy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txweb2/http.py (12210 => 12211)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txweb2/http.py        2014-01-02 17:02:37 UTC (rev 12210)
+++ CalendarServer/trunk/txweb2/http.py        2014-01-02 17:19:18 UTC (rev 12211)
</span><span class="lines">@@ -558,7 +558,7 @@
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     def __init__(self, code, jobj):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        @param xml_responses: an iterable of davxml.Response objects.
</del><ins>+        @param jobj: a Python object that can be serialized to JSON.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Response.__init__(self, code, stream=json.dumps(jobj))
</span><span class="cx">         self.headers.setHeader(&quot;content-type&quot;, http_headers.MimeType(&quot;application&quot;, &quot;json&quot;))
</span></span></pre>
</div>
</div>

</body>
</html>