<!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>[14302] CalendarServer/trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/14302">14302</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-01-14 07:42:38 -0800 (Wed, 14 Jan 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Update to latest VPOLL spec.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunkcontribwebpollMakefile">CalendarServer/trunk/contrib/webpoll/Makefile</a></li>
<li><a href="#CalendarServertrunkcontribwebpollwebappindexhtml">CalendarServer/trunk/contrib/webpoll/webapp/index.html</a></li>
<li><a href="#CalendarServertrunkcontribwebpollwebappjscaldavjs">CalendarServer/trunk/contrib/webpoll/webapp/js/caldav.js</a></li>
<li><a href="#CalendarServertrunkrequirementsdevtxt">CalendarServer/trunk/requirements-dev.txt</a></li>
<li><a href="#CalendarServertrunkrequirementsstabletxt">CalendarServer/trunk/requirements-stable.txt</a></li>
<li><a href="#CalendarServertrunktwistedcaldavicalpy">CalendarServer/trunk/twistedcaldav/ical.py</a></li>
<li><a href="#CalendarServertrunktwistedcaldavsimpleresourcepy">CalendarServer/trunk/twistedcaldav/simpleresource.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingicaldiffpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingimplicitpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingitippy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingtesttest_icaldiffpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingtesttest_itippy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingworkpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py</a></li>
</ul>

<h3>Property Changed</h3>
<ul>
<li><a href="#CalendarServertrunkcontribwebpollwebappcss">CalendarServer/trunk/contrib/webpoll/webapp/css/</a></li>
<li><a href="#CalendarServertrunkcontribwebpollwebappjs">CalendarServer/trunk/contrib/webpoll/webapp/js/</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunkcontribwebpollMakefile"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/webpoll/Makefile (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/contrib/webpoll/Makefile        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/contrib/webpoll/Makefile        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -15,22 +15,19 @@
</span><span class="cx"> ##
</span><span class="cx"> 
</span><span class="cx"> webpoll:
</span><del>-        curl http://code.jquery.com/jquery-2.0.3.js -o webapp/js/jquery-2.0.3.js
-        curl http://code.jquery.com/ui/1.10.3/jquery-ui.js -o webapp/js/jquery-ui-1.10.3.js
-        curl https://raw.github.com/douglascrockford/JSON-js/master/json2.js -o webapp/js/json2.js
-        curl http://trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.js -o webapp/js/datetimepicker.js
</del><ins>+        mkdir -p webapp/js/3rdparty
+        curl http://code.jquery.com/jquery-2.1.3.js -o webapp/js/3rdparty/jquery-2.1.3.js
+        curl http://code.jquery.com/ui/1.11.2/jquery-ui.js -o webapp/js/3rdparty/jquery-ui-1.11.2.js
+        curl -L https://github.com/douglascrockford/JSON-js/raw/master/json2.js -o webapp/js/3rdparty/json2.js
+        curl http://trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.js -o webapp/js/3rdparty/datetimepicker.js
</ins><span class="cx">         
</span><del>-        curl http://trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.css -o webapp/css/datetimepicker.css
-        curl http://jqueryui.com/resources/download/jquery-ui-themes-1.10.3.zip -o /tmp/jquery-ui-themes-1.10.3.zip
-        unzip /tmp/jquery-ui-themes-1.10.3.zip jquery-ui-themes-1.10.3/themes/cupertino/* -d /tmp
-        mv /tmp/jquery-ui-themes-1.10.3/themes/cupertino webapp/css
-        rm -rf /tmp/jquery-ui-themes-1.10.3
</del><ins>+        mkdir -p webapp/css/3rdparty
+        curl http://trentrichardson.com/examples/timepicker/jquery-ui-timepicker-addon.css -o webapp/css/3rdparty/datetimepicker.css
+        curl http://jqueryui.com/resources/download/jquery-ui-themes-1.11.2.zip -o /tmp/jquery-ui-themes-1.11.2.zip
+        unzip /tmp/jquery-ui-themes-1.11.2.zip jquery-ui-themes-1.11.2/themes/cupertino/* -d /tmp
+        mv /tmp/jquery-ui-themes-1.11.2/themes/cupertino webapp/css/3rdparty
+        rm -rf /tmp/jquery-ui-themes-1.11.2
</ins><span class="cx"> 
</span><span class="cx"> clean:
</span><del>-        rm -f webapp/js/jquery-2.0.3.js
-        rm -f webapp/js/jquery-ui-1.10.3.js
-        rm -f webapp/js/json2.js
-        rm -f webapp/js/datetimepicker.js
-        
-        rm -f webapp/css/datetimepicker.css
-        rm -rf webapp/css/cupertino
</del><ins>+        rm -rf webapp/js/3rdparty
+        rm -rf webapp/css/3rdparty
</ins></span></pre></div>
<a id="CalendarServertrunkcontribwebpollwebappcss"></a>
<div class="propset"><h4>Property changes: CalendarServer/trunk/contrib/webpoll/webapp/css</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnignore"></a>
<div class="addfile"><h4>Added: svn:ignore</h4></div>
<a id="CalendarServertrunkcontribwebpollwebappindexhtml"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/webpoll/webapp/index.html (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/contrib/webpoll/webapp/index.html        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/contrib/webpoll/webapp/index.html        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -3,13 +3,19 @@
</span><span class="cx"> &lt;head&gt;
</span><span class="cx">         &lt;meta charset=&quot;utf-8&quot;&gt;
</span><span class="cx">         &lt;title&gt;WebPoll&lt;/title&gt;
</span><del>-        &lt;link href=&quot;css/cupertino/jquery-ui.css&quot; rel=&quot;stylesheet&quot;&gt;
-        &lt;link href=&quot;css/datetimepicker.css&quot; rel=&quot;stylesheet&quot;&gt;
</del><ins>+
+        &lt;!-- 3rd party --&gt;
+        &lt;link href=&quot;css/3rdparty/cupertino/jquery-ui.css&quot; rel=&quot;stylesheet&quot;&gt;
+        &lt;link href=&quot;css/3rdparty/datetimepicker.css&quot; rel=&quot;stylesheet&quot;&gt;
+        
+        &lt;script src=&quot;js/3rdparty/jquery-2.1.3.js&quot;&gt;&lt;/script&gt;
+        &lt;script src=&quot;js/3rdparty/jquery-ui-1.11.2.js&quot;&gt;&lt;/script&gt;
+        &lt;script src=&quot;js/3rdparty/datetimepicker.js&quot;&gt;&lt;/script&gt;
+        &lt;script src=&quot;js/3rdparty/json2.js&quot;&gt;&lt;/script&gt;
+        
+        &lt;!-- Ours --&gt;
</ins><span class="cx">         &lt;link href=&quot;css/webpoll.css&quot; rel=&quot;stylesheet&quot;&gt;
</span><del>-        &lt;script src=&quot;js/jquery-2.0.3.js&quot;&gt;&lt;/script&gt;
-        &lt;script src=&quot;js/jquery-ui-1.10.3.js&quot;&gt;&lt;/script&gt;
-        &lt;script src=&quot;js/datetimepicker.js&quot;&gt;&lt;/script&gt;
-        &lt;script src=&quot;js/json2.js&quot;&gt;&lt;/script&gt;
</del><ins>+
</ins><span class="cx">         &lt;script src=&quot;js/utils.js&quot;&gt;&lt;/script&gt;
</span><span class="cx">         &lt;script src=&quot;js/jcal.js&quot;&gt;&lt;/script&gt;
</span><span class="cx">         &lt;script src=&quot;js/caldav.js&quot;&gt;&lt;/script&gt;
</span></span></pre></div>
<a id="CalendarServertrunkcontribwebpollwebappjs"></a>
<div class="propset"><h4>Property changes: CalendarServer/trunk/contrib/webpoll/webapp/js</h4>
<pre class="diff"><span>
</span></pre></div>
<a id="svnignore"></a>
<div class="addfile"><h4>Added: svn:ignore</h4></div>
<a id="CalendarServertrunkcontribwebpollwebappjscaldavjs"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/contrib/webpoll/webapp/js/caldav.js (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/contrib/webpoll/webapp/js/caldav.js        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/contrib/webpoll/webapp/js/caldav.js        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -772,34 +772,45 @@
</span><span class="cx"> 
</span><span class="cx"> CalendarComponent.prototype.voter_responses = function() {
</span><span class="cx">         var voter_results = {}
</span><del>-        $.each(this.data.properties(&quot;voter&quot;), function(index, voter) {
-                voter_results[voter[3]] = parseInt(voter[1][&quot;response&quot;]);
</del><ins>+        var pollitemid = this.pollitemid();
+        $.each(this.parent.data.components(&quot;vvoter&quot;), function(index, vvoter) {
+                var voter = vvoter.getPropertyValue(&quot;voter&quot;)
+                $.each(vvoter.components(&quot;vote&quot;), function(index, vote) {
+                        if (vote.getPropertyValue(&quot;poll-item-id&quot;) == pollitemid) {
+                                voter_results[voter] = vote.getPropertyValue(&quot;response&quot;);
+                                return false;
+                        }
+                });
</ins><span class="cx">         });
</span><span class="cx">         return voter_results;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> // Change active user's response to this event
</span><span class="cx"> CalendarComponent.prototype.changeVoterResponse = function(response) {
</span><ins>+        var matches_vvoter = $.grep(this.parent.data.components(&quot;vvoter&quot;), function(vvoter, index) {
+                return gSession.currentPrincipal.matchingAddress(vvoter.getPropertyValue(&quot;voter&quot;));
+        });
+        var pollitemid = this.pollitemid();
</ins><span class="cx">         if (response !== null) {
</span><del>-                var matches = $.grep(this.data.properties(&quot;voter&quot;), function(voter, index) {
-                        return gSession.currentPrincipal.matchingAddress(voter[3]);
</del><ins>+                var matches_vote = $.grep(matches_vvoter[0].components(&quot;vote&quot;), function(vote, index) {
+                        return vote.getPropertyValue(&quot;poll-item-id&quot;) == pollitemid;
</ins><span class="cx">                 });
</span><del>-                if (matches.length == 1) {
-                        new CalendarUser(matches[0], this).response(response.toString());
</del><ins>+                if (matches_vote.length == 1) {
+                        matches_vote[0].getProperty(&quot;response&quot;)[3] = response;
</ins><span class="cx">                 } else {
</span><del>-                        this.data.newProperty(
-                                &quot;voter&quot;,
-                                gSession.currentPrincipal.defaultAddress(),
-                                { &quot;response&quot; : response.toString() },
-                                &quot;cal-address&quot;
-                        );
-                        this.changed(true);
</del><ins>+                        var vote = matches_vvoter[0].newComponent(&quot;vote&quot;);
+                        vote.newProperty(&quot;response&quot;, response, {}, &quot;integer&quot;);
+                        vote.newProperty(&quot;poll-item-id&quot;, pollitemid, {}, &quot;integer&quot;);
</ins><span class="cx">                 }
</span><span class="cx">         } else {
</span><del>-                this.data.removePropertiesMatchingValue(function(propdata) {
-                        return propdata[0] == &quot;voter&quot; &amp;&amp; gSession.currentPrincipal.matchingAddress(propdata[3]); 
</del><ins>+                $.each(matches_vvoter[0].components(&quot;vote&quot;), function(index, vote) {
+                        if (vote.getPropertyValue(&quot;poll-item-id&quot;) == pollitemid) {
+                                matches_vvoter[0].caldata[2].remove(index);
+                                return false;
+                        }
</ins><span class="cx">                 });
</span><span class="cx">         }
</span><ins>+        this.changed(true);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> // A container class for VCALENDAR objects
</span><span class="lines">@@ -849,7 +860,8 @@
</span><span class="cx">                 { &quot;cn&quot; : gSession.currentPrincipal.cn },
</span><span class="cx">                 &quot;cal-address&quot;
</span><span class="cx">         );
</span><del>-        vpoll.newProperty(
</del><ins>+        var vvoter = vpoll.newComponent(&quot;vvoter&quot;);
+        vvoter.newProperty(
</ins><span class="cx">                 &quot;voter&quot;,
</span><span class="cx">                 gSession.currentPrincipal.defaultAddress(),
</span><span class="cx">                 {
</span><span class="lines">@@ -895,43 +907,55 @@
</span><span class="cx"> // Add a new VEVENT to the VPOLL
</span><span class="cx"> CalendarPoll.prototype.addEvent = function(dtstart, dtend) {
</span><span class="cx">         this.changed(true);
</span><ins>+        var poll_item_id = this.data.components(&quot;vevent&quot;).length;
</ins><span class="cx">         var vevent = this.data.newComponent(&quot;vevent&quot;, true);
</span><span class="cx">         vevent.newProperty(&quot;dtstart&quot;, jcaldate.jsDateTojCal(dtstart), {}, &quot;date-time&quot;);
</span><span class="cx">         vevent.newProperty(&quot;dtend&quot;, jcaldate.jsDateTojCal(dtend), {}, &quot;date-time&quot;);
</span><span class="cx">         vevent.newProperty(&quot;summary&quot;, this.summary());
</span><del>-        vevent.newProperty(&quot;poll-item-id&quot;, (this.data.components(&quot;vevent&quot;).length).toString());
-        vevent.newProperty(
-                &quot;voter&quot;,
-                this.organizer(),
-                {&quot;response&quot; : &quot;80&quot;},
-                &quot;cal-address&quot;
-        );
</del><ins>+        vevent.newProperty(&quot;poll-item-id&quot;, poll_item_id, {}, &quot;integer&quot;);
+
+        var matches_vvoter = $.grep(this.data.components(&quot;vvoter&quot;), function(vvoter, index) {
+                return gSession.currentPrincipal.matchingAddress(vvoter.getPropertyValue(&quot;voter&quot;));
+        });
+
+        var vvoter = null;
+        if (matches_vvoter.length == 1) {
+                vvoter = matches_vvoter[0];
+        }
+        else {
+                vvoter = this.data.newComponent(&quot;vvoter&quot;);
+                vvoter.newProperty(&quot;voter&quot;, this.organizer(), {}, &quot;cal-address&quot;);
+        }
+        var vote = vvoter.newComponent(&quot;vote&quot;);
+        vote.newProperty(&quot;response&quot;, 80, {}, &quot;integer&quot;);
+        vote.newProperty(&quot;poll-item-id&quot;, poll_item_id, {}, &quot;integer&quot;);
</ins><span class="cx">         return new CalendarEvent(vevent, this);
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> // Get an array of voters in the VPOLL
</span><span class="cx"> CalendarPoll.prototype.voters = function() {
</span><span class="cx">         var this_vpoll = this;
</span><del>-        return $.map(this.data.properties(&quot;voter&quot;), function(voter) {
-                return new CalendarUser(voter, this_vpoll);
</del><ins>+        return $.map(this.data.components(&quot;vvoter&quot;), function(vvoter) {
+                return new CalendarUser(vvoter.getProperty(&quot;voter&quot;), this_vpoll);
</ins><span class="cx">         });
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> // Add a voter to the VPOLL
</span><span class="cx"> CalendarPoll.prototype.addVoter = function() {
</span><span class="cx">         this.changed(true);
</span><del>-        return new CalendarUser(this.data.newProperty(&quot;voter&quot;, &quot;&quot;, {}, &quot;cal-address&quot;), this);
</del><ins>+        var vvoter = this.data.newComponent(&quot;vvoter&quot;);
+        return new CalendarUser(vvoter.newProperty(&quot;voter&quot;, &quot;&quot;, {}, &quot;cal-address&quot;), this);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> // Mark current user as accepted
</span><span class="cx"> CalendarPoll.prototype.acceptInvite = function() {
</span><span class="cx">         if (!this.isOwned()) {
</span><del>-                var voters = $.grep(this.data.properties(&quot;voter&quot;), function(voter) {
-                        return gSession.currentPrincipal.matchingAddress(voter[3]);
-                });
-                $.each(voters, function(index, voter) {
-                        voter[1][&quot;partstat&quot;] = &quot;ACCEPTED&quot;;
-                        delete voter[1][&quot;rsvp&quot;];
</del><ins>+                $.each(this.data.components(&quot;vvoter&quot;), function(index, vvoter) {
+                        var voter = vvoter.getProperty(&quot;voter&quot;);
+                        if (gSession.currentPrincipal.matchingAddress(voter[3])) {
+                                voter[1][&quot;partstat&quot;] = &quot;ACCEPTED&quot;;
+                                delete voter[1][&quot;rsvp&quot;];
+                        }
</ins><span class="cx">                 })
</span><span class="cx">         }
</span><span class="cx"> }
</span><span class="lines">@@ -962,7 +986,8 @@
</span><span class="cx">         vevent.copyProperty(&quot;dtstart&quot;, this.data);
</span><span class="cx">         vevent.copyProperty(&quot;dtend&quot;, this.data);
</span><span class="cx">         vevent.copyProperty(&quot;organizer&quot;, vpoll.data);
</span><del>-        $.each(vpoll.data.properties(&quot;voter&quot;), function(index, voter) {
</del><ins>+        $.each(vpoll.data.components(&quot;vvoter&quot;), function(index, vvoter) {
+                var voter = vvoter.getProperty(&quot;voter&quot;);
</ins><span class="cx">                 var attendee = vevent.newProperty(
</span><span class="cx">                         &quot;attendee&quot;,
</span><span class="cx">                         voter[3],
</span></span></pre></div>
<a id="CalendarServertrunkrequirementsdevtxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/requirements-dev.txt (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/requirements-dev.txt        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/requirements-dev.txt        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -8,4 +8,4 @@
</span><span class="cx"> q
</span><span class="cx"> tl.eggdeps
</span><span class="cx"> --editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@13420#egg=CalDAVClientLibrary
</span><del>---editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@14244#egg=CalDAVTester
</del><ins>+--editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@14300#egg=CalDAVTester
</ins></span></pre></div>
<a id="CalendarServertrunkrequirementsstabletxt"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/requirements-stable.txt (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/requirements-stable.txt        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/requirements-stable.txt        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -68,7 +68,7 @@
</span><span class="cx"> 
</span><span class="cx">     -e svn+http://svn.calendarserver.org/repository/calendarserver/PyKerberos/trunk@13420#egg=kerberos
</span><span class="cx"> 
</span><del>-    -e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@14245#egg=pycalendar
</del><ins>+    -e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@14301#egg=pycalendar
</ins><span class="cx">     python-dateutil==1.5  # Note: v2.0+ is for Python 3
</span><span class="cx">     pytz==2014.10
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavicalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/ical.py (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/ical.py        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/twistedcaldav/ical.py        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -2296,6 +2296,25 @@
</span><span class="cx">         return ()
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getOrganizerProperties(self):
+        &quot;&quot;&quot;
+        Get the organizer value. Works on either a VCALENDAR or on a component.
+
+        @return: the string value of the Organizer property, or None
+        &quot;&quot;&quot;
+
+        # Extract appropriate sub-component if this is a VCALENDAR
+        if self.name() == &quot;VCALENDAR&quot;:
+            return [component.getOrganizerProperty() for component in self.subcomponents(ignore=True)]
+        else:
+            try:
+                return self.getProperty(&quot;ORGANIZER&quot;)
+            except InvalidICalendarDataError:
+                pass
+
+        return None
+
+
</ins><span class="cx">     def getOrganizerProperty(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get the organizer value. Works on either a VCALENDAR or on a component.
</span><span class="lines">@@ -2349,28 +2368,44 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def recipientPropertyName(self):
</span><del>-        return &quot;VOTER&quot; if self.name() == &quot;VPOLL&quot; else &quot;ATTENDEE&quot;
</del><ins>+        return &quot;VOTER&quot; if self.name() in (&quot;VPOLL&quot;, &quot;VVOTER&quot;,) else &quot;ATTENDEE&quot;
</ins><span class="cx"> 
</span><span class="cx"> 
</span><del>-    def getAttendees(self):
</del><ins>+    def getRecipientProperties(self):
</ins><span class="cx">         &quot;&quot;&quot;
</span><del>-        Get the attendee value. Works on either a VCALENDAR or on a component.
</del><ins>+        Get the attendee properties. Works on either a VCALENDAR or on a component.
</ins><span class="cx"> 
</span><del>-        @param match: a C{list} of calendar user address strings to try and match.
-        @return: a C{list} of the string values of the Attendee property, or None
</del><ins>+        @return: a C{list} of the the Attendee properties
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         # Extract appropriate sub-component if this is a VCALENDAR
</span><span class="cx">         if self.name() == &quot;VCALENDAR&quot;:
</span><span class="cx">             for component in self.subcomponents(ignore=True):
</span><del>-                return component.getAttendees()
</del><ins>+                return component.getRecipientProperties()
</ins><span class="cx">         else:
</span><span class="cx">             # Find the property values
</span><del>-            return [p.value() for p in self.properties(self.recipientPropertyName())]
</del><ins>+            if self.name() == &quot;VPOLL&quot;:
+                results = []
+                for c in self.subcomponents():
+                    if c.name() == &quot;VVOTER&quot;:
+                        results.extend(c.properties(self.recipientPropertyName()))
+                return results
+            else:
+                return list(self.properties(self.recipientPropertyName()))
</ins><span class="cx"> 
</span><span class="cx">         return None
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def getAttendees(self):
+        &quot;&quot;&quot;
+        Get the attendee value. Works on either a VCALENDAR or on a component.
+
+        @return: a C{list} of the string values of the Attendee property, or None
+        &quot;&quot;&quot;
+
+        return [p.value() for p in self.getRecipientProperties()]
+
+
</ins><span class="cx">     def getAttendeesByInstance(self, makeUnique=False, onlyScheduleAgentServer=False):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Get the attendee values for each instance. Optionally remove duplicates.
</span><span class="lines">@@ -2392,7 +2427,7 @@
</span><span class="cx">             result = ()
</span><span class="cx">             attendees = set()
</span><span class="cx">             rid = self.getRecurrenceIDUTC()
</span><del>-            for attendee in tuple(self.properties(self.recipientPropertyName())):
</del><ins>+            for attendee in tuple(self.getRecipientProperties()):
</ins><span class="cx"> 
</span><span class="cx">                 if onlyScheduleAgentServer:
</span><span class="cx">                     if attendee.hasParameter(&quot;SCHEDULE-AGENT&quot;):
</span><span class="lines">@@ -2450,7 +2485,7 @@
</span><span class="cx">                     return attendee
</span><span class="cx">         else:
</span><span class="cx">             # Find the primary subcomponent
</span><del>-            for attendee in self.properties(self.recipientPropertyName()):
</del><ins>+            for attendee in self.getRecipientProperties():
</ins><span class="cx">                 if normalizeCUAddr(attendee.value()) in test:
</span><span class="cx">                     return attendee
</span><span class="cx"> 
</span><span class="lines">@@ -2491,7 +2526,7 @@
</span><span class="cx">                     yield attendee
</span><span class="cx">         else:
</span><span class="cx">             # Find the primary subcomponent
</span><del>-            for attendee in self.properties(self.recipientPropertyName()):
</del><ins>+            for attendee in self.getRecipientProperties():
</ins><span class="cx">                 yield attendee
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -2845,6 +2880,33 @@
</span><span class="cx">                 master_component.addProperty(Property(&quot;EXDATE&quot;, [exdate, ]))
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def voterComponentForVoter(self, voter):
+        &quot;&quot;&quot;
+        Find the VVOTER subcomponent with a VOTER property matching the specified attendee (voter).
+
+        @param voter: the calendar user address of the attendee (voter) to match
+        @type voter: L{str}
+        &quot;&quot;&quot;
+        for voterComponent in tuple(self.subcomponents(ignore=True)):
+            if voterComponent.name() == &quot;VVOTER&quot; and voterComponent.getVoterProperty((voter,)) is not None:
+                return voterComponent
+        else:
+            return None
+
+
+    def voteMap(self):
+        &quot;&quot;&quot;
+        Get a dict mapping each VOTE component POLL-ITEM-ID to the VOTE component.
+        &quot;&quot;&quot;
+        results = {}
+        for component in self.subcomponents():
+            if component.name() == &quot;VOTE&quot;:
+                poll_id = component.propertyValue(&quot;POLL-ITEM-ID&quot;)
+                if poll_id is not None:
+                    results[poll_id] = component
+        return results
+
+
</ins><span class="cx">     def filterComponents(self, rids):
</span><span class="cx"> 
</span><span class="cx">         # If master is in rids do nothing
</span><span class="lines">@@ -2873,7 +2935,15 @@
</span><span class="cx">         assert self.name() == &quot;VCALENDAR&quot;, &quot;Not a calendar: {0!r}&quot;.format(self,)
</span><span class="cx"> 
</span><span class="cx">         for component in self.subcomponents(ignore=True):
</span><del>-            [component.removeProperty(p) for p in tuple(component.properties(component.recipientPropertyName())) if p.value().lower() != attendee.lower()]
</del><ins>+            if component.name() == &quot;VPOLL&quot;:
+                for vvoter in tuple(self.subcomponents()):
+                    if vvoter.name() == &quot;VVOTER&quot;:
+                        if vvoter.propertyValue(component.recipientPropertyName()).lower() != attendee.lower():
+                            component.removeComponent(vvoter)
+            else:
+                for p in tuple(component.properties(component.recipientPropertyName())):
+                    if p.value().lower() != attendee.lower():
+                        component.removeProperty(p)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def removeAllButTheseAttendees(self, attendees):
</span><span class="lines">@@ -2886,7 +2956,15 @@
</span><span class="cx">         attendees = set([attendee.lower() for attendee in attendees])
</span><span class="cx"> 
</span><span class="cx">         for component in self.subcomponents(ignore=True):
</span><del>-            [component.removeProperty(p) for p in tuple(component.properties(component.recipientPropertyName())) if p.value().lower() not in attendees]
</del><ins>+            if component.name() == &quot;VPOLL&quot;:
+                for vvoter in tuple(self.subcomponents()):
+                    if vvoter.name() == &quot;VVOTER&quot;:
+                        if vvoter.propertyValue(component.recipientPropertyName()).lower() not in attendees:
+                            component.removeComponent(vvoter)
+            else:
+                for p in tuple(component.properties(component.recipientPropertyName())):
+                    if p.value().lower() not in attendees:
+                        component.removeProperty(p)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def hasAlarm(self):
</span><span class="lines">@@ -3059,6 +3137,9 @@
</span><span class="cx">             for prop in props:
</span><span class="cx">                 for param in params:
</span><span class="cx">                     prop.removeParameter(param)
</span><ins>+            if self.name() == &quot;VPOLL&quot;:
+                for component in self.subcomponents(ignore=True):
+                    component.removePropertyParameters(property, params)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def removePropertyParametersByValue(self, property, paramvalues):
</span><span class="lines">@@ -3074,6 +3155,9 @@
</span><span class="cx">             for prop in props:
</span><span class="cx">                 for param, value in paramvalues:
</span><span class="cx">                     prop.removeParameterValue(param, value)
</span><ins>+            if self.name() == &quot;VPOLL&quot;:
+                for component in self.subcomponents(ignore=True):
+                    component.removePropertyParametersByValue(property, paramvalues)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def getITIPInfo(self):
</span></span></pre></div>
<a id="CalendarServertrunktwistedcaldavsimpleresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/twistedcaldav/simpleresource.py (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/twistedcaldav/simpleresource.py        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/twistedcaldav/simpleresource.py        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -32,7 +32,6 @@
</span><span class="cx"> 
</span><span class="cx"> from twisted.internet.defer import succeed
</span><span class="cx"> 
</span><del>-from twistedcaldav.config import config
</del><span class="cx"> from twistedcaldav.resource import CalDAVResource
</span><span class="cx"> 
</span><span class="cx"> from txdav.xml import element as davxml
</span><span class="lines">@@ -105,7 +104,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def renderHTTP(self, request):
</span><del>-        return http.RedirectResponse(request.unparseURL(host=config.ServerHostName, **self._kwargs))
</del><ins>+        return http.RedirectResponse(request.unparseURL(host=request.host, **self._kwargs))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingicaldiffpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -593,36 +593,39 @@
</span><span class="cx"> 
</span><span class="cx">         changed = False
</span><span class="cx"> 
</span><del>-        # Get the VOTER properties in sub-components of the VPOLL as set by the attendee
-        poll_items = {}
-        for component in clientComponent.subcomponents():
-            poll_id = component.propertyValue(&quot;POLL-ITEM-ID&quot;)
-            if poll_id is not None:
-                poll_items[poll_id] = component.getVoterProperty((self.attendee,))
</del><ins>+        # Get the matching VVOTER component in each VPOLL
+        serverVoter = serverComponent.voterComponentForVoter(self.attendee)
+        clientVoter = clientComponent.voterComponentForVoter(self.attendee)
</ins><span class="cx"> 
</span><del>-        # Transfer attendee data with the master set
-        for component in serverComponent.subcomponents():
-            poll_id = component.propertyValue(&quot;POLL-ITEM-ID&quot;)
-            if poll_id is not None:
-                voter = component.getVoterProperty((self.attendee,))
-                attendee_voter = poll_items.get(poll_id)
-                if attendee_voter is None:
-                    if voter is not None:
-                        component.removeProperty(voter)
-                        changed = True
-                elif voter is None:
-                    component.addProperty(attendee_voter)
-                    changed = True
</del><ins>+        # Now get a map of each response
+        serverMap = serverVoter.voteMap()
+        clientMap = clientVoter.voteMap()
+
+        # Remove missing
+        for poll_id in set(serverMap.keys()) - set(clientMap.keys()):
+            serverVoter.removeComponent(serverMap[poll_id])
+            changed = True
+
+        # Add new ones
+        for poll_id in set(clientMap.keys()) - set(serverMap.keys()):
+            vote = clientMap[poll_id].duplicate()
+            vote.replaceProperty(Property(&quot;LAST-MODIFIED&quot;, DateTime.getNowUTC()))
+            serverVoter.addComponent(vote)
+            changed = True
+
+        # Look for response change
+        for poll_id in set(serverMap.keys()) &amp; set(clientMap.keys()):
+            server_vote = serverMap[poll_id]
+            client_vote = clientMap[poll_id]
+            server_response = server_vote.propertyValue(&quot;RESPONSE&quot;)
+            client_response = client_vote.propertyValue(&quot;RESPONSE&quot;)
+            if server_response != client_response:
+                if client_response is not None:
+                    server_vote.replaceProperty(Property(&quot;RESPONSE&quot;, client_response))
</ins><span class="cx">                 else:
</span><del>-                    for paramname in (&quot;RESPONSE&quot;,):
-                        paramvalue = attendee_voter.parameterValue(paramname)
-                        if paramvalue is None:
-                            voter.removeParameter(paramname)
-                            changed = True
-                        else:
-                            if paramvalue != voter.parameterValue(paramname):
-                                voter.setParameter(paramname, paramvalue)
-                                changed = True
</del><ins>+                    server_vote.removeProperty(&quot;RESPONSE&quot;)
+                server_vote.replaceProperty(Property(&quot;LAST-MODIFIED&quot;, DateTime.getNowUTC()))
+                changed = True
</ins><span class="cx"> 
</span><span class="cx">         return changed
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingimplicitpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -671,6 +671,8 @@
</span><span class="cx"> 
</span><span class="cx">         self.coerceOrganizerScheduleAgent()
</span><span class="cx"> 
</span><ins>+        partstatProcessing = self.calendar.mainType() != &quot;VPOLL&quot;
+
</ins><span class="cx">         # Check for a delete
</span><span class="cx">         if self.action == &quot;remove&quot;:
</span><span class="cx"> 
</span><span class="lines">@@ -692,8 +694,10 @@
</span><span class="cx">                 self.oldcalendar = (yield self.resource.componentForUser())
</span><span class="cx">             self.oldAttendeesByInstance = self.oldcalendar.getAttendeesByInstance(True, onlyScheduleAgentServer=True)
</span><span class="cx">             self.oldInstances = set(self.oldcalendar.getComponentInstances())
</span><del>-            self.coerceAttendeesPartstatOnModify()
</del><span class="cx"> 
</span><ins>+            if partstatProcessing:
+                self.coerceAttendeesPartstatOnModify()
+
</ins><span class="cx">             # Don't allow any SEQUENCE to decrease
</span><span class="cx">             if self.oldcalendar and (not queued or not config.Scheduling.Options.WorkQueues.Enabled):
</span><span class="cx">                 self.calendar.sequenceInSync(self.oldcalendar)
</span><span class="lines">@@ -737,10 +741,11 @@
</span><span class="cx">                                 # the PARTSTAT to NEEDS-ACTION.
</span><span class="cx">                                 # The organizer is automatically ACCEPTED to the event.
</span><span class="cx">                                 continue
</span><del>-                            if attendee.hasParameter(&quot;PARTSTAT&quot;):
-                                attendee.setParameter(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;)
-                            seq = comp.propertyValue(&quot;SEQUENCE&quot;, 0)
-                            attendee.setParameter(&quot;X-CALENDARSERVER-RESET-PARTSTAT&quot;, str(seq))
</del><ins>+                            if partstatProcessing:
+                                if attendee.hasParameter(&quot;PARTSTAT&quot;):
+                                    attendee.setParameter(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;)
+                                seq = comp.propertyValue(&quot;SEQUENCE&quot;, 0)
+                                attendee.setParameter(&quot;X-CALENDARSERVER-RESET-PARTSTAT&quot;, str(seq))
</ins><span class="cx"> 
</span><span class="cx">                     # Look for changes to a specific attendee within an instance
</span><span class="cx">                     for rid, attendees in needs_action_changed_rids.items():
</span><span class="lines">@@ -750,10 +755,11 @@
</span><span class="cx">                             if comp is not None:
</span><span class="cx">                                 self.calendar.addComponent(comp)
</span><span class="cx"> 
</span><del>-                        for attendee in comp.getAllAttendeeProperties():
-                            if attendee.value() in attendees:
-                                seq = comp.propertyValue(&quot;SEQUENCE&quot;, 0)
-                                attendee.setParameter(&quot;X-CALENDARSERVER-RESET-PARTSTAT&quot;, str(seq))
</del><ins>+                        if partstatProcessing:
+                            for attendee in comp.getAllAttendeeProperties():
+                                if attendee.value() in attendees:
+                                    seq = comp.propertyValue(&quot;SEQUENCE&quot;, 0)
+                                    attendee.setParameter(&quot;X-CALENDARSERVER-RESET-PARTSTAT&quot;, str(seq))
</ins><span class="cx">                 else:
</span><span class="cx">                     log.debug(&quot;Implicit - organizer '{organizer}' is splitting UID: '{uid}'&quot;, organizer=self.organizer, uid=self.uid)
</span><span class="cx"> 
</span><span class="lines">@@ -768,7 +774,8 @@
</span><span class="cx">         elif self.action == &quot;create&quot;:
</span><span class="cx">             if self.split_details is None:
</span><span class="cx">                 log.debug(&quot;Implicit - organizer '{organizer}' is creating UID: '{uid}'&quot;, organizer=self.organizer, uid=self.uid)
</span><del>-                self.coerceAttendeesPartstatOnCreate()
</del><ins>+                if partstatProcessing:
+                    self.coerceAttendeesPartstatOnCreate()
</ins><span class="cx"> 
</span><span class="cx">                 # We need to handle the case where an organizer &quot;restores&quot; a previously delete event that has a sequence
</span><span class="cx">                 # lower than the one used in the cancel that attendees may still have. In this case what we need to do
</span><span class="lines">@@ -784,14 +791,15 @@
</span><span class="cx">                 log.debug(&quot;Implicit - organizer '{organizer}' is creating a split UID: '{uid}'&quot;, organizer=self.organizer, uid=self.uid)
</span><span class="cx"> 
</span><span class="cx">         # Always set RSVP=TRUE for any NEEDS-ACTION
</span><del>-        for attendee in self.calendar.getAllAttendeeProperties():
-            if attendee.parameterValue(&quot;CUTYPE&quot;) != &quot;X-SERVER-GROUP&quot;:
-                if attendee.parameterValue(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;).upper() == &quot;NEEDS-ACTION&quot;:
-                    attendee.setParameter(&quot;RSVP&quot;, &quot;TRUE&quot;)
-            else:
-                # Always remove RSVP and PARTSTAT
-                attendee.removeParameter(&quot;RSVP&quot;)
-                attendee.removeParameter(&quot;PARTSTAT&quot;)
</del><ins>+        if partstatProcessing:
+            for attendee in self.calendar.getAllAttendeeProperties():
+                if attendee.parameterValue(&quot;CUTYPE&quot;) != &quot;X-SERVER-GROUP&quot;:
+                    if attendee.parameterValue(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;).upper() == &quot;NEEDS-ACTION&quot;:
+                        attendee.setParameter(&quot;RSVP&quot;, &quot;TRUE&quot;)
+                else:
+                    # Always remove RSVP and PARTSTAT
+                    attendee.removeParameter(&quot;RSVP&quot;)
+                    attendee.removeParameter(&quot;PARTSTAT&quot;)
</ins><span class="cx"> 
</span><span class="cx">         # If processing a queue item, actually execute the scheduling operations, else queue it.
</span><span class="cx">         # Note a split is always a queued execution, so we do not need to re-queue
</span><span class="lines">@@ -1229,6 +1237,9 @@
</span><span class="cx">             aggregated.setdefault(attendee, []).append(rid)
</span><span class="cx"> 
</span><span class="cx">         count = 0
</span><ins>+        recipientProperties = collections.defaultdict(list)
+        for p in self.calendar.getAllAttendeeProperties():
+            recipientProperties[p.value()].append(p)
</ins><span class="cx">         for attendee, rids in aggregated.iteritems():
</span><span class="cx"> 
</span><span class="cx">             # Don't send message back to the ORGANIZER
</span><span class="lines">@@ -1261,12 +1272,8 @@
</span><span class="cx"> 
</span><span class="cx">                 if queued:
</span><span class="cx">                     # Always make it look like scheduling succeeded when queuing
</span><del>-                    self.calendar.setParameterToValueForPropertyWithValue(
-                        &quot;SCHEDULE-STATUS&quot;,
-                        iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
-                        &quot;ATTENDEE&quot;,
-                        attendee,
-                    )
</del><ins>+                    for p in recipientProperties[attendee]:
+                        p.setParameter(&quot;SCHEDULE-STATUS&quot;, iTIPRequestStatus.MESSAGE_DELIVERED_CODE)
</ins><span class="cx">                 else:
</span><span class="cx">                     # Add split details if needed
</span><span class="cx">                     if self.split_details is not None:
</span><span class="lines">@@ -1299,6 +1306,9 @@
</span><span class="cx"> 
</span><span class="cx">         # Do one per attendee
</span><span class="cx">         count = 0
</span><ins>+        recipientProperties = collections.defaultdict(list)
+        for p in self.calendar.getAllAttendeeProperties():
+            recipientProperties[p.value()].append(p)
</ins><span class="cx">         for attendee in self.attendees:
</span><span class="cx"> 
</span><span class="cx">             # Don't send message back to the ORGANIZER
</span><span class="lines">@@ -1327,10 +1337,8 @@
</span><span class="cx">             # Do not schedule with groups - ever
</span><span class="cx">             if attendeeAddress.hosted() and attendeeAddress.getCUType() == &quot;GROUP&quot;:
</span><span class="cx">                 # Set SCHEDULE-STATUS to something appropriate
</span><del>-                self.calendar.setParametersForPropertyWithValue(
-                    {&quot;SCHEDULE-STATUS&quot;: iTIPRequestStatus.REQUEST_FORWARDED_CODE if config.GroupAttendees.Enabled else iTIPRequestStatus.NO_USER_SUPPORT_CODE},
-                    &quot;ATTENDEE&quot;, attendee,
-                )
</del><ins>+                for p in recipientProperties[attendee]:
+                    p.setParameter(&quot;SCHEDULE-STATUS&quot;, iTIPRequestStatus.REQUEST_FORWARDED_CODE if config.GroupAttendees.Enabled else iTIPRequestStatus.NO_USER_SUPPORT_CODE)
</ins><span class="cx">                 continue
</span><span class="cx"> 
</span><span class="cx">             itipmsg = iTipGenerator.generateAttendeeRequest(self.calendar, (attendee,), self.changed_rids)
</span><span class="lines">@@ -1340,12 +1348,8 @@
</span><span class="cx"> 
</span><span class="cx">                 if queued:
</span><span class="cx">                     # Always make it look like scheduling succeeded when queuing
</span><del>-                    self.calendar.setParameterToValueForPropertyWithValue(
-                        &quot;SCHEDULE-STATUS&quot;,
-                        iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
-                        &quot;ATTENDEE&quot;,
-                        attendee,
-                    )
</del><ins>+                    for p in recipientProperties[attendee]:
+                        p.setParameter(&quot;SCHEDULE-STATUS&quot;, iTIPRequestStatus.MESSAGE_DELIVERED_CODE)
</ins><span class="cx">                 else:
</span><span class="cx">                     # Add split details if needed
</span><span class="cx">                     if self.split_details is not None:
</span><span class="lines">@@ -1411,20 +1415,19 @@
</span><span class="cx">             self.queuedResponses.append(response)
</span><span class="cx">         else:
</span><span class="cx">             # Map each recipient in the response to a status code
</span><ins>+            recipients = collections.defaultdict(list)
+            for p in self.calendar.getAllAttendeeProperties() if is_organizer else self.calendar.getOrganizerProperties():
+                recipients[p.value()].append(p)
+
</ins><span class="cx">             responses = {}
</span><del>-            propname = self.calendar.mainComponent().recipientPropertyName() if is_organizer else &quot;ORGANIZER&quot;
</del><span class="cx">             for item in response.responses:
</span><span class="cx">                 recipient = str(item.recipient.children[0])
</span><span class="cx">                 status = str(item.reqstatus)
</span><span class="cx">                 responses[recipient] = status
</span><span class="cx"> 
</span><span class="cx">                 # Now apply to each ATTENDEE/ORGANIZER in the original data
</span><del>-                self.calendar.setParameterToValueForPropertyWithValue(
-                    &quot;SCHEDULE-STATUS&quot;,
-                    status.split(&quot;;&quot;)[0],
-                    propname,
-                    recipient,
-                )
</del><ins>+                for p in recipients[recipient]:
+                    p.setParameter(&quot;SCHEDULE-STATUS&quot;, status.split(&quot;;&quot;)[0])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingitippy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -485,7 +485,7 @@
</span><span class="cx">             reqstatus = &quot;2.0&quot;
</span><span class="cx"> 
</span><span class="cx">         # Get attendee in reply_component - there MUST be only one
</span><del>-        attendees = tuple(reply_component.properties(reply_component.recipientPropertyName()))
</del><ins>+        attendees = tuple(reply_component.getRecipientProperties())
</ins><span class="cx">         if len(attendees) != 1:
</span><span class="cx">             log.error(&quot;There must be one and only one ATTENDEE property in a REPLY\n%s&quot; % (str(reply_component),))
</span><span class="cx">             return None, False, False
</span><span class="lines">@@ -500,17 +500,18 @@
</span><span class="cx"> 
</span><span class="cx">         # Only process the change for this component if it was made after the last partstat reset
</span><span class="cx">         if existing_attendee and reply_sequence &gt;= existing_reset_sequence:
</span><del>-            oldpartstat = existing_attendee.parameterValue(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;)
-            existing_attendee.setParameter(&quot;PARTSTAT&quot;, partstat)
-            existing_attendee.setParameter(&quot;SCHEDULE-STATUS&quot;, reqstatus)
-            partstat_changed = (oldpartstat != partstat)
</del><ins>+            if existing_attendee.name() == &quot;ATTENDEE&quot;:
+                oldpartstat = existing_attendee.parameterValue(&quot;PARTSTAT&quot;, &quot;NEEDS-ACTION&quot;)
+                existing_attendee.setParameter(&quot;PARTSTAT&quot;, partstat)
+                existing_attendee.setParameter(&quot;SCHEDULE-STATUS&quot;, reqstatus)
+                partstat_changed = (oldpartstat != partstat)
</ins><span class="cx"> 
</span><del>-            # Always delete RSVP on PARTSTAT change
-            if partstat_changed:
-                try:
-                    existing_attendee.removeParameter(&quot;RSVP&quot;)
-                except KeyError:
-                    pass
</del><ins>+                # Always delete RSVP on PARTSTAT change
+                if partstat_changed:
+                    try:
+                        existing_attendee.removeParameter(&quot;RSVP&quot;)
+                    except KeyError:
+                        pass
</ins><span class="cx"> 
</span><span class="cx">             # Handle attendee comments
</span><span class="cx">             if config.Scheduling.CalDAV.get(&quot;EnablePrivateComments&quot;, True):
</span><span class="lines">@@ -583,7 +584,8 @@
</span><span class="cx">     @staticmethod
</span><span class="cx">     def updateVPOLLDataFromReply(reply_component, organizer_component, attendee):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Update VPOLL sub-components with voter's response.
</del><ins>+        Update VPOLL sub-components with voter's response. Just replace the organizer's
+        VVOTER component for the replying attendee (voter) with the one in the replyVoter.
</ins><span class="cx"> 
</span><span class="cx">         @param reply_component: component to copy from
</span><span class="cx">         @type reply_component: L{Component}
</span><span class="lines">@@ -593,29 +595,47 @@
</span><span class="cx">         @type attendee: L{Property}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        responses = {}
-        for prop in reply_component.properties(&quot;POLL-ITEM-ID&quot;):
-            responses[prop.value()] = prop
</del><ins>+        # Get REQUEST-STATUS as we need to write that into the saved ATTENDEE property
+        reqstatus = tuple(reply_component.properties(&quot;REQUEST-STATUS&quot;))
+        if reqstatus:
+            reqstatus = &quot;,&quot;.join(status.value()[0] for status in reqstatus)
+        else:
+            reqstatus = &quot;2.0&quot;
</ins><span class="cx"> 
</span><del>-        for component in organizer_component.subcomponents(ignore=True):
-            poll_item_id = component.propertyValue(&quot;POLL-ITEM-ID&quot;)
-            if poll_item_id is None:
-                continue
-            voter = component.getVoterProperty((attendee.value(),))
</del><ins>+        # Get the matching VVOTER component in each VPOLL
+        replyVoter = reply_component.voterComponentForVoter(attendee.value())
+        organizerVoter = organizer_component.voterComponentForVoter(attendee.value())
</ins><span class="cx"> 
</span><del>-            # If no response - remove
-            if poll_item_id not in responses or not responses[poll_item_id].hasParameter(&quot;RESPONSE&quot;):
-                if voter is not None:
-                    component.removeProperty(voter)
-                continue
</del><ins>+        if replyVoter is None:
+            return
</ins><span class="cx"> 
</span><del>-            # Add or update voter
-            if voter is None:
-                voter = Property(&quot;VOTER&quot;, attendee.value())
-                component.addProperty(voter)
-            voter.setParameter(&quot;RESPONSE&quot;, responses[poll_item_id].parameterValue(&quot;RESPONSE&quot;))
</del><ins>+        if organizerVoter is None:
+            # Add in the new one
+            organizerVoter = replyVoter.duplicate()
+            reply_component.addComponent(organizerVoter)
+        else:
+            # Merge each vote
+            replyMap = replyVoter.voteMap()
+            organizerMap = organizerVoter.voteMap()
</ins><span class="cx"> 
</span><ins>+            # Add new ones
+            for vote in set(replyMap.keys()) - set(organizerMap.keys()):
+                organizerVoter.addComponent(replyMap[vote].duplicate())
</ins><span class="cx"> 
</span><ins>+            # Replace existing ones
+            for vote in set(replyMap.keys()) &amp; set(organizerMap.keys()):
+                organizerVoter.removeComponent(organizerMap[vote])
+                organizerVoter.addComponent(replyMap[vote].duplicate())
+
+        # Update VOTER property
+        existing_voter = organizerVoter.getProperty(&quot;VOTER&quot;)
+        existing_voter.setParameter(&quot;SCHEDULE-STATUS&quot;, reqstatus)
+        try:
+            existing_voter.removeParameter(&quot;RSVP&quot;)
+        except KeyError:
+            pass
+
+
</ins><span class="cx">     @staticmethod
</span><span class="cx">     def transferItems(from_calendar, to_component, needs_action_rids, reschedule, master_details, remove_matched=False):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -1068,22 +1088,18 @@
</span><span class="cx">     @staticmethod
</span><span class="cx">     def generateVPOLLReply(vpoll, attendee):
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        Generate the proper poll response in a reply for each component being voted on.
</del><ins>+        Generate the proper poll response in a reply by removing all sub-components
+        except fore the VVOTER matching the attendee (voter) replying.
</ins><span class="cx"> 
</span><span class="cx">         @param vpoll: the VPOLL component to process
</span><span class="cx">         @type vpoll: L{Component}
</span><del>-        @param attendee: calendar user address of attendee replying
</del><ins>+        @param attendee: calendar user address of attendee (voter) replying
</ins><span class="cx">         @type attendee: C{str}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         for component in tuple(vpoll.subcomponents(ignore=True)):
</span><del>-            poll_item_id = component.propertyValue(&quot;POLL-ITEM-ID&quot;)
-            if poll_item_id is None:
-                continue
-            voter = component.getVoterProperty((attendee,))
-            if voter is not None and voter.hasParameter(&quot;RESPONSE&quot;):
-                vpoll.addProperty(Property(&quot;POLL-ITEM-ID&quot;, poll_item_id, {&quot;RESPONSE&quot;: voter.parameterValue(&quot;RESPONSE&quot;)}))
-            vpoll.removeComponent(component)
</del><ins>+            if component.name() != &quot;VVOTER&quot; or component.getVoterProperty((attendee,)) is None:
+                vpoll.removeComponent(component)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @staticmethod
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingtesttest_icaldiffpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -6615,3 +6615,740 @@
</span><span class="cx">             strcal2 = str(cal2)
</span><span class="cx">             strchanged = str(Component.fromString(changed_calendar))
</span><span class="cx">             self.assertEqual(strchanged, strcal2, msg=&quot;%s mismatch:\n%s&quot; % (description, &quot;\n&quot;.join(unified_diff(strchanged.split(&quot;\n&quot;), strcal2.split(&quot;\n&quot;)))))
</span><ins>+
+
+    def test_attendee_merge_vpoll(self):
+
+        data = (
+            (
+                &quot;#1.1 Simple component, no change&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;mailto:user2@example.com&quot;,
+                (True, False, (), &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2@example.com
+DTSTAMP:20150113T152404Z
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;)
+            ),
+            (
+                &quot;#1.2 Simple component, response added&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;mailto:user2@example.com&quot;,
+                (True, True, (None,), &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2@example.com
+DTSTAMP:20150113T152404Z
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:XXXXXXXXTXXXXXXZ
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;)
+            ),
+            (
+                &quot;#1.3 Simple component, response changed&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:20150113T152404Z
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:50
+LAST-MODIFIED:20150113T152404Z
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;mailto:user2@example.com&quot;,
+                (True, True, (None,), &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2@example.com
+DTSTAMP:20150113T152404Z
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:50
+LAST-MODIFIED:XXXXXXXXTXXXXXXZ
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;)
+            ),
+            (
+                &quot;#1.4 Simple component, response removed&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:20150113T152404Z
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;mailto:user2@example.com&quot;,
+                (True, True, (None,), &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2@example.com
+DTSTAMP:20150113T152404Z
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;)
+            ),
+            (
+                &quot;#1.5 Simple component, response unchanged&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:20150113T152404Z
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:20150113T152404Z
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;mailto:user2@example.com&quot;,
+                (True, False, (), &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2@example.com
+DTSTAMP:20150113T152404Z
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:XXXXXXXXTXXXXXXZ
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;)
+            ),
+            (
+                &quot;#1.6 Simple component, bad changed&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:20150113T152404Z
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 02&quot;:mailto:user2@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:20150113T152404Z
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;mailto:user2@example.com&quot;,
+                (True, False, (), &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2@example.com
+DTSTAMP:20150113T152404Z
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+LAST-MODIFIED:XXXXXXXXTXXXXXXZ
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2@example.com
+ORGANIZER;CN=User 01:mailto:user1@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;)
+            ),
+        )
+
+        for description, calendar1, calendar2, attendee, result in data:
+            differ = iCalDiff(Component.fromString(calendar1), Component.fromString(calendar2), False)
+            diffResult = differ.attendeeMerge(attendee)
+            diffResult = (
+                diffResult[0],
+                diffResult[1],
+                tuple(sorted(diffResult[2])),
+                re.sub(
+                    &quot;LAST-MODIFIED:.*&quot;,
+                    &quot;LAST-MODIFIED:XXXXXXXXTXXXXXXZ&quot;,
+                    str(diffResult[3]).replace(&quot;\r&quot;, &quot;&quot;).replace(&quot;\n &quot;, &quot;&quot;)
+                ) if diffResult[3] else None,
+            )
+            result = list(result)
+            result[2] = tuple([(DateTime.parseText(dt) if dt else None) for dt in result[2]])
+            result = tuple(result)
+            self.assertEqual(diffResult, result, msg=&quot;%s: actual result: (%s)&quot; % (description, &quot;, &quot;.join([str(i).replace(&quot;\r&quot;, &quot;&quot;) for i in diffResult]),))
</ins></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingtesttest_itippy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -1775,6 +1775,434 @@
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><span class="cx">             ),
</span><ins>+            (
+                &quot;3.1 Simple VPOLL Reply - response added&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+END:VVOTER
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;SCHEDULE-STATUS=2.0:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                True,
+            ),
+            (
+                &quot;3.2 Simple VPOLL Reply - response changed&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:50
+END:VOTE
+END:VVOTER
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;SCHEDULE-STATUS=2.0:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:50
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                True,
+            ),
+            (
+                &quot;3.3 Simple VPOLL Reply - response added and changed&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:50
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;SCHEDULE-STATUS=2.0:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:50
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                True,
+            ),
+            (
+                &quot;3.4 Simple VPOLL Reply - response one changed&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:50
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:REPLY
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:0
+END:VOTE
+END:VVOTER
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VPOLL
+UID:8D2B2048-7915-6ECD-A82B-01F4EF8EEBEA
+DTSTAMP:20150113T152404Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;SCHEDULE-STATUS=2.0:mailto:user2@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:0
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN=&quot;User 01&quot;:mailto:user1@example.com
+ATTENDEE:mailto:user1@example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2@example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+&quot;&quot;&quot;,
+                True,
+            ),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         for title, calendar_txt, itip_txt, changed_txt, expected in data:
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingworkpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py (14301 => 14302)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py        2015-01-14 15:39:56 UTC (rev 14301)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py        2015-01-14 15:42:38 UTC (rev 14302)
</span><span class="lines">@@ -32,6 +32,7 @@
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema, \
</span><span class="cx">     scheduleActionToSQL, scheduleActionFromSQL
</span><span class="cx"> 
</span><ins>+import collections
</ins><span class="cx"> import datetime
</span><span class="cx"> import hashlib
</span><span class="cx"> import traceback
</span><span class="lines">@@ -262,18 +263,19 @@
</span><span class="cx"> 
</span><span class="cx">         # Map each recipient in the response to a status code
</span><span class="cx">         changed = False
</span><del>-        propname = calendar.mainComponent().recipientPropertyName() if is_organizer else &quot;ORGANIZER&quot;
</del><ins>+        recipients = collections.defaultdict(list)
+        for p in calendar.getAllAttendeeProperties() if is_organizer else calendar.getOrganizerProperties():
+            recipients[p.value()].append(p)
+
</ins><span class="cx">         for recipient, statusCode in response:
</span><span class="cx">             # Now apply to each ATTENDEE/ORGANIZER in the original data only if not 1.2
</span><span class="cx">             if statusCode != iTIPRequestStatus.MESSAGE_DELIVERED_CODE:
</span><del>-                calendar.setParameterToValueForPropertyWithValue(
-                    &quot;SCHEDULE-STATUS&quot;,
-                    statusCode,
-                    propname,
-                    recipient,
-                )
-                changed = True
</del><span class="cx"> 
</span><ins>+                # Now apply to each ATTENDEE/ORGANIZER in the original data
+                for p in recipients[recipient]:
+                    p.setParameter(&quot;SCHEDULE-STATUS&quot;, statusCode)
+                    changed = True
+
</ins><span class="cx">         return changed
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>