<!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>[15011] CalendarServer/branches/users/cdaboo/cfod</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/15011">15011</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-07-27 19:43:19 -0700 (Mon, 27 Jul 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Merge from trunk.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboocfodproject">CalendarServer/branches/users/cdaboo/cfod/.project</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodpydevproject">CalendarServer/branches/users/cdaboo/cfod/.pydevproject</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodbinpackage">CalendarServer/branches/users/cdaboo/cfod/bin/package</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcalendarserveraccesslogpy">CalendarServer/branches/users/cdaboo/cfod/calendarserver/accesslog.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcalendarservertoolsconfigpy">CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/config.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcalendarservertoolsgatewaypy">CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/gateway.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcalendarservertoolspurgepy">CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/purge.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcalendarservertoolstesttest_configpy">CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_config.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcalendarservertoolstesttest_gatewaypy">CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_gateway.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcalendarservertoolstesttest_purge_old_eventspy">CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_purge_old_events.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodconfcaldavdappleplist">CalendarServer/branches/users/cdaboo/cfod/conf/caldavd-apple.plist</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcontribperformanceloadtestsimpy">CalendarServer/branches/users/cdaboo/cfod/contrib/performance/loadtest/sim.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfoddocExtensionscaldavrecursplittxt">CalendarServer/branches/users/cdaboo/cfod/doc/Extensions/caldav-recursplit.txt</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfoddocExtensionscaldavrecursplitxml">CalendarServer/branches/users/cdaboo/cfod/doc/Extensions/caldav-recursplit.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodrequirementsdevtxt">CalendarServer/branches/users/cdaboo/cfod/requirements-dev.txt</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodrequirementsstabletxt">CalendarServer/branches/users/cdaboo/cfod/requirements-stable.txt</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodsetuppy">CalendarServer/branches/users/cdaboo/cfod/setup.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodsupportApplemake">CalendarServer/branches/users/cdaboo/cfod/support/Apple.make</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavcontrolapipy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/controlapi.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavcustomxmlpy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/customxml.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavextensionspy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/extensions.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavicalpy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/ical.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavstdconfigpy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavstorebridgepy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/storebridge.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavtesttest_configpy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_config.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavtesttest_icalendarpy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_icalendar.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavtesttest_sharingpy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavtesttest_upgradepy">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_upgrade.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoAfricaCasablancaics">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Africa/Casablanca.ics</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoAfricaEl_Aaiunics">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Africa/El_Aaiun.ics</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoAmericaCaymanics">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/America/Cayman.ics</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoMoroccoStandardTimeics">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Morocco Standard Time.ics</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfolinkstxt">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/links.txt</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfotimezonesxml">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/timezones.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoversiontxt">CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/version.txt</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavbasedatastoresubpostgrespy">CalendarServer/branches/users/cdaboo/cfod/txdav/base/datastore/subpostgres.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingicalsplitterpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/icalsplitter.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimipinboundpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/inbound.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimipoutboundpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/outbound.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimipsmtpsenderpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/smtpsender.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimiptesttest_inboundpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/test/test_inbound.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimiptesttest_outboundpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/test/test_outbound.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingprocessingpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/processing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoresql_externalpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretestcalendar_storehomehome_badcalendar_bad1ics">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/calendar_store/ho/me/home_bad/calendar_bad/1.ics</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretesttest_attachmentspy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_attachments.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretesttest_filepy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_file.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretesttest_queue_schedulingpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_queue_scheduling.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretestutilpy">CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcarddavdatastoresqlpy">CalendarServer/branches/users/cdaboo/cfod/txdav/carddav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcarddavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/cfod/txdav/carddav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastorefilepy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/file.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastorepoddingattachmentspy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/podding/attachments.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastorepoddingtesttest_conduitpy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/podding/test/test_conduit.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemacurrentoracledialectsql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/current-oracle-dialect.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemacurrentsql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/current.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_sharingpy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_utilpy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoretestaccountsaccountsxml">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/test/accounts/accounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoretestutilpy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreupgradesqltesttest_upgrade_with_datapy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/upgrade/sql/test/test_upgrade_with_data.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreupgradetesttest_migratepy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/upgrade/test/test_migrate.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreworkrevision_cleanuppy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/revision_cleanup.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreworktesttest_revision_cleanuppy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/test/test_revision_cleanup.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavdpsserverpy">CalendarServer/branches/users/cdaboo/cfod/txdav/dps/server.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhodirectorypy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/directory.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhogroupspy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/groups.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhotestaccountsgroupAccountsxml">CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/accounts/groupAccounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_directorypy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_directory.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_group_attendeespy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_group_attendees.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_group_shareespy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_group_sharees.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_utilpy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_wikipy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_wiki.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhoutilpy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavwhowikipy">CalendarServer/branches/users/cdaboo/cfod/txdav/who/wiki.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>CalendarServer/branches/users/cdaboo/cfod/contrib/performance/jobqueue/</li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcontribtoolsflowd">CalendarServer/branches/users/cdaboo/cfod/contrib/tools/flow.d</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodcontribtoolslldb_utilspy">CalendarServer/branches/users/cdaboo/cfod/contrib/tools/lldb_utils.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodsupportpythonwrapperc">CalendarServer/branches/users/cdaboo/cfod/support/python-wrapper.c</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodsupportundovirtualenv">CalendarServer/branches/users/cdaboo/cfod/support/undo-virtualenv</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaoldoracledialectv55sql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/oracle-dialect/v55.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaoldoracledialectv56sql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/oracle-dialect/v56.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaoldpostgresdialectv55sql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/postgres-dialect/v55.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaoldpostgresdialectv56sql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/postgres-dialect/v56.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_55_to_56sql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_55_to_56.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_56_to_57sql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_56_to_57.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_55_to_56sql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_55_to_56.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_56_to_57sql">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_56_to_57.sql</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreworkload_workpy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/load_work.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreworktesttest_load_workpy">CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/test/test_load_work.py</a></li>
</ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboocfod">CalendarServer/branches/users/cdaboo/cfod/</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboocfod"></a>
<div class="propset"><h4>Property changes: CalendarServer/branches/users/cdaboo/cfod</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/release/CalendarServer-5.2-dev:11972,12357-12358,12794,12814
</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/pod2pod-migration:14338-14520
</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/scheduling-queue-refresh:11783-12557
</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/cleanrevisions:12152-12334
</span><span class="cx">/CalendarServer/branches/users/gaya/groupsharee2:13669-13773
</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/move2who:12819-12860
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-2:12861-12898
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-3:12899-12913
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-4:12914-13157
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-5:13158-13163
</span><span class="cx">/CalendarServer/branches/users/sagen/newcua:13309-13327
</span><span class="cx">/CalendarServer/branches/users/sagen/newcua-1:13328-13330
</span><span class="cx">/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
</span><span class="cx">/CalendarServer/branches/users/sagen/recordtypes:13648-13656
</span><span class="cx">/CalendarServer/branches/users/sagen/recordtypes-2:13657
</span><span class="cx">/CalendarServer/branches/users/sagen/request-socket:14748-14767
</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/sagen/trashcan:14185-14269
</span><span class="cx">/CalendarServer/branches/users/sagen/trashcan-2:14270-14324
</span><span class="cx">/CalendarServer/branches/users/sagen/trashcan-3:14325-14450
</span><span class="cx">/CalendarServer/branches/users/sagen/trashcan-4:14451-14471
</span><span class="cx">/CalendarServer/branches/users/sagen/trashcan-5:14471-14555
</span><span class="cx">/CalendarServer/branches/users/wsanchez/psycopg2cffi:14427-14439
</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,12357-12358,12794,12814
</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/pod2pod-migration:14338-14520
</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/scheduling-queue-refresh:11783-12557
</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/cleanrevisions:12152-12334
</span><span class="cx">/CalendarServer/branches/users/gaya/groupsharee2:13669-13773
</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/move2who:12819-12860
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-2:12861-12898
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-3:12899-12913
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-4:12914-13157
</span><span class="cx">/CalendarServer/branches/users/sagen/move2who-5:13158-13163
</span><span class="cx">/CalendarServer/branches/users/sagen/newcua:13309-13327
</span><span class="cx">/CalendarServer/branches/users/sagen/newcua-1:13328-13330
</span><span class="cx">/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
</span><span class="cx">/CalendarServer/branches/users/sagen/recordtypes:13648-13656
</span><span class="cx">/CalendarServer/branches/users/sagen/recordtypes-2:13657
</span><span class="cx">/CalendarServer/branches/users/sagen/request-socket:14748-14767
</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/sagen/trashcan:14185-14269
</span><span class="cx">/CalendarServer/branches/users/sagen/trashcan-2:14270-14324
</span><span class="cx">/CalendarServer/branches/users/sagen/trashcan-3:14325-14450
</span><span class="cx">/CalendarServer/branches/users/sagen/trashcan-4:14451-14471
</span><span class="cx">/CalendarServer/branches/users/sagen/trashcan-5:14471-14555
</span><span class="cx">/CalendarServer/branches/users/wsanchez/psycopg2cffi:14427-14439
</span><span class="cx">/CalendarServer/branches/users/wsanchez/transations:5515-5593
</span><span class="cx">/CalendarServer/trunk:14901-15009
</span><a id="CalendarServerbranchesuserscdaboocfodproject"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/.project (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/.project        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/.project        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -1,19 +1,16 @@
</span><span class="cx"> &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
</span><span class="cx"> &lt;projectDescription&gt;
</span><del>-        &lt;name&gt;CalendarServer&lt;/name&gt;
</del><ins>+        &lt;name&gt;CalendarServer-4&lt;/name&gt;
</ins><span class="cx">         &lt;comment&gt;&lt;/comment&gt;
</span><span class="cx">         &lt;projects&gt;
</span><span class="cx">                 &lt;project&gt;CalDAVClientLibrary&lt;/project&gt;
</span><span class="cx">                 &lt;project&gt;cffi&lt;/project&gt;
</span><del>-                &lt;project&gt;cx_Oracle&lt;/project&gt;
-                &lt;project&gt;kerberos&lt;/project&gt;
</del><span class="cx">                 &lt;project&gt;pg8000&lt;/project&gt;
</span><span class="cx">                 &lt;project&gt;psutil&lt;/project&gt;
</span><span class="cx">                 &lt;project&gt;pycalendar&lt;/project&gt;
</span><del>-                &lt;project&gt;pycparser&lt;/project&gt;
</del><span class="cx">                 &lt;project&gt;pycrypto&lt;/project&gt;
</span><del>-                &lt;project&gt;pyOpenSSL&lt;/project&gt;
-                &lt;project&gt;service_identity&lt;/project&gt;
</del><ins>+                &lt;project&gt;PyKerberos&lt;/project&gt;
+                &lt;project&gt;PyOpenDirectory&lt;/project&gt;
</ins><span class="cx">                 &lt;project&gt;twextpy&lt;/project&gt;
</span><span class="cx">                 &lt;project&gt;Twisted&lt;/project&gt;
</span><span class="cx">                 &lt;project&gt;zope.interface-4.0.3&lt;/project&gt;
</span><span class="lines">@@ -28,24 +25,4 @@
</span><span class="cx">         &lt;natures&gt;
</span><span class="cx">                 &lt;nature&gt;org.python.pydev.pythonNature&lt;/nature&gt;
</span><span class="cx">         &lt;/natures&gt;
</span><del>-        &lt;filteredResources&gt;
-                &lt;filter&gt;
-                        &lt;id&gt;1396668930421&lt;/id&gt;
-                        &lt;name&gt;&lt;/name&gt;
-                        &lt;type&gt;10&lt;/type&gt;
-                        &lt;matcher&gt;
-                                &lt;id&gt;org.eclipse.ui.ide.multiFilter&lt;/id&gt;
-                                &lt;arguments&gt;1.0-projectRelativePath-matches-true-false-.develop&lt;/arguments&gt;
-                        &lt;/matcher&gt;
-                &lt;/filter&gt;
-                &lt;filter&gt;
-                        &lt;id&gt;1396668930422&lt;/id&gt;
-                        &lt;name&gt;&lt;/name&gt;
-                        &lt;type&gt;10&lt;/type&gt;
-                        &lt;matcher&gt;
-                                &lt;id&gt;org.eclipse.ui.ide.multiFilter&lt;/id&gt;
-                                &lt;arguments&gt;1.0-name-matches-true-false-subprojects&lt;/arguments&gt;
-                        &lt;/matcher&gt;
-                &lt;/filter&gt;
-        &lt;/filteredResources&gt;
</del><span class="cx"> &lt;/projectDescription&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodpydevproject"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/.pydevproject (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/.pydevproject        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/.pydevproject        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -1,9 +1,8 @@
</span><span class="cx"> &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;
</span><span class="cx"> &lt;?eclipse-pydev version=&quot;1.0&quot;?&gt;&lt;pydev_project&gt;
</span><ins>+&lt;pydev_property name=&quot;org.python.pydev.PYTHON_PROJECT_INTERPRETER&quot;&gt;PyPy-2.6.0&lt;/pydev_property&gt;
</ins><span class="cx"> &lt;pydev_property name=&quot;org.python.pydev.PYTHON_PROJECT_VERSION&quot;&gt;python 2.7&lt;/pydev_property&gt;
</span><span class="cx"> &lt;pydev_pathproperty name=&quot;org.python.pydev.PROJECT_SOURCE_PATH&quot;&gt;
</span><del>-&lt;path&gt;/${PROJECT_DIR_NAME}&lt;/path&gt;
</del><ins>+&lt;path&gt;/CalendarServer-4&lt;/path&gt;
</ins><span class="cx"> &lt;/pydev_pathproperty&gt;
</span><del>-
-&lt;pydev_property name=&quot;org.python.pydev.PYTHON_PROJECT_INTERPRETER&quot;&gt;Default&lt;/pydev_property&gt;
</del><span class="cx"> &lt;/pydev_project&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodbinpackage"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/bin/package (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/bin/package        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/bin/package        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -115,6 +115,7 @@
</span><span class="cx">     bootstrap_virtualenv;
</span><span class="cx">     &quot;${bootstrap_python}&quot; -m virtualenv  \
</span><span class="cx">       --always-copy                      \
</span><ins>+      --system-site-packages             \
</ins><span class="cx">       --no-setuptools                    \
</span><span class="cx">       &quot;${py_virtualenv}&quot;;
</span><span class="cx">   fi;
</span><span class="lines">@@ -149,13 +150,29 @@
</span><span class="cx"> 
</span><span class="cx">   cd &quot;${destination}/lib&quot;;
</span><span class="cx"> 
</span><del>-  find &quot;../roots&quot; -type f  \
</del><ins>+  find &quot;../roots&quot; &quot;(&quot; -type f -o -type l &quot;)&quot;  \
</ins><span class="cx">     &quot;(&quot;                    \
</span><span class="cx">       -name '*.so'     -o  \
</span><span class="cx">       -name '*.so.*'   -o  \
</span><span class="cx">       -name '*.dylib'      \
</span><span class="cx">     &quot;)&quot; -print0            \
</span><span class="cx">     | xargs -0 -I % ln -s % .;
</span><ins>+
+  # Write out environment.sh
+  local dst=&quot;${destination}&quot;;
+  cat &gt; &quot;${dst}/environment.sh&quot; &lt;&lt; __EOF__
+export              PATH=&quot;${dst}/bin:\${PATH}&quot;;
+export    C_INCLUDE_PATH=&quot;${dst}/include:\${C_INCLUDE_PATH:-}&quot;;
+export   LD_LIBRARY_PATH=&quot;${dst}/lib:${dst}/lib64:\${LD_LIBRARY_PATH:-}:\$ORACLE_HOME&quot;;
+export          CPPFLAGS=&quot;-I${dst}/include \${CPPFLAGS:-} &quot;;
+export           LDFLAGS=&quot;-L${dst}/lib -L${dst}/lib64 \${LDFLAGS:-} &quot;;
+export DYLD_LIBRARY_PATH=&quot;${dst}/lib:${dst}/lib64:\${DYLD_LIBRARY_PATH:-}:\$ORACLE_HOME&quot;;
+__EOF__
+
+  # Install CalendarServer into venv
+  cd ${wd}
+  ${python} setup.py install
+
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcalendarserveraccesslogpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/calendarserver/accesslog.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/calendarserver/accesslog.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/calendarserver/accesslog.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -181,6 +181,13 @@
</span><span class="cx">                     format += &quot; fwd=%(fwd)s&quot;
</span><span class="cx">                     formatArgs[&quot;fwd&quot;] = forwardedFor
</span><span class="cx"> 
</span><ins>+            if formatArgs[&quot;host&quot;] == &quot;0.0.0.0&quot;:
+                fwdHeaders = request.headers.getRawHeaders(&quot;x-forwarded-for&quot;, &quot;&quot;)
+                if fwdHeaders:
+                    formatArgs[&quot;host&quot;] = fwdHeaders[-1].split(&quot;,&quot;)[-1].strip()
+                    format += &quot; unix=%(unix)s&quot;
+                    formatArgs[&quot;unix&quot;] = &quot;true&quot;
+
</ins><span class="cx">         elif &quot;overloaded&quot; in eventDict:
</span><span class="cx">             overloaded = eventDict.get(&quot;overloaded&quot;)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcalendarservertoolsconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/config.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/config.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/config.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -59,12 +59,6 @@
</span><span class="cx">     &quot;EnableSSL&quot;,
</span><span class="cx">     &quot;HTTPPort&quot;,
</span><span class="cx">     &quot;LogLevels&quot;,
</span><del>-    &quot;Notifications.Services.APNS.CalDAV.AuthorityChainPath&quot;,
-    &quot;Notifications.Services.APNS.CalDAV.CertificatePath&quot;,
-    &quot;Notifications.Services.APNS.CalDAV.PrivateKeyPath&quot;,
-    &quot;Notifications.Services.APNS.CardDAV.AuthorityChainPath&quot;,
-    &quot;Notifications.Services.APNS.CardDAV.CertificatePath&quot;,
-    &quot;Notifications.Services.APNS.CardDAV.PrivateKeyPath&quot;,
</del><span class="cx">     &quot;Notifications.Services.APNS.Enabled&quot;,
</span><span class="cx">     &quot;RedirectHTTPToHTTPS&quot;,
</span><span class="cx">     &quot;Scheduling.iMIP.Enabled&quot;,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcalendarservertoolsgatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/gateway.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/gateway.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/gateway.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -577,7 +577,7 @@
</span><span class="cx">         cutoff = DateTime.getToday()
</span><span class="cx">         cutoff.setDateOnly(False)
</span><span class="cx">         cutoff.offsetDay(-retainDays)
</span><del>-        eventCount = (yield PurgeOldEventsService.purgeOldEvents(self.store, cutoff, DEFAULT_BATCH_SIZE))
</del><ins>+        eventCount = (yield PurgeOldEventsService.purgeOldEvents(self.store, None, cutoff, DEFAULT_BATCH_SIZE))
</ins><span class="cx">         self.respond(command, {'EventsRemoved': eventCount, &quot;RetainDays&quot;: retainDays})
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcalendarservertoolspurgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/purge.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/purge.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/purge.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -29,7 +29,7 @@
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.dal.record import fromTable
</span><del>-from twext.enterprise.dal.syntax import Delete, Select, Union
</del><ins>+from twext.enterprise.dal.syntax import Delete, Select, Union, Parameter, Max
</ins><span class="cx"> from twext.enterprise.jobqueue import WorkItem, RegeneratingWorkItem
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><span class="lines">@@ -37,9 +37,11 @@
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import caldavxml
</span><span class="cx"> from twistedcaldav.config import config
</span><ins>+from twistedcaldav.dateops import parseSQLDateToPyCalendar, pyCalendarToSQLTimestamp
+from twistedcaldav.ical import Component, InvalidICalendarDataError
</ins><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.query.filter import Filter
</span><del>-from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_NORMAL
</del><ins>+from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_NORMAL, _BIND_MODE_OWN
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -277,10 +279,11 @@
</span><span class="cx"> 
</span><span class="cx"> class PurgeOldEventsService(WorkerService):
</span><span class="cx"> 
</span><ins>+    uuid = None
</ins><span class="cx">     cutoff = None
</span><span class="cx">     batchSize = None
</span><span class="cx">     dryrun = False
</span><del>-    verbose = False
</del><ins>+    debug = False
</ins><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     def usage(cls, e=None):
</span><span class="lines">@@ -293,8 +296,8 @@
</span><span class="cx">         print(&quot;options:&quot;)
</span><span class="cx">         print(&quot;  -h --help: print this help and exit&quot;)
</span><span class="cx">         print(&quot;  -f --config &lt;path&gt;: Specify caldavd.plist configuration path&quot;)
</span><ins>+        print(&quot;  -u --uuid &lt;uuid&gt;: Only process this user(s) [REQUIRED]&quot;)
</ins><span class="cx">         print(&quot;  -d --days &lt;number&gt;: specify how many days in the past to retain (default=%d)&quot; % (DEFAULT_RETAIN_DAYS,))
</span><del>-        # print(&quot;  -b --batch &lt;number&gt;: number of events to remove in each transaction (default=%d)&quot; % (DEFAULT_BATCH_SIZE,))
</del><span class="cx">         print(&quot;  -n --dry-run: calculate how many events to purge, but do not purge data&quot;)
</span><span class="cx">         print(&quot;  -v --verbose: print progress information&quot;)
</span><span class="cx">         print(&quot;  -D --debug: debug logging&quot;)
</span><span class="lines">@@ -312,11 +315,12 @@
</span><span class="cx"> 
</span><span class="cx">         try:
</span><span class="cx">             (optargs, args) = getopt(
</span><del>-                sys.argv[1:], &quot;Dd:b:f:hnv&quot;, [
</del><ins>+                sys.argv[1:], &quot;Dd:b:f:hnu:v&quot;, [
</ins><span class="cx">                     &quot;days=&quot;,
</span><span class="cx">                     &quot;batch=&quot;,
</span><span class="cx">                     &quot;dry-run&quot;,
</span><span class="cx">                     &quot;config=&quot;,
</span><ins>+                    &quot;uuid=&quot;,
</ins><span class="cx">                     &quot;help&quot;,
</span><span class="cx">                     &quot;verbose&quot;,
</span><span class="cx">                     &quot;debug&quot;,
</span><span class="lines">@@ -329,6 +333,7 @@
</span><span class="cx">         # Get configuration
</span><span class="cx">         #
</span><span class="cx">         configFileName = None
</span><ins>+        uuid = None
</ins><span class="cx">         days = DEFAULT_RETAIN_DAYS
</span><span class="cx">         batchSize = DEFAULT_BATCH_SIZE
</span><span class="cx">         dryrun = False
</span><span class="lines">@@ -365,12 +370,19 @@
</span><span class="cx">             elif opt in (&quot;-f&quot;, &quot;--config&quot;):
</span><span class="cx">                 configFileName = arg
</span><span class="cx"> 
</span><ins>+            elif opt in (&quot;-u&quot;, &quot;--uuid&quot;):
+                uuid = arg
+
</ins><span class="cx">             else:
</span><span class="cx">                 raise NotImplementedError(opt)
</span><span class="cx"> 
</span><span class="cx">         if args:
</span><span class="cx">             cls.usage(&quot;Too many arguments: %s&quot; % (args,))
</span><span class="cx"> 
</span><ins>+        if uuid is None:
+            cls.usage(&quot;uuid must be specified&quot;)
+        cls.uuid = uuid
+
</ins><span class="cx">         if dryrun:
</span><span class="cx">             verbose = True
</span><span class="cx"> 
</span><span class="lines">@@ -380,68 +392,321 @@
</span><span class="cx">         cls.cutoff = cutoff
</span><span class="cx">         cls.batchSize = batchSize
</span><span class="cx">         cls.dryrun = dryrun
</span><del>-        cls.verbose = verbose
</del><ins>+        cls.debug = debug
</ins><span class="cx"> 
</span><span class="cx">         utilityMain(
</span><span class="cx">             configFileName,
</span><span class="cx">             cls,
</span><del>-            verbose=debug,
</del><ins>+            verbose=verbose,
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def purgeOldEvents(cls, store, cutoff, batchSize, verbose=False, dryrun=False):
</del><ins>+    def purgeOldEvents(cls, store, uuid, cutoff, batchSize, debug=False, dryrun=False):
</ins><span class="cx"> 
</span><span class="cx">         service = cls(store)
</span><ins>+        service.uuid = uuid
</ins><span class="cx">         service.cutoff = cutoff
</span><span class="cx">         service.batchSize = batchSize
</span><span class="cx">         service.dryrun = dryrun
</span><del>-        service.verbose = verbose
</del><ins>+        service.debug = debug
</ins><span class="cx">         result = yield service.doWork()
</span><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def getMatchingHomeUIDs(self):
+        &quot;&quot;&quot;
+        Find all the calendar homes that match the uuid cli argument.
+        &quot;&quot;&quot;
+        log.debug(&quot;Searching for calendar homes matching: '{}'&quot;.format(self.uuid))
+        txn = self.store.newTransaction(label=&quot;Find matching homes&quot;)
+        ch = schema.CALENDAR_HOME
+        if self.uuid:
+            kwds = {&quot;uuid&quot;: self.uuid}
+            rows = (yield Select(
+                [ch.RESOURCE_ID, ch.OWNER_UID, ],
+                From=ch,
+                Where=(ch.OWNER_UID.StartsWith(Parameter(&quot;uuid&quot;))),
+            ).on(txn, **kwds))
+        else:
+            rows = (yield Select(
+                [ch.RESOURCE_ID, ch.OWNER_UID, ],
+                From=ch,
+            ).on(txn))
+
+        yield txn.commit()
+        log.debug(&quot;  Found {} calendar homes&quot;.format(len(rows)))
+        returnValue(sorted(rows, key=lambda x: x[1]))
+
+
+    @inlineCallbacks
+    def getMatchingCalendarIDs(self, home_id, owner_uid):
+        &quot;&quot;&quot;
+        Find all the owned calendars for the specified calendar home.
+
+        @param home_id: resource-id of calendar home to check
+        @type home_id: L{int}
+        @param owner_uid: owner UUID of home to check
+        @type owner_uid: L{str}
+        &quot;&quot;&quot;
+        log.debug(&quot;Checking calendar home: {} '{}'&quot;.format(home_id, owner_uid))
+        txn = self.store.newTransaction(label=&quot;Find matching calendars&quot;)
+        cb = schema.CALENDAR_BIND
+        kwds = {&quot;home_id&quot;: home_id}
+        rows = (yield Select(
+            [cb.CALENDAR_RESOURCE_ID, cb.CALENDAR_RESOURCE_NAME, ],
+            From=cb,
+            Where=(cb.CALENDAR_HOME_RESOURCE_ID == Parameter(&quot;home_id&quot;)).And(
+                cb.BIND_MODE == _BIND_MODE_OWN
+            ),
+        ).on(txn, **kwds))
+        yield txn.commit()
+        log.debug(&quot;  Found {} calendars&quot;.format(len(rows)))
+        returnValue(rows)
+
+
+    PurgeEvent = collections.namedtuple(&quot;PurgeEvent&quot;, (&quot;home&quot;, &quot;calendar&quot;, &quot;resource&quot;,))
+
+    @inlineCallbacks
+    def getResourceIDsToPurge(self, home_id, calendar_id, calendar_name):
+        &quot;&quot;&quot;
+        For the given calendar find which calendar objects are older than the cut-off and return the
+        resource-ids of those.
+
+        @param home_id: resource-id of calendar home
+        @type home_id: L{int}
+        @param calendar_id: resource-id of the calendar to check
+        @type calendar_id: L{int}
+        @param calendar_name: name of the calendar to check
+        @type calendar_name: L{str}
+        &quot;&quot;&quot;
+
+        log.debug(&quot;  Checking calendar: {} '{}'&quot;.format(calendar_id, calendar_name))
+        purge = set()
+        txn = self.store.newTransaction(label=&quot;Find matching resources&quot;)
+        co = schema.CALENDAR_OBJECT
+        tr = schema.TIME_RANGE
+        kwds = {&quot;calendar_id&quot;: calendar_id}
+        rows = (yield Select(
+            [co.RESOURCE_ID, co.RECURRANCE_MAX, co.RECURRANCE_MIN, Max(tr.END_DATE)],
+            From=co.join(tr, on=(co.RESOURCE_ID == tr.CALENDAR_OBJECT_RESOURCE_ID)),
+            Where=(co.CALENDAR_RESOURCE_ID == Parameter(&quot;calendar_id&quot;)).And(
+                co.ICALENDAR_TYPE == &quot;VEVENT&quot;
+            ),
+            GroupBy=(co.RESOURCE_ID, co.RECURRANCE_MAX, co.RECURRANCE_MIN,),
+            Having=(
+                (co.RECURRANCE_MAX == None).And(Max(tr.END_DATE) &lt; pyCalendarToSQLTimestamp(self.cutoff))
+            ).Or(
+                (co.RECURRANCE_MAX != None).And(co.RECURRANCE_MAX &lt; pyCalendarToSQLTimestamp(self.cutoff))
+            ),
+        ).on(txn, **kwds))
+
+        log.debug(&quot;    Found {} resources to check&quot;.format(len(rows)))
+        for resource_id, recurrence_max, recurrence_min, max_end_date in rows:
+
+            recurrence_max = parseSQLDateToPyCalendar(recurrence_max) if recurrence_max else None
+            recurrence_min = parseSQLDateToPyCalendar(recurrence_min) if recurrence_min else None
+            max_end_date = parseSQLDateToPyCalendar(max_end_date) if max_end_date else None
+
+            # Find events where we know the max(end_date) represents a valid,
+            # untruncated expansion
+            if recurrence_min is None or recurrence_min &lt; self.cutoff:
+                if recurrence_max is None:
+                    # Here we know max_end_date is the fully expand final instance
+                    if max_end_date &lt; self.cutoff:
+                        purge.add(self.PurgeEvent(home_id, calendar_id, resource_id,))
+                    continue
+                elif recurrence_max &gt; self.cutoff:
+                    # Here we know that there are instances newer than the cut-off
+                    # but they have not yet been indexed out that far
+                    continue
+
+            # Manually detect the max_end_date from the actual calendar data
+            calendar = yield self.getCalendar(txn, resource_id)
+            if calendar is not None:
+                if self.checkLastInstance(calendar):
+                    purge.add(self.PurgeEvent(home_id, calendar_id, resource_id,))
+
+        yield txn.commit()
+        log.debug(&quot;    Found {} resources to purge&quot;.format(len(purge)))
+        returnValue(purge)
+
+
+    @inlineCallbacks
+    def getCalendar(self, txn, resid):
+        &quot;&quot;&quot;
+        Get the calendar data for a calendar object resource.
+
+        @param resid: resource-id of the calendar object resource to load
+        @type resid: L{int}
+        &quot;&quot;&quot;
+        co = schema.CALENDAR_OBJECT
+        kwds = {&quot;ResourceID&quot; : resid}
+        rows = (yield Select(
+            [co.ICALENDAR_TEXT],
+            From=co,
+            Where=(
+                co.RESOURCE_ID == Parameter(&quot;ResourceID&quot;)
+            ),
+        ).on(txn, **kwds))
+        try:
+            caldata = Component.fromString(rows[0][0]) if rows else None
+        except InvalidICalendarDataError:
+            returnValue(None)
+
+        returnValue(caldata)
+
+
+    def checkLastInstance(self, calendar):
+        &quot;&quot;&quot;
+        Determine the last instance of a calendar event. Try a &quot;static&quot; analysis of the data first,
+        and only if needed, do an instance expansion.
+
+        @param calendar: the calendar object to examine
+        @type calendar: L{Component}
+        &quot;&quot;&quot;
+
+        # Is it recurring
+        master = calendar.masterComponent()
+        if not calendar.isRecurring() or master is None:
+            # Just check the end date
+            for comp in calendar.subcomponents():
+                if comp.name() == &quot;VEVENT&quot;:
+                    if comp.getEndDateUTC() &gt; self.cutoff:
+                        return False
+            else:
+                return True
+        elif calendar.isRecurringUnbounded():
+            return False
+        else:
+            # First test all sub-components
+            # Just check the end date
+            for comp in calendar.subcomponents():
+                if comp.name() == &quot;VEVENT&quot;:
+                    if comp.getEndDateUTC() &gt; self.cutoff:
+                        return False
+
+            # If we get here we need to test the RRULE - if there is an until use
+            # that as the end point, if a count, we have to expand
+            rrules = tuple(master.properties(&quot;RRULE&quot;))
+            if len(rrules):
+                if rrules[0].value().getUseUntil():
+                    return rrules[0].value().getUntil() &lt; self.cutoff
+                else:
+                    return not calendar.hasInstancesAfter(self.cutoff)
+
+        return True
+
+
+    @inlineCallbacks
+    def getResourcesToPurge(self, home_id, owner_uid):
+        &quot;&quot;&quot;
+        Find all the resource-ids of calendar object resources that need to be purged in the specified home.
+
+        @param home_id: resource-id of calendar home to check
+        @type home_id: L{int}
+        @param owner_uid: owner UUID of home to check
+        @type owner_uid: L{str}
+        &quot;&quot;&quot;
+
+        purge = set()
+        calendars = yield self.getMatchingCalendarIDs(home_id, owner_uid)
+        for calendar_id, calendar_name in calendars:
+            purge.update((yield self.getResourceIDsToPurge(home_id, calendar_id, calendar_name)))
+
+        returnValue(purge)
+
+
+    @inlineCallbacks
+    def purgeResources(self, events):
+        &quot;&quot;&quot;
+        Remove up to batchSize events and return how
+        many were removed.
+        &quot;&quot;&quot;
+
+        txn = self.store.newTransaction(label=&quot;Remove old events&quot;)
+        count = 0
+        last_home = None
+        last_calendar = None
+        for event in events:
+            if event.home != last_home:
+                home = (yield txn.calendarHomeWithResourceID(event.home))
+                last_home = event.home
+            if event.calendar != last_calendar:
+                calendar = (yield home.childWithID(event.calendar))
+                last_calendar = event.calendar
+            resource = (yield calendar.objectResourceWithID(event.resource))
+            yield resource.purge(implicitly=False)
+            log.debug(&quot;Removed resource {} '{}' from calendar {} '{}' of calendar home '{}'&quot;.format(
+                resource.id(),
+                resource.name(),
+                resource.parentCollection().id(),
+                resource.parentCollection().name(),
+                resource.parentCollection().ownerHome().uid()
+            ))
+            count += 1
+        yield txn.commit()
+        returnValue(count)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def doWork(self):
</span><span class="cx"> 
</span><ins>+        if self.debug:
+            # Turn on debug logging for this module
+            config.LogLevels[__name__] = &quot;debug&quot;
+        else:
+            config.LogLevels[__name__] = &quot;info&quot;
+        config.update()
+
+        homes = yield self.getMatchingHomeUIDs()
+        if not homes:
+            log.info(&quot;No homes to process&quot;)
+            returnValue(0)
+
</ins><span class="cx">         if self.dryrun:
</span><del>-            if self.verbose:
-                print(&quot;(Dry run) Searching for old events...&quot;)
-            txn = self.store.newTransaction(label=&quot;Find old events&quot;)
-            oldEvents = yield txn.eventsOlderThan(self.cutoff)
-            eventCount = len(oldEvents)
-            if self.verbose:
-                if eventCount == 0:
-                    print(&quot;No events are older than %s&quot; % (self.cutoff,))
-                elif eventCount == 1:
-                    print(&quot;1 event is older than %s&quot; % (self.cutoff,))
-                else:
-                    print(&quot;%d events are older than %s&quot; % (eventCount, self.cutoff))
</del><ins>+            log.info(&quot;Purge dry run only&quot;)
+
+        log.info(&quot;Searching for old events...&quot;)
+
+        purge = set()
+        homes = yield self.getMatchingHomeUIDs()
+        for home_id, owner_uid in homes:
+            purge.update((yield self.getResourcesToPurge(home_id, owner_uid)))
+
+        if self.dryrun:
+            eventCount = len(purge)
+            if eventCount == 0:
+                log.info(&quot;No events are older than %s&quot; % (self.cutoff,))
+            elif eventCount == 1:
+                log.info(&quot;1 event is older than %s&quot; % (self.cutoff,))
+            else:
+                log.info(&quot;%d events are older than %s&quot; % (eventCount, self.cutoff))
</ins><span class="cx">             returnValue(eventCount)
</span><span class="cx"> 
</span><del>-        if self.verbose:
-            print(&quot;Removing events older than %s...&quot; % (self.cutoff,))
</del><ins>+        purge = list(purge)
+        purge.sort()
+        totalEvents = len(purge)
</ins><span class="cx"> 
</span><ins>+        log.info(&quot;Removing {} events older than {}...&quot;.format(len(purge), self.cutoff,))
+
</ins><span class="cx">         numEventsRemoved = -1
</span><span class="cx">         totalRemoved = 0
</span><span class="cx">         while numEventsRemoved:
</span><del>-            txn = self.store.newTransaction(label=&quot;Remove old events&quot;)
-            numEventsRemoved = yield txn.removeOldEvents(self.cutoff, batchSize=self.batchSize)
-            yield txn.commit()
</del><ins>+            numEventsRemoved = (yield self.purgeResources(purge[:self.batchSize]))
</ins><span class="cx">             if numEventsRemoved:
</span><span class="cx">                 totalRemoved += numEventsRemoved
</span><del>-                if self.verbose:
-                    print(&quot;%d,&quot; % (totalRemoved,),)
</del><ins>+                log.debug(&quot;  Removed {} of {} events...&quot;.format(totalRemoved, totalEvents))
+                purge = purge[numEventsRemoved:]
</ins><span class="cx"> 
</span><del>-        if self.verbose:
-            print(&quot;&quot;)
-            if totalRemoved == 0:
-                print(&quot;No events were removed&quot;)
-            elif totalRemoved == 1:
-                print(&quot;1 event was removed in total&quot;)
-            else:
-                print(&quot;%d events were removed in total&quot; % (totalRemoved,))
</del><ins>+        if totalRemoved == 0:
+            log.info(&quot;No events were removed&quot;)
+        elif totalRemoved == 1:
+            log.info(&quot;1 event was removed in total&quot;)
+        else:
+            log.info(&quot;%d events were removed in total&quot; % (totalRemoved,))
</ins><span class="cx"> 
</span><span class="cx">         returnValue(totalRemoved)
</span><span class="cx"> 
</span><span class="lines">@@ -671,20 +936,18 @@
</span><span class="cx">             # Print table of results
</span><span class="cx">             table = tables.Table()
</span><span class="cx">             table.addHeader((&quot;User&quot;, &quot;Current Quota&quot;, &quot;Orphan Size&quot;, &quot;Orphan Count&quot;, &quot;Dropbox Size&quot;, &quot;Dropbox Count&quot;, &quot;Managed Size&quot;, &quot;Managed Count&quot;, &quot;Total Size&quot;, &quot;Total Count&quot;))
</span><del>-            table.setDefaultColumnFormats(
-                (
-                    tables.Table.ColumnFormat(&quot;%s&quot;, tables.Table.ColumnFormat.LEFT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                    tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
-                )
-            )
</del><ins>+            table.setDefaultColumnFormats((
+                tables.Table.ColumnFormat(&quot;%s&quot;, tables.Table.ColumnFormat.LEFT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+                tables.Table.ColumnFormat(&quot;%d&quot;, tables.Table.ColumnFormat.RIGHT_JUSTIFY),
+            ))
</ins><span class="cx"> 
</span><span class="cx">             totals = [0] * 8
</span><span class="cx">             for user, data in sorted(byuser.items(), key=lambda x: x[0]):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcalendarservertoolstesttest_configpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_config.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_config.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_config.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -131,7 +131,6 @@
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;DefaultLogLevel&quot;], &quot;warn&quot;)
</span><span class="cx"> 
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;Notifications&quot;][&quot;Services&quot;][&quot;APNS&quot;][&quot;Enabled&quot;], False)
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;Notifications&quot;][&quot;Services&quot;][&quot;APNS&quot;][&quot;CalDAV&quot;][&quot;CertificatePath&quot;], &quot;/example/calendar.cer&quot;)
</del><span class="cx"> 
</span><span class="cx">         # Verify not all keys are present, such as umask which is not accessible
</span><span class="cx">         self.assertFalse(&quot;umask&quot; in results[&quot;result&quot;])
</span><span class="lines">@@ -150,7 +149,6 @@
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;EnableCardDAV&quot;], False)
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;EnableSSL&quot;], True)
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;Notifications&quot;][&quot;Services&quot;][&quot;APNS&quot;][&quot;Enabled&quot;], True)
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;Notifications&quot;][&quot;Services&quot;][&quot;APNS&quot;][&quot;CalDAV&quot;][&quot;CertificatePath&quot;], &quot;/example/changed.cer&quot;)
</del><span class="cx">         hostName = &quot;hostname_%s_%s&quot; % (unichr(208), u&quot;\ud83d\udca3&quot;)
</span><span class="cx">         self.assertTrue(results[&quot;result&quot;][&quot;ServerHostName&quot;].endswith(hostName))
</span><span class="cx"> 
</span><span class="lines">@@ -242,8 +240,6 @@
</span><span class="cx">             &lt;true/&gt;
</span><span class="cx">             &lt;key&gt;Notifications.Services.APNS.Enabled&lt;/key&gt;
</span><span class="cx">             &lt;true/&gt;
</span><del>-            &lt;key&gt;Notifications.Services.APNS.CalDAV.CertificatePath&lt;/key&gt;
-            &lt;string&gt;/example/changed.cer&lt;/string&gt;
</del><span class="cx">             &lt;key&gt;ServerRoot&lt;/key&gt;
</span><span class="cx">             &lt;string&gt;/You/Shall/Not/Pass&lt;/string&gt;
</span><span class="cx">             &lt;key&gt;ServerHostName&lt;/key&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcalendarservertoolstesttest_gatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_gateway.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_gateway.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_gateway.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -466,7 +466,6 @@
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;DefaultLogLevel&quot;], &quot;warn&quot;)
</span><span class="cx"> 
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;Notifications&quot;][&quot;Services&quot;][&quot;APNS&quot;][&quot;Enabled&quot;], False)
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;Notifications&quot;][&quot;Services&quot;][&quot;APNS&quot;][&quot;CalDAV&quot;][&quot;CertificatePath&quot;], &quot;/example/calendar.cer&quot;)
</del><span class="cx"> 
</span><span class="cx">         # This is a read only key that is returned
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;ServerRoot&quot;], self.absoluteServerRoot)
</span><span class="lines">@@ -489,7 +488,6 @@
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;EnableCardDAV&quot;], False)
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;EnableSSL&quot;], True)
</span><span class="cx">         self.assertEquals(results[&quot;result&quot;][&quot;Notifications&quot;][&quot;Services&quot;][&quot;APNS&quot;][&quot;Enabled&quot;], True)
</span><del>-        self.assertEquals(results[&quot;result&quot;][&quot;Notifications&quot;][&quot;Services&quot;][&quot;APNS&quot;][&quot;CalDAV&quot;][&quot;CertificatePath&quot;], &quot;/example/changed.cer&quot;)
</del><span class="cx">         hostName = &quot;hostname_%s_%s&quot; % (unichr(208), u&quot;\ud83d\udca3&quot;)
</span><span class="cx">         self.assertTrue(results[&quot;result&quot;][&quot;ServerHostName&quot;].endswith(hostName))
</span><span class="cx"> 
</span><span class="lines">@@ -972,8 +970,6 @@
</span><span class="cx">             &lt;true/&gt;
</span><span class="cx">             &lt;key&gt;Notifications.Services.APNS.Enabled&lt;/key&gt;
</span><span class="cx">             &lt;true/&gt;
</span><del>-            &lt;key&gt;Notifications.Services.APNS.CalDAV.CertificatePath&lt;/key&gt;
-            &lt;string&gt;/example/changed.cer&lt;/string&gt;
</del><span class="cx">             &lt;key&gt;ServerHostName&lt;/key&gt;
</span><span class="cx">             &lt;string&gt;hostname_%s_%s&lt;/string&gt;
</span><span class="cx">         &lt;/dict&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcalendarservertoolstesttest_purge_old_eventspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_purge_old_events.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_purge_old_events.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/calendarserver/tools/test/test_purge_old_events.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -642,33 +642,123 @@
</span><span class="cx">         # Dry run
</span><span class="cx">         total = (yield PurgeOldEventsService.purgeOldEvents(
</span><span class="cx">             self._sqlCalendarStore,
</span><ins>+            None,
</ins><span class="cx">             DateTime(now, 4, 1, 0, 0, 0),
</span><span class="cx">             2,
</span><span class="cx">             dryrun=True,
</span><del>-            verbose=False
</del><ins>+            debug=True
</ins><span class="cx">         ))
</span><span class="cx">         self.assertEquals(total, 13)
</span><span class="cx"> 
</span><span class="cx">         # Actually remove
</span><span class="cx">         total = (yield PurgeOldEventsService.purgeOldEvents(
</span><span class="cx">             self._sqlCalendarStore,
</span><ins>+            None,
</ins><span class="cx">             DateTime(now, 4, 1, 0, 0, 0),
</span><span class="cx">             2,
</span><del>-            verbose=False
</del><ins>+            debug=True
</ins><span class="cx">         ))
</span><span class="cx">         self.assertEquals(total, 13)
</span><span class="cx"> 
</span><span class="cx">         # There should be no more left
</span><span class="cx">         total = (yield PurgeOldEventsService.purgeOldEvents(
</span><span class="cx">             self._sqlCalendarStore,
</span><ins>+            None,
</ins><span class="cx">             DateTime(now, 4, 1, 0, 0, 0),
</span><span class="cx">             2,
</span><del>-            verbose=False
</del><ins>+            debug=True
</ins><span class="cx">         ))
</span><span class="cx">         self.assertEquals(total, 0)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_purgeOldEvents_home_filtering(self):
+
+        # Dry run
+        total = (yield PurgeOldEventsService.purgeOldEvents(
+            self._sqlCalendarStore,
+            &quot;ho&quot;,
+            DateTime(now, 4, 1, 0, 0, 0),
+            2,
+            dryrun=True,
+            debug=True
+        ))
+        self.assertEquals(total, 13)
+
+        # Dry run
+        total = (yield PurgeOldEventsService.purgeOldEvents(
+            self._sqlCalendarStore,
+            &quot;home&quot;,
+            DateTime(now, 4, 1, 0, 0, 0),
+            2,
+            dryrun=True,
+            debug=True
+        ))
+        self.assertEquals(total, 13)
+
+        # Dry run
+        total = (yield PurgeOldEventsService.purgeOldEvents(
+            self._sqlCalendarStore,
+            &quot;home1&quot;,
+            DateTime(now, 4, 1, 0, 0, 0),
+            2,
+            dryrun=True,
+            debug=True
+        ))
+        self.assertEquals(total, 5)
+
+        # Dry run
+        total = (yield PurgeOldEventsService.purgeOldEvents(
+            self._sqlCalendarStore,
+            &quot;home2&quot;,
+            DateTime(now, 4, 1, 0, 0, 0),
+            2,
+            dryrun=True,
+            debug=True
+        ))
+        self.assertEquals(total, 8)
+
+
+    @inlineCallbacks
+    def test_purgeOldEvents_old_cutoff(self):
+
+        # Dry run
+        cutoff = DateTime.getToday()
+        cutoff.setDateOnly(False)
+        cutoff.offsetDay(-400)
+
+        total = (yield PurgeOldEventsService.purgeOldEvents(
+            self._sqlCalendarStore,
+            &quot;ho&quot;,
+            cutoff,
+            2,
+            dryrun=True,
+            debug=True
+        ))
+        self.assertEquals(total, 12)
+
+        # Actually remove
+        total = (yield PurgeOldEventsService.purgeOldEvents(
+            self._sqlCalendarStore,
+            None,
+            cutoff,
+            2,
+            debug=True
+        ))
+        self.assertEquals(total, 12)
+
+        total = (yield PurgeOldEventsService.purgeOldEvents(
+            self._sqlCalendarStore,
+            &quot;ho&quot;,
+            cutoff,
+            2,
+            dryrun=True,
+            debug=True
+        ))
+        self.assertEquals(total, 0)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_purgeUID(self):
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx"> 
</span><span class="lines">@@ -753,9 +843,10 @@
</span><span class="cx">         # Remove old events first
</span><span class="cx">         total = (yield PurgeOldEventsService.purgeOldEvents(
</span><span class="cx">             self._sqlCalendarStore,
</span><ins>+            None,
</ins><span class="cx">             DateTime(now, 4, 1, 0, 0, 0),
</span><span class="cx">             2,
</span><del>-            verbose=False
</del><ins>+            debug=False
</ins><span class="cx">         ))
</span><span class="cx">         self.assertEquals(total, 13)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodconfcaldavdappleplist"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/conf/caldavd-apple.plist (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/conf/caldavd-apple.plist        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/conf/caldavd-apple.plist        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -230,6 +230,9 @@
</span><span class="cx">       &lt;/dict&gt;
</span><span class="cx">     &lt;/dict&gt;
</span><span class="cx"> 
</span><ins>+    &lt;key&gt;DirectoryFilterStartsWith&lt;/key&gt;
+    &lt;true/&gt;
+
</ins><span class="cx">     &lt;!--
</span><span class="cx">         Special principals
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcontribperformanceloadtestsimpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/contrib/performance/loadtest/sim.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/contrib/performance/loadtest/sim.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/contrib/performance/loadtest/sim.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -97,20 +97,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def recordsFromCount(count, uid=u&quot;user%02d&quot;, password=u&quot;user%02d&quot;,
-                     commonName=u&quot;User %02d&quot;, email=u&quot;user%02d@example.com&quot;,
-                     guid=&quot;10000000-0000-0000-0000-000000000%03d&quot;):
-    for i in range(1, count + 1):
-        yield _DirectoryRecord(
-            uid % i,
-            password % i,
-            commonName % i,
-            email % i,
-            guid % i,
-        )
-
-
-
</del><span class="cx"> class LagTrackingReactor(object):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     This reactor wraps another reactor and proxies all attribute
</span><span class="lines">@@ -292,16 +278,10 @@
</span><span class="cx">             workerID = config.get(&quot;workerID&quot;, 0)
</span><span class="cx">             workerCount = config.get(&quot;workerCount&quot;, 1)
</span><span class="cx">             configTemplate = None
</span><del>-            server = 'http://127.0.0.1:8008'
-            principalPathTemplate = &quot;/principals/users/%s/&quot;
</del><ins>+            server = config.get('server', 'http://127.0.0.1:8008')
+            principalPathTemplate = config.get('principalPathTemplate', '/principals/users/%s/')
</ins><span class="cx">             serializationPath = None
</span><span class="cx"> 
</span><del>-            if 'server' in config:
-                server = config['server']
-
-            if 'principalPathTemplate' in config:
-                principalPathTemplate = config['principalPathTemplate']
-
</del><span class="cx">             if 'clientDataSerialization' in config:
</span><span class="cx">                 serializationPath = config['clientDataSerialization']['Path']
</span><span class="cx">                 if not config['clientDataSerialization']['UseOldData']:
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcontribtoolsflowdfromrev15009CalendarServertrunkcontribtoolsflowd"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/contrib/tools/flow.d (from rev 15009, CalendarServer/trunk/contrib/tools/flow.d) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/contrib/tools/flow.d                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/contrib/tools/flow.d        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,74 @@
</span><ins>+#!/usr/sbin/dtrace -Zs
+/*
+ * py_flow.d - snoop Python execution showing function flow.
+ *             Written for the Python DTrace provider.
+ *
+ * $Id: py_flow.d 51 2007-09-24 00:55:23Z brendan $
+ *
+ * This traces Python activity from all Python programs on the system
+ * running with Python provider support.
+ *
+ * USAGE: flow.d &lt;PID&gt;                        # hit Ctrl-C to end
+ *
+ * This watches Python function entries and returns, and indents child
+ * function calls.
+ *
+ * FIELDS:
+ *                C                        CPU-id
+ *                TIME(us)        Time since boot, us
+ *                FILE                Filename that this function belongs to
+ *                FUNC                Function name
+ *
+ * LEGEND:
+ *                -&gt;                function entry
+ *                &lt;-                function return
+ *
+ * WARNING: Watch the first column carefully, it prints the CPU-id. If it
+ * changes, then it is very likely that the output has been shuffled.
+ *
+ * COPYRIGHT: Copyright (c) 2007 Brendan Gregg.
+ *
+ * CDDL HEADER START
+ *
+ *  The contents of this file are subject to the terms of the
+ *  Common Development and Distribution License, Version 1.0 only
+ *  (the &quot;License&quot;).  You may not use this file except in compliance
+ *  with the License.
+ *
+ *  You can obtain a copy of the license at Docs/cddl1.txt
+ *  or http://www.opensolaris.org/os/licensing.
+ *  See the License for the specific language governing permissions
+ *  and limitations under the License.
+ *
+ * CDDL HEADER END
+ *
+ * 09-Sep-2007        Brendan Gregg        Created this.
+ *
+ * This is a variation of py_flow.d that takes a pid.
+ */
+
+#pragma D option quiet
+#pragma D option switchrate=10
+
+self int depth;
+
+dtrace:::BEGIN
+{
+        printf(&quot;%3s %-16s %-16s -- %s\n&quot;, &quot;C&quot;, &quot;TIME(us)&quot;, &quot;FILE&quot;, &quot;FUNC&quot;);
+}
+
+python*:::function-entry
+/pid == $1/
+{
+        printf(&quot;%3d %-16d %-16s %*s-&gt; %s\n&quot;, cpu, timestamp / 1000,
+            basename(copyinstr(arg0)), self-&gt;depth * 2, &quot;&quot;, copyinstr(arg1));
+        self-&gt;depth++;
+}
+
+python*:::function-return
+/pid == $1/
+{
+        self-&gt;depth -= self-&gt;depth &gt; 0 ? 1 : 0;
+        printf(&quot;%3d %-16d %-16s %*s&lt;- %s\n&quot;, cpu, timestamp / 1000,
+            basename(copyinstr(arg0)), self-&gt;depth * 2, &quot;&quot;, copyinstr(arg1));
+}
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodcontribtoolslldb_utilspyfromrev15009CalendarServertrunkcontribtoolslldb_utilspy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/contrib/tools/lldb_utils.py (from rev 15009, CalendarServer/trunk/contrib/tools/lldb_utils.py) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/contrib/tools/lldb_utils.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/contrib/tools/lldb_utils.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,184 @@
</span><ins>+##
+# Copyright (c) 2015 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;
+Below are a bunch of useful functions that can be used inside of lldb to debug a Python
+process (this applies to cPython only).
+
+The steps are as follows:
+
+    &gt; lldb
+    (lldb) target symbols add &lt;path to Python.framework.dSYM&gt;
+    (lldb) command script import contrib/tools/lldb_utils.py
+
+    Run commands using:
+
+    (lldb) pybt
+        ...
+    (lldb) pybtall
+        ...
+    (lldb) pylocals
+        ...
+
+    or inside the python shell:
+
+    (lldb) script
+    Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D.
+    &gt;&gt;&gt; lldb_utils.pybt()
+        ...
+    &gt;&gt;&gt; lldb_utils.pybtall()
+        ...
+    &gt;&gt;&gt; lldb_utils.pylocals()
+        ...
+
+pybt - generate a python function call back trace of the currently selected thread
+pybtall - generate a python function call back trace of all threads
+pylocals - generate a list of the name and values of all locals in the current
+    python frame (only works when the currently selected frame is a Python call
+    frame as found by the pybt command).
+&quot;&quot;&quot;
+
+import lldb #@UnresolvedImport
+
+def _toStr(obj, pystring_t):
+    return obj.Cast(pystring_t).GetValueForExpressionPath(&quot;-&gt;ob_sval&quot;).summary
+
+
+
+def pybt(debugger=None, command=None, result=None, dict=None, thread=None):
+    &quot;&quot;&quot;
+    An lldb command that prints a Python call back trace for the specified
+    thread or the currently selected thread.
+
+    @param debugger: debugger to use
+    @type debugger: L{lldb.SBDebugger}
+    @param command: ignored
+    @type command: ignored
+    @param result: ignored
+    @type result: ignored
+    @param dict: ignored
+    @type dict: ignored
+    @param thread: the specific thread to target
+    @type thread: L{lldb.SBThread}
+    &quot;&quot;&quot;
+
+    if debugger is None:
+        debugger = lldb.debugger
+    target = debugger.GetSelectedTarget()
+    if not isinstance(thread, lldb.SBThread):
+        thread = target.GetProcess().GetSelectedThread()
+
+    pystring_t = target.FindFirstType(&quot;PyStringObject&quot;).GetPointerType()
+
+    num_frames = thread.GetNumFrames()
+    for i in range(num_frames - 1):
+        fr = thread.GetFrameAtIndex(i)
+        if fr.GetFunctionName() == &quot;PyEval_EvalFrameEx&quot;:
+            fr_next = thread.GetFrameAtIndex(i + 1)
+            if fr_next.GetFunctionName() == &quot;PyEval_EvalCodeEx&quot;:
+                f = fr.GetValueForVariablePath(&quot;f&quot;)
+                filename = _toStr(f.GetValueForExpressionPath(&quot;-&gt;f_code-&gt;co_filename&quot;), pystring_t)
+                name = _toStr(f.GetValueForExpressionPath(&quot;-&gt;f_code-&gt;co_name&quot;), pystring_t)
+                lineno = f.GetValueForExpressionPath(&quot;-&gt;f_lineno&quot;).GetValue()
+                print(&quot;#{}: {} - {}:{}&quot;.format(
+                    fr.GetFrameID(),
+                    filename[1:-1] if filename else &quot;.&quot;,
+                    name[1:-1] if name else &quot;.&quot;,
+                    lineno if lineno else &quot;.&quot;,
+                ))
+
+
+
+def pybtall(debugger=None, command=None, result=None, dict=None):
+    &quot;&quot;&quot;
+    An lldb command that prints a Python call back trace for all threads.
+
+    @param debugger: debugger to use
+    @type debugger: L{lldb.SBDebugger}
+    @param command: ignored
+    @type command: ignored
+    @param result: ignored
+    @type result: ignored
+    @param dict: ignored
+    @type dict: ignored
+    &quot;&quot;&quot;
+    if debugger is None:
+        debugger = lldb.debugger
+    process = debugger.GetSelectedTarget().GetProcess()
+    numthreads = process.GetNumThreads()
+    for i in range(numthreads):
+        thread = process.GetThreadAtIndex(i)
+        print(&quot;----- Thread: {} -----&quot;.format(i + 1))
+        pybt(debugger=debugger, thread=thread)
+
+
+
+def pylocals(debugger=None, command=None, result=None, dict=None):
+    &quot;&quot;&quot;
+    An lldb command that prints a list of Python local variables for the
+    currently selected frame.
+
+    @param debugger: debugger to use
+    @type debugger: L{lldb.SBDebugger}
+    @param command: ignored
+    @type command: ignored
+    @param result: ignored
+    @type result: ignored
+    @param dict: ignored
+    @type dict: ignored
+    &quot;&quot;&quot;
+    if debugger is None:
+        debugger = lldb.debugger
+    target = debugger.GetSelectedTarget()
+    frame = target.GetProcess().GetSelectedThread().GetSelectedFrame()
+
+    pystring_t = target.FindFirstType(&quot;PyStringObject&quot;).GetPointerType()
+    pytuple_t = target.FindFirstType(&quot;PyTupleObject&quot;).GetPointerType()
+
+    f = frame.GetValueForVariablePath(&quot;f&quot;)
+    try:
+        numlocals = int(f.GetValueForExpressionPath(&quot;-&gt;f_code-&gt;co_nlocals&quot;).GetValue())
+    except TypeError:
+        print(&quot;Current frame is not a Python function&quot;)
+        return
+    print(&quot;Locals in frame #{}&quot;.format(frame.GetFrameID()))
+    names = f.GetValueForExpressionPath(&quot;-&gt;f_code-&gt;co_varnames&quot;).Cast(pytuple_t)
+    for i in range(numlocals):
+        localname = _toStr(names.GetValueForExpressionPath(&quot;-&gt;ob_item[{}]&quot;.format(i)), pystring_t)
+        local = frame.EvaluateExpression(&quot;PyString_AsString(PyObject_Repr(f-&gt;f_localsplus[{}]))&quot;.format(i)).summary
+        print(&quot;    {} = {}&quot;.format(
+            localname[1:-1] if localname else &quot;.&quot;,
+            local[1:-1] if local else &quot;.&quot;,
+        ))
+
+
+CMDS = (&quot;pybt&quot;, &quot;pybtall&quot;, &quot;pylocals&quot;,)
+
+
+def __lldb_init_module(debugger, dict):
+    &quot;&quot;&quot;
+    Register each command with lldb so they are available directly within lldb as
+    well as within its Python script shell.
+
+    @param debugger: debugger to use
+    @type debugger: L{lldb.SBDebugger}
+    @param dict: ignored
+    @type dict: ignored
+    &quot;&quot;&quot;
+    for cmd in CMDS:
+        debugger.HandleCommand(
+            &quot;command script add -f lldb_utils.{cmd} {cmd}&quot;.format(cmd=cmd)
+        )
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfoddocExtensionscaldavrecursplittxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/doc/Extensions/caldav-recursplit.txt (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/doc/Extensions/caldav-recursplit.txt        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/doc/Extensions/caldav-recursplit.txt        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -4,11 +4,11 @@
</span><span class="cx"> 
</span><span class="cx">                                                                 C. Daboo
</span><span class="cx">                                                                    Apple
</span><del>-                                                        October 30, 2014
</del><ins>+                                                           June 26, 2015
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">              Smart Splitting of Recurring Events in CalDAV
</span><del>-                          caldav-recursplit-01
</del><ins>+                          caldav-recursplit-02
</ins><span class="cx"> 
</span><span class="cx"> Abstract
</span><span class="cx"> 
</span><span class="lines">@@ -22,7 +22,7 @@
</span><span class="cx">    1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   1
</span><span class="cx">    2.  Conventions Used in This Document . . . . . . . . . . . . . .   2
</span><span class="cx">    3.  New behavior  . . . . . . . . . . . . . . . . . . . . . . . .   3
</span><del>-     3.1.  Example . . . . . . . . . . . . . . . . . . . . . . . . .   5
</del><ins>+     3.1.  Example . . . . . . . . . . . . . . . . . . . . . . . . .   6
</ins><span class="cx">    4.  Server Initiated Splitting  . . . . . . . . . . . . . . . . .   8
</span><span class="cx">    5.  Security Considerations . . . . . . . . . . . . . . . . . . .   8
</span><span class="cx">    6.  IANA Considerations . . . . . . . . . . . . . . . . . . . . .   8
</span><span class="lines">@@ -53,9 +53,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-Daboo                      Expires May 3, 2015                  [Page 1]
</del><ins>+Daboo                   Expires December 28, 2015               [Page 1]
</ins><span class="cx"> 
</span><del>-                       CalDAV Recurrence Splitting          October 2014
</del><ins>+                       CalDAV Recurrence Splitting             June 2015
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">    instances from that point onwards.  Typically this is done by
</span><span class="lines">@@ -109,9 +109,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-Daboo                      Expires May 3, 2015                  [Page 2]
</del><ins>+Daboo                   Expires December 28, 2015               [Page 2]
</ins><span class="cx"> 
</span><del>-                       CalDAV Recurrence Splitting          October 2014
</del><ins>+                       CalDAV Recurrence Splitting             June 2015
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 3.  New behavior
</span><span class="lines">@@ -128,16 +128,30 @@
</span><span class="cx"> 
</span><span class="cx">    1.  &quot;action&quot; set to the value &quot;split&quot; - REQUIRED
</span><span class="cx"> 
</span><del>-   2.  &quot;rid&quot; set to an iCalendar format DATE-TIME value in UTC
-       (&quot;YYYYMMDDTHHMMSSZ&quot; style format) - REQUIRED
</del><ins>+   2.  &quot;rid&quot; set to an iCalendar format DATE or DATE-TIME value (with
+       format choice determined as per description below) - REQUIRED
</ins><span class="cx"> 
</span><span class="cx">    3.  &quot;uid&quot; set to an iCalendar UID property value - OPTIONAL
</span><span class="cx"> 
</span><span class="cx">    The &quot;action&quot; parameter is used to distinguish this operation from
</span><span class="cx">    others that might be defined in the future for calendar object
</span><del>-   resources.  The &quot;rid&quot; parameter specified the UTC date-time where the
-   split is to occur.  The actual split occurs at the next recurrence
-   instance on or after the &quot;rid&quot; parameter value - the &quot;split point&quot;.
</del><ins>+   resources.
+
+   The &quot;rid&quot; parameter specifies the date or date-time where the split
+   is to occur.  The actual split occurs at the next recurrence instance
+   on or after the &quot;rid&quot; parameter value - the &quot;split point&quot;.  The &quot;rid&quot;
+   parameter value is an iCalendar DATE or DATE-TIME value that follows
+   that matched the event's DTSTART value as follows:
+
+   +---------------------------+--------------------+------------------+
+   | DTSTART value             | rid value          | rid example      |
+   +---------------------------+--------------------+------------------+
+   | DATE                      | DATE               | 20150626         |
+   | DATE-TIME UTC             | DATE-TIME UTC      | 20150626T120000Z |
+   | DATE-TIME local+time zone | DATE-TIME UTC      | 20150626T120000Z |
+   | DATE-TIME floating        | DATE-TIME floating | 20150626T120000  |
+   +---------------------------+--------------------+------------------+
+
</ins><span class="cx">    The optional &quot;uid&quot; parameter can be used by the client to specify the
</span><span class="cx">    iCalendar UID property value used in the new calendar object resource
</span><span class="cx">    created by the server.  In the absence of the &quot;uid&quot; parameter, the
</span><span class="lines">@@ -145,9 +159,17 @@
</span><span class="cx">    resource.
</span><span class="cx"> 
</span><span class="cx">    Clients MUST include both &quot;action&quot; and &quot;rid&quot; parameters in the POST
</span><del>-   request and MUST ensure a valid date-time value is used.  The date-
-   time value MUST NOT be earlier than the start time of the first
</del><ins>+   request and MUST ensure a valid date-time value is used.  The &quot;rid&quot;
+   parameter value MUST NOT be earlier than the start time of the first
</ins><span class="cx">    instance of the recurrence set, and it MUST NOT be later than the
</span><ins>+
+
+
+Daboo                   Expires December 28, 2015               [Page 3]
+
+                       CalDAV Recurrence Splitting             June 2015
+
+
</ins><span class="cx">    start time of the last instance of the recurrence set.  If the &quot;rid&quot;
</span><span class="cx">    parameter value is not of the correct format or missing, the server
</span><span class="cx">    MUST return a DAV:error response with the CALDAV:valid-rid-parameter
</span><span class="lines">@@ -162,14 +184,6 @@
</span><span class="cx">    too long as per reasonable requirements), then it MUST return a
</span><span class="cx">    DAV:error response with the CS:invalid-split pre-condition code.
</span><span class="cx"> 
</span><del>-
-
-
-Daboo                      Expires May 3, 2015                  [Page 3]
-
-                       CalDAV Recurrence Splitting          October 2014
-
-
</del><span class="cx">    Clients MAY include an HTTP &quot;Prefer&quot; request header including the
</span><span class="cx">    value &quot;return=representation&quot; (see [RFC7240]).  That instructs the
</span><span class="cx">    server to return a WebDAV multistatus response containing two
</span><span class="lines">@@ -203,6 +217,15 @@
</span><span class="cx">            by subtracting the number of instances prior to the split
</span><span class="cx">            point.
</span><span class="cx"> 
</span><ins>+
+
+
+
+Daboo                   Expires December 28, 2015               [Page 4]
+
+                       CalDAV Recurrence Splitting             June 2015
+
+
</ins><span class="cx">        E.  The &quot;DTSTART&quot; property of the master instance is adjusted to
</span><span class="cx">            the value of the first instance of the &quot;RRULE&quot; on or after
</span><span class="cx">            the split point, or, in the absence of an &quot;RRULE&quot;, to the
</span><span class="lines">@@ -219,16 +242,10 @@
</span><span class="cx">        C.  Any &quot;RRULE&quot; property that only generates instances on or
</span><span class="cx">            after the split point is removed.
</span><span class="cx"> 
</span><del>-
-
-Daboo                      Expires May 3, 2015                  [Page 4]
-
-                       CalDAV Recurrence Splitting          October 2014
-
-
</del><span class="cx">        D.  Any remaining &quot;RRULE&quot; property has an &quot;UNTIL&quot; value applied,
</span><del>-           with the until value being one second less than the split
-           point.
</del><ins>+           with the until value being one second (for a &quot;rid&quot; DATE-TIME
+           value) or one day (for a &quot;rid&quot; DATE value) less than the
+           split point.
</ins><span class="cx"> 
</span><span class="cx">        E.  The &quot;UID&quot; property of all components is changed to (the same)
</span><span class="cx">            new value.
</span><span class="lines">@@ -256,6 +273,15 @@
</span><span class="cx">        attendee with both the modified (old) resource data and the new
</span><span class="cx">        resource.  This will likely result in loss of per-attendee data
</span><span class="cx">        such as alarms (though the participation status might be
</span><ins>+
+
+
+
+Daboo                   Expires December 28, 2015               [Page 5]
+
+                       CalDAV Recurrence Splitting             June 2015
+
+
</ins><span class="cx">        preserved if the calendar user agent processing the new iTIP
</span><span class="cx">        message for the new resource allows it).
</span><span class="cx"> 
</span><span class="lines">@@ -272,16 +298,6 @@
</span><span class="cx">    Assume the following iCalendar data is stored in the resource with
</span><span class="cx">    URI &quot;/event.ics&quot;:
</span><span class="cx"> 
</span><del>-
-
-
-
-
-Daboo                      Expires May 3, 2015                  [Page 5]
-
-                       CalDAV Recurrence Splitting          October 2014
-
-
</del><span class="cx">    BEGIN:VCALENDAR
</span><span class="cx">    PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx">    VERSION:2.0
</span><span class="lines">@@ -314,6 +330,14 @@
</span><span class="cx">    Content-Type: text/xml
</span><span class="cx">    Content-Length: xxxx
</span><span class="cx"> 
</span><ins>+
+
+
+Daboo                   Expires December 28, 2015               [Page 6]
+
+                       CalDAV Recurrence Splitting             June 2015
+
+
</ins><span class="cx">    &lt;?xml version='1.0' encoding='UTF-8'?&gt;
</span><span class="cx">    &lt;multistatus xmlns='DAV:'&gt;
</span><span class="cx">      &lt;response&gt;
</span><span class="lines">@@ -330,14 +354,6 @@
</span><span class="cx">    DTSTART:20140110T120000Z
</span><span class="cx">    DURATION:PT1H
</span><span class="cx">    DTSTAMP:20140110T135358Z
</span><del>-
-
-
-Daboo                      Expires May 3, 2015                  [Page 6]
-
-                       CalDAV Recurrence Splitting          October 2014
-
-
</del><span class="cx">    RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:8DE45ECB-8145-
</span><span class="cx">     4AEC-B3E1-11A9DB22A578
</span><span class="cx">    RRULE:FREQ=DAILY;COUNT=11
</span><span class="lines">@@ -370,6 +386,14 @@
</span><span class="cx">    END:VEVENT
</span><span class="cx">    END:VCALENDAR
</span><span class="cx">    &lt;/calendar-data&gt;
</span><ins>+
+
+
+Daboo                   Expires December 28, 2015               [Page 7]
+
+                       CalDAV Recurrence Splitting             June 2015
+
+
</ins><span class="cx">          &lt;/prop&gt;
</span><span class="cx">          &lt;status&gt;HTTP/1.1 200 OK&lt;/status&gt;
</span><span class="cx">        &lt;/propstat&gt;
</span><span class="lines">@@ -386,14 +410,6 @@
</span><span class="cx">    &quot;RELTYPE&quot; parameter set to &quot;X-CALENDARSERVER-RECURRENCE-SET&quot; and a
</span><span class="cx">    value set to a new &quot;UID&quot; value.
</span><span class="cx"> 
</span><del>-
-
-
-Daboo                      Expires May 3, 2015                  [Page 7]
-
-                       CalDAV Recurrence Splitting          October 2014
-
-
</del><span class="cx"> 4.  Server Initiated Splitting
</span><span class="cx"> 
</span><span class="cx">    Servers can automatically split events that have already been stored
</span><span class="lines">@@ -426,6 +442,14 @@
</span><span class="cx">               &quot;Calendaring Extensions to WebDAV (CalDAV)&quot;, RFC 4791,
</span><span class="cx">               March 2007.
</span><span class="cx"> 
</span><ins>+
+
+
+Daboo                   Expires December 28, 2015               [Page 8]
+
+                       CalDAV Recurrence Splitting             June 2015
+
+
</ins><span class="cx">    [RFC5545]  Desruisseaux, B., &quot;Internet Calendaring and Scheduling
</span><span class="cx">               Core Object Specification (iCalendar)&quot;, RFC 5545,
</span><span class="cx">               September 2009.
</span><span class="lines">@@ -439,17 +463,6 @@
</span><span class="cx"> 
</span><span class="cx">    [RFC7240]  Snell, J., &quot;Prefer Header for HTTP&quot;, RFC 7240, June 2014.
</span><span class="cx"> 
</span><del>-
-
-
-
-
-
-Daboo                      Expires May 3, 2015                  [Page 8]
-
-                       CalDAV Recurrence Splitting          October 2014
-
-
</del><span class="cx"> Appendix A.  Acknowledgments
</span><span class="cx"> 
</span><span class="cx">    This specification is the result of discussions between the Apple
</span><span class="lines">@@ -457,6 +470,14 @@
</span><span class="cx"> 
</span><span class="cx"> Appendix B.  Change History
</span><span class="cx"> 
</span><ins>+   Changes since -02
+
+   1.  Clarify that rid query parameter value matches the event DTSTART
+       value type using the UNTIL &quot;rules&quot;
+
+   2.  Clarify that earlier event uses an UNTIL one second or one day
+       less that the &quot;rid&quot; value depending on the value type.
+
</ins><span class="cx">    Changes since -01
</span><span class="cx"> 
</span><span class="cx">    1.  Added &quot;uid&quot; query parameter to POST request
</span><span class="lines">@@ -480,25 +501,4 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Daboo                      Expires May 3, 2015                  [Page 9]
</del><ins>+Daboo                   Expires December 28, 2015               [Page 9]
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfoddocExtensionscaldavrecursplitxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/doc/Extensions/caldav-recursplit.xml (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/doc/Extensions/caldav-recursplit.xml        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/doc/Extensions/caldav-recursplit.xml        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -18,7 +18,7 @@
</span><span class="cx"> &lt;?rfc compact=&quot;yes&quot;?&gt;
</span><span class="cx"> &lt;?rfc subcompact=&quot;no&quot;?&gt;
</span><span class="cx"> &lt;?rfc private=&quot;Calendar Server Extension&quot;?&gt;
</span><del>-&lt;rfc ipr=&quot;none&quot; docName='caldav-recursplit-01'&gt;
</del><ins>+&lt;rfc ipr=&quot;none&quot; docName='caldav-recursplit-02'&gt;
</ins><span class="cx">     &lt;front&gt;
</span><span class="cx">         &lt;title abbrev=&quot;CalDAV Recurrence Splitting&quot;&gt;Smart Splitting of Recurring Events in CalDAV&lt;/title&gt; 
</span><span class="cx">         &lt;author initials=&quot;C.&quot; surname=&quot;Daboo&quot; fullname=&quot;Cyrus Daboo&quot;&gt;
</span><span class="lines">@@ -79,12 +79,37 @@
</span><span class="cx">           &lt;t&gt;To split an existing calendar object resource containing a recurring event, a client issues an HTTP POST resource with the request-uri set to the URI of the calendar object resource. The client also includes the following two URI query parameters, and one optional one:
</span><span class="cx">             &lt;list style='numbers'&gt;
</span><span class="cx">               &lt;t&gt;&quot;action&quot; set to the value &quot;split&quot; - REQUIRED&lt;/t&gt;
</span><del>-              &lt;t&gt;&quot;rid&quot; set to an iCalendar format DATE-TIME value in UTC (&quot;YYYYMMDDTHHMMSSZ&quot; style format) - REQUIRED&lt;/t&gt;
</del><ins>+              &lt;t&gt;&quot;rid&quot; set to an iCalendar format DATE or DATE-TIME value (with format choice determined as per description below) - REQUIRED&lt;/t&gt;
</ins><span class="cx">               &lt;t&gt;&quot;uid&quot; set to an iCalendar UID property value - OPTIONAL&lt;/t&gt;
</span><span class="cx">             &lt;/list&gt;
</span><del>-            The &quot;action&quot; parameter is used to distinguish this operation from others that might be defined in the future for calendar object resources. The &quot;rid&quot; parameter specified the UTC date-time where the split is to occur. The actual split occurs at the next recurrence instance on or after the &quot;rid&quot; parameter value - the &quot;split point&quot;. The optional &quot;uid&quot; parameter can be used by the client to specify the iCalendar UID property value used in the new calendar object resource created by the server. In the absence of the &quot;uid&quot; parameter, the server will itself generate the UID value for the new calendar object resource.&lt;/t&gt;
</del><ins>+            &lt;/t&gt;
+            &lt;t&gt;The &quot;action&quot; parameter is used to distinguish this operation from others that might be defined in the future for calendar object resources.&lt;/t&gt;
+            &lt;t&gt;The &quot;rid&quot; parameter specifies the date or date-time where the split is to occur. The actual split occurs at the next recurrence instance on or after the &quot;rid&quot; parameter value - the &quot;split point&quot;. The &quot;rid&quot; parameter value is an iCalendar DATE or DATE-TIME value that follows that matched the event's DTSTART value as follows:&lt;/t&gt;
+            &lt;texttable&gt;
+              &lt;ttcol&gt;DTSTART value&lt;/ttcol&gt;
+              &lt;ttcol&gt;rid value&lt;/ttcol&gt;
+              &lt;ttcol&gt;rid example&lt;/ttcol&gt;
+
+              &lt;c&gt;DATE&lt;/c&gt;
+              &lt;c&gt;DATE&lt;/c&gt;
+              &lt;c&gt;20150626&lt;/c&gt;
+
+              &lt;c&gt;DATE-TIME UTC&lt;/c&gt;
+              &lt;c&gt;DATE-TIME UTC&lt;/c&gt;
+              &lt;c&gt;20150626T120000Z&lt;/c&gt;
+
+              &lt;c&gt;DATE-TIME local+time zone&lt;/c&gt;
+              &lt;c&gt;DATE-TIME UTC&lt;/c&gt;
+              &lt;c&gt;20150626T120000Z&lt;/c&gt;
+
+              &lt;c&gt;DATE-TIME floating&lt;/c&gt;
+              &lt;c&gt;DATE-TIME floating&lt;/c&gt;
+              &lt;c&gt;20150626T120000&lt;/c&gt;
+
+            &lt;/texttable&gt;
+            &lt;t&gt;The optional &quot;uid&quot; parameter can be used by the client to specify the iCalendar UID property value used in the new calendar object resource created by the server. In the absence of the &quot;uid&quot; parameter, the server will itself generate the UID value for the new calendar object resource.&lt;/t&gt;
</ins><span class="cx">             &lt;t&gt;
</span><del>-            Clients MUST include both &quot;action&quot; and &quot;rid&quot; parameters in the POST request and MUST ensure a valid date-time value is used. The date-time value MUST NOT be earlier than the start time of the first instance of the recurrence set, and it MUST NOT be later than the start time of the last instance of the recurrence set. If the &quot;rid&quot; parameter value is not of the correct format or missing, the server MUST return a DAV:error response with the CALDAV:valid-rid-parameter pre-condition code. If the &quot;rid&quot; parameter is valid, but outside of the allowed range, or the targeted calendar object resource is not recurring, then the server MUST return a DAV:error response with the CS:invalid-split pre-condition code. The server MUST reject any attempt by an attendee to split their copy of a scheduled calendar object resource - only organizers are allowed to split events. If the optional &quot;uid&quot; parameter is used by the c
 lient, and the server determines that the specified value is invalid (e.g., too short or too long as per reasonable requirements), then it MUST return a DAV:error response with the CS:invalid-split pre-condition code.
</del><ins>+            Clients MUST include both &quot;action&quot; and &quot;rid&quot; parameters in the POST request and MUST ensure a valid date-time value is used. The &quot;rid&quot; parameter value MUST NOT be earlier than the start time of the first instance of the recurrence set, and it MUST NOT be later than the start time of the last instance of the recurrence set. If the &quot;rid&quot; parameter value is not of the correct format or missing, the server MUST return a DAV:error response with the CALDAV:valid-rid-parameter pre-condition code. If the &quot;rid&quot; parameter is valid, but outside of the allowed range, or the targeted calendar object resource is not recurring, then the server MUST return a DAV:error response with the CS:invalid-split pre-condition code. The server MUST reject any attempt by an attendee to split their copy of a scheduled calendar object resource - only organizers are allowed to split events. If the optional &quot;uid&quot; parameter i
 s used by the client, and the server determines that the specified value is invalid (e.g., too short or too long as per reasonable requirements), then it MUST return a DAV:error response with the CS:invalid-split pre-condition code.
</ins><span class="cx">           &lt;/t&gt;
</span><span class="cx">           &lt;t&gt;
</span><span class="cx">             Clients MAY include an HTTP &quot;Prefer&quot; request header including the value &quot;return=representation&quot; (see &lt;xref target='RFC7240'/&gt;). That instructs the server to return a WebDAV multistatus response containing two responses: one for the targeted resource and one for the new resource created as a result of the split. The multistatus response MUST include the DAV:getetag and CALDAV:calendar-data properties for each resource. In the absence of the &quot;Prefer:return=representation&quot; request header, the server MUST return an HTTP &quot;Split-Component-URL&quot; response header whose value is the URI of the new resource created as a result of the split.
</span><span class="lines">@@ -107,7 +132,7 @@
</span><span class="cx">                   &lt;t&gt;Any overridden components with a &quot;RECURRENCE-ID&quot; property value on or after the split point are removed.&lt;/t&gt;
</span><span class="cx">                   &lt;t&gt;Any &quot;RDATE&quot; or &quot;EXDATE&quot; property values on or after the split point are removed.&lt;/t&gt;
</span><span class="cx">                   &lt;t&gt;Any &quot;RRULE&quot; property that only generates instances on or after the split point is removed.&lt;/t&gt;
</span><del>-                  &lt;t&gt;Any remaining &quot;RRULE&quot; property has an &quot;UNTIL&quot; value applied, with the until value being one second less than the split point.&lt;/t&gt;
</del><ins>+                  &lt;t&gt;Any remaining &quot;RRULE&quot; property has an &quot;UNTIL&quot; value applied, with the until value being one second (for a &quot;rid&quot; DATE-TIME value) or one day (for a &quot;rid&quot; DATE value) less than the split point.&lt;/t&gt;
</ins><span class="cx">                   &lt;t&gt;The &quot;UID&quot; property of all components is changed to (the same) new value.&lt;/t&gt;
</span><span class="cx">                   &lt;t&gt;Attendee participation status MUST NOT be changed.&lt;/t&gt;
</span><span class="cx">                 &lt;/list&gt;
</span><span class="lines">@@ -259,6 +284,12 @@
</span><span class="cx">             &lt;/t&gt;
</span><span class="cx">         &lt;/section&gt;
</span><span class="cx">         &lt;section title='Change History'&gt;
</span><ins>+          &lt;t&gt;Changes since -02
+            &lt;list style='numbers'&gt;
+              &lt;t&gt;Clarify that rid query parameter value matches the event DTSTART value type using the UNTIL &quot;rules&quot;&lt;/t&gt;
+              &lt;t&gt;Clarify that earlier event uses an UNTIL one second or one day less that the &quot;rid&quot; value depending on the value type.&lt;/t&gt;
+            &lt;/list&gt;
+          &lt;/t&gt;
</ins><span class="cx">           &lt;t&gt;Changes since -01
</span><span class="cx">             &lt;list style='numbers'&gt;
</span><span class="cx">               &lt;t&gt;Added &quot;uid&quot; query parameter to POST request&lt;/t&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodrequirementsdevtxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/requirements-dev.txt (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/requirements-dev.txt        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/requirements-dev.txt        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -8,4 +8,4 @@
</span><span class="cx"> q
</span><span class="cx"> tl.eggdeps
</span><span class="cx"> --editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@14856#egg=CalDAVClientLibrary
</span><del>---editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@14892#egg=CalDAVTester
</del><ins>+--editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@14955#egg=CalDAVTester
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodrequirementsstabletxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/requirements-stable.txt (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/requirements-stable.txt        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/requirements-stable.txt        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -36,7 +36,7 @@
</span><span class="cx">             #pyOpenSSL
</span><span class="cx">         pycrypto==2.6.1
</span><span class="cx"> 
</span><del>-    --editable svn+http://svn.calendarserver.org/repository/calendarserver/twext/branches/users/cdaboo/cfod@14909#egg=twextpy
</del><ins>+    --editable svn+http://svn.calendarserver.org/repository/calendarserver/twext/branches/users/cdaboo/cfod@15010#egg=twextpy
</ins><span class="cx">         cffi==1.1.0
</span><span class="cx">             pycparser==2.13
</span><span class="cx">         #twisted
</span><span class="lines">@@ -56,16 +56,18 @@
</span><span class="cx"> 
</span><span class="cx">     pyOpenSSL==0.14
</span><span class="cx">         cryptography==0.9
</span><ins>+                idna
</ins><span class="cx">             #pyasn1
</span><span class="cx">             #cffi
</span><span class="cx">             enum34==1.0.4
</span><ins>+            ipaddress
</ins><span class="cx">             setuptools==17.0
</span><span class="cx">             #six
</span><span class="cx">         six==1.9.0
</span><span class="cx"> 
</span><span class="cx">     --editable svn+http://svn.calendarserver.org/repository/calendarserver/PyKerberos/trunk@13420#egg=kerberos
</span><span class="cx"> 
</span><del>-    --editable svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@14695#egg=pycalendar
</del><ins>+    --editable svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@14915#egg=pycalendar
</ins><span class="cx">     python-dateutil==1.5  # Note: v2.0+ is for Python 3
</span><span class="cx">     pytz==2015.4
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodsetuppy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/setup.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/setup.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/setup.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -322,11 +322,11 @@
</span><span class="cx"> install_requirements = [
</span><span class="cx">     # Core frameworks
</span><span class="cx">     &quot;zope.interface&quot;,
</span><del>-    &quot;Twisted&gt;=13.2.0&quot;,
</del><ins>+    &quot;Twisted&gt;=15.2.1&quot;,
</ins><span class="cx">     &quot;twextpy&quot;,
</span><span class="cx"> 
</span><span class="cx">     # Security frameworks
</span><del>-    &quot;pyOpenSSL&gt;=0.13.1&quot;,  # also for Twisted
</del><ins>+    &quot;pyOpenSSL&gt;=0.14&quot;,    # also for Twisted
</ins><span class="cx">     &quot;service_identity&quot;,   # for Twisted
</span><span class="cx">     &quot;pycrypto&quot;,           # for Twisted
</span><span class="cx">     &quot;kerberos&quot;,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodsupportApplemake"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/support/Apple.make (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/support/Apple.make        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/support/Apple.make        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -58,6 +58,12 @@
</span><span class="cx"> 
</span><span class="cx"> build:: $(BuildDirectory)/$(Project)
</span><span class="cx"> 
</span><ins>+build:: build-wrapper
+build-wrapper: $(BuildDirectory)/python-wrapper
+
+$(BuildDirectory)/python-wrapper: $(Sources)/support/python-wrapper.c
+        $(CC) $(Sources)/support/python-wrapper.c -o $(BuildDirectory)/python-wrapper
+
</ins><span class="cx"> install:: install-python
</span><span class="cx"> install-python:: build
</span><span class="cx">         @#
</span><span class="lines">@@ -85,18 +91,15 @@
</span><span class="cx">         @#
</span><span class="cx">         @# Set up a virtual environment in Server.app; we'll install into that.
</span><span class="cx">         @# That creates a self-contained environment which has specific version of
</span><del>-        @# (almost) all of our dependancies in it.
</del><ins>+        @# (almost) all of our dependencies in it.
</ins><span class="cx">         @# Use --system-site-packages so that we use the packages provided by the
</span><span class="cx">         @# OS, such as PyObjC.
</span><del>-        @# Use --always-copy because we want copies of, not links to, the system
-        @# python, as Server.app is an independent product train.
</del><span class="cx">         @#
</span><span class="cx">         @echo &quot;Creating virtual environment...&quot;;
</span><span class="cx">         $(_v) $(RMDIR) &quot;$(DSTROOT)$(CS_VIRTUALENV)&quot;;
</span><span class="cx">         $(_v) PYTHONPATH=&quot;$(BuildDirectory)/pytools/lib&quot; \
</span><span class="cx">                   &quot;$(PYTHON)&quot; -m virtualenv              \
</span><span class="cx">                           --system-site-packages             \
</span><del>-                          --always-copy                      \
</del><span class="cx">                           &quot;$(DSTROOT)$(CS_VIRTUALENV)&quot;;
</span><span class="cx">         @#
</span><span class="cx">         @# Use the pip in the virtual environment (as opposed to pip in the OS) to
</span><span class="lines">@@ -131,7 +134,7 @@
</span><span class="cx">                       --ignore-installed                                      \
</span><span class="cx">                       Twisted;
</span><span class="cx"> 
</span><del>-        @echo &quot;Installing CalendarServer and remaining dependancies...&quot;;
</del><ins>+        @echo &quot;Installing CalendarServer and remaining dependencies...&quot;;
</ins><span class="cx">         $(_v) $(Environment)                                                  \
</span><span class="cx">                   &quot;$(DSTROOT)$(CS_VIRTUALENV)/bin/pip&quot; install                \
</span><span class="cx">                       --disable-pip-version-check                             \
</span><span class="lines">@@ -153,14 +156,18 @@
</span><span class="cx">         $(_v) perl -i -pe &quot;s|#PATH|export PYTHON=$(CS_VIRTUALENV)/bin/python;|&quot; &quot;$(DSTROOT)$(CS_VIRTUALENV)/bin/caldavd&quot;;
</span><span class="cx">         @echo &quot;Stripping binaries...&quot;;
</span><span class="cx">         $(_v) find &quot;$(DSTROOT)$(CS_VIRTUALENV)&quot; -type f -name &quot;*.so&quot; -print0 | xargs -0 $(STRIP) -Sx;
</span><del>-        @echo &quot;Updating install location of Python library...&quot;;
-        $(_v) find &quot;$(DSTROOT)$(CS_VIRTUALENV)/bin&quot; -type f -name &quot;python*&quot; -print0 | \
-                  xargs -0 -n 1 install_name_tool -change &quot;@executable_path/../.Python&quot; &quot;$(CS_VIRTUALENV)/.Python&quot;;
-        $(_v) install_name_tool -id &quot;$(CS_VIRTUALENV)/.Python&quot; &quot;$(DSTROOT)$(CS_VIRTUALENV)/.Python&quot;;
</del><span class="cx">         @echo &quot;Putting comments into empty files...&quot;;
</span><span class="cx">         $(_v) find &quot;$(DSTROOT)$(CS_VIRTUALENV)&quot; -type f -size 0 -name &quot;*.py&quot; -exec sh -c 'printf &quot;# empty\n&quot; &gt; {}' &quot;;&quot;;
</span><span class="cx">         $(_v) find &quot;$(DSTROOT)$(CS_VIRTUALENV)&quot; -type f -size 0 -name &quot;*.h&quot; -exec sh -c 'printf &quot;/* empty */\n&quot; &gt; {}' &quot;;&quot;;
</span><span class="cx"> 
</span><ins>+        @#
+        @# Undo virtualenv so we use the python-wrapper
+        @#
+        @echo &quot;Undo virtualenv...&quot;;
+        $(_v) cp &quot;$(BuildDirectory)/python-wrapper&quot; &quot;$(DSTROOT)$(CS_VIRTUALENV)/bin&quot;
+        $(_v) $(STRIP) -Sx &quot;$(DSTROOT)$(CS_VIRTUALENV)/bin/python-wrapper&quot;
+        $(_v) &quot;$(Sources)/support/undo-virtualenv&quot; &quot;$(DSTROOT)$(CS_VIRTUALENV)&quot;
+
</ins><span class="cx"> install:: install-config
</span><span class="cx"> install-config::
</span><span class="cx">         $(_v) $(INSTALL_DIRECTORY) &quot;$(DSTROOT)$(SIPP)$(ETCDIR)$(CALDAVDSUBDIR)&quot;;
</span><span class="lines">@@ -216,12 +223,14 @@
</span><span class="cx">         @echo &quot;Installing CalDAVTester package...&quot;;
</span><span class="cx">         $(_v) $(INSTALL_DIRECTORY) &quot;$(DSTROOT)/AppleInternal/ServerTools&quot;;
</span><span class="cx">         $(_v) tar -C &quot;$(DSTROOT)/AppleInternal/ServerTools&quot; -xvzf &quot;$(Sources)/CalDAVTester.tgz&quot;;
</span><ins>+        $(_v) chown -R root:wheel &quot;$(DSTROOT)/AppleInternal/ServerTools/CalDAVTester&quot;;
</ins><span class="cx"> 
</span><span class="cx"> install:: install-caldavsim
</span><span class="cx"> install-caldavsim::
</span><span class="cx">         @echo &quot;Installing caldavsim...&quot;;
</span><span class="cx">         $(_v) $(INSTALL_DIRECTORY) &quot;$(DSTROOT)/AppleInternal/ServerTools&quot;;
</span><span class="cx">         $(_v) cp -a &quot;$(Sources)/contrib/performance/&quot; &quot;$(DSTROOT)/AppleInternal/ServerTools/CalDAVSim/&quot;;
</span><ins>+        $(_v) chown -R root:wheel &quot;$(DSTROOT)/AppleInternal/ServerTools/CalDAVSim&quot;;
</ins><span class="cx"> 
</span><span class="cx"> #
</span><span class="cx"> # Automatic Extract
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodsupportpythonwrappercfromrev15009CalendarServertrunksupportpythonwrapperc"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/support/python-wrapper.c (from rev 15009, CalendarServer/trunk/support/python-wrapper.c) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/support/python-wrapper.c                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/support/python-wrapper.c        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,47 @@
</span><ins>+//
+//  python-wrapper.c
+//
+//  Copyright Â© 2015 Apple Inc. All rights reserved.
+//
+
+#include &lt;stdio.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;string.h&gt;
+#include &lt;unistd.h&gt;
+
+const char* python = &quot;/usr/bin/python2.7&quot;;
+const char* bin = &quot;/Applications/Server.app/Contents/ServerRoot/Library/CalendarServer/bin&quot;;
+const char* site = &quot;/Applications/Server.app/Contents/ServerRoot/Library/CalendarServer/lib/python2.7/site-packages&quot;;
+
+// Prepend a path to the named environment variable
+int prependToPath(const char* name, const char* prepend) {
+    const char* old_value = getenv(name);
+    char* new_value = NULL;
+    if (old_value == NULL) {
+        // No existing value - set to the prepend value
+        size_t max_length = strlen(prepend) + 1;
+        new_value = malloc(max_length);
+        strlcpy(new_value, prepend, max_length);
+    } else {
+        // Existing value - so prepend with a &quot;:&quot; in between
+        size_t max_length = strlen(old_value) + strlen(prepend) + 2;
+        new_value = malloc(max_length);
+        strlcpy(new_value, prepend, max_length);
+        strlcat(new_value, &quot;:&quot;, max_length);
+        strlcat(new_value, old_value, max_length);
+    }
+    setenv(name, new_value, 1);
+    free(new_value);
+    return 0;
+}
+
+int main(int argc, const char * argv[]) {
+    
+    // Update PATH and PYTHONPATH
+    prependToPath(&quot;PATH&quot;, bin);
+    prependToPath(&quot;PYTHONPATH&quot;, site);
+    
+    // Launch real python
+    argv[0] = python;
+    return execvp(python, (char* const*)argv);
+}
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodsupportundovirtualenvfromrev15009CalendarServertrunksupportundovirtualenv"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/support/undo-virtualenv (from rev 15009, CalendarServer/trunk/support/undo-virtualenv) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/support/undo-virtualenv                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/support/undo-virtualenv        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,48 @@
</span><ins>+#!/bin/sh
+
+##
+# Copyright (c) 2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+set -e
+set -u
+
+base=&quot;${1}&quot;;
+if [ $# == 1 ]; then
+  appbase=&quot;/Applications/Server.app/Contents/ServerRoot/Library/CalendarServer&quot;;
+else
+  appbase=&quot;${2}&quot;;
+fi;
+
+# Remove python binaries
+rm -f &quot;${base}/bin/python&quot;;
+rm -f &quot;${base}/bin/python2&quot;;
+rm -f &quot;${base}/bin/python2.7&quot;;
+
+# Remove unused virtualenv files
+rm -f &quot;${base}/.Python&quot;;
+rm -f &quot;${base}/include/python2.7&quot;;
+rm -f &quot;${base}/lib/python2.7/&quot;*.py*;
+rm -f &quot;${base}/lib/python2.7/config&quot;;
+rm -rf &quot;${base}/lib/python2.7/distutils&quot;;
+rm -f &quot;${base}/lib/python2.7/encodings&quot;;
+rm -f &quot;${base}/lib/python2.7/lib-dynload&quot;;
+rm -f &quot;${base}/lib/python2.7/orig-prefix.txt&quot;;
+
+# Create links
+cd &quot;${base}/bin&quot;
+ln -s &quot;python-wrapper&quot; &quot;python&quot;;
+ln -s &quot;python-wrapper&quot; &quot;python2&quot;;
+ln -s &quot;python-wrapper&quot; &quot;python2.7&quot;;
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavcontrolapipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/controlapi.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/controlapi.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/controlapi.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -28,7 +28,7 @@
</span><span class="cx"> 
</span><span class="cx"> from calendarserver.tools.util import recordForPrincipalID
</span><span class="cx"> 
</span><del>-from twext.enterprise.jobqueue import JobItem
</del><ins>+from twext.enterprise.jobqueue import JobItem, WORK_PRIORITY_HIGH, WORK_WEIGHT_1
</ins><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet import reactor
</span><span class="lines">@@ -42,6 +42,7 @@
</span><span class="cx"> from txdav.caldav.datastore.scheduling.work import ScheduleOrganizerWork, \
</span><span class="cx">     ScheduleOrganizerSendWork, ScheduleReplyWork, ScheduleRefreshWork, \
</span><span class="cx">     ScheduleAutoReplyWork
</span><ins>+from txdav.common.datastore.work.load_work import TestWork
</ins><span class="cx"> from txdav.who.groups import GroupCacherPollingWork, GroupRefreshWork, \
</span><span class="cx">     GroupAttendeeReconciliationWork, GroupDelegateChangesWork, \
</span><span class="cx">     GroupShareeReconciliationWork
</span><span class="lines">@@ -355,6 +356,45 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def action_testwork(self, j):
+        &quot;&quot;&quot;
+        Wait for all schedule queue items to complete.
+        &quot;&quot;&quot;
+
+        try:
+            when = j[&quot;when&quot;]
+        except KeyError:
+            when = 0
+        try:
+            priority = j[&quot;priority&quot;]
+        except KeyError:
+            priority = WORK_PRIORITY_HIGH
+        try:
+            weight = j[&quot;weight&quot;]
+        except KeyError:
+            weight = WORK_WEIGHT_1
+        try:
+            delay = j[&quot;delay&quot;]
+        except KeyError:
+            delay = 0
+        try:
+            jobs = j[&quot;jobs&quot;]
+        except KeyError:
+            jobs = 1
+
+        for _ in range(jobs):
+            yield TestWork.schedule(
+                self._store,
+                when,
+                priority,
+                weight,
+                delay,
+            )
+
+        returnValue(self._ok(&quot;ok&quot;, &quot;Test work scheduled&quot;))
+
+
+    @inlineCallbacks
</ins><span class="cx">     def action_revisioncleanup(self, j):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Wait for all schedule queue items to complete.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavcustomxmlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/customxml.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/customxml.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/customxml.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -68,10 +68,18 @@
</span><span class="cx">     &quot;calendarserver-sharing-no-scheduling&quot;,
</span><span class="cx"> )
</span><span class="cx"> 
</span><ins>+calendarserver_group_sharee_compliance = (
+    &quot;calendarserver-group-sharee&quot;,
+)
+
</ins><span class="cx"> calendarserver_partstat_changes_compliance = (
</span><span class="cx">     &quot;calendarserver-partstat-changes&quot;,
</span><span class="cx"> )
</span><span class="cx"> 
</span><ins>+calendarserver_group_attendee_compliance = (
+    &quot;calendarserver-group-attendee&quot;,
+)
+
</ins><span class="cx"> calendarserver_home_sync_compliance = (
</span><span class="cx">     &quot;calendarserver-home-sync&quot;,
</span><span class="cx"> )
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavextensionspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/extensions.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/extensions.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/extensions.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -1041,7 +1041,9 @@
</span><span class="cx">             applyTo = True
</span><span class="cx"> 
</span><span class="cx">         elif child.qname() == (calendarserver_namespace, &quot;search-token&quot;):
</span><del>-            tokens.append(child.toString())
</del><ins>+            tokenValue = child.toString().strip()
+            if tokenValue:
+                tokens.append(tokenValue)
</ins><span class="cx"> 
</span><span class="cx">         elif child.qname() == (calendarserver_namespace, &quot;limit&quot;):
</span><span class="cx">             try:
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavicalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/ical.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/ical.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/ical.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -1255,7 +1255,10 @@
</span><span class="cx">                     rrule.setUseUntil(True)
</span><span class="cx">                     rrule.setUseCount(False)
</span><span class="cx">                     until = rid.duplicate()
</span><del>-                    until.offsetSeconds(-1)
</del><ins>+                    if until.isDateOnly():
+                        until.offsetDay(-1)
+                    else:
+                        until.offsetSeconds(-1)
</ins><span class="cx">                     rrule.setUntil(until)
</span><span class="cx"> 
</span><span class="cx">             # Remove any RDATEs or EXDATEs in the future
</span><span class="lines">@@ -3541,13 +3544,6 @@
</span><span class="cx">                 if name:
</span><span class="cx">                     if name != oldCN:
</span><span class="cx">                         prop.setParameter(&quot;CN&quot;, name)
</span><del>-
-                        # Also adjust any previously matching location property
-                        if cutype == &quot;ROOM&quot;:
-                            location = component.getProperty(&quot;LOCATION&quot;)
-                            if location is not None:
-                                if location.value() == oldCN:
-                                    location.setValue(name)
</del><span class="cx">                 else:
</span><span class="cx">                     prop.removeParameter(&quot;CN&quot;)
</span><span class="cx"> 
</span><span class="lines">@@ -3586,7 +3582,19 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _reconcileGroupAttendee(self, groupCUA, memberAttendeeProps):
</span><ins>+        &quot;&quot;&quot;
+        Make sure there are attendee properties for every member of the group, and no
+        other attendee properties marked as a member of the group. Note that attendee
+        properties already present with a MEMBER parameter are not given a MEMBER
+        parameter if they are in the group. This ensures that manually added attendees
+        are not automatically removed when they dissappear from a group.
</ins><span class="cx"> 
</span><ins>+        @param groupCUA: calendar user address of the group
+        @type groupCUA: L{str}
+        @param memberAttendeeProps: list of member properties
+        @type memberAttendeeProps: L{tuple}
+        &quot;&quot;&quot;
+
</ins><span class="cx">         changed = False
</span><span class="cx">         for component in self.subcomponents(ignore=True):
</span><span class="cx">             oldAttendeeProps = tuple(component.properties(&quot;ATTENDEE&quot;))
</span><span class="lines">@@ -3622,7 +3630,15 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def reconcileGroupAttendees(self, groupCUAToAttendeeMemberPropMap):
</span><ins>+        &quot;&quot;&quot;
+        Reconcile the attendee properties in this L{Component}.
</ins><span class="cx"> 
</span><ins>+        @param groupCUAToAttendeeMemberPropMap: map of group to potential attendees
+        @type groupCUAToAttendeeMemberPropMap: L{dict}
+        &quot;&quot;&quot;
+
+        # Reconcile the member ship list of each group attendee, keeping track of which
+        # groups are actually used
</ins><span class="cx">         changed = False
</span><span class="cx">         allMemberCUAs = set()
</span><span class="cx">         nonemptyGroupCUAs = set()
</span><span class="lines">@@ -3632,7 +3648,8 @@
</span><span class="cx">             if memberAttendeeProps:
</span><span class="cx">                 nonemptyGroupCUAs.add(groupCUA)
</span><span class="cx"> 
</span><del>-        # remove orphans
</del><ins>+        # Remove attendee properties that have a MEMBER value that contains only groups no longer
+        # used in this component
</ins><span class="cx">         for component in self.subcomponents(ignore=True):
</span><span class="cx">             for attendeeProp in tuple(component.properties(&quot;ATTENDEE&quot;)):
</span><span class="cx">                 if attendeeProp.hasParameter(&quot;MEMBER&quot;):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/stdconfig.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/stdconfig.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/stdconfig.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -316,6 +316,10 @@
</span><span class="cx"> 
</span><span class="cx">     &quot;DirectoryRealmName&quot;: &quot;&quot;,
</span><span class="cx"> 
</span><ins>+    # Apply an additional filter for attendee lookups where names must start
+    # with the search tokens rather than just contain them.
+    &quot;DirectoryFilterStartsWith&quot;: False,
+
</ins><span class="cx">     #
</span><span class="cx">     # Locations and Resources service
</span><span class="cx">     #
</span><span class="lines">@@ -817,16 +821,16 @@
</span><span class="cx">                 &quot;EnableStaggering&quot; : False,
</span><span class="cx">                 &quot;StaggerSeconds&quot; : 3,
</span><span class="cx">                 &quot;CalDAV&quot; : {
</span><del>-                    &quot;CertificatePath&quot; : &quot;&quot;,
-                    &quot;PrivateKeyPath&quot; : &quot;&quot;,
-                    &quot;AuthorityChainPath&quot; : &quot;&quot;,
</del><ins>+                    &quot;CertificatePath&quot; : &quot;Certificates/apns:com.apple.calendar.cert.pem&quot;,
+                    &quot;PrivateKeyPath&quot; : &quot;Certificates/apns:com.apple.calendar.key.pem&quot;,
+                    &quot;AuthorityChainPath&quot; : &quot;Certificates/apns:com.apple.calendar.chain.pem&quot;,
</ins><span class="cx">                     &quot;Passphrase&quot; : &quot;&quot;,
</span><span class="cx">                     &quot;Topic&quot; : &quot;&quot;,
</span><span class="cx">                 },
</span><span class="cx">                 &quot;CardDAV&quot; : {
</span><del>-                    &quot;CertificatePath&quot; : &quot;&quot;,
-                    &quot;PrivateKeyPath&quot; : &quot;&quot;,
-                    &quot;AuthorityChainPath&quot; : &quot;&quot;,
</del><ins>+                    &quot;CertificatePath&quot; : &quot;Certificates/apns:com.apple.contact.cert.pem&quot;,
+                    &quot;PrivateKeyPath&quot; : &quot;Certificates/apns:com.apple.contact.key.pem&quot;,
+                    &quot;AuthorityChainPath&quot; : &quot;Certificates/apns:com.apple.contact.chain.pem&quot;,
</ins><span class="cx">                     &quot;Passphrase&quot; : &quot;&quot;,
</span><span class="cx">                     &quot;Topic&quot; : &quot;&quot;,
</span><span class="cx">                 },
</span><span class="lines">@@ -1218,6 +1222,12 @@
</span><span class="cx">     (&quot;ConfigRoot&quot;, (&quot;Scheduling&quot;, &quot;iSchedule&quot;, &quot;DKIM&quot;, &quot;PublicKeyFile&quot;,)),
</span><span class="cx">     (&quot;ConfigRoot&quot;, (&quot;Scheduling&quot;, &quot;iSchedule&quot;, &quot;DKIM&quot;, &quot;PrivateExchanges&quot;,)),
</span><span class="cx">     (&quot;ConfigRoot&quot;, &quot;WritableConfigFile&quot;),
</span><ins>+    (&quot;ConfigRoot&quot;, (&quot;Notifications&quot;, &quot;Services&quot;, &quot;APNS&quot;, &quot;CalDAV&quot;, &quot;AuthorityChainPath&quot;,)),
+    (&quot;ConfigRoot&quot;, (&quot;Notifications&quot;, &quot;Services&quot;, &quot;APNS&quot;, &quot;CalDAV&quot;, &quot;CertificatePath&quot;,)),
+    (&quot;ConfigRoot&quot;, (&quot;Notifications&quot;, &quot;Services&quot;, &quot;APNS&quot;, &quot;CalDAV&quot;, &quot;PrivateKeyPath&quot;,)),
+    (&quot;ConfigRoot&quot;, (&quot;Notifications&quot;, &quot;Services&quot;, &quot;APNS&quot;, &quot;CardDAV&quot;, &quot;AuthorityChainPath&quot;,)),
+    (&quot;ConfigRoot&quot;, (&quot;Notifications&quot;, &quot;Services&quot;, &quot;APNS&quot;, &quot;CardDAV&quot;, &quot;CertificatePath&quot;,)),
+    (&quot;ConfigRoot&quot;, (&quot;Notifications&quot;, &quot;Services&quot;, &quot;APNS&quot;, &quot;CardDAV&quot;, &quot;PrivateKeyPath&quot;,)),
</ins><span class="cx">     (&quot;LogRoot&quot;, &quot;AccessLogFile&quot;),
</span><span class="cx">     (&quot;LogRoot&quot;, &quot;ErrorLogFile&quot;),
</span><span class="cx">     (&quot;LogRoot&quot;, &quot;AgentLogFile&quot;),
</span><span class="lines">@@ -1717,6 +1727,8 @@
</span><span class="cx">             compliance += customxml.calendarserver_sharing_compliance
</span><span class="cx">             # TODO: This is only needed whilst we do not support scheduling in shared calendars
</span><span class="cx">             compliance += customxml.calendarserver_sharing_no_scheduling_compliance
</span><ins>+            if config.Sharing.Calendars.Enabled and config.Sharing.Calendars.Groups.Enabled:
+                compliance += customxml.calendarserver_group_sharee_compliance
</ins><span class="cx">         if configDict.EnableCalendarQueryExtended:
</span><span class="cx">             compliance += caldavxml.caldav_query_extended_compliance
</span><span class="cx">         if configDict.EnableDefaultAlarms:
</span><span class="lines">@@ -1725,6 +1737,8 @@
</span><span class="cx">             compliance += caldavxml.caldav_managed_attachments_compliance
</span><span class="cx">         if configDict.Scheduling.Options.TimestampAttendeePartStatChanges:
</span><span class="cx">             compliance += customxml.calendarserver_partstat_changes_compliance
</span><ins>+        if config.GroupAttendees.Enabled:
+            compliance += customxml.calendarserver_group_attendee_compliance
</ins><span class="cx">         if configDict.EnableTimezonesByReference:
</span><span class="cx">             compliance += caldavxml.caldav_timezones_by_reference_compliance
</span><span class="cx">         compliance += customxml.calendarserver_recurrence_split
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavstorebridgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/storebridge.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/storebridge.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/storebridge.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -2011,34 +2011,35 @@
</span><span class="cx">                 userprivs.extend(privileges)
</span><span class="cx"> 
</span><span class="cx">             principal = yield self.principalForUID(invite.shareeUID)
</span><del>-            aces += (
-                # Inheritable specific access for the resource's associated principal.
-                davxml.ACE(
-                    davxml.Principal(davxml.HRef(principal.principalURL())),
-                    davxml.Grant(*userprivs),
-                    davxml.Protected(),
-                    TwistedACLInheritable(),
-                ),
-            )
-
-            if config.EnableProxyPrincipals:
</del><ins>+            if principal is not None:
</ins><span class="cx">                 aces += (
</span><del>-                    # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
</del><ins>+                    # Inheritable specific access for the resource's associated principal.
</ins><span class="cx">                     davxml.ACE(
</span><del>-                        davxml.Principal(davxml.HRef(joinURL(principal.principalURL(), &quot;calendar-proxy-read/&quot;))),
</del><ins>+                        davxml.Principal(davxml.HRef(principal.principalURL())),
</ins><span class="cx">                         davxml.Grant(*userprivs),
</span><span class="cx">                         davxml.Protected(),
</span><span class="cx">                         TwistedACLInheritable(),
</span><span class="cx">                     ),
</span><del>-                    # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
-                    davxml.ACE(
-                        davxml.Principal(davxml.HRef(joinURL(principal.principalURL(), &quot;calendar-proxy-write/&quot;))),
-                        davxml.Grant(*userprivs),
-                        davxml.Protected(),
-                        TwistedACLInheritable(),
-                    ),
</del><span class="cx">                 )
</span><span class="cx"> 
</span><ins>+                if config.EnableProxyPrincipals:
+                    aces += (
+                        # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
+                        davxml.ACE(
+                            davxml.Principal(davxml.HRef(joinURL(principal.principalURL(), &quot;calendar-proxy-read/&quot;))),
+                            davxml.Grant(*userprivs),
+                            davxml.Protected(),
+                            TwistedACLInheritable(),
+                        ),
+                        # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
+                        davxml.ACE(
+                            davxml.Principal(davxml.HRef(joinURL(principal.principalURL(), &quot;calendar-proxy-write/&quot;))),
+                            davxml.Grant(*userprivs),
+                            davxml.Protected(),
+                            TwistedACLInheritable(),
+                        ),
+                    )
+
</ins><span class="cx">         returnValue(aces)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -3162,12 +3163,18 @@
</span><span class="cx">                 content_type, filename = _getContentInfo()
</span><span class="cx">                 attachment, location = (yield self._newStoreObject.addAttachment(rids, content_type, filename, request.stream))
</span><span class="cx">                 post_result = Response(CREATED)
</span><ins>+                if not hasattr(request, &quot;extendedLogItems&quot;):
+                    request.extendedLogItems = {}
+                request.extendedLogItems[&quot;cl&quot;] = str(attachment.size())
</ins><span class="cx"> 
</span><span class="cx">             elif action == &quot;attachment-update&quot;:
</span><span class="cx">                 mid = _getMID()
</span><span class="cx">                 content_type, filename = _getContentInfo()
</span><span class="cx">                 attachment, location = (yield self._newStoreObject.updateAttachment(mid, content_type, filename, request.stream))
</span><span class="cx">                 post_result = Response(NO_CONTENT)
</span><ins>+                if not hasattr(request, &quot;extendedLogItems&quot;):
+                    request.extendedLogItems = {}
+                request.extendedLogItems[&quot;cl&quot;] = str(attachment.size())
</ins><span class="cx"> 
</span><span class="cx">             elif action == &quot;attachment-remove&quot;:
</span><span class="cx">                 rids = _getRIDs()
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavtesttest_configpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_config.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_config.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_config.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -385,7 +385,21 @@
</span><span class="cx">         config.EnableProxyPrincipals = False
</span><span class="cx">         self.assertTrue(&quot;calendar-proxy&quot; not in resource.davComplianceClasses())
</span><span class="cx"> 
</span><ins>+        self.assertTrue(&quot;calendarserver-group-sharee&quot; in resource.davComplianceClasses())
+        config.Sharing.Calendars.Groups.Enabled = False
+        config.update()
+        self.assertTrue(&quot;calendarserver-group-sharee&quot; not in resource.davComplianceClasses())
+        config.Sharing.Calendars.Groups.Enabled = True
+        config.update()
</ins><span class="cx"> 
</span><ins>+        self.assertTrue(&quot;calendarserver-group-attendee&quot; in resource.davComplianceClasses())
+        config.GroupAttendees.Enabled = False
+        config.update()
+        self.assertTrue(&quot;calendarserver-group-attendee&quot; not in resource.davComplianceClasses())
+        config.GroupAttendees.Enabled = True
+        config.update()
+
+
</ins><span class="cx">     def test_logging(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Logging module configures properly.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavtesttest_icalendarpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_icalendar.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_icalendar.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_icalendar.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -8241,9 +8241,6 @@
</span><span class="cx"> 
</span><span class="cx">         yield component.normalizeCalendarUserAddresses(lookupFunction, None, toCanonical=True)
</span><span class="cx"> 
</span><del>-        # Location value changed
-        prop = component.mainComponent().getProperty(&quot;LOCATION&quot;)
-        self.assertEquals(prop.value(), &quot;{Restricted} Buzz&quot;)
</del><span class="cx">         prop = component.getAttendeeProperty((&quot;urn:x-uid:buzz&quot;,))
</span><span class="cx">         self.assertEquals(&quot;urn:x-uid:buzz&quot;, prop.value())
</span><span class="cx">         self.assertEquals(prop.parameterValue(&quot;CN&quot;), &quot;{Restricted} Buzz&quot;)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavtesttest_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_sharing.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_sharing.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_sharing.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -18,25 +18,28 @@
</span><span class="cx"> from txweb2.dav.util import allDataFromStream
</span><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="cx"> from txweb2.iweb import IResponse
</span><ins>+from txweb2.stream import MemoryStream
</ins><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import customxml
</span><span class="cx"> from twistedcaldav.config import config
</span><ins>+from twistedcaldav.ical import Component
</ins><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><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><del>-
-from xml.etree.cElementTree import XML
</del><span class="cx"> from txdav.who.wiki import (
</span><span class="cx">     DirectoryRecord as WikiDirectoryRecord,
</span><span class="cx">     DirectoryService as WikiDirectoryService,
</span><span class="cx">     WikiAccessLevel
</span><span class="cx"> )
</span><span class="cx"> 
</span><ins>+from xml.etree.cElementTree import XML
+import urlparse
+
</ins><span class="cx"> sharedOwnerType = davxml.ResourceType.sharedownercalendar  # @UndefinedVariable
</span><span class="cx"> regularCalendarType = davxml.ResourceType.calendar  # @UndefinedVariable
</span><span class="cx"> 
</span><span class="lines">@@ -51,13 +54,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class SharingTests(StoreTestCase):
</del><ins>+class BaseSharingTests(StoreTestCase):
</ins><span class="cx"> 
</span><span class="cx">     def configure(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Override configuration hook to turn on sharing.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        super(SharingTests, self).configure()
</del><ins>+        super(BaseSharingTests, self).configure()
</ins><span class="cx">         self.patch(config.Sharing, &quot;Enabled&quot;, True)
</span><span class="cx">         self.patch(config.Sharing.Calendars, &quot;Enabled&quot;, True)
</span><span class="cx">         self.patch(config.Authentication.Wiki, &quot;Enabled&quot;, True)
</span><span class="lines">@@ -65,7 +68,7 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setUp(self):
</span><del>-        yield super(SharingTests, self).setUp()
</del><ins>+        yield super(BaseSharingTests, self).setUp()
</ins><span class="cx">         self.resource = yield self._getResource()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -73,7 +76,7 @@
</span><span class="cx">     def _refreshRoot(self, request=None):
</span><span class="cx">         if request is None:
</span><span class="cx">             request = norequest()
</span><del>-        result = yield super(SharingTests, self)._refreshRoot(request)
</del><ins>+        result = yield super(BaseSharingTests, self)._refreshRoot(request)
</ins><span class="cx">         self.resource = (
</span><span class="cx">             yield self.site.resource.locateChild(request, [&quot;calendar&quot;])
</span><span class="cx">         )[0]
</span><span class="lines">@@ -228,6 +231,9 @@
</span><span class="cx">         return None
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class SharingTests(BaseSharingTests):
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_upgradeToShare(self):
</span><span class="cx"> 
</span><span class="lines">@@ -1499,3 +1505,156 @@
</span><span class="cx">                 customxml.InviteStatusAccepted(),
</span><span class="cx">             ),
</span><span class="cx">         ))
</span><ins>+
+
+
+class DropboxSharingTests(BaseSharingTests):
+
+    def configure(self):
+        &quot;&quot;&quot;
+        Override configuration hook to turn on dropbox.
+        &quot;&quot;&quot;
+        super(DropboxSharingTests, self).configure()
+        self.patch(config, &quot;EnableDropBox&quot;, True)
+        self.patch(config, &quot;EnableManagedAttachments&quot;, False)
+
+
+    @inlineCallbacks
+    def test_dropboxWithMissingInvitee(self):
+
+        yield self.resource.upgradeToShare()
+
+        yield self._doPOST(&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
+            &lt;CS:share xmlns:D=&quot;DAV:&quot; xmlns:CS=&quot;http://calendarserver.org/ns/&quot;&gt;
+                &lt;CS:set&gt;
+                    &lt;D:href&gt;mailto:user02@example.com&lt;/D:href&gt;
+                    &lt;CS:summary&gt;My Shared Calendar&lt;/CS:summary&gt;
+                    &lt;CS:read-write/&gt;
+                &lt;/CS:set&gt;
+            &lt;/CS:share&gt;
+            &quot;&quot;&quot;)
+
+        propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+        uid = self._getUIDElementValue(propInvite)
+
+        yield self._doPOSTSharerAccept(&quot;&quot;&quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+            &lt;invite-reply xmlns='http://calendarserver.org/ns/'&gt;
+              &lt;href xmlns='DAV:'&gt;mailto:user01@example.com&lt;/href&gt;
+              &lt;invite-accepted/&gt;
+              &lt;hosturl&gt;
+                &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/calendar/&lt;/href&gt;
+              &lt;/hosturl&gt;
+              &lt;in-reply-to&gt;%s&lt;/in-reply-to&gt;
+              &lt;summary&gt;The Shared Calendar&lt;/summary&gt;
+              &lt;common-name&gt;User 02&lt;/common-name&gt;
+              &lt;first-name&gt;user&lt;/first-name&gt;
+              &lt;last-name&gt;02&lt;/last-name&gt;
+            &lt;/invite-reply&gt;
+            &quot;&quot;&quot; % (uid,)
+        )
+
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        component = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20060101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+ATTACH;VALUE=URI:/calendars/users/home1/some-dropbox-id/some-dropbox-id/caldavd.plist
+X-APPLE-DROPBOX:/calendars/users/home1/dropbox/some-dropbox-id
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+        yield calendar.createCalendarObjectWithName(&quot;dropbox.ics&quot;, component)
+        yield self.commit()
+
+        yield self.directory.removeRecords(((yield self.userUIDFromShortName(&quot;user02&quot;)),))
+        self.assertTrue((yield self.userUIDFromShortName(&quot;user02&quot;)) is None)
+
+        # Get dropbox and test ACLs
+        request = SimpleStoreRequest(self, &quot;GET&quot;, &quot;/calendars/__uids__/user01/dropbox/some-dropbox-id/&quot;)
+        resource = yield request.locateResource(&quot;/calendars/__uids__/user01/dropbox/some-dropbox-id/&quot;)
+        acl = yield resource.accessControlList(request)
+        self.assertTrue(acl is not None)
+
+
+
+class MamnagedAttachmentSharingTests(BaseSharingTests):
+
+    def configure(self):
+        &quot;&quot;&quot;
+        Override configuration hook to turn on managed attachments.
+        &quot;&quot;&quot;
+        super(MamnagedAttachmentSharingTests, self).configure()
+        self.patch(config, &quot;EnableDropBox&quot;, False)
+        self.patch(config, &quot;EnableManagedAttachments&quot;, True)
+
+
+    @inlineCallbacks
+    def test_attachmentWithMissingInvitee(self):
+
+        yield self.resource.upgradeToShare()
+
+        yield self._doPOST(&quot;&quot;&quot;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
+            &lt;CS:share xmlns:D=&quot;DAV:&quot; xmlns:CS=&quot;http://calendarserver.org/ns/&quot;&gt;
+                &lt;CS:set&gt;
+                    &lt;D:href&gt;mailto:user02@example.com&lt;/D:href&gt;
+                    &lt;CS:summary&gt;My Shared Calendar&lt;/CS:summary&gt;
+                    &lt;CS:read-write/&gt;
+                &lt;/CS:set&gt;
+            &lt;/CS:share&gt;
+            &quot;&quot;&quot;)
+
+        propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+        uid = self._getUIDElementValue(propInvite)
+
+        yield self._doPOSTSharerAccept(&quot;&quot;&quot;&lt;?xml version='1.0' encoding='UTF-8'?&gt;
+            &lt;invite-reply xmlns='http://calendarserver.org/ns/'&gt;
+              &lt;href xmlns='DAV:'&gt;mailto:user01@example.com&lt;/href&gt;
+              &lt;invite-accepted/&gt;
+              &lt;hosturl&gt;
+                &lt;href xmlns='DAV:'&gt;/calendars/__uids__/user01/calendar/&lt;/href&gt;
+              &lt;/hosturl&gt;
+              &lt;in-reply-to&gt;%s&lt;/in-reply-to&gt;
+              &lt;summary&gt;The Shared Calendar&lt;/summary&gt;
+              &lt;common-name&gt;User 02&lt;/common-name&gt;
+              &lt;first-name&gt;user&lt;/first-name&gt;
+              &lt;last-name&gt;02&lt;/last-name&gt;
+            &lt;/invite-reply&gt;
+            &quot;&quot;&quot; % (uid,)
+        )
+
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        component = Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20051222T205953Z
+CREATED:20060101T150000Z
+DTSTART:20060101T100000Z
+DURATION:PT1H
+SUMMARY:event 1
+UID:event1@ninevah.local
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;)
+        obj = yield calendar.createCalendarObjectWithName(&quot;dropbox.ics&quot;, component)
+        _ignore_attachment, location = yield obj.addAttachment(None, MimeType(&quot;text&quot;, &quot;plain&quot;), &quot;new.txt&quot;, MemoryStream(&quot;new attachment text&quot;))
+        yield self.commit()
+
+        yield self.directory.removeRecords(((yield self.userUIDFromShortName(&quot;user02&quot;)),))
+        self.assertTrue((yield self.userUIDFromShortName(&quot;user02&quot;)) is None)
+
+        # Get dropbox and test ACLs
+        location = urlparse.urlparse(location)[2]
+        location = &quot;/&quot;.join(location.split(&quot;/&quot;)[:-1])
+        request = SimpleStoreRequest(self, &quot;GET&quot;, location)
+        resource = yield request.locateResource(location)
+        acl = yield resource.accessControlList(request)
+        self.assertTrue(acl is not None)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavtesttest_upgradepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_upgrade.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_upgrade.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/test/test_upgrade.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -1740,8 +1740,8 @@
</span><span class="cx"> UID:1E238CA1-3C95-4468-B8CD-C8A399F78C71
</span><span class="cx"> DTSTART;TZID=US/Pacific:20090203T120000
</span><span class="cx"> DTEND;TZID=US/Pacific:20090203T130000
</span><del>-ATTENDEE;CN=Wilfredo Sanchez;CUTYPE=INDIVIDUAL;EMAIL=wsanchez@example.com;
- PARTSTAT=ACCEPTED:urn:x-uid:6423F94A-6B76-4A3A-815B-D52CFD77935D
</del><ins>+ATTENDEE;CN=Wilfredo Sanchez-Vega;CUTYPE=INDIVIDUAL;EMAIL=wsanchez@example
+ .com;PARTSTAT=ACCEPTED:urn:x-uid:6423F94A-6B76-4A3A-815B-D52CFD77935D
</ins><span class="cx"> ATTENDEE;CN=Double 'quotey' Quotes;CUTYPE=INDIVIDUAL;EMAIL=doublequotes@ex
</span><span class="cx">  ample.com;PARTSTAT=ACCEPTED:urn:x-uid:8E04787E-336D-41ED-A70B-D233AD0DCE6
</span><span class="cx">  F
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoAfricaCasablancaics"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Africa/Casablanca.ics (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Africa/Casablanca.ics        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Africa/Casablanca.ics        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -27,16 +27,16 @@
</span><span class="cx"> RDATE:20120820T020000
</span><span class="cx"> RDATE:20130810T020000
</span><span class="cx"> RDATE:20140802T020000
</span><del>-RDATE:20150718T020000
-RDATE:20160709T020000
-RDATE:20170701T020000
-RDATE:20180616T020000
-RDATE:20190608T020000
-RDATE:20200530T020000
-RDATE:20210515T020000
-RDATE:20220507T020000
-RDATE:20230422T020000
-RDATE:20240413T020000
</del><ins>+RDATE:20150719T020000
+RDATE:20160710T020000
+RDATE:20170702T020000
+RDATE:20180617T020000
+RDATE:20190609T020000
+RDATE:20200524T020000
+RDATE:20210516T020000
+RDATE:20220508T020000
+RDATE:20230423T020000
+RDATE:20240414T020000
</ins><span class="cx"> TZNAME:WEST
</span><span class="cx"> TZOFFSETFROM:+0000
</span><span class="cx"> TZOFFSETTO:+0100
</span><span class="lines">@@ -59,14 +59,13 @@
</span><span class="cx"> RDATE:20120930T030000
</span><span class="cx"> RDATE:20130707T030000
</span><span class="cx"> RDATE:20140628T030000
</span><del>-RDATE:20150613T030000
-RDATE:20160604T030000
-RDATE:20170520T030000
-RDATE:20180512T030000
-RDATE:20190504T030000
-RDATE:20200418T030000
-RDATE:20210410T030000
-RDATE:20220402T030000
</del><ins>+RDATE:20150614T030000
+RDATE:20160605T030000
+RDATE:20170521T030000
+RDATE:20180513T030000
+RDATE:20190505T030000
+RDATE:20200419T030000
+RDATE:20210411T030000
</ins><span class="cx"> TZNAME:WET
</span><span class="cx"> TZOFFSETFROM:+0100
</span><span class="cx"> TZOFFSETTO:+0000
</span><span class="lines">@@ -108,7 +107,7 @@
</span><span class="cx"> END:STANDARD
</span><span class="cx"> BEGIN:DAYLIGHT
</span><span class="cx"> DTSTART:20140330T020000
</span><del>-RRULE:FREQ=YEARLY;UNTIL=20220327T020000Z;BYDAY=-1SU;BYMONTH=3
</del><ins>+RRULE:FREQ=YEARLY;UNTIL=20210328T020000Z;BYDAY=-1SU;BYMONTH=3
</ins><span class="cx"> TZNAME:WEST
</span><span class="cx"> TZOFFSETFROM:+0000
</span><span class="cx"> TZOFFSETTO:+0100
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoAfricaEl_Aaiunics"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Africa/El_Aaiun.ics (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Africa/El_Aaiun.ics        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Africa/El_Aaiun.ics        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -39,14 +39,13 @@
</span><span class="cx"> RDATE:20120930T030000
</span><span class="cx"> RDATE:20130707T030000
</span><span class="cx"> RDATE:20140628T030000
</span><del>-RDATE:20150613T030000
-RDATE:20160604T030000
-RDATE:20170520T030000
-RDATE:20180512T030000
-RDATE:20190504T030000
-RDATE:20200418T030000
-RDATE:20210410T030000
-RDATE:20220402T030000
</del><ins>+RDATE:20150614T030000
+RDATE:20160605T030000
+RDATE:20170521T030000
+RDATE:20180513T030000
+RDATE:20190505T030000
+RDATE:20200419T030000
+RDATE:20210411T030000
</ins><span class="cx"> TZNAME:WET
</span><span class="cx"> TZOFFSETFROM:+0100
</span><span class="cx"> TZOFFSETTO:+0000
</span><span class="lines">@@ -61,16 +60,16 @@
</span><span class="cx"> RDATE:20120820T020000
</span><span class="cx"> RDATE:20130810T020000
</span><span class="cx"> RDATE:20140802T020000
</span><del>-RDATE:20150718T020000
-RDATE:20160709T020000
-RDATE:20170701T020000
-RDATE:20180616T020000
-RDATE:20190608T020000
-RDATE:20200530T020000
-RDATE:20210515T020000
-RDATE:20220507T020000
-RDATE:20230422T020000
-RDATE:20240413T020000
</del><ins>+RDATE:20150719T020000
+RDATE:20160710T020000
+RDATE:20170702T020000
+RDATE:20180617T020000
+RDATE:20190609T020000
+RDATE:20200524T020000
+RDATE:20210516T020000
+RDATE:20220508T020000
+RDATE:20230423T020000
+RDATE:20240414T020000
</ins><span class="cx"> TZNAME:WEST
</span><span class="cx"> TZOFFSETFROM:+0000
</span><span class="cx"> TZOFFSETTO:+0100
</span><span class="lines">@@ -91,7 +90,7 @@
</span><span class="cx"> END:STANDARD
</span><span class="cx"> BEGIN:DAYLIGHT
</span><span class="cx"> DTSTART:20140330T020000
</span><del>-RRULE:FREQ=YEARLY;UNTIL=20220327T020000Z;BYDAY=-1SU;BYMONTH=3
</del><ins>+RRULE:FREQ=YEARLY;UNTIL=20210328T020000Z;BYDAY=-1SU;BYMONTH=3
</ins><span class="cx"> TZNAME:WEST
</span><span class="cx"> TZOFFSETFROM:+0000
</span><span class="cx"> TZOFFSETTO:+0100
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoAmericaCaymanics"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/America/Cayman.ics (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/America/Cayman.ics        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/America/Cayman.ics        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -8,16 +8,37 @@
</span><span class="cx"> BEGIN:STANDARD
</span><span class="cx"> DTSTART:18900101T000000
</span><span class="cx"> RDATE:18900101T000000
</span><del>-TZNAME:CMT
-TZOFFSETFROM:-051808
-TZOFFSETTO:-051936
</del><ins>+TZNAME:KMT
+TZOFFSETFROM:-052532
+TZOFFSETTO:-050711
</ins><span class="cx"> END:STANDARD
</span><span class="cx"> BEGIN:STANDARD
</span><del>-DTSTART:19080422T000000
-RDATE:19080422T000000
</del><ins>+DTSTART:19120201T000000
+RDATE:19120201T000000
</ins><span class="cx"> TZNAME:EST
</span><del>-TZOFFSETFROM:-051936
</del><ins>+TZOFFSETFROM:-050711
</ins><span class="cx"> TZOFFSETTO:-0500
</span><span class="cx"> END:STANDARD
</span><ins>+BEGIN:STANDARD
+DTSTART:20160101T000000
+RDATE:20160101T000000
+TZNAME:EST
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0500
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20160313T020000
+RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20161106T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
</ins><span class="cx"> END:VTIMEZONE
</span><span class="cx"> END:VCALENDAR
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoMoroccoStandardTimeics"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Morocco Standard Time.ics (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Morocco Standard Time.ics        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/Morocco Standard Time.ics        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -27,16 +27,16 @@
</span><span class="cx"> RDATE:20120820T020000
</span><span class="cx"> RDATE:20130810T020000
</span><span class="cx"> RDATE:20140802T020000
</span><del>-RDATE:20150718T020000
-RDATE:20160709T020000
-RDATE:20170701T020000
-RDATE:20180616T020000
-RDATE:20190608T020000
-RDATE:20200530T020000
-RDATE:20210515T020000
-RDATE:20220507T020000
-RDATE:20230422T020000
-RDATE:20240413T020000
</del><ins>+RDATE:20150719T020000
+RDATE:20160710T020000
+RDATE:20170702T020000
+RDATE:20180617T020000
+RDATE:20190609T020000
+RDATE:20200524T020000
+RDATE:20210516T020000
+RDATE:20220508T020000
+RDATE:20230423T020000
+RDATE:20240414T020000
</ins><span class="cx"> TZNAME:WEST
</span><span class="cx"> TZOFFSETFROM:+0000
</span><span class="cx"> TZOFFSETTO:+0100
</span><span class="lines">@@ -59,14 +59,13 @@
</span><span class="cx"> RDATE:20120930T030000
</span><span class="cx"> RDATE:20130707T030000
</span><span class="cx"> RDATE:20140628T030000
</span><del>-RDATE:20150613T030000
-RDATE:20160604T030000
-RDATE:20170520T030000
-RDATE:20180512T030000
-RDATE:20190504T030000
-RDATE:20200418T030000
-RDATE:20210410T030000
-RDATE:20220402T030000
</del><ins>+RDATE:20150614T030000
+RDATE:20160605T030000
+RDATE:20170521T030000
+RDATE:20180513T030000
+RDATE:20190505T030000
+RDATE:20200419T030000
+RDATE:20210411T030000
</ins><span class="cx"> TZNAME:WET
</span><span class="cx"> TZOFFSETFROM:+0100
</span><span class="cx"> TZOFFSETTO:+0000
</span><span class="lines">@@ -108,7 +107,7 @@
</span><span class="cx"> END:STANDARD
</span><span class="cx"> BEGIN:DAYLIGHT
</span><span class="cx"> DTSTART:20140330T020000
</span><del>-RRULE:FREQ=YEARLY;UNTIL=20220327T020000Z;BYDAY=-1SU;BYMONTH=3
</del><ins>+RRULE:FREQ=YEARLY;UNTIL=20210328T020000Z;BYDAY=-1SU;BYMONTH=3
</ins><span class="cx"> TZNAME:WEST
</span><span class="cx"> TZOFFSETFROM:+0000
</span><span class="cx"> TZOFFSETTO:+0100
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfolinkstxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/links.txt (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/links.txt        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/links.txt        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -45,7 +45,6 @@
</span><span class="cx"> America/Atka        America/Adak
</span><span class="cx"> America/Buenos_Aires        America/Argentina/Buenos_Aires
</span><span class="cx"> America/Catamarca        America/Argentina/Catamarca
</span><del>-America/Cayman        America/Panama
</del><span class="cx"> America/Coral_Harbour        America/Atikokan
</span><span class="cx"> America/Cordoba        America/Argentina/Cordoba
</span><span class="cx"> America/Dominica        America/Port_of_Spain
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfotimezonesxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/timezones.xml (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/timezones.xml        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/timezones.xml        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -2,7 +2,7 @@
</span><span class="cx"> &lt;!DOCTYPE timezones SYSTEM &quot;timezones.dtd&quot;&gt;
</span><span class="cx"> 
</span><span class="cx"> &lt;timezones&gt;
</span><del>-  &lt;dtstamp&gt;2015-04-24T16:13:20Z&lt;/dtstamp&gt;
</del><ins>+  &lt;dtstamp&gt;2015-07-01T14:44:04Z&lt;/dtstamp&gt;
</ins><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;AUS Central Standard Time&lt;/tzid&gt;
</span><span class="cx">     &lt;dtstamp&gt;2014-08-25T14:48:40Z&lt;/dtstamp&gt;
</span><span class="lines">@@ -104,9 +104,9 @@
</span><span class="cx">   &lt;/timezone&gt;
</span><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;Africa/Casablanca&lt;/tzid&gt;
</span><del>-    &lt;dtstamp&gt;2015-04-24T16:13:20Z&lt;/dtstamp&gt;
</del><ins>+    &lt;dtstamp&gt;2015-07-01T14:44:04Z&lt;/dtstamp&gt;
</ins><span class="cx">     &lt;alias&gt;Morocco Standard Time&lt;/alias&gt;
</span><del>-    &lt;md5&gt;5d06f7259ed2bf2162e69a5dffd2954b&lt;/md5&gt;
</del><ins>+    &lt;md5&gt;bc9dfe82c24d4798a1c952fa92ed4746&lt;/md5&gt;
</ins><span class="cx">   &lt;/timezone&gt;
</span><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;Africa/Ceuta&lt;/tzid&gt;
</span><span class="lines">@@ -140,8 +140,8 @@
</span><span class="cx">   &lt;/timezone&gt;
</span><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;Africa/El_Aaiun&lt;/tzid&gt;
</span><del>-    &lt;dtstamp&gt;2015-04-24T16:13:20Z&lt;/dtstamp&gt;
-    &lt;md5&gt;d73f03e09cb199fb0349281b9b2d349f&lt;/md5&gt;
</del><ins>+    &lt;dtstamp&gt;2015-07-01T14:44:04Z&lt;/dtstamp&gt;
+    &lt;md5&gt;2404746bd6c906d84ba245b48ba0ebcb&lt;/md5&gt;
</ins><span class="cx">   &lt;/timezone&gt;
</span><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;Africa/Freetown&lt;/tzid&gt;
</span><span class="lines">@@ -554,8 +554,8 @@
</span><span class="cx">   &lt;/timezone&gt;
</span><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;America/Cayman&lt;/tzid&gt;
</span><del>-    &lt;dtstamp&gt;2015-04-24T16:13:20Z&lt;/dtstamp&gt;
-    &lt;md5&gt;8319a9f7d2931dbc39b4960360992fa3&lt;/md5&gt;
</del><ins>+    &lt;dtstamp&gt;2015-07-01T14:44:04Z&lt;/dtstamp&gt;
+    &lt;md5&gt;32249cf2917460aa068887267d979a5d&lt;/md5&gt;
</ins><span class="cx">   &lt;/timezone&gt;
</span><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;America/Chicago&lt;/tzid&gt;
</span><span class="lines">@@ -3112,8 +3112,8 @@
</span><span class="cx">   &lt;/timezone&gt;
</span><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;Morocco Standard Time&lt;/tzid&gt;
</span><del>-    &lt;dtstamp&gt;2015-04-24T16:13:20Z&lt;/dtstamp&gt;
-    &lt;md5&gt;a10813c9508c9e96a26c67edbd803e4d&lt;/md5&gt;
</del><ins>+    &lt;dtstamp&gt;2015-07-01T14:44:04Z&lt;/dtstamp&gt;
+    &lt;md5&gt;1e27973c6723a745831c474ebedbf11a&lt;/md5&gt;
</ins><span class="cx">   &lt;/timezone&gt;
</span><span class="cx">   &lt;timezone&gt;
</span><span class="cx">     &lt;tzid&gt;Mountain Standard Time&lt;/tzid&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtwistedcaldavzoneinfoversiontxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/version.txt (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/version.txt        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/twistedcaldav/zoneinfo/version.txt        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -1 +1 @@
</span><del>-IANA Timezone Registry: 2015d
</del><span class="cx">\ No newline at end of file
</span><ins>+IANA Timezone Registry: 2015e
</ins><span class="cx">\ No newline at end of file
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavbasedatastoresubpostgrespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/base/datastore/subpostgres.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/base/datastore/subpostgres.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/base/datastore/subpostgres.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -35,7 +35,7 @@
</span><span class="cx"> from twext.python.filepath import CachingFilePath
</span><span class="cx"> 
</span><span class="cx"> from twisted.protocols.basic import LineReceiver
</span><del>-from twisted.internet.defer import Deferred
</del><ins>+from twisted.internet.defer import Deferred, succeed
</ins><span class="cx"> from txdav.base.datastore.dbapiclient import DBAPIConnector
</span><span class="cx"> from txdav.base.datastore.dbapiclient import postgres
</span><span class="cx"> from txdav.common.icommondatastore import InternalDataStoreError
</span><span class="lines">@@ -280,6 +280,15 @@
</span><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx">         self._pgCtl = locateCommand(&quot;pg_ctl&quot;, pgCtl)
</span><ins>+
+        # Make note of the inode for the pg_ctl script; if it changes or is
+        # missing when it comes time to stop postgres, instead send SIGTERM
+        # to stop our postgres (since we can't do a graceful shutdown)
+        try:
+            self._pgCtlInode = os.stat(self._pgCtl).st_ino
+        except:
+            self._pgCtlInode = 0
+
</ins><span class="cx">         self._initdb = locateCommand(&quot;initdb&quot;, initDB)
</span><span class="cx">         self._reactor = reactor
</span><span class="cx">         self._postgresPid = None
</span><span class="lines">@@ -657,30 +666,43 @@
</span><span class="cx">             # If pg_ctl's startup wasn't successful, don't bother to stop the
</span><span class="cx">             # database.  (This also happens in command-line tools.)
</span><span class="cx">             if self.shouldStopDatabase:
</span><del>-                monitor = PostgresMonitor()
-                args = [
-                    self._pgCtl, &quot;stop&quot;,
-                    &quot;--log={}&quot;.format(self.logFile),
-                ]
-                log.info(&quot;Requesting postgres stop via: {args}&quot;, args=args)
-                self.reactor.spawnProcess(
-                    monitor, self._pgCtl,
-                    args,
-                    env=self.env, path=self.workingDir.path,
-                    uid=self.uid, gid=self.gid,
-                )
-                return monitor.completionDeferred
</del><ins>+
+                # Compare pg_ctl inode with one we saw at the start; if different
+                # (or missing), fall back to SIGTERM
+                try:
+                    newInode = os.stat(self._pgCtl).st_ino
+                except OSError:
+                    # Missing
+                    newInode = -1
+
+                if self._pgCtlInode != newInode:
+                    # send SIGTERM to postgres
+                    log.info(&quot;Postgres control script mismatch&quot;)
+                    if self._postgresPid:
+                        log.info(&quot;Sending SIGTERM to Postgres&quot;)
+                        try:
+                            os.kill(self._postgresPid, signal.SIGTERM)
+                        except OSError:
+                            pass
+                    return succeed(None)
+                else:
+                    # use pg_ctl stop
+                    monitor = PostgresMonitor()
+                    args = [
+                        self._pgCtl, &quot;stop&quot;,
+                        &quot;--log={}&quot;.format(self.logFile),
+                    ]
+                    log.info(&quot;Requesting postgres stop via: {args}&quot;, args=args)
+                    self.reactor.spawnProcess(
+                        monitor, self._pgCtl,
+                        args,
+                        env=self.env, path=self.workingDir.path,
+                        uid=self.uid, gid=self.gid,
+                    )
+                    return monitor.completionDeferred
</ins><span class="cx">         return d.addCallback(superStopped)
</span><span class="cx"> 
</span><del>-#        def maybeStopSubprocess(result):
-#            if self.monitor is not None:
-#                self.monitor.transport.signalProcess(&quot;INT&quot;)
-#                return self.monitor.completionDeferred
-#            return result
-#        d.addCallback(maybeStopSubprocess)
-#        return d
</del><span class="cx"> 
</span><del>-
</del><span class="cx">     def hardStop(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Stop postgres quickly by sending it SIGQUIT
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingicalsplitterpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/icalsplitter.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/icalsplitter.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/icalsplitter.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -139,6 +139,15 @@
</span><span class="cx">             # where the Organizer event has L{willSplit} == C{True}
</span><span class="cx">             rid = break_point if allow_past_the_end else None
</span><span class="cx"> 
</span><ins>+        if rid is not None:
+            # rid value type must match
+            dtstart = ical.mainComponent().propertyValue(&quot;DTSTART&quot;)
+            if dtstart.isDateOnly():
+                rid.setDateOnly(True)
+            elif dtstart.floating():
+                rid.setTimezoneID(None)
+
+
</ins><span class="cx">         return rid
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimipinboundpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/inbound.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/inbound.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/inbound.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -27,7 +27,6 @@
</span><span class="cx"> from twisted.internet import protocol, defer, ssl
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</span><span class="cx"> from twisted.mail import pop3client, imap4
</span><del>-from twisted.mail.smtp import messageid
</del><span class="cx"> 
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.ical import Property, Component
</span><span class="lines">@@ -189,6 +188,30 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def sanitizeCalendar(calendar):
+    &quot;&quot;&quot;
+    Clean up specific issues seen in the wild from third party IMIP capable
+    servers.
+
+    @param calendar: the calendar Component to sanitize
+    @type calendar: L{Component}
+    &quot;&quot;&quot;
+    # Don't let a missing PRODID prevent the reply from being processed
+    if not calendar.hasProperty(&quot;PRODID&quot;):
+        calendar.addProperty(
+            Property(
+                &quot;PRODID&quot;, &quot;Unknown&quot;
+            )
+        )
+
+    # For METHOD:REPLY we can remove STATUS properties
+    methodProperty = calendar.getProperty(&quot;METHOD&quot;)
+    if methodProperty is not None:
+        if methodProperty.value() == &quot;REPLY&quot;:
+            calendar.removeAllPropertiesWithName(&quot;STATUS&quot;)
+
+
+
</ins><span class="cx"> class MailReceiver(object):
</span><span class="cx"> 
</span><span class="cx">     NO_TOKEN = 0
</span><span class="lines">@@ -392,7 +415,7 @@
</span><span class="cx">             del msg[&quot;To&quot;]
</span><span class="cx">             msg[&quot;To&quot;] = toAddr
</span><span class="cx">             log.warn(&quot;Mail gateway forwarding reply back to organizer&quot;)
</span><del>-            yield smtpSender.sendMessage(fromAddr, toAddr, messageid(), msg.as_string())
</del><ins>+            yield smtpSender.sendMessage(fromAddr, toAddr, SMTPSender.betterMessageID(), msg.as_string())
</ins><span class="cx">             returnValue(self.REPLY_FORWARDED_TO_ORGANIZER)
</span><span class="cx"> 
</span><span class="cx">         # Process the imip attachment; inject to calendar server
</span><span class="lines">@@ -401,13 +424,7 @@
</span><span class="cx">         calendar = Component.fromString(calBody)
</span><span class="cx">         event = calendar.mainComponent()
</span><span class="cx"> 
</span><del>-        # Don't let a missing PRODID prevent the reply from being processed
-        if not calendar.hasProperty(&quot;PRODID&quot;):
-            calendar.addProperty(
-                Property(
-                    &quot;PRODID&quot;, &quot;Unknown&quot;
-                )
-            )
</del><ins>+        sanitizeCalendar(calendar)
</ins><span class="cx"> 
</span><span class="cx">         calendar.removeAllButOneAttendee(record.attendee)
</span><span class="cx">         organizerProperty = calendar.getOrganizerProperty()
</span><span class="lines">@@ -691,7 +708,7 @@
</span><span class="cx">         self.log.debug(&quot;IMAP in cbGotMessage&quot;)
</span><span class="cx">         try:
</span><span class="cx">             messageData = results.values()[0]['RFC822']
</span><del>-        except IndexError:
</del><ins>+        except (IndexError, KeyError):
</ins><span class="cx">             # results will be empty unless the &quot;twistedmail-imap-flags-anywhere&quot;
</span><span class="cx">             # patch from http://twistedmatrix.com/trac/ticket/1105 is applied
</span><span class="cx">             self.log.error(&quot;Skipping empty results -- apply twisted patch!&quot;)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimipoutboundpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/outbound.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/outbound.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/outbound.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -32,7 +32,7 @@
</span><span class="cx"> from twext.enterprise.jobqueue import WorkItem
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><del>-from twisted.mail.smtp import messageid, rfc822date
</del><ins>+from twisted.mail.smtp import rfc822date
</ins><span class="cx"> from twisted.web.microdom import Text as DOMText, Element as DOMElement
</span><span class="cx"> from twisted.web.microdom import parseString
</span><span class="cx"> from twisted.web.template import XMLString, TEMPLATE_NAMESPACE, Element, renderer, flattenString, tags
</span><span class="lines">@@ -453,6 +453,8 @@
</span><span class="cx">             orgEmail = organizerMailto[7:]
</span><span class="cx"> 
</span><span class="cx">             orgCN = calendar.getOrganizerProperty().parameterValue('CN', None)
</span><ins>+            if orgCN:
+                orgCN = orgCN.decode(&quot;utf-8&quot;)
</ins><span class="cx">             addressWithToken = formattedFrom
</span><span class="cx"> 
</span><span class="cx">         # At the point we've created the token in the db, which we always
</span><span class="lines">@@ -558,7 +560,7 @@
</span><span class="cx">         msg[&quot;Reply-To&quot;] = replyToAddress
</span><span class="cx">         msg[&quot;To&quot;] = toAddress
</span><span class="cx">         msg[&quot;Date&quot;] = rfc822date()
</span><del>-        msgId = messageid()
</del><ins>+        msgId = SMTPSender.betterMessageID()
</ins><span class="cx">         msg[&quot;Message-ID&quot;] = msgId
</span><span class="cx"> 
</span><span class="cx">         msgAlt = MIMEMultipart(&quot;alternative&quot;)
</span><span class="lines">@@ -663,8 +665,9 @@
</span><span class="cx">         # template stuff, and once again, it's just a 'mailto:'.
</span><span class="cx">         # tags.a(href=&quot;mailto:&quot;+email)[cn]
</span><span class="cx">         if orgEmail:
</span><del>-            details['htmlOrganizer'] = tags.a(href=&quot;mailto:%s&quot; % (orgEmail,))(
-                orgCN)
</del><ins>+            if not orgCN:
+                orgCN = orgEmail
+            details['htmlOrganizer'] = tags.a(href=&quot;mailto:%s&quot; % (orgEmail,))(orgCN)
</ins><span class="cx">         else:
</span><span class="cx">             details['htmlOrganizer'] = orgCN
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimipsmtpsenderpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/smtpsender.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/smtpsender.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/smtpsender.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -24,7 +24,8 @@
</span><span class="cx"> from twext.internet.gaiendpoint import GAIEndpoint
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twisted.internet import defer, ssl, reactor as _reactor
</span><del>-from twisted.mail.smtp import ESMTPSenderFactory
</del><ins>+from twisted.mail.smtp import ESMTPSenderFactory, messageid
+from twistedcaldav.config import config
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -81,3 +82,15 @@
</span><span class="cx">         deferred.addCallback(_success, msgId, fromAddr, toAddr)
</span><span class="cx">         deferred.addErrback(_failure, msgId, fromAddr, toAddr)
</span><span class="cx">         return deferred
</span><ins>+
+
+    @staticmethod
+    def betterMessageID():
+        &quot;&quot;&quot;
+        Strip out the domain in the default Twisted Message-ID value and replace with our configured
+        server host name. That will avoid leaking internal app-server host names in a multi-host setup.
+
+        @return: our safe message-id value
+        @rtype: L{str}
+        &quot;&quot;&quot;
+        return &quot;{}@{}&gt;&quot;.format(messageid().split(&quot;@&quot;)[0], config.ServerHostName)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimiptesttest_inboundpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/test/test_inbound.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/test/test_inbound.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/test/test_inbound.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -29,6 +29,7 @@
</span><span class="cx"> from txdav.caldav.datastore.scheduling.imip.inbound import injectMessage
</span><span class="cx"> from txdav.caldav.datastore.scheduling.imip.inbound import shouldDeleteAllMail
</span><span class="cx"> from txdav.caldav.datastore.scheduling.imip.inbound import IMAP4DownloadProtocol
</span><ins>+from txdav.caldav.datastore.scheduling.imip.inbound import sanitizeCalendar
</ins><span class="cx"> from txdav.common.datastore.test.util import CommonCommonTests
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.jobqueue import JobItem
</span><span class="lines">@@ -457,7 +458,64 @@
</span><span class="cx">         self.assertEquals(self.flagDeletedResult, &quot;xyzzy&quot;)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def test_missingIMAPMessages(self):
+        &quot;&quot;&quot;
+        Make sure L{IMAP4DownloadProtocol.cbGotMessage} can deal with missing messages.
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+        class DummyResult(object):
+            def __init__(self):
+                self._values = []
+
+            def values(self):
+                return self._values
+
+        noResult = DummyResult()
+        missingKey = DummyResult()
+        missingKey.values().append({})
+
+        imap4 = IMAP4DownloadProtocol()
+        imap4.messageUIDs = []
+        imap4.fetchNextMessage = lambda : None
+
+        result = yield imap4.cbGotMessage(noResult, [])
+        self.assertTrue(result is None)
+        result = yield imap4.cbGotMessage(missingKey, [])
+        self.assertTrue(result is None)
+
+
+    def test_sanitizeCalendar(self):
+        &quot;&quot;&quot;
+        Verify certain inbound third party mistakes are corrected.
+        &quot;&quot;&quot;
+
+        data = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REPLY
+BEGIN:VEVENT
+UID:12345-67890
+DTSTAMP:20130208T120000Z
+DTSTART:20180601T120000Z
+DTEND:20180601T130000Z
+ORGANIZER:urn:x-uid:user01
+ATTENDEE:mailto:xyzzy@example.com;PARTSTAT=ACCEPTED
+STATUS:ACCEPTED
+STATUS:ACCEPTED
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+        calendar = Component.fromString(data)
+        self.assertFalse(calendar.hasProperty(&quot;PRODID&quot;))
+        self.assertTrue(calendar.masterComponent().hasProperty(&quot;STATUS&quot;))
+        sanitizeCalendar(calendar)
+        self.assertTrue(calendar.hasProperty(&quot;PRODID&quot;))
+        self.assertFalse(calendar.masterComponent().hasProperty(&quot;STATUS&quot;))
+
+
+
+
+
</ins><span class="cx"> class StubFactory(object):
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, actionTaken, deleteAllMail):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingimiptesttest_outboundpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/test/test_outbound.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/test/test_outbound.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/imip/test/test_outbound.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -60,7 +60,7 @@
</span><span class="cx"> DESCRIPTION:awesome description with &quot;&lt;&quot; and &quot;&amp;&quot;
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;
</del><ins>+&quot;&quot;&quot;.encode(&quot;utf-8&quot;)
</ins><span class="cx"> 
</span><span class="cx"> inviteTextNoTimezone = u&quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -83,7 +83,7 @@
</span><span class="cx"> DESCRIPTION:awesome description with &quot;&lt;&quot; and &quot;&amp;&quot;
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;
</del><ins>+&quot;&quot;&quot;.encode(&quot;utf-8&quot;)
</ins><span class="cx"> 
</span><span class="cx"> inviteTextWithTimezone = u&quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -249,7 +249,7 @@
</span><span class="cx"> DESCRIPTION:awesome description with &quot;&lt;&quot; and &quot;&amp;&quot;
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;
</del><ins>+&quot;&quot;&quot;.encode(&quot;utf-8&quot;)
</ins><span class="cx"> 
</span><span class="cx"> ORGANIZER = &quot;urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A&quot;
</span><span class="cx"> ATTENDEE = &quot;mailto:attendee@example.com&quot;
</span><span class="lines">@@ -400,63 +400,63 @@
</span><span class="cx"> 
</span><span class="cx">             # Update
</span><span class="cx">             (
</span><del>-                &quot;&quot;&quot;BEGIN:VCALENDAR
</del><ins>+                u&quot;&quot;&quot;BEGIN:VCALENDAR
</ins><span class="cx"> VERSION:2.0
</span><span class="cx"> METHOD:REQUEST
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:CFDD5E46-4F74-478A-9311-B3FF905449C3
</span><span class="cx"> DTSTART:20100325T154500Z
</span><span class="cx"> DTEND:20100325T164500Z
</span><del>-ATTENDEE;CN=The Attendee;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:
</del><ins>+ATTENDEE;CN=Th\xe9 Attendee;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:
</ins><span class="cx">  mailto:attendee@example.com
</span><del>-ATTENDEE;CN=The Organizer;CUTYPE=INDIVIDUAL;EMAIL=organizer@example.com;PAR
</del><ins>+ATTENDEE;CN=Th\xe9 Organizer;CUTYPE=INDIVIDUAL;EMAIL=organizer@example.com;PAR
</ins><span class="cx">  TSTAT=ACCEPTED:urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A
</span><del>-ORGANIZER;CN=The Organizer;EMAIL=organizer@example.com:urn:uuid:C3B38B00-41
</del><ins>+ORGANIZER;CN=Th\xe9 Organizer;EMAIL=organizer@example.com:urn:uuid:C3B38B00-41
</ins><span class="cx">  66-11DD-B22C-A07C87E02F6A
</span><del>-SUMMARY:testing outbound( ) *update*
</del><ins>+SUMMARY:t\xe9sting outbound( ) *update*
</ins><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;,
</del><ins>+&quot;&quot;&quot;.encode(&quot;utf-8&quot;),
</ins><span class="cx">                 &quot;CFDD5E46-4F74-478A-9311-B3FF905449C3&quot;,
</span><span class="cx">                 &quot;urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A&quot;,
</span><span class="cx">                 &quot;mailto:attendee@example.com&quot;,
</span><span class="cx">                 &quot;update&quot;,
</span><span class="cx">                 &quot;organizer@example.com&quot;,
</span><del>-                &quot;The Organizer&quot;,
</del><ins>+                u&quot;Th\xe9 Organizer&quot;,
</ins><span class="cx">                 [
</span><del>-                    (u'The Attendee', u'attendee@example.com'),
-                    (u'The Organizer', u'organizer@example.com')
</del><ins>+                    (u'Th\xe9 Attendee', u'attendee@example.com'),
+                    (u'Th\xe9 Organizer', u'organizer@example.com')
</ins><span class="cx">                 ],
</span><del>-                &quot;The Organizer &lt;organizer@example.com&gt;&quot;,
-                &quot;The Organizer &lt;organizer@example.com&gt;&quot;,
</del><ins>+                u&quot;Th\xe9 Organizer &lt;organizer@example.com&gt;&quot;,
+                &quot;=?utf-8?q?Th=C3=A9_Organizer_=3Corganizer=40example=2Ecom=3E?=&quot;,
</ins><span class="cx">                 &quot;attendee@example.com&quot;,
</span><span class="cx">             ),
</span><span class="cx"> 
</span><span class="cx">             # Reply
</span><span class="cx">             (
</span><del>-                &quot;&quot;&quot;BEGIN:VCALENDAR
</del><ins>+                u&quot;&quot;&quot;BEGIN:VCALENDAR
</ins><span class="cx"> VERSION:2.0
</span><span class="cx"> METHOD:REPLY
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:DFDD5E46-4F74-478A-9311-B3FF905449C4
</span><span class="cx"> DTSTART:20100325T154500Z
</span><span class="cx"> DTEND:20100325T164500Z
</span><del>-ATTENDEE;CN=The Attendee;CUTYPE=INDIVIDUAL;EMAIL=attendee@example.com;PARTST
</del><ins>+ATTENDEE;CN=Th\xe9 Attendee;CUTYPE=INDIVIDUAL;EMAIL=attendee@example.com;PARTST
</ins><span class="cx">  AT=ACCEPTED:urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A
</span><del>-ORGANIZER;CN=The Organizer;EMAIL=organizer@example.com:mailto:organizer@exam
</del><ins>+ORGANIZER;CN=Th\xe9 Organizer;EMAIL=organizer@example.com:mailto:organizer@exam
</ins><span class="cx">  ple.com
</span><del>-SUMMARY:testing outbound( ) *reply*
</del><ins>+SUMMARY:t\xe9sting outbound( ) *reply*
</ins><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;,
</del><ins>+&quot;&quot;&quot;.encode(&quot;utf-8&quot;),
</ins><span class="cx">                 None,
</span><span class="cx">                 &quot;urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A&quot;,
</span><span class="cx">                 &quot;mailto:organizer@example.com&quot;,
</span><span class="cx">                 &quot;reply&quot;,
</span><span class="cx">                 &quot;organizer@example.com&quot;,
</span><del>-                &quot;The Organizer&quot;,
</del><ins>+                u&quot;Th\xe9 Organizer&quot;,
</ins><span class="cx">                 [
</span><del>-                    (u'The Attendee', u'attendee@example.com'),
</del><ins>+                    (u'Th\xe9 Attendee', u'attendee@example.com'),
</ins><span class="cx">                 ],
</span><span class="cx">                 &quot;attendee@example.com&quot;,
</span><span class="cx">                 &quot;attendee@example.com&quot;,
</span><span class="lines">@@ -635,6 +635,59 @@
</span><span class="cx">         self.assertEquals(actualTypes, expectedTypes)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def test_generateEmail_noOrganizerCN(self):
+        &quot;&quot;&quot;
+        L{MailHandler.generateEmail} generates a MIME-formatted email when
+        the organizer property has no CN parameter.
+        &quot;&quot;&quot;
+        calendar = Component.fromString(initialInviteText)
+        _ignore_msgID, msgTxt = self.sender.generateEmail(
+            inviteState='new',
+            calendar=calendar,
+            orgEmail=u&quot;user01@localhost&quot;,
+            orgCN=None,
+            attendees=[(u&quot;Us\xe9r One&quot;, &quot;user01@localhost&quot;),
+                       (u&quot;User 2&quot;, &quot;user02@localhost&quot;)],
+            fromAddress=&quot;user01@localhost&quot;,
+            replyToAddress=&quot;imip-system@localhost&quot;,
+            toAddress=&quot;user03@localhost&quot;,
+        )
+        message = email.message_from_string(msgTxt)
+        self.assertTrue(message is not None)
+
+
+    def test_generateEmail_noAttendeeCN(self):
+        &quot;&quot;&quot;
+        L{MailHandler.generateEmail} generates a MIME-formatted email when
+        the attendee property has no CN parameter.
+        &quot;&quot;&quot;
+        calendar = Component.fromString(initialInviteText)
+        _ignore_msgID, msgTxt = self.sender.generateEmail(
+            inviteState='new',
+            calendar=calendar,
+            orgEmail=u&quot;user01@localhost&quot;,
+            orgCN=u&quot;User Z\xe9ro One&quot;,
+            attendees=[(None, &quot;user01@localhost&quot;),
+                       (None, &quot;user02@localhost&quot;)],
+            fromAddress=&quot;user01@localhost&quot;,
+            replyToAddress=&quot;imip-system@localhost&quot;,
+            toAddress=&quot;user03@localhost&quot;,
+        )
+        message = email.message_from_string(msgTxt)
+        self.assertTrue(message is not None)
+
+
+    def test_messageID(self):
+        &quot;&quot;&quot;
+        L{SMTPSender.betterMessageID} generates a Message-ID domain matching
+        the L{config.ServerHostName} value.
+        &quot;&quot;&quot;
+        self.patch(config, &quot;ServerHostName&quot;, &quot;calendar.example.com&quot;)
+        msgID, message = self.generateSampleEmail()
+        self.assertEquals(message['Message-ID'], msgID)
+        self.assertEqual(msgID[:-1].split(&quot;@&quot;)[1], config.ServerHostName)
+
+
</ins><span class="cx">     def test_alwaysIncludeTimezones(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         L{MailHandler.generateEmail} generates a MIME-formatted email with a
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoreschedulingprocessingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/processing.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/processing.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/scheduling/processing.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -413,7 +413,7 @@
</span><span class="cx">                 log.debug(&quot;ImplicitProcessing - originator '%s' to recipient '%s' ignoring UID: '%s' - split already done&quot; % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
</span><span class="cx">                 returnValue((True, False, False, None,))
</span><span class="cx">             else:
</span><del>-                self.message.removeProperty(&quot;X-CALENDARSERVER-SPLIT-OLDER-UID&quot;)
</del><ins>+                self.message.removeProperty(&quot;X-CALENDARSERVER-SPLIT-NEWER-UID&quot;)
</ins><span class="cx">                 self.message.removeProperty(&quot;X-CALENDARSERVER-SPLIT-RID&quot;)
</span><span class="cx"> 
</span><span class="cx">         # Different based on method
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/sql.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/sql.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/sql.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -82,7 +82,8 @@
</span><span class="cx">     UnknownTimezone, SetComponentOptions
</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_directory import GroupsRecord
</del><ins>+from txdav.common.datastore.sql_directory import GroupsRecord, \
+    GroupMembershipRecord
</ins><span class="cx"> from txdav.common.datastore.sql_tables import _ATTACHMENTS_MODE_NONE, \
</span><span class="cx">     _ATTACHMENTS_MODE_READ, _ATTACHMENTS_MODE_WRITE, _BIND_MODE_DIRECT, \
</span><span class="cx">     _BIND_MODE_GROUP, _BIND_MODE_GROUP_READ, _BIND_MODE_GROUP_WRITE, \
</span><span class="lines">@@ -2127,40 +2128,61 @@
</span><span class="cx"> 
</span><span class="cx">         # First check that the actual group membership has changed
</span><span class="cx">         if (yield self.updateShareeGroupLink(groupUID)):
</span><del>-            group = yield self._txn.groupByUID(groupUID)
-            memberUIDs = yield self._txn.groupMemberUIDs(group.groupID)
</del><ins>+
+            # First find all the members of all the groups being shared to
+            groupMembers = yield GroupMembershipRecord.query(
+                self._txn,
+                GroupMembershipRecord.groupID.In(
+                    GroupShareeRecord.queryExpr(
+                        expr=GroupShareeRecord.calendarID == self.id(),
+                        attributes=[GroupShareeRecord.groupID, ]
+                    )
+                ),
+                distinct=True,
+            )
+            memberUIDs = set([grp.memberUID for grp in groupMembers])
+
+            # Find each currently bound group sharee UID along with their bind mode
</ins><span class="cx">             boundUIDs = set()
</span><del>-
</del><span class="cx">             home = self._homeSchema
</span><span class="cx">             bind = self._bindSchema
</span><span class="cx">             rows = yield Select(
</span><del>-                [home.OWNER_UID],
-                From=home,
-                Where=home.RESOURCE_ID.In(
-                    Select(
-                        [bind.HOME_RESOURCE_ID],
-                        From=bind,
-                        Where=(bind.CALENDAR_RESOURCE_ID == self._resourceID).And(
-                            (bind.BIND_MODE == _BIND_MODE_GROUP)
-                            .Or(bind.BIND_MODE == _BIND_MODE_GROUP_READ)
-                            .Or(bind.BIND_MODE == _BIND_MODE_GROUP_WRITE)
-                        )
-                    )
</del><ins>+                [home.OWNER_UID, bind.BIND_MODE],
+                From=bind.join(home, on=bind.HOME_RESOURCE_ID == home.RESOURCE_ID),
+                Where=(bind.CALENDAR_RESOURCE_ID == self._resourceID).And(
+                    bind.BIND_MODE.In((_BIND_MODE_GROUP, _BIND_MODE_GROUP_READ, _BIND_MODE_GROUP_WRITE))
</ins><span class="cx">                 )
</span><span class="cx">             ).on(self._txn)
</span><del>-            for [shareeHomeUID] in rows:
</del><ins>+            for shareeHomeUID, shareeBindMode in rows:
+
</ins><span class="cx">                 if shareeHomeUID in memberUIDs:
</span><ins>+                    # Group sharee still referenced via a group - make a note of it
</ins><span class="cx">                     boundUIDs.add(shareeHomeUID)
</span><del>-                else:
</del><ins>+
+                elif shareeBindMode == _BIND_MODE_GROUP:
+                    # Group only sharee is no longer referenced by any group - uninvite them
</ins><span class="cx">                     yield self.uninviteUIDFromShare(shareeHomeUID)
</span><span class="cx">                     changed = True
</span><span class="cx"> 
</span><ins>+                else:
+                    # Group+individual sharee is no longer referenced by a group so update the bind
+                    # mode to reflect just the individual mode
+                    yield super(Calendar, self).inviteUIDToShare(
+                        shareeHomeUID,
+                        {
+                            _BIND_MODE_GROUP_READ: _BIND_MODE_READ,
+                            _BIND_MODE_GROUP_WRITE: _BIND_MODE_WRITE,
+                        }.get(shareeBindMode),
+                    )
+
</ins><span class="cx">             for memberUID in memberUIDs - boundUIDs:
</span><del>-                shareeView = yield self.shareeView(memberUID)
-                newMode = _BIND_MODE_GROUP if shareeView is None else shareeView._groupModeAfterAddingOneGroupSharee()
-                if newMode is not None:
-                    yield super(Calendar, self).inviteUIDToShare(memberUID, newMode)
-                    changed = True
</del><ins>+                # Never reconcile the sharer
+                if memberUID != self._home.uid():
+                    shareeView = yield self.shareeView(memberUID)
+                    newMode = _BIND_MODE_GROUP if shareeView is None else shareeView._groupModeAfterAddingOneGroupSharee()
+                    if newMode is not None:
+                        yield super(Calendar, self).inviteUIDToShare(memberUID, newMode)
+                        changed = True
</ins><span class="cx"> 
</span><span class="cx">         returnValue(changed)
</span><span class="cx"> 
</span><span class="lines">@@ -2502,6 +2524,24 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def ownerDeleteShare(self):
+        &quot;&quot;&quot;
+        This share is being deleted (by the owner) - we also need to clean up the group sharees.
+        &quot;&quot;&quot;
+
+        yield super(Calendar, self).ownerDeleteShare()
+
+        # Delete referenced group sharees. Note that whilst the table uses an on delete cascade,
+        # we do need to remove the sharees for the case where the calendar is trashed and not
+        # removed. Since the cascade is not triggered in that case and we have to do it by hand.
+        gs = schema.GROUP_SHAREE
+        yield Delete(
+            From=gs,
+            Where=(gs.CALENDAR_ID == self._resourceID),
+        ).on(self._txn)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def allInvitations(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get list of all invitations (non-direct) to this object.
</span><span class="lines">@@ -2568,11 +2608,8 @@
</span><span class="cx"> }
</span><span class="cx"> accesstype_to_accessMode = dict([(v, k) for k, v in accessMode_to_type.items()])
</span><span class="cx"> 
</span><del>-def _pathToName(path):
-    return path.rsplit(&quot;.&quot;, 1)[0]
</del><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> class CalendarObject(CommonObjectResource, CalendarObjectBase):
</span><span class="cx">     implements(ICalendarObject)
</span><span class="cx"> 
</span><span class="lines">@@ -2592,7 +2629,7 @@
</span><span class="cx">         self.scheduleTag = options.get(&quot;scheduleTag&quot;, &quot;&quot;)
</span><span class="cx">         self.scheduleEtags = options.get(&quot;scheduleEtags&quot;, &quot;&quot;)
</span><span class="cx">         self.hasPrivateComment = options.get(&quot;hasPrivateComment&quot;, False)
</span><del>-        self._dropboxID = None
</del><ins>+        self._dropboxID = options.get(&quot;dropboxID&quot;, None)
</ins><span class="cx"> 
</span><span class="cx">         # Component caching
</span><span class="cx">         self._cachedComponent = None
</span><span class="lines">@@ -2694,7 +2731,9 @@
</span><span class="cx"> 
</span><span class="cx">         # Possible timezone stripping
</span><span class="cx">         if config.EnableTimezonesByReference:
</span><del>-            component.stripStandardTimezones()
</del><ins>+            changed = component.stripStandardTimezones()
+            if changed:
+                self._componentChanged = True
</ins><span class="cx"> 
</span><span class="cx">         # Do validation on external requests
</span><span class="cx">         if internal_state == ComponentUpdateState.NORMAL:
</span><span class="lines">@@ -2747,6 +2786,7 @@
</span><span class="cx">             if attendeeProp.parameterValue(&quot;CUTYPE&quot;) == &quot;X-SERVER-GROUP&quot;
</span><span class="cx">         ])
</span><span class="cx"> 
</span><ins>+        # Map each group attendee to a list of potential member properties
</ins><span class="cx">         groupCUAToAttendeeMemberPropMap = {}
</span><span class="cx">         for groupCUA in groupCUAs:
</span><span class="cx"> 
</span><span class="lines">@@ -2805,6 +2845,8 @@
</span><span class="cx">             else:
</span><span class="cx">                 groupUID = uidFromCalendarUserAddress(groupCUA)
</span><span class="cx">             group = yield self._txn.groupByUID(groupUID)
</span><ins>+            if group is None:
+                continue
</ins><span class="cx"> 
</span><span class="cx">             if group.groupID in groupIDToMembershipHashMap:
</span><span class="cx">                 if groupIDToMembershipHashMap[group.groupID].membershipHash != group.membershipHash:
</span><span class="lines">@@ -3087,6 +3129,8 @@
</span><span class="cx">         comment (which will trigger scheduling with the organizer to remove the comment on the organizer's
</span><span class="cx">         side).
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+        changed = False
+
</ins><span class="cx">         if config.Scheduling.CalDAV.get(&quot;EnablePrivateComments&quot;, True):
</span><span class="cx">             old_has_private_comments = not inserting and self.hasPrivateComment
</span><span class="cx">             new_has_private_comments = component.hasPropertyInAnyComponent((
</span><span class="lines">@@ -3100,6 +3144,7 @@
</span><span class="cx">                 component.transferProperties(old_calendar, (
</span><span class="cx">                     &quot;X-CALENDARSERVER-ATTENDEE-COMMENT&quot;,
</span><span class="cx">                 ))
</span><ins>+                changed = True
</ins><span class="cx"> 
</span><span class="cx">             self.hasPrivateComment = new_has_private_comments
</span><span class="cx"> 
</span><span class="lines">@@ -3110,7 +3155,9 @@
</span><span class="cx">             if component.hasDuplicatePrivateComments(doFix=config.RemoveDuplicatePrivateComments) and internal_state == ComponentUpdateState.NORMAL:
</span><span class="cx">                 raise DuplicatePrivateCommentsError(&quot;Duplicate X-CALENDARSERVER-ATTENDEE-COMMENT properties present.&quot;)
</span><span class="cx"> 
</span><ins>+        returnValue(changed)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def replaceMissingToDoProperties(self, calendar, inserting, internal_state):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -3135,7 +3182,10 @@
</span><span class="cx"> 
</span><span class="cx">                 # Get the originator who is the owner of the calendar resource being modified
</span><span class="cx">                 originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
</span><del>-                originatorAddresses = originatorPrincipal.calendarUserAddresses
</del><ins>+                if originatorPrincipal is not None:
+                    originatorAddresses = originatorPrincipal.calendarUserAddresses
+                else:
+                    originatorAddresses = (&quot;urn:x-uid:{}&quot;.format(self.calendar().ownerHome().uid),)
</ins><span class="cx"> 
</span><span class="cx">                 for component in calendar.subcomponents():
</span><span class="cx">                     if component.name() != &quot;VTODO&quot;:
</span><span class="lines">@@ -3172,7 +3222,10 @@
</span><span class="cx"> 
</span><span class="cx">                 # Get the originator who is the owner of the calendar resource being modified
</span><span class="cx">                 originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
</span><del>-                originatorAddresses = originatorPrincipal.calendarUserAddresses
</del><ins>+                if originatorPrincipal is not None:
+                    originatorAddresses = originatorPrincipal.calendarUserAddresses
+                else:
+                    originatorAddresses = (&quot;urn:x-uid:{}&quot;.format(self.calendar().ownerHome().uid),)
</ins><span class="cx"> 
</span><span class="cx">                 for component in calendar.subcomponents():
</span><span class="cx">                     if component.name() != &quot;VTODO&quot;:
</span><span class="lines">@@ -3296,14 +3349,39 @@
</span><span class="cx">         Scan the component for ROOM attendees; if any are associated with an
</span><span class="cx">         address record which has street address and geo coordinates, add an
</span><span class="cx">         X-APPLE-STRUCTURED-LOCATION property and update the LOCATION property
</span><del>-        to contain the name and street address.
</del><ins>+        to contain the name and street address.  X-APPLE-STRUCTURED-LOCATION
+        with X-CUADDR but no corresponding ATTENDEE are removed.
</ins><span class="cx">         &quot;&quot;&quot;
</span><ins>+        dir = self.directoryService()
</ins><span class="cx"> 
</span><ins>+        changed = False
</ins><span class="cx">         cache = {}
</span><del>-        dir = self.directoryService()
</del><ins>+
</ins><span class="cx">         for sub in component.subcomponents():
</span><del>-            locations = []
-            removed = False
</del><ins>+            existingLocationProps = list(sub.properties(&quot;LOCATION&quot;))
+            if len(existingLocationProps) == 0:
+                existingLocationValue = &quot;&quot;
+            else:
+                existingLocationValue = existingLocationProps[0].value()
+            existingLocations = []
+            for value in existingLocationValue.split(&quot;;&quot;):
+                if value:
+                    existingLocations.append(value.strip())
+
+            # index the structured locations on X-CUADDR and X-TITLE
+            allStructured = {
+                &quot;cua&quot;: {},
+                &quot;title&quot;: {}
+            }
+            for structured in sub.properties(&quot;X-APPLE-STRUCTURED-LOCATION&quot;):
+                cuAddr = structured.parameterValue(&quot;X-CUADDR&quot;)
+                if cuAddr:
+                    allStructured[&quot;cua&quot;][cuAddr] = structured
+                else:
+                    title = structured.parameterValue(&quot;X-TITLE&quot;)
+                    if title:
+                        allStructured[&quot;title&quot;][title] = structured
+
</ins><span class="cx">             for attendee in sub.getAllAttendeeProperties():
</span><span class="cx">                 if attendee.parameterValue(&quot;CUTYPE&quot;) == &quot;ROOM&quot;:
</span><span class="cx">                     value = attendee.value()
</span><span class="lines">@@ -3326,34 +3404,96 @@
</span><span class="cx">                     # Use the cached data if present
</span><span class="cx">                     entry = cache[value]
</span><span class="cx">                     if entry is not None:
</span><ins>+
</ins><span class="cx">                         street, geo, title = entry
</span><ins>+                        newLocationValue = &quot;{0}\n{1}&quot;.format(title, street.encode(&quot;utf-8&quot;))
+
+                        # Is there already a structured location property for
+                        # this attendee?  If so, we'll update it.
+                        # Unfortunately, we can only depend on X-CUADDR
+                        # going forward, but there is going to be old existing
+                        # X-APPLE-STRUCTURED-LOCATIONs that haven't yet had
+                        # those added.  So let's first look up by X-CUADDR and
+                        # then by X-TITLE.
+                        if value in allStructured[&quot;cua&quot;]:
+                            structured = allStructured[&quot;cua&quot;][value]
+                        elif title in allStructured[&quot;title&quot;]:
+                            structured = allStructured[&quot;title&quot;][title]
+                        else:
+                            structured = None
+
</ins><span class="cx">                         params = {
</span><span class="cx">                             &quot;X-ADDRESS&quot;: street,
</span><span class="cx">                             &quot;X-APPLE-RADIUS&quot;: &quot;71&quot;,
</span><span class="cx">                             &quot;X-TITLE&quot;: title,
</span><ins>+                            &quot;X-CUADDR&quot;: value,
</ins><span class="cx">                         }
</span><del>-                        structured = Property(
-                            &quot;X-APPLE-STRUCTURED-LOCATION&quot;,
-                            geo.encode(&quot;utf-8&quot;), params=params,
-                            valuetype=Value.VALUETYPE_URI
-                        )
</del><span class="cx"> 
</span><del>-                        # The first time we have any X- prop, remove all existing ones
-                        if not removed:
-                            sub.removeProperty(&quot;X-APPLE-STRUCTURED-LOCATION&quot;)
-                            removed = True
-                        sub.addProperty(structured)
-                        locations.append(&quot;{0}\n{1}&quot;.format(title, street.encode(&quot;utf-8&quot;)))
</del><ins>+                        if structured is None:
+                            # Create a new one
+                            prevTitle = attendee.parameterValue(&quot;CN&quot;)
+                            structured = Property(
+                                &quot;X-APPLE-STRUCTURED-LOCATION&quot;,
+                                geo.encode(&quot;utf-8&quot;), params=params,
+                                valuetype=Value.VALUETYPE_URI
+                            )
+                            changed = True
+                            sub.addProperty(structured)
+                        else:
+                            # Update existing one
+                            prevGeo = structured.value()
+                            if geo != prevGeo:
+                                structured.setValue(geo)
+                                changed = True
+                            prevTitle = structured.parameterValue(&quot;X-TITLE&quot;)
+                            for paramName, paramValue in params.iteritems():
+                                prevValue = structured.parameterValue(paramName)
+                                if paramValue != prevValue:
+                                    structured.setParameter(paramName, paramValue)
+                                    changed = True
</ins><span class="cx"> 
</span><del>-            # Update the LOCATION if X-'s were added
-            if locations:
</del><ins>+                        if changed:
+                            # Replace old location values with the new ones
+                            for i in xrange(len(existingLocations)):
+                                existingLocation = existingLocations[i]
+                                if (
+                                    prevTitle is not None and
+                                    (
+                                        # it's either an exact match or matches
+                                        # up to the newline which precedes the
+                                        # street address
+                                        existingLocation == prevTitle or
+                                        existingLocation.startswith(&quot;{}\n&quot;.format(prevTitle))
+                                    )
+                                ):
+                                    existingLocations[i] = newLocationValue
+                                    break
+                            else:
+                                existingLocations.append(newLocationValue)
+
+            # Remove any server-generated structured locations without an ATTENDEE
+            for structured in sub.properties(&quot;X-APPLE-STRUCTURED-LOCATION&quot;):
+                cuAddr = structured.parameterValue(&quot;X-CUADDR&quot;)
+                if cuAddr is not None: # therefore it's one that requires an ATTENDEE...
+                    attendeeProp = sub.getAttendeeProperty((cuAddr,))
+                    if attendeeProp is None: # ...remove it if no matching ATTENDEE
+                        sub.removeProperty(structured)
+                        changed = True
+
+            # Update the LOCATION
+            newLocationValue = &quot;;&quot;.join(existingLocations)
+            if newLocationValue != existingLocationValue:
</ins><span class="cx">                 newLocProperty = Property(
</span><span class="cx">                     &quot;LOCATION&quot;,
</span><del>-                    &quot;; &quot;.join(locations)
</del><ins>+                    newLocationValue
</ins><span class="cx">                 )
</span><span class="cx">                 sub.replaceProperty(newLocProperty)
</span><ins>+                changed = True
</ins><span class="cx"> 
</span><ins>+        if changed:
+            self._componentChanged = True
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def decorateHostedStatus(self, component):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -3649,9 +3789,13 @@
</span><span class="cx">             yield self._lockAndCheckUID(component, inserting, internal_state)
</span><span class="cx"> 
</span><span class="cx">             # Preserve private comments
</span><del>-            yield self.preservePrivateComments(component, inserting, internal_state)
</del><ins>+            changed = yield self.preservePrivateComments(component, inserting, internal_state)
+            if changed:
+                self._componentChanged = True
</ins><span class="cx"> 
</span><span class="cx">             # Fix broken VTODOs
</span><ins>+            # Note: if this does recover lost organizer/attendee properties, the
+            # implicit code below will set _componentChanged
</ins><span class="cx">             yield self.replaceMissingToDoProperties(component, inserting, internal_state)
</span><span class="cx"> 
</span><span class="cx">             # Handle sharing dropbox normalization
</span><span class="lines">@@ -5071,6 +5215,19 @@
</span><span class="cx">         if organizer is not None and organizerAddress.record.uid != self.calendar().ownerHome().uid():
</span><span class="cx">             raise InvalidSplit(&quot;Only organizers can split events.&quot;)
</span><span class="cx"> 
</span><ins>+        # rid value type must match
+        dtstart = component.mainComponent().propertyValue(&quot;DTSTART&quot;)
+        if dtstart.isDateOnly():
+            if not rid.isDateOnly():
+                # We ought to reject this but for now we will fix it
+                rid.setDateOnly(True)
+        elif dtstart.floating():
+            if not rid.floating() or rid.isDateOnly():
+                raise InvalidSplit(&quot;rid parameter value type must match DTSTART value type.&quot;)
+        else:
+            if rid.floating():
+                raise InvalidSplit(&quot;rid parameter value type must match DTSTART value type.&quot;)
+
</ins><span class="cx">         # Determine valid split point
</span><span class="cx">         splitter = iCalSplitter(1024, 14)
</span><span class="cx">         rid = splitter.whereSplit(component, break_point=rid, allow_past_the_end=False)
</span><span class="lines">@@ -5159,6 +5316,7 @@
</span><span class="cx">             olderResourceName,
</span><span class="cx">             calendar_old,
</span><span class="cx">             ComponentUpdateState.SPLIT_OWNER,
</span><ins>+            options={&quot;dropboxID&quot;: olderUID},
</ins><span class="cx">             split_details=(rid, newerUID, False, False)
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -5217,7 +5375,12 @@
</span><span class="cx"> 
</span><span class="cx">         # Create a new resource and store its data (but not if the parent is &quot;inbox&quot;, or if it is empty)
</span><span class="cx">         if not self.calendar().isInbox() and ical_old.mainType() is not None:
</span><del>-            olderObject = yield self.calendar()._createCalendarObjectWithNameInternal(&quot;{0}.ics&quot;.format(olderUID,), ical_old, ComponentUpdateState.SPLIT_ATTENDEE)
</del><ins>+            olderObject = yield self.calendar()._createCalendarObjectWithNameInternal(
+                &quot;{0}.ics&quot;.format(olderUID,),
+                ical_old,
+                ComponentUpdateState.SPLIT_ATTENDEE,
+                options={&quot;dropboxID&quot;: olderUID},
+            )
</ins><span class="cx"> 
</span><span class="cx">             # Reconcile trash state
</span><span class="cx">             if self.isInTrash():
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/sql_external.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/sql_external.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/sql_external.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -255,15 +255,15 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def addAttachment(self, rids, content_type, filename, stream):
</span><span class="cx">         result = yield self._txn.store().conduit.send_add_attachment(self, rids, content_type, filename, stream)
</span><del>-        managedID, location = result
-        returnValue((ManagedAttachmentExternal(str(managedID)), str(location),))
</del><ins>+        managedID, size, location = result
+        returnValue((ManagedAttachmentExternal(str(managedID), size), str(location),))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def updateAttachment(self, managed_id, content_type, filename, stream):
</span><span class="cx">         result = yield self._txn.store().conduit.send_update_attachment(self, managed_id, content_type, filename, stream)
</span><del>-        managedID, location = result
-        returnValue((ManagedAttachmentExternal(str(managedID)), str(location),))
</del><ins>+        managedID, size, location = result
+        returnValue((ManagedAttachmentExternal(str(managedID), size), str(location),))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -279,12 +279,17 @@
</span><span class="cx">     L{CalendarObjectExternal.updateAttachment}.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    def __init__(self, managedID):
</del><ins>+    def __init__(self, managedID, size):
</ins><span class="cx">         self._managedID = managedID
</span><ins>+        self._size = size
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def managedID(self):
</span><span class="cx">         return self._managedID
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def size(self):
+        return self._size
+
+
</ins><span class="cx"> CalendarExternal._objectResourceClass = CalendarObjectExternal
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretestcalendar_storehomehome_badcalendar_bad1ics"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/calendar_store/ho/me/home_bad/calendar_bad/1.ics (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/calendar_store/ho/me/home_bad/calendar_bad/1.ics        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/calendar_store/ho/me/home_bad/calendar_bad/1.ics        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -20,11 +20,11 @@
</span><span class="cx"> END:STANDARD
</span><span class="cx"> END:VTIMEZONE
</span><span class="cx"> BEGIN:VEVENT
</span><del>-DTEND;TZID=US/Pacific:20000324T124500
</del><ins>+DTEND;TZID=US/Pacific:%(now-1)s0324T124500
</ins><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20090326T145447Z
</span><span class="cx"> SUMMARY:Busted
</span><del>-DTSTART;TZID=US/Pacific:20000324T121500
</del><ins>+DTSTART;TZID=US/Pacific:%(now-1)s0324T121500
</ins><span class="cx"> CREATED:20090326T145440Z
</span><span class="cx"> RRULE:FREQ=HOURLY
</span><span class="cx"> END:VEVENT
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretesttest_attachmentspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_attachments.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_attachments.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_attachments.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -1689,7 +1689,7 @@
</span><span class="cx">         for attach in attachments:
</span><span class="cx">             if attach.hasParameter(&quot;MANAGED-ID&quot;):
</span><span class="cx">                 managed_count += 1
</span><del>-                self.assertTrue(attach.value().find(&quot;/dropbox/&quot;) == -1)
</del><ins>+                self.assertTrue(attach.value().find(&quot;/dropbox/&quot;) != -1)
</ins><span class="cx">                 self.assertTrue(attach.parameterValue(&quot;FILENAME&quot;) in filenames)
</span><span class="cx">             else:
</span><span class="cx">                 dropbox_count += 1
</span><span class="lines">@@ -1869,7 +1869,7 @@
</span><span class="cx">         for attach in attachments:
</span><span class="cx">             if attach.hasParameter(&quot;MANAGED-ID&quot;):
</span><span class="cx">                 managed_count += 1
</span><del>-                self.assertTrue(attach.value().find(&quot;1.2.dropbox&quot;) == -1)
</del><ins>+                self.assertTrue(attach.value().find(&quot;1.2.dropbox&quot;) != -1)
</ins><span class="cx">                 self.assertEqual(attach.parameterValue(&quot;MANAGED-ID&quot;), mnew.managedID())
</span><span class="cx">                 self.assertEqual(attach.parameterValue(&quot;FILENAME&quot;), mnew.name())
</span><span class="cx">             else:
</span><span class="lines">@@ -1907,7 +1907,7 @@
</span><span class="cx">         for attach in attachments:
</span><span class="cx">             if attach.hasParameter(&quot;MANAGED-ID&quot;):
</span><span class="cx">                 managed_count += 1
</span><del>-                self.assertTrue(attach.value().find(&quot;1.2.dropbox&quot;) == -1)
</del><ins>+                self.assertTrue(attach.value().find(&quot;1.2.dropbox&quot;) != -1)
</ins><span class="cx">                 self.assertTrue(attach.parameterValue(&quot;FILENAME&quot;) in (&quot;attach_1_2_1.txt&quot;, &quot;attach_1_2_2.txt&quot;))
</span><span class="cx">             else:
</span><span class="cx">                 dropbox_count += 1
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretesttest_filepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_file.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_file.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_file.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -66,14 +66,19 @@
</span><span class="cx">     storePath.copyTo(calendarPath)
</span><span class="cx"> 
</span><span class="cx">     # Set year values to current year
</span><ins>+    subs = {}
</ins><span class="cx">     nowYear = DateTime.getToday().getYear()
</span><ins>+    subs[&quot;now&quot;] = nowYear
+    for i in range(1, 10):
+        subs[&quot;now-{}&quot;.format(i)] = nowYear - 1
+        subs[&quot;now+{}&quot;.format(i)] = nowYear + 1
</ins><span class="cx">     for home in calendarPath.child(&quot;ho&quot;).child(&quot;me&quot;).children():
</span><span class="cx">         if not home.basename().startswith(&quot;.&quot;):
</span><span class="cx">             for calendar in home.children():
</span><span class="cx">                 if not calendar.basename().startswith(&quot;.&quot;):
</span><span class="cx">                     for resource in calendar.children():
</span><span class="cx">                         if resource.basename().endswith(&quot;.ics&quot;):
</span><del>-                            resource.setContent(resource.getContent() % {&quot;now&quot;: nowYear})
</del><ins>+                            resource.setContent(resource.getContent() % subs)
</ins><span class="cx"> 
</span><span class="cx">     testID = test.id()
</span><span class="cx">     test.counter = 0
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretesttest_queue_schedulingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_queue_scheduling.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_queue_scheduling.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_queue_scheduling.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -19,11 +19,10 @@
</span><span class="cx"> from twext.python.clsprop import classproperty
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from txdav.caldav.datastore.scheduling.work import ScheduleWorkMixin
</span><del>-from txdav.caldav.datastore.test.util import CommonStoreTests
-from txdav.common.datastore.test.util import componentUpdate
-from twistedcaldav.ical import normalize_iCalStr
</del><ins>+from txdav.caldav.datastore.test.util import CommonStoreTests, \
+    DateTimeSubstitutionsMixin
</ins><span class="cx"> 
</span><del>-class BaseQueueSchedulingTests(CommonStoreTests):
</del><ins>+class BaseQueueSchedulingTests(CommonStoreTests, DateTimeSubstitutionsMixin):
</ins><span class="cx"> 
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test store-based calendar sharing.
</span><span class="lines">@@ -41,7 +40,9 @@
</span><span class="cx">         self.patch(config.Scheduling.Options.WorkQueues, &quot;AttendeeRefreshBatchDelaySeconds&quot;, 1)
</span><span class="cx">         self.patch(config.Scheduling.Options.WorkQueues, &quot;AttendeeRefreshBatchIntervalSeconds&quot;, 1)
</span><span class="cx"> 
</span><ins>+        self.setupDateTimeValues()
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @classproperty(cache=False)
</span><span class="cx">     def requirements(cls): #@NoSelf
</span><span class="cx">         return {
</span><span class="lines">@@ -87,7 +88,7 @@
</span><span class="cx">         self.assertEqual(len(objs), 1)
</span><span class="cx"> 
</span><span class="cx">         caldata = yield objs[0].componentForUser()
</span><del>-        self.assertEqual(normalize_iCalStr(caldata), normalize_iCalStr(componentUpdate(data)))
</del><ins>+        self.assertEqualCalendarData(caldata, data.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -104,7 +105,7 @@
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:12345-67890
</span><del>-DTSTART:{now}T000000Z
</del><ins>+DTSTART:{nowDate}T000000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01@example.com
</span><span class="cx"> ATTENDEE:mailto:user02@example.com
</span><span class="lines">@@ -120,7 +121,7 @@
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:12345-67890
</span><del>-DTSTART:{now}T000000Z
</del><ins>+DTSTART:{nowDate}T000000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user02
</span><span class="lines">@@ -137,7 +138,7 @@
</span><span class="cx"> METHOD:REQUEST
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:12345-67890
</span><del>-DTSTART:{now}T000000Z
</del><ins>+DTSTART:{nowDate}T000000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="lines">@@ -153,7 +154,7 @@
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:12345-67890
</span><del>-DTSTART:{now}T000000Z
</del><ins>+DTSTART:{nowDate}T000000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="lines">@@ -170,7 +171,7 @@
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:12345-67890
</span><del>-DTSTART:{now}T000000Z
</del><ins>+DTSTART:{nowDate}T000000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user02
</span><span class="lines">@@ -186,7 +187,7 @@
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:12345-67890
</span><del>-DTSTART:{now}T000000Z
</del><ins>+DTSTART:{nowDate}T000000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:urn:x-uid:user02
</span><span class="lines">@@ -203,7 +204,7 @@
</span><span class="cx"> METHOD:REPLY
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:12345-67890
</span><del>-DTSTART:{now}T000000Z
</del><ins>+DTSTART:{nowDate}T000000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user02
</span><span class="cx"> DTSTAMP:20051222T210507Z
</span><span class="lines">@@ -219,7 +220,7 @@
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:12345-67890
</span><del>-DTSTART:{now}T000000Z
</del><ins>+DTSTART:{nowDate}T000000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user02
</span><span class="lines">@@ -232,7 +233,7 @@
</span><span class="cx"> 
</span><span class="cx">         waitForWork = ScheduleWorkMixin.allDone()
</span><span class="cx">         calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><del>-        yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, componentUpdate(data1))
</del><ins>+        yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, data1.format(**self.dtsubs))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         yield waitForWork
</span><span class="lines">@@ -244,7 +245,7 @@
</span><span class="cx"> 
</span><span class="cx">         waitForWork = ScheduleWorkMixin.allDone()
</span><span class="cx">         cobj = yield self._getOneResource(&quot;user02&quot;, &quot;calendar&quot;)
</span><del>-        yield cobj.setComponent(componentUpdate(data5))
</del><ins>+        yield cobj.setComponent(data5.format(**self.dtsubs))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         yield waitForWork
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_sql.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_sql.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/test_sql.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -21,6 +21,7 @@
</span><span class="cx"> 
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="cx"> from pycalendar.timezone import Timezone
</span><ins>+from pycalendar.value import Value
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> from txweb2 import responsecode
</span><span class="lines">@@ -38,7 +39,7 @@
</span><span class="cx"> from twistedcaldav.caldavxml import CalendarDescription
</span><span class="cx"> from twistedcaldav.stdconfig import config
</span><span class="cx"> from twistedcaldav.dateops import datetimeMktime
</span><del>-from twistedcaldav.ical import Component, normalize_iCalStr, diff_iCalStrs
</del><ins>+from twistedcaldav.ical import Component, normalize_iCalStr, diff_iCalStrs, Property
</ins><span class="cx"> from twistedcaldav.instance import InvalidOverriddenInstanceError
</span><span class="cx"> from twistedcaldav.timezones import TimezoneCache, readVTZ, TimezoneException
</span><span class="cx"> 
</span><span class="lines">@@ -55,12 +56,13 @@
</span><span class="cx"> from txdav.common.datastore.sql import ECALENDARTYPE, CommonObjectResource, \
</span><span class="cx">     CommonStoreTransactionMonitor
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema, _BIND_MODE_DIRECT, \
</span><del>-    _BIND_STATUS_ACCEPTED, _TRANSP_OPAQUE
</del><ins>+    _BIND_STATUS_ACCEPTED, _TRANSP_OPAQUE, _BIND_MODE_WRITE
</ins><span class="cx"> from txdav.caldav.datastore.test.common import CommonTests as CalendarCommonTests, \
</span><del>-    test_event_text
</del><ins>+    test_event_text, cal1Root, OTHER_HOME_UID
</ins><span class="cx"> from txdav.caldav.datastore.test.test_file import setUpCalendarStore
</span><ins>+from txdav.caldav.datastore.test.util import DateTimeSubstitutionsMixin
</ins><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom, \
</span><del>-    CommonCommonTests
</del><ins>+    CommonCommonTests, updateToCurrentYear
</ins><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">     InvalidSplit, UnknownTimezone
</span><span class="lines">@@ -1912,117 +1914,6 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def test_calendarRevisionChangeConcurrency(self):
-        &quot;&quot;&quot;
-        Test that two concurrent attempts to add resources in two separate
-        calendar homes does not deadlock on the revision table update.
-        &quot;&quot;&quot;
-
-        calendarStore = self._sqlCalendarStore
-
-        # Make sure homes are provisioned
-        txn = self.transactionUnderTest()
-        home_uid1 = yield txn.homeWithUID(ECALENDARTYPE, &quot;user01&quot;, create=True)
-        home_uid2 = yield txn.homeWithUID(ECALENDARTYPE, &quot;user02&quot;, create=True)
-        self.assertNotEqual(home_uid1, None)
-        self.assertNotEqual(home_uid2, None)
-        yield self.commit()
-
-        # Create first events in different calendar homes
-        txn1 = calendarStore.newTransaction()
-        txn2 = calendarStore.newTransaction()
-
-        calendar_uid1_in_txn1 = yield self.calendarUnderTest(txn1, &quot;calendar&quot;, &quot;user01&quot;)
-        calendar_uid2_in_txn2 = yield self.calendarUnderTest(txn2, &quot;calendar&quot;, &quot;user02&quot;)
-
-        data = &quot;&quot;&quot;BEGIN:VCALENDAR
-VERSION:2.0
-CALSCALE:GREGORIAN
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:data%(ctr)s
-DTSTART:20130102T140000Z
-DURATION:PT1H
-CREATED:20060102T190000Z
-DTSTAMP:20051222T210507Z
-SUMMARY:data%(ctr)s
-END:VEVENT
-END:VCALENDAR
-&quot;&quot;&quot;
-
-        component = Component.fromString(data % {&quot;ctr&quot;: 1})
-        yield calendar_uid1_in_txn1.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
-
-        component = Component.fromString(data % {&quot;ctr&quot;: 2})
-        yield calendar_uid2_in_txn2.createCalendarObjectWithName(&quot;data2.ics&quot;, component)
-
-        # Setup deferreds to run concurrently and create second events in the calendar homes
-        # previously used by the other transaction - this could create the deadlock.
-        @inlineCallbacks
-        def _defer_uid3():
-            calendar_uid1_in_txn2 = yield self.calendarUnderTest(txn2, &quot;calendar&quot;, &quot;user01&quot;)
-            component = Component.fromString(data % {&quot;ctr&quot;: 3})
-            yield calendar_uid1_in_txn2.createCalendarObjectWithName(&quot;data3.ics&quot;, component)
-            yield txn2.commit()
-        d1 = _defer_uid3()
-
-        @inlineCallbacks
-        def _defer_uid4():
-            calendar_uid2_in_txn1 = yield self.calendarUnderTest(txn1, &quot;calendar&quot;, &quot;user02&quot;)
-            component = Component.fromString(data % {&quot;ctr&quot;: 4})
-            yield calendar_uid2_in_txn1.createCalendarObjectWithName(&quot;data4.ics&quot;, component)
-            yield txn1.commit()
-        d2 = _defer_uid4()
-
-        # Now do the concurrent provision attempt
-        yield DeferredList([d1, d2])
-
-        # Verify we did not have a deadlock and all resources have been created.
-        caldata1 = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
-        caldata2 = yield self.calendarObjectUnderTest(name=&quot;data2.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
-        caldata3 = yield self.calendarObjectUnderTest(name=&quot;data3.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
-        caldata4 = yield self.calendarObjectUnderTest(name=&quot;data4.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
-        self.assertNotEqual(caldata1, None)
-        self.assertNotEqual(caldata2, None)
-        self.assertNotEqual(caldata3, None)
-        self.assertNotEqual(caldata4, None)
-
-
-    @inlineCallbacks
-    def test_calendarMissingRevision(self):
-        &quot;&quot;&quot;
-        Test that two concurrent attempts to add resources in two separate
-        calendar homes does not deadlock on the revision table update.
-        &quot;&quot;&quot;
-
-        # Get details
-        home = yield self.homeUnderTest(name=&quot;user01&quot;, create=True)
-        self.assertNotEqual(home, None)
-        calendar = yield home.childWithName(&quot;calendar&quot;)
-        self.assertNotEqual(calendar, None)
-
-        rev = calendar._revisionsSchema
-        yield Delete(
-            From=rev,
-            Where=(
-                rev.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)).And(
-                rev.COLLECTION_NAME == Parameter(&quot;collectionName&quot;)
-            )
-        ).on(self.transactionUnderTest(), homeID=home.id(), collectionName=&quot;calendar&quot;)
-
-        yield self.commit()
-
-        home = yield self.homeUnderTest(name=&quot;user01&quot;)
-        children = yield home.loadChildren()
-        self.assertEqual(len(children), 3)
-        yield self.commit()
-
-        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
-        token = yield calendar.syncToken()
-        self.assertTrue(token is not None)
-
-
-    @inlineCallbacks
</del><span class="cx">     def test_inboxTransp(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Make sure inbox is always transparent no matter what is stored in the DB.
</span><span class="lines">@@ -2319,6 +2210,228 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_sharedTasksMissingSharer(self):
+        &quot;&quot;&quot;
+        Make sure that a sharee can store tasks when the sharer has been removed from
+        the directory.
+        &quot;&quot;&quot;
+        home = yield self.homeUnderTest()
+        cal = yield home.createCalendarWithName(&quot;shared_tasks&quot;)
+        yield cal.setSupportedComponents(&quot;VTODO&quot;)
+        yield self.commit()
+
+        cal = yield self.calendarUnderTest(name=&quot;shared_tasks&quot;)
+        other = yield self.homeUnderTest(name=OTHER_HOME_UID)
+        newCalName = yield cal.shareWith(other, _BIND_MODE_WRITE)
+        self.sharedName = newCalName
+        yield self.commit()
+
+        cal = yield self.calendarUnderTest(name=&quot;shared_tasks&quot;)
+        yield cal.createCalendarObjectWithName(&quot;data1.ics&quot;, Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTODO
+UID:12345-67890-attendee-reply
+DTSTAMP:20080601T120000Z
+DTSTART:20080601T120000Z
+SUMMARY:original
+END:VTODO
+END:VCALENDAR
+&quot;&quot;&quot;))
+        yield self.commit()
+
+        yield self._sqlCalendarStore.directoryService().removeRecords(((yield self.userUIDFromShortName(&quot;home1&quot;)),))
+
+        cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=self.sharedName, home=OTHER_HOME_UID)
+        yield cobj.setComponent(Component.fromString(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTODO
+UID:12345-67890-attendee-reply
+DTSTAMP:20080601T120000Z
+DTSTART:20080601T120000Z
+COMPLETED:20080601T130000Z
+SUMMARY:changed
+END:VTODO
+END:VCALENDAR
+&quot;&quot;&quot;))
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=self.sharedName, home=OTHER_HOME_UID)
+        comp = yield cobj.componentForUser()
+        self.assertEqual(normalize_iCalStr(comp), normalize_iCalStr(&quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VTODO
+UID:12345-67890-attendee-reply
+DTSTAMP:20080601T120000Z
+DTSTART:20080601T120000Z
+COMPLETED:20080601T130000Z
+SUMMARY:changed
+END:VTODO
+END:VCALENDAR
+&quot;&quot;&quot;))
+        yield self.commit()
+
+
+
+class SyncTests(CommonCommonTests, unittest.TestCase):
+    &quot;&quot;&quot;
+    Revision table/sync report tests.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(SyncTests, self).setUp()
+        yield self.buildStoreAndDirectory()
+        yield self.populate()
+
+
+    requirements = {
+        &quot;user01&quot;: {
+            &quot;calendar&quot;: {
+                &quot;1.ics&quot;: (cal1Root.child(&quot;1.ics&quot;).getContent(), CalendarCommonTests.metadata1),
+                &quot;2.ics&quot;: (cal1Root.child(&quot;2.ics&quot;).getContent(), CalendarCommonTests.metadata2),
+                &quot;3.ics&quot;: (cal1Root.child(&quot;3.ics&quot;).getContent(), CalendarCommonTests.metadata3),
+                &quot;4.ics&quot;: (cal1Root.child(&quot;4.ics&quot;).getContent(), CalendarCommonTests.metadata4),
+                &quot;5.ics&quot;: (cal1Root.child(&quot;5.ics&quot;).getContent(), CalendarCommonTests.metadata5),
+            },
+        },
+    }
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+
+    def token2revision(self, token):
+        &quot;&quot;&quot;
+        FIXME: the API names for L{syncToken}() and L{resourceNamesSinceToken}()
+        are slightly inaccurate; one doesn't produce input for the other.
+        Actually it should be resource names since I{revision} and you need to
+        understand the structure of the tokens to extract the revision.  Right
+        now that logic lives in the protocol layer, so this testing method
+        replicates it.
+        &quot;&quot;&quot;
+        _ignore_uuid, rev = token.split(&quot;_&quot;, 1)
+        rev = int(rev)
+        return rev
+
+
+    @inlineCallbacks
+    def test_calendarRevisionChangeConcurrency(self):
+        &quot;&quot;&quot;
+        Test that two concurrent attempts to add resources in two separate
+        calendar homes does not deadlock on the revision table update.
+        &quot;&quot;&quot;
+
+        calendarStore = self._sqlCalendarStore
+
+        # Make sure homes are provisioned
+        txn = self.transactionUnderTest()
+        home_uid1 = yield txn.homeWithUID(ECALENDARTYPE, &quot;user01&quot;, create=True)
+        home_uid2 = yield txn.homeWithUID(ECALENDARTYPE, &quot;user02&quot;, create=True)
+        self.assertNotEqual(home_uid1, None)
+        self.assertNotEqual(home_uid2, None)
+        yield self.commit()
+
+        # Create first events in different calendar homes
+        txn1 = calendarStore.newTransaction()
+        txn2 = calendarStore.newTransaction()
+
+        calendar_uid1_in_txn1 = yield self.calendarUnderTest(txn1, &quot;calendar&quot;, &quot;user01&quot;)
+        calendar_uid2_in_txn2 = yield self.calendarUnderTest(txn2, &quot;calendar&quot;, &quot;user02&quot;)
+
+        data = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:data%(ctr)s
+DTSTART:20130102T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:data%(ctr)s
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        component = Component.fromString(data % {&quot;ctr&quot;: 1})
+        yield calendar_uid1_in_txn1.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
+
+        component = Component.fromString(data % {&quot;ctr&quot;: 2})
+        yield calendar_uid2_in_txn2.createCalendarObjectWithName(&quot;data2.ics&quot;, component)
+
+        # Setup deferreds to run concurrently and create second events in the calendar homes
+        # previously used by the other transaction - this could create the deadlock.
+        @inlineCallbacks
+        def _defer_uid3():
+            calendar_uid1_in_txn2 = yield self.calendarUnderTest(txn2, &quot;calendar&quot;, &quot;user01&quot;)
+            component = Component.fromString(data % {&quot;ctr&quot;: 3})
+            yield calendar_uid1_in_txn2.createCalendarObjectWithName(&quot;data3.ics&quot;, component)
+            yield txn2.commit()
+        d1 = _defer_uid3()
+
+        @inlineCallbacks
+        def _defer_uid4():
+            calendar_uid2_in_txn1 = yield self.calendarUnderTest(txn1, &quot;calendar&quot;, &quot;user02&quot;)
+            component = Component.fromString(data % {&quot;ctr&quot;: 4})
+            yield calendar_uid2_in_txn1.createCalendarObjectWithName(&quot;data4.ics&quot;, component)
+            yield txn1.commit()
+        d2 = _defer_uid4()
+
+        # Now do the concurrent provision attempt
+        yield DeferredList([d1, d2])
+
+        # Verify we did not have a deadlock and all resources have been created.
+        caldata1 = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        caldata2 = yield self.calendarObjectUnderTest(name=&quot;data2.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
+        caldata3 = yield self.calendarObjectUnderTest(name=&quot;data3.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        caldata4 = yield self.calendarObjectUnderTest(name=&quot;data4.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
+        self.assertNotEqual(caldata1, None)
+        self.assertNotEqual(caldata2, None)
+        self.assertNotEqual(caldata3, None)
+        self.assertNotEqual(caldata4, None)
+
+
+    @inlineCallbacks
+    def test_calendarMissingRevision(self):
+        &quot;&quot;&quot;
+        Test that two concurrent attempts to add resources in two separate
+        calendar homes does not deadlock on the revision table update.
+        &quot;&quot;&quot;
+
+        # Get details
+        home = yield self.homeUnderTest(name=&quot;user02&quot;, create=True)
+        self.assertNotEqual(home, None)
+        calendar = yield home.childWithName(&quot;calendar&quot;)
+        self.assertNotEqual(calendar, None)
+
+        rev = calendar._revisionsSchema
+        yield Delete(
+            From=rev,
+            Where=(
+                rev.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)).And(
+                rev.COLLECTION_NAME == Parameter(&quot;collectionName&quot;)
+            )
+        ).on(self.transactionUnderTest(), homeID=home.id(), collectionName=&quot;calendar&quot;)
+
+        yield self.commit()
+
+        home = yield self.homeUnderTest(name=&quot;user02&quot;)
+        children = yield home.loadChildren()
+        self.assertEqual(len(children), 3)
+        yield self.commit()
+
+        calendar = yield self.calendarUnderTest(home=&quot;user02&quot;, name=&quot;calendar&quot;)
+        token = yield calendar.syncToken()
+        self.assertTrue(token is not None)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_removeAfterRevisionCleanup(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Make sure L{Calendar}'s can be renamed after revision cleanup
</span><span class="lines">@@ -2349,6 +2462,58 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_revisionModified(self):
+        &quot;&quot;&quot;
+        Make sure the revision table MODIFIED value changes for an update or delete
+        &quot;&quot;&quot;
+
+        @inlineCallbacks
+        def _getModified():
+            home = yield self.homeUnderTest(name=&quot;user01&quot;)
+            calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+            rev = calendar._revisionsSchema
+            modified = yield Select(
+                [rev.MODIFIED, ],
+                From=rev,
+                Where=(
+                    rev.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)).And(
+                    rev.CALENDAR_RESOURCE_ID == Parameter(&quot;collectionID&quot;)).And(
+                    rev.RESOURCE_NAME == Parameter(&quot;resourceName&quot;)
+                )
+            ).on(
+                home._txn,
+                homeID=home.id(),
+                collectionID=calendar.id(),
+                resourceName=&quot;1.ics&quot;,
+            )
+            yield self.commit()
+            returnValue(modified[0][0])
+
+        # Get current modified
+        old_modified = yield _getModified()
+        self.assertNotEqual(old_modified, None)
+
+        # Update resource
+        cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield cobj.setComponent(Component.fromString(updateToCurrentYear(cal1Root.child(&quot;1.ics&quot;).getContent())))
+        yield self.commit()
+
+        # Modified changed
+        update_modified = yield _getModified()
+        self.assertGreater(update_modified, old_modified)
+
+        # Delete resource
+        cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;, name=&quot;1.ics&quot;)
+        yield cobj.remove()
+        yield self.commit()
+
+        # Modified changed
+        delete_modified = yield _getModified()
+        self.assertGreater(delete_modified, old_modified)
+        self.assertGreater(delete_modified, update_modified)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_homeSyncTokenWithTrash_Visible(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         L{ICalendarHome.resourceNamesSinceToken} will return the names of
</span><span class="lines">@@ -2359,8 +2524,8 @@
</span><span class="cx">         self.patch(config, &quot;EnableTrashCollection&quot;, True)
</span><span class="cx">         self.patch(config, &quot;ExposeTrashCollection&quot;, True)
</span><span class="cx"> 
</span><del>-        home = yield self.homeUnderTest()
-        cal = yield self.calendarUnderTest()
</del><ins>+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</ins><span class="cx">         st = yield home.syncToken()
</span><span class="cx">         yield cal.createCalendarObjectWithName(&quot;new.ics&quot;, Component.fromString(
</span><span class="cx">             test_event_text
</span><span class="lines">@@ -2372,12 +2537,12 @@
</span><span class="cx">         st2 = yield home.syncToken()
</span><span class="cx">         self.failIfEquals(st, st2)
</span><span class="cx"> 
</span><del>-        home = yield self.homeUnderTest()
</del><ins>+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
</ins><span class="cx"> 
</span><span class="cx">         expected = [
</span><del>-            &quot;calendar_1/&quot;,
-            &quot;calendar_1/new.ics&quot;,
-            &quot;calendar_1/2.ics&quot;,
</del><ins>+            &quot;calendar/&quot;,
+            &quot;calendar/new.ics&quot;,
+            &quot;calendar/2.ics&quot;,
</ins><span class="cx">             &quot;other-calendar/&quot;
</span><span class="cx">         ]
</span><span class="cx"> 
</span><span class="lines">@@ -2393,7 +2558,7 @@
</span><span class="cx">             self.token2revision(st), &quot;infinity&quot;)
</span><span class="cx"> 
</span><span class="cx">         self.assertEquals(set(changed), set(expected))
</span><del>-        self.assertEquals(set(deleted), set([&quot;calendar_1/2.ics&quot;]))
</del><ins>+        self.assertEquals(set(deleted), set([&quot;calendar/2.ics&quot;]))
</ins><span class="cx">         self.assertEquals(invalid, [])
</span><span class="cx"> 
</span><span class="cx">         changed, deleted, invalid = yield home.resourceNamesSinceToken(
</span><span class="lines">@@ -2413,8 +2578,8 @@
</span><span class="cx"> 
</span><span class="cx">         self.patch(config, &quot;EnableTrashCollection&quot;, True)
</span><span class="cx"> 
</span><del>-        home = yield self.homeUnderTest()
-        cal = yield self.calendarUnderTest()
</del><ins>+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</ins><span class="cx">         st = yield home.syncToken()
</span><span class="cx">         yield cal.createCalendarObjectWithName(&quot;new.ics&quot;, Component.fromString(
</span><span class="cx">             test_event_text
</span><span class="lines">@@ -2426,12 +2591,12 @@
</span><span class="cx">         st2 = yield home.syncToken()
</span><span class="cx">         self.failIfEquals(st, st2)
</span><span class="cx"> 
</span><del>-        home = yield self.homeUnderTest()
</del><ins>+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
</ins><span class="cx"> 
</span><span class="cx">         expected = [
</span><del>-            &quot;calendar_1/&quot;,
-            &quot;calendar_1/new.ics&quot;,
-            &quot;calendar_1/2.ics&quot;,
</del><ins>+            &quot;calendar/&quot;,
+            &quot;calendar/new.ics&quot;,
+            &quot;calendar/2.ics&quot;,
</ins><span class="cx">             &quot;other-calendar/&quot;
</span><span class="cx">         ]
</span><span class="cx"> 
</span><span class="lines">@@ -2439,7 +2604,7 @@
</span><span class="cx">             self.token2revision(st), &quot;infinity&quot;)
</span><span class="cx"> 
</span><span class="cx">         self.assertEquals(set(changed), set(expected))
</span><del>-        self.assertEquals(set(deleted), set([&quot;calendar_1/2.ics&quot;]))
</del><ins>+        self.assertEquals(set(deleted), set([&quot;calendar/2.ics&quot;]))
</ins><span class="cx">         self.assertEquals(invalid, [])
</span><span class="cx"> 
</span><span class="cx">         changed, deleted, invalid = yield home.resourceNamesSinceToken(
</span><span class="lines">@@ -2450,7 +2615,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class SchedulingTests(CommonCommonTests, unittest.TestCase):
</del><ins>+class SchedulingTests(CommonCommonTests, DateTimeSubstitutionsMixin, unittest.TestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     CalendarObject splitting tests
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -2459,6 +2624,7 @@
</span><span class="cx">     def setUp(self):
</span><span class="cx">         yield super(SchedulingTests, self).setUp()
</span><span class="cx">         yield self.buildStoreAndDirectory()
</span><ins>+        self.setupDateTimeValues()
</ins><span class="cx"> 
</span><span class="cx">         # Make sure homes are provisioned
</span><span class="cx">         txn = self.transactionUnderTest()
</span><span class="lines">@@ -2682,7 +2848,7 @@
</span><span class="cx">             calendar_name=&quot;calendar&quot;,
</span><span class="cx">             home=&quot;user01&quot;
</span><span class="cx">         )
</span><del>-        comp = yield cobj.component()
</del><ins>+        comp = yield cobj.componentForUser()
</ins><span class="cx">         components = list(comp.subcomponents())
</span><span class="cx"> 
</span><span class="cx">         # Check first component
</span><span class="lines">@@ -2773,14 +2939,14 @@
</span><span class="cx">             calendar_name=&quot;calendar&quot;,
</span><span class="cx">             home=&quot;user01&quot;
</span><span class="cx">         )
</span><del>-        comp = yield cobj.component()
</del><ins>+        comp = yield cobj.componentForUser()
</ins><span class="cx">         components = list(comp.subcomponents())
</span><span class="cx"> 
</span><span class="cx">         # Check first component
</span><span class="cx">         locProp = components[0].getProperty(&quot;LOCATION&quot;)
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             locProp.value(),
</span><del>-            &quot;Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014; Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014&quot;
</del><ins>+            &quot;Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014;Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014&quot;
</ins><span class="cx">         )
</span><span class="cx">         structProps = tuple(components[0].properties(&quot;X-APPLE-STRUCTURED-LOCATION&quot;))
</span><span class="cx">         self.assertEqual(len(structProps), 2)
</span><span class="lines">@@ -2817,7 +2983,7 @@
</span><span class="cx">         locProp = components[0].getProperty(&quot;LOCATION&quot;)
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             locProp.value(),
</span><del>-            &quot;Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014; Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014&quot;
</del><ins>+            &quot;Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014;Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014&quot;
</ins><span class="cx">         )
</span><span class="cx">         structProps = tuple(components[0].properties(&quot;X-APPLE-STRUCTURED-LOCATION&quot;))
</span><span class="cx">         self.assertEqual(len(structProps), 2)
</span><span class="lines">@@ -2830,6 +2996,429 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_setComponent_structuredLocation_Mixed(self):
+        &quot;&quot;&quot;
+        Verify adding a location that's not in the directory to an event which
+        already has a location that's in the directory keeps them both.
+        X-APPLE-STRUCTURED-LOCATION properties which have X-CUADDR but no
+        corresponding ATTENDEE are removed.
+        &quot;&quot;&quot;
+
+        data = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+DTSTART:20131211T164500Z
+DURATION:PT1H
+ATTENDEE;CN=Old Room with Address 1;CUTYPE=ROOM;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPAN
+ T;SCHEDULE-STATUS=2.0:urn:x-uid:room-addr-1
+ATTENDEE;CN=Room with Address 2;CUTYPE=ROOM;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPAN
+ T;SCHEDULE-STATUS=2.0:urn:x-uid:room-addr-2
+ATTENDEE;CN=Mercury Seven;CUTYPE=ROOM;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPAN
+ T;SCHEDULE-STATUS=2.0:urn:x-uid:mercury
+ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC
+ CEPTED:urn:x-uid:user01
+X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=&quot;1 Infinite Loop, Cupertin
+ o, CA 95014&quot;;X-APPLE-RADIUS=71;X-CUADDR=&quot;urn:x-uid:room-addr-1&quot;;X-TITLE=O
+ ld Room with Address 1:geo:37.331741,-122.030333
+X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=&quot;2 Infinite Loop, Cupertin
+ o, CA 95014&quot;;X-APPLE-RADIUS=71;X-TITLE=Room with Address 2:geo:37.332633,
+ -122.030502
+X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=123 Main St;X-APPLE-RADIUS
+ =14164;X-TITLE=Mercury Seven:geo:37.351164,-122.032686
+CREATED:20131211T221854Z
+DTSTAMP:20131211T230632Z
+ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:x-uid:user01
+SEQUENCE:1
+SUMMARY:locations
+LOCATION:Old Room with Address 1;Unstructured Location; Mercury Seven; Room with Address 2
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        yield calendar.createCalendarObjectWithName(
+            &quot;structured.ics&quot;,
+            Component.fromString(data)
+        )
+
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(
+            name=&quot;structured.ics&quot;,
+            calendar_name=&quot;calendar&quot;,
+            home=&quot;user01&quot;
+        )
+        comp = yield cobj.componentForUser()
+        components = list(comp.subcomponents())
+
+        # Check first component -- LOCATION now has the street addresses, and
+        # location values that don't have an ATTENDEE or X-APPLE-STRUCTURED-LOCATIONs
+        # are retained
+        locProp = components[0].getProperty(&quot;LOCATION&quot;)
+        self.assertEquals(
+            locProp.value(),
+            &quot;Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014;Unstructured Location;Mercury Seven;Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014&quot;
+        )
+        structProps = tuple(components[0].properties(&quot;X-APPLE-STRUCTURED-LOCATION&quot;))
+        self.assertEqual(len(structProps), 3)
+        self.assertEquals(
+            structProps[0].value(),
+            &quot;geo:37.331741,-122.030333&quot;,
+        )
+        # Make sure server has also added X-CUADDR
+        self.assertEquals(
+            structProps[0].parameterValue(&quot;X-CUADDR&quot;),
+            &quot;urn:x-uid:room-addr-1&quot;
+        )
+
+        # Client now adds a location not in the directory:
+        comp = comp.duplicate()
+        main = comp.mainComponent()
+        main.replaceProperty(Property(&quot;LOCATION&quot;, &quot;Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014; Unstructured Location; Falafel Stop\n1325 Sunnyvale Saratoga, Sunnyvale, CA 94087;Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014&quot;))
+
+        params = {
+            &quot;X-ADDRESS&quot;: &quot;1325 Sunnyvale Saratoga Rd&quot;,
+            &quot;X-APPLE-RADIUS&quot;: &quot;14164&quot;,
+            &quot;X-TITLE&quot;: &quot;Falafel Stop&quot;,
+        }
+        structured = Property(
+            &quot;X-APPLE-STRUCTURED-LOCATION&quot;,
+            &quot;geo:37.351164,-122.032686&quot;, params=params,
+            valuetype=Value.VALUETYPE_URI
+        )
+        main.addProperty(structured)
+
+        # ...plus let's prove we clean up structured locations which have X-CUADDR
+        # but no matching ATTENDEE
+        params = {
+            &quot;X-ADDRESS&quot;: &quot;1122 Boogie Woogie Ave&quot;,
+            &quot;X-APPLE-RADIUS&quot;: &quot;14164&quot;,
+            &quot;X-TITLE&quot;: &quot;Home of the Boogie, House of the Funk&quot;,
+            &quot;X-CUADDR&quot;: &quot;urn:x-uid:boogie-home&quot;,
+        }
+        structured = Property(
+            &quot;X-APPLE-STRUCTURED-LOCATION&quot;,
+            &quot;geo:37.351164,-122.032686&quot;, params=params,
+            valuetype=Value.VALUETYPE_URI
+        )
+        main.addProperty(structured)
+
+        # Store the new component and let the server do its thing
+        yield cobj.setComponent(comp)
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(
+            name=&quot;structured.ics&quot;,
+            calendar_name=&quot;calendar&quot;,
+            home=&quot;user01&quot;
+        )
+        comp = yield cobj.componentForUser()
+        components = list(comp.subcomponents())
+
+        # Check first component
+        structProps = tuple(components[0].properties(&quot;X-APPLE-STRUCTURED-LOCATION&quot;))
+        self.assertEqual(len(structProps), 4)
+        self.assertEquals(
+            set([structProp.parameterValue(&quot;X-TITLE&quot;) for structProp in structProps]),
+            set((&quot;Room with Address 1&quot;, &quot;Room with Address 2&quot;, &quot;Falafel Stop&quot;, &quot;Mercury Seven&quot;))
+        )
+
+        locProp = components[0].getProperty(&quot;LOCATION&quot;)
+        self.assertEquals(
+            locProp.value(),
+            &quot;Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014;Unstructured Location;Falafel Stop\n1325 Sunnyvale Saratoga, Sunnyvale, CA 94087;Room with Address 2\n2 Infinite Loop, Cupertino, CA 95014&quot;
+        )
+
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_setComponent_structuredLocation_MissingValue(self):
+        &quot;&quot;&quot;
+        Verify we detect a change to X-APPLE-STRUCTURED-LOCATION and update it.
+        (This also works if the client neglects to provide a value)
+        &quot;&quot;&quot;
+
+        data = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+DTSTART:20131211T164500Z
+DURATION:PT1H
+ATTENDEE;CN=Room with Address 1;CUTYPE=ROOM;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPAN
+ T;SCHEDULE-STATUS=2.0:urn:x-uid:room-addr-1
+ATTENDEE;CN=User 01;CUTYPE=INDIVIDUAL;EMAIL=user01@example.com;PARTSTAT=AC
+ CEPTED:urn:x-uid:user01
+X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-TITLE=Room with Address 1:
+CREATED:20131211T221854Z
+DTSTAMP:20131211T230632Z
+ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:x-uid:user01
+SEQUENCE:1
+SUMMARY:locations
+LOCATION:Room with Address 1
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        yield calendar.createCalendarObjectWithName(
+            &quot;structured.ics&quot;,
+            Component.fromString(data)
+        )
+
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(
+            name=&quot;structured.ics&quot;,
+            calendar_name=&quot;calendar&quot;,
+            home=&quot;user01&quot;
+        )
+        comp = yield cobj.componentForUser()
+        components = list(comp.subcomponents())
+
+        # Check first component -- LOCATION now has the street addresses, and
+        # location values that don't have an ATTENDEE or X-APPLE-STRUCTURED-LOCATIONs
+        # are retained
+        locProp = components[0].getProperty(&quot;LOCATION&quot;)
+        self.assertEquals(
+            locProp.value(),
+            &quot;Room with Address 1\n1 Infinite Loop, Cupertino, CA 95014&quot;
+        )
+        structProps = tuple(components[0].properties(&quot;X-APPLE-STRUCTURED-LOCATION&quot;))
+        self.assertEqual(len(structProps), 1)
+        self.assertEquals(
+            structProps[0].value(),
+            &quot;geo:37.331741,-122.030333&quot;,
+        )
+        # Make sure server has also added X-CUADDR
+        self.assertEquals(
+            structProps[0].parameterValue(&quot;X-CUADDR&quot;),
+            &quot;urn:x-uid:room-addr-1&quot;
+        )
+
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_setComponent_changed_stripStandardTimezones(self):
+        &quot;&quot;&quot;
+        Verify we let the client know we stripped standard timezones
+        &quot;&quot;&quot;
+
+        data = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+DTSTART:20131211T164500Z
+DURATION:PT1H
+CREATED:20131211T221854Z
+DTSTAMP:20131211T230632Z
+SEQUENCE:1
+SUMMARY:testing
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        tzdata = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        TimezoneCache.create()
+        self.addCleanup(TimezoneCache.clear)
+
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        yield calendar.createCalendarObjectWithName(
+            &quot;structured.ics&quot;,
+            Component.fromString(data)
+        )
+
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(
+            name=&quot;structured.ics&quot;,
+            calendar_name=&quot;calendar&quot;,
+            home=&quot;user01&quot;
+        )
+        comp = yield cobj.componentForUser()
+        comp = comp.duplicate()
+
+        # create timezone component
+        tzCalendar = Component.fromString(tzdata)
+        tzComponent = list(tzCalendar.subcomponents())[0]
+        comp.addComponent(tzComponent)
+
+        # update DTSTART to reference that tz
+        main = comp.mainComponent()
+        newStart = Property(
+            &quot;DTSTART&quot;,
+            DateTime(2015, 7, 7, 5, 0, 0, tzid=Timezone(tzid=&quot;US/Pacific&quot;))
+        )
+        main.replaceProperty(newStart)
+        yield cobj.setComponent(comp)
+        comp = yield cobj.componentForUser()
+        self.assertTrue(cobj._componentChanged)
+
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_setComponent_changed_preservePrivateComments(self):
+        &quot;&quot;&quot;
+        Verify we let the client know we preserved private comments
+        &quot;&quot;&quot;
+
+        dataWith = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+DTSTART:%(now_fwd30)s
+DURATION:PT1H
+CREATED:%(now)s
+DTSTAMP:%(now)s
+SEQUENCE:1
+SUMMARY:testing
+TRANSP:OPAQUE
+X-CALENDARSERVER-ATTENDEE-COMMENT;X-CALENDARSERVER-ATTENDEE-REF=&quot;urn:uuid:user01&quot;;X-CALENDARSERVER-DTSTAMP=%(now)s:Message1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        dataWithout = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+DTSTART:%(now_fwd30)s
+DURATION:PT1H
+CREATED:%(now)s
+DTSTAMP:%(now)s
+SEQUENCE:1
+SUMMARY:testing
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        yield calendar.createCalendarObjectWithName(
+            &quot;comments.ics&quot;,
+            Component.fromString(dataWith % self.dtsubs)
+        )
+
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(
+            name=&quot;comments.ics&quot;,
+            calendar_name=&quot;calendar&quot;,
+            home=&quot;user01&quot;
+        )
+
+        comp = Component.fromString(dataWithout % self.dtsubs)
+        yield cobj.setComponent(comp)
+        comp = yield cobj.componentForUser()
+        self.assertTrue(cobj._componentChanged)
+
+        yield self.commit()
+
+
+    @inlineCallbacks
+    def test_setComponent_changed_dropboxPathNormalization(self):
+        &quot;&quot;&quot;
+        Verify we let the client know we normalized dropbox paths
+        &quot;&quot;&quot;
+
+        dataWithout = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+DTSTART:%(now_fwd30)s
+DURATION:PT1H
+DTSTAMP:%(now)s
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        dataWith = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//Mac OS X 10.9.1//EN
+BEGIN:VEVENT
+UID:561F5DBB-3F38-4B3A-986F-DD05CBAF554F
+DTSTART:%(now_fwd30)s
+DURATION:PT1H
+DTSTAMP:%(now)s
+SEQUENCE:2
+X-APPLE-DROPBOX:https://example.com/calendars/users/user02/dropbox/123.dropbox
+ATTACH;VALUE=URI:https://example.com/calendars/users/user02/dropbox/123.dropbox/1.txt
+ATTACH;VALUE=URI:https://example.org/attachments/2.txt
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;)
+
+        calendarCollection = (yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;))
+        shareeHome = (yield self.homeUnderTest(name=&quot;user02&quot;))
+        sharedName = (yield calendarCollection.shareWith(shareeHome, _BIND_MODE_WRITE,))
+        yield self.commit()
+
+        calendar = yield self.calendarUnderTest(name=sharedName, home=&quot;user02&quot;)
+        yield calendar.createCalendarObjectWithName(
+            &quot;attach.ics&quot;,
+            Component.fromString(dataWithout % self.dtsubs)
+        )
+
+        yield self.commit()
+
+        cobj = yield self.calendarObjectUnderTest(
+            name=&quot;attach.ics&quot;,
+            calendar_name=sharedName,
+            home=&quot;user02&quot;
+        )
+
+        comp = Component.fromString(dataWith % self.dtsubs)
+        yield cobj.setComponent(comp)
+        comp = yield cobj.componentForUser()
+        self.assertTrue(cobj._componentChanged)
+
+        yield self.commit()
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_setComponent_externalPrincipal(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Verify attendees who are not locally hosted have X-APPLE-HOSTED-STATUS=EXTERNAL
</span><span class="lines">@@ -2922,7 +3511,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class CalendarObjectSplitting(CommonCommonTests, unittest.TestCase):
</del><ins>+class CalendarObjectSplitting(CommonCommonTests, DateTimeSubstitutionsMixin, unittest.TestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     CalendarObject splitting tests
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -2939,40 +3528,8 @@
</span><span class="cx">             self.assertNotEqual(home_uid, None)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        self.subs = {}
</del><ins>+        self.setupDateTimeValues()
</ins><span class="cx"> 
</span><del>-        self.now = DateTime.getNowUTC()
-        self.now.setHHMMSS(0, 0, 0)
-
-        self.subs[&quot;now&quot;] = self.now
-
-        for i in range(30):
-            attrname = &quot;now_back%s&quot; % (i + 1,)
-            setattr(self, attrname, self.now.duplicate())
-            getattr(self, attrname).offsetDay(-(i + 1))
-            self.subs[attrname] = getattr(self, attrname)
-
-            attrname_12h = &quot;now_back%s_12h&quot; % (i + 1,)
-            setattr(self, attrname_12h, getattr(self, attrname).duplicate())
-            getattr(self, attrname_12h).offsetHours(12)
-            self.subs[attrname_12h] = getattr(self, attrname_12h)
-
-            attrname_1 = &quot;now_back%s_1&quot; % (i + 1,)
-            setattr(self, attrname_1, getattr(self, attrname).duplicate())
-            getattr(self, attrname_1).offsetSeconds(-1)
-            self.subs[attrname_1] = getattr(self, attrname_1)
-
-        for i in range(30):
-            attrname = &quot;now_fwd%s&quot; % (i + 1,)
-            setattr(self, attrname, self.now.duplicate())
-            getattr(self, attrname).offsetDay(i + 1)
-            self.subs[attrname] = getattr(self, attrname)
-
-            attrname_12h = &quot;now_fwd%s_12h&quot; % (i + 1,)
-            setattr(self, attrname_12h, getattr(self, attrname).duplicate())
-            getattr(self, attrname_12h).offsetHours(12)
-            self.subs[attrname_12h] = getattr(self, attrname_12h)
-
</del><span class="cx">         self.patch(config, &quot;MaxAllowedInstances&quot;, 500)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -3181,7 +3738,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -3205,7 +3762,7 @@
</span><span class="cx">         ical_future, ical_past, pastUID, relID, _ignore_new_name = yield self._splitDetails(&quot;user01&quot;)
</span><span class="cx"> 
</span><span class="cx">         title = &quot;temp&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = pastUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s&quot; % (title,))
</span><span class="lines">@@ -3213,6 +3770,428 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_calendarObjectSplit_AllDay(self):
+        &quot;&quot;&quot;
+        Test that (manual) splitting of all-day calendar objects works.
+        &quot;&quot;&quot;
+
+        self.patch(config.Scheduling.Options.Splitting, &quot;Enabled&quot;, False)
+        self.patch(config.Scheduling.Options.Splitting, &quot;Size&quot;, 1024)
+        self.patch(config.Scheduling.Options.Splitting, &quot;PastDays&quot;, 14)
+
+        # Create one event that will split
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+
+        data = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:%(nowDate_back30)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+ATTENDEE:mailto:user3@example.org
+ATTENDEE:mailto:user4@example.org
+ATTENDEE:mailto:user5@example.org
+ATTENDEE:mailto:user6@example.org
+ATTENDEE:mailto:user7@example.org
+ATTENDEE:mailto:user8@example.org
+ATTENDEE:mailto:user9@example.org
+ATTENDEE:mailto:user10@example.org
+ATTENDEE:mailto:user11@example.org
+ATTENDEE:mailto:user12@example.org
+ATTENDEE:mailto:user13@example.org
+ATTENDEE:mailto:user14@example.org
+ATTENDEE:mailto:user15@example.org
+ATTENDEE:mailto:user16@example.org
+ATTENDEE:mailto:user17@example.org
+ATTENDEE:mailto:user18@example.org
+ATTENDEE:mailto:user19@example.org
+ATTENDEE:mailto:user20@example.org
+ATTENDEE:mailto:user21@example.org
+ATTENDEE:mailto:user22@example.org
+ATTENDEE:mailto:user23@example.org
+ATTENDEE:mailto:user24@example.org
+ATTENDEE:mailto:user25@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user1@example.org
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;VALUE=DATE:%(nowDate_back25)s
+DTSTART;VALUE=DATE:%(nowDate_back25)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user1@example.org
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID;VALUE=DATE:%(nowDate_back24)s
+DTSTART;VALUE=DATE:%(nowDate_back24)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user1@example.org
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        data_future = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART;VALUE=DATE:%(nowDate_back14)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+ATTENDEE:mailto:user3@example.org
+ATTENDEE:mailto:user4@example.org
+ATTENDEE:mailto:user5@example.org
+ATTENDEE:mailto:user6@example.org
+ATTENDEE:mailto:user7@example.org
+ATTENDEE:mailto:user8@example.org
+ATTENDEE:mailto:user9@example.org
+ATTENDEE:mailto:user10@example.org
+ATTENDEE:mailto:user11@example.org
+ATTENDEE:mailto:user12@example.org
+ATTENDEE:mailto:user13@example.org
+ATTENDEE:mailto:user14@example.org
+ATTENDEE:mailto:user15@example.org
+ATTENDEE:mailto:user16@example.org
+ATTENDEE:mailto:user17@example.org
+ATTENDEE:mailto:user18@example.org
+ATTENDEE:mailto:user19@example.org
+ATTENDEE:mailto:user20@example.org
+ATTENDEE:mailto:user21@example.org
+ATTENDEE:mailto:user22@example.org
+ATTENDEE:mailto:user23@example.org
+ATTENDEE:mailto:user24@example.org
+ATTENDEE:mailto:user25@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1@example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+SEQUENCE:1
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        data_past = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(uid)s
+DTSTART;VALUE=DATE:%(nowDate_back30)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+ATTENDEE:mailto:user3@example.org
+ATTENDEE:mailto:user4@example.org
+ATTENDEE:mailto:user5@example.org
+ATTENDEE:mailto:user6@example.org
+ATTENDEE:mailto:user7@example.org
+ATTENDEE:mailto:user8@example.org
+ATTENDEE:mailto:user9@example.org
+ATTENDEE:mailto:user10@example.org
+ATTENDEE:mailto:user11@example.org
+ATTENDEE:mailto:user12@example.org
+ATTENDEE:mailto:user13@example.org
+ATTENDEE:mailto:user14@example.org
+ATTENDEE:mailto:user15@example.org
+ATTENDEE:mailto:user16@example.org
+ATTENDEE:mailto:user17@example.org
+ATTENDEE:mailto:user18@example.org
+ATTENDEE:mailto:user19@example.org
+ATTENDEE:mailto:user20@example.org
+ATTENDEE:mailto:user21@example.org
+ATTENDEE:mailto:user22@example.org
+ATTENDEE:mailto:user23@example.org
+ATTENDEE:mailto:user24@example.org
+ATTENDEE:mailto:user25@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1@example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(nowDate_back15)s
+SEQUENCE:1
+TRANSP:OPAQUE
+END:VEVENT
+BEGIN:VEVENT
+UID:%(uid)s
+RECURRENCE-ID;VALUE=DATE:%(nowDate_back25)s
+DTSTART;VALUE=DATE:%(nowDate_back25)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1@example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+TRANSP:OPAQUE
+END:VEVENT
+BEGIN:VEVENT
+UID:%(uid)s
+RECURRENCE-ID;VALUE=DATE:%(nowDate_back24)s
+DTSTART;VALUE=DATE:%(nowDate_back24)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1@example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        component = Component.fromString(data % self.dtsubs)
+        cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
+        self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
+        yield self.commit()
+
+        w = schema.CALENDAR_OBJECT_SPLITTER_WORK
+        rows = yield Select(
+            [w.RESOURCE_ID, ],
+            From=w
+        ).on(self.transactionUnderTest())
+        self.assertEqual(len(rows), 0)
+        yield self.abort()
+
+        # Do manual split
+        cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        will = yield cobj.willSplit()
+        self.assertTrue(will)
+
+        yield cobj.split()
+        yield self.commit()
+
+        ical_future, ical_past, pastUID, relID, _ignore_new_name = yield self._splitDetails(&quot;user01&quot;)
+
+        title = &quot;temp&quot;
+        relsubs = dict(self.dtsubs)
+        relsubs[&quot;uid&quot;] = pastUID
+        relsubs[&quot;relID&quot;] = relID
+        self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s %s&quot; % (title, diff_iCalStrs(ical_future, data_future % relsubs)))
+        self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % relsubs, &quot;Failed past: %s %s&quot; % (title, diff_iCalStrs(ical_past, data_past % relsubs)))
+
+
+    @inlineCallbacks
+    def test_calendarObjectSplit_Floating(self):
+        &quot;&quot;&quot;
+        Test that (manual) splitting of floating calendar objects works.
+        &quot;&quot;&quot;
+
+        self.patch(config.Scheduling.Options.Splitting, &quot;Enabled&quot;, False)
+        self.patch(config.Scheduling.Options.Splitting, &quot;Size&quot;, 1024)
+        self.patch(config.Scheduling.Options.Splitting, &quot;PastDays&quot;, 14)
+
+        # Create one event that will split
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+
+        data = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(nowFloating_back30)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+ATTENDEE:mailto:user3@example.org
+ATTENDEE:mailto:user4@example.org
+ATTENDEE:mailto:user5@example.org
+ATTENDEE:mailto:user6@example.org
+ATTENDEE:mailto:user7@example.org
+ATTENDEE:mailto:user8@example.org
+ATTENDEE:mailto:user9@example.org
+ATTENDEE:mailto:user10@example.org
+ATTENDEE:mailto:user11@example.org
+ATTENDEE:mailto:user12@example.org
+ATTENDEE:mailto:user13@example.org
+ATTENDEE:mailto:user14@example.org
+ATTENDEE:mailto:user15@example.org
+ATTENDEE:mailto:user16@example.org
+ATTENDEE:mailto:user17@example.org
+ATTENDEE:mailto:user18@example.org
+ATTENDEE:mailto:user19@example.org
+ATTENDEE:mailto:user20@example.org
+ATTENDEE:mailto:user21@example.org
+ATTENDEE:mailto:user22@example.org
+ATTENDEE:mailto:user23@example.org
+ATTENDEE:mailto:user24@example.org
+ATTENDEE:mailto:user25@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user1@example.org
+RRULE:FREQ=DAILY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(nowFloating_back25)s
+DTSTART:%(nowFloating_back25)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user1@example.org
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:%(nowFloating_back24)s
+DTSTART:%(nowFloating_back24)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER:mailto:user1@example.org
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        data_future = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:%(nowFloating_back14)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+ATTENDEE:mailto:user3@example.org
+ATTENDEE:mailto:user4@example.org
+ATTENDEE:mailto:user5@example.org
+ATTENDEE:mailto:user6@example.org
+ATTENDEE:mailto:user7@example.org
+ATTENDEE:mailto:user8@example.org
+ATTENDEE:mailto:user9@example.org
+ATTENDEE:mailto:user10@example.org
+ATTENDEE:mailto:user11@example.org
+ATTENDEE:mailto:user12@example.org
+ATTENDEE:mailto:user13@example.org
+ATTENDEE:mailto:user14@example.org
+ATTENDEE:mailto:user15@example.org
+ATTENDEE:mailto:user16@example.org
+ATTENDEE:mailto:user17@example.org
+ATTENDEE:mailto:user18@example.org
+ATTENDEE:mailto:user19@example.org
+ATTENDEE:mailto:user20@example.org
+ATTENDEE:mailto:user21@example.org
+ATTENDEE:mailto:user22@example.org
+ATTENDEE:mailto:user23@example.org
+ATTENDEE:mailto:user24@example.org
+ATTENDEE:mailto:user25@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1@example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        data_past = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:%(uid)s
+DTSTART:%(nowFloating_back30)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+ATTENDEE:mailto:user3@example.org
+ATTENDEE:mailto:user4@example.org
+ATTENDEE:mailto:user5@example.org
+ATTENDEE:mailto:user6@example.org
+ATTENDEE:mailto:user7@example.org
+ATTENDEE:mailto:user8@example.org
+ATTENDEE:mailto:user9@example.org
+ATTENDEE:mailto:user10@example.org
+ATTENDEE:mailto:user11@example.org
+ATTENDEE:mailto:user12@example.org
+ATTENDEE:mailto:user13@example.org
+ATTENDEE:mailto:user14@example.org
+ATTENDEE:mailto:user15@example.org
+ATTENDEE:mailto:user16@example.org
+ATTENDEE:mailto:user17@example.org
+ATTENDEE:mailto:user18@example.org
+ATTENDEE:mailto:user19@example.org
+ATTENDEE:mailto:user20@example.org
+ATTENDEE:mailto:user21@example.org
+ATTENDEE:mailto:user22@example.org
+ATTENDEE:mailto:user23@example.org
+ATTENDEE:mailto:user24@example.org
+ATTENDEE:mailto:user25@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1@example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+RRULE:FREQ=DAILY;UNTIL=%(nowFloating_back14_1)s
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:%(uid)s
+RECURRENCE-ID:%(nowFloating_back25)s
+DTSTART:%(nowFloating_back25)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1@example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+BEGIN:VEVENT
+UID:%(uid)s
+RECURRENCE-ID:%(nowFloating_back24)s
+DTSTART:%(nowFloating_back24)s
+DURATION:P1D
+ATTENDEE:mailto:user1@example.org
+ATTENDEE:mailto:user2@example.org
+DTSTAMP:20051222T210507Z
+ORGANIZER;SCHEDULE-AGENT=NONE;SCHEDULE-STATUS=5.3:mailto:user1@example.org
+RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:%(relID)s
+SEQUENCE:1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        component = Component.fromString(data % self.dtsubs)
+        cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
+        self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
+        yield self.commit()
+
+        w = schema.CALENDAR_OBJECT_SPLITTER_WORK
+        rows = yield Select(
+            [w.RESOURCE_ID, ],
+            From=w
+        ).on(self.transactionUnderTest())
+        self.assertEqual(len(rows), 0)
+        yield self.abort()
+
+        # Do manual split
+        cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        will = yield cobj.willSplit()
+        self.assertTrue(will)
+
+        yield cobj.split()
+        yield self.commit()
+
+        ical_future, ical_past, pastUID, relID, _ignore_new_name = yield self._splitDetails(&quot;user01&quot;)
+
+        title = &quot;temp&quot;
+        relsubs = dict(self.dtsubs)
+        relsubs[&quot;uid&quot;] = pastUID
+        relsubs[&quot;relID&quot;] = relID
+        self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s %s&quot; % (title, diff_iCalStrs(ical_future, data_future % relsubs)))
+        self.assertEqual(normalize_iCalStr(ical_past), normalize_iCalStr(data_past) % relsubs, &quot;Failed past: %s %s&quot; % (title, diff_iCalStrs(ical_past, data_past % relsubs)))
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_calendarObjectSplit_work(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Test that splitting of calendar objects works.
</span><span class="lines">@@ -3631,7 +4610,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertTrue(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -3660,7 +4639,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="cx">         title = &quot;user01&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = pastUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s&quot; % (title,))
</span><span class="lines">@@ -3793,7 +4772,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertTrue(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -4015,7 +4994,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -4027,16 +5006,16 @@
</span><span class="cx">         cobj = cobjs[0]
</span><span class="cx">         cname2 = cobj.name()
</span><span class="cx">         ical = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_2) % self.subs, &quot;Failed 2&quot;)
-        yield cobj.setComponent(Component.fromString(data_2_update % self.subs))
</del><ins>+        self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_2) % self.dtsubs, &quot;Failed 2&quot;)
+        yield cobj.setComponent(Component.fromString(data_2_update % self.dtsubs))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         ical = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_1) % self.subs, &quot;Failed 2&quot;)
</del><ins>+        self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_1) % self.dtsubs, &quot;Failed 2&quot;)
</ins><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=cname2, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         ical = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_2_changed) % self.subs, &quot;Failed 2&quot;)
</del><ins>+        self.assertEqual(normalize_iCalStr(ical), normalize_iCalStr(data_2_changed) % self.dtsubs, &quot;Failed 2&quot;)
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -4101,7 +5080,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -4397,7 +5376,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Create initial non-split event
</span><del>-        cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, Component.fromString(data_1 % self.subs))
</del><ins>+        cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, Component.fromString(data_1 % self.dtsubs))
</ins><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -4414,7 +5393,7 @@
</span><span class="cx">         self.assertEqual(attachment.parameterValue(&quot;MANAGED-ID&quot;), mid)
</span><span class="cx">         self.assertEqual(attachment.value(), location)
</span><span class="cx"> 
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;mid&quot;] = mid
</span><span class="cx">         relsubs[&quot;att_uri&quot;] = location
</span><span class="cx">         relsubs[&quot;dtstamp&quot;] = str(ical.masterComponent().propertyValue(&quot;DTSTAMP&quot;))
</span><span class="lines">@@ -4441,6 +5420,7 @@
</span><span class="cx">         self.assertEqual(attachment.value(), location)
</span><span class="cx"> 
</span><span class="cx">         relsubs[&quot;past_mid&quot;] = attachment.parameterValue(&quot;MANAGED-ID&quot;)
</span><ins>+        attachment = ical_past.masterComponent().getProperty(&quot;ATTACH&quot;)
</ins><span class="cx">         relsubs[&quot;att_past_uri&quot;] = attachment.value()
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="lines">@@ -4754,7 +5734,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -4766,7 +5746,7 @@
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         processor.recipient_calendar_resource = cobj
</span><span class="cx">         processor.recipient_calendar = (yield cobj.componentForUser(&quot;user01&quot;))
</span><del>-        processor.message = Component.fromString(itip1 % self.subs)
</del><ins>+        processor.message = Component.fromString(itip1 % self.dtsubs)
</ins><span class="cx">         processor.originator = RemoteCalendarUser(&quot;mailto:cuser01@example.org&quot;)
</span><span class="cx">         processor.recipient = LocalCalendarUser(&quot;urn:x-uid:user01&quot;, None)
</span><span class="cx">         processor.method = &quot;REQUEST&quot;
</span><span class="lines">@@ -4777,7 +5757,7 @@
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         new_names = []
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def _verify_state():
</span><span class="lines">@@ -4906,7 +5886,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -4918,7 +5898,7 @@
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         processor.recipient_calendar_resource = cobj
</span><span class="cx">         processor.recipient_calendar = (yield cobj.componentForUser(&quot;user01&quot;))
</span><del>-        processor.message = Component.fromString(itip1 % self.subs)
</del><ins>+        processor.message = Component.fromString(itip1 % self.dtsubs)
</ins><span class="cx">         processor.originator = RemoteCalendarUser(&quot;mailto:cuser01@example.org&quot;)
</span><span class="cx">         processor.recipient = LocalCalendarUser(&quot;urn:x-uid:user01&quot;, None)
</span><span class="cx">         processor.method = &quot;CANCEL&quot;
</span><span class="lines">@@ -4929,7 +5909,7 @@
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Get user01 data
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         cobjs = yield cal.calendarObjects()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><span class="lines">@@ -5027,7 +6007,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -5039,7 +6019,7 @@
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         processor.recipient_calendar_resource = cobj
</span><span class="cx">         processor.recipient_calendar = (yield cobj.componentForUser(&quot;user01&quot;))
</span><del>-        processor.message = Component.fromString(itip1 % self.subs)
</del><ins>+        processor.message = Component.fromString(itip1 % self.dtsubs)
</ins><span class="cx">         processor.originator = RemoteCalendarUser(&quot;mailto:cuser01@example.org&quot;)
</span><span class="cx">         processor.recipient = LocalCalendarUser(&quot;urn:x-uid:user01&quot;, None)
</span><span class="cx">         processor.method = &quot;REQUEST&quot;
</span><span class="lines">@@ -5050,7 +6030,7 @@
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Get user01 data
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         cobjs = yield cal.calendarObjects()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><span class="lines">@@ -5200,7 +6180,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -5212,7 +6192,7 @@
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         processor.recipient_calendar_resource = cobj
</span><span class="cx">         processor.recipient_calendar = (yield cobj.componentForUser(&quot;user01&quot;))
</span><del>-        processor.message = Component.fromString(itip1 % self.subs)
</del><ins>+        processor.message = Component.fromString(itip1 % self.dtsubs)
</ins><span class="cx">         processor.originator = RemoteCalendarUser(&quot;mailto:cuser01@example.org&quot;)
</span><span class="cx">         processor.recipient = LocalCalendarUser(&quot;urn:x-uid:user01&quot;, None)
</span><span class="cx">         processor.method = &quot;REQUEST&quot;
</span><span class="lines">@@ -5224,7 +6204,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Get user01 data
</span><span class="cx">         ical_future, ical_past, pastUID, relID, _ignore_new_name = yield self._splitDetails(&quot;user01&quot;)
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = pastUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx"> 
</span><span class="lines">@@ -5403,7 +6383,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -5424,7 +6404,7 @@
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         processor.recipient_calendar_resource = cobj
</span><span class="cx">         processor.recipient_calendar = (yield cobj.componentForUser(&quot;user01&quot;))
</span><del>-        processor.message = Component.fromString(itip1 % self.subs)
</del><ins>+        processor.message = Component.fromString(itip1 % self.dtsubs)
</ins><span class="cx">         processor.originator = RemoteCalendarUser(&quot;mailto:cuser01@example.org&quot;)
</span><span class="cx">         processor.recipient = LocalCalendarUser(&quot;urn:x-uid:user01&quot;, None)
</span><span class="cx">         processor.method = &quot;REQUEST&quot;
</span><span class="lines">@@ -5443,7 +6423,7 @@
</span><span class="cx"> 
</span><span class="cx">         processor.recipient_calendar_resource = None
</span><span class="cx">         processor.recipient_calendar = None
</span><del>-        processor.message = Component.fromString(itip2 % self.subs)
</del><ins>+        processor.message = Component.fromString(itip2 % self.dtsubs)
</ins><span class="cx">         processor.originator = RemoteCalendarUser(&quot;mailto:cuser01@example.org&quot;)
</span><span class="cx">         processor.recipient = LocalCalendarUser(&quot;urn:x-uid:user01&quot;, None)
</span><span class="cx">         processor.method = &quot;REQUEST&quot;
</span><span class="lines">@@ -5804,7 +6784,7 @@
</span><span class="cx">                     responses.add(recipient, responsecode.NOT_FOUND, reqstatus=iTIPRequestStatus.INVALID_CALENDAR_USER)
</span><span class="cx">             return succeed(responses)
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertTrue(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -5845,7 +6825,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="cx">         title = &quot;user01&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = newUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s\n%s&quot; % (title, diff_iCalStrs(ical_future, data_future % relsubs),))
</span><span class="lines">@@ -6201,7 +7181,7 @@
</span><span class="cx">         # Create one event without active split
</span><span class="cx">         self.patch(config.Scheduling.Options.Splitting, &quot;Enabled&quot;, False)
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        component = Component.fromString(data_init % self.subs)
</del><ins>+        component = Component.fromString(data_init % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -6236,7 +7216,7 @@
</span><span class="cx">             return oldScheduling(self, do_smart_merge, split_details)
</span><span class="cx">         self.patch(ImplicitScheduler, &quot;doImplicitScheduling&quot;, newScheduling)
</span><span class="cx"> 
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         yield self.failUnlessFailure(cobj.setComponent(component), AlreadyFinishedError)
</span><span class="cx">         self.assertTrue(self.transactionUnderTest().timedout)
</span><span class="cx"> 
</span><span class="lines">@@ -6280,7 +7260,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="cx">         title = &quot;user01&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = newUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s&quot; % (title,))
</span><span class="lines">@@ -6542,7 +7522,7 @@
</span><span class="cx">         # Create one event without active split
</span><span class="cx">         self.patch(config.Scheduling.Options.Splitting, &quot;Enabled&quot;, False)
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -6585,7 +7565,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="cx">         title = &quot;user01&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = newUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s&quot; % (title,))
</span><span class="lines">@@ -6885,7 +7865,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Create it
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -7013,7 +7993,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Create it
</span><del>-        component = Component.fromString(data % self.subs)
</del><ins>+        component = Component.fromString(data % self.dtsubs)
</ins><span class="cx">         cobj = yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, component)
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -7039,7 +8019,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Update it
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        oldobj = yield cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.subs))
</del><ins>+        oldobj = yield cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.dtsubs))
</ins><span class="cx">         oldname = oldobj.name()
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -7076,7 +8056,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="cx">         title = &quot;user01&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = newUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s&quot; % (title,))
</span><span class="lines">@@ -7115,7 +8095,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Update it
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        oldobj = yield cobj.splitAt(DateTime.parseText(&quot;%(now_back15_12h)s&quot; % self.subs))
</del><ins>+        oldobj = yield cobj.splitAt(DateTime.parseText(&quot;%(now_back15_12h)s&quot; % self.dtsubs))
</ins><span class="cx">         oldname = oldobj.name()
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -7152,7 +8132,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="cx">         title = &quot;user01&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = newUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s&quot; % (title,))
</span><span class="lines">@@ -7191,7 +8171,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Update it
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        oldobj = yield cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.subs))
</del><ins>+        oldobj = yield cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.dtsubs))
</ins><span class="cx">         oldname = oldobj.name()
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -7228,7 +8208,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="cx">         title = &quot;user01&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = newUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s&quot; % (title,))
</span><span class="lines">@@ -7247,7 +8227,7 @@
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         cobjs = yield cal.calendarObjects()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><del>-        yield self.failUnlessFailure(cobjs[0].splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.subs)), InvalidSplit)
</del><ins>+        yield self.failUnlessFailure(cobjs[0].splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.dtsubs)), InvalidSplit)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -7262,7 +8242,7 @@
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         cobjs = yield cal.calendarObjects()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><del>-        yield self.failUnlessFailure(cobjs[0].splitAt(DateTime.parseText(&quot;%(now_back30)s&quot; % self.subs)), InvalidSplit)
</del><ins>+        yield self.failUnlessFailure(cobjs[0].splitAt(DateTime.parseText(&quot;%(now_back30)s&quot; % self.dtsubs)), InvalidSplit)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -7277,7 +8257,7 @@
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         cobjs = yield cal.calendarObjects()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><del>-        yield self.failUnlessFailure(cobjs[0].splitAt(DateTime.parseText(&quot;%(now_fwd25)s&quot; % self.subs)), InvalidSplit)
</del><ins>+        yield self.failUnlessFailure(cobjs[0].splitAt(DateTime.parseText(&quot;%(now_fwd25)s&quot; % self.dtsubs)), InvalidSplit)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -7291,7 +8271,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Update it
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        oldobj = yield cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.subs), pastUID=pastUID)
</del><ins>+        oldobj = yield cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.dtsubs), pastUID=pastUID)
</ins><span class="cx">         oldname = oldobj.name()
</span><span class="cx">         self.assertFalse(hasattr(cobj, &quot;_workItems&quot;))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -7330,7 +8310,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Verify user01 data
</span><span class="cx">         title = &quot;user01&quot;
</span><del>-        relsubs = dict(self.subs)
</del><ins>+        relsubs = dict(self.dtsubs)
</ins><span class="cx">         relsubs[&quot;uid&quot;] = newUID
</span><span class="cx">         relsubs[&quot;relID&quot;] = relID
</span><span class="cx">         self.assertEqual(normalize_iCalStr(ical_future), normalize_iCalStr(data_future) % relsubs, &quot;Failed future: %s&quot; % (title,))
</span><span class="lines">@@ -7370,7 +8350,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Update it
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        yield self.failUnlessFailure(cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.subs), pastUID=&quot;12345-67890&quot;), InvalidSplit)
</del><ins>+        yield self.failUnlessFailure(cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.dtsubs), pastUID=&quot;12345-67890&quot;), InvalidSplit)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -7400,17 +8380,64 @@
</span><span class="cx">         yield self._setupSplitAt()
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        component = Component.fromString(data_existing % self.subs)
</del><ins>+        component = Component.fromString(data_existing % self.dtsubs)
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data2.ics&quot;, component)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Update it
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><del>-        yield self.failUnlessFailure(cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.subs), pastUID=&quot;12345-67890-existing&quot;), InvalidSplit)
</del><ins>+        yield self.failUnlessFailure(cobj.splitAt(DateTime.parseText(&quot;%(now_back14)s&quot; % self.dtsubs), pastUID=&quot;12345-67890-existing&quot;), InvalidSplit)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def test_calendarObjectSplit_splitat_wrong_value_type(self):
+        &quot;&quot;&quot;
+        Test that user triggered splitting of calendar objects does not work if wrong rid value type is used.
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><del>-class TimeRangeUpdateOptimization(CommonCommonTests, unittest.TestCase):
</del><ins>+        yield self._setupSplitAt()
+
+        # DTSTART DATE-TIME UTC/rid DATE
+        cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
+        cobjs = yield cal.calendarObjects()
+        self.assertEqual(len(cobjs), 1)
+        yield self.failUnlessFailure(cobjs[0].splitAt(DateTime.parseText(&quot;%(nowDate)s&quot; % self.dtsubs)), InvalidSplit)
+
+        # DTSTART DATE-TIME UTC/rid DATE-TIME floating
+        yield self.failUnlessFailure(cobjs[0].splitAt(DateTime.parseText(&quot;%(nowFloating)s&quot; % self.dtsubs)), InvalidSplit)
+
+        data_floating = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-existing
+DTSTART:%(nowFloating)s
+DURATION:P1D
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=DAILY;COUNT=50
+SUMMARY:1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+ 1234567890123456789012345678901234567890
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
+        calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        component = Component.fromString(data_floating % self.dtsubs)
+        yield calendar.createCalendarObjectWithName(&quot;data2.ics&quot;, component)
+        yield self.commit()
+
+        # DTSTART DATE/rid DATE-TIME floating
+        cobj = yield self.calendarObjectUnderTest(name=&quot;data2.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        yield self.failUnlessFailure(cobj.splitAt(DateTime.parseText(&quot;%(nowFloating)s&quot; % self.dtsubs)), InvalidSplit)
+
+        # DTSTART DATE/rid DATE-TIME UTC
+        yield self.failUnlessFailure(cobj.splitAt(DateTime.parseText(&quot;%(now)s&quot; % self.dtsubs)), InvalidSplit)
+
+
+
+class TimeRangeUpdateOptimization(CommonCommonTests, DateTimeSubstitutionsMixin, unittest.TestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     CalendarObject time range optimization tests.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -7422,7 +8449,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7437,7 +8464,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7452,7 +8479,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T130000Z
</del><ins>+DTSTART:{nowDate}T130000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7467,7 +8494,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7483,7 +8510,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> STATUS:CANCELLED
</span><span class="lines">@@ -7499,7 +8526,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7515,7 +8542,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7531,7 +8558,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7547,12 +8574,12 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="cx"> RRULE:FREQ=DAILY
</span><del>-EXDATE:{now}T120000Z
</del><ins>+EXDATE:{nowDate}T120000Z
</ins><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -7564,12 +8591,12 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="cx"> RRULE:FREQ=DAILY
</span><del>-RDATE:{now}T150000Z
</del><ins>+RDATE:{nowDate}T150000Z
</ins><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -7581,12 +8608,7 @@
</span><span class="cx">         yield self.buildStoreAndDirectory()
</span><span class="cx">         yield self.populate()
</span><span class="cx"> 
</span><del>-        self.now = DateTime.getNowUTC()
-        self.now.setDateOnly(True)
-        self.now1 = self.now.duplicate()
-        self.now1.offsetDay(1)
-        self.now2 = self.now.duplicate()
-        self.now2.offsetDay(2)
</del><ins>+        self.setupDateTimeValues()
</ins><span class="cx"> 
</span><span class="cx">         self.trcount = 0
</span><span class="cx">         base_addInstances = CalendarObject._addInstances
</span><span class="lines">@@ -7630,7 +8652,7 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="lines">@@ -7644,14 +8666,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT does not cause T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT2.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT2.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="lines">@@ -7667,14 +8689,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT does cause T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT2.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT2.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 2)
</span><span class="lines">@@ -7688,14 +8710,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT3.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT3.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 2)
</span><span class="lines">@@ -7709,14 +8731,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT4.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT4.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 2)
</span><span class="lines">@@ -7730,14 +8752,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT5.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT5.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 2)
</span><span class="lines">@@ -7751,14 +8773,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT6.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT6.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 2)
</span><span class="lines">@@ -7772,14 +8794,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT7.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT7.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT8.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT8.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 2)
</span><span class="lines">@@ -7793,14 +8815,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT7.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT7.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT9.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT9.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 2)
</span><span class="lines">@@ -7814,14 +8836,14 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest()
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT7.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.EVENT7.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 1)
</span><span class="cx"> 
</span><span class="cx">         # Second PUT causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest()
</span><del>-        yield cobj.setComponent(Component.fromString(self.EVENT10.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.EVENT10.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 2)
</span><span class="lines">@@ -7834,7 +8856,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7852,7 +8874,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7870,7 +8892,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7888,7 +8910,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T140000Z
</del><ins>+DTSTART:{nowDate}T140000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7910,7 +8932,7 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.INVITE1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.INVITE1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 3)
</span><span class="lines">@@ -7919,21 +8941,21 @@
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user02&quot;, name=&quot;calendar&quot;)
</span><span class="cx">         cobjs = yield cal.calendarObjects()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><del>-        yield cobjs[0].setComponent(Component.fromString(self.INVITE2.format(now=self.now.getText())))
</del><ins>+        yield cobjs[0].setComponent(Component.fromString(self.INVITE2.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 5)
</span><span class="cx"> 
</span><span class="cx">         # Organizer summary change does not cause T-R change (except for inbox item)
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;)
</span><del>-        yield cobj.setComponent(Component.fromString(self.INVITE3.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.INVITE3.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 6)
</span><span class="cx"> 
</span><span class="cx">         # Organizer dtstart change causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;)
</span><del>-        yield cobj.setComponent(Component.fromString(self.INVITE4.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.INVITE4.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 9)
</span><span class="lines">@@ -7952,7 +8974,7 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.INVITE1.format(now=self.now.getText())))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.INVITE1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 3)
</span><span class="lines">@@ -7961,21 +8983,21 @@
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user02&quot;, name=&quot;calendar&quot;)
</span><span class="cx">         cobjs = yield cal.calendarObjects()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><del>-        yield cobjs[0].setComponent(Component.fromString(self.INVITE2.format(now=self.now.getText())))
</del><ins>+        yield cobjs[0].setComponent(Component.fromString(self.INVITE2.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 5)
</span><span class="cx"> 
</span><span class="cx">         # Organizer summary change causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;)
</span><del>-        yield cobj.setComponent(Component.fromString(self.INVITE3.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.INVITE3.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 8)
</span><span class="cx"> 
</span><span class="cx">         # Organizer dtstart change causes T-R change
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;)
</span><del>-        yield cobj.setComponent(Component.fromString(self.INVITE4.format(now=self.now.getText())))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.INVITE4.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         self.assertEqual(self.trcount, 11)
</span><span class="lines">@@ -7988,7 +9010,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -7999,8 +9021,8 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-RECURRENCE-ID:{now1}T120000Z
-DTSTART:{now1}T120000Z
</del><ins>+RECURRENCE-ID:{nowDate_fwd1}T120000Z
+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event now1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8018,8 +9040,8 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-RECURRENCE-ID:{now1}T120000Z
-DTSTART:{now1}T120000Z
</del><ins>+RECURRENCE-ID:{nowDate_fwd1}T120000Z
+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event now1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8037,7 +9059,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8048,8 +9070,8 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-RECURRENCE-ID:{now1}T120000Z
-DTSTART:{now1}T120000Z
</del><ins>+RECURRENCE-ID:{nowDate_fwd1}T120000Z
+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event now1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8060,8 +9082,8 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-RECURRENCE-ID:{now2}T120000Z
-DTSTART:{now2}T120000Z
</del><ins>+RECURRENCE-ID:{nowDate_fwd2}T120000Z
+DTSTART:{nowDate_fwd2}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event now2
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8079,7 +9101,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now}T120000Z
</del><ins>+DTSTART:{nowDate}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8090,8 +9112,8 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-RECURRENCE-ID:{now1}T120000Z
-DTSTART:{now1}T120000Z
</del><ins>+RECURRENCE-ID:{nowDate_fwd1}T120000Z
+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event now1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8101,8 +9123,8 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> CREATED:20100203T013849Z
</span><span class="cx"> UID:uid1
</span><del>-RECURRENCE-ID:{now2}T120000Z
-DTSTART:{now2}T120000Z
</del><ins>+RECURRENCE-ID:{nowDate_fwd2}T120000Z
+DTSTART:{nowDate_fwd2}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event now2
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8126,11 +9148,7 @@
</span><span class="cx"> 
</span><span class="cx">         # First PUT causes T-R change
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><del>-        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.INVITE_OVERRIDE1.format(
-            now=self.now.getText(),
-            now1=self.now1.getText(),
-            now2=self.now2.getText(),
-        )))
</del><ins>+        yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(self.INVITE_OVERRIDE1.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Wait for it to complete
</span><span class="lines">@@ -8140,11 +9158,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Organizer adds attendee to override causes T-R change (except for their item)
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;)
</span><del>-        yield cobj.setComponent(Component.fromString(self.INVITE_OVERRIDE3.format(
-            now=self.now.getText(),
-            now1=self.now1.getText(),
-            now2=self.now2.getText(),
-        )))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.INVITE_OVERRIDE3.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Wait for it to complete
</span><span class="lines">@@ -8154,11 +9168,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Organizer removes attendee from override causes T-R change (except for their item)
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(home=&quot;user01&quot;, calendar_name=&quot;calendar&quot;)
</span><del>-        yield cobj.setComponent(Component.fromString(self.INVITE_OVERRIDE4.format(
-            now=self.now.getText(),
-            now1=self.now1.getText(),
-            now2=self.now2.getText(),
-        )))
</del><ins>+        yield cobj.setComponent(Component.fromString(self.INVITE_OVERRIDE4.format(**self.dtsubs)))
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Wait for it to complete
</span><span class="lines">@@ -8168,7 +9178,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class GroupExpand(CommonCommonTests, unittest.TestCase):
</del><ins>+class GroupExpand(CommonCommonTests, DateTimeSubstitutionsMixin, unittest.TestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     CalendarObject group attendee expansion.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -8186,26 +9196,11 @@
</span><span class="cx"> 
</span><span class="cx">         yield self.populate()
</span><span class="cx"> 
</span><del>-        now = DateTime.getNowUTC()
-        now.setDateOnly(True)
-        past1 = now.duplicate()
-        past1.offsetDay(-1)
-        past2 = now.duplicate()
-        past2.offsetDay(-2)
-        past400 = now.duplicate()
</del><ins>+        self.setupDateTimeValues()
+
+        past400 = self.nowDate.duplicate()
</ins><span class="cx">         past400.offsetDay(-400)
</span><del>-        now1 = now.duplicate()
-        now1.offsetDay(1)
-        now2 = now.duplicate()
-        now2.offsetDay(2)
-        self.subs = {
-            &quot;now&quot;: now,
-            &quot;past1&quot;: past1,
-            &quot;past2&quot;: past2,
-            &quot;past400&quot;: past400,
-            &quot;now1&quot;: now1,
-            &quot;now2&quot;: now2,
-        }
</del><ins>+        self.dtsubs[&quot;nowDate_back400&quot;] = past400
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -8244,7 +9239,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8253,7 +9248,7 @@
</span><span class="cx"> ATTENDEE:urn:x-uid:group01
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8262,7 +9257,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8271,7 +9266,7 @@
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT causes expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -8298,7 +9293,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8306,7 +9301,7 @@
</span><span class="cx"> ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01@example.com
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event2 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8314,7 +9309,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8323,7 +9318,7 @@
</span><span class="cx"> ATTENDEE:urn:x-uid:group01
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8332,7 +9327,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8342,8 +9337,17 @@
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><ins>+        # Group user has no data
+        cal = yield self.calendarUnderTest(home=&quot;user02&quot;, name=&quot;calendar&quot;)
+        cobjs = yield cal.calendarObjects()
+        self.assertEqual(len(cobjs), 0)
+        cal = yield self.calendarUnderTest(home=&quot;user02&quot;, name=&quot;inbox&quot;)
+        cobjs = yield cal.calendarObjects()
+        self.assertEqual(len(cobjs), 0)
+        yield self.commit()
+
</ins><span class="cx">         # PUT does not cause expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="cx">         yield cal.createObjectResourceWithName(&quot;1.ics&quot;, Component.fromString(event1))
</span><span class="lines">@@ -8366,7 +9370,18 @@
</span><span class="cx">         links = yield calobj.groupEventLinks()
</span><span class="cx">         self.assertEqual(len(links), 1)
</span><span class="cx"> 
</span><ins>+        # Group user has invite data
+        cal = yield self.calendarUnderTest(home=&quot;user02&quot;, name=&quot;calendar&quot;)
+        cobjs = yield cal.calendarObjects()
+        self.assertEqual(len(cobjs), 1)
+        cal = yield self.calendarUnderTest(home=&quot;user02&quot;, name=&quot;inbox&quot;)
+        cobjs = yield cal.calendarObjects()
+        self.assertEqual(len(cobjs), 1)
+        comp = yield cobjs[0].componentForUser()
+        self.assertTrue(&quot;METHOD:REQUEST&quot; in str(comp))
+        yield self.commit()
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_expand_update_existing(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -8379,7 +9394,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8388,7 +9403,7 @@
</span><span class="cx"> ATTENDEE:urn:x-uid:group01
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event2 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8397,7 +9412,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8407,7 +9422,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event3 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8416,7 +9431,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T130000Z
</del><ins>+DTSTART:{nowDate_fwd1}T130000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8426,7 +9441,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8435,7 +9450,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T130000Z
</del><ins>+DTSTART:{nowDate_fwd1}T130000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8445,7 +9460,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT causes expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -8491,7 +9506,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8501,7 +9516,7 @@
</span><span class="cx"> RRULE:FREQ=DAILY
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8510,7 +9525,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8520,7 +9535,7 @@
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT causes expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -8547,7 +9562,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8556,7 +9571,7 @@
</span><span class="cx"> RRULE:FREQ=DAILY
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event2 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8564,7 +9579,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8574,7 +9589,7 @@
</span><span class="cx"> RRULE:FREQ=DAILY
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8583,7 +9598,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8594,7 +9609,7 @@
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT does not cause expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -8631,7 +9646,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8641,7 +9656,7 @@
</span><span class="cx"> RRULE:FREQ=DAILY
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event2 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8650,7 +9665,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T120000Z
</del><ins>+DTSTART:{nowDate_fwd1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8661,7 +9676,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event3 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8670,7 +9685,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T130000Z
</del><ins>+DTSTART:{nowDate_fwd1}T130000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8681,7 +9696,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8690,7 +9705,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{now1}T130000Z
</del><ins>+DTSTART:{nowDate_fwd1}T130000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8701,7 +9716,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT causes expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -8747,7 +9762,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8756,7 +9771,7 @@
</span><span class="cx"> ATTENDEE:urn:x-uid:group01
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8765,7 +9780,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8774,7 +9789,7 @@
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT causes expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -8801,7 +9816,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8809,7 +9824,7 @@
</span><span class="cx"> ATTENDEE;PARTSTAT=ACCEPTED:mailto:user01@example.com
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event2 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8817,7 +9832,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8826,7 +9841,7 @@
</span><span class="cx"> ATTENDEE:urn:x-uid:group01
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8835,7 +9850,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8845,7 +9860,7 @@
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT does not cause expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -8882,7 +9897,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -8891,7 +9906,7 @@
</span><span class="cx"> ATTENDEE:urn:x-uid:group01
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event2 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8900,7 +9915,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8910,7 +9925,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         event3 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8919,7 +9934,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{past1}T130000Z
</del><ins>+DTSTART:{nowDate_back1}T130000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8929,7 +9944,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -8938,7 +9953,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{past1}T130000Z
</del><ins>+DTSTART:{nowDate_back1}T130000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -8948,7 +9963,7 @@
</span><span class="cx"> SUMMARY:New Event #2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT causes expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -8995,7 +10010,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -9005,7 +10020,7 @@
</span><span class="cx"> RRULE:FREQ=YEARLY;INTERVAL=2
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -9014,7 +10029,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{past1}T120000Z
</del><ins>+DTSTART:{nowDate_back1}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -9024,7 +10039,7 @@
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT causes expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="lines">@@ -9052,7 +10067,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><del>-DTSTART:{past400}T120000Z
</del><ins>+DTSTART:{nowDate_back400}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><span class="lines">@@ -9062,7 +10077,7 @@
</span><span class="cx"> RRULE:FREQ=YEARLY;INTERVAL=4
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         result = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="lines">@@ -9071,7 +10086,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:uid1
</span><span class="cx"> DTSTAMP:20100203T013909Z
</span><del>-DTSTART:{past400}T120000Z
</del><ins>+DTSTART:{nowDate_back400}T120000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;PARTSTAT=ACCEPTED:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -9081,7 +10096,7 @@
</span><span class="cx"> SUMMARY:New Event
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><del>-&quot;&quot;&quot;.format(**self.subs)
</del><ins>+&quot;&quot;&quot;.format(**self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">         # PUT causes expansion
</span><span class="cx">         cal = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcaldavdatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/util.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/util.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/caldav/datastore/test/util.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -15,8 +15,10 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><ins>+from twisted.internet.defer import inlineCallbacks
</ins><span class="cx"> from twext.python.clsprop import classproperty
</span><del>-from twisted.internet.defer import inlineCallbacks
</del><ins>+from twistedcaldav.ical import Component, normalize_iCalStr, diff_iCalStrs
+from pycalendar.datetime import DateTime
</ins><span class="cx"> 
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> Store test utility functions
</span><span class="lines">@@ -70,3 +72,96 @@
</span><span class="cx">                 },
</span><span class="cx">             },
</span><span class="cx">         }
</span><ins>+
+
+
+class DateTimeSubstitutionsMixin(object):
+    &quot;&quot;&quot;
+    Mix-in class for tests that defines a set of str.format() substitutions for date-time values
+    relative to the current time. This allows tests to always use relative-to-now values rather
+    than fixed values which may become in valid in the future (e.g., a test that needs an event
+    in the future and uses 2017 works fine up until 2017 and then starts to fail.
+    &quot;&quot;&quot;
+
+    def setupDateTimeValues(self):
+
+        self.dtsubs = {}
+
+        # Set of &quot;now&quot; values that are directly accessible
+        self.now = DateTime.getNowUTC()
+        self.now.setHHMMSS(0, 0, 0)
+        self.now12 = DateTime.getNowUTC()
+        self.now12.setHHMMSS(12, 0, 0)
+        self.nowDate = self.now.duplicate()
+        self.nowDate.setDateOnly(True)
+        self.nowFloating = self.now.duplicate()
+        self.nowFloating.setTimezoneID(None)
+
+        self.dtsubs[&quot;now&quot;] = self.now
+        self.dtsubs[&quot;now12&quot;] = self.now12
+        self.dtsubs[&quot;nowDate&quot;] = self.nowDate
+        self.dtsubs[&quot;nowFloating&quot;] = self.nowFloating
+
+        # Values going 30 days back from now
+        for i in range(30):
+            attrname = &quot;now_back%s&quot; % (i + 1,)
+            setattr(self, attrname, self.now.duplicate())
+            getattr(self, attrname).offsetDay(-(i + 1))
+            self.dtsubs[attrname] = getattr(self, attrname)
+
+            attrname_12h = &quot;now_back%s_12h&quot; % (i + 1,)
+            setattr(self, attrname_12h, getattr(self, attrname).duplicate())
+            getattr(self, attrname_12h).offsetHours(12)
+            self.dtsubs[attrname_12h] = getattr(self, attrname_12h)
+
+            attrname_1 = &quot;now_back%s_1&quot; % (i + 1,)
+            setattr(self, attrname_1, getattr(self, attrname).duplicate())
+            getattr(self, attrname_1).offsetSeconds(-1)
+            self.dtsubs[attrname_1] = getattr(self, attrname_1)
+
+            attrname = &quot;nowDate_back%s&quot; % (i + 1,)
+            setattr(self, attrname, self.nowDate.duplicate())
+            getattr(self, attrname).offsetDay(-(i + 1))
+            self.dtsubs[attrname] = getattr(self, attrname)
+
+            attrname = &quot;nowFloating_back%s&quot; % (i + 1,)
+            setattr(self, attrname, self.nowFloating.duplicate())
+            getattr(self, attrname).offsetDay(-(i + 1))
+            self.dtsubs[attrname] = getattr(self, attrname)
+
+            attrname_1 = &quot;nowFloating_back%s_1&quot; % (i + 1,)
+            setattr(self, attrname_1, getattr(self, attrname).duplicate())
+            getattr(self, attrname_1).offsetSeconds(-1)
+            self.dtsubs[attrname_1] = getattr(self, attrname_1)
+
+        # Values going 30 days forward from now
+        for i in range(30):
+            attrname = &quot;now_fwd%s&quot; % (i + 1,)
+            setattr(self, attrname, self.now.duplicate())
+            getattr(self, attrname).offsetDay(i + 1)
+            self.dtsubs[attrname] = getattr(self, attrname)
+
+            attrname_12h = &quot;now_fwd%s_12h&quot; % (i + 1,)
+            setattr(self, attrname_12h, getattr(self, attrname).duplicate())
+            getattr(self, attrname_12h).offsetHours(12)
+            self.dtsubs[attrname_12h] = getattr(self, attrname_12h)
+
+            attrname = &quot;nowDate_fwd%s&quot; % (i + 1,)
+            setattr(self, attrname, self.nowDate.duplicate())
+            getattr(self, attrname).offsetDay(i + 1)
+            self.dtsubs[attrname] = getattr(self, attrname)
+
+            attrname = &quot;nowFloating_fwd%s&quot; % (i + 1,)
+            setattr(self, attrname, self.nowFloating.duplicate())
+            getattr(self, attrname).offsetDay(i + 1)
+            self.dtsubs[attrname] = getattr(self, attrname)
+
+
+    def assertEqualCalendarData(self, cal1, cal2):
+        if isinstance(cal1, str):
+            cal1 = Component.fromString(cal1)
+        if isinstance(cal2, str):
+            cal2 = Component.fromString(cal2)
+        ncal1 = normalize_iCalStr(cal1)
+        ncal2 = normalize_iCalStr(cal2)
+        self.assertEqual(ncal1, ncal2, msg=diff_iCalStrs(ncal1, ncal2))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcarddavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/carddav/datastore/sql.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/carddav/datastore/sql.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/carddav/datastore/sql.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -570,7 +570,8 @@
</span><span class="cx">             {
</span><span class="cx">                 rev.REVISION: schema.REVISION_SEQ,
</span><span class="cx">                 rev.OBJECT_RESOURCE_ID: Parameter(&quot;id&quot;),
</span><del>-                rev.DELETED: True
</del><ins>+                rev.DELETED: True,
+                rev.MODIFIED: utcNowSQL,
</ins><span class="cx">             },
</span><span class="cx">             Where=(
</span><span class="cx">                 rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;)).And(
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcarddavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/carddav/datastore/test/test_sql.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/carddav/datastore/test/test_sql.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/carddav/datastore/test/test_sql.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -28,20 +28,21 @@
</span><span class="cx"> from twisted.trial import unittest
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import carddavxml
</span><del>-from twistedcaldav.vcard import Component as VCard
</del><ins>+from twistedcaldav.vcard import Component as VCard, Component
</ins><span class="cx"> from twistedcaldav.vcard import Component as VComponent
</span><span class="cx"> 
</span><span class="cx"> from txdav.base.propertystore.base import PropertyName
</span><span class="cx"> 
</span><span class="cx"> from txdav.carddav.datastore.test.common import CommonTests as AddressBookCommonTests, \
</span><del>-    vcard4_text
</del><ins>+    vcard4_text, adbk1Root
</ins><span class="cx"> from txdav.carddav.datastore.test.test_file import setUpAddressBookStore
</span><span class="cx"> from txdav.carddav.datastore.util import _migrateAddressbook, migrateHome
</span><span class="cx"> 
</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 cleanStore
</del><ins>+from txdav.common.datastore.test.util import cleanStore, CommonCommonTests, \
+    populateAddressBooksFrom
</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">@@ -919,7 +920,38 @@
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+class SyncTests(CommonCommonTests, unittest.TestCase):
+    &quot;&quot;&quot;
+    Revision table/sync report tests.
+    &quot;&quot;&quot;
+
</ins><span class="cx">     @inlineCallbacks
</span><ins>+    def setUp(self):
+        yield super(SyncTests, self).setUp()
+        yield self.buildStoreAndDirectory()
+        yield self.populate()
+
+
+    requirements = {
+        &quot;user01&quot;: {
+            &quot;addressbook&quot;: {
+                &quot;1.vcf&quot;: adbk1Root.child(&quot;1.vcf&quot;).getContent(),
+                &quot;2.vcf&quot;: adbk1Root.child(&quot;2.vcf&quot;).getContent(),
+                &quot;3.vcf&quot;: adbk1Root.child(&quot;3.vcf&quot;).getContent(),
+            },
+            &quot;not_a_addressbook&quot;: None
+        },
+    }
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateAddressBooksFrom(self.requirements, self.storeUnderTest())
+        self.notifierFactory.reset()
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_updateAfterRevisionCleanup(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Make sure L{AddressBookObject}'s can be updated or removed after revision cleanup
</span><span class="lines">@@ -957,26 +989,26 @@
</span><span class="cx"> END:VCARD
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        yield self.homeUnderTest()
-        adbk = yield self.addressbookUnderTest(name=&quot;addressbook&quot;)
</del><ins>+        yield self.addressbookHomeUnderTest(name=&quot;user01&quot;)
+        adbk = yield self.addressbookUnderTest(home=&quot;user01&quot;, name=&quot;addressbook&quot;)
</ins><span class="cx">         yield adbk.createAddressBookObjectWithName(&quot;person.vcf&quot;, VCard.fromString(person))
</span><span class="cx">         yield adbk.createAddressBookObjectWithName(&quot;group.vcf&quot;, VCard.fromString(group))
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Remove the revision
</span><del>-        adbk = yield self.addressbookUnderTest(name=&quot;addressbook&quot;)
</del><ins>+        adbk = yield self.addressbookUnderTest(home=&quot;user01&quot;, name=&quot;addressbook&quot;)
</ins><span class="cx">         yield adbk.syncToken()
</span><span class="cx">         yield self.transactionUnderTest().deleteRevisionsBefore(adbk._syncTokenRevision + 1)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Update the object
</span><del>-        obj = yield self.addressbookObjectUnderTest(name=&quot;group.vcf&quot;, addressbook_name=&quot;addressbook&quot;)
</del><ins>+        obj = yield self.addressbookObjectUnderTest(name=&quot;group.vcf&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         yield obj.setComponent(VCard.fromString(group_update))
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        obj = yield self.addressbookObjectUnderTest(name=&quot;group.vcf&quot;, addressbook_name=&quot;addressbook&quot;)
</del><ins>+        obj = yield self.addressbookObjectUnderTest(name=&quot;group.vcf&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         self.assertTrue(obj is not None)
</span><del>-        obj = yield self.addressbookObjectUnderTest(name=&quot;person.vcf&quot;, addressbook_name=&quot;addressbook&quot;)
</del><ins>+        obj = yield self.addressbookObjectUnderTest(name=&quot;person.vcf&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         self.assertTrue(obj is not None)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -1010,26 +1042,76 @@
</span><span class="cx"> END:VCARD
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        yield self.homeUnderTest()
-        adbk = yield self.addressbookUnderTest(name=&quot;addressbook&quot;)
</del><ins>+        yield self.addressbookHomeUnderTest(name=&quot;user01&quot;)
+        adbk = yield self.addressbookUnderTest(home=&quot;user01&quot;, name=&quot;addressbook&quot;)
</ins><span class="cx">         yield adbk.createAddressBookObjectWithName(&quot;person.vcf&quot;, VCard.fromString(person))
</span><span class="cx">         yield adbk.createAddressBookObjectWithName(&quot;group.vcf&quot;, VCard.fromString(group))
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Remove the revision
</span><del>-        adbk = yield self.addressbookUnderTest(name=&quot;addressbook&quot;)
</del><ins>+        adbk = yield self.addressbookUnderTest(home=&quot;user01&quot;, name=&quot;addressbook&quot;)
</ins><span class="cx">         yield adbk.syncToken()
</span><span class="cx">         yield self.transactionUnderTest().deleteRevisionsBefore(adbk._syncTokenRevision + 1)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Remove the object
</span><del>-        obj = yield self.addressbookObjectUnderTest(name=&quot;group.vcf&quot;, addressbook_name=&quot;addressbook&quot;)
</del><ins>+        obj = yield self.addressbookObjectUnderTest(name=&quot;group.vcf&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         self.assertTrue(obj is not None)
</span><span class="cx">         yield obj.remove()
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><del>-        obj = yield self.addressbookObjectUnderTest(name=&quot;group.vcf&quot;, addressbook_name=&quot;addressbook&quot;)
</del><ins>+        obj = yield self.addressbookObjectUnderTest(name=&quot;group.vcf&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         self.assertTrue(obj is None)
</span><del>-        obj = yield self.addressbookObjectUnderTest(name=&quot;person.vcf&quot;, addressbook_name=&quot;addressbook&quot;)
</del><ins>+        obj = yield self.addressbookObjectUnderTest(name=&quot;person.vcf&quot;, addressbook_name=&quot;addressbook&quot;, home=&quot;user01&quot;)
</ins><span class="cx">         self.assertTrue(obj is not None)
</span><span class="cx">         yield self.commit()
</span><ins>+
+
+    @inlineCallbacks
+    def test_revisionModified(self):
+        &quot;&quot;&quot;
+        Make sure the revision table MODIFIED value changes for an update or delete
+        &quot;&quot;&quot;
+
+        @inlineCallbacks
+        def _getModified():
+            home = yield self.addressbookHomeUnderTest(name=&quot;user01&quot;)
+            addressbook = yield self.addressbookUnderTest(home=&quot;user01&quot;, name=&quot;addressbook&quot;)
+            rev = addressbook._revisionsSchema
+            modified = yield Select(
+                [rev.MODIFIED, ],
+                From=rev,
+                Where=(
+                    rev.ADDRESSBOOK_HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)).And(
+                    rev.RESOURCE_NAME == Parameter(&quot;resourceName&quot;)
+                )
+            ).on(
+                home._txn,
+                homeID=home.id(),
+                resourceName=&quot;1.vcf&quot;,
+            )
+            yield self.commit()
+            returnValue(modified[0][0])
+
+        # Get current modified
+        old_modified = yield _getModified()
+        self.assertNotEqual(old_modified, None)
+
+        # Update resource
+        aobj = yield self.addressbookObjectUnderTest(home=&quot;user01&quot;, addressbook_name=&quot;addressbook&quot;, name=&quot;1.vcf&quot;)
+        yield aobj.setComponent(Component.fromString(adbk1Root.child(&quot;1.vcf&quot;).getContent()))
+        yield self.commit()
+
+        # Modified changed
+        update_modified = yield _getModified()
+        self.assertGreater(update_modified, old_modified)
+
+        # Delete resource
+        aobj = yield self.addressbookObjectUnderTest(home=&quot;user01&quot;, addressbook_name=&quot;addressbook&quot;, name=&quot;1.vcf&quot;)
+        yield aobj.remove()
+        yield self.commit()
+
+        # Modified changed
+        delete_modified = yield _getModified()
+        self.assertGreater(delete_modified, old_modified)
+        self.assertGreater(delete_modified, update_modified)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastorefilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/file.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/file.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/file.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -276,7 +276,8 @@
</span><span class="cx">                             continue
</span><span class="cx">                         txn = self.newTransaction(&quot;enumerate home %r&quot; % (uid,))
</span><span class="cx">                         home = txn.homeWithUID(storeType, uid, False)
</span><del>-                        yield (txn, home)
</del><ins>+                        if home is not None:
+                            yield (txn, home)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def _eachCalendarHome(self):
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastorepoddingattachmentspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/podding/attachments.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/podding/attachments.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/podding/attachments.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -69,7 +69,7 @@
</span><span class="cx">             request[&quot;stream&quot;],
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        returnValue((attachment.managedID(), location,))
</del><ins>+        returnValue((attachment.managedID(), attachment.size(), location,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -115,7 +115,7 @@
</span><span class="cx">             request[&quot;stream&quot;],
</span><span class="cx">         )
</span><span class="cx"> 
</span><del>-        returnValue((attachment.managedID(), location,))
</del><ins>+        returnValue((attachment.managedID(), attachment.size(), location,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastorepoddingtesttest_conduitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/podding/test/test_conduit.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/podding/test/test_conduit.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/podding/test/test_conduit.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -970,11 +970,13 @@
</span><span class="cx">         yield self.commitTransaction(0)
</span><span class="cx"> 
</span><span class="cx">         shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
</span><del>-        attachment, location = yield shared_object.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some text.&quot;))
</del><ins>+        data = &quot;Here is some text.&quot;
+        attachment, location = yield shared_object.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(data))
</ins><span class="cx">         managedID = attachment.managedID()
</span><span class="cx">         from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
</span><span class="cx">         self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
</span><del>-        self.assertTrue(&quot;user01/attachments/test&quot; in location)
</del><ins>+        self.assertEqual(attachment.size(), len(data))
+        self.assertTrue(&quot;user01/dropbox/&quot; in location)
</ins><span class="cx">         yield self.commitTransaction(1)
</span><span class="cx"> 
</span><span class="cx">         cobjs = yield ManagedAttachment.referencesTo(self.theTransactionUnderTest(0), managedID)
</span><span class="lines">@@ -1005,11 +1007,13 @@
</span><span class="cx">         yield self.commitTransaction(0)
</span><span class="cx"> 
</span><span class="cx">         shared_object = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), home=&quot;puser01&quot;, calendar_name=&quot;shared-calendar&quot;, name=&quot;1.ics&quot;)
</span><del>-        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;))
</del><ins>+        data = &quot;Here is some more text.&quot;
+        attachment, location = yield shared_object.updateAttachment(managedID, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(data))
</ins><span class="cx">         managedID = attachment.managedID()
</span><span class="cx">         from txdav.caldav.datastore.sql_external import ManagedAttachmentExternal
</span><span class="cx">         self.assertTrue(isinstance(attachment, ManagedAttachmentExternal))
</span><del>-        self.assertTrue(&quot;user01/attachments/test&quot; in location)
</del><ins>+        self.assertEqual(attachment.size(), len(data))
+        self.assertTrue(&quot;user01/dropbox/&quot; in location)
</ins><span class="cx">         yield self.commitTransaction(1)
</span><span class="cx"> 
</span><span class="cx">         cobjs = yield ManagedAttachment.referencesTo(self.theTransactionUnderTest(0), managedID)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -3409,8 +3409,7 @@
</span><span class="cx">             )
</span><span class="cx"> 
</span><span class="cx">             # Get revisions
</span><del>-            revisions = (yield cls._revisionsForResourceIDs(childResourceIDs).on(home._txn, resourceIDs=childResourceIDs))
-            revisions = dict(revisions)
</del><ins>+            revisions = yield cls.childSyncTokenRevisions(home, childResourceIDs)
</ins><span class="cx"> 
</span><span class="cx">         # Create the actual objects merging in properties
</span><span class="cx">         for dataRow in dataRows:
</span><span class="lines">@@ -3421,7 +3420,7 @@
</span><span class="cx">             propstore = propertyStores.get(resourceID, None)
</span><span class="cx"> 
</span><span class="cx">             child = yield cls.makeClass(home, bindData, additionalBindData, metadataData, propstore)
</span><del>-            child._syncTokenRevision = revisions.get(resourceID, 0)
</del><ins>+            child._syncTokenRevision = revisions.get(resourceID, None)
</ins><span class="cx">             results.append(child)
</span><span class="cx"> 
</span><span class="cx">         returnValue(results)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemacurrentoracledialectsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/current-oracle-dialect.sql (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/current-oracle-dialect.sql        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/current-oracle-dialect.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -413,6 +413,12 @@
</span><span class="cx">     primary key (&quot;ORGANIZER&quot;, &quot;ATTENDEE&quot;, &quot;ICALUID&quot;)
</span><span class="cx"> );
</span><span class="cx"> 
</span><ins>+create table TEST_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;DELAY&quot; integer
+);
+
</ins><span class="cx"> create table IMIP_INVITATION_WORK (
</span><span class="cx">     &quot;WORK_ID&quot; integer primary key,
</span><span class="cx">     &quot;JOB_ID&quot; integer not null references JOB,
</span><span class="lines">@@ -669,7 +675,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', '55');
</del><ins>+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '57');
</ins><span class="cx"> insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '6');
</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 class="lines">@@ -821,6 +827,10 @@
</span><span class="cx">     &quot;TOKEN&quot;
</span><span class="cx"> );
</span><span class="cx"> 
</span><ins>+create index TEST_WORK_JOB_ID_228ede32 on TEST_WORK (
+    &quot;JOB_ID&quot;
+);
+
</ins><span class="cx"> create index IMIP_INVITATION_WORK__586d064c on IMIP_INVITATION_WORK (
</span><span class="cx">     &quot;JOB_ID&quot;
</span><span class="cx"> );
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemacurrentsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/current.sql (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/current.sql        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/current.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -765,7 +765,20 @@
</span><span class="cx"> 
</span><span class="cx"> create sequence WORKITEM_SEQ;
</span><span class="cx"> 
</span><ins>+---------------
+-- Test Work --
+---------------
</ins><span class="cx"> 
</span><ins>+create table TEST_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  DELAY                                                 integer
+);
+
+create index TEST_WORK_JOB_ID on
+  TEST_WORK(JOB_ID);
+
+
</ins><span class="cx"> ---------------------------
</span><span class="cx"> -- IMIP Inivitation Work --
</span><span class="cx"> ---------------------------
</span><span class="lines">@@ -1269,7 +1282,7 @@
</span><span class="cx">   VALUE                         varchar(255)
</span><span class="cx"> );
</span><span class="cx"> 
</span><del>-insert into CALENDARSERVER values ('VERSION', '55');
</del><ins>+insert into CALENDARSERVER values ('VERSION', '57');
</ins><span class="cx"> insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '6');
</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="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaoldoracledialectv55sqlfromrev15009CalendarServertrunktxdavcommondatastoresql_schemaoldoracledialectv55sql"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/oracle-dialect/v55.sql (from rev 15009, CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v55.sql) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/oracle-dialect/v55.sql                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/oracle-dialect/v55.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,1034 @@
</span><ins>+create sequence RESOURCE_ID_SEQ;
+create sequence JOB_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 JOB (
+    &quot;JOB_ID&quot; integer primary key,
+    &quot;WORK_TYPE&quot; nvarchar2(255),
+    &quot;PRIORITY&quot; integer default 0,
+    &quot;WEIGHT&quot; integer default 0,
+    &quot;NOT_BEFORE&quot; timestamp not null,
+    &quot;ASSIGNED&quot; timestamp default null,
+    &quot;OVERDUE&quot; timestamp default null,
+    &quot;FAILED&quot; integer default 0,
+    &quot;PAUSE&quot; integer default 0
+);
+
+create table CALENDAR_HOME (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;OWNER_UID&quot; nvarchar2(255),
+    &quot;STATUS&quot; integer default 0 not null,
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    unique (&quot;OWNER_UID&quot;, &quot;STATUS&quot;)
+);
+
+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);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('purging', 2);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('migrating', 3);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('disabled', 4);
+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;TRASH&quot; integer default null references CALENDAR on delete set 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;CHILD_TYPE&quot; integer default 0 not null,
+    &quot;TRASHED&quot; timestamp default null,
+    &quot;IS_IN_TRASH&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 CHILD_TYPE (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into CHILD_TYPE (DESCRIPTION, ID) values ('normal', 0);
+insert into CHILD_TYPE (DESCRIPTION, ID) values ('inbox', 1);
+insert into CHILD_TYPE (DESCRIPTION, ID) values ('trash', 2);
+create table CALENDAR_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer references CALENDAR_HOME on delete cascade,
+    &quot;REMOTE_RESOURCE_ID&quot; integer not null,
+    &quot;LOCAL_RESOURCE_ID&quot; integer references CALENDAR on delete cascade,
+    &quot;LAST_SYNC_TOKEN&quot; nvarchar2(255), 
+    primary key (&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;REMOTE_RESOURCE_ID&quot;)
+);
+
+create table NOTIFICATION_HOME (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;OWNER_UID&quot; nvarchar2(255),
+    &quot;STATUS&quot; integer default 0 not null,
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    unique (&quot;OWNER_UID&quot;, &quot;STATUS&quot;)
+);
+
+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;BIND_UID&quot; nvarchar2(36) default 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);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group', 5);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group_read', 6);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group_write', 7);
+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;TRASHED&quot; timestamp default null,
+    &quot;ORIGINAL_COLLECTION&quot; integer default null,
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    unique (&quot;CALENDAR_RESOURCE_ID&quot;, &quot;RESOURCE_NAME&quot;)
+);
+
+create table CALENDAR_OBJ_ATTACHMENTS_MODE (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (DESCRIPTION, ID) values ('none', 0);
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (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 PERUSER (
+    &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,
+    &quot;ADJUSTED_START_DATE&quot; timestamp default null,
+    &quot;ADJUSTED_END_DATE&quot; timestamp default null, 
+    primary key (&quot;TIME_RANGE_INSTANCE_ID&quot;, &quot;USER_ID&quot;)
+);
+
+create table CALENDAR_OBJECT_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer references CALENDAR_HOME on delete cascade,
+    &quot;REMOTE_RESOURCE_ID&quot; integer not null,
+    &quot;LOCAL_RESOURCE_ID&quot; integer references CALENDAR_OBJECT on delete cascade, 
+    primary key (&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;REMOTE_RESOURCE_ID&quot;)
+);
+
+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 ATTACHMENT_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer references CALENDAR_HOME on delete cascade,
+    &quot;REMOTE_RESOURCE_ID&quot; integer not null,
+    &quot;LOCAL_RESOURCE_ID&quot; integer references ATTACHMENT on delete cascade, 
+    primary key (&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;REMOTE_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),
+    &quot;STATUS&quot; integer default 0 not null,
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    unique (&quot;OWNER_UID&quot;, &quot;STATUS&quot;)
+);
+
+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;BIND_UID&quot; nvarchar2(36) default 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;TRASHED&quot; timestamp default null,
+    &quot;IS_IN_TRASH&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',
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    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,
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    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;BIND_UID&quot; nvarchar2(36) default 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,
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique (&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;CALENDAR_RESOURCE_ID&quot;, &quot;CALENDAR_NAME&quot;, &quot;RESOURCE_NAME&quot;)
+);
+
+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,
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique (&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;OWNER_HOME_RESOURCE_ID&quot;, &quot;ADDRESSBOOK_NAME&quot;, &quot;RESOURCE_NAME&quot;)
+);
+
+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,
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    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,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &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,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table IMIP_REPLY_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &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,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;PUSH_ID&quot; nvarchar2(255),
+    &quot;PUSH_PRIORITY&quot; integer not null
+);
+
+create table GROUP_CACHER_POLLING_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table GROUP_REFRESH_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;GROUP_UID&quot; nvarchar2(255)
+);
+
+create table GROUP_DELEGATE_CHANGES_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;DELEGATOR_UID&quot; nvarchar2(255),
+    &quot;READ_DELEGATE_UID&quot; nvarchar2(255),
+    &quot;WRITE_DELEGATE_UID&quot; nvarchar2(255)
+);
+
+create table GROUPS (
+    &quot;GROUP_ID&quot; integer primary key,
+    &quot;NAME&quot; nvarchar2(255),
+    &quot;GROUP_UID&quot; nvarchar2(255) unique,
+    &quot;MEMBERSHIP_HASH&quot; nvarchar2(255),
+    &quot;EXTANT&quot; integer default 1,
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table GROUP_MEMBERSHIP (
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade,
+    &quot;MEMBER_UID&quot; nvarchar2(255), 
+    primary key (&quot;GROUP_ID&quot;, &quot;MEMBER_UID&quot;)
+);
+
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade
+);
+
+create table GROUP_ATTENDEE (
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;MEMBERSHIP_HASH&quot; nvarchar2(255), 
+    primary key (&quot;GROUP_ID&quot;, &quot;RESOURCE_ID&quot;)
+);
+
+create table GROUP_SHAREE_RECONCILE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;CALENDAR_ID&quot; integer not null references CALENDAR on delete cascade,
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade
+);
+
+create table GROUP_SHAREE (
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade,
+    &quot;CALENDAR_ID&quot; integer not null references CALENDAR on delete cascade,
+    &quot;GROUP_BIND_MODE&quot; integer not null,
+    &quot;MEMBERSHIP_HASH&quot; nvarchar2(255), 
+    primary key (&quot;GROUP_ID&quot;, &quot;CALENDAR_ID&quot;)
+);
+
+create table DELEGATES (
+    &quot;DELEGATOR&quot; nvarchar2(255),
+    &quot;DELEGATE&quot; nvarchar2(255),
+    &quot;READ_WRITE&quot; integer not null, 
+    primary key (&quot;DELEGATOR&quot;, &quot;READ_WRITE&quot;, &quot;DELEGATE&quot;)
+);
+
+create table DELEGATE_GROUPS (
+    &quot;DELEGATOR&quot; nvarchar2(255),
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade,
+    &quot;READ_WRITE&quot; integer not null,
+    &quot;IS_EXTERNAL&quot; integer not null, 
+    primary key (&quot;DELEGATOR&quot;, &quot;READ_WRITE&quot;, &quot;GROUP_ID&quot;)
+);
+
+create table EXTERNAL_DELEGATE_GROUPS (
+    &quot;DELEGATOR&quot; nvarchar2(255) primary key,
+    &quot;GROUP_UID_READ&quot; nvarchar2(255),
+    &quot;GROUP_UID_WRITE&quot; nvarchar2(255)
+);
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table CALENDAR_OBJECT_UPGRADE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table FIND_MIN_VALID_REVISION_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table REVISION_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table INBOX_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table CLEANUP_ONE_INBOX_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;HOME_ID&quot; integer not null unique references CALENDAR_HOME on delete cascade
+);
+
+create table SCHEDULE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;ICALENDAR_UID&quot; nvarchar2(255),
+    &quot;WORK_TYPE&quot; nvarchar2(255)
+);
+
+create table SCHEDULE_REFRESH_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;ATTENDEE_COUNT&quot; integer
+);
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;ATTENDEE&quot; nvarchar2(255), 
+    primary key (&quot;RESOURCE_ID&quot;, &quot;ATTENDEE&quot;)
+);
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;PARTSTAT&quot; nvarchar2(255)
+);
+
+create table SCHEDULE_ORGANIZER_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;SCHEDULE_ACTION&quot; integer not null,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer,
+    &quot;ICALENDAR_TEXT_OLD&quot; nclob,
+    &quot;ICALENDAR_TEXT_NEW&quot; nclob,
+    &quot;ATTENDEE_COUNT&quot; integer,
+    &quot;SMART_MERGE&quot; integer
+);
+
+create table SCHEDULE_ACTION (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('create', 0);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify', 1);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify-cancelled', 2);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 3);
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;SCHEDULE_ACTION&quot; integer not null,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer,
+    &quot;ATTENDEE&quot; nvarchar2(255),
+    &quot;ITIP_MSG&quot; nclob,
+    &quot;NO_REFRESH&quot; integer
+);
+
+create table SCHEDULE_REPLY_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer,
+    &quot;ITIP_MSG&quot; nclob
+);
+
+create table PRINCIPAL_PURGE_POLLING_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table PRINCIPAL_PURGE_CHECK_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;UID&quot; nvarchar2(255)
+);
+
+create table PRINCIPAL_PURGE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;UID&quot; nvarchar2(255)
+);
+
+create table PRINCIPAL_PURGE_HOME_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade
+);
+
+create table MIGRATION_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade
+);
+
+create table HOME_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;OWNER_UID&quot; nvarchar2(255)
+);
+
+create table MIGRATED_HOME_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;OWNER_UID&quot; nvarchar2(255)
+);
+
+create table CALENDARSERVER (
+    &quot;NAME&quot; nvarchar2(255) primary key,
+    &quot;VALUE&quot; nvarchar2(255)
+);
+
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '55');
+insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '6');
+insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER (NAME, VALUE) values ('MIN-VALID-REVISION', '1');
+create index CALENDAR_HOME_METADAT_475de898 on CALENDAR_HOME_METADATA (
+    &quot;TRASH&quot;
+);
+
+create index CALENDAR_HOME_METADAT_3cb9049e on CALENDAR_HOME_METADATA (
+    &quot;DEFAULT_EVENTS&quot;
+);
+
+create index CALENDAR_HOME_METADAT_d55e5548 on CALENDAR_HOME_METADATA (
+    &quot;DEFAULT_TASKS&quot;
+);
+
+create index CALENDAR_HOME_METADAT_910264ce on CALENDAR_HOME_METADATA (
+    &quot;DEFAULT_POLLS&quot;
+);
+
+create index CALENDAR_MIGRATION_LO_0525c72b on CALENDAR_MIGRATION (
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index NOTIFICATION_NOTIFICA_f891f5f9 on NOTIFICATION (
+    &quot;NOTIFICATION_HOME_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_BIND_RESOURC_e57964d4 on CALENDAR_BIND (
+    &quot;CALENDAR_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_CALEN_a9a453a9 on CALENDAR_OBJECT (
+    &quot;CALENDAR_RESOURCE_ID&quot;,
+    &quot;ICALENDAR_UID&quot;
+);
+
+create index CALENDAR_OBJECT_CALEN_c4dc619c on CALENDAR_OBJECT (
+    &quot;CALENDAR_RESOURCE_ID&quot;,
+    &quot;RECURRANCE_MAX&quot;,
+    &quot;RECURRANCE_MIN&quot;
+);
+
+create index CALENDAR_OBJECT_ICALE_82e731d5 on CALENDAR_OBJECT (
+    &quot;ICALENDAR_UID&quot;
+);
+
+create index CALENDAR_OBJECT_DROPB_de041d80 on CALENDAR_OBJECT (
+    &quot;DROPBOX_ID&quot;
+);
+
+create index TIME_RANGE_CALENDAR_R_beb6e7eb on TIME_RANGE (
+    &quot;CALENDAR_RESOURCE_ID&quot;
+);
+
+create index TIME_RANGE_CALENDAR_O_acf37bd1 on TIME_RANGE (
+    &quot;CALENDAR_OBJECT_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_MIGRA_0502cbef on CALENDAR_OBJECT_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot;,
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_MIGRA_3577efd9 on CALENDAR_OBJECT_MIGRATION (
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index ATTACHMENT_CALENDAR_H_0078845c on ATTACHMENT (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot;
+);
+
+create index ATTACHMENT_DROPBOX_ID_5073cf23 on ATTACHMENT (
+    &quot;DROPBOX_ID&quot;
+);
+
+create index ATTACHMENT_CALENDAR_O_81508484 on ATTACHMENT_CALENDAR_OBJECT (
+    &quot;CALENDAR_OBJECT_RESOURCE_ID&quot;
+);
+
+create index ATTACHMENT_MIGRATION__804bf85e on ATTACHMENT_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot;,
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index ATTACHMENT_MIGRATION__816947fe on ATTACHMENT_MIGRATION (
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index SHARED_ADDRESSBOOK_BI_e9a2e6d4 on SHARED_ADDRESSBOOK_BIND (
+    &quot;OWNER_HOME_RESOURCE_ID&quot;
+);
+
+create index ABO_MEMBERS_ADDRESSBO_4effa879 on ABO_MEMBERS (
+    &quot;ADDRESSBOOK_ID&quot;
+);
+
+create index ABO_MEMBERS_MEMBER_ID_8d66adcf on ABO_MEMBERS (
+    &quot;MEMBER_ID&quot;
+);
+
+create index ABO_FOREIGN_MEMBERS_A_1fd2c5e9 on ABO_FOREIGN_MEMBERS (
+    &quot;ADDRESSBOOK_ID&quot;
+);
+
+create index SHARED_GROUP_BIND_RES_cf52f95d on SHARED_GROUP_BIND (
+    &quot;GROUP_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_REVIS_6d9d929c on CALENDAR_OBJECT_REVISIONS (
+    &quot;CALENDAR_RESOURCE_ID&quot;,
+    &quot;RESOURCE_NAME&quot;,
+    &quot;DELETED&quot;,
+    &quot;REVISION&quot;
+);
+
+create index CALENDAR_OBJECT_REVIS_265c8acf on CALENDAR_OBJECT_REVISIONS (
+    &quot;CALENDAR_RESOURCE_ID&quot;,
+    &quot;REVISION&quot;
+);
+
+create index CALENDAR_OBJECT_REVIS_550b1c56 on CALENDAR_OBJECT_REVISIONS (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot;,
+    &quot;REVISION&quot;
+);
+
+create index ADDRESSBOOK_OBJECT_RE_00fe8288 on ADDRESSBOOK_OBJECT_REVISIONS (
+    &quot;OWNER_HOME_RESOURCE_ID&quot;,
+    &quot;RESOURCE_NAME&quot;,
+    &quot;DELETED&quot;,
+    &quot;REVISION&quot;
+);
+
+create index ADDRESSBOOK_OBJECT_RE_45004780 on ADDRESSBOOK_OBJECT_REVISIONS (
+    &quot;OWNER_HOME_RESOURCE_ID&quot;,
+    &quot;REVISION&quot;
+);
+
+create index NOTIFICATION_OBJECT_R_036a9cee on NOTIFICATION_OBJECT_REVISIONS (
+    &quot;NOTIFICATION_HOME_RESOURCE_ID&quot;,
+    &quot;REVISION&quot;
+);
+
+create index APN_SUBSCRIPTIONS_RES_9610d78e on APN_SUBSCRIPTIONS (
+    &quot;RESOURCE_KEY&quot;
+);
+
+create index IMIP_TOKENS_TOKEN_e94b918f on IMIP_TOKENS (
+    &quot;TOKEN&quot;
+);
+
+create index IMIP_INVITATION_WORK__586d064c on IMIP_INVITATION_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index IMIP_POLLING_WORK_JOB_d5535891 on IMIP_POLLING_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index IMIP_REPLY_WORK_JOB_I_bf4ae73e on IMIP_REPLY_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PUSH_NOTIFICATION_WOR_8bbab117 on PUSH_NOTIFICATION_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PUSH_NOTIFICATION_WOR_3a3ee588 on PUSH_NOTIFICATION_WORK (
+    &quot;PUSH_ID&quot;
+);
+
+create index GROUP_CACHER_POLLING__6eb3151c on GROUP_CACHER_POLLING_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_REFRESH_WORK_JO_717ede20 on GROUP_REFRESH_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_REFRESH_WORK_GR_0325f3a8 on GROUP_REFRESH_WORK (
+    &quot;GROUP_UID&quot;
+);
+
+create index GROUP_DELEGATE_CHANGE_8bf9e6d8 on GROUP_DELEGATE_CHANGES_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_DELEGATE_CHANGE_d8f7af69 on GROUP_DELEGATE_CHANGES_WORK (
+    &quot;DELEGATOR_UID&quot;
+);
+
+create index GROUP_MEMBERSHIP_MEMB_0ca508e8 on GROUP_MEMBERSHIP (
+    &quot;MEMBER_UID&quot;
+);
+
+create index GROUP_ATTENDEE_RECONC_da73d3c2 on GROUP_ATTENDEE_RECONCILE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_ATTENDEE_RECONC_b894ee7a on GROUP_ATTENDEE_RECONCILE_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index GROUP_ATTENDEE_RECONC_5eabc549 on GROUP_ATTENDEE_RECONCILE_WORK (
+    &quot;GROUP_ID&quot;
+);
+
+create index GROUP_ATTENDEE_RESOUR_855124dc on GROUP_ATTENDEE (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index GROUP_SHAREE_RECONCIL_9aad0858 on GROUP_SHAREE_RECONCILE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_SHAREE_RECONCIL_4dc60f78 on GROUP_SHAREE_RECONCILE_WORK (
+    &quot;CALENDAR_ID&quot;
+);
+
+create index GROUP_SHAREE_RECONCIL_1d14c921 on GROUP_SHAREE_RECONCILE_WORK (
+    &quot;GROUP_ID&quot;
+);
+
+create index GROUP_SHAREE_CALENDAR_28a88850 on GROUP_SHAREE (
+    &quot;CALENDAR_ID&quot;
+);
+
+create index DELEGATE_TO_DELEGATOR_5e149b11 on DELEGATES (
+    &quot;DELEGATE&quot;,
+    &quot;READ_WRITE&quot;,
+    &quot;DELEGATOR&quot;
+);
+
+create index DELEGATE_GROUPS_GROUP_25117446 on DELEGATE_GROUPS (
+    &quot;GROUP_ID&quot;
+);
+
+create index CALENDAR_OBJECT_SPLIT_af71dcda on CALENDAR_OBJECT_SPLITTER_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_SPLIT_33603b72 on CALENDAR_OBJECT_SPLITTER_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index CALENDAR_OBJECT_UPGRA_a5c181eb on CALENDAR_OBJECT_UPGRADE_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_UPGRA_39d6f8f9 on CALENDAR_OBJECT_UPGRADE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index FIND_MIN_VALID_REVISI_78d17400 on FIND_MIN_VALID_REVISION_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index REVISION_CLEANUP_WORK_eb062686 on REVISION_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index INBOX_CLEANUP_WORK_JO_799132bd on INBOX_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index CLEANUP_ONE_INBOX_WOR_375dac36 on CLEANUP_ONE_INBOX_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index SCHEDULE_WORK_JOB_ID_65e810ee on SCHEDULE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index SCHEDULE_WORK_ICALEND_089f33dc on SCHEDULE_WORK (
+    &quot;ICALENDAR_UID&quot;
+);
+
+create index SCHEDULE_REFRESH_WORK_26084c7b on SCHEDULE_REFRESH_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_REFRESH_WORK_989efe54 on SCHEDULE_REFRESH_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0256478d on SCHEDULE_AUTO_REPLY_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0755e754 on SCHEDULE_AUTO_REPLY_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_ORGANIZER_WO_18ce4edd on SCHEDULE_ORGANIZER_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_ORGANIZER_WO_14702035 on SCHEDULE_ORGANIZER_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_ORGANIZER_SE_9ec9f827 on SCHEDULE_ORGANIZER_SEND_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_ORGANIZER_SE_699fefc4 on SCHEDULE_ORGANIZER_SEND_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_REPLY_WORK_H_745af8cf on SCHEDULE_REPLY_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_REPLY_WORK_R_11bd3fbb on SCHEDULE_REPLY_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_POLLI_6383e68a on PRINCIPAL_PURGE_POLLING_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_CHECK_b0c024c1 on PRINCIPAL_PURGE_CHECK_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_CHECK_198388a5 on PRINCIPAL_PURGE_CHECK_WORK (
+    &quot;UID&quot;
+);
+
+create index PRINCIPAL_PURGE_WORK__7a8141a3 on PRINCIPAL_PURGE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_WORK__db35cfdc on PRINCIPAL_PURGE_WORK (
+    &quot;UID&quot;
+);
+
+create index PRINCIPAL_PURGE_HOME__f35eea7a on PRINCIPAL_PURGE_HOME_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_HOME__967e4480 on PRINCIPAL_PURGE_HOME_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index MIGRATION_CLEANUP_WOR_8c23cc35 on MIGRATION_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index MIGRATION_CLEANUP_WOR_86181cb8 on MIGRATION_CLEANUP_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index HOME_CLEANUP_WORK_JOB_9631dfb0 on HOME_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index MIGRATED_HOME_CLEANUP_4c714fd4 on MIGRATED_HOME_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+-- Extra schema to add to current-oracle-dialect.sql
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaoldoracledialectv56sqlfromrev15009CalendarServertrunktxdavcommondatastoresql_schemaoldoracledialectv56sql"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/oracle-dialect/v56.sql (from rev 15009, CalendarServer/trunk/txdav/common/datastore/sql_schema/old/oracle-dialect/v56.sql) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/oracle-dialect/v56.sql                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/oracle-dialect/v56.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,1044 @@
</span><ins>+create sequence RESOURCE_ID_SEQ;
+create sequence JOB_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 JOB (
+    &quot;JOB_ID&quot; integer primary key,
+    &quot;WORK_TYPE&quot; nvarchar2(255),
+    &quot;PRIORITY&quot; integer default 0,
+    &quot;WEIGHT&quot; integer default 0,
+    &quot;NOT_BEFORE&quot; timestamp not null,
+    &quot;ASSIGNED&quot; timestamp default null,
+    &quot;OVERDUE&quot; timestamp default null,
+    &quot;FAILED&quot; integer default 0,
+    &quot;PAUSE&quot; integer default 0
+);
+
+create table CALENDAR_HOME (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;OWNER_UID&quot; nvarchar2(255),
+    &quot;STATUS&quot; integer default 0 not null,
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    unique (&quot;OWNER_UID&quot;, &quot;STATUS&quot;)
+);
+
+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);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('purging', 2);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('migrating', 3);
+insert into HOME_STATUS (DESCRIPTION, ID) values ('disabled', 4);
+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;TRASH&quot; integer default null references CALENDAR on delete set 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;CHILD_TYPE&quot; integer default 0 not null,
+    &quot;TRASHED&quot; timestamp default null,
+    &quot;IS_IN_TRASH&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 CHILD_TYPE (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into CHILD_TYPE (DESCRIPTION, ID) values ('normal', 0);
+insert into CHILD_TYPE (DESCRIPTION, ID) values ('inbox', 1);
+insert into CHILD_TYPE (DESCRIPTION, ID) values ('trash', 2);
+create table CALENDAR_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer references CALENDAR_HOME on delete cascade,
+    &quot;REMOTE_RESOURCE_ID&quot; integer not null,
+    &quot;LOCAL_RESOURCE_ID&quot; integer references CALENDAR on delete cascade,
+    &quot;LAST_SYNC_TOKEN&quot; nvarchar2(255), 
+    primary key (&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;REMOTE_RESOURCE_ID&quot;)
+);
+
+create table NOTIFICATION_HOME (
+    &quot;RESOURCE_ID&quot; integer primary key,
+    &quot;OWNER_UID&quot; nvarchar2(255),
+    &quot;STATUS&quot; integer default 0 not null,
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    unique (&quot;OWNER_UID&quot;, &quot;STATUS&quot;)
+);
+
+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;BIND_UID&quot; nvarchar2(36) default 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);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group', 5);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group_read', 6);
+insert into CALENDAR_BIND_MODE (DESCRIPTION, ID) values ('group_write', 7);
+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;TRASHED&quot; timestamp default null,
+    &quot;ORIGINAL_COLLECTION&quot; integer default null,
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    unique (&quot;CALENDAR_RESOURCE_ID&quot;, &quot;RESOURCE_NAME&quot;)
+);
+
+create table CALENDAR_OBJ_ATTACHMENTS_MODE (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (DESCRIPTION, ID) values ('none', 0);
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (DESCRIPTION, ID) values ('read', 1);
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE (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 PERUSER (
+    &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,
+    &quot;ADJUSTED_START_DATE&quot; timestamp default null,
+    &quot;ADJUSTED_END_DATE&quot; timestamp default null, 
+    primary key (&quot;TIME_RANGE_INSTANCE_ID&quot;, &quot;USER_ID&quot;)
+);
+
+create table CALENDAR_OBJECT_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer references CALENDAR_HOME on delete cascade,
+    &quot;REMOTE_RESOURCE_ID&quot; integer not null,
+    &quot;LOCAL_RESOURCE_ID&quot; integer references CALENDAR_OBJECT on delete cascade, 
+    primary key (&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;REMOTE_RESOURCE_ID&quot;)
+);
+
+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 ATTACHMENT_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot; integer references CALENDAR_HOME on delete cascade,
+    &quot;REMOTE_RESOURCE_ID&quot; integer not null,
+    &quot;LOCAL_RESOURCE_ID&quot; integer references ATTACHMENT on delete cascade, 
+    primary key (&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;REMOTE_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),
+    &quot;STATUS&quot; integer default 0 not null,
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    unique (&quot;OWNER_UID&quot;, &quot;STATUS&quot;)
+);
+
+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;BIND_UID&quot; nvarchar2(36) default 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;TRASHED&quot; timestamp default null,
+    &quot;IS_IN_TRASH&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',
+    &quot;DATAVERSION&quot; integer default 0 not null, 
+    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,
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    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;BIND_UID&quot; nvarchar2(36) default 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,
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique (&quot;CALENDAR_HOME_RESOURCE_ID&quot;, &quot;CALENDAR_RESOURCE_ID&quot;, &quot;CALENDAR_NAME&quot;, &quot;RESOURCE_NAME&quot;)
+);
+
+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,
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    unique (&quot;ADDRESSBOOK_HOME_RESOURCE_ID&quot;, &quot;OWNER_HOME_RESOURCE_ID&quot;, &quot;ADDRESSBOOK_NAME&quot;, &quot;RESOURCE_NAME&quot;)
+);
+
+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,
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC', 
+    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 TEST_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;DELAY&quot; integer
+);
+
+create table IMIP_INVITATION_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &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,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table IMIP_REPLY_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &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,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;PUSH_ID&quot; nvarchar2(255),
+    &quot;PUSH_PRIORITY&quot; integer not null
+);
+
+create table GROUP_CACHER_POLLING_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table GROUP_REFRESH_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;GROUP_UID&quot; nvarchar2(255)
+);
+
+create table GROUP_DELEGATE_CHANGES_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;DELEGATOR_UID&quot; nvarchar2(255),
+    &quot;READ_DELEGATE_UID&quot; nvarchar2(255),
+    &quot;WRITE_DELEGATE_UID&quot; nvarchar2(255)
+);
+
+create table GROUPS (
+    &quot;GROUP_ID&quot; integer primary key,
+    &quot;NAME&quot; nvarchar2(255),
+    &quot;GROUP_UID&quot; nvarchar2(255) unique,
+    &quot;MEMBERSHIP_HASH&quot; nvarchar2(255),
+    &quot;EXTANT&quot; integer default 1,
+    &quot;CREATED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
+    &quot;MODIFIED&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC'
+);
+
+create table GROUP_MEMBERSHIP (
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade,
+    &quot;MEMBER_UID&quot; nvarchar2(255), 
+    primary key (&quot;GROUP_ID&quot;, &quot;MEMBER_UID&quot;)
+);
+
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade
+);
+
+create table GROUP_ATTENDEE (
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;MEMBERSHIP_HASH&quot; nvarchar2(255), 
+    primary key (&quot;GROUP_ID&quot;, &quot;RESOURCE_ID&quot;)
+);
+
+create table GROUP_SHAREE_RECONCILE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;CALENDAR_ID&quot; integer not null references CALENDAR on delete cascade,
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade
+);
+
+create table GROUP_SHAREE (
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade,
+    &quot;CALENDAR_ID&quot; integer not null references CALENDAR on delete cascade,
+    &quot;GROUP_BIND_MODE&quot; integer not null,
+    &quot;MEMBERSHIP_HASH&quot; nvarchar2(255), 
+    primary key (&quot;GROUP_ID&quot;, &quot;CALENDAR_ID&quot;)
+);
+
+create table DELEGATES (
+    &quot;DELEGATOR&quot; nvarchar2(255),
+    &quot;DELEGATE&quot; nvarchar2(255),
+    &quot;READ_WRITE&quot; integer not null, 
+    primary key (&quot;DELEGATOR&quot;, &quot;READ_WRITE&quot;, &quot;DELEGATE&quot;)
+);
+
+create table DELEGATE_GROUPS (
+    &quot;DELEGATOR&quot; nvarchar2(255),
+    &quot;GROUP_ID&quot; integer not null references GROUPS on delete cascade,
+    &quot;READ_WRITE&quot; integer not null,
+    &quot;IS_EXTERNAL&quot; integer not null, 
+    primary key (&quot;DELEGATOR&quot;, &quot;READ_WRITE&quot;, &quot;GROUP_ID&quot;)
+);
+
+create table EXTERNAL_DELEGATE_GROUPS (
+    &quot;DELEGATOR&quot; nvarchar2(255) primary key,
+    &quot;GROUP_UID_READ&quot; nvarchar2(255),
+    &quot;GROUP_UID_WRITE&quot; nvarchar2(255)
+);
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table CALENDAR_OBJECT_UPGRADE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade
+);
+
+create table FIND_MIN_VALID_REVISION_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table REVISION_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table INBOX_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table CLEANUP_ONE_INBOX_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;HOME_ID&quot; integer not null unique references CALENDAR_HOME on delete cascade
+);
+
+create table SCHEDULE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;ICALENDAR_UID&quot; nvarchar2(255),
+    &quot;WORK_TYPE&quot; nvarchar2(255)
+);
+
+create table SCHEDULE_REFRESH_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;ATTENDEE_COUNT&quot; integer
+);
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;ATTENDEE&quot; nvarchar2(255), 
+    primary key (&quot;RESOURCE_ID&quot;, &quot;ATTENDEE&quot;)
+);
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer not null references CALENDAR_OBJECT on delete cascade,
+    &quot;PARTSTAT&quot; nvarchar2(255)
+);
+
+create table SCHEDULE_ORGANIZER_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;SCHEDULE_ACTION&quot; integer not null,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer,
+    &quot;ICALENDAR_TEXT_OLD&quot; nclob,
+    &quot;ICALENDAR_TEXT_NEW&quot; nclob,
+    &quot;ATTENDEE_COUNT&quot; integer,
+    &quot;SMART_MERGE&quot; integer
+);
+
+create table SCHEDULE_ACTION (
+    &quot;ID&quot; integer primary key,
+    &quot;DESCRIPTION&quot; nvarchar2(16) unique
+);
+
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('create', 0);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify', 1);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('modify-cancelled', 2);
+insert into SCHEDULE_ACTION (DESCRIPTION, ID) values ('remove', 3);
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;SCHEDULE_ACTION&quot; integer not null,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer,
+    &quot;ATTENDEE&quot; nvarchar2(255),
+    &quot;ITIP_MSG&quot; nclob,
+    &quot;NO_REFRESH&quot; integer
+);
+
+create table SCHEDULE_REPLY_WORK (
+    &quot;WORK_ID&quot; integer primary key references SCHEDULE_WORK on delete cascade,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade,
+    &quot;RESOURCE_ID&quot; integer,
+    &quot;ITIP_MSG&quot; nclob
+);
+
+create table PRINCIPAL_PURGE_POLLING_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB
+);
+
+create table PRINCIPAL_PURGE_CHECK_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;UID&quot; nvarchar2(255)
+);
+
+create table PRINCIPAL_PURGE_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;UID&quot; nvarchar2(255)
+);
+
+create table PRINCIPAL_PURGE_HOME_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade
+);
+
+create table MIGRATION_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;HOME_RESOURCE_ID&quot; integer not null references CALENDAR_HOME on delete cascade
+);
+
+create table HOME_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;OWNER_UID&quot; nvarchar2(255)
+);
+
+create table MIGRATED_HOME_CLEANUP_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;OWNER_UID&quot; nvarchar2(255)
+);
+
+create table CALENDARSERVER (
+    &quot;NAME&quot; nvarchar2(255) primary key,
+    &quot;VALUE&quot; nvarchar2(255)
+);
+
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '56');
+insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '6');
+insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER (NAME, VALUE) values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER (NAME, VALUE) values ('MIN-VALID-REVISION', '1');
+create index CALENDAR_HOME_METADAT_475de898 on CALENDAR_HOME_METADATA (
+    &quot;TRASH&quot;
+);
+
+create index CALENDAR_HOME_METADAT_3cb9049e on CALENDAR_HOME_METADATA (
+    &quot;DEFAULT_EVENTS&quot;
+);
+
+create index CALENDAR_HOME_METADAT_d55e5548 on CALENDAR_HOME_METADATA (
+    &quot;DEFAULT_TASKS&quot;
+);
+
+create index CALENDAR_HOME_METADAT_910264ce on CALENDAR_HOME_METADATA (
+    &quot;DEFAULT_POLLS&quot;
+);
+
+create index CALENDAR_MIGRATION_LO_0525c72b on CALENDAR_MIGRATION (
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index NOTIFICATION_NOTIFICA_f891f5f9 on NOTIFICATION (
+    &quot;NOTIFICATION_HOME_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_BIND_RESOURC_e57964d4 on CALENDAR_BIND (
+    &quot;CALENDAR_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_CALEN_a9a453a9 on CALENDAR_OBJECT (
+    &quot;CALENDAR_RESOURCE_ID&quot;,
+    &quot;ICALENDAR_UID&quot;
+);
+
+create index CALENDAR_OBJECT_CALEN_c4dc619c on CALENDAR_OBJECT (
+    &quot;CALENDAR_RESOURCE_ID&quot;,
+    &quot;RECURRANCE_MAX&quot;,
+    &quot;RECURRANCE_MIN&quot;
+);
+
+create index CALENDAR_OBJECT_ICALE_82e731d5 on CALENDAR_OBJECT (
+    &quot;ICALENDAR_UID&quot;
+);
+
+create index CALENDAR_OBJECT_DROPB_de041d80 on CALENDAR_OBJECT (
+    &quot;DROPBOX_ID&quot;
+);
+
+create index TIME_RANGE_CALENDAR_R_beb6e7eb on TIME_RANGE (
+    &quot;CALENDAR_RESOURCE_ID&quot;
+);
+
+create index TIME_RANGE_CALENDAR_O_acf37bd1 on TIME_RANGE (
+    &quot;CALENDAR_OBJECT_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_MIGRA_0502cbef on CALENDAR_OBJECT_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot;,
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_MIGRA_3577efd9 on CALENDAR_OBJECT_MIGRATION (
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index ATTACHMENT_CALENDAR_H_0078845c on ATTACHMENT (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot;
+);
+
+create index ATTACHMENT_DROPBOX_ID_5073cf23 on ATTACHMENT (
+    &quot;DROPBOX_ID&quot;
+);
+
+create index ATTACHMENT_CALENDAR_O_81508484 on ATTACHMENT_CALENDAR_OBJECT (
+    &quot;CALENDAR_OBJECT_RESOURCE_ID&quot;
+);
+
+create index ATTACHMENT_MIGRATION__804bf85e on ATTACHMENT_MIGRATION (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot;,
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index ATTACHMENT_MIGRATION__816947fe on ATTACHMENT_MIGRATION (
+    &quot;LOCAL_RESOURCE_ID&quot;
+);
+
+create index SHARED_ADDRESSBOOK_BI_e9a2e6d4 on SHARED_ADDRESSBOOK_BIND (
+    &quot;OWNER_HOME_RESOURCE_ID&quot;
+);
+
+create index ABO_MEMBERS_ADDRESSBO_4effa879 on ABO_MEMBERS (
+    &quot;ADDRESSBOOK_ID&quot;
+);
+
+create index ABO_MEMBERS_MEMBER_ID_8d66adcf on ABO_MEMBERS (
+    &quot;MEMBER_ID&quot;
+);
+
+create index ABO_FOREIGN_MEMBERS_A_1fd2c5e9 on ABO_FOREIGN_MEMBERS (
+    &quot;ADDRESSBOOK_ID&quot;
+);
+
+create index SHARED_GROUP_BIND_RES_cf52f95d on SHARED_GROUP_BIND (
+    &quot;GROUP_RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_REVIS_6d9d929c on CALENDAR_OBJECT_REVISIONS (
+    &quot;CALENDAR_RESOURCE_ID&quot;,
+    &quot;RESOURCE_NAME&quot;,
+    &quot;DELETED&quot;,
+    &quot;REVISION&quot;
+);
+
+create index CALENDAR_OBJECT_REVIS_265c8acf on CALENDAR_OBJECT_REVISIONS (
+    &quot;CALENDAR_RESOURCE_ID&quot;,
+    &quot;REVISION&quot;
+);
+
+create index CALENDAR_OBJECT_REVIS_550b1c56 on CALENDAR_OBJECT_REVISIONS (
+    &quot;CALENDAR_HOME_RESOURCE_ID&quot;,
+    &quot;REVISION&quot;
+);
+
+create index ADDRESSBOOK_OBJECT_RE_00fe8288 on ADDRESSBOOK_OBJECT_REVISIONS (
+    &quot;OWNER_HOME_RESOURCE_ID&quot;,
+    &quot;RESOURCE_NAME&quot;,
+    &quot;DELETED&quot;,
+    &quot;REVISION&quot;
+);
+
+create index ADDRESSBOOK_OBJECT_RE_45004780 on ADDRESSBOOK_OBJECT_REVISIONS (
+    &quot;OWNER_HOME_RESOURCE_ID&quot;,
+    &quot;REVISION&quot;
+);
+
+create index NOTIFICATION_OBJECT_R_036a9cee on NOTIFICATION_OBJECT_REVISIONS (
+    &quot;NOTIFICATION_HOME_RESOURCE_ID&quot;,
+    &quot;REVISION&quot;
+);
+
+create index APN_SUBSCRIPTIONS_RES_9610d78e on APN_SUBSCRIPTIONS (
+    &quot;RESOURCE_KEY&quot;
+);
+
+create index IMIP_TOKENS_TOKEN_e94b918f on IMIP_TOKENS (
+    &quot;TOKEN&quot;
+);
+
+create index TEST_WORK_JOB_ID_228ede32 on TEST_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index IMIP_INVITATION_WORK__586d064c on IMIP_INVITATION_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index IMIP_POLLING_WORK_JOB_d5535891 on IMIP_POLLING_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index IMIP_REPLY_WORK_JOB_I_bf4ae73e on IMIP_REPLY_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PUSH_NOTIFICATION_WOR_8bbab117 on PUSH_NOTIFICATION_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PUSH_NOTIFICATION_WOR_3a3ee588 on PUSH_NOTIFICATION_WORK (
+    &quot;PUSH_ID&quot;
+);
+
+create index GROUP_CACHER_POLLING__6eb3151c on GROUP_CACHER_POLLING_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_REFRESH_WORK_JO_717ede20 on GROUP_REFRESH_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_REFRESH_WORK_GR_0325f3a8 on GROUP_REFRESH_WORK (
+    &quot;GROUP_UID&quot;
+);
+
+create index GROUP_DELEGATE_CHANGE_8bf9e6d8 on GROUP_DELEGATE_CHANGES_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_DELEGATE_CHANGE_d8f7af69 on GROUP_DELEGATE_CHANGES_WORK (
+    &quot;DELEGATOR_UID&quot;
+);
+
+create index GROUP_MEMBERSHIP_MEMB_0ca508e8 on GROUP_MEMBERSHIP (
+    &quot;MEMBER_UID&quot;
+);
+
+create index GROUP_ATTENDEE_RECONC_da73d3c2 on GROUP_ATTENDEE_RECONCILE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_ATTENDEE_RECONC_b894ee7a on GROUP_ATTENDEE_RECONCILE_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index GROUP_ATTENDEE_RECONC_5eabc549 on GROUP_ATTENDEE_RECONCILE_WORK (
+    &quot;GROUP_ID&quot;
+);
+
+create index GROUP_ATTENDEE_RESOUR_855124dc on GROUP_ATTENDEE (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index GROUP_SHAREE_RECONCIL_9aad0858 on GROUP_SHAREE_RECONCILE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index GROUP_SHAREE_RECONCIL_4dc60f78 on GROUP_SHAREE_RECONCILE_WORK (
+    &quot;CALENDAR_ID&quot;
+);
+
+create index GROUP_SHAREE_RECONCIL_1d14c921 on GROUP_SHAREE_RECONCILE_WORK (
+    &quot;GROUP_ID&quot;
+);
+
+create index GROUP_SHAREE_CALENDAR_28a88850 on GROUP_SHAREE (
+    &quot;CALENDAR_ID&quot;
+);
+
+create index DELEGATE_TO_DELEGATOR_5e149b11 on DELEGATES (
+    &quot;DELEGATE&quot;,
+    &quot;READ_WRITE&quot;,
+    &quot;DELEGATOR&quot;
+);
+
+create index DELEGATE_GROUPS_GROUP_25117446 on DELEGATE_GROUPS (
+    &quot;GROUP_ID&quot;
+);
+
+create index CALENDAR_OBJECT_SPLIT_af71dcda on CALENDAR_OBJECT_SPLITTER_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_SPLIT_33603b72 on CALENDAR_OBJECT_SPLITTER_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index CALENDAR_OBJECT_UPGRA_a5c181eb on CALENDAR_OBJECT_UPGRADE_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index CALENDAR_OBJECT_UPGRA_39d6f8f9 on CALENDAR_OBJECT_UPGRADE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index FIND_MIN_VALID_REVISI_78d17400 on FIND_MIN_VALID_REVISION_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index REVISION_CLEANUP_WORK_eb062686 on REVISION_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index INBOX_CLEANUP_WORK_JO_799132bd on INBOX_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index CLEANUP_ONE_INBOX_WOR_375dac36 on CLEANUP_ONE_INBOX_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index SCHEDULE_WORK_JOB_ID_65e810ee on SCHEDULE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index SCHEDULE_WORK_ICALEND_089f33dc on SCHEDULE_WORK (
+    &quot;ICALENDAR_UID&quot;
+);
+
+create index SCHEDULE_REFRESH_WORK_26084c7b on SCHEDULE_REFRESH_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_REFRESH_WORK_989efe54 on SCHEDULE_REFRESH_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0256478d on SCHEDULE_AUTO_REPLY_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_AUTO_REPLY_W_0755e754 on SCHEDULE_AUTO_REPLY_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_ORGANIZER_WO_18ce4edd on SCHEDULE_ORGANIZER_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_ORGANIZER_WO_14702035 on SCHEDULE_ORGANIZER_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_ORGANIZER_SE_9ec9f827 on SCHEDULE_ORGANIZER_SEND_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_ORGANIZER_SE_699fefc4 on SCHEDULE_ORGANIZER_SEND_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_REPLY_WORK_H_745af8cf on SCHEDULE_REPLY_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index SCHEDULE_REPLY_WORK_R_11bd3fbb on SCHEDULE_REPLY_WORK (
+    &quot;RESOURCE_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_POLLI_6383e68a on PRINCIPAL_PURGE_POLLING_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_CHECK_b0c024c1 on PRINCIPAL_PURGE_CHECK_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_CHECK_198388a5 on PRINCIPAL_PURGE_CHECK_WORK (
+    &quot;UID&quot;
+);
+
+create index PRINCIPAL_PURGE_WORK__7a8141a3 on PRINCIPAL_PURGE_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_WORK__db35cfdc on PRINCIPAL_PURGE_WORK (
+    &quot;UID&quot;
+);
+
+create index PRINCIPAL_PURGE_HOME__f35eea7a on PRINCIPAL_PURGE_HOME_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index PRINCIPAL_PURGE_HOME__967e4480 on PRINCIPAL_PURGE_HOME_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index MIGRATION_CLEANUP_WOR_8c23cc35 on MIGRATION_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index MIGRATION_CLEANUP_WOR_86181cb8 on MIGRATION_CLEANUP_WORK (
+    &quot;HOME_RESOURCE_ID&quot;
+);
+
+create index HOME_CLEANUP_WORK_JOB_9631dfb0 on HOME_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+create index MIGRATED_HOME_CLEANUP_4c714fd4 on MIGRATED_HOME_CLEANUP_WORK (
+    &quot;JOB_ID&quot;
+);
+
+-- Extra schema to add to current-oracle-dialect.sql
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaoldpostgresdialectv55sqlfromrev15009CalendarServertrunktxdavcommondatastoresql_schemaoldpostgresdialectv55sql"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/postgres-dialect/v55.sql (from rev 15009, CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v55.sql) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/postgres-dialect/v55.sql                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/postgres-dialect/v55.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,1276 @@
</span><ins>+-- -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+
+----
+-- Copyright (c) 2010-2015 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
+);
+
+
+--------------------
+-- Jobs           --
+--------------------
+
+create sequence JOB_SEQ;
+
+create table JOB (
+  JOB_ID      integer primary key default nextval('JOB_SEQ'), --implicit index
+  WORK_TYPE   varchar(255) not null,
+  PRIORITY    integer default 0,
+  WEIGHT      integer default 0,
+  NOT_BEFORE  timestamp not null,
+  ASSIGNED    timestamp default null,
+  OVERDUE     timestamp default null,
+  FAILED      integer default 0,
+  PAUSE       integer default 0
+);
+
+-------------------
+-- Calendar Home --
+-------------------
+
+create table CALENDAR_HOME (
+  RESOURCE_ID      integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID        varchar(255) not null,                                                -- implicit index
+  STATUS           integer      default 0 not null,                             -- enum HOME_STATUS
+  DATAVERSION      integer      default 0 not null,
+  
+  unique (OWNER_UID, STATUS)        -- implicit index
+);
+
+-- 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');
+insert into HOME_STATUS values (2, 'purging');
+insert into HOME_STATUS values (3, 'migrating');
+insert into HOME_STATUS values (4, 'disabled');
+
+
+--------------
+-- 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,
+  TRASH                    integer     default null references CALENDAR on delete set 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_TRASH on
+  CALENDAR_HOME_METADATA(TRASH);
+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,
+  CHILD_TYPE            integer      default 0 not null,                                     -- enum CHILD_TYPE
+  TRASHED               timestamp    default null,
+  IS_IN_TRASH           boolean      default false not null, -- collection is in the trash
+  CREATED               timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+-- Enumeration of child type
+
+create table CHILD_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CHILD_TYPE values (0, 'normal');
+insert into CHILD_TYPE values (1, 'inbox');
+insert into CHILD_TYPE values (2, 'trash');
+
+
+------------------------
+-- Calendar Migration --
+------------------------
+
+create table CALENDAR_MIGRATION (
+  CALENDAR_HOME_RESOURCE_ID                integer references CALENDAR_HOME on delete cascade,
+  REMOTE_RESOURCE_ID                        integer not null,
+  LOCAL_RESOURCE_ID                                integer        references CALENDAR on delete cascade,
+  LAST_SYNC_TOKEN                                varchar(255),
+   
+  primary key (CALENDAR_HOME_RESOURCE_ID, REMOTE_RESOURCE_ID) -- implicit index
+);
+
+create index CALENDAR_MIGRATION_LOCAL_RESOURCE_ID on
+  CALENDAR_MIGRATION(LOCAL_RESOURCE_ID);
+
+
+---------------------------
+-- Sharing Notifications --
+---------------------------
+
+create table NOTIFICATION_HOME (
+  RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID   varchar(255) not null,                                           -- implicit index
+  STATUS      integer      default 0 not null,                             -- enum HOME_STATUS
+  DATAVERSION integer      default 0 not null,
+    
+  unique (OWNER_UID, STATUS)        -- implicit index
+);
+
+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,
+  BIND_UID                  varchar(36)  default 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');
+insert into CALENDAR_BIND_MODE values (5, 'group');
+insert into CALENDAR_BIND_MODE values (6, 'group_read');
+insert into CALENDAR_BIND_MODE values (7, 'group_write');
+
+-- 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_OBJ_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,
+  TRASHED              timestamp    default null,
+  ORIGINAL_COLLECTION  integer      default null, -- calendar_resource_id prior to trash
+  CREATED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED             timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  DATAVERSION          integer      default 0 not null,
+
+  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_MIN on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, RECURRANCE_MAX, RECURRANCE_MIN);
+
+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_OBJ_ATTACHMENTS_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE values (0, 'none' );
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE values (1, 'read' );
+insert into CALENDAR_OBJ_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'  );
+
+
+-------------------
+-- Per-user data --
+-------------------
+
+create table PERUSER (
+  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
+  USER_ID                     varchar(255) not null,
+  TRANSPARENT                 boolean      not null,
+  ADJUSTED_START_DATE         timestamp    default null,
+  ADJUSTED_END_DATE           timestamp    default null,
+
+  primary key (TIME_RANGE_INSTANCE_ID, USER_ID)    -- implicit index
+);
+
+
+-------------------------------
+-- Calendar Object Migration --
+-------------------------------
+
+create table CALENDAR_OBJECT_MIGRATION (
+  CALENDAR_HOME_RESOURCE_ID                integer references CALENDAR_HOME on delete cascade,
+  REMOTE_RESOURCE_ID                        integer not null,
+  LOCAL_RESOURCE_ID                                integer        references CALENDAR_OBJECT on delete cascade,
+   
+  primary key (CALENDAR_HOME_RESOURCE_ID, REMOTE_RESOURCE_ID) -- implicit index
+);
+
+create index CALENDAR_OBJECT_MIGRATION_HOME_LOCAL on
+  CALENDAR_OBJECT_MIGRATION(CALENDAR_HOME_RESOURCE_ID, LOCAL_RESOURCE_ID);
+create index CALENDAR_OBJECT_MIGRATION_LOCAL_RESOURCE_ID on
+  CALENDAR_OBJECT_MIGRATION(LOCAL_RESOURCE_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);
+
+create index ATTACHMENT_DROPBOX_ID on
+  ATTACHMENT(DROPBOX_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);
+
+-----------------------------------
+-- Calendar Attachment Migration --
+-----------------------------------
+
+create table ATTACHMENT_MIGRATION (
+  CALENDAR_HOME_RESOURCE_ID                integer references CALENDAR_HOME on delete cascade,
+  REMOTE_RESOURCE_ID                        integer not null,
+  LOCAL_RESOURCE_ID                                integer        references ATTACHMENT on delete cascade,
+   
+  primary key (CALENDAR_HOME_RESOURCE_ID, REMOTE_RESOURCE_ID) -- implicit index
+);
+
+create index ATTACHMENT_MIGRATION_HOME_LOCAL on
+  ATTACHMENT_MIGRATION(CALENDAR_HOME_RESOURCE_ID, LOCAL_RESOURCE_ID);
+create index ATTACHMENT_MIGRATION_LOCAL_RESOURCE_ID on
+  ATTACHMENT_MIGRATION(LOCAL_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,
+  STATUS                        integer         default 0 not null,                             -- enum HOME_STATUS
+  DATAVERSION                   integer         default 0 not null,
+    
+  unique (OWNER_UID, STATUS)        -- implicit index
+);
+
+
+-------------------------------
+-- 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,
+  BIND_UID                              varchar(36)     default 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,
+  TRASHED                       timestamp       default null,
+  IS_IN_TRASH                   boolean         default false not null,
+  CREATED                       timestamp       default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp       default timezone('UTC', CURRENT_TIMESTAMP),
+  DATAVERSION                   integer         default 0 not null,
+
+  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,
+  MODIFIED        timestamp   default timezone('UTC', CURRENT_TIMESTAMP),
+
+  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,
+  BIND_UID                          varchar(36)  default 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,
+  MODIFIED                  timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID, CALENDAR_NAME, RESOURCE_NAME)    -- implicit index
+);
+
+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);
+
+create index CALENDAR_OBJECT_REVISIONS_HOME_RESOURCE_ID_REVISION
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_HOME_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,
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique(ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID, ADDRESSBOOK_NAME, RESOURCE_NAME)    -- implicit index
+);
+
+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,
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  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'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  FROM_ADDR                     varchar(255) not null,
+  TO_ADDR                       varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index IMIP_INVITATION_WORK_JOB_ID on
+  IMIP_INVITATION_WORK(JOB_ID);
+
+-----------------------
+-- IMIP Polling Work --
+-----------------------
+
+create table IMIP_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index IMIP_POLLING_WORK_JOB_ID on
+  IMIP_POLLING_WORK(JOB_ID);
+
+
+---------------------
+-- IMIP Reply Work --
+---------------------
+
+create table IMIP_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index IMIP_REPLY_WORK_JOB_ID on
+  IMIP_REPLY_WORK(JOB_ID);
+
+
+------------------------
+-- Push Notifications --
+------------------------
+
+create table PUSH_NOTIFICATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  PUSH_ID                       varchar(255) not null,
+  PUSH_PRIORITY                 integer      not null -- 1:low 5:medium 10:high
+);
+
+create index PUSH_NOTIFICATION_WORK_JOB_ID on
+  PUSH_NOTIFICATION_WORK(JOB_ID);
+create index PUSH_NOTIFICATION_WORK_PUSH_ID on
+  PUSH_NOTIFICATION_WORK(PUSH_ID);
+
+-----------------
+-- GroupCacher --
+-----------------
+
+create table GROUP_CACHER_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index GROUP_CACHER_POLLING_WORK_JOB_ID on
+  GROUP_CACHER_POLLING_WORK(JOB_ID);
+
+create table GROUP_REFRESH_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  GROUP_UID                     varchar(255) not null
+);
+
+create index GROUP_REFRESH_WORK_JOB_ID on
+  GROUP_REFRESH_WORK(JOB_ID);
+create index GROUP_REFRESH_WORK_GROUP_UID on
+  GROUP_REFRESH_WORK(GROUP_UID);
+
+create table GROUP_DELEGATE_CHANGES_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  DELEGATOR_UID                 varchar(255) not null,
+  READ_DELEGATE_UID             varchar(255) not null,
+  WRITE_DELEGATE_UID            varchar(255) not null
+);
+
+create index GROUP_DELEGATE_CHANGES_WORK_JOB_ID on
+  GROUP_DELEGATE_CHANGES_WORK(JOB_ID);
+create index GROUP_DELEGATE_CHANGES_WORK_DELEGATOR_UID on
+  GROUP_DELEGATE_CHANGES_WORK(DELEGATOR_UID);
+
+create table GROUPS (
+  GROUP_ID                      integer      primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
+  NAME                          varchar(255) not null,
+  GROUP_UID                     varchar(255) not null unique,                                                                        -- implicit index
+  MEMBERSHIP_HASH               varchar(255) not null,
+  EXTANT                        integer default 1,
+  CREATED                       timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+create table GROUP_MEMBERSHIP (
+  GROUP_ID                     integer not null references GROUPS on delete cascade,
+  MEMBER_UID                   varchar(255) not null,
+
+  primary key (GROUP_ID, MEMBER_UID)
+);
+
+create index GROUP_MEMBERSHIP_MEMBER on
+  GROUP_MEMBERSHIP(MEMBER_UID);
+
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer not null references JOB,
+  RESOURCE_ID                   integer not null references CALENDAR_OBJECT on delete cascade,
+  GROUP_ID                      integer not null references GROUPS on delete cascade
+);
+
+create index GROUP_ATTENDEE_RECONCILE_WORK_JOB_ID on
+  GROUP_ATTENDEE_RECONCILE_WORK(JOB_ID);
+create index GROUP_ATTENDEE_RECONCILE_WORK_RESOURCE_ID on
+  GROUP_ATTENDEE_RECONCILE_WORK(RESOURCE_ID);
+create index GROUP_ATTENDEE_RECONCILE_WORK_GROUP_ID on
+  GROUP_ATTENDEE_RECONCILE_WORK(GROUP_ID);
+
+
+create table GROUP_ATTENDEE (
+  GROUP_ID                      integer not null references GROUPS on delete cascade,
+  RESOURCE_ID                   integer not null references CALENDAR_OBJECT on delete cascade,
+  MEMBERSHIP_HASH               varchar(255) not null,
+
+  primary key (GROUP_ID, RESOURCE_ID)
+);
+
+create index GROUP_ATTENDEE_RESOURCE_ID on
+  GROUP_ATTENDEE(RESOURCE_ID);
+
+
+create table GROUP_SHAREE_RECONCILE_WORK (
+  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer not null references JOB,
+  CALENDAR_ID                   integer        not null references CALENDAR on delete cascade,
+  GROUP_ID                      integer not null references GROUPS on delete cascade
+);
+
+create index GROUP_SHAREE_RECONCILE_WORK_JOB_ID on
+  GROUP_SHAREE_RECONCILE_WORK(JOB_ID);
+create index GROUP_SHAREE_RECONCILE_WORK_CALENDAR_ID on
+  GROUP_SHAREE_RECONCILE_WORK(CALENDAR_ID);
+create index GROUP_SHAREE_RECONCILE_WORK_GROUP_ID on
+  GROUP_SHAREE_RECONCILE_WORK(GROUP_ID);
+
+
+create table GROUP_SHAREE (
+  GROUP_ID                      integer not null references GROUPS on delete cascade,
+  CALENDAR_ID                                      integer not null references CALENDAR on delete cascade,
+  GROUP_BIND_MODE               integer not null, -- enum CALENDAR_BIND_MODE
+  MEMBERSHIP_HASH               varchar(255) not null,
+
+  primary key (GROUP_ID, CALENDAR_ID)
+);
+
+create index GROUP_SHAREE_CALENDAR_ID on
+  GROUP_SHAREE(CALENDAR_ID);
+
+---------------
+-- Delegates --
+---------------
+
+create table DELEGATES (
+  DELEGATOR                     varchar(255) not null,
+  DELEGATE                      varchar(255) not null,
+  READ_WRITE                    integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+
+  primary key (DELEGATOR, READ_WRITE, DELEGATE)
+);
+create index DELEGATE_TO_DELEGATOR on
+  DELEGATES(DELEGATE, READ_WRITE, DELEGATOR);
+
+create table DELEGATE_GROUPS (
+  DELEGATOR                     varchar(255) not null,
+  GROUP_ID                      integer      not null references GROUPS on delete cascade,
+  READ_WRITE                    integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+  IS_EXTERNAL                   integer      not null, -- 1 = External, 0 = Internal
+
+  primary key (DELEGATOR, READ_WRITE, GROUP_ID)
+);
+create index DELEGATE_GROUPS_GROUP_ID on
+  DELEGATE_GROUPS(GROUP_ID);
+
+create table EXTERNAL_DELEGATE_GROUPS (
+  DELEGATOR                     varchar(255) primary key,
+  GROUP_UID_READ                varchar(255),
+  GROUP_UID_WRITE               varchar(255)
+);
+
+--------------------------
+-- Object Splitter Work --
+--------------------------
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  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);
+create index CALENDAR_OBJECT_SPLITTER_WORK_JOB_ID on
+  CALENDAR_OBJECT_SPLITTER_WORK(JOB_ID);
+
+-------------------------
+-- Object Upgrade Work --
+-------------------------
+
+create table CALENDAR_OBJECT_UPGRADE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
+);
+
+create index CALENDAR_OBJECT_UPGRADE_WORK_RESOURCE_ID on
+  CALENDAR_OBJECT_UPGRADE_WORK(RESOURCE_ID);
+create index CALENDAR_OBJECT_UPGRADE_WORK_JOB_ID on
+  CALENDAR_OBJECT_UPGRADE_WORK(JOB_ID);
+
+---------------------------
+-- Revision Cleanup Work --
+---------------------------
+
+create table FIND_MIN_VALID_REVISION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index FIND_MIN_VALID_REVISION_WORK_JOB_ID on
+  FIND_MIN_VALID_REVISION_WORK(JOB_ID);
+
+create table REVISION_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index REVISION_CLEANUP_WORK_JOB_ID on
+  REVISION_CLEANUP_WORK(JOB_ID);
+
+------------------------
+-- Inbox Cleanup Work --
+------------------------
+
+create table INBOX_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index INBOX_CLEANUP_WORK_JOB_ID on
+   INBOX_CLEANUP_WORK(JOB_ID);
+
+create table CLEANUP_ONE_INBOX_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_ID                       integer      not null unique references CALENDAR_HOME on delete cascade -- implicit index
+);
+
+create index CLEANUP_ONE_INBOX_WORK_JOB_ID on
+  CLEANUP_ONE_INBOX_WORK(JOB_ID);
+
+-------------------
+-- Schedule Work --
+-------------------
+
+create table SCHEDULE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ICALENDAR_UID                 varchar(255) not null,
+  WORK_TYPE                     varchar(255) not null
+);
+
+create index SCHEDULE_WORK_JOB_ID on
+  SCHEDULE_WORK(JOB_ID);
+create index SCHEDULE_WORK_ICALENDAR_UID on
+  SCHEDULE_WORK(ICALENDAR_UID);
+
+---------------------------
+-- Schedule Refresh Work --
+---------------------------
+
+create table SCHEDULE_REFRESH_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE_COUNT                integer
+);
+
+create index SCHEDULE_REFRESH_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REFRESH_WORK_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(RESOURCE_ID);
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE                      varchar(255) not null,
+
+  primary key (RESOURCE_ID, ATTENDEE)
+);
+
+------------------------------
+-- Schedule Auto Reply Work --
+------------------------------
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  PARTSTAT                      varchar(255) not null
+);
+
+create index SCHEDULE_AUTO_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_AUTO_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(RESOURCE_ID);
+
+-----------------------------
+-- Schedule Organizer Work --
+-----------------------------
+
+create table SCHEDULE_ORGANIZER_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ICALENDAR_TEXT_OLD            text,
+  ICALENDAR_TEXT_NEW            text,
+  ATTENDEE_COUNT                integer,
+  SMART_MERGE                   boolean
+);
+
+create index SCHEDULE_ORGANIZER_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(RESOURCE_ID);
+
+-- Enumeration of schedule actions
+
+create table SCHEDULE_ACTION (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into SCHEDULE_ACTION values (0, 'create');
+insert into SCHEDULE_ACTION values (1, 'modify');
+insert into SCHEDULE_ACTION values (2, 'modify-cancelled');
+insert into SCHEDULE_ACTION values (3, 'remove');
+
+----------------------------------
+-- Schedule Organizer Send Work --
+----------------------------------
+
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ATTENDEE                      varchar(255) not null,
+  ITIP_MSG                      text,
+  NO_REFRESH                    boolean
+);
+
+create index SCHEDULE_ORGANIZER_SEND_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_SEND_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(RESOURCE_ID);
+
+-------------------------
+-- Schedule Reply Work --
+-------------------------
+
+create table SCHEDULE_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ITIP_MSG                      text
+);
+
+create index SCHEDULE_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(RESOURCE_ID);
+
+----------------------------------
+-- Principal Purge Polling Work --
+----------------------------------
+
+create table PRINCIPAL_PURGE_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index PRINCIPAL_PURGE_POLLING_WORK_JOB_ID on
+  PRINCIPAL_PURGE_POLLING_WORK(JOB_ID);
+
+--------------------------------
+-- Principal Purge Check Work --
+--------------------------------
+
+create table PRINCIPAL_PURGE_CHECK_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  UID                           varchar(255) not null
+);
+
+create index PRINCIPAL_PURGE_CHECK_WORK_JOB_ID on
+  PRINCIPAL_PURGE_CHECK_WORK(JOB_ID);
+create index PRINCIPAL_PURGE_CHECK_WORK_UID on
+  PRINCIPAL_PURGE_CHECK_WORK(UID);
+
+--------------------------
+-- Principal Purge Work --
+--------------------------
+
+create table PRINCIPAL_PURGE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  UID                           varchar(255) not null
+);
+
+create index PRINCIPAL_PURGE_WORK_JOB_ID on
+  PRINCIPAL_PURGE_WORK(JOB_ID);
+create index PRINCIPAL_PURGE_WORK_UID on
+  PRINCIPAL_PURGE_WORK(UID);
+
+
+--------------------------------
+-- Principal Home Remove Work --
+--------------------------------
+
+create table PRINCIPAL_PURGE_HOME_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade
+);
+
+create index PRINCIPAL_PURGE_HOME_WORK_JOB_ID on
+  PRINCIPAL_PURGE_HOME_WORK(JOB_ID);
+create index PRINCIPAL_PURGE_HOME_HOME_RESOURCE_ID on
+  PRINCIPAL_PURGE_HOME_WORK(HOME_RESOURCE_ID);
+
+
+----------------------------
+-- Migration Cleanup Work --
+----------------------------
+
+create table MIGRATION_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade
+);
+
+create index MIGRATION_CLEANUP_WORK_JOB_ID on
+  MIGRATION_CLEANUP_WORK(JOB_ID);
+create index MIGRATION_CLEANUP_WORK_HOME_RESOURCE_ID on
+  MIGRATION_CLEANUP_WORK(HOME_RESOURCE_ID);
+
+-----------------------
+-- Home Cleanup Work --
+-----------------------
+
+create table HOME_CLEANUP_WORK (
+  WORK_ID          integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID           integer      references JOB not null,
+  OWNER_UID        varchar(255) not null
+);
+
+create index HOME_CLEANUP_WORK_JOB_ID on
+  HOME_CLEANUP_WORK(JOB_ID);
+
+--------------------------------
+-- Migrated Home Cleanup Work --
+--------------------------------
+
+create table MIGRATED_HOME_CLEANUP_WORK (
+  WORK_ID          integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID           integer      references JOB not null,
+  OWNER_UID        varchar(255) not null
+);
+
+create index MIGRATED_HOME_CLEANUP_WORK_JOB_ID on
+  MIGRATED_HOME_CLEANUP_WORK(JOB_ID);
+
+--------------------
+-- Schema Version --
+--------------------
+
+create table CALENDARSERVER (
+  NAME                          varchar(255) primary key, -- implicit index
+  VALUE                         varchar(255)
+);
+
+insert into CALENDARSERVER values ('VERSION', '55');
+insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '6');
+insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER values ('MIN-VALID-REVISION', '1');
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaoldpostgresdialectv56sqlfromrev15009CalendarServertrunktxdavcommondatastoresql_schemaoldpostgresdialectv56sql"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/postgres-dialect/v56.sql (from rev 15009, CalendarServer/trunk/txdav/common/datastore/sql_schema/old/postgres-dialect/v56.sql) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/postgres-dialect/v56.sql                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/old/postgres-dialect/v56.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,1289 @@
</span><ins>+-- -*- test-case-name: txdav.caldav.datastore.test.test_sql,txdav.carddav.datastore.test.test_sql -*-
+
+----
+-- Copyright (c) 2010-2015 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
+);
+
+
+--------------------
+-- Jobs           --
+--------------------
+
+create sequence JOB_SEQ;
+
+create table JOB (
+  JOB_ID      integer primary key default nextval('JOB_SEQ'), --implicit index
+  WORK_TYPE   varchar(255) not null,
+  PRIORITY    integer default 0,
+  WEIGHT      integer default 0,
+  NOT_BEFORE  timestamp not null,
+  ASSIGNED    timestamp default null,
+  OVERDUE     timestamp default null,
+  FAILED      integer default 0,
+  PAUSE       integer default 0
+);
+
+-------------------
+-- Calendar Home --
+-------------------
+
+create table CALENDAR_HOME (
+  RESOURCE_ID      integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID        varchar(255) not null,                                                -- implicit index
+  STATUS           integer      default 0 not null,                             -- enum HOME_STATUS
+  DATAVERSION      integer      default 0 not null,
+  
+  unique (OWNER_UID, STATUS)        -- implicit index
+);
+
+-- 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');
+insert into HOME_STATUS values (2, 'purging');
+insert into HOME_STATUS values (3, 'migrating');
+insert into HOME_STATUS values (4, 'disabled');
+
+
+--------------
+-- 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,
+  TRASH                    integer     default null references CALENDAR on delete set 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_TRASH on
+  CALENDAR_HOME_METADATA(TRASH);
+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,
+  CHILD_TYPE            integer      default 0 not null,                                     -- enum CHILD_TYPE
+  TRASHED               timestamp    default null,
+  IS_IN_TRASH           boolean      default false not null, -- collection is in the trash
+  CREATED               timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+-- Enumeration of child type
+
+create table CHILD_TYPE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CHILD_TYPE values (0, 'normal');
+insert into CHILD_TYPE values (1, 'inbox');
+insert into CHILD_TYPE values (2, 'trash');
+
+
+------------------------
+-- Calendar Migration --
+------------------------
+
+create table CALENDAR_MIGRATION (
+  CALENDAR_HOME_RESOURCE_ID                integer references CALENDAR_HOME on delete cascade,
+  REMOTE_RESOURCE_ID                        integer not null,
+  LOCAL_RESOURCE_ID                                integer        references CALENDAR on delete cascade,
+  LAST_SYNC_TOKEN                                varchar(255),
+   
+  primary key (CALENDAR_HOME_RESOURCE_ID, REMOTE_RESOURCE_ID) -- implicit index
+);
+
+create index CALENDAR_MIGRATION_LOCAL_RESOURCE_ID on
+  CALENDAR_MIGRATION(LOCAL_RESOURCE_ID);
+
+
+---------------------------
+-- Sharing Notifications --
+---------------------------
+
+create table NOTIFICATION_HOME (
+  RESOURCE_ID integer      primary key default nextval('RESOURCE_ID_SEQ'), -- implicit index
+  OWNER_UID   varchar(255) not null,                                           -- implicit index
+  STATUS      integer      default 0 not null,                             -- enum HOME_STATUS
+  DATAVERSION integer      default 0 not null,
+    
+  unique (OWNER_UID, STATUS)        -- implicit index
+);
+
+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,
+  BIND_UID                  varchar(36)  default 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');
+insert into CALENDAR_BIND_MODE values (5, 'group');
+insert into CALENDAR_BIND_MODE values (6, 'group_read');
+insert into CALENDAR_BIND_MODE values (7, 'group_write');
+
+-- 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_OBJ_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,
+  TRASHED              timestamp    default null,
+  ORIGINAL_COLLECTION  integer      default null, -- calendar_resource_id prior to trash
+  CREATED              timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED             timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+  DATAVERSION          integer      default 0 not null,
+
+  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_MIN on
+  CALENDAR_OBJECT(CALENDAR_RESOURCE_ID, RECURRANCE_MAX, RECURRANCE_MIN);
+
+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_OBJ_ATTACHMENTS_MODE (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE values (0, 'none' );
+insert into CALENDAR_OBJ_ATTACHMENTS_MODE values (1, 'read' );
+insert into CALENDAR_OBJ_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'  );
+
+
+-------------------
+-- Per-user data --
+-------------------
+
+create table PERUSER (
+  TIME_RANGE_INSTANCE_ID      integer      not null references TIME_RANGE on delete cascade,
+  USER_ID                     varchar(255) not null,
+  TRANSPARENT                 boolean      not null,
+  ADJUSTED_START_DATE         timestamp    default null,
+  ADJUSTED_END_DATE           timestamp    default null,
+
+  primary key (TIME_RANGE_INSTANCE_ID, USER_ID)    -- implicit index
+);
+
+
+-------------------------------
+-- Calendar Object Migration --
+-------------------------------
+
+create table CALENDAR_OBJECT_MIGRATION (
+  CALENDAR_HOME_RESOURCE_ID                integer references CALENDAR_HOME on delete cascade,
+  REMOTE_RESOURCE_ID                        integer not null,
+  LOCAL_RESOURCE_ID                                integer        references CALENDAR_OBJECT on delete cascade,
+   
+  primary key (CALENDAR_HOME_RESOURCE_ID, REMOTE_RESOURCE_ID) -- implicit index
+);
+
+create index CALENDAR_OBJECT_MIGRATION_HOME_LOCAL on
+  CALENDAR_OBJECT_MIGRATION(CALENDAR_HOME_RESOURCE_ID, LOCAL_RESOURCE_ID);
+create index CALENDAR_OBJECT_MIGRATION_LOCAL_RESOURCE_ID on
+  CALENDAR_OBJECT_MIGRATION(LOCAL_RESOURCE_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);
+
+create index ATTACHMENT_DROPBOX_ID on
+  ATTACHMENT(DROPBOX_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);
+
+-----------------------------------
+-- Calendar Attachment Migration --
+-----------------------------------
+
+create table ATTACHMENT_MIGRATION (
+  CALENDAR_HOME_RESOURCE_ID                integer references CALENDAR_HOME on delete cascade,
+  REMOTE_RESOURCE_ID                        integer not null,
+  LOCAL_RESOURCE_ID                                integer        references ATTACHMENT on delete cascade,
+   
+  primary key (CALENDAR_HOME_RESOURCE_ID, REMOTE_RESOURCE_ID) -- implicit index
+);
+
+create index ATTACHMENT_MIGRATION_HOME_LOCAL on
+  ATTACHMENT_MIGRATION(CALENDAR_HOME_RESOURCE_ID, LOCAL_RESOURCE_ID);
+create index ATTACHMENT_MIGRATION_LOCAL_RESOURCE_ID on
+  ATTACHMENT_MIGRATION(LOCAL_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,
+  STATUS                        integer         default 0 not null,                             -- enum HOME_STATUS
+  DATAVERSION                   integer         default 0 not null,
+    
+  unique (OWNER_UID, STATUS)        -- implicit index
+);
+
+
+-------------------------------
+-- 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,
+  BIND_UID                              varchar(36)     default 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,
+  TRASHED                       timestamp       default null,
+  IS_IN_TRASH                   boolean         default false not null,
+  CREATED                       timestamp       default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp       default timezone('UTC', CURRENT_TIMESTAMP),
+  DATAVERSION                   integer         default 0 not null,
+
+  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,
+  MODIFIED        timestamp   default timezone('UTC', CURRENT_TIMESTAMP),
+
+  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,
+  BIND_UID                          varchar(36)  default 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,
+  MODIFIED                  timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique(CALENDAR_HOME_RESOURCE_ID, CALENDAR_RESOURCE_ID, CALENDAR_NAME, RESOURCE_NAME)    -- implicit index
+);
+
+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);
+
+create index CALENDAR_OBJECT_REVISIONS_HOME_RESOURCE_ID_REVISION
+  on CALENDAR_OBJECT_REVISIONS(CALENDAR_HOME_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,
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  unique(ADDRESSBOOK_HOME_RESOURCE_ID, OWNER_HOME_RESOURCE_ID, ADDRESSBOOK_NAME, RESOURCE_NAME)    -- implicit index
+);
+
+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,
+  MODIFIED                      timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
+
+  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;
+
+---------------
+-- Test Work --
+---------------
+
+create table TEST_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  DELAY                                                 integer
+);
+
+create index TEST_WORK_JOB_ID on
+  TEST_WORK(JOB_ID);
+
+
+---------------------------
+-- IMIP Inivitation Work --
+---------------------------
+
+create table IMIP_INVITATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  FROM_ADDR                     varchar(255) not null,
+  TO_ADDR                       varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index IMIP_INVITATION_WORK_JOB_ID on
+  IMIP_INVITATION_WORK(JOB_ID);
+
+-----------------------
+-- IMIP Polling Work --
+-----------------------
+
+create table IMIP_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index IMIP_POLLING_WORK_JOB_ID on
+  IMIP_POLLING_WORK(JOB_ID);
+
+
+---------------------
+-- IMIP Reply Work --
+---------------------
+
+create table IMIP_REPLY_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ORGANIZER                     varchar(255) not null,
+  ATTENDEE                      varchar(255) not null,
+  ICALENDAR_TEXT                text         not null
+);
+
+create index IMIP_REPLY_WORK_JOB_ID on
+  IMIP_REPLY_WORK(JOB_ID);
+
+
+------------------------
+-- Push Notifications --
+------------------------
+
+create table PUSH_NOTIFICATION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  PUSH_ID                       varchar(255) not null,
+  PUSH_PRIORITY                 integer      not null -- 1:low 5:medium 10:high
+);
+
+create index PUSH_NOTIFICATION_WORK_JOB_ID on
+  PUSH_NOTIFICATION_WORK(JOB_ID);
+create index PUSH_NOTIFICATION_WORK_PUSH_ID on
+  PUSH_NOTIFICATION_WORK(PUSH_ID);
+
+-----------------
+-- GroupCacher --
+-----------------
+
+create table GROUP_CACHER_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index GROUP_CACHER_POLLING_WORK_JOB_ID on
+  GROUP_CACHER_POLLING_WORK(JOB_ID);
+
+create table GROUP_REFRESH_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  GROUP_UID                     varchar(255) not null
+);
+
+create index GROUP_REFRESH_WORK_JOB_ID on
+  GROUP_REFRESH_WORK(JOB_ID);
+create index GROUP_REFRESH_WORK_GROUP_UID on
+  GROUP_REFRESH_WORK(GROUP_UID);
+
+create table GROUP_DELEGATE_CHANGES_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  DELEGATOR_UID                 varchar(255) not null,
+  READ_DELEGATE_UID             varchar(255) not null,
+  WRITE_DELEGATE_UID            varchar(255) not null
+);
+
+create index GROUP_DELEGATE_CHANGES_WORK_JOB_ID on
+  GROUP_DELEGATE_CHANGES_WORK(JOB_ID);
+create index GROUP_DELEGATE_CHANGES_WORK_DELEGATOR_UID on
+  GROUP_DELEGATE_CHANGES_WORK(DELEGATOR_UID);
+
+create table GROUPS (
+  GROUP_ID                      integer      primary key default nextval('RESOURCE_ID_SEQ'),    -- implicit index
+  NAME                          varchar(255) not null,
+  GROUP_UID                     varchar(255) not null unique,                                                                        -- implicit index
+  MEMBERSHIP_HASH               varchar(255) not null,
+  EXTANT                        integer default 1,
+  CREATED                       timestamp default timezone('UTC', CURRENT_TIMESTAMP),
+  MODIFIED                      timestamp default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+create table GROUP_MEMBERSHIP (
+  GROUP_ID                     integer not null references GROUPS on delete cascade,
+  MEMBER_UID                   varchar(255) not null,
+
+  primary key (GROUP_ID, MEMBER_UID)
+);
+
+create index GROUP_MEMBERSHIP_MEMBER on
+  GROUP_MEMBERSHIP(MEMBER_UID);
+
+create table GROUP_ATTENDEE_RECONCILE_WORK (
+  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer not null references JOB,
+  RESOURCE_ID                   integer not null references CALENDAR_OBJECT on delete cascade,
+  GROUP_ID                      integer not null references GROUPS on delete cascade
+);
+
+create index GROUP_ATTENDEE_RECONCILE_WORK_JOB_ID on
+  GROUP_ATTENDEE_RECONCILE_WORK(JOB_ID);
+create index GROUP_ATTENDEE_RECONCILE_WORK_RESOURCE_ID on
+  GROUP_ATTENDEE_RECONCILE_WORK(RESOURCE_ID);
+create index GROUP_ATTENDEE_RECONCILE_WORK_GROUP_ID on
+  GROUP_ATTENDEE_RECONCILE_WORK(GROUP_ID);
+
+
+create table GROUP_ATTENDEE (
+  GROUP_ID                      integer not null references GROUPS on delete cascade,
+  RESOURCE_ID                   integer not null references CALENDAR_OBJECT on delete cascade,
+  MEMBERSHIP_HASH               varchar(255) not null,
+
+  primary key (GROUP_ID, RESOURCE_ID)
+);
+
+create index GROUP_ATTENDEE_RESOURCE_ID on
+  GROUP_ATTENDEE(RESOURCE_ID);
+
+
+create table GROUP_SHAREE_RECONCILE_WORK (
+  WORK_ID                       integer primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer not null references JOB,
+  CALENDAR_ID                   integer        not null references CALENDAR on delete cascade,
+  GROUP_ID                      integer not null references GROUPS on delete cascade
+);
+
+create index GROUP_SHAREE_RECONCILE_WORK_JOB_ID on
+  GROUP_SHAREE_RECONCILE_WORK(JOB_ID);
+create index GROUP_SHAREE_RECONCILE_WORK_CALENDAR_ID on
+  GROUP_SHAREE_RECONCILE_WORK(CALENDAR_ID);
+create index GROUP_SHAREE_RECONCILE_WORK_GROUP_ID on
+  GROUP_SHAREE_RECONCILE_WORK(GROUP_ID);
+
+
+create table GROUP_SHAREE (
+  GROUP_ID                      integer not null references GROUPS on delete cascade,
+  CALENDAR_ID                                      integer not null references CALENDAR on delete cascade,
+  GROUP_BIND_MODE               integer not null, -- enum CALENDAR_BIND_MODE
+  MEMBERSHIP_HASH               varchar(255) not null,
+
+  primary key (GROUP_ID, CALENDAR_ID)
+);
+
+create index GROUP_SHAREE_CALENDAR_ID on
+  GROUP_SHAREE(CALENDAR_ID);
+
+---------------
+-- Delegates --
+---------------
+
+create table DELEGATES (
+  DELEGATOR                     varchar(255) not null,
+  DELEGATE                      varchar(255) not null,
+  READ_WRITE                    integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+
+  primary key (DELEGATOR, READ_WRITE, DELEGATE)
+);
+create index DELEGATE_TO_DELEGATOR on
+  DELEGATES(DELEGATE, READ_WRITE, DELEGATOR);
+
+create table DELEGATE_GROUPS (
+  DELEGATOR                     varchar(255) not null,
+  GROUP_ID                      integer      not null references GROUPS on delete cascade,
+  READ_WRITE                    integer      not null, -- 1 = ReadWrite, 0 = ReadOnly
+  IS_EXTERNAL                   integer      not null, -- 1 = External, 0 = Internal
+
+  primary key (DELEGATOR, READ_WRITE, GROUP_ID)
+);
+create index DELEGATE_GROUPS_GROUP_ID on
+  DELEGATE_GROUPS(GROUP_ID);
+
+create table EXTERNAL_DELEGATE_GROUPS (
+  DELEGATOR                     varchar(255) primary key,
+  GROUP_UID_READ                varchar(255),
+  GROUP_UID_WRITE               varchar(255)
+);
+
+--------------------------
+-- Object Splitter Work --
+--------------------------
+
+create table CALENDAR_OBJECT_SPLITTER_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  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);
+create index CALENDAR_OBJECT_SPLITTER_WORK_JOB_ID on
+  CALENDAR_OBJECT_SPLITTER_WORK(JOB_ID);
+
+-------------------------
+-- Object Upgrade Work --
+-------------------------
+
+create table CALENDAR_OBJECT_UPGRADE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade
+);
+
+create index CALENDAR_OBJECT_UPGRADE_WORK_RESOURCE_ID on
+  CALENDAR_OBJECT_UPGRADE_WORK(RESOURCE_ID);
+create index CALENDAR_OBJECT_UPGRADE_WORK_JOB_ID on
+  CALENDAR_OBJECT_UPGRADE_WORK(JOB_ID);
+
+---------------------------
+-- Revision Cleanup Work --
+---------------------------
+
+create table FIND_MIN_VALID_REVISION_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index FIND_MIN_VALID_REVISION_WORK_JOB_ID on
+  FIND_MIN_VALID_REVISION_WORK(JOB_ID);
+
+create table REVISION_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index REVISION_CLEANUP_WORK_JOB_ID on
+  REVISION_CLEANUP_WORK(JOB_ID);
+
+------------------------
+-- Inbox Cleanup Work --
+------------------------
+
+create table INBOX_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index INBOX_CLEANUP_WORK_JOB_ID on
+   INBOX_CLEANUP_WORK(JOB_ID);
+
+create table CLEANUP_ONE_INBOX_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_ID                       integer      not null unique references CALENDAR_HOME on delete cascade -- implicit index
+);
+
+create index CLEANUP_ONE_INBOX_WORK_JOB_ID on
+  CLEANUP_ONE_INBOX_WORK(JOB_ID);
+
+-------------------
+-- Schedule Work --
+-------------------
+
+create table SCHEDULE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  ICALENDAR_UID                 varchar(255) not null,
+  WORK_TYPE                     varchar(255) not null
+);
+
+create index SCHEDULE_WORK_JOB_ID on
+  SCHEDULE_WORK(JOB_ID);
+create index SCHEDULE_WORK_ICALENDAR_UID on
+  SCHEDULE_WORK(ICALENDAR_UID);
+
+---------------------------
+-- Schedule Refresh Work --
+---------------------------
+
+create table SCHEDULE_REFRESH_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE_COUNT                integer
+);
+
+create index SCHEDULE_REFRESH_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REFRESH_WORK_RESOURCE_ID on
+  SCHEDULE_REFRESH_WORK(RESOURCE_ID);
+
+create table SCHEDULE_REFRESH_ATTENDEES (
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  ATTENDEE                      varchar(255) not null,
+
+  primary key (RESOURCE_ID, ATTENDEE)
+);
+
+------------------------------
+-- Schedule Auto Reply Work --
+------------------------------
+
+create table SCHEDULE_AUTO_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer      not null references CALENDAR_OBJECT on delete cascade,
+  PARTSTAT                      varchar(255) not null
+);
+
+create index SCHEDULE_AUTO_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_AUTO_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_AUTO_REPLY_WORK(RESOURCE_ID);
+
+-----------------------------
+-- Schedule Organizer Work --
+-----------------------------
+
+create table SCHEDULE_ORGANIZER_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ICALENDAR_TEXT_OLD            text,
+  ICALENDAR_TEXT_NEW            text,
+  ATTENDEE_COUNT                integer,
+  SMART_MERGE                   boolean
+);
+
+create index SCHEDULE_ORGANIZER_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_WORK(RESOURCE_ID);
+
+-- Enumeration of schedule actions
+
+create table SCHEDULE_ACTION (
+  ID          integer     primary key,
+  DESCRIPTION varchar(16) not null unique
+);
+
+insert into SCHEDULE_ACTION values (0, 'create');
+insert into SCHEDULE_ACTION values (1, 'modify');
+insert into SCHEDULE_ACTION values (2, 'modify-cancelled');
+insert into SCHEDULE_ACTION values (3, 'remove');
+
+----------------------------------
+-- Schedule Organizer Send Work --
+----------------------------------
+
+create table SCHEDULE_ORGANIZER_SEND_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  SCHEDULE_ACTION               integer      not null, -- Enum SCHEDULE_ACTION
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ATTENDEE                      varchar(255) not null,
+  ITIP_MSG                      text,
+  NO_REFRESH                    boolean
+);
+
+create index SCHEDULE_ORGANIZER_SEND_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_ORGANIZER_SEND_WORK_RESOURCE_ID on
+  SCHEDULE_ORGANIZER_SEND_WORK(RESOURCE_ID);
+
+-------------------------
+-- Schedule Reply Work --
+-------------------------
+
+create table SCHEDULE_REPLY_WORK (
+  WORK_ID                       integer      primary key references SCHEDULE_WORK on delete cascade, -- implicit index
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade,
+  RESOURCE_ID                   integer,     -- this references a possibly non-existent CALENDAR_OBJECT
+  ITIP_MSG                      text
+);
+
+create index SCHEDULE_REPLY_WORK_HOME_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(HOME_RESOURCE_ID);
+create index SCHEDULE_REPLY_WORK_RESOURCE_ID on
+  SCHEDULE_REPLY_WORK(RESOURCE_ID);
+
+----------------------------------
+-- Principal Purge Polling Work --
+----------------------------------
+
+create table PRINCIPAL_PURGE_POLLING_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null
+);
+
+create index PRINCIPAL_PURGE_POLLING_WORK_JOB_ID on
+  PRINCIPAL_PURGE_POLLING_WORK(JOB_ID);
+
+--------------------------------
+-- Principal Purge Check Work --
+--------------------------------
+
+create table PRINCIPAL_PURGE_CHECK_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  UID                           varchar(255) not null
+);
+
+create index PRINCIPAL_PURGE_CHECK_WORK_JOB_ID on
+  PRINCIPAL_PURGE_CHECK_WORK(JOB_ID);
+create index PRINCIPAL_PURGE_CHECK_WORK_UID on
+  PRINCIPAL_PURGE_CHECK_WORK(UID);
+
+--------------------------
+-- Principal Purge Work --
+--------------------------
+
+create table PRINCIPAL_PURGE_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  UID                           varchar(255) not null
+);
+
+create index PRINCIPAL_PURGE_WORK_JOB_ID on
+  PRINCIPAL_PURGE_WORK(JOB_ID);
+create index PRINCIPAL_PURGE_WORK_UID on
+  PRINCIPAL_PURGE_WORK(UID);
+
+
+--------------------------------
+-- Principal Home Remove Work --
+--------------------------------
+
+create table PRINCIPAL_PURGE_HOME_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade
+);
+
+create index PRINCIPAL_PURGE_HOME_WORK_JOB_ID on
+  PRINCIPAL_PURGE_HOME_WORK(JOB_ID);
+create index PRINCIPAL_PURGE_HOME_HOME_RESOURCE_ID on
+  PRINCIPAL_PURGE_HOME_WORK(HOME_RESOURCE_ID);
+
+
+----------------------------
+-- Migration Cleanup Work --
+----------------------------
+
+create table MIGRATION_CLEANUP_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  HOME_RESOURCE_ID              integer      not null references CALENDAR_HOME on delete cascade
+);
+
+create index MIGRATION_CLEANUP_WORK_JOB_ID on
+  MIGRATION_CLEANUP_WORK(JOB_ID);
+create index MIGRATION_CLEANUP_WORK_HOME_RESOURCE_ID on
+  MIGRATION_CLEANUP_WORK(HOME_RESOURCE_ID);
+
+-----------------------
+-- Home Cleanup Work --
+-----------------------
+
+create table HOME_CLEANUP_WORK (
+  WORK_ID          integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID           integer      references JOB not null,
+  OWNER_UID        varchar(255) not null
+);
+
+create index HOME_CLEANUP_WORK_JOB_ID on
+  HOME_CLEANUP_WORK(JOB_ID);
+
+--------------------------------
+-- Migrated Home Cleanup Work --
+--------------------------------
+
+create table MIGRATED_HOME_CLEANUP_WORK (
+  WORK_ID          integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID           integer      references JOB not null,
+  OWNER_UID        varchar(255) not null
+);
+
+create index MIGRATED_HOME_CLEANUP_WORK_JOB_ID on
+  MIGRATED_HOME_CLEANUP_WORK(JOB_ID);
+
+--------------------
+-- Schema Version --
+--------------------
+
+create table CALENDARSERVER (
+  NAME                          varchar(255) primary key, -- implicit index
+  VALUE                         varchar(255)
+);
+
+insert into CALENDARSERVER values ('VERSION', '56');
+insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '6');
+insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
+insert into CALENDARSERVER values ('NOTIFICATION-DATAVERSION', '1');
+insert into CALENDARSERVER values ('MIN-VALID-REVISION', '1');
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_55_to_56sqlfromrev15009CalendarServertrunktxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_55_to_56sql"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_55_to_56.sql (from rev 15009, CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_55_to_56.sql) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_55_to_56.sql                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_55_to_56.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,33 @@
</span><ins>+----
+-- Copyright (c) 2012-2015 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 55 to 56 --
+---------------------------------------------------
+
+-- New table
+create table TEST_WORK (
+    &quot;WORK_ID&quot; integer primary key,
+    &quot;JOB_ID&quot; integer not null references JOB,
+    &quot;DELAY&quot; integer
+);
+
+create index TEST_WORK_JOB_ID_228ede32 on TEST_WORK (
+    &quot;JOB_ID&quot;
+);
+
+-- update the version
+update CALENDARSERVER set VALUE = '56' where NAME = 'VERSION';
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_56_to_57sqlfromrev15009CalendarServertrunktxdavcommondatastoresql_schemaupgradesoracledialectupgrade_from_56_to_57sql"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_56_to_57.sql (from rev 15009, CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_56_to_57.sql) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_56_to_57.sql                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/oracle-dialect/upgrade_from_56_to_57.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,28 @@
</span><ins>+----
+-- Copyright (c) 2012-2015 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 56 to 57 --
+---------------------------------------------------
+
+-- pre-delete any that would conflict during the update
+delete from IMIP_TOKENS where (ORGANIZER, ATTENDEE, ICALUID) in (select concat('urn:uuid:', substr(ORGANIZER, 11)), ATTENDEE, ICALUID from IMIP_TOKENS where substr(ORGANIZER, 1, 10) = 'urn:x-uid:');
+
+-- convert the old-style urn:uuid: CUAs to new style urn:x-uid:
+update IMIP_TOKENS set ORGANIZER = concat('urn:x-uid:', substr(ORGANIZER, 10)) where substr(ORGANIZER, 1, 9) = 'urn:uuid:';
+
+-- update the version
+update CALENDARSERVER set VALUE = '57' where NAME = 'VERSION';
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_55_to_56sqlfromrev15009CalendarServertrunktxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_55_to_56sql"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_55_to_56.sql (from rev 15009, CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_55_to_56.sql) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_55_to_56.sql                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_55_to_56.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,32 @@
</span><ins>+----
+-- Copyright (c) 2012-2015 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 55 to 56 --
+---------------------------------------------------
+
+-- New table
+create table TEST_WORK (
+  WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ'), -- implicit index
+  JOB_ID                        integer      references JOB not null,
+  DELAY                                                 integer
+);
+
+create index TEST_WORK_JOB_ID on
+  TEST_WORK(JOB_ID);
+
+-- update the version
+update CALENDARSERVER set VALUE = '56' where NAME = 'VERSION';
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_56_to_57sqlfromrev15009CalendarServertrunktxdavcommondatastoresql_schemaupgradespostgresdialectupgrade_from_56_to_57sql"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_56_to_57.sql (from rev 15009, CalendarServer/trunk/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_56_to_57.sql) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_56_to_57.sql                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_schema/upgrades/postgres-dialect/upgrade_from_56_to_57.sql        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,28 @@
</span><ins>+----
+-- Copyright (c) 2012-2015 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 56 to 57 --
+---------------------------------------------------
+
+-- pre-delete any that would conflict during the update
+delete from IMIP_TOKENS where (ORGANIZER, ATTENDEE, ICALUID) in (select concat('urn:uuid:', substr(ORGANIZER, 11)), ATTENDEE, ICALUID from IMIP_TOKENS where substr(ORGANIZER, 1, 10) = 'urn:x-uid:');
+
+-- convert the old-style urn:uuid: CUAs to new style urn:x-uid:
+update IMIP_TOKENS set ORGANIZER = concat('urn:x-uid:', substr(ORGANIZER, 10)) where substr(ORGANIZER, 1, 9) = 'urn:uuid:';
+
+-- update the version
+update CALENDARSERVER set VALUE = '57' where NAME = 'VERSION';
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_sharing.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_sharing.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_sharing.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -19,7 +19,7 @@
</span><span class="cx"> from pycalendar.datetime import DateTime
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.dal.syntax import Insert, Parameter, Update, Delete, \
</span><del>-    Select, Max
</del><ins>+    Select
</ins><span class="cx"> from twext.python.clsprop import classproperty
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><span class="lines">@@ -1451,18 +1451,6 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @classmethod
-    def _revisionsForResourceIDs(cls, resourceIDs):
-        rev = cls._revisionsSchema
-        return Select(
-            [rev.RESOURCE_ID, Max(rev.REVISION)],
-            From=rev,
-            Where=rev.RESOURCE_ID.In(Parameter(&quot;resourceIDs&quot;, len(resourceIDs))).And(
-                (rev.RESOURCE_NAME != None).Or(rev.DELETED == False)),
-            GroupBy=rev.RESOURCE_ID
-        )
-
-
</del><span class="cx">     @inlineCallbacks
</span><span class="cx">     def invalidateQueryCache(self):
</span><span class="cx">         queryCacher = self._txn._queryCacher
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoresql_utilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_util.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_util.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/sql_util.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -16,7 +16,7 @@
</span><span class="cx"> ##
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.dal.syntax import Max, Select, Parameter, Delete, Insert, \
</span><del>-    Update, ColumnSyntax, TableSyntax, Upper
</del><ins>+    Update, ColumnSyntax, TableSyntax, Upper, utcNowSQL
</ins><span class="cx"> from twext.python.clsprop import classproperty
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twisted.internet.defer import succeed, inlineCallbacks, returnValue
</span><span class="lines">@@ -66,6 +66,18 @@
</span><span class="cx">                       Where=rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
+    def _revisionsForResourceIDs(cls, resourceIDs):
+        rev = cls._revisionsSchema
+        return Select(
+            [rev.RESOURCE_ID, Max(rev.REVISION)],
+            From=rev,
+            Where=rev.RESOURCE_ID.In(Parameter(&quot;resourceIDs&quot;, len(resourceIDs))).And(
+                (rev.RESOURCE_NAME != None).Or(rev.DELETED == False)),
+            GroupBy=rev.RESOURCE_ID
+        )
+
+
</ins><span class="cx">     def revisionFromToken(self, token):
</span><span class="cx">         if token is None:
</span><span class="cx">             return 0
</span><span class="lines">@@ -91,6 +103,21 @@
</span><span class="cx">         returnValue(revision)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @classmethod
+    @inlineCallbacks
+    def childSyncTokenRevisions(cls, home, childResourceIDs):
+        rows = (yield cls._revisionsForResourceIDs(childResourceIDs).on(home._txn, resourceIDs=childResourceIDs))
+        revisions = dict(rows)
+
+        # Add in any that were missing - this assumes that childResourceIDs were all valid to begin with
+        missingIDs = set(childResourceIDs) - set(revisions.keys())
+        if missingIDs:
+            min_revision = int((yield home._txn.calendarserverValue(&quot;MIN-VALID-REVISION&quot;)))
+            for resourceID in missingIDs:
+                revisions[resourceID] = min_revision
+        returnValue(revisions)
+
+
</ins><span class="cx">     def objectResourcesSinceToken(self, token):
</span><span class="cx">         raise NotImplementedError()
</span><span class="cx"> 
</span><span class="lines">@@ -206,7 +233,8 @@
</span><span class="cx">         return Update(
</span><span class="cx">             {
</span><span class="cx">                 rev.REVISION: schema.REVISION_SEQ,
</span><del>-                rev.COLLECTION_NAME: Parameter(&quot;name&quot;)
</del><ins>+                rev.COLLECTION_NAME: Parameter(&quot;name&quot;),
+                rev.MODIFIED: utcNowSQL,
</ins><span class="cx">             },
</span><span class="cx">             Where=(rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;)).And
</span><span class="cx">                   (rev.RESOURCE_NAME == None),
</span><span class="lines">@@ -233,7 +261,10 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         rev = cls._revisionsSchema
</span><span class="cx">         return Update(
</span><del>-            {rev.REVISION: schema.REVISION_SEQ, },
</del><ins>+            {
+                rev.REVISION: schema.REVISION_SEQ,
+                rev.MODIFIED: utcNowSQL,
+            },
</ins><span class="cx">             Where=(rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;)).And
</span><span class="cx">                   (rev.RESOURCE_NAME == None)
</span><span class="cx">         )
</span><span class="lines">@@ -276,7 +307,8 @@
</span><span class="cx">             {
</span><span class="cx">                 rev.RESOURCE_ID: None,
</span><span class="cx">                 rev.REVISION: schema.REVISION_SEQ,
</span><del>-                rev.DELETED: True
</del><ins>+                rev.DELETED: True,
+                rev.MODIFIED: utcNowSQL,
</ins><span class="cx">             },
</span><span class="cx">             Where=(rev.HOME_RESOURCE_ID == Parameter(&quot;homeID&quot;)).And(
</span><span class="cx">                 rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;)).And(
</span><span class="lines">@@ -294,7 +326,8 @@
</span><span class="cx">             {
</span><span class="cx">                 rev.RESOURCE_ID: None,
</span><span class="cx">                 rev.REVISION: schema.REVISION_SEQ,
</span><del>-                rev.DELETED: True
</del><ins>+                rev.DELETED: True,
+                rev.MODIFIED: utcNowSQL,
</ins><span class="cx">             },
</span><span class="cx">             Where=(rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;)).And(
</span><span class="cx">                 rev.RESOURCE_NAME == None),
</span><span class="lines">@@ -346,7 +379,11 @@
</span><span class="cx">     def _deleteBumpTokenQuery(cls):
</span><span class="cx">         rev = cls._revisionsSchema
</span><span class="cx">         return Update(
</span><del>-            {rev.REVISION: schema.REVISION_SEQ, rev.DELETED: True},
</del><ins>+            {
+                rev.REVISION: schema.REVISION_SEQ,
+                rev.DELETED: True,
+                rev.MODIFIED: utcNowSQL,
+            },
</ins><span class="cx">             Where=(rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;)).And(
</span><span class="cx">                 rev.RESOURCE_NAME == Parameter(&quot;name&quot;)),
</span><span class="cx">             Return=rev.REVISION
</span><span class="lines">@@ -357,7 +394,10 @@
</span><span class="cx">     def _updateBumpTokenQuery(cls):
</span><span class="cx">         rev = cls._revisionsSchema
</span><span class="cx">         return Update(
</span><del>-            {rev.REVISION: schema.REVISION_SEQ},
</del><ins>+            {
+                rev.REVISION: schema.REVISION_SEQ,
+                rev.MODIFIED: utcNowSQL,
+            },
</ins><span class="cx">             Where=(rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;)).And(
</span><span class="cx">                 rev.RESOURCE_NAME == Parameter(&quot;name&quot;)),
</span><span class="cx">             Return=rev.REVISION
</span><span class="lines">@@ -379,7 +419,11 @@
</span><span class="cx">     def _updatePreviouslyNamedQuery(cls):
</span><span class="cx">         rev = cls._revisionsSchema
</span><span class="cx">         return Update(
</span><del>-            {rev.REVISION: schema.REVISION_SEQ, rev.DELETED: False},
</del><ins>+            {
+                rev.REVISION: schema.REVISION_SEQ,
+                rev.DELETED: False,
+                rev.MODIFIED: utcNowSQL,
+            },
</ins><span class="cx">             Where=(rev.RESOURCE_ID == Parameter(&quot;resourceID&quot;)).And(
</span><span class="cx">                 rev.RESOURCE_NAME == Parameter(&quot;name&quot;)),
</span><span class="cx">             Return=rev.REVISION
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoretestaccountsaccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/test/accounts/accounts.xml (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/test/accounts/accounts.xml        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/test/accounts/accounts.xml        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -30,7 +30,7 @@
</span><span class="cx">     &lt;guid&gt;6423F94A-6B76-4A3A-815B-D52CFD77935D&lt;/guid&gt;
</span><span class="cx">     &lt;short-name&gt;wsanchez&lt;/short-name&gt;
</span><span class="cx">     &lt;password&gt;zehcnasw&lt;/password&gt;
</span><del>-    &lt;full-name&gt;Wilfredo Sanchez&lt;/full-name&gt;
</del><ins>+    &lt;full-name&gt;Wilfredo Sanchez-Vega&lt;/full-name&gt;
</ins><span class="cx">     &lt;email&gt;wsanchez@example.com&lt;/email&gt;
</span><span class="cx">   &lt;/record&gt;
</span><span class="cx">   &lt;record type=&quot;user&quot;&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/test/util.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/test/util.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/test/util.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -49,7 +49,7 @@
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav import ical
</span><span class="cx"> from twistedcaldav.config import config, ConfigDict
</span><del>-from twistedcaldav.ical import Component as VComponent, Component
</del><ins>+from twistedcaldav.ical import Component as VComponent
</ins><span class="cx"> from twistedcaldav.stdconfig import DEFAULT_CONFIG
</span><span class="cx"> from twistedcaldav.vcard import Component as ABComponent
</span><span class="cx"> 
</span><span class="lines">@@ -260,10 +260,7 @@
</span><span class="cx">             {&quot;push&quot;: notifierFactory} if notifierFactory is not None else {},
</span><span class="cx">             directoryService,
</span><span class="cx">             attachmentRoot,
</span><del>-            (
-                &quot;https://example.com/calendars/__uids__/&quot;
-                &quot;%(home)s/attachments/%(name)s&quot;
-            ),
</del><ins>+            &quot;https://example.com/calendars/__uids__/%(home)s/dropbox/%(dropbox_id)s/%(name)s&quot;,
</ins><span class="cx">             quota=quota
</span><span class="cx">         )
</span><span class="cx">         store.label = currentTestID
</span><span class="lines">@@ -549,40 +546,16 @@
</span><span class="cx">     current year.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+    subs = {}
</ins><span class="cx">     nowYear = DateTime.getToday().getYear()
</span><del>-    return data % {&quot;now&quot;: nowYear}
</del><ins>+    subs[&quot;now&quot;] = nowYear
+    for i in range(1, 10):
+        subs[&quot;now-{}&quot;.format(i)] = nowYear - 1
+        subs[&quot;now+{}&quot;.format(i)] = nowYear + 1
+    return data % subs
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-relativeDateSubstitutions = {}
</del><span class="cx"> 
</span><del>-
-def componentUpdate(data):
-    &quot;&quot;&quot;
-    Update the supplied iCalendar data so that all dates are updated to the
-    current year.
-    &quot;&quot;&quot;
-
-    if len(relativeDateSubstitutions) == 0:
-        now = DateTime.getToday()
-
-        relativeDateSubstitutions[&quot;now&quot;] = now
-
-        for i in range(30):
-            attrname = &quot;now_back%s&quot; % (i + 1,)
-            dt = now.duplicate()
-            dt.offsetDay(-(i + 1))
-            relativeDateSubstitutions[attrname] = dt
-
-        for i in range(30):
-            attrname = &quot;now_fwd%s&quot; % (i + 1,)
-            dt = now.duplicate()
-            dt.offsetDay(i + 1)
-            relativeDateSubstitutions[attrname] = dt
-
-    return Component.fromString(data.format(**relativeDateSubstitutions))
-
-
-
</del><span class="cx"> @inlineCallbacks
</span><span class="cx"> def resetCalendarMD5s(md5s, store):
</span><span class="cx">     &quot;&quot;&quot;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreupgradesqltesttest_upgrade_with_datapy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/upgrade/sql/test/test_upgrade_with_data.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/upgrade/sql/test/test_upgrade_with_data.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/upgrade/sql/test/test_upgrade_with_data.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -25,6 +25,7 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><span class="cx"> 
</span><ins>+from txdav.caldav.datastore.scheduling.imip.token import iMIPTokenRecord
</ins><span class="cx"> from txdav.caldav.datastore.scheduling.work import ScheduleReplyWork, \
</span><span class="cx">     ScheduleWork
</span><span class="cx"> from txdav.common.datastore.sql_tables import _populateSchema
</span><span class="lines">@@ -298,3 +299,44 @@
</span><span class="cx">         self.assertEqual(workers[0].workType, &quot;SCHEDULE_REPLY_WORK&quot;)
</span><span class="cx"> 
</span><span class="cx">         yield txn.commit()
</span><ins>+
+
+    @inlineCallbacks
+    def test_upgrade_imipTokens(self):
+        &quot;&quot;&quot;
+        Old-style canonical CUAs (urn:uuid:) are converted to new style (urn:x-uid:)
+        &quot;&quot;&quot;
+
+        # Load old schema and populate with data
+        yield self._loadOldSchema(self.upgradePath.child(&quot;v56.sql&quot;))
+
+        # Add two tokens records crafted to simulate conflicting old-style and
+        # new style CUAs -- the result should be only the new-style copy.
+        txn = self.store.newTransaction(&quot;loadData&quot;)
+        yield iMIPTokenRecord.create(
+            txn,
+            token=&quot;123&quot;,
+            organizer=&quot;urn:uuid:PLUGH&quot;,
+            attendee=&quot;mailto:user@example.com&quot;,
+            icaluid=&quot;XYZZY&quot;
+        )
+        yield iMIPTokenRecord.create(
+            txn,
+            token=&quot;456&quot;,
+            organizer=&quot;urn:x-uid:PLUGH&quot;,
+            attendee=&quot;mailto:user@example.com&quot;,
+            icaluid=&quot;XYZZY&quot;
+        )
+        yield txn.commit()
+
+        upgrader = UpgradeDatabaseSchemaStep(self.store)
+        yield upgrader.databaseUpgrade()
+
+        txn = self.store.newTransaction(&quot;loadData&quot;)
+        tokens = yield iMIPTokenRecord.all(txn)
+
+        self.assertEqual(len(tokens), 1)
+        token = list(tokens)[0]
+        self.assertEqual(token.token, &quot;456&quot;)
+        self.assertEqual(token.organizer, &quot;urn:x-uid:PLUGH&quot;)
+        yield txn.commit()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreupgradetesttest_migratepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/upgrade/test/test_migrate.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/upgrade/test/test_migrate.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/upgrade/test/test_migrate.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -295,6 +295,15 @@
</span><span class="cx">         L{UpgradeToDatabaseService.startService} will do the upgrade, then
</span><span class="cx">         start its dependent service by adding it to its service hierarchy.
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
+        # Create a fake directory in the same place as a home, but with a non-existent uid
+        fake_dir = self.filesPath.child(&quot;calendars&quot;).child(&quot;__uids__&quot;).child(&quot;ho&quot;).child(&quot;me&quot;).child(&quot;foobar&quot;)
+        fake_dir.makedirs()
+
+        # Create a fake file in the same place as a home,with a name that matches the hash uid prefix
+        fake_file = self.filesPath.child(&quot;calendars&quot;).child(&quot;__uids__&quot;).child(&quot;ho&quot;).child(&quot;me&quot;).child(&quot;home_file&quot;)
+        fake_file.setContent(&quot;&quot;)
+
</ins><span class="cx">         yield self.upgrader.stepWithResult(None)
</span><span class="cx">         txn = self.sqlStore.newTransaction()
</span><span class="cx">         self.addCleanup(txn.commit)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreworkload_workpyfromrev15009CalendarServertrunktxdavcommondatastoreworkload_workpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/load_work.py (from rev 15009, CalendarServer/trunk/txdav/common/datastore/work/load_work.py) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/load_work.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/load_work.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,76 @@
</span><ins>+# -*- test-case-name: txdav.common.datastore.work.test.test_revision_cleanup -*-
+##
+# Copyright (c) 2013-2015 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.record import fromTable
+from twext.enterprise.jobqueue import WorkItem
+from twext.python.log import Logger
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, Deferred
+from txdav.common.datastore.sql_tables import schema
+
+log = Logger()
+
+
+class TestWork(WorkItem, fromTable(schema.TEST_WORK)):
+    &quot;&quot;&quot;
+    This work item is used solely for testing purposes to allow us to simulate different
+    types of work with varying priority, weight and notBefore, and taking a variable amount of
+    time to complete. This will allow us to load test the job queue.
+    &quot;&quot;&quot;
+
+    @classmethod
+    def schedule(cls, store, delay, priority, weight, runtime):
+        &quot;&quot;&quot;
+        Create a new L{TestWork} item.
+
+        @param store: the L{CommonStore} to use
+        @type store: L{CommonStore}
+        @param delay: seconds before work executes
+        @type delay: L{int}
+        @param priority: priority to use for this work
+        @type priority: L{int}
+        @param weight: weight to use for thus work
+        @type weight: L{int}
+        @param runtime: amount of time this work should take to execute in milliseconds
+        @type runtime: L{int}
+        &quot;&quot;&quot;
+        def _enqueue(txn):
+            return TestWork.reschedule(
+                txn,
+                delay,
+                priority=priority,
+                weight=weight,
+                delay=runtime
+            )
+
+        return store.inTransaction(&quot;TestWork.schedule&quot;, _enqueue)
+
+
+    @inlineCallbacks
+    def doWork(self):
+        &quot;&quot;&quot;
+        All this work does is wait for the specified amount of time.
+        &quot;&quot;&quot;
+
+        log.debug(&quot;TestWork started: {}&quot;.format(self.jobID))
+        if self.delay != 0:
+            wait = Deferred()
+            def _timedDeferred():
+                wait.callback(True)
+            reactor.callLater(self.delay / 1000.0, _timedDeferred)
+            yield wait
+        log.debug(&quot;TestWork done: {}&quot;.format(self.jobID))
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreworkrevision_cleanuppy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/revision_cleanup.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/revision_cleanup.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/revision_cleanup.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -53,6 +53,10 @@
</span><span class="cx">         return float(config.RevisionCleanup.CleanupPeriodDays) * 24 * 60 * 60
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def dateCutoff(self):
+        return datetime.datetime.utcnow() - datetime.timedelta(days=float(config.RevisionCleanup.SyncTokenLifetimeDays))
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def doWork(self):
</span><span class="cx"> 
</span><span class="lines">@@ -60,10 +64,7 @@
</span><span class="cx">         minValidRevision = int((yield self.transaction.calendarserverValue(&quot;MIN-VALID-REVISION&quot;)))
</span><span class="cx"> 
</span><span class="cx">         # get max revision on table rows before dateLimit
</span><del>-        dateLimit = (
-            datetime.datetime.utcnow() -
-            datetime.timedelta(days=float(config.RevisionCleanup.SyncTokenLifetimeDays))
-        )
</del><ins>+        dateLimit = self.dateCutoff()
</ins><span class="cx">         maxRevOlderThanDate = 0
</span><span class="cx"> 
</span><span class="cx">         # TODO: Use one Select statement
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreworktesttest_load_workpyfromrev15009CalendarServertrunktxdavcommondatastoreworktesttest_load_workpy"></a>
<div class="copfile"><h4>Copied: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/test/test_load_work.py (from rev 15009, CalendarServer/trunk/txdav/common/datastore/work/test/test_load_work.py) (0 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/test/test_load_work.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/test/test_load_work.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -0,0 +1,57 @@
</span><ins>+##
+# Copyright (c) 2013-2015 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.jobqueue import JobItem
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+from twisted.trial.unittest import TestCase
+from txdav.common.datastore.test.util import CommonCommonTests
+from txdav.common.datastore.work.load_work import TestWork
+
+
+
+class LoadWorkTests(CommonCommonTests, TestCase):
+    &quot;&quot;&quot;
+    Test L{TestWork}.
+    &quot;&quot;&quot;
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(LoadWorkTests, self).setUp()
+        yield self.buildStoreAndDirectory()
+
+
+    @inlineCallbacks
+    def test_basicWork(self):
+        &quot;&quot;&quot;
+        Verify that an L{TestWork} item can be enqueued and executed.
+        &quot;&quot;&quot;
+
+        # do FindMinValidRevisionWork
+        yield TestWork.schedule(self.storeUnderTest(), 0, 1, 2, 3)
+
+        work = yield TestWork.all(self.transactionUnderTest())
+        self.assertEqual(len(work), 1)
+        self.assertEqual(work[0].delay, 3)
+        job = yield JobItem.querysimple(self.transactionUnderTest(), jobID=work[0].jobID)
+        self.assertEqual(len(job), 1)
+        self.assertEqual(job[0].priority, 1)
+        self.assertEqual(job[0].weight, 2)
+        yield self.commit()
+
+        yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavcommondatastoreworktesttest_revision_cleanuppy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/test/test_revision_cleanup.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/test/test_revision_cleanup.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/common/datastore/work/test/test_revision_cleanup.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -23,12 +23,14 @@
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.trial.unittest import TestCase
</span><span class="cx"> from twistedcaldav.config import config
</span><ins>+from twistedcaldav.ical import Component
</ins><span class="cx"> from twistedcaldav.vcard import Component as VCard
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema, _BIND_MODE_READ
</span><span class="cx"> from txdav.common.datastore.test.util import CommonCommonTests, populateCalendarsFrom
</span><span class="cx"> from txdav.common.datastore.work.revision_cleanup import FindMinValidRevisionWork, RevisionCleanupWork
</span><span class="cx"> from txdav.common.icommondatastore import SyncTokenValidException
</span><span class="cx"> import datetime
</span><ins>+import time
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -88,6 +90,21 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+    cal1_mod = &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.1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;
+
</ins><span class="cx">     cal2 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="cx"> VERSION:2.0
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="lines">@@ -242,6 +259,10 @@
</span><span class="cx">         Verify that all extra calendar object revisions are deleted by FindMinValidRevisionWork and RevisionCleanupWork
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        # get home sync token
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        hometoken = yield home.syncToken()
+
</ins><span class="cx">         # get sync token
</span><span class="cx">         calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="cx">         token = yield calendar.syncToken()
</span><span class="lines">@@ -267,7 +288,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Get the minimum valid revision and check it
</span><span class="cx">         minValidRevision = yield self.transactionUnderTest().calendarserverValue(&quot;MIN-VALID-REVISION&quot;)
</span><del>-        self.assertEqual(int(minValidRevision), max([row[0] for row in revisionRows]))
</del><ins>+        self.assertEqual(int(minValidRevision), max([row[0] for row in revisionRows]) + 1)
</ins><span class="cx"> 
</span><span class="cx">         # do RevisionCleanupWork
</span><span class="cx">         yield self.transactionUnderTest().enqueue(RevisionCleanupWork, notBefore=datetime.datetime.utcnow())
</span><span class="lines">@@ -280,14 +301,111 @@
</span><span class="cx">             [rev.REVISION],
</span><span class="cx">             From=rev,
</span><span class="cx">         ).on(self.transactionUnderTest())
</span><del>-        self.assertEqual(len(revisionRows), 1)  # deleteRevisionsBefore() leaves 1 revision behind
</del><ins>+        self.assertEqual(len(revisionRows), 0)
</ins><span class="cx"> 
</span><span class="cx">         # old sync token fails
</span><span class="cx">         calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
</span><span class="cx">         yield self.failUnlessFailure(calendar.resourceNamesSinceToken(token), SyncTokenValidException)
</span><ins>+        yield self.commit()
</ins><span class="cx"> 
</span><ins>+        # old sync token fails
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        yield self.failUnlessFailure(home.resourceNamesSinceToken(hometoken, 1), SyncTokenValidException)
+        yield self.commit()
</ins><span class="cx"> 
</span><ins>+        # calendar sync token changed
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        newtoken = yield calendar.syncToken()
+        self.assertGreater(newtoken, token)
+        yield self.commit()
+
+        # home sync token changed
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        newhometoken = yield home.syncToken()
+        self.assertGreater(newhometoken, hometoken)
+        yield self.commit()
+
+        # Depth:1 tokens match
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        yield home.loadChildren()
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        newtoken1 = yield calendar.syncToken()
+        self.assertEqual(newtoken1, newtoken)
+        yield self.commit()
+
+
</ins><span class="cx">     @inlineCallbacks
</span><ins>+    def test_calendarObjectRevisions_Modified(self):
+        &quot;&quot;&quot;
+        Verify that a calendar object created before the revision cut-off, but modified after it is correctly reported as changed
+        after revision clean-up
+        &quot;&quot;&quot;
+
+        # Need to add one non-event change that creates a revision after the last event change revisions in order
+        # for the logic in this test to work correctly
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        yield home.createCalendarWithName(&quot;_ignore_me&quot;)
+        yield self.commit()
+
+        # get initial sync token
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        initial_token = yield calendar.syncToken()
+        yield self.commit()
+
+        # Pause to give some space in the modified time
+        time.sleep(1)
+        modified = datetime.datetime.utcnow()
+        time.sleep(1)
+
+        # Patch the work item to use the modified cut-off we need
+        def _dateCutoff(self):
+            return modified
+        self.patch(FindMinValidRevisionWork, &quot;dateCutoff&quot;, _dateCutoff)
+
+        # Make a change to get a pre-update token
+        cal2Object = yield self.calendarObjectUnderTest(self.transactionUnderTest(), name=&quot;cal2.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        yield cal2Object.remove()
+        yield self.commit()
+
+        # get changed sync token
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        pre_update_token = yield calendar.syncToken()
+        yield self.commit()
+
+        # make changes
+        cal1Object = yield self.calendarObjectUnderTest(self.transactionUnderTest(), name=&quot;cal1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        yield cal1Object.setComponent(Component.fromString(self.cal1_mod))
+        yield self.commit()
+
+        # get changed sync token
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        update_token = yield calendar.syncToken()
+        yield self.commit()
+
+        # do FindMinValidRevisionWork and RevisionCleanupWork
+        yield FindMinValidRevisionWork.reschedule(self.transactionUnderTest(), 0)
+        yield self.commit()
+        yield JobItem.waitEmpty(self.storeUnderTest().newTransaction, reactor, 60)
+
+        # initial sync token fails
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield self.failUnlessFailure(calendar.resourceNamesSinceToken(initial_token), SyncTokenValidException)
+        yield self.commit()
+
+        # Pre-update sync token returns one item
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        names = yield calendar.resourceNamesSinceToken(pre_update_token)
+        self.assertEqual(names, (['cal1.ics'], [], []))
+        yield self.commit()
+
+        # Post-update sync token returns one item
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        names = yield calendar.resourceNamesSinceToken(update_token)
+        self.assertEqual(names, ([], [], []))
+        yield self.commit()
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_notificationObjectRevisions(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Verify that all extra notification object revisions are deleted by FindMinValidRevisionWork and RevisionCleanupWork
</span><span class="lines">@@ -315,7 +433,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Get the minimum valid revision and check it
</span><span class="cx">         minValidRevision = yield self.transactionUnderTest().calendarserverValue(&quot;MIN-VALID-REVISION&quot;)
</span><del>-        self.assertEqual(int(minValidRevision), max([row[0] for row in revisionRows]))
</del><ins>+        self.assertEqual(int(minValidRevision), max([row[0] for row in revisionRows]) + 1)
</ins><span class="cx"> 
</span><span class="cx">         # do RevisionCleanupWork
</span><span class="cx">         yield self.transactionUnderTest().enqueue(RevisionCleanupWork, notBefore=datetime.datetime.utcnow())
</span><span class="lines">@@ -328,7 +446,7 @@
</span><span class="cx">             [rev.REVISION],
</span><span class="cx">             From=rev,
</span><span class="cx">         ).on(self.transactionUnderTest())
</span><del>-        self.assertEqual(len(revisionRows), 1)  # deleteRevisionsBefore() leaves 1 revision behind
</del><ins>+        self.assertEqual(len(revisionRows), 0)
</ins><span class="cx"> 
</span><span class="cx">         # old sync token fails
</span><span class="cx">         home = yield self.homeUnderTest(name=&quot;user01&quot;)
</span><span class="lines">@@ -367,7 +485,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Get the minimum valid revision and check it
</span><span class="cx">         minValidRevision = yield self.transactionUnderTest().calendarserverValue(&quot;MIN-VALID-REVISION&quot;)
</span><del>-        self.assertEqual(int(minValidRevision), max([row[0] for row in revisionRows]))
</del><ins>+        self.assertEqual(int(minValidRevision), max([row[0] for row in revisionRows]) + 1)
</ins><span class="cx"> 
</span><span class="cx">         # do RevisionCleanupWork
</span><span class="cx">         yield self.transactionUnderTest().enqueue(RevisionCleanupWork, notBefore=datetime.datetime.utcnow())
</span><span class="lines">@@ -380,7 +498,7 @@
</span><span class="cx">             [rev.REVISION],
</span><span class="cx">             From=rev,
</span><span class="cx">         ).on(self.transactionUnderTest())
</span><del>-        self.assertEqual(len(revisionRows), 1)  # deleteRevisionsBefore() leaves 1 revision behind
</del><ins>+        self.assertEqual(len(revisionRows), 0)
</ins><span class="cx"> 
</span><span class="cx">         # old sync token fails
</span><span class="cx">         addressbook = yield self.addressbookUnderTest(home=&quot;user01&quot;, name=&quot;addressbook&quot;)
</span><span class="lines">@@ -440,7 +558,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Get the minimum valid revision and check it
</span><span class="cx">         minValidRevision = yield self.transactionUnderTest().calendarserverValue(&quot;MIN-VALID-REVISION&quot;)
</span><del>-        self.assertEqual(int(minValidRevision), max([row[3] for row in group1Rows + group2Rows]))
</del><ins>+        self.assertEqual(int(minValidRevision), max([row[3] for row in group1Rows + group2Rows]) + 1)
</ins><span class="cx"> 
</span><span class="cx">         # do RevisionCleanupWork
</span><span class="cx">         yield self.transactionUnderTest().enqueue(RevisionCleanupWork, notBefore=datetime.datetime.utcnow())
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavdpsserverpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/dps/server.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/dps/server.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/dps/server.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -808,9 +808,12 @@
</span><span class="cx">         else:
</span><span class="cx">             setproctitle(&quot;CalendarServer Directory Proxy Service&quot;)
</span><span class="cx"> 
</span><ins>+        multiService = MultiService()
+
</ins><span class="cx">         try:
</span><del>-            _ignore_pool, txnFactory = getDBPool(config)
</del><ins>+            pool, txnFactory = getDBPool(config)
</ins><span class="cx">             store = storeFromConfigWithDPSServer(config, txnFactory)
</span><ins>+            pool.setServiceParent(multiService)
</ins><span class="cx">         except Exception as e:
</span><span class="cx">             log.error(&quot;Failed to create directory service&quot;, error=e)
</span><span class="cx">             raise
</span><span class="lines">@@ -832,10 +835,9 @@
</span><span class="cx">             ),
</span><span class="cx">             DirectoryProxyAMPFactory(store.directoryService())
</span><span class="cx">         )
</span><ins>+        dpsService.setServiceParent(multiService)
</ins><span class="cx"> 
</span><span class="cx">         if config.Manhole.Enabled:
</span><del>-            multiService = MultiService()
-            dpsService.setServiceParent(multiService)
</del><span class="cx">             try:
</span><span class="cx">                 from twisted.conch.manhole_tap import (
</span><span class="cx">                     makeService as manholeMakeService
</span><span class="lines">@@ -864,6 +866,4 @@
</span><span class="cx">                     &quot;manhole_tap could not be imported&quot;
</span><span class="cx">                 )
</span><span class="cx"> 
</span><del>-            return multiService
-        else:
-            return dpsService
</del><ins>+        return multiService
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhodirectorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/directory.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/directory.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/directory.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -200,10 +200,20 @@
</span><span class="cx">         else:
</span><span class="cx">             recordTypes = None
</span><span class="cx"> 
</span><del>-        results = yield self.recordsFromExpression(
-            expression, recordTypes=recordTypes, limitResults=limitResults,
-            timeoutSeconds=timeoutSeconds
-        )
</del><ins>+        # If a filter has been set, pass self.recordsFromExpression to it for
+        # result processing
+        if getattr(self, &quot;_resultFilter&quot;, None):
+            results = yield self._resultFilter(
+                self.recordsFromExpression, tokens, expression,
+                recordTypes=recordTypes, limitResults=limitResults,
+                timeoutSeconds=timeoutSeconds
+            )
+        else:
+            results = yield self.recordsFromExpression(
+                expression, recordTypes=recordTypes, limitResults=limitResults,
+                timeoutSeconds=timeoutSeconds
+            )
+
</ins><span class="cx">         log.debug(
</span><span class="cx">             &quot;Tokens ({t}) matched {n} records&quot;,
</span><span class="cx">             t=tokens, n=len(results)
</span><span class="lines">@@ -256,6 +266,18 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def setFilter(self, filter):
+        &quot;&quot;&quot;
+        Assign a filter for post-processing recordsMatchingTokens and
+        recordsMatchingFields results.
+
+        @param filter: a callable taking the following args:
+            method, tokens, expression, recordTypes, limitResults, timeoutSeconds
+            ...and returning a deferred firing with the list of records
+        &quot;&quot;&quot;
+        self._resultFilter = filter
+
+
</ins><span class="cx">     _oldRecordTypeNames = {
</span><span class="cx">         &quot;address&quot;: &quot;addresses&quot;,
</span><span class="cx">         &quot;group&quot;: &quot;groups&quot;,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhogroupspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/groups.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/groups.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/groups.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -184,8 +184,10 @@
</span><span class="cx">             homeID = rows[0][0]
</span><span class="cx">             home = yield self.transaction.calendarHomeWithResourceID(homeID)
</span><span class="cx">             calendar = yield home.childWithID(self.calendarID)
</span><del>-            group = (yield self.transaction.groupByID(self.groupID))
-            yield calendar.reconcileGroupSharee(group.groupUID)
</del><ins>+            # Might be None if the calendar is in the trash or was removed before the work started
+            if calendar is not None:
+                group = (yield self.transaction.groupByID(self.groupID))
+                yield calendar.reconcileGroupSharee(group.groupUID)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhotestaccountsgroupAccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/accounts/groupAccounts.xml (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/accounts/groupAccounts.xml        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/accounts/groupAccounts.xml        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -136,4 +136,21 @@
</span><span class="cx">             &lt;member-uid&gt;group03&lt;/member-uid&gt;
</span><span class="cx">             &lt;member-uid&gt;user10&lt;/member-uid&gt;
</span><span class="cx">         &lt;/record&gt;
</span><ins>+        &lt;record type=&quot;group&quot;&gt;
+            &lt;short-name&gt;group05&lt;/short-name&gt;
+            &lt;uid&gt;group05&lt;/uid&gt;
+            &lt;guid&gt;20000000-0000-0000-0000-000000000005&lt;/guid&gt;
+            &lt;full-name&gt;Group 05&lt;/full-name&gt;
+            &lt;email&gt;group05@example.com&lt;/email&gt;
+            &lt;member-uid&gt;user01&lt;/member-uid&gt;
+            &lt;member-uid&gt;user02&lt;/member-uid&gt;
+        &lt;/record&gt;
+        &lt;record type=&quot;group&quot;&gt;
+            &lt;short-name&gt;group06&lt;/short-name&gt;
+            &lt;uid&gt;group06&lt;/uid&gt;
+            &lt;guid&gt;20000000-0000-0000-0000-000000000006&lt;/guid&gt;
+            &lt;full-name&gt;Group 06&lt;/full-name&gt;
+            &lt;email&gt;group06@example.com&lt;/email&gt;
+            &lt;member-uid&gt;user02&lt;/member-uid&gt;
+        &lt;/record&gt;
</ins><span class="cx"> &lt;/directory&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_directorypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_directory.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_directory.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_directory.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -23,6 +23,7 @@
</span><span class="cx"> from twext.who.directory import DirectoryRecord
</span><span class="cx"> from twext.who.idirectory import FieldName, RecordType
</span><span class="cx"> from txdav.who.directory import CalendarDirectoryRecordMixin, AutoScheduleMode
</span><ins>+from txdav.who.util import startswithFilter
</ins><span class="cx"> from uuid import UUID
</span><span class="cx"> from twext.who.expression import (
</span><span class="cx">     MatchType, MatchFlags, MatchExpression
</span><span class="lines">@@ -50,7 +51,7 @@
</span><span class="cx"> 
</span><span class="cx">         expanded = yield record.expandedMembers()
</span><span class="cx">         self.assertEquals(
</span><del>-            set([u&quot;Chris Lecroy&quot;, u&quot;Cyrus Daboo&quot;, u&quot;David Reid&quot;, u&quot;Wilfredo Sanchez&quot;]),
</del><ins>+            set([u&quot;Chris Lecroy&quot;, u&quot;Cyrus Daboo&quot;, u&quot;David Reid&quot;, u&quot;Wilfredo Sanchez-Vega&quot;]),
</ins><span class="cx">             set([r.displayName for r in expanded])
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -205,6 +206,53 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def test_recordsMatchingTokensNoFilter(self):
+        &quot;&quot;&quot;
+        Records with names containing the token are returned
+        &quot;&quot;&quot;
+
+        records = (yield self.directory.recordsMatchingTokens(
+            [u&quot;anche&quot;]
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
+    def test_recordsMatchingTokensStartswithFilter(self):
+        &quot;&quot;&quot;
+        Records with names starting with the token are returned, because of
+        the filter installed.  Note that hyphens and spaces are used to split
+        fullname into names.
+        &quot;&quot;&quot;
+        self.directory.setFilter(startswithFilter)
+
+        records = (yield self.directory.recordsMatchingTokens(
+            [u&quot;anche&quot;]
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; not in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; not in matchingShortNames)
+
+        records = (yield self.directory.recordsMatchingTokens(
+            [u&quot;vega&quot;, u&quot;wilf&quot;]
+        ))
+        matchingShortNames = set()
+        for r in records:
+            for shortName in r.shortNames:
+                matchingShortNames.add(shortName)
+        self.assertTrue(&quot;dre&quot; not in matchingShortNames)
+        self.assertTrue(&quot;wsanchez&quot; in matchingShortNames)
+
+
+    @inlineCallbacks
</ins><span class="cx">     def test_getAutoScheduleMode(self):
</span><span class="cx"> 
</span><span class="cx">         apollo = yield self.directory.recordWithUID(u&quot;apollo&quot;)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_group_attendeespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_group_attendees.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_group_attendees.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_group_attendees.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -27,13 +27,14 @@
</span><span class="cx"> from twistedcaldav.config import config
</span><span class="cx"> from twistedcaldav.ical import Component, normalize_iCalStr
</span><span class="cx"> from txdav.caldav.datastore.sql_directory import GroupAttendeeRecord
</span><del>-from txdav.caldav.datastore.test.util import populateCalendarsFrom, CommonCommonTests
</del><ins>+from txdav.caldav.datastore.test.util import populateCalendarsFrom, CommonCommonTests, \
+    DateTimeSubstitutionsMixin
</ins><span class="cx"> from txdav.who.directory import CalendarDirectoryRecordMixin
</span><span class="cx"> from txdav.who.groups import GroupCacher
</span><span class="cx"> import os
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-class GroupAttendeeTestBase(CommonCommonTests, unittest.TestCase):
</del><ins>+class GroupAttendeeTestBase(CommonCommonTests, DateTimeSubstitutionsMixin, unittest.TestCase):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     GroupAttendeeReconciliation tests
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -50,6 +51,8 @@
</span><span class="cx">         )
</span><span class="cx">         yield self.populate()
</span><span class="cx"> 
</span><ins>+        self.setupDateTimeValues()
+
</ins><span class="cx">         self.paths = {}
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -84,23 +87,26 @@
</span><span class="cx"> 
</span><span class="cx">     def _assertICalStrEqual(self, iCalStr1, iCalStr2):
</span><span class="cx"> 
</span><del>-        def orderMemberValues(event):
</del><ins>+        def orderAttendeePropAndMemberValues(event):
</ins><span class="cx"> 
</span><span class="cx">             for component in event.subcomponents(ignore=True):
</span><span class="cx">                 # remove all values and add them again
</span><span class="cx">                 # this is sort of a hack, better pycalendar has ordering
</span><del>-                for attendeeProp in tuple(component.properties(&quot;ATTENDEE&quot;)):
</del><ins>+                attendees = sorted(list(component.properties(&quot;ATTENDEE&quot;)), key=lambda x: x.value())
+                component.removeProperty(&quot;ATTENDEE&quot;)
+                for attendeeProp in attendees:
</ins><span class="cx">                     if attendeeProp.hasParameter(&quot;MEMBER&quot;):
</span><span class="cx">                         parameterValues = tuple(attendeeProp.parameterValues(&quot;MEMBER&quot;))
</span><span class="cx">                         for paramterValue in parameterValues:
</span><span class="cx">                             attendeeProp.removeParameterValue(&quot;MEMBER&quot;, paramterValue)
</span><span class="cx">                         attendeeProp.setParameter(&quot;MEMBER&quot;, sorted(parameterValues))
</span><ins>+                    component.addProperty(attendeeProp)
</ins><span class="cx"> 
</span><span class="cx">             return event
</span><span class="cx"> 
</span><del>-        self.assertEqual(
-            orderMemberValues(Component.fromString(normalize_iCalStr(iCalStr1))),
-            orderMemberValues(Component.fromString(normalize_iCalStr(iCalStr2)))
</del><ins>+        self.assertEqualCalendarData(
+            orderAttendeePropAndMemberValues(Component.fromString(normalize_iCalStr(iCalStr1))),
+            orderAttendeePropAndMemberValues(Component.fromString(normalize_iCalStr(iCalStr2)))
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -121,7 +127,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -137,7 +143,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group02
</span><span class="lines">@@ -154,13 +160,13 @@
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user06&quot;, 0)
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user07&quot;, 0)
</span><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_1)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user06&quot;, 1)
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user07&quot;, 1)
</span><span class="lines">@@ -180,7 +186,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate_fwd1}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -196,7 +202,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate_fwd1}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CUTYPE=X-SERVER-GROUP;SCHEDULE-STATUS=3.7:urn:uuid:FFFFFFFF-EEEE-DDDD-CCCC-BBBBBBBBBBBB
</span><span class="lines">@@ -207,13 +213,13 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -230,7 +236,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -247,7 +253,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user02
</span><span class="lines">@@ -258,13 +264,13 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -287,7 +293,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -303,7 +309,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 04;CUTYPE=X-SERVER-GROUP;SCHEDULE-STATUS=2.7:urn:x-uid:group04
</span><span class="lines">@@ -319,13 +325,13 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_1)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user06&quot;, 1)
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user07&quot;, 1)
</span><span class="lines">@@ -353,7 +359,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -371,7 +377,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -387,13 +393,13 @@
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR&quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_1)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user06&quot;, 1)
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user07&quot;, 1)
</span><span class="lines">@@ -404,7 +410,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_groupPutOldEvent(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Test that old event with group attendee is expaned but not linked to group update
</del><ins>+        Test that old event with group attendee is expanded but not linked to group update
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         data_put_1 = &quot;&quot;&quot;BEGIN:VCALENDAR
</span><span class="lines">@@ -414,7 +420,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate_back2}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -430,7 +436,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate_back2}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -444,7 +450,7 @@
</span><span class="cx">         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -459,7 +465,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -478,7 +484,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -494,7 +500,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -511,7 +517,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -530,7 +536,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -558,13 +564,13 @@
</span><span class="cx">         self.assertEqual(len(wps), 0)
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, 0)
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -582,7 +588,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_3))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_3.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, 1)
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -595,7 +601,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_4))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_4.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         cobjs = yield cal.objectResources()
</span><span class="lines">@@ -617,7 +623,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event {0}
</span><span class="cx"> UID:event{0}@ninevah.local
</span><span class="lines">@@ -633,7 +639,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event{0}@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 0{0};EMAIL=user0{0}@example.com;RSVP=TRUE:urn:x-uid:user0{0}
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -650,7 +656,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event{0}@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 0{0};EMAIL=user0{0}@example.com;RSVP=TRUE:urn:x-uid:user0{0}
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -669,7 +675,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event{0}@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 0{0};EMAIL=user0{0}@example.com;RSVP=TRUE:urn:x-uid:user0{0}
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -700,13 +706,13 @@
</span><span class="cx"> 
</span><span class="cx">         for i in userRange:
</span><span class="cx">             calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user0{0}&quot;.format(i))
</span><del>-            vcalendar = Component.fromString(data_put_1.format(i))
</del><ins>+            vcalendar = Component.fromString(data_put_1.format(i, **self.dtsubs))
</ins><span class="cx">             yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">             yield self.commit()
</span><span class="cx"> 
</span><span class="cx">             cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user0{0}&quot;.format(i))
</span><span class="cx">             vcalendar = yield cobj.component()
</span><del>-            self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2.format(i)))
</del><ins>+            self._assertICalStrEqual(vcalendar, data_get_2.format(i, **self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, 0)
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -725,7 +731,7 @@
</span><span class="cx">         for i in userRange:
</span><span class="cx">             cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user0{0}&quot;.format(i))
</span><span class="cx">             vcalendar = yield cobj.component()
</span><del>-            self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_3.format(i)))
</del><ins>+            self._assertICalStrEqual(vcalendar, data_get_3.format(i, **self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, len(userRange))
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -739,7 +745,7 @@
</span><span class="cx">         for i in userRange:
</span><span class="cx">             cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user0{0}&quot;.format(i))
</span><span class="cx">             vcalendar = yield cobj.component()
</span><del>-            self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_4.format(i)))
</del><ins>+            self._assertICalStrEqual(vcalendar, data_get_4.format(i, **self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         cobjs = yield cal.objectResources()
</span><span class="lines">@@ -762,7 +768,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -779,7 +785,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -795,7 +801,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -813,7 +819,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -834,7 +840,7 @@
</span><span class="cx">         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -848,11 +854,11 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, 1)
</span><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_2)
</del><ins>+        vcalendar = Component.fromString(data_put_2.format(**self.dtsubs))
</ins><span class="cx">         yield cobj.setComponent(vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -860,7 +866,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group01&quot;)
</span><span class="cx">         self.assertEqual(len(wps), 0)
</span><span class="lines">@@ -884,7 +890,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         '''
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="lines">@@ -916,9 +922,9 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><del>-RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="cx"> ORGANIZER:MAILTO:user02@example.com
</span><span class="lines">@@ -934,9 +940,9 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><del>-RRULE:FREQ=DAILY;UNTIL=20140101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_back1}T100000
</ins><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="cx"> ORGANIZER:MAILTO:user02@example.com
</span><span class="lines">@@ -951,14 +957,14 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;MEMBER=&quot;urn:x-uid:group01&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SUMMARY:event 1
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="lines">@@ -970,13 +976,13 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-RRULE:FREQ=DAILY;UNTIL=20140101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_back1}T100000
</ins><span class="cx"> SEQUENCE:1
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> END:VEVENT
</span><span class="lines">@@ -992,7 +998,7 @@
</span><span class="cx">         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -1006,11 +1012,11 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, 1)
</span><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_2)
</del><ins>+        vcalendar = Component.fromString(data_put_2.format(**self.dtsubs))
</ins><span class="cx">         yield cobj.setComponent(vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -1018,7 +1024,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group01&quot;)
</span><span class="cx">         if len(wps): # This is needed because the test currently fails and does actually create job items we have to wait for
</span><span class="lines">@@ -1043,7 +1049,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1059,9 +1065,9 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><del>-RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="cx"> ORGANIZER:MAILTO:user02@example.com
</span><span class="lines">@@ -1076,14 +1082,14 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;MEMBER=&quot;urn:x-uid:group01&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SUMMARY:event 1
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="lines">@@ -1095,14 +1101,14 @@
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com:urn:x-uid:group01
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;MEMBER=&quot;urn:x-uid:group01&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SUMMARY:event 1
</span><span class="cx"> TRANSP:TRANSPARENT
</span><span class="cx"> END:VEVENT
</span><span class="lines">@@ -1120,7 +1126,7 @@
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-{relatedTo}RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+{relatedTo}RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SEQUENCE:2
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> END:VEVENT
</span><span class="lines">@@ -1131,7 +1137,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><del>-{uid}DTSTART:20120101T100000Z
</del><ins>+{uid}DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -1156,7 +1162,7 @@
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;MEMBER=&quot;urn:x-uid:group01&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-{relatedTo}RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+{relatedTo}RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SEQUENCE:2
</span><span class="cx"> STATUS:CANCELLED
</span><span class="cx"> SUMMARY:event 1
</span><span class="lines">@@ -1169,7 +1175,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
</span><span class="cx"> BEGIN:VEVENT
</span><del>-{uid}DTSTART:20120101T100000Z
</del><ins>+{uid}DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com:urn:x-uid:group01
</span><span class="lines">@@ -1191,21 +1197,26 @@
</span><span class="cx">         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         cobjs = yield cal.objectResources()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><span class="cx">         vcalendar = yield cobjs[0].componentForUser()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1_user01))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1_user01.format(**self.dtsubs))
</ins><span class="cx">         user01_cname = cobjs[0].name()
</span><span class="cx"> 
</span><ins>+        cal = yield self.calendarUnderTest(name=&quot;inbox&quot;, home=&quot;user01&quot;)
+        cobjs = yield cal.objectResources()
+        self.assertEqual(len(cobjs), 1)
+        yield cobjs[0].remove()
+
</ins><span class="cx">         self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, expandedMembers)
</span><span class="cx"> 
</span><span class="cx">         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group01&quot;)
</span><span class="lines">@@ -1225,22 +1236,13 @@
</span><span class="cx">                     &quot;uid&quot;: component.getProperty(&quot;UID&quot;),
</span><span class="cx">                 }
</span><span class="cx">                 break
</span><ins>+            props.update(self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">             if cobj.name() == &quot;data1.ics&quot;:
</span><del>-                self.assertEqual(
-                    normalize_iCalStr(vcalendar),
-                    normalize_iCalStr(
-                        data_get_2.format(**props)
-                    )
-                )
</del><ins>+                self._assertICalStrEqual(vcalendar, data_get_2.format(**props))
</ins><span class="cx">                 props_orig = props
</span><span class="cx">             else:
</span><del>-                self.assertEqual(
-                    normalize_iCalStr(vcalendar),
-                    normalize_iCalStr(
-                        data_get_3.format(**props)
-                    )
-                )
</del><ins>+                self._assertICalStrEqual(vcalendar, data_get_3.format(**props))
</ins><span class="cx">                 props_new = props
</span><span class="cx"> 
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="lines">@@ -1248,21 +1250,17 @@
</span><span class="cx">         for cobj in cobjs:
</span><span class="cx">             vcalendar = yield cobj.componentForUser()
</span><span class="cx">             if cobj.name() == user01_cname:
</span><del>-                self.assertEqual(
-                    normalize_iCalStr(vcalendar),
-                    normalize_iCalStr(
-                        data_get_2_user01.format(**props_orig)
-                    )
-                )
</del><ins>+                self._assertICalStrEqual(vcalendar, data_get_2_user01.format(**props_orig))
</ins><span class="cx">             else:
</span><del>-                self.assertEqual(
-                    normalize_iCalStr(vcalendar),
-                    normalize_iCalStr(
-                        data_get_3_user01.format(**props_new)
-                    )
-                )
</del><ins>+                self._assertICalStrEqual(vcalendar, data_get_3_user01.format(**props_new))
</ins><span class="cx"> 
</span><ins>+        cal = yield self.calendarUnderTest(name=&quot;inbox&quot;, home=&quot;user01&quot;)
+        cobjs = yield cal.objectResources()
+        self.assertEqual(len(cobjs), 1)
+        comp = yield cobjs[0].componentForUser()
+        self.assertTrue(&quot;METHOD:CANCEL&quot; in str(comp))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_groupChangeLargerSpanningEvent(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -1276,9 +1274,9 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><del>-RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="cx"> ORGANIZER:MAILTO:user02@example.com
</span><span class="lines">@@ -1293,13 +1291,13 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20120101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SUMMARY:event 1
</span><span class="cx"> END:VEVENT
</span><span class="cx"> END:VCALENDAR
</span><span class="lines">@@ -1317,7 +1315,7 @@
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;MEMBER=&quot;urn:x-uid:group01&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-{relatedTo}RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+{relatedTo}RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SEQUENCE:2
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> END:VEVENT
</span><span class="lines">@@ -1329,7 +1327,7 @@
</span><span class="cx"> CALSCALE:GREGORIAN
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><del>-{uid}DTSTART:20120101T100000Z
</del><ins>+{uid}DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -1353,7 +1351,7 @@
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;MEMBER=&quot;urn:x-uid:group01&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 02;EMAIL=user02@example.com:urn:x-uid:user02
</span><del>-{relatedTo}RRULE:FREQ=DAILY;UNTIL=20240101T100000
</del><ins>+{relatedTo}RRULE:FREQ=DAILY;UNTIL={nowDate_fwd20}T100000
</ins><span class="cx"> SEQUENCE:2
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> TRANSP:TRANSPARENT
</span><span class="lines">@@ -1373,13 +1371,13 @@
</span><span class="cx">         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_1))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, 0)
</span><span class="cx"> 
</span><span class="lines">@@ -1402,31 +1400,19 @@
</span><span class="cx">                     &quot;uid&quot;: component.getProperty(&quot;UID&quot;),
</span><span class="cx">                 }
</span><span class="cx">                 break
</span><ins>+            props.update(self.dtsubs)
</ins><span class="cx"> 
</span><span class="cx">             if cobj.name() == &quot;data1.ics&quot;:
</span><del>-                self.assertEqual(
-                    normalize_iCalStr(vcalendar),
-                    normalize_iCalStr(
-                        data_get_2.format(**props)
-                    )
-                )
</del><ins>+                self._assertICalStrEqual(vcalendar, data_get_2.format(**props))
</ins><span class="cx">                 props_orig = props
</span><span class="cx">             else:
</span><del>-                self.assertEqual(
-                    normalize_iCalStr(vcalendar),
-                    normalize_iCalStr(
-                        data_get_3.format(**props)
-                    )
-                )
</del><ins>+                self._assertICalStrEqual(vcalendar, data_get_3.format(**props))
</ins><span class="cx"> 
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         cobjs = yield cal.objectResources()
</span><span class="cx">         self.assertEqual(len(cobjs), 1)
</span><span class="cx">         vcalendar = yield cobjs[0].componentForUser()
</span><del>-        self.assertEqual(
-            normalize_iCalStr(vcalendar),
-            normalize_iCalStr(data_get_2_user01.format(**props_orig))
-        )
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2_user01.format(**props_orig))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1452,7 +1438,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -1470,7 +1456,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -1492,7 +1478,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -1515,16 +1501,16 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="cx"> ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group02
</span><span class="cx"> ATTENDEE;CN=Group 03;CUTYPE=X-SERVER-GROUP;EMAIL=group03@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group03
</span><del>-ATTENDEE;CN=User 06;EMAIL=user06@example.com;MEMBER=&quot;urn:x-uid:group02&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user06
</del><span class="cx"> ATTENDEE;CN=User 07;EMAIL=user07@example.com;MEMBER=&quot;urn:x-uid:group02&quot;,&quot;urn:x-uid:group03&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user07
</span><span class="cx"> ATTENDEE;CN=User 08;EMAIL=user08@example.com;MEMBER=&quot;urn:x-uid:group02&quot;,&quot;urn:x-uid:group03&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user08
</span><span class="cx"> ATTENDEE;CN=User 09;EMAIL=user09@example.com;MEMBER=&quot;urn:x-uid:group03&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user09
</span><ins>+ATTENDEE;CN=User 06;EMAIL=user06@example.com;MEMBER=&quot;urn:x-uid:group02&quot;;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:user06
</ins><span class="cx"> CREATED:20060101T150000Z
</span><span class="cx"> ORGANIZER;CN=User 01;EMAIL=user01@example.com:urn:x-uid:user01
</span><span class="cx"> SEQUENCE:2
</span><span class="lines">@@ -1543,13 +1529,13 @@
</span><span class="cx">                 result = yield unpatchedRecordWithUID(self, uid)
</span><span class="cx">             returnValue(result)
</span><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_1)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user06&quot;, 1)
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user07&quot;, 1)
</span><span class="lines">@@ -1567,7 +1553,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_1)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         # remove group members run cacher again
</span><span class="cx">         self.patch(DirectoryService, &quot;recordWithUID&quot;, recordWithUID)
</span><span class="lines">@@ -1579,7 +1565,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_2)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user06&quot;)
</span><span class="cx">         cobjs = yield cal.objectResources()
</span><span class="lines">@@ -1598,7 +1584,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_3)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_3.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         cal = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user06&quot;)
</span><span class="cx">         cobjs = yield cal.objectResources()
</span><span class="lines">@@ -1631,7 +1617,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -1648,7 +1634,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group02
</span><span class="lines">@@ -1670,7 +1656,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -1686,7 +1672,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 01;EMAIL=user01@example.com;RSVP=TRUE:urn:x-uid:user01
</span><span class="cx"> ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group02
</span><span class="lines">@@ -1701,13 +1687,13 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_1)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user06&quot;, 1)
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user07&quot;, 1)
</span><span class="lines">@@ -1728,15 +1714,15 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_1)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_1.format(**self.dtsubs))
</ins><span class="cx"> 
</span><del>-        vcalendar = Component.fromString(data_put_2)
</del><ins>+        vcalendar = Component.fromString(data_put_2.format(**self.dtsubs))
</ins><span class="cx">         yield cobj.setComponent(vcalendar)
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self._assertICalStrEqual(vcalendar, data_get_2)
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user06&quot;, 1)
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user07&quot;, 1)
</span><span class="lines">@@ -1774,7 +1760,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="cx"> UID:event1@ninevah.local
</span><span class="lines">@@ -1790,7 +1776,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20240101T100000Z
</del><ins>+DTSTART:{nowDate_fwd20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -1805,7 +1791,7 @@
</span><span class="cx">         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx"> 
</span><span class="cx">         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
</span><span class="lines">@@ -1822,7 +1808,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, 1)
</span><span class="cx">         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
</span><span class="lines">@@ -1876,7 +1862,7 @@
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> DTSTAMP:20051222T205953Z
</span><span class="cx"> CREATED:20060101T150000Z
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> RRULE:FREQ=DAILY
</span><span class="cx"> DURATION:PT1H
</span><span class="cx"> SUMMARY:event 1
</span><span class="lines">@@ -1893,7 +1879,7 @@
</span><span class="cx"> PRODID:-//Example Inc.//Example Calendar//EN
</span><span class="cx"> BEGIN:VEVENT
</span><span class="cx"> UID:event1@ninevah.local
</span><del>-DTSTART:20140101T100000Z
</del><ins>+DTSTART:{nowDate_back20}T100000Z
</ins><span class="cx"> DURATION:PT1H
</span><span class="cx"> ATTENDEE;CN=User 02;EMAIL=user02@example.com;RSVP=TRUE:urn:x-uid:user02
</span><span class="cx"> ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01@example.com;SCHEDULE-STATUS=2.7:urn:x-uid:group01
</span><span class="lines">@@ -1909,7 +1895,7 @@
</span><span class="cx">         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest(name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><del>-        vcalendar = Component.fromString(data_put_1)
</del><ins>+        vcalendar = Component.fromString(data_put_1.format(**self.dtsubs))
</ins><span class="cx">         yield calendar.createCalendarObjectWithName(&quot;data1.ics&quot;, vcalendar)
</span><span class="cx"> 
</span><span class="cx">         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
</span><span class="lines">@@ -1926,7 +1912,7 @@
</span><span class="cx"> 
</span><span class="cx">         cobj = yield self.calendarObjectUnderTest(name=&quot;data1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
</span><span class="cx">         vcalendar = yield cobj.component()
</span><del>-        self.assertEqual(normalize_iCalStr(vcalendar), normalize_iCalStr(data_get_2))
</del><ins>+        self._assertICalStrEqual(vcalendar, data_get_2.format(**self.dtsubs))
</ins><span class="cx"> 
</span><span class="cx">         yield self._verifyObjectResourceCount(&quot;user01&quot;, 1)
</span><span class="cx">         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_group_shareespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_group_sharees.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_group_sharees.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_group_sharees.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -28,7 +28,8 @@
</span><span class="cx"> from txdav.who.directory import CalendarDirectoryRecordMixin
</span><span class="cx"> from txdav.who.groups import GroupCacher, GroupShareeReconciliationWork
</span><span class="cx"> import os
</span><del>-from txdav.common.datastore.sql_tables import _BIND_MODE_GROUP
</del><ins>+from txdav.common.datastore.sql_tables import _BIND_MODE_GROUP, _BIND_MODE_WRITE, \
+    _BIND_MODE_GROUP_READ
</ins><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_READ
</span><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_STATUS_INVITED
</span><span class="cx"> 
</span><span class="lines">@@ -68,6 +69,7 @@
</span><span class="cx">     requirements = {
</span><span class="cx">         &quot;user01&quot; : None,
</span><span class="cx">         &quot;user02&quot; : None,
</span><ins>+        &quot;user03&quot; : None,
</ins><span class="cx">         &quot;user06&quot; : None,
</span><span class="cx">         &quot;user07&quot; : None,
</span><span class="cx">         &quot;user08&quot; : None,
</span><span class="lines">@@ -348,3 +350,499 @@
</span><span class="cx">         yield calendar.uninviteUIDFromShare(&quot;group02&quot;)
</span><span class="cx">         noinvites = yield calendar.sharingInvites()
</span><span class="cx">         self.assertEqual(len(noinvites), 3)
</span><ins>+
+
+    @inlineCallbacks
+    def test_no_self_invite(self):
+        &quot;&quot;&quot;
+        Test that group shares where the group includes the sharee work. Then remove
+        the sharee from the group and make sure it works.
+        &quot;&quot;&quot;
+
+        record02 = yield self.transactionUnderTest().directoryService().recordWithUID(&quot;user02&quot;)
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None, seen=None):
+
+            if self.uid == &quot;group05&quot;:
+                returnValue(frozenset((record02,)))
+            else:
+                returnValue((yield unpatchedExpandedMembers(self, records, seen)))
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+
+        # setup group cacher
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group05&quot;)
+        self.assertEqual(len(wps), 0)
+
+        yield self._check_notifications(&quot;user01&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())
+
+        yield self._check_notifications(&quot;user01&quot;, [])
+        shareeViews = yield calendar.inviteUIDToShare(&quot;group05&quot;, _BIND_MODE_READ)
+        self.assertEqual(len(shareeViews), 1)
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user01&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        # 1 group members
+        self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, expandedMembers)
+
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group05&quot;)
+        self.assertEqual(len(wps), 1)
+        yield self.commit()
+        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user01&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        yield self._check_notifications(&quot;user01&quot;, [])
+
+        # Uninvite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield calendar.uninviteUIDFromShare(&quot;group05&quot;)
+        noinvites = yield calendar.sharingInvites()
+        self.assertEqual(len(noinvites), 0)
+
+
+    @inlineCallbacks
+    def test_no_self_invite_on_add(self):
+        &quot;&quot;&quot;
+        Test that the sharee is not invited to their own share when they are added as a member
+        of a group to whom the calendar is shared.
+        &quot;&quot;&quot;
+
+        record01 = yield self.transactionUnderTest().directoryService().recordWithUID(&quot;user01&quot;)
+        record02 = yield self.transactionUnderTest().directoryService().recordWithUID(&quot;user02&quot;)
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None, seen=None):
+
+            if self.uid == &quot;group06&quot;:
+                returnValue(frozenset((record01, record02,)))
+            else:
+                returnValue((yield unpatchedExpandedMembers(self, records, seen)))
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+
+        # setup group cacher
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group06&quot;)
+        self.assertEqual(len(wps), 0)
+
+        yield self._check_notifications(&quot;user01&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())
+
+        yield self._check_notifications(&quot;user01&quot;, [])
+        shareeViews = yield calendar.inviteUIDToShare(&quot;group06&quot;, _BIND_MODE_READ)
+        self.assertEqual(len(shareeViews), 1)
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user01&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        # 1 group members
+        self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, expandedMembers)
+
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group06&quot;)
+        self.assertEqual(len(wps), 1)
+        yield self.commit()
+        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user01&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        yield self._check_notifications(&quot;user01&quot;, [])
+
+        # Uninvite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;calendar&quot;)
+        yield calendar.uninviteUIDFromShare(&quot;group06&quot;)
+        noinvites = yield calendar.sharingInvites()
+        self.assertEqual(len(noinvites), 0)
+
+
+    @inlineCallbacks
+    def test_group_change_trashed_calendar(self):
+        &quot;&quot;&quot;
+        Test that group shares are properly cleaned when a calendar is trashed.
+        &quot;&quot;&quot;
+
+        self.patch(config, &quot;EnableTrashCollection&quot;, True)
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None, seen=None):
+
+            if self.uid == &quot;group02&quot;:
+                returnValue(frozenset())
+            else:
+                returnValue((yield unpatchedExpandedMembers(self, records, seen)))
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+
+        # setup group cacher
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group02&quot;)
+        self.assertEqual(len(wps), 0)
+
+        yield self._check_notifications(&quot;user01&quot;, [])
+
+        # New calendar for sharing
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        yield home.createCalendarWithName(&quot;shared&quot;)
+        yield self.commit()
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;shared&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        yield self._check_notifications(&quot;user01&quot;, [])
+        shareeViews = yield calendar.inviteUIDToShare(&quot;group02&quot;, _BIND_MODE_READ)
+        self.assertEqual(len(shareeViews), 3)
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;shared&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 3)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user01&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 1)
+        yield self.commit()
+
+        # Trash the collection
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;shared&quot;)
+        yield calendar.remove()
+        self.assertTrue(calendar.isInTrash())
+        trash_id = calendar.id()
+        yield self.commit()
+
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        calendar = yield home.childWithID(trash_id, onlyInTrash=True)
+        self.assertTrue(calendar.isInTrash())
+        invitations = yield calendar.allInvitations()
+        self.assertEqual(len(invitations), 0)
+        yield self.commit()
+
+        # 1 group member
+        self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, expandedMembers)
+
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group02&quot;)
+        self.assertEqual(len(wps), 0)
+        yield self.commit()
+        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+
+    @inlineCallbacks
+    def test_group_change_removed_calendar(self):
+        &quot;&quot;&quot;
+        Test that group shares are properly cleaned when a calendar is removed (and not trashed).
+        &quot;&quot;&quot;
+
+        self.patch(config, &quot;EnableTrashCollection&quot;, False)
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None, seen=None):
+
+            if self.uid == &quot;group02&quot;:
+                returnValue(frozenset())
+            else:
+                returnValue((yield unpatchedExpandedMembers(self, records, seen)))
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+
+        # setup group cacher
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group02&quot;)
+        self.assertEqual(len(wps), 0)
+
+        yield self._check_notifications(&quot;user01&quot;, [])
+
+        # New calendar for sharing
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        yield home.createCalendarWithName(&quot;shared&quot;)
+        yield self.commit()
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;shared&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        yield self._check_notifications(&quot;user01&quot;, [])
+        shareeViews = yield calendar.inviteUIDToShare(&quot;group02&quot;, _BIND_MODE_READ)
+        self.assertEqual(len(shareeViews), 3)
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;shared&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 3)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user01&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 1)
+        yield self.commit()
+
+        # Remove the collection
+        calendar = yield self.calendarUnderTest(home=&quot;user01&quot;, name=&quot;shared&quot;)
+        remove_id = calendar.id()
+        yield calendar.remove()
+        yield self.commit()
+
+        home = yield self.homeUnderTest(name=&quot;user01&quot;)
+        calendar = yield home.childWithID(remove_id)
+        self.assertTrue(calendar is None)
+        calendar = yield home.childWithID(remove_id, onlyInTrash=True)
+        self.assertTrue(calendar is None)
+        yield self.commit()
+
+        # 1 group member
+        self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, expandedMembers)
+
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group02&quot;)
+        self.assertEqual(len(wps), 0)
+        yield self.commit()
+        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+
+    @inlineCallbacks
+    def test_multiple_groups_remove_from_one_group(self):
+        &quot;&quot;&quot;
+        Test that a multi-group share each containing the same user still lists the user
+        when they are removed from one group.
+        &quot;&quot;&quot;
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None, seen=None):
+
+            if self.uid == &quot;group05&quot;:
+                returnValue(frozenset())
+            else:
+                returnValue((yield unpatchedExpandedMembers(self, records, seen)))
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+
+        # setup group cacher
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group05&quot;)
+        self.assertEqual(len(wps), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group06&quot;)
+        self.assertEqual(len(wps), 0)
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user03&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeViews = yield calendar.inviteUIDToShare(&quot;group05&quot;, _BIND_MODE_WRITE)
+        self.assertEqual(len(shareeViews), 2)
+        shareeViews = yield calendar.inviteUIDToShare(&quot;group06&quot;, _BIND_MODE_READ)
+        self.assertEqual(len(shareeViews), 1)
+
+        calendar = yield self.calendarUnderTest(home=&quot;user03&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 2)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user03&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_WRITE)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 2)
+
+        # Change group membership
+        self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, expandedMembers)
+
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group05&quot;)
+        self.assertEqual(len(wps), 1)
+        yield self.commit()
+        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+        calendar = yield self.calendarUnderTest(home=&quot;user03&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 1)
+        for invite in invites:
+            self.assertEqual(invite.shareeUID, &quot;user02&quot;)
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user03&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        # Uninvite
+        calendar = yield self.calendarUnderTest(home=&quot;user03&quot;, name=&quot;calendar&quot;)
+        yield calendar.uninviteUIDFromShare(&quot;group05&quot;)
+        yield calendar.uninviteUIDFromShare(&quot;group06&quot;)
+        noinvites = yield calendar.sharingInvites()
+        self.assertEqual(len(noinvites), 0)
+
+
+    @inlineCallbacks
+    def test_multiple_groups_with_individual_remove_from_one_group(self):
+        &quot;&quot;&quot;
+        Test that a multi-group share each containing the same user still lists the user
+        when they are removed from one group.
+        &quot;&quot;&quot;
+
+        @inlineCallbacks
+        def expandedMembers(self, records=None, seen=None):
+
+            if self.uid == &quot;group05&quot;:
+                returnValue(frozenset())
+            else:
+                returnValue((yield unpatchedExpandedMembers(self, records, seen)))
+
+        unpatchedExpandedMembers = CalendarDirectoryRecordMixin.expandedMembers
+
+        # setup group cacher
+        groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group05&quot;)
+        self.assertEqual(len(wps), 0)
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group06&quot;)
+        self.assertEqual(len(wps), 0)
+
+        # Invite
+        calendar = yield self.calendarUnderTest(home=&quot;user03&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 0)
+        self.assertFalse(calendar.isShared())
+
+        shareeView = yield calendar.inviteUIDToShare(&quot;user01&quot;, _BIND_MODE_READ)
+        self.assertTrue(shareeView is not None)
+        shareeViews = yield calendar.inviteUIDToShare(&quot;group05&quot;, _BIND_MODE_WRITE)
+        self.assertEqual(len(shareeViews), 2)
+        shareeViews = yield calendar.inviteUIDToShare(&quot;group06&quot;, _BIND_MODE_READ)
+        self.assertEqual(len(shareeViews), 1)
+
+        calendar = yield self.calendarUnderTest(home=&quot;user03&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 2)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user03&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            if invite.shareeUID == &quot;user01&quot;:
+                self.assertEqual(invite.mode, _BIND_MODE_GROUP_READ)
+            else:
+                self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_WRITE)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
+        self.assertEqual(len(groupsToRefresh), 2)
+
+        # Change group membership
+        self.patch(CalendarDirectoryRecordMixin, &quot;expandedMembers&quot;, expandedMembers)
+
+        wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), &quot;group05&quot;)
+        self.assertEqual(len(wps), 1)
+        yield self.commit()
+        yield JobItem.waitEmpty(self._sqlCalendarStore.newTransaction, reactor, 60)
+
+        calendar = yield self.calendarUnderTest(home=&quot;user03&quot;, name=&quot;calendar&quot;)
+        invites = yield calendar.sharingInvites()
+        self.assertEqual(len(invites), 2)
+        for invite in invites:
+            shareeView = yield calendar.shareeView(invite.shareeUID)
+            self.assertEqual(invite.ownerUID, &quot;user03&quot;)
+            self.assertEqual(invite.uid, shareeView.shareName())
+            if invite.shareeUID == &quot;user01&quot;:
+                self.assertEqual(invite.mode, _BIND_MODE_READ)
+            else:
+                self.assertEqual(invite.mode, _BIND_MODE_GROUP)
+            self.assertEqual((yield shareeView.effectiveShareMode()), _BIND_MODE_READ)
+            self.assertEqual(invite.status, _BIND_STATUS_INVITED)
+            self.assertEqual(invite.summary, None)
+            yield self._check_notifications(invite.shareeUID, [invite.uid, ])
+
+        # Uninvite
+        calendar = yield self.calendarUnderTest(home=&quot;user03&quot;, name=&quot;calendar&quot;)
+        yield calendar.uninviteUIDFromShare(&quot;user01&quot;)
+        yield calendar.uninviteUIDFromShare(&quot;group05&quot;)
+        yield calendar.uninviteUIDFromShare(&quot;group06&quot;)
+        noinvites = yield calendar.sharingInvites()
+        self.assertEqual(len(noinvites), 0)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_utilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_util.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_util.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_util.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -114,6 +114,7 @@
</span><span class="cx">                     &quot;InProcessCachingSeconds&quot;: 60,
</span><span class="cx">                     &quot;InSidecarCachingSeconds&quot;: 120,
</span><span class="cx">                 },
</span><ins>+                &quot;DirectoryFilterStartsWith&quot;: False,
</ins><span class="cx">             }
</span><span class="cx">         )
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhotesttest_wikipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_wiki.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_wiki.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/test/test_wiki.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -23,7 +23,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> from twisted.trial import unittest
</span><del>-from twisted.internet.defer import inlineCallbacks, succeed
</del><ins>+from twisted.internet.defer import inlineCallbacks, succeed, TimeoutError
</ins><span class="cx"> from twistedcaldav.test.util import StoreTestCase
</span><span class="cx"> 
</span><span class="cx"> from ..wiki import (
</span><span class="lines">@@ -94,6 +94,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def stubAccessForUserToWiki(self, *args, **kwds):
</span><ins>+        if hasattr(self, &quot;raiseTimeout&quot;):
+            raise TimeoutError
</ins><span class="cx">         return succeed(self.access)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -117,8 +119,14 @@
</span><span class="cx">         access = yield record.accessForRecord(None)
</span><span class="cx">         self.assertEquals(access, WikiAccessLevel.write)
</span><span class="cx"> 
</span><ins>+        self.access = &quot;write&quot;
+        self.raiseTimeout = True
+        access = yield record.accessForRecord(None)
+        self.assertEquals(access, WikiAccessLevel.none)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> # Test getWikiACL()
</span><span class="cx"> # Currently stubs out enough functionality to test that an unauthenticated
</span><span class="cx"> # request can support read access when generating an ACL element
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhoutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/util.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/util.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/util.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -14,7 +14,8 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><del>-
</del><ins>+import re
+from twisted.internet.defer import inlineCallbacks, returnValue
</ins><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twext.python.types import MappingProxyType
</span><span class="cx"> from twext.who.aggregate import DirectoryService as AggregateDirectoryService
</span><span class="lines">@@ -69,14 +70,15 @@
</span><span class="cx">         config.AugmentService,
</span><span class="cx">         config.Authentication.Wiki,
</span><span class="cx">         serversDB=serversDB,
</span><del>-        cachingSeconds=config.DirectoryProxy.InSidecarCachingSeconds
</del><ins>+        cachingSeconds=config.DirectoryProxy.InSidecarCachingSeconds,
+        filterStartsWith=config.DirectoryFilterStartsWith
</ins><span class="cx">     )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> def buildDirectory(
</span><span class="cx">     store, dataRoot, servicesInfo, augmentServiceInfo, wikiServiceInfo,
</span><del>-    serversDB=None, cachingSeconds=0
</del><ins>+    serversDB=None, cachingSeconds=0, filterStartsWith=False
</ins><span class="cx"> ):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Return a directory without using a config object; suitable for tests
</span><span class="lines">@@ -315,6 +317,9 @@
</span><span class="cx">     if serversDB is not None:
</span><span class="cx">         augmented.setServersDB(serversDB)
</span><span class="cx"> 
</span><ins>+    if filterStartsWith:
+        augmented.setFilter(startswithFilter)
+
</ins><span class="cx">     return augmented
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -327,3 +332,49 @@
</span><span class="cx"> 
</span><span class="cx"> &lt;augments/&gt;
</span><span class="cx"> &quot;&quot;&quot;
</span><ins>+
+
+@inlineCallbacks
+def startswithFilter(
+    method, tokens, expression, recordTypes=None, records=None,
+    limitResults=None, timeoutSeconds=None
+):
+    &quot;&quot;&quot;
+    Call the passed-in method to retrieve records from the directory, but
+    further filter the results by only returning records whose email addresses
+    and names actually start with the tokens.  Without this filter, it's only
+    required that the record's fullname *contains* the tokens. &quot;Names&quot; are split
+    from the record's fullname, delimited by whitespace and hypens.
+    &quot;&quot;&quot;
+
+    tokens = [t.lower() for t in tokens]
+
+    results = []
+    records = yield method(
+        expression, recordTypes=recordTypes, limitResults=1000,
+        timeoutSeconds=timeoutSeconds
+    )
+    count = 0
+    for record in records:
+        try:
+            names = list(record.emailAddresses)
+        except AttributeError:
+            names = []
+        for fullName in record.fullNames:
+            names.extend(re.split(' |-', fullName))
+        match = True # assume it will match
+        for token in tokens:
+            for name in names:
+                if name.lower().startswith(token):
+                    break
+            else:
+                # there was no match for this token
+                match = False
+                break
+        if match:
+            results.append(record)
+            count += 1
+            if limitResults and count == limitResults:
+                break
+
+    returnValue(results)
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboocfodtxdavwhowikipy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/cfod/txdav/who/wiki.py (15010 => 15011)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/cfod/txdav/who/wiki.py        2015-07-28 02:31:38 UTC (rev 15010)
+++ CalendarServer/branches/users/cdaboo/cfod/txdav/who/wiki.py        2015-07-28 02:43:19 UTC (rev 15011)
</span><span class="lines">@@ -33,9 +33,11 @@
</span><span class="cx"> from twext.who.idirectory import FieldName as BaseFieldName
</span><span class="cx"> from twext.who.util import ConstantsContainer
</span><span class="cx"> from twisted.internet import reactor
</span><del>-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
</del><ins>+from twisted.internet.defer import (
+    inlineCallbacks, returnValue, succeed, TimeoutError
+)
</ins><span class="cx"> from twisted.python.constants import Names, NamedConstant
</span><del>-from twisted.web.client import HTTPPageGetter, HTTPClientFactory
</del><ins>+from twisted.web.client import HTTPClientFactory
</ins><span class="cx"> from twisted.web.error import Error as WebError
</span><span class="cx"> from txdav.who.directory import CalendarDirectoryRecordMixin
</span><span class="cx"> from txdav.who.idirectory import FieldName
</span><span class="lines">@@ -218,6 +220,12 @@
</span><span class="cx">             )
</span><span class="cx">             returnValue(WikiAccessLevel.none)
</span><span class="cx"> 
</span><ins>+        except TimeoutError as e:
+            self.log.error(
+                &quot;Wiki request timed out&quot;
+            )
+            returnValue(WikiAccessLevel.none)
+
</ins><span class="cx">         try:
</span><span class="cx">             returnValue({
</span><span class="cx">                 &quot;no-access&quot;: WikiAccessLevel.none,
</span><span class="lines">@@ -439,7 +447,6 @@
</span><span class="cx">         otherwise a twisted.web.error.Error is the result.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     point = endpoints.clientFromString(reactor, descriptor)
</span><del>-    factory = HTTPClientFactory(url)
-    factory.protocol = HTTPPageGetter
</del><ins>+    factory = HTTPClientFactory(url, timeout=10)
</ins><span class="cx">     point.connect(factory)
</span><span class="cx">     return factory.deferred
</span></span></pre>
</div>
</div>

</body>
</html>