<!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"> <head>
</span><span class="cx">         <meta charset="utf-8">
</span><span class="cx">         <title>WebPoll</title>
</span><del>-        <link href="css/cupertino/jquery-ui.css" rel="stylesheet">
-        <link href="css/datetimepicker.css" rel="stylesheet">
</del><ins>+
+        <!-- 3rd party -->
+        <link href="css/3rdparty/cupertino/jquery-ui.css" rel="stylesheet">
+        <link href="css/3rdparty/datetimepicker.css" rel="stylesheet">
+        
+        <script src="js/3rdparty/jquery-2.1.3.js"></script>
+        <script src="js/3rdparty/jquery-ui-1.11.2.js"></script>
+        <script src="js/3rdparty/datetimepicker.js"></script>
+        <script src="js/3rdparty/json2.js"></script>
+        
+        <!-- Ours -->
</ins><span class="cx">         <link href="css/webpoll.css" rel="stylesheet">
</span><del>-        <script src="js/jquery-2.0.3.js"></script>
-        <script src="js/jquery-ui-1.10.3.js"></script>
-        <script src="js/datetimepicker.js"></script>
-        <script src="js/json2.js"></script>
</del><ins>+
</ins><span class="cx">         <script src="js/utils.js"></script>
</span><span class="cx">         <script src="js/jcal.js"></script>
</span><span class="cx">         <script src="js/caldav.js"></script>
</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("voter"), function(index, voter) {
-                voter_results[voter[3]] = parseInt(voter[1]["response"]);
</del><ins>+        var pollitemid = this.pollitemid();
+        $.each(this.parent.data.components("vvoter"), function(index, vvoter) {
+                var voter = vvoter.getPropertyValue("voter")
+                $.each(vvoter.components("vote"), function(index, vote) {
+                        if (vote.getPropertyValue("poll-item-id") == pollitemid) {
+                                voter_results[voter] = vote.getPropertyValue("response");
+                                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("vvoter"), function(vvoter, index) {
+                return gSession.currentPrincipal.matchingAddress(vvoter.getPropertyValue("voter"));
+        });
+        var pollitemid = this.pollitemid();
</ins><span class="cx">         if (response !== null) {
</span><del>-                var matches = $.grep(this.data.properties("voter"), function(voter, index) {
-                        return gSession.currentPrincipal.matchingAddress(voter[3]);
</del><ins>+                var matches_vote = $.grep(matches_vvoter[0].components("vote"), function(vote, index) {
+                        return vote.getPropertyValue("poll-item-id") == 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("response")[3] = response;
</ins><span class="cx">                 } else {
</span><del>-                        this.data.newProperty(
-                                "voter",
-                                gSession.currentPrincipal.defaultAddress(),
-                                { "response" : response.toString() },
-                                "cal-address"
-                        );
-                        this.changed(true);
</del><ins>+                        var vote = matches_vvoter[0].newComponent("vote");
+                        vote.newProperty("response", response, {}, "integer");
+                        vote.newProperty("poll-item-id", pollitemid, {}, "integer");
</ins><span class="cx">                 }
</span><span class="cx">         } else {
</span><del>-                this.data.removePropertiesMatchingValue(function(propdata) {
-                        return propdata[0] == "voter" && gSession.currentPrincipal.matchingAddress(propdata[3]);
</del><ins>+                $.each(matches_vvoter[0].components("vote"), function(index, vote) {
+                        if (vote.getPropertyValue("poll-item-id") == 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">                 { "cn" : gSession.currentPrincipal.cn },
</span><span class="cx">                 "cal-address"
</span><span class="cx">         );
</span><del>-        vpoll.newProperty(
</del><ins>+        var vvoter = vpoll.newComponent("vvoter");
+        vvoter.newProperty(
</ins><span class="cx">                 "voter",
</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("vevent").length;
</ins><span class="cx">         var vevent = this.data.newComponent("vevent", true);
</span><span class="cx">         vevent.newProperty("dtstart", jcaldate.jsDateTojCal(dtstart), {}, "date-time");
</span><span class="cx">         vevent.newProperty("dtend", jcaldate.jsDateTojCal(dtend), {}, "date-time");
</span><span class="cx">         vevent.newProperty("summary", this.summary());
</span><del>-        vevent.newProperty("poll-item-id", (this.data.components("vevent").length).toString());
-        vevent.newProperty(
-                "voter",
-                this.organizer(),
-                {"response" : "80"},
-                "cal-address"
-        );
</del><ins>+        vevent.newProperty("poll-item-id", poll_item_id, {}, "integer");
+
+        var matches_vvoter = $.grep(this.data.components("vvoter"), function(vvoter, index) {
+                return gSession.currentPrincipal.matchingAddress(vvoter.getPropertyValue("voter"));
+        });
+
+        var vvoter = null;
+        if (matches_vvoter.length == 1) {
+                vvoter = matches_vvoter[0];
+        }
+        else {
+                vvoter = this.data.newComponent("vvoter");
+                vvoter.newProperty("voter", this.organizer(), {}, "cal-address");
+        }
+        var vote = vvoter.newComponent("vote");
+        vote.newProperty("response", 80, {}, "integer");
+        vote.newProperty("poll-item-id", poll_item_id, {}, "integer");
</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("voter"), function(voter) {
-                return new CalendarUser(voter, this_vpoll);
</del><ins>+        return $.map(this.data.components("vvoter"), function(vvoter) {
+                return new CalendarUser(vvoter.getProperty("voter"), 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("voter", "", {}, "cal-address"), this);
</del><ins>+        var vvoter = this.data.newComponent("vvoter");
+        return new CalendarUser(vvoter.newProperty("voter", "", {}, "cal-address"), 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("voter"), function(voter) {
-                        return gSession.currentPrincipal.matchingAddress(voter[3]);
-                });
-                $.each(voters, function(index, voter) {
-                        voter[1]["partstat"] = "ACCEPTED";
-                        delete voter[1]["rsvp"];
</del><ins>+                $.each(this.data.components("vvoter"), function(index, vvoter) {
+                        var voter = vvoter.getProperty("voter");
+                        if (gSession.currentPrincipal.matchingAddress(voter[3])) {
+                                voter[1]["partstat"] = "ACCEPTED";
+                                delete voter[1]["rsvp"];
+                        }
</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("dtstart", this.data);
</span><span class="cx">         vevent.copyProperty("dtend", this.data);
</span><span class="cx">         vevent.copyProperty("organizer", vpoll.data);
</span><del>-        $.each(vpoll.data.properties("voter"), function(index, voter) {
</del><ins>+        $.each(vpoll.data.components("vvoter"), function(index, vvoter) {
+                var voter = vvoter.getProperty("voter");
</ins><span class="cx">                 var attendee = vevent.newProperty(
</span><span class="cx">                         "attendee",
</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):
+ """
+ Get the organizer value. Works on either a VCALENDAR or on a component.
+
+ @return: the string value of the Organizer property, or None
+ """
+
+ # Extract appropriate sub-component if this is a VCALENDAR
+ if self.name() == "VCALENDAR":
+ return [component.getOrganizerProperty() for component in self.subcomponents(ignore=True)]
+ else:
+ try:
+ return self.getProperty("ORGANIZER")
+ except InvalidICalendarDataError:
+ pass
+
+ return None
+
+
</ins><span class="cx"> def getOrganizerProperty(self):
</span><span class="cx"> """
</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 "VOTER" if self.name() == "VPOLL" else "ATTENDEE"
</del><ins>+ return "VOTER" if self.name() in ("VPOLL", "VVOTER",) else "ATTENDEE"
</ins><span class="cx">
</span><span class="cx">
</span><del>- def getAttendees(self):
</del><ins>+ def getRecipientProperties(self):
</ins><span class="cx"> """
</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"> """
</span><span class="cx">
</span><span class="cx"> # Extract appropriate sub-component if this is a VCALENDAR
</span><span class="cx"> if self.name() == "VCALENDAR":
</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() == "VPOLL":
+ results = []
+ for c in self.subcomponents():
+ if c.name() == "VVOTER":
+ 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):
+ """
+ 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
+ """
+
+ return [p.value() for p in self.getRecipientProperties()]
+
+
</ins><span class="cx"> def getAttendeesByInstance(self, makeUnique=False, onlyScheduleAgentServer=False):
</span><span class="cx"> """
</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("SCHEDULE-AGENT"):
</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("EXDATE", [exdate, ]))
</span><span class="cx">
</span><span class="cx">
</span><ins>+ def voterComponentForVoter(self, voter):
+ """
+ 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}
+ """
+ for voterComponent in tuple(self.subcomponents(ignore=True)):
+ if voterComponent.name() == "VVOTER" and voterComponent.getVoterProperty((voter,)) is not None:
+ return voterComponent
+ else:
+ return None
+
+
+ def voteMap(self):
+ """
+ Get a dict mapping each VOTE component POLL-ITEM-ID to the VOTE component.
+ """
+ results = {}
+ for component in self.subcomponents():
+ if component.name() == "VOTE":
+ poll_id = component.propertyValue("POLL-ITEM-ID")
+ 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() == "VCALENDAR", "Not a calendar: {0!r}".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() == "VPOLL":
+ for vvoter in tuple(self.subcomponents()):
+ if vvoter.name() == "VVOTER":
+ 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() == "VPOLL":
+ for vvoter in tuple(self.subcomponents()):
+ if vvoter.name() == "VVOTER":
+ 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() == "VPOLL":
+ 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() == "VPOLL":
+ 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("POLL-ITEM-ID")
- 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("POLL-ITEM-ID")
- 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("LAST-MODIFIED", DateTime.getNowUTC()))
+ serverVoter.addComponent(vote)
+ changed = True
+
+ # Look for response change
+ for poll_id in set(serverMap.keys()) & set(clientMap.keys()):
+ server_vote = serverMap[poll_id]
+ client_vote = clientMap[poll_id]
+ server_response = server_vote.propertyValue("RESPONSE")
+ client_response = client_vote.propertyValue("RESPONSE")
+ if server_response != client_response:
+ if client_response is not None:
+ server_vote.replaceProperty(Property("RESPONSE", client_response))
</ins><span class="cx"> else:
</span><del>- for paramname in ("RESPONSE",):
- 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("RESPONSE")
+ server_vote.replaceProperty(Property("LAST-MODIFIED", 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() != "VPOLL"
+
</ins><span class="cx"> # Check for a delete
</span><span class="cx"> if self.action == "remove":
</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("PARTSTAT"):
- attendee.setParameter("PARTSTAT", "NEEDS-ACTION")
- seq = comp.propertyValue("SEQUENCE", 0)
- attendee.setParameter("X-CALENDARSERVER-RESET-PARTSTAT", str(seq))
</del><ins>+ if partstatProcessing:
+ if attendee.hasParameter("PARTSTAT"):
+ attendee.setParameter("PARTSTAT", "NEEDS-ACTION")
+ seq = comp.propertyValue("SEQUENCE", 0)
+ attendee.setParameter("X-CALENDARSERVER-RESET-PARTSTAT", 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("SEQUENCE", 0)
- attendee.setParameter("X-CALENDARSERVER-RESET-PARTSTAT", str(seq))
</del><ins>+ if partstatProcessing:
+ for attendee in comp.getAllAttendeeProperties():
+ if attendee.value() in attendees:
+ seq = comp.propertyValue("SEQUENCE", 0)
+ attendee.setParameter("X-CALENDARSERVER-RESET-PARTSTAT", str(seq))
</ins><span class="cx"> else:
</span><span class="cx"> log.debug("Implicit - organizer '{organizer}' is splitting UID: '{uid}'", 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 == "create":
</span><span class="cx"> if self.split_details is None:
</span><span class="cx"> log.debug("Implicit - organizer '{organizer}' is creating UID: '{uid}'", 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 "restores" 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("Implicit - organizer '{organizer}' is creating a split UID: '{uid}'", 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("CUTYPE") != "X-SERVER-GROUP":
- if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION").upper() == "NEEDS-ACTION":
- attendee.setParameter("RSVP", "TRUE")
- else:
- # Always remove RSVP and PARTSTAT
- attendee.removeParameter("RSVP")
- attendee.removeParameter("PARTSTAT")
</del><ins>+ if partstatProcessing:
+ for attendee in self.calendar.getAllAttendeeProperties():
+ if attendee.parameterValue("CUTYPE") != "X-SERVER-GROUP":
+ if attendee.parameterValue("PARTSTAT", "NEEDS-ACTION").upper() == "NEEDS-ACTION":
+ attendee.setParameter("RSVP", "TRUE")
+ else:
+ # Always remove RSVP and PARTSTAT
+ attendee.removeParameter("RSVP")
+ attendee.removeParameter("PARTSTAT")
</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(
- "SCHEDULE-STATUS",
- iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
- "ATTENDEE",
- attendee,
- )
</del><ins>+ for p in recipientProperties[attendee]:
+ p.setParameter("SCHEDULE-STATUS", 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() == "GROUP":
</span><span class="cx"> # Set SCHEDULE-STATUS to something appropriate
</span><del>- self.calendar.setParametersForPropertyWithValue(
- {"SCHEDULE-STATUS": iTIPRequestStatus.REQUEST_FORWARDED_CODE if config.GroupAttendees.Enabled else iTIPRequestStatus.NO_USER_SUPPORT_CODE},
- "ATTENDEE", attendee,
- )
</del><ins>+ for p in recipientProperties[attendee]:
+ p.setParameter("SCHEDULE-STATUS", 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(
- "SCHEDULE-STATUS",
- iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
- "ATTENDEE",
- attendee,
- )
</del><ins>+ for p in recipientProperties[attendee]:
+ p.setParameter("SCHEDULE-STATUS", 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 "ORGANIZER"
</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(
- "SCHEDULE-STATUS",
- status.split(";")[0],
- propname,
- recipient,
- )
</del><ins>+ for p in recipients[recipient]:
+ p.setParameter("SCHEDULE-STATUS", status.split(";")[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 = "2.0"
</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("There must be one and only one ATTENDEE property in a REPLY\n%s" % (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 >= existing_reset_sequence:
</span><del>- oldpartstat = existing_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
- existing_attendee.setParameter("PARTSTAT", partstat)
- existing_attendee.setParameter("SCHEDULE-STATUS", reqstatus)
- partstat_changed = (oldpartstat != partstat)
</del><ins>+ if existing_attendee.name() == "ATTENDEE":
+ oldpartstat = existing_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
+ existing_attendee.setParameter("PARTSTAT", partstat)
+ existing_attendee.setParameter("SCHEDULE-STATUS", reqstatus)
+ partstat_changed = (oldpartstat != partstat)
</ins><span class="cx">
</span><del>- # Always delete RSVP on PARTSTAT change
- if partstat_changed:
- try:
- existing_attendee.removeParameter("RSVP")
- except KeyError:
- pass
</del><ins>+ # Always delete RSVP on PARTSTAT change
+ if partstat_changed:
+ try:
+ existing_attendee.removeParameter("RSVP")
+ except KeyError:
+ pass
</ins><span class="cx">
</span><span class="cx"> # Handle attendee comments
</span><span class="cx"> if config.Scheduling.CalDAV.get("EnablePrivateComments", 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"> """
</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"> """
</span><span class="cx">
</span><del>- responses = {}
- for prop in reply_component.properties("POLL-ITEM-ID"):
- 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("REQUEST-STATUS"))
+ if reqstatus:
+ reqstatus = ",".join(status.value()[0] for status in reqstatus)
+ else:
+ reqstatus = "2.0"
</ins><span class="cx">
</span><del>- for component in organizer_component.subcomponents(ignore=True):
- poll_item_id = component.propertyValue("POLL-ITEM-ID")
- 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("RESPONSE"):
- 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("VOTER", attendee.value())
- component.addProperty(voter)
- voter.setParameter("RESPONSE", responses[poll_item_id].parameterValue("RESPONSE"))
</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()) & set(organizerMap.keys()):
+ organizerVoter.removeComponent(organizerMap[vote])
+ organizerVoter.addComponent(replyMap[vote].duplicate())
+
+ # Update VOTER property
+ existing_voter = organizerVoter.getProperty("VOTER")
+ existing_voter.setParameter("SCHEDULE-STATUS", reqstatus)
+ try:
+ existing_voter.removeParameter("RSVP")
+ 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"> """
</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"> """
</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"> """
</span><span class="cx">
</span><span class="cx"> for component in tuple(vpoll.subcomponents(ignore=True)):
</span><del>- poll_item_id = component.propertyValue("POLL-ITEM-ID")
- if poll_item_id is None:
- continue
- voter = component.getVoterProperty((attendee,))
- if voter is not None and voter.hasParameter("RESPONSE"):
- vpoll.addProperty(Property("POLL-ITEM-ID", poll_item_id, {"RESPONSE": voter.parameterValue("RESPONSE")}))
- vpoll.removeComponent(component)
</del><ins>+ if component.name() != "VVOTER" 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="%s mismatch:\n%s" % (description, "\n".join(unified_diff(strchanged.split("\n"), strcal2.split("\n")))))
</span><ins>+
+
+ def test_attendee_merge_vpoll(self):
+
+ data = (
+ (
+ "#1.1 Simple component, no change",
+ """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
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":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="User 01":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
+""",
+ """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
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":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="User 01":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
+""",
+ "mailto:user2@example.com",
+ (True, False, (), """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
+""")
+ ),
+ (
+ "#1.2 Simple component, response added",
+ """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
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":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="User 01":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
+""",
+ """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
+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="User 01":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="User 01":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
+""",
+ "mailto:user2@example.com",
+ (True, True, (None,), """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
+""")
+ ),
+ (
+ "#1.3 Simple component, response changed",
+ """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
+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="User 01":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="User 01":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
+""",
+ """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
+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="User 01":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="User 01":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
+""",
+ "mailto:user2@example.com",
+ (True, True, (None,), """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
+""")
+ ),
+ (
+ "#1.4 Simple component, response removed",
+ """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
+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="User 01":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="User 01":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
+""",
+ """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
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":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="User 01":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
+""",
+ "mailto:user2@example.com",
+ (True, True, (None,), """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
+""")
+ ),
+ (
+ "#1.5 Simple component, response unchanged",
+ """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
+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="User 01":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="User 01":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
+""",
+ """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
+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="User 01":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="User 01":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
+""",
+ "mailto:user2@example.com",
+ (True, False, (), """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
+""")
+ ),
+ (
+ "#1.6 Simple component, bad changed",
+ """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
+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="User 01":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="User 01":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
+""",
+ """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 02":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="User 01":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="User 01":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
+""",
+ "mailto:user2@example.com",
+ (True, False, (), """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
+""")
+ ),
+ )
+
+ 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(
+ "LAST-MODIFIED:.*",
+ "LAST-MODIFIED:XXXXXXXXTXXXXXXZ",
+ str(diffResult[3]).replace("\r", "").replace("\n ", "")
+ ) 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="%s: actual result: (%s)" % (description, ", ".join([str(i).replace("\r", "") 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"> """,
</span><span class="cx"> True,
</span><span class="cx"> ),
</span><ins>+ (
+ "3.1 Simple VPOLL Reply - response added",
+ """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
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":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="User 01":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
+""",
+ """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="User 01":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
+""",
+ """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
+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="User 01":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="User 01":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
+""",
+ True,
+ ),
+ (
+ "3.2 Simple VPOLL Reply - response changed",
+ """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
+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="User 01":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="User 01":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
+""",
+ """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="User 01":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
+""",
+ """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
+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="User 01":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="User 01":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
+""",
+ True,
+ ),
+ (
+ "3.3 Simple VPOLL Reply - response added and changed",
+ """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
+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="User 01":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="User 01":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
+""",
+ """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="User 01":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
+""",
+ """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
+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="User 01":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="User 01":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
+""",
+ True,
+ ),
+ (
+ "3.4 Simple VPOLL Reply - response one changed",
+ """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
+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="User 01":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="User 01":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
+""",
+ """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="User 01":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
+""",
+ """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
+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="User 01":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="User 01":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
+""",
+ 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 "ORGANIZER"
</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(
- "SCHEDULE-STATUS",
- 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("SCHEDULE-STATUS", statusCode)
+ changed = True
+
</ins><span class="cx"> return changed
</span><span class="cx">
</span><span class="cx">
</span></span></pre>
</div>
</div>
</body>
</html>