<!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>[12273] CalendarServer/branches/release/CalendarServer-5.2-dev</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/12273">12273</a></dd>
<dt>Author</dt> <dd>wsanchez@apple.com</dd>
<dt>Date</dt> <dd>2014-01-08 14:20:19 -0800 (Wed, 08 Jan 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Pulled up <a href="http://trac.calendarserver.org//changeset/11934">r11934</a> from trunk.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushamppushpy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/amppush.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushapplepushpy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/applepush.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushnotifierpy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/notifier.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushtesttest_amppushpy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_amppush.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushtesttest_applepushpy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_applepush.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushtesttest_notifierpy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_notifier.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushutilpy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/util.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolsampnotificationspy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/ampnotifications.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolsgatewaypy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/gateway.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolstesttest_gatewaypy">CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/test_gateway.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoresqlpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoretestcommonpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/common.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcarddavdatastoretestcommonpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/carddav/datastore/test/common.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoresqlpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoresql_schemacurrentoracledialectsql">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql_schema/current-oracle-dialect.sql</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoresql_schemacurrentsql">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql_schema/current.sql</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoretestutilpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52devtxdavidavpy">CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/idav.py</a></li>
</ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#CalendarServerbranchesreleaseCalendarServer52dev">CalendarServer/branches/release/CalendarServer-5.2-dev/</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesreleaseCalendarServer52dev"></a>
<div class="propset"><h4>Property changes: CalendarServer/branches/release/CalendarServer-5.2-dev</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnmergeinfo"></a>
<div class="modfile"><h4>Modified: svn:mergeinfo</h4></div>
<span class="cx">/CalendarServer/branches/config-separation:4379-4443
</span><span class="cx">/CalendarServer/branches/egg-info-351:4589-4625
</span><span class="cx">/CalendarServer/branches/generic-sqlstore:6167-6191
</span><span class="cx">/CalendarServer/branches/new-store:5594-5934
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile:5911-5935
</span><span class="cx">/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
</span><span class="cx">/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
</span><span class="cx">/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
</span><span class="cx">/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
</span><span class="cx">/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
</span><span class="cx">/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
</span><span class="cx">/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
</span><span class="cx">/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
</span><span class="cx">/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
</span><span class="cx">/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
</span><span class="cx">/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
</span><span class="cx">/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pods:7297-7377
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycard:7227-7237
</span><span class="cx">/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
</span><span class="cx">/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
</span><span class="cx">/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
</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/sharedgroups-3:11088-11204
</span><span class="cx">/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
</span><span class="cx">/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
</span><span class="cx">/CalendarServer/branches/users/glyph/conn-limit:6574-6577
</span><span class="cx">/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
</span><span class="cx">/CalendarServer/branches/users/glyph/dalify:6932-7023
</span><span class="cx">/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
</span><span class="cx">/CalendarServer/branches/users/glyph/deploybuild:7563-7572
</span><span class="cx">/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
</span><span class="cx">/CalendarServer/branches/users/glyph/disable-quota:7718-7727
</span><span class="cx">/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
</span><span class="cx">/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
</span><span class="cx">/CalendarServer/branches/users/glyph/hang-fix:11465-11491
</span><span class="cx">/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
</span><span class="cx">/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
</span><span class="cx">/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
</span><span class="cx">/CalendarServer/branches/users/glyph/linux-tests:6893-6900
</span><span class="cx">/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
</span><span class="cx">/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
</span><span class="cx">/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
</span><span class="cx">/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
</span><span class="cx">/CalendarServer/branches/users/glyph/new-export:7444-7485
</span><span class="cx">/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle:7106-7155
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
</span><span class="cx">/CalendarServer/branches/users/glyph/other-html:8062-8091
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
</span><span class="cx">/CalendarServer/branches/users/glyph/q:9560-9688
</span><span class="cx">/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
</span><span class="cx">/CalendarServer/branches/users/glyph/quota:7604-7637
</span><span class="cx">/CalendarServer/branches/users/glyph/sendfdport:5388-5424
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
</span><span class="cx">/CalendarServer/branches/users/glyph/sharedpool:6490-6550
</span><span class="cx">/CalendarServer/branches/users/glyph/sharing-api:9192-9205
</span><span class="cx">/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
</span><span class="cx">/CalendarServer/branches/users/glyph/sql-store:5929-6073
</span><span class="cx">/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
</span><span class="cx">/CalendarServer/branches/users/glyph/subtransactions:7248-7258
</span><span class="cx">/CalendarServer/branches/users/glyph/table-alias:8651-8664
</span><span class="cx">/CalendarServer/branches/users/glyph/uidexport:7673-7676
</span><span class="cx">/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
</span><span class="cx">/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
</span><span class="cx">/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
</span><span class="cx">/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
</span><span class="cx">/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
</span><span class="cx">/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
</span><span class="cx">/CalendarServer/branches/users/sagen/applepush:8126-8184
</span><span class="cx">/CalendarServer/branches/users/sagen/inboxitems:7380-7381
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources:5032-5051
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
</span><span class="cx">/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
</span><span class="cx">/CalendarServer/branches/users/sagen/resources-2:5084-5093
</span><span class="cx">/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
</span><span class="cx">/CalendarServer/branches/users/wsanchez/transations:5515-5593
</span><span class="cx">/CalendarServer/trunk:11898,11991,12021,12028,12030-12031,12036,12241,12262-12263
</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/users/cdaboo/batchupload-6699:6700-7198
</span><span class="cx">/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
</span><span class="cx">/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
</span><span class="cx">/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
</span><span class="cx">/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
</span><span class="cx">/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
</span><span class="cx">/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
</span><span class="cx">/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
</span><span class="cx">/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
</span><span class="cx">/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
</span><span class="cx">/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pods:7297-7377
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
</span><span class="cx">/CalendarServer/branches/users/cdaboo/pycard:7227-7237
</span><span class="cx">/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
</span><span class="cx">/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
</span><span class="cx">/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
</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/sharedgroups-3:11088-11204
</span><span class="cx">/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
</span><span class="cx">/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
</span><span class="cx">/CalendarServer/branches/users/glyph/conn-limit:6574-6577
</span><span class="cx">/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
</span><span class="cx">/CalendarServer/branches/users/glyph/dalify:6932-7023
</span><span class="cx">/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
</span><span class="cx">/CalendarServer/branches/users/glyph/deploybuild:7563-7572
</span><span class="cx">/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
</span><span class="cx">/CalendarServer/branches/users/glyph/disable-quota:7718-7727
</span><span class="cx">/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
</span><span class="cx">/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
</span><span class="cx">/CalendarServer/branches/users/glyph/hang-fix:11465-11491
</span><span class="cx">/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
</span><span class="cx">/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
</span><span class="cx">/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
</span><span class="cx">/CalendarServer/branches/users/glyph/linux-tests:6893-6900
</span><span class="cx">/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
</span><span class="cx">/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
</span><span class="cx">/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
</span><span class="cx">/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
</span><span class="cx">/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
</span><span class="cx">/CalendarServer/branches/users/glyph/new-export:7444-7485
</span><span class="cx">/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle:7106-7155
</span><span class="cx">/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
</span><span class="cx">/CalendarServer/branches/users/glyph/other-html:8062-8091
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
</span><span class="cx">/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
</span><span class="cx">/CalendarServer/branches/users/glyph/q:9560-9688
</span><span class="cx">/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
</span><span class="cx">/CalendarServer/branches/users/glyph/quota:7604-7637
</span><span class="cx">/CalendarServer/branches/users/glyph/sendfdport:5388-5424
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
</span><span class="cx">/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
</span><span class="cx">/CalendarServer/branches/users/glyph/sharedpool:6490-6550
</span><span class="cx">/CalendarServer/branches/users/glyph/sharing-api:9192-9205
</span><span class="cx">/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
</span><span class="cx">/CalendarServer/branches/users/glyph/sql-store:5929-6073
</span><span class="cx">/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
</span><span class="cx">/CalendarServer/branches/users/glyph/subtransactions:7248-7258
</span><span class="cx">/CalendarServer/branches/users/glyph/table-alias:8651-8664
</span><span class="cx">/CalendarServer/branches/users/glyph/uidexport:7673-7676
</span><span class="cx">/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
</span><span class="cx">/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
</span><span class="cx">/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
</span><span class="cx">/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
</span><span class="cx">/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
</span><span class="cx">/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
</span><span class="cx">/CalendarServer/branches/users/sagen/applepush:8126-8184
</span><span class="cx">/CalendarServer/branches/users/sagen/inboxitems:7380-7381
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources:5032-5051
</span><span class="cx">/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
</span><span class="cx">/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
</span><span class="cx">/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
</span><span class="cx">/CalendarServer/branches/users/sagen/resources-2:5084-5093
</span><span class="cx">/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
</span><span class="cx">/CalendarServer/branches/users/wsanchez/transations:5515-5593
</span><span class="cx">/CalendarServer/trunk:11898,11934,11991,12021,12028,12030-12031,12036,12241,12262-12263
</span><a id="CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushamppushpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/amppush.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/amppush.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/amppush.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -23,7 +23,9 @@
</span><span class="cx"> import time
</span><span class="cx"> import uuid
</span><span class="cx"> 
</span><ins>+from calendarserver.push.util import PushPriority
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -49,7 +51,8 @@
</span><span class="cx"> 
</span><span class="cx"> class NotificationForID(amp.Command):
</span><span class="cx">     arguments = [('id', amp.String()),
</span><del>-                 ('dataChangedTimestamp', amp.Integer(optional=True))]
</del><ins>+                 ('dataChangedTimestamp', amp.Integer(optional=True)),
+                 ('priority', amp.Integer(optional=True))]
</ins><span class="cx">     response = [('status', amp.String())]
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -82,12 +85,14 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def enqueue(self, transaction, id, dataChangedTimestamp=None):
</del><ins>+    def enqueue(self, transaction, id, dataChangedTimestamp=None,
+        priority=PushPriority.high):
</ins><span class="cx">         if dataChangedTimestamp is None:
</span><span class="cx">             dataChangedTimestamp = int(time.time())
</span><span class="cx">         for protocol in self.protocols:
</span><span class="cx">             yield protocol.callRemote(NotificationForID, id=id,
</span><del>-                dataChangedTimestamp=dataChangedTimestamp)
</del><ins>+                dataChangedTimestamp=dataChangedTimestamp,
+                priority=priority.value)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -103,10 +108,12 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @NotificationForID.responder
</span><del>-    def enqueueFromWorker(self, id, dataChangedTimestamp=None):
</del><ins>+    def enqueueFromWorker(self, id, dataChangedTimestamp=None,
+        priority=PushPriority.high.value):
</ins><span class="cx">         if dataChangedTimestamp is None:
</span><span class="cx">             dataChangedTimestamp = int(time.time())
</span><del>-        self.master.enqueue(None, id, dataChangedTimestamp=dataChangedTimestamp)
</del><ins>+        self.master.enqueue(None, id, dataChangedTimestamp=dataChangedTimestamp,
+            priority=PushPriority.lookupByValue(priority))
</ins><span class="cx">         return {&quot;status&quot; : &quot;OK&quot;}
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -167,7 +174,8 @@
</span><span class="cx">         self.subscribers.remove(p)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None):
</del><ins>+    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None,
+        priority=PushPriority.high):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Sends an AMP push notification to any clients subscribing to this pushKey.
</span><span class="cx"> 
</span><span class="lines">@@ -192,23 +200,26 @@
</span><span class="cx">             if token is not None:
</span><span class="cx">                 tokens.append(token)
</span><span class="cx">         if tokens:
</span><del>-            return self.scheduleNotifications(tokens, pushKey, dataChangedTimestamp)
</del><ins>+            return self.scheduleNotifications(tokens, pushKey,
+                dataChangedTimestamp, priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def sendNotification(self, token, id, dataChangedTimestamp):
</del><ins>+    def sendNotification(self, token, id, dataChangedTimestamp, priority):
</ins><span class="cx">         for subscriber in self.subscribers:
</span><span class="cx">             if subscriber.subscribedToID(id):
</span><del>-                yield subscriber.notify(token, id, dataChangedTimestamp)
</del><ins>+                yield subscriber.notify(token, id, dataChangedTimestamp,
+                    priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def scheduleNotifications(self, tokens, id, dataChangedTimestamp):
</del><ins>+    def scheduleNotifications(self, tokens, id, dataChangedTimestamp, priority):
</ins><span class="cx">         if self.scheduler is not None:
</span><del>-            self.scheduler.schedule(tokens, id, dataChangedTimestamp)
</del><ins>+            self.scheduler.schedule(tokens, id, dataChangedTimestamp, priority)
</ins><span class="cx">         else:
</span><span class="cx">             for token in tokens:
</span><del>-                yield self.sendNotification(token, id, dataChangedTimestamp)
</del><ins>+                yield self.sendNotification(token, id, dataChangedTimestamp,
+                    priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -238,11 +249,12 @@
</span><span class="cx">         return {&quot;status&quot; : &quot;OK&quot;}
</span><span class="cx">     UnsubscribeFromID.responder(unsubscribe)
</span><span class="cx"> 
</span><del>-    def notify(self, token, id, dataChangedTimestamp):
</del><ins>+    def notify(self, token, id, dataChangedTimestamp, priority):
</ins><span class="cx">         if self.subscribedToID(id) == token:
</span><span class="cx">             self.log.debug(&quot;Sending notification for %s to %s&quot; % (id, token))
</span><span class="cx">             return self.callRemote(NotificationForID, id=id,
</span><del>-                dataChangedTimestamp=dataChangedTimestamp)
</del><ins>+                dataChangedTimestamp=dataChangedTimestamp,
+                priority=priority.value)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def subscribedToID(self, id):
</span><span class="lines">@@ -288,8 +300,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def notificationForID(self, id, dataChangedTimestamp):
-        yield self.callback(id, dataChangedTimestamp)
</del><ins>+    def notificationForID(self, id, dataChangedTimestamp, priority):
+        yield self.callback(id, dataChangedTimestamp, PushPriority.lookupByValue(priority))
</ins><span class="cx">         returnValue({&quot;status&quot; : &quot;OK&quot;})
</span><span class="cx"> 
</span><span class="cx">     NotificationForID.responder(notificationForID)
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushapplepushpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/applepush.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/applepush.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/applepush.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -36,16 +36,27 @@
</span><span class="cx"> import struct
</span><span class="cx"> import time
</span><span class="cx"> from txdav.common.icommondatastore import InvalidSubscriptionValues
</span><del>-
-from calendarserver.push.util import validToken, TokenHistory, PushScheduler
-
</del><ins>+from calendarserver.push.util import (
+    validToken, TokenHistory, PushScheduler, PushPriority
+)
</ins><span class="cx"> from twext.internet.adaptendpoint import connect
</span><span class="cx"> from twext.internet.gaiendpoint import GAIEndpoint
</span><ins>+from twisted.python.constants import Values, ValueConstant
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class ApplePushPriority(Values):
+    &quot;&quot;&quot;
+    Maps calendarserver.push.util.PushPriority values to APNS-specific values
+    &quot;&quot;&quot;
+    low    = ValueConstant(PushPriority.low.value)
+    medium = ValueConstant(PushPriority.medium.value)
+    high   = ValueConstant(PushPriority.high.value)
+
+
+
</ins><span class="cx"> class ApplePushNotifierService(service.MultiService):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     ApplePushNotifierService is a MultiService responsible for
</span><span class="lines">@@ -55,7 +66,7 @@
</span><span class="cx"> 
</span><span class="cx">     The Apple Push Notification protocol is described here:
</span><span class="cx"> 
</span><del>-    http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html
</del><ins>+    https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -177,7 +188,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None):
</del><ins>+    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None,
+        priority=PushPriority.high):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Sends an Apple Push Notification to any device token subscribed to
</span><span class="cx">         this pushKey.
</span><span class="lines">@@ -191,6 +203,8 @@
</span><span class="cx">         @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change
</span><span class="cx">             which triggered this notification (Only used for unit tests)
</span><span class="cx">         @type key: C{int}
</span><ins>+        @param priority: the priority level
+        @type priority: L{PushPriority}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         try:
</span><span class="lines">@@ -219,7 +233,8 @@
</span><span class="cx">                     if token and uid:
</span><span class="cx">                         tokens.append(token)
</span><span class="cx">                 if tokens:
</span><del>-                    provider.scheduleNotifications(tokens, pushKey, dataChangedTimestamp)
</del><ins>+                    provider.scheduleNotifications(tokens, pushKey,
+                        dataChangedTimestamp, priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -230,8 +245,7 @@
</span><span class="cx">     log = Logger()
</span><span class="cx"> 
</span><span class="cx">     # Sent by provider
</span><del>-    COMMAND_SIMPLE = 0
-    COMMAND_ENHANCED = 1
</del><ins>+    COMMAND_PROVIDER = 2
</ins><span class="cx"> 
</span><span class="cx">     # Received by provider
</span><span class="cx">     COMMAND_ERROR = 8
</span><span class="lines">@@ -333,7 +347,7 @@
</span><span class="cx">                 yield txn.commit()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def sendNotification(self, token, key, dataChangedTimestamp):
</del><ins>+    def sendNotification(self, token, key, dataChangedTimestamp, priority):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Sends a push notification message for the key to the device associated
</span><span class="cx">         with the token.
</span><span class="lines">@@ -357,6 +371,7 @@
</span><span class="cx">             return
</span><span class="cx"> 
</span><span class="cx">         identifier = self.history.add(token)
</span><ins>+        apnsPriority = ApplePushPriority.lookupByValue(priority.value).value
</ins><span class="cx">         payload = json.dumps(
</span><span class="cx">             {
</span><span class="cx">                 &quot;key&quot; : key,
</span><span class="lines">@@ -365,23 +380,79 @@
</span><span class="cx">             }
</span><span class="cx">         )
</span><span class="cx">         payloadLength = len(payload)
</span><del>-        self.log.debug(&quot;Sending APNS notification to {token}: id={id} payload={payload}&quot;,
-            token=token, id=identifier, payload=payload)
</del><ins>+        self.log.debug(&quot;Sending APNS notification to {token}: id={id} payload={payload} priority={priority}&quot;,
+            token=token, id=identifier, payload=payload, priority=apnsPriority)
</ins><span class="cx"> 
</span><ins>+        &quot;&quot;&quot;
+        Notification format
+
+        Top level:  Command (1 byte), Frame length (4 bytes), Frame data (variable)
+        Within Frame data:  Item ...
+        Item: Item number (1 byte), Item data length (2 bytes), Item data (variable)
+        Item 1: Device token (32 bytes)
+        Item 2: Payload (variable length) in JSON format, not null-terminated
+        Item 3: Notification ID (4 bytes) an opaque value used for reporting errors
+        Item 4: Expiration date (4 bytes) UNIX epoch in secondcs UTC
+        Item 5: Priority (1 byte): 10 (push sent immediately) or 5 (push sent
+            at a time that conservces power on the device receiving it)
+        &quot;&quot;&quot;
+
+                                                    # Frame struct.pack format
+                                                    # ! Network byte order
+        command = self.COMMAND_PROVIDER             # B
+        frameLength = (                             # I
+            # Item 1 (Device token)
+            1 +  # Item number                      # B
+            2 +  # Item length                      # H
+            32 + # device token                     # 32s
+            # Item 2 (Payload)
+            1 +  # Item number                      # B
+            2 +  # Item length                      # H
+            payloadLength + # the JSON payload      # %d s
+            # Item 3 (Notification ID)
+            1 +  # Item number                      # B    
+            2 +  # Item length                      # H
+            4 +  # Notification ID                  # I
+            # Item 4 (Expiration)
+            1 +  # Item number                      # B
+            2 +  # Item length                      # H
+            4 +  # Expiration seconds since epoch   # I
+            # Item 5 (Priority)
+            1 +  # Item number                      # B
+            2 +  # Item length                      # H
+            1    # Priority                         # B
+        )
+
</ins><span class="cx">         self.transport.write(
</span><del>-            struct.pack(&quot;!BIIH32sH%ds&quot; % (payloadLength,),
-                self.COMMAND_ENHANCED,           # Command
-                identifier,                      # Identifier
-                int(time.time()) + 72 * 60 * 60, # Expires in 72 hours
</del><ins>+            struct.pack(&quot;!BIBH32sBH%dsBHIBHIBHB&quot; % (payloadLength,),
+
+                command,                         # Command
+                frameLength,                     # Frame length
+
+                1,                               # Item 1 (Device token)
</ins><span class="cx">                 32,                              # Token Length
</span><span class="cx">                 binaryToken,                     # Token
</span><del>-                payloadLength,                   # Payload Length
-                payload,                         # Payload in JSON format
</del><ins>+
+                2,                               # Item 2 (Payload)
+                payloadLength,                   # Payload length
+                payload,                         # Payload
+
+                3,                               # Item 3 (Notification ID)
+                4,                               # Notification ID Length
+                identifier,                      # Notification ID
+
+                4,                               # Item 4 (Expiration)
+                4,                               # Expiration length
+                int(time.time()) + 72 * 60 * 60, # Expires in 72 hours
+
+                5,                               # Item 5 (Priority)
+                1,                               # Priority length
+                apnsPriority,                    # Priority
+
</ins><span class="cx">             )
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> class APNProviderFactory(ReconnectingClientFactory):
</span><span class="cx">     log = Logger()
</span><span class="cx"> 
</span><span class="lines">@@ -509,12 +580,13 @@
</span><span class="cx">             # sent will be put back into the queue.
</span><span class="cx">             queued = list(self.queue)
</span><span class="cx">             self.queue = []
</span><del>-            for (token, key), dataChangedTimestamp in queued:
-                if token and key and dataChangedTimestamp:
-                    self.sendNotification(token, key, dataChangedTimestamp)
</del><ins>+            for (token, key), dataChangedTimestamp, priority in queued:
+                if token and key and dataChangedTimestamp and priority:
+                    self.sendNotification(token, key, dataChangedTimestamp,
+                        priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def scheduleNotifications(self, tokens, key, dataChangedTimestamp):
</del><ins>+    def scheduleNotifications(self, tokens, key, dataChangedTimestamp, priority):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         The starting point for getting notifications to the APNS server.  If there is
</span><span class="cx">         a connection to the APNS server, these notifications are scheduled (or directly
</span><span class="lines">@@ -533,15 +605,15 @@
</span><span class="cx">         connection = getattr(self.factory, &quot;connection&quot;, None)
</span><span class="cx">         if connection is not None:
</span><span class="cx">             if self.scheduler is not None:
</span><del>-                self.scheduler.schedule(tokens, key, dataChangedTimestamp)
</del><ins>+                self.scheduler.schedule(tokens, key, dataChangedTimestamp, priority)
</ins><span class="cx">             else:
</span><span class="cx">                 for token in tokens:
</span><del>-                    self.sendNotification(token, key, dataChangedTimestamp)
</del><ins>+                    self.sendNotification(token, key, dataChangedTimestamp, priority)
</ins><span class="cx">         else:
</span><del>-            self._saveForWhenConnected(tokens, key, dataChangedTimestamp)
</del><ins>+            self._saveForWhenConnected(tokens, key, dataChangedTimestamp, priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def _saveForWhenConnected(self, tokens, key, dataChangedTimestamp):
</del><ins>+    def _saveForWhenConnected(self, tokens, key, dataChangedTimestamp, priority):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Called in order to save notifications that can't be sent now because there
</span><span class="cx">         is no connection to the APNS server.  (token, key) tuples are appended to
</span><span class="lines">@@ -557,16 +629,16 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         for token in tokens:
</span><span class="cx">             tokenKeyPair = (token, key)
</span><del>-            for existingPair, ignored in self.queue:
</del><ins>+            for existingPair, timstamp, priority in self.queue:
</ins><span class="cx">                 if tokenKeyPair == existingPair:
</span><span class="cx">                     self.log.debug(&quot;APNProviderService has no connection; skipping duplicate: %s %s&quot; % (token, key))
</span><span class="cx">                     break # Already scheduled
</span><span class="cx">             else:
</span><span class="cx">                 self.log.debug(&quot;APNProviderService has no connection; queuing: %s %s&quot; % (token, key))
</span><del>-                self.queue.append(((token, key), dataChangedTimestamp))
</del><ins>+                self.queue.append(((token, key), dataChangedTimestamp, priority))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def sendNotification(self, token, key, dataChangedTimestamp):
</del><ins>+    def sendNotification(self, token, key, dataChangedTimestamp, priority):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         If there is a connection the notification is sent right away, otherwise
</span><span class="cx">         the notification is saved for later.
</span><span class="lines">@@ -579,15 +651,15 @@
</span><span class="cx">             which triggered this notification
</span><span class="cx">         @type key: C{int}
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        if not (token and key and dataChangedTimestamp):
</del><ins>+        if not (token and key and dataChangedTimestamp, priority):
</ins><span class="cx">             return
</span><span class="cx"> 
</span><span class="cx">         # Service has reference to factory has reference to protocol instance
</span><span class="cx">         connection = getattr(self.factory, &quot;connection&quot;, None)
</span><span class="cx">         if connection is None:
</span><del>-            self._saveForWhenConnected([token], key, dataChangedTimestamp)
</del><ins>+            self._saveForWhenConnected([token], key, dataChangedTimestamp, priority)
</ins><span class="cx">         else:
</span><del>-            connection.sendNotification(token, key, dataChangedTimestamp)
</del><ins>+            connection.sendNotification(token, key, dataChangedTimestamp, priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushnotifierpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/notifier.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/notifier.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/notifier.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -19,7 +19,7 @@
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.dal.record import fromTable
</span><del>-from twext.enterprise.dal.syntax import Delete
</del><ins>+from twext.enterprise.dal.syntax import Delete, Select, Parameter
</ins><span class="cx"> from twext.enterprise.queue import WorkItem
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> 
</span><span class="lines">@@ -32,10 +32,13 @@
</span><span class="cx"> 
</span><span class="cx"> import datetime
</span><span class="cx"> 
</span><ins>+from calendarserver.push.util import PushPriority
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+
+
</ins><span class="cx"> class PushNotificationWork(WorkItem, fromTable(schema.PUSH_NOTIFICATION_WORK)):
</span><span class="cx"> 
</span><span class="cx">     group = property(lambda self: self.pushID)
</span><span class="lines">@@ -43,14 +46,36 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def doWork(self):
</span><span class="cx"> 
</span><del>-        # Delete all other work items with the same pushID
-        yield Delete(From=self.table,
-                     Where=self.table.PUSH_ID == self.pushID
-                    ).on(self.transaction)
</del><ins>+        # Find all work items with the same push ID and find the highest
+        # priority.  Delete matching work items.
+        results = (yield Select([self.table.WORK_ID, self.table.PRIORITY,
+            self.table.PUSH_ID],
+            From=self.table, Where=self.table.PUSH_ID == self.pushID).on(
+            self.transaction))
</ins><span class="cx"> 
</span><ins>+        maxPriority = self.priority
+
+        # If there are other enqueued work items for this push ID, find the
+        # highest priority one and use that value
+        if results:
+            workIDs = []
+            for workID, priority, pushID in results:
+                if priority &gt; maxPriority:
+                    maxPriority = priority
+                workIDs.append(workID)
+
+            # Delete the work items we selected
+            yield Delete(From=self.table,
+                         Where=self.table.WORK_ID.In(
+                            Parameter(&quot;workIDs&quot;, len(workIDs)))
+                        ).on(self.transaction, workIDs=workIDs)
+
</ins><span class="cx">         pushDistributor = self.transaction._pushDistributor
</span><span class="cx">         if pushDistributor is not None:
</span><del>-            yield pushDistributor.enqueue(self.transaction, self.pushID)
</del><ins>+            # Convert the integer priority value back into a constant
+            priority = PushPriority.lookupByValue(maxPriority)
+            yield pushDistributor.enqueue(self.transaction, self.pushID,
+                priority=priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -84,13 +109,15 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def notify(self, txn):
</del><ins>+    def notify(self, txn, priority=PushPriority.high):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Send the notification. For a home object we just push using the home id. For a home
</span><span class="cx">         child we push both the owner home id and the owned home child id.
</span><span class="cx"> 
</span><span class="cx">         @param txn: The transaction to create the work item with
</span><span class="cx">         @type txn: L{CommonStoreTransaction}
</span><ins>+        @param priority: the priority level
+        @type priority: L{PushPriority}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # Push ids from the store objects are a tuple of (prefix, name,) and we need to compose that
</span><span class="cx">         # into a single token.
</span><span class="lines">@@ -102,10 +129,13 @@
</span><span class="cx"> 
</span><span class="cx">         for prefix, id in ids:
</span><span class="cx">             if self._notify:
</span><del>-                self.log.debug(&quot;Notifications are enabled: %s %s/%s&quot; % (self._storeObject, prefix, id,))
-                yield self._notifierFactory.send(prefix, id, txn)
</del><ins>+                self.log.debug(&quot;Notifications are enabled: %s %s/%s priority=%d&quot; %
+                    (self._storeObject, prefix, id, priority.value))
+                yield self._notifierFactory.send(prefix, id, txn,
+                    priority=priority)
</ins><span class="cx">             else:
</span><del>-                self.log.debug(&quot;Skipping notification for: %s %s/%s&quot; % (self._storeObject, prefix, id,))
</del><ins>+                self.log.debug(&quot;Skipping notification for: %s %s/%s&quot; %
+                    (self._storeObject, prefix, id,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def clone(self, storeObject):
</span><span class="lines">@@ -150,12 +180,14 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def send(self, prefix, id, txn):
</del><ins>+    def send(self, prefix, id, txn, priority=PushPriority.high):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Enqueue a push notification work item on the provided transaction.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=self.coalesceSeconds)
</span><del>-        yield txn.enqueue(PushNotificationWork, pushID=self.pushKeyForId(prefix, id), notBefore=notBefore)
</del><ins>+        yield txn.enqueue(PushNotificationWork,
+            pushID=self.pushKeyForId(prefix, id), notBefore=notBefore,
+            priority=priority.value)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def newNotifier(self, storeObject):
</span><span class="lines">@@ -212,7 +244,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def enqueue(self, transaction, pushKey):
</del><ins>+    def enqueue(self, transaction, pushKey, priority=PushPriority.high):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Pass along enqueued pushKey to any observers
</span><span class="cx"> 
</span><span class="lines">@@ -221,6 +253,10 @@
</span><span class="cx"> 
</span><span class="cx">         @param pushKey: the push key to distribute to the observers
</span><span class="cx">         @type pushKey: C{str}
</span><ins>+
+        @param priority: the priority level
+        @type priority: L{PushPriority}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         for observer in self.observers:
</span><del>-            yield observer.enqueue(transaction, pushKey)
</del><ins>+            yield observer.enqueue(transaction, pushKey,
+                dataChangedTimestamp=None, priority=priority)
</ins></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushtesttest_amppushpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_amppush.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_amppush.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_amppush.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -18,6 +18,7 @@
</span><span class="cx"> from calendarserver.push.amppush import NotificationForID
</span><span class="cx"> from twistedcaldav.test.util import StoreTestCase
</span><span class="cx"> from twisted.internet.task import Clock
</span><ins>+from calendarserver.push.util import PushPriority
</ins><span class="cx"> 
</span><span class="cx"> class AMPPushMasterTests(StoreTestCase):
</span><span class="cx"> 
</span><span class="lines">@@ -57,27 +58,81 @@
</span><span class="cx">         self.assertTrue(client3.subscribedToID(&quot;/CalDAV/localhost/user03/&quot;))
</span><span class="cx"> 
</span><span class="cx">         dataChangedTimestamp = 1354815999
</span><del>-        service.enqueue(None, &quot;/CalDAV/localhost/user01/&quot;, dataChangedTimestamp=dataChangedTimestamp)
</del><ins>+        service.enqueue(None, &quot;/CalDAV/localhost/user01/&quot;,
+            dataChangedTimestamp=dataChangedTimestamp,
+            priority=PushPriority.high)
</ins><span class="cx">         self.assertEquals(len(client1.history), 0)
</span><span class="cx">         self.assertEquals(len(client2.history), 0)
</span><span class="cx">         self.assertEquals(len(client3.history), 0)
</span><span class="cx">         clock.advance(1)
</span><del>-        self.assertEquals(client1.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp': 1354815999})])
</del><ins>+        self.assertEquals(
+            client1.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.high.value,
+                    }
+                )
+            ]
+        )
</ins><span class="cx">         self.assertEquals(len(client2.history), 0)
</span><span class="cx">         self.assertEquals(len(client3.history), 0)
</span><span class="cx">         clock.advance(3)
</span><del>-        self.assertEquals(client2.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp': 1354815999})])
</del><ins>+        self.assertEquals(
+            client2.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.high.value,
+                    }
+                )
+            ]
+        )
+
</ins><span class="cx">         self.assertEquals(len(client3.history), 0)
</span><span class="cx">         clock.advance(3)
</span><del>-        self.assertEquals(client3.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp': 1354815999})])
</del><ins>+        self.assertEquals(
+            client3.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.high.value,
+                    }
+                )
+            ]
+        )
</ins><span class="cx"> 
</span><span class="cx">         client1.reset()
</span><span class="cx">         client2.reset()
</span><span class="cx">         client2.unsubscribe(&quot;token2&quot;, &quot;/CalDAV/localhost/user01/&quot;)
</span><del>-        service.enqueue(None, &quot;/CalDAV/localhost/user01/&quot;, dataChangedTimestamp=dataChangedTimestamp)
</del><ins>+        service.enqueue(None, &quot;/CalDAV/localhost/user01/&quot;,
+            dataChangedTimestamp=dataChangedTimestamp,
+            priority=PushPriority.low)
</ins><span class="cx">         self.assertEquals(len(client1.history), 0)
</span><span class="cx">         clock.advance(1)
</span><del>-        self.assertEquals(client1.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
</del><ins>+        self.assertEquals(
+            client1.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.low.value,
+                    }
+                )
+            ]
+        )
+
</ins><span class="cx">         self.assertEquals(len(client2.history), 0)
</span><span class="cx">         clock.advance(3)
</span><span class="cx">         self.assertEquals(len(client2.history), 0)
</span><span class="lines">@@ -87,9 +142,35 @@
</span><span class="cx">         client1.reset()
</span><span class="cx">         client2.reset()
</span><span class="cx">         client2.subscribe(&quot;token2&quot;, &quot;/CalDAV/localhost/user01/&quot;)
</span><del>-        service.enqueue(None, &quot;/CalDAV/localhost/user01/&quot;, dataChangedTimestamp=dataChangedTimestamp)
-        self.assertEquals(client1.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
-        self.assertEquals(client2.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
</del><ins>+        service.enqueue(None, &quot;/CalDAV/localhost/user01/&quot;,
+            dataChangedTimestamp=dataChangedTimestamp,
+            priority=PushPriority.medium)
+        self.assertEquals(
+            client1.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.medium.value,
+                    }
+                )
+            ]
+        )
+        self.assertEquals(
+            client2.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.medium.value,
+                    }
+                )
+            ]
+        )
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushtesttest_applepushpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_applepush.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_applepush.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_applepush.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -18,14 +18,15 @@
</span><span class="cx"> import struct
</span><span class="cx"> import time
</span><span class="cx"> from calendarserver.push.applepush import (
</span><del>-    ApplePushNotifierService, APNProviderProtocol
</del><ins>+    ApplePushNotifierService, APNProviderProtocol, ApplePushPriority
</ins><span class="cx"> )
</span><del>-from calendarserver.push.util import validToken, TokenHistory
</del><ins>+from calendarserver.push.util import validToken, TokenHistory, PushPriority
</ins><span class="cx"> from twistedcaldav.test.util import StoreTestCase
</span><span class="cx"> from twisted.internet.defer import inlineCallbacks, succeed
</span><span class="cx"> from twisted.internet.task import Clock
</span><span class="cx"> from txdav.common.icommondatastore import InvalidSubscriptionValues
</span><span class="cx"> 
</span><ins>+
</ins><span class="cx"> class ApplePushNotifierServiceTests(StoreTestCase):
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -120,12 +121,14 @@
</span><span class="cx">         dataChangedTimestamp = 1354815999
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx">         yield service.enqueue(txn, &quot;/CalDAV/calendars.example.com/user01/calendar/&quot;,
</span><del>-            dataChangedTimestamp=dataChangedTimestamp)
</del><ins>+            dataChangedTimestamp=dataChangedTimestamp, priority=PushPriority.high)
</ins><span class="cx">         yield txn.commit()
</span><span class="cx"> 
</span><span class="cx">         # The notifications should be in the queue
</span><del>-        self.assertTrue(((token, key1), dataChangedTimestamp) in service.providers[&quot;CalDAV&quot;].queue)
-        self.assertTrue(((token2, key1), dataChangedTimestamp) in service.providers[&quot;CalDAV&quot;].queue)
</del><ins>+        self.assertTrue(((token, key1), dataChangedTimestamp, PushPriority.high)
+            in service.providers[&quot;CalDAV&quot;].queue)
+        self.assertTrue(((token2, key1), dataChangedTimestamp, PushPriority.high)
+            in service.providers[&quot;CalDAV&quot;].queue)
</ins><span class="cx"> 
</span><span class="cx">         # Start the service, making the connection which should service the
</span><span class="cx">         # queue
</span><span class="lines">@@ -137,17 +140,40 @@
</span><span class="cx">         # Verify data sent to APN
</span><span class="cx">         providerConnector = service.providers[&quot;CalDAV&quot;].testConnector
</span><span class="cx">         rawData = providerConnector.transport.data
</span><del>-        self.assertEquals(len(rawData), 183)
-        data = struct.unpack(&quot;!BIIH32sH&quot;, rawData[:45])
-        self.assertEquals(data[0], 1) # command
-        self.assertEquals(data[4].encode(&quot;hex&quot;), token.replace(&quot; &quot;, &quot;&quot;)) # token
-        payloadLength = data[5]
-        payload = struct.unpack(&quot;%ds&quot; % (payloadLength,),
-            rawData[45:])
</del><ins>+        self.assertEquals(len(rawData), 199)
+        data = struct.unpack(&quot;!BI&quot;, rawData[:5])
+        self.assertEquals(data[0], 2) # command
+        self.assertEquals(data[1], 194) # frame length
+        # Item 1 (device token)
+        data = struct.unpack(&quot;!BH32s&quot;, rawData[5:40])
+        self.assertEquals(data[0], 1)
+        self.assertEquals(data[1], 32)
+        self.assertEquals(data[2].encode(&quot;hex&quot;), token.replace(&quot; &quot;, &quot;&quot;)) # token
+        # Item 2 (payload)
+        data = struct.unpack(&quot;!BH&quot;, rawData[40:43])
+        self.assertEquals(data[0], 2)
+        payloadLength = data[1]
+        self.assertEquals(payloadLength, 138)
+        payload = struct.unpack(&quot;!%ds&quot; % (payloadLength,), rawData[43:181])
</ins><span class="cx">         payload = json.loads(payload[0])
</span><span class="cx">         self.assertEquals(payload[&quot;key&quot;], u&quot;/CalDAV/calendars.example.com/user01/calendar/&quot;)
</span><span class="cx">         self.assertEquals(payload[&quot;dataChangedTimestamp&quot;], dataChangedTimestamp)
</span><span class="cx">         self.assertTrue(&quot;pushRequestSubmittedTimestamp&quot; in payload)
</span><ins>+        # Item 3 (notification id)
+        data = struct.unpack(&quot;!BHI&quot;, rawData[181:188])
+        self.assertEquals(data[0], 3)
+        self.assertEquals(data[1], 4)
+        self.assertEquals(data[2], 2)
+        # Item 4 (expiration)
+        data = struct.unpack(&quot;!BHI&quot;, rawData[188:195])
+        self.assertEquals(data[0], 4)
+        self.assertEquals(data[1], 4)
+        # Item 5 (priority)
+        data = struct.unpack(&quot;!BHB&quot;, rawData[195:199])
+        self.assertEquals(data[0], 5)
+        self.assertEquals(data[1], 1)
+        self.assertEquals(data[2], ApplePushPriority.high.value)
+
</ins><span class="cx">         # Verify token history is updated
</span><span class="cx">         self.assertTrue(token in [t for (_ignore_i, t) in providerConnector.service.protocol.history.history])
</span><span class="cx">         self.assertTrue(token2 in [t for (_ignore_i, t) in providerConnector.service.protocol.history.history])
</span><span class="lines">@@ -160,14 +186,21 @@
</span><span class="cx">         providerConnector.transport.data = None
</span><span class="cx">         # Send notification while service is connected
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><del>-        yield service.enqueue(txn, &quot;/CalDAV/calendars.example.com/user01/calendar/&quot;)
</del><ins>+        yield service.enqueue(txn, &quot;/CalDAV/calendars.example.com/user01/calendar/&quot;,
+            priority=PushPriority.low)
</ins><span class="cx">         yield txn.commit()
</span><span class="cx">         clock.advance(1) # so that first push is sent
</span><del>-        self.assertEquals(len(providerConnector.transport.data), 183)
</del><ins>+        self.assertEquals(len(providerConnector.transport.data), 199)
+        # Ensure that the priority is &quot;low&quot;
+        data = struct.unpack(&quot;!BHB&quot;, providerConnector.transport.data[195:199])
+        self.assertEquals(data[0], 5)
+        self.assertEquals(data[1], 1)
+        self.assertEquals(data[2], ApplePushPriority.low.value)
+
</ins><span class="cx">         # Reset sent data
</span><span class="cx">         providerConnector.transport.data = None
</span><span class="cx">         clock.advance(3) # so that second push is sent
</span><del>-        self.assertEquals(len(providerConnector.transport.data), 183)
</del><ins>+        self.assertEquals(len(providerConnector.transport.data), 199)
</ins><span class="cx"> 
</span><span class="cx">         history = []
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushtesttest_notifierpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_notifier.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_notifier.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/test/test_notifier.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -22,6 +22,8 @@
</span><span class="cx"> from twistedcaldav.config import ConfigDict
</span><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom
</span><span class="cx"> from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
</span><ins>+from calendarserver.push.util import PushPriority
+from txdav.idav import ChangeCategory
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> class StubService(object):
</span><span class="lines">@@ -33,8 +35,9 @@
</span><span class="cx">         self.history = []
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def enqueue(self, transaction, id):
-        self.history.append(id)
</del><ins>+    def enqueue(self, transaction, id, dataChangedTimestamp=None,
+        priority=None):
+        self.history.append((id, priority))
</ins><span class="cx">         return(succeed(None))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -45,8 +48,8 @@
</span><span class="cx">     def test_enqueue(self):
</span><span class="cx">         stub = StubService()
</span><span class="cx">         dist = PushDistributor([stub])
</span><del>-        yield dist.enqueue(None, &quot;testing&quot;)
-        self.assertEquals(stub.history, [&quot;testing&quot;])
</del><ins>+        yield dist.enqueue(None, &quot;testing&quot;, PushPriority.high)
+        self.assertEquals(stub.history, [(&quot;testing&quot;, PushPriority.high)])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def test_getPubSubAPSConfiguration(self):
</span><span class="lines">@@ -91,8 +94,9 @@
</span><span class="cx">         self.history = []
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def enqueue(self, transaction, pushID):
-        self.history.append(pushID)
</del><ins>+    def enqueue(self, transaction, pushID, dataChangedTimestamp=None,
+        priority=None):
+        self.history.append((pushID, priority))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -111,35 +115,62 @@
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx">         wp = (yield txn.enqueue(PushNotificationWork,
</span><span class="cx">             pushID=&quot;/CalDAV/localhost/foo/&quot;,
</span><ins>+            priority=PushPriority.high.value
</ins><span class="cx">         ))
</span><span class="cx">         yield txn.commit()
</span><span class="cx">         yield wp.whenExecuted()
</span><del>-        self.assertEquals(pushDistributor.history, [&quot;/CalDAV/localhost/foo/&quot;])
</del><ins>+        self.assertEquals(pushDistributor.history,
+            [(&quot;/CalDAV/localhost/foo/&quot;, PushPriority.high)])
</ins><span class="cx"> 
</span><span class="cx">         pushDistributor.reset()
</span><span class="cx">         txn = self._sqlCalendarStore.newTransaction()
</span><span class="cx">         wp = (yield txn.enqueue(PushNotificationWork,
</span><span class="cx">             pushID=&quot;/CalDAV/localhost/bar/&quot;,
</span><ins>+            priority=PushPriority.high.value
</ins><span class="cx">         ))
</span><span class="cx">         wp = (yield txn.enqueue(PushNotificationWork,
</span><span class="cx">             pushID=&quot;/CalDAV/localhost/bar/&quot;,
</span><ins>+            priority=PushPriority.high.value
</ins><span class="cx">         ))
</span><span class="cx">         wp = (yield txn.enqueue(PushNotificationWork,
</span><span class="cx">             pushID=&quot;/CalDAV/localhost/bar/&quot;,
</span><ins>+            priority=PushPriority.high.value
</ins><span class="cx">         ))
</span><span class="cx">         # Enqueue a different pushID to ensure those are not grouped with
</span><span class="cx">         # the others:
</span><span class="cx">         wp = (yield txn.enqueue(PushNotificationWork,
</span><span class="cx">             pushID=&quot;/CalDAV/localhost/baz/&quot;,
</span><ins>+            priority=PushPriority.high.value
</ins><span class="cx">         ))
</span><span class="cx"> 
</span><span class="cx">         yield txn.commit()
</span><span class="cx">         yield wp.whenExecuted()
</span><ins>+        self.assertEquals(set(pushDistributor.history),
+            set([(&quot;/CalDAV/localhost/bar/&quot;, PushPriority.high),
+             (&quot;/CalDAV/localhost/baz/&quot;, PushPriority.high)]))
+
+        # Ensure only the high-water-mark priority push goes out, by
+        # enqueuing low, medium, and high notifications
+        pushDistributor.reset()
+        txn = self._sqlCalendarStore.newTransaction()
+        wp = (yield txn.enqueue(PushNotificationWork,
+            pushID=&quot;/CalDAV/localhost/bar/&quot;,
+            priority=PushPriority.low.value
+        ))
+        wp = (yield txn.enqueue(PushNotificationWork,
+            pushID=&quot;/CalDAV/localhost/bar/&quot;,
+            priority=PushPriority.high.value
+        ))
+        wp = (yield txn.enqueue(PushNotificationWork,
+            pushID=&quot;/CalDAV/localhost/bar/&quot;,
+            priority=PushPriority.medium.value
+        ))
+        yield txn.commit()
+        yield wp.whenExecuted()
</ins><span class="cx">         self.assertEquals(pushDistributor.history,
</span><del>-            [&quot;/CalDAV/localhost/bar/&quot;, &quot;/CalDAV/localhost/baz/&quot;])
</del><ins>+            [(&quot;/CalDAV/localhost/bar/&quot;, PushPriority.high)])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> class NotifierFactory(StoreTestCase):
</span><span class="cx"> 
</span><span class="cx">     requirements = {
</span><span class="lines">@@ -168,8 +199,9 @@
</span><span class="cx">     def test_homeNotifier(self):
</span><span class="cx"> 
</span><span class="cx">         home = yield self.homeUnderTest()
</span><del>-        yield home.notifyChanged()
-        self.assertEquals(self.notifierFactory.history, [&quot;/CalDAV/example.com/home1/&quot;])
</del><ins>+        yield home.notifyChanged(category=ChangeCategory.default)
+        self.assertEquals(self.notifierFactory.history,
+            [(&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high)])
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -177,10 +209,12 @@
</span><span class="cx">     def test_calendarNotifier(self):
</span><span class="cx"> 
</span><span class="cx">         calendar = yield self.calendarUnderTest()
</span><del>-        yield calendar.notifyChanged()
</del><ins>+        yield calendar.notifyChanged(category=ChangeCategory.default)
</ins><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><del>-            set([&quot;/CalDAV/example.com/home1/&quot;, &quot;/CalDAV/example.com/home1/calendar_1/&quot;])
</del><ins>+            set([
+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high)])
</ins><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -194,9 +228,9 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CalDAV/example.com/home1/&quot;,
-                &quot;/CalDAV/example.com/home1/calendar_1/&quot;,
-                &quot;/CalDAV/example.com/home2/&quot;
</del><ins>+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home2/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -207,9 +241,9 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CalDAV/example.com/home1/&quot;,
-                &quot;/CalDAV/example.com/home1/calendar_1/&quot;,
-                &quot;/CalDAV/example.com/home2/&quot;
</del><ins>+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home2/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -225,10 +259,12 @@
</span><span class="cx">         self.notifierFactory.reset()
</span><span class="cx"> 
</span><span class="cx">         shared = yield self.calendarUnderTest(home=&quot;home2&quot;, name=shareName)
</span><del>-        yield shared.notifyChanged()
</del><ins>+        yield shared.notifyChanged(category=ChangeCategory.default)
</ins><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><del>-            set([&quot;/CalDAV/example.com/home1/&quot;, &quot;/CalDAV/example.com/home1/calendar_1/&quot;])
</del><ins>+            set([
+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high)])
</ins><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -237,9 +273,11 @@
</span><span class="cx">     def test_notificationNotifier(self):
</span><span class="cx"> 
</span><span class="cx">         notifications = yield self.transactionUnderTest().notificationsWithUID(&quot;home1&quot;)
</span><del>-        yield notifications.notifyChanged()
</del><ins>+        yield notifications.notifyChanged(category=ChangeCategory.default)
</ins><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><del>-            set([&quot;/CalDAV/example.com/home1/&quot;, &quot;/CalDAV/example.com/home1/notification/&quot;])
</del><ins>+            set([
+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/notification/&quot;, PushPriority.high)])
</ins><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarserverpushutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/util.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/util.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/push/util.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -16,7 +16,20 @@
</span><span class="cx"> 
</span><span class="cx"> from OpenSSL import crypto
</span><span class="cx"> from twext.python.log import Logger
</span><ins>+from twisted.python.constants import Values, ValueConstant
</ins><span class="cx"> 
</span><ins>+
+
+class PushPriority(Values):
+    &quot;&quot;&quot;
+    Constants to use for push priorities
+    &quot;&quot;&quot;
+    low    = ValueConstant(1)
+    medium = ValueConstant(5)
+    high   = ValueConstant(10)
+
+
+
</ins><span class="cx"> def getAPNTopicFromCertificate(certPath):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Given the path to a certificate, extract the UID value portion of the
</span><span class="lines">@@ -128,7 +141,7 @@
</span><span class="cx">         self.staggerSeconds = staggerSeconds
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def schedule(self, tokens, key, dataChangedTimestamp):
</del><ins>+    def schedule(self, tokens, key, dataChangedTimestamp, priority):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Schedules a batch of notifications for the given tokens, staggered
</span><span class="cx">         with self.staggerSeconds between each one.  Duplicates are ignored,
</span><span class="lines">@@ -151,13 +164,14 @@
</span><span class="cx">                     (internalKey,))
</span><span class="cx">             else:
</span><span class="cx">                 self.outstanding[internalKey] = self.reactor.callLater(
</span><del>-                    scheduleTime, self.send, token, key, dataChangedTimestamp)
</del><ins>+                    scheduleTime, self.send, token, key, dataChangedTimestamp,
+                    priority)
</ins><span class="cx">                 self.log.debug(&quot;PushScheduler scheduled: %s in %.0f sec&quot; %
</span><span class="cx">                     (internalKey, scheduleTime))
</span><span class="cx">                 scheduleTime += self.staggerSeconds
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def send(self, token, key, dataChangedTimestamp):
</del><ins>+    def send(self, token, key, dataChangedTimestamp, priority):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         This method is what actually gets scheduled.  Its job is to remove
</span><span class="cx">         its corresponding entry from the outstanding dict and call the
</span><span class="lines">@@ -173,7 +187,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         self.log.debug(&quot;PushScheduler fired for %s %s %d&quot; % (token, key, dataChangedTimestamp))
</span><span class="cx">         del self.outstanding[(token, key)]
</span><del>-        return self.callback(token, key, dataChangedTimestamp)
</del><ins>+        return self.callback(token, key, dataChangedTimestamp, priority)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def stop(self):
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolsampnotificationspy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/ampnotifications.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/ampnotifications.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/ampnotifications.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -141,8 +141,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def notificationCallback(id, dataChangedTimestamp):
-    print(&quot;Received notification for:&quot;, id)
</del><ins>+def notificationCallback(id, dataChangedTimestamp, priority):
+    print(&quot;Received notification for:&quot;, id, &quot;Priority&quot;, priority)
</ins><span class="cx">     return succeed(True)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolsgatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/gateway.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/gateway.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/gateway.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -142,7 +142,6 @@
</span><span class="cx">     utilityMain(configFileName, RunnerService, verbose=debug)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> class Runner(object):
</span><span class="cx"> 
</span><span class="cx">     def __init__(self, root, directory, store, commands, output=None):
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devcalendarservertoolstesttest_gatewaypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/test_gateway.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/test_gateway.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/calendarserver/tools/test/test_gateway.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -219,6 +219,7 @@
</span><span class="cx">         self.assertEquals(record.extras[&quot;comment&quot;], &quot;Test Comment&quot;)
</span><span class="cx">         self.assertEquals(record.extras[&quot;floor&quot;], &quot;First&quot;)
</span><span class="cx">         self.assertEquals(record.extras[&quot;capacity&quot;], &quot;40&quot;)
</span><ins>+        self.assertEquals(record.extras[&quot;geo&quot;], &quot;geo:37.331,-122.030&quot;)
</ins><span class="cx"> 
</span><span class="cx">         results = yield self.runCommand(command_getLocationAttributes)
</span><span class="cx">         self.assertEquals(set(results[&quot;result&quot;][&quot;ReadProxies&quot;]), set(['user03', 'user04']))
</span><span class="lines">@@ -457,6 +458,8 @@
</span><span class="cx">         &lt;string&gt;40&lt;/string&gt;
</span><span class="cx">         &lt;key&gt;AssociatedAddress&lt;/key&gt;
</span><span class="cx">         &lt;string&gt;C701069D-9CA1-4925-A1A9-5CD94767B74B&lt;/string&gt;
</span><ins>+        &lt;key&gt;Geo&lt;/key&gt;
+        &lt;string&gt;geo:37.331,-122.030&lt;/string&gt;
</ins><span class="cx">         &lt;key&gt;ReadProxies&lt;/key&gt;
</span><span class="cx">         &lt;array&gt;
</span><span class="cx">             &lt;string&gt;users:user03&lt;/string&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/sql.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/sql.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/sql.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -94,6 +94,7 @@
</span><span class="cx"> from pycalendar.duration import PyCalendarDuration
</span><span class="cx"> from pycalendar.timezone import PyCalendarTimezone
</span><span class="cx"> from pycalendar.value import PyCalendarValue
</span><ins>+from txdav.idav import ChangeCategory
</ins><span class="cx"> 
</span><span class="cx"> from zope.interface.declarations import implements
</span><span class="cx"> 
</span><span class="lines">@@ -2284,8 +2285,18 @@
</span><span class="cx">         else:
</span><span class="cx">             yield self._calendar._updateRevision(self._name)
</span><span class="cx"> 
</span><del>-        yield self._calendar.notifyChanged()
</del><ins>+        # Determine change category
+        category = ChangeCategory.default
+        if internal_state == ComponentUpdateState.INBOX:
+            category = ChangeCategory.inbox
+        elif internal_state == ComponentUpdateState.ORGANIZER_ITIP_UPDATE:
+            category = ChangeCategory.organizerITIPUpdate
+        elif (internal_state == ComponentUpdateState.ATTENDEE_ITIP_UPDATE and
+            hasattr(self._txn, &quot;doing_attende_refresh&quot;)):
+            category = ChangeCategory.attendeeITIPUpdate
</ins><span class="cx"> 
</span><ins>+        yield self._calendar.notifyChanged(category=category)
+
</ins><span class="cx">         # Finally check if a split is needed
</span><span class="cx">         if internal_state not in (ComponentUpdateState.SPLIT_OWNER, ComponentUpdateState.SPLIT_ATTENDEE,) and schedule_state == &quot;organizer&quot;:
</span><span class="cx">             yield self.checkSplit()
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcaldavdatastoretestcommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/common.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/common.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/caldav/datastore/test/common.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -53,7 +53,9 @@
</span><span class="cx"> from txdav.common.icommondatastore import ConcurrentModification
</span><span class="cx"> from twistedcaldav.ical import Component
</span><span class="cx"> from twistedcaldav.config import config
</span><ins>+from calendarserver.push.util import PushPriority
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> storePath = FilePath(__file__).parent().child(&quot;calendar_store&quot;)
</span><span class="cx"> 
</span><span class="cx"> homeRoot = storePath.child(&quot;ho&quot;).child(&quot;me&quot;).child(&quot;home1&quot;)
</span><span class="lines">@@ -456,8 +458,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CalDAV/example.com/home1/&quot;,
-                &quot;/CalDAV/example.com/home1/notification/&quot;,
</del><ins>+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/notification/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -474,8 +476,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CalDAV/example.com/home1/&quot;,
-                &quot;/CalDAV/example.com/home1/notification/&quot;,
</del><ins>+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/notification/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -698,7 +700,7 @@
</span><span class="cx">         calendarProperties = (yield home.calendarWithName(name)).properties()
</span><span class="cx">         self.assertEqual(len(calendarProperties), 0)
</span><span class="cx">         # notify is called prior to commit
</span><del>-        self.assertTrue(&quot;/CalDAV/example.com/home1/&quot; in self.notifierFactory.history)
</del><ins>+        self.assertTrue((&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high) in self.notifierFactory.history)
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Make sure it's available in a new transaction; i.e. test the commit.
</span><span class="lines">@@ -741,10 +743,10 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CalDAV/example.com/home1/&quot;,
-                &quot;/CalDAV/example.com/home1/calendar_1/&quot;,
-                &quot;/CalDAV/example.com/home1/calendar_2/&quot;,
-                &quot;/CalDAV/example.com/home1/calendar_empty/&quot;,
</del><ins>+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_2/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_empty/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -918,8 +920,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CalDAV/example.com/home1/&quot;,
-                &quot;/CalDAV/example.com/home1/calendar_1/&quot;,
</del><ins>+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -1474,8 +1476,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CalDAV/example.com/home1/&quot;,
-                &quot;/CalDAV/example.com/home1/calendar_1/&quot;,
</del><ins>+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="lines">@@ -1593,8 +1595,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CalDAV/example.com/home1/&quot;,
-                &quot;/CalDAV/example.com/home1/calendar_1/&quot;,
</del><ins>+                (&quot;/CalDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CalDAV/example.com/home1/calendar_1/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcarddavdatastoretestcommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/carddav/datastore/test/common.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/carddav/datastore/test/common.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/carddav/datastore/test/common.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -39,7 +39,9 @@
</span><span class="cx"> from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
</span><span class="cx"> from txdav.idav import IPropertyStore, IDataStore
</span><span class="cx"> from txdav.xml.element import WebDAVUnknownElement
</span><ins>+from calendarserver.push.util import PushPriority
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx"> storePath = FilePath(__file__).parent().child(&quot;addressbook_store&quot;)
</span><span class="cx"> 
</span><span class="cx"> home1Root = storePath.child(&quot;ho&quot;).child(&quot;me&quot;).child(&quot;home1&quot;)
</span><span class="lines">@@ -372,7 +374,7 @@
</span><span class="cx">         yield home.removeAddressBookWithName(name)
</span><span class="cx">         self.assertNotIdentical((yield home.addressbookWithName(name)), None)
</span><span class="cx">         # notify is called prior to commit
</span><del>-        self.assertTrue(&quot;/CardDAV/example.com/home1/&quot; in self.notifierFactory.history)
</del><ins>+        self.assertTrue((&quot;/CardDAV/example.com/home1/&quot;, PushPriority.high) in self.notifierFactory.history)
</ins><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="cx">         # Make sure it's available in a new transaction; i.e. test the commit.
</span><span class="lines">@@ -399,8 +401,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CardDAV/example.com/home1/&quot;,
-                &quot;/CardDAV/example.com/home1/addressbook/&quot;,
</del><ins>+                (&quot;/CardDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CardDAV/example.com/home1/addressbook/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -532,8 +534,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CardDAV/example.com/home1/&quot;,
-                &quot;/CardDAV/example.com/home1/addressbook/&quot;,
</del><ins>+                (&quot;/CardDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CardDAV/example.com/home1/addressbook/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -693,8 +695,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CardDAV/example.com/home1/&quot;,
-                &quot;/CardDAV/example.com/home1/addressbook/&quot;,
</del><ins>+                (&quot;/CardDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CardDAV/example.com/home1/addressbook/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="lines">@@ -809,8 +811,8 @@
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span><span class="cx">             set([
</span><del>-                &quot;/CardDAV/example.com/home1/&quot;,
-                &quot;/CardDAV/example.com/home1/addressbook/&quot;,
</del><ins>+                (&quot;/CardDAV/example.com/home1/&quot;, PushPriority.high),
+                (&quot;/CardDAV/example.com/home1/addressbook/&quot;, PushPriority.high),
</ins><span class="cx">             ])
</span><span class="cx">         )
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -73,6 +73,7 @@
</span><span class="cx"> from txdav.common.inotifications import INotificationCollection, \
</span><span class="cx">     INotificationObject
</span><span class="cx"> from txdav.xml.parser import WebDAVDocument
</span><ins>+from txdav.idav import ChangeCategory
</ins><span class="cx"> 
</span><span class="cx"> from uuid import uuid4, UUID
</span><span class="cx"> 
</span><span class="lines">@@ -2245,7 +2246,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def notifyChanged(self):
</del><ins>+    def notifyChanged(self, category=ChangeCategory.default):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Send notifications, change sync token and bump last modified because
</span><span class="cx">         the resource has changed.  We ensure we only do this once per object
</span><span class="lines">@@ -2269,7 +2270,7 @@
</span><span class="cx">             # push notifiers add their work items immediately
</span><span class="cx">             notifier = self._notifiers.get(&quot;push&quot;, None)
</span><span class="cx">             if notifier:
</span><del>-                yield notifier.notify(self._txn)
</del><ins>+                yield notifier.notify(self._txn, priority=category.value)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><span class="lines">@@ -4306,11 +4307,11 @@
</span><span class="cx">         return self.ownerHome().notifierID()
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def notifyChanged(self):
</del><ins>+    def notifyChanged(self, category=ChangeCategory.default):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Send notifications when a child resource is changed.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        return self._notifyChanged(property_change=False)
</del><ins>+        return self._notifyChanged(property_change=False, category=category)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def notifyPropertyChanged(self):
</span><span class="lines">@@ -4321,7 +4322,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def _notifyChanged(self, property_change=False):
</del><ins>+    def _notifyChanged(self, property_change=False,
+            category=ChangeCategory.default):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Send notifications, change sync token and bump last modified because
</span><span class="cx">         the resource has changed.  We ensure we only do this once per object
</span><span class="lines">@@ -4357,7 +4359,7 @@
</span><span class="cx">             # push notifiers add their work items immediately
</span><span class="cx">             notifier = self._notifiers.get(&quot;push&quot;, None)
</span><span class="cx">             if notifier:
</span><del>-                yield notifier.notify(self._txn)
</del><ins>+                yield notifier.notify(self._txn, priority=category.value)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classproperty
</span><span class="lines">@@ -5171,7 +5173,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def notifyChanged(self):
</del><ins>+    def notifyChanged(self, category=ChangeCategory.default):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Send notifications, change sync token and bump last modified because
</span><span class="cx">         the resource has changed.  We ensure we only do this once per object
</span><span class="lines">@@ -5190,7 +5192,7 @@
</span><span class="cx">             # push notifiers add their work items immediately
</span><span class="cx">             notifier = self._notifiers.get(&quot;push&quot;, None)
</span><span class="cx">             if notifier:
</span><del>-                yield notifier.notify(self._txn)
</del><ins>+                yield notifier.notify(self._txn, priority=category.value)
</ins><span class="cx"> 
</span><span class="cx">         returnValue(None)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoresql_schemacurrentoracledialectsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql_schema/current-oracle-dialect.sql (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql_schema/current-oracle-dialect.sql        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql_schema/current-oracle-dialect.sql        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -346,7 +346,8 @@
</span><span class="cx"> create table PUSH_NOTIFICATION_WORK (
</span><span class="cx">     &quot;WORK_ID&quot; integer primary key not null,
</span><span class="cx">     &quot;NOT_BEFORE&quot; timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
</span><del>-    &quot;PUSH_ID&quot; nvarchar2(255)
</del><ins>+    &quot;PUSH_ID&quot; nvarchar2(255),
+    &quot;PRIORITY&quot; integer not null
</ins><span class="cx"> );
</span><span class="cx"> 
</span><span class="cx"> create table GROUP_CACHER_POLLING_WORK (
</span><span class="lines">@@ -365,7 +366,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', '27');
</del><ins>+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '28');
</ins><span class="cx"> insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '5');
</span><span class="cx"> insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
</span><span class="cx"> create index CALENDAR_HOME_METADAT_3cb9049e on CALENDAR_HOME_METADATA (
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoresql_schemacurrentsql"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql_schema/current.sql (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql_schema/current.sql        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/sql_schema/current.sql        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -663,7 +663,8 @@
</span><span class="cx"> create table PUSH_NOTIFICATION_WORK (
</span><span class="cx">   WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
</span><span class="cx">   NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
</span><del>-  PUSH_ID                       varchar(255) not null
</del><ins>+  PUSH_ID                       varchar(255) not null,
+  PRIORITY                      integer      not null -- 1:low 5:medium 10:high
</ins><span class="cx"> );
</span><span class="cx"> 
</span><span class="cx"> -----------------
</span><span class="lines">@@ -698,6 +699,6 @@
</span><span class="cx">   VALUE                         varchar(255)
</span><span class="cx"> );
</span><span class="cx"> 
</span><del>-insert into CALENDARSERVER values ('VERSION', '27');
</del><ins>+insert into CALENDARSERVER values ('VERSION', '28');
</ins><span class="cx"> insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
</span><span class="cx"> insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavcommondatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/test/util.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/test/util.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/common/datastore/test/util.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -733,8 +733,8 @@
</span><span class="cx">         return &quot;/%s/%s/%s/&quot; % (prefix, self.hostname, id)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def send(self, prefix, id, txn):
-        self.history.append(self.pushKeyForId(prefix, id))
</del><ins>+    def send(self, prefix, id, txn, priority): 
+        self.history.append((self.pushKeyForId(prefix, id), priority))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def reset(self):
</span></span></pre></div>
<a id="CalendarServerbranchesreleaseCalendarServer52devtxdavidavpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/idav.py (12272 => 12273)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/idav.py        2014-01-08 22:13:14 UTC (rev 12272)
+++ CalendarServer/branches/release/CalendarServer-5.2-dev/txdav/idav.py        2014-01-08 22:20:19 UTC (rev 12273)
</span><span class="lines">@@ -34,6 +34,9 @@
</span><span class="cx"> from zope.interface import Attribute, Interface
</span><span class="cx"> from zope.interface.common.mapping import IMapping
</span><span class="cx"> 
</span><ins>+from twisted.python.constants import Values, ValueConstant
+from calendarserver.push.util import PushPriority
+
</ins><span class="cx"> #
</span><span class="cx"> # Exceptions
</span><span class="cx"> #
</span><span class="lines">@@ -231,6 +234,19 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class ChangeCategory(Values):
+    &quot;&quot;&quot;
+    Constants to use for notifyChanged's category parameter.  Maps
+    types of changes to the appropriate push priority level.
+    TODO: make these values configurable in plist perhaps.
+    &quot;&quot;&quot;
+    default             = ValueConstant(PushPriority.high)
+    inbox               = ValueConstant(PushPriority.medium)
+    attendeeITIPUpdate  = ValueConstant(PushPriority.medium)
+    organizerITIPUpdate = ValueConstant(PushPriority.medium)
+
+
+
</ins><span class="cx"> class INotifier(Interface):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Interface for an object that can send change notifications. Notifiers are associated with specific notifier factories
</span><span class="lines">@@ -260,9 +276,12 @@
</span><span class="cx">         @rtype: L{IStoreNotifier} or C{None}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-    def notifyChanged(): #@NoSelf
</del><ins>+    def notifyChanged(category): #@NoSelf
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Send a change notification to any notifiers assigned to the object.
</span><ins>+
+        @param category: the kind of change triggering this notification
+        @type: L{ChangeCategory}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     def notifierID(): #@NoSelf
</span></span></pre>
</div>
</div>

</body>
</html>