<!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>[14353] CalendarServer/trunk/txdav/caldav/datastore/scheduling</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/14353">14353</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-01-29 14:44:58 -0800 (Thu, 29 Jan 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>VPOLL related updates.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingitippy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingprocessingpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingschedulerpy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/scheduler.py</a></li>
<li><a href="#CalendarServertrunktxdavcaldavdatastoreschedulingtesttest_itippy">CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingitippy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py (14352 => 14353)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py        2015-01-29 20:22:01 UTC (rev 14352)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py        2015-01-29 22:44:58 UTC (rev 14353)
</span><span class="lines">@@ -358,6 +358,40 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @staticmethod
</span><ins>+    def processPollStatus(itip_message, calendar, recipient):
+        &quot;&quot;&quot;
+        Process a METHOD=POLLSTATUS.
+
+        @param itip_message: the iTIP message to process.
+        @type itip_message: L{Component}
+        @param calendar: the calendar object to apply the POLLSTATUS to
+        @type calendar: L{Component}
+
+        @return: the update calendar component or C{None}
+        &quot;&quot;&quot;
+
+        # Check sequencing
+        if not iTipProcessing.sequenceComparison(itip_message, calendar):
+            # Ignore out of sequence message
+            return None
+
+        calendar_master = calendar.masterComponent()
+        itip_master = itip_message.masterComponent()
+
+        # Remove each VVOTER in the original (except for the recipients)
+        for component in tuple(calendar_master.subcomponents()):
+            if component.name() == &quot;VVOTER&quot; and component.propertyValue(&quot;VOTER&quot;) != recipient:
+                calendar_master.removeComponent(component)
+
+        # Add each VVOTER in the iTip message
+        for component in itip_master.subcomponents():
+            if component.name() == &quot;VVOTER&quot; and component.propertyValue(&quot;VOTER&quot;) != recipient:
+                calendar_master.addComponent(component.duplicate())
+
+        return calendar
+
+
+    @staticmethod
</ins><span class="cx">     def processReply(itip_message, calendar):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Process a METHOD=REPLY.
</span><span class="lines">@@ -576,7 +610,7 @@
</span><span class="cx">             # Do VPOLL transfer
</span><span class="cx">             if reply_component.name() == &quot;VPOLL&quot;:
</span><span class="cx">                 # TODO: figure out how to report changes back
</span><del>-                iTipProcessing.updateVPOLLDataFromReply(reply_component, organizer_component, attendee)
</del><ins>+                partstat_changed = iTipProcessing.updateVPOLLDataFromReply(reply_component, organizer_component, attendee)
</ins><span class="cx"> 
</span><span class="cx">         return attendee.value(), partstat_changed, private_comment_changed
</span><span class="cx"> 
</span><span class="lines">@@ -595,6 +629,8 @@
</span><span class="cx">         @type attendee: L{Property}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        partstat_changed = False
+
</ins><span class="cx">         # Get REQUEST-STATUS as we need to write that into the saved ATTENDEE property
</span><span class="cx">         reqstatus = tuple(reply_component.properties(&quot;REQUEST-STATUS&quot;))
</span><span class="cx">         if reqstatus:
</span><span class="lines">@@ -607,12 +643,13 @@
</span><span class="cx">         organizerVoter = organizer_component.voterComponentForVoter(attendee.value())
</span><span class="cx"> 
</span><span class="cx">         if replyVoter is None:
</span><del>-            return
</del><ins>+            return partstat_changed
</ins><span class="cx"> 
</span><span class="cx">         if organizerVoter is None:
</span><span class="cx">             # Add in the new one
</span><span class="cx">             organizerVoter = replyVoter.duplicate()
</span><span class="cx">             reply_component.addComponent(organizerVoter)
</span><ins>+            partstat_changed = True
</ins><span class="cx">         else:
</span><span class="cx">             # Merge each vote
</span><span class="cx">             replyMap = replyVoter.voteMap()
</span><span class="lines">@@ -621,9 +658,12 @@
</span><span class="cx">             # Add new ones
</span><span class="cx">             for vote in set(replyMap.keys()) - set(organizerMap.keys()):
</span><span class="cx">                 organizerVoter.addComponent(replyMap[vote].duplicate())
</span><ins>+                partstat_changed = True
</ins><span class="cx"> 
</span><span class="cx">             # Replace existing ones
</span><span class="cx">             for vote in set(replyMap.keys()) &amp; set(organizerMap.keys()):
</span><ins>+                if organizerMap[vote].propertyValue(&quot;RESPONSE&quot;) != replyMap[vote].propertyValue(&quot;RESPONSE&quot;):
+                    partstat_changed = True
</ins><span class="cx">                 organizerVoter.removeComponent(organizerMap[vote])
</span><span class="cx">                 organizerVoter.addComponent(replyMap[vote].duplicate())
</span><span class="cx"> 
</span><span class="lines">@@ -635,7 +675,9 @@
</span><span class="cx">         except KeyError:
</span><span class="cx">             pass
</span><span class="cx"> 
</span><ins>+        return partstat_changed
</ins><span class="cx"> 
</span><ins>+
</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></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingprocessingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py (14352 => 14353)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py        2015-01-29 20:22:01 UTC (rev 14352)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/processing.py        2015-01-29 22:44:58 UTC (rev 14353)
</span><span class="lines">@@ -90,8 +90,9 @@
</span><span class="cx">         @param recipient: calendar user receiving the message
</span><span class="cx">         @type recipient: C{str}
</span><span class="cx"> 
</span><del>-        @return: a C{tuple} of (C{bool}, C{bool}) indicating whether the message was processed, and if it was whether
-            auto-processing has taken place.
</del><ins>+        @return: a C{tuple} of (C{bool}, C{bool}, C{bool}, C{bool}) indicating whether the message was processed,
+            and if it was whether auto-processing has taken place, whether it needs to be stored in the inbox, and
+            the changes property for the inbox item.
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         self.txn = txn
</span><span class="lines">@@ -151,7 +152,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def isAttendeeReceivingMessage(self):
</span><del>-        return self.method in (&quot;REQUEST&quot;, &quot;ADD&quot;, &quot;CANCEL&quot;)
</del><ins>+        return self.method in (&quot;REQUEST&quot;, &quot;ADD&quot;, &quot;CANCEL&quot;, &quot;POLLSTATUS&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -393,6 +394,8 @@
</span><span class="cx">         elif self.method == &quot;ADD&quot;:
</span><span class="cx">             # TODO: implement ADD
</span><span class="cx">             result = (False, False, False, None)
</span><ins>+        elif self.method == &quot;POLLSTATUS&quot;:
+            result = (yield self.doImplicitAttendeePollStatus())
</ins><span class="cx">         else:
</span><span class="cx">             # NB We should never get here as we will have rejected unsupported METHODs earlier.
</span><span class="cx">             result = (True, True, False, None,)
</span><span class="lines">@@ -552,7 +555,7 @@
</span><span class="cx">             else:
</span><span class="cx">                 # Request needs to be ignored
</span><span class="cx">                 log.debug(&quot;ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - ignoring&quot; % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
</span><del>-                result = (True, True, False, None,)
</del><ins>+                result = (True, False, False, None,)
</ins><span class="cx"> 
</span><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="lines">@@ -570,7 +573,7 @@
</span><span class="cx">         # If there is no existing copy, then ignore
</span><span class="cx">         if self.recipient_calendar is None:
</span><span class="cx">             log.debug(&quot;ImplicitProcessing - originator '%s' to recipient '%s' ignoring METHOD:CANCEL, UID: '%s' - attendee has no copy&quot; % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
</span><del>-            result = (True, True, True, None)
</del><ins>+            result = (True, False, True, None)
</ins><span class="cx">         else:
</span><span class="cx">             # Need to check for auto-respond attendees. These need to suppress the inbox message
</span><span class="cx">             # if the cancel is processed. However, if the principal is a user we always force the
</span><span class="lines">@@ -631,12 +634,36 @@
</span><span class="cx">                     result = (True, autoprocessed, store_inbox, changes)
</span><span class="cx">             else:
</span><span class="cx">                 log.debug(&quot;ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:CANCEL, UID: '%s' - ignoring&quot; % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
</span><del>-                result = (True, True, False, None)
</del><ins>+                result = (True, False, False, None)
</ins><span class="cx"> 
</span><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def doImplicitAttendeePollStatus(self):
+        &quot;&quot;&quot;
+        An iTIP message status update has been sent to an attendee by the organizer. We need to update the
+        attendee state based on the nature of the iTIP message.
+        &quot;&quot;&quot;
+        # If there is no existing copy, then we must fail
+        if self.new_resource:
+            log.debug(&quot;ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:POLLSTATUS, UID: '%s' - attendee has no copy&quot; % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+            returnValue((True, False, False, None,))
+
+        processed_message = iTipProcessing.processPollStatus(self.message, self.recipient_calendar)
+
+        # Let the store know that no time-range info has changed for a refresh (assuming that
+        # no auto-accept changes were made)
+        processed_message.noInstanceIndexing = True
+
+        # Update the attendee's copy of the event
+        log.debug(&quot;ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:POLLSTATUS, UID: '%s' - updating poll&quot; % (self.originator.cuaddr, self.recipient.cuaddr, self.uid))
+        yield self.writeCalendarResource(None, self.recipient_calendar_resource, processed_message)
+
+        returnValue((True, False, False, None,))
+
+
+    @inlineCallbacks
</ins><span class="cx">     def checkAttendeeAutoReply(self, calendar, automode):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Check whether a reply to the given iTIP message is needed and if so make the
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingschedulerpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/scheduler.py (14352 => 14353)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/scheduler.py        2015-01-29 20:22:01 UTC (rev 14352)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/scheduler.py        2015-01-29 22:44:58 UTC (rev 14353)
</span><span class="lines">@@ -515,7 +515,7 @@
</span><span class="cx">             yield self.generateRemoteSchedulingResponses(otherserver_recipients, responses, freebusy, getattr(self.txn, 'doing_attendee_refresh', False))
</span><span class="cx"> 
</span><span class="cx">         # To reduce chatter, we suppress certain messages
</span><del>-        if not self.suppress_refresh:
</del><ins>+        if not self.suppress_refresh or self.calendar.mainType() == &quot;VPOLL&quot;:
</ins><span class="cx"> 
</span><span class="cx">             # Now process remote recipients
</span><span class="cx">             if remote_recipients:
</span></span></pre></div>
<a id="CalendarServertrunktxdavcaldavdatastoreschedulingtesttest_itippy"></a>
<div class="modfile"><h4>Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py (14352 => 14353)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py        2015-01-29 20:22:01 UTC (rev 14352)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py        2015-01-29 22:44:58 UTC (rev 14353)
</span><span class="lines">@@ -21,7 +21,7 @@
</span><span class="cx"> from twisted.trial import unittest
</span><span class="cx"> 
</span><span class="cx"> from twistedcaldav.stdconfig import config
</span><del>-from twistedcaldav.ical import Component
</del><ins>+from twistedcaldav.ical import Component, normalize_iCalStr
</ins><span class="cx"> 
</span><span class="cx"> from txdav.caldav.datastore.scheduling.itip import iTipProcessing, iTipGenerator
</span><span class="cx"> 
</span><span class="lines">@@ -1289,6 +1289,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;1.2 Simple Reply - recurring no overrides&quot;,
</span><span class="lines">@@ -1335,6 +1336,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;1.3 Simple Reply - recurring with missing master&quot;,
</span><span class="lines">@@ -1389,6 +1391,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;1.4 Simple Reply - recurring with overrides in master but not reply&quot;,
</span><span class="lines">@@ -1453,6 +1456,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;1.5 Simple Reply - recurring with overrides in master invalid in reply&quot;,
</span><span class="lines">@@ -1517,6 +1521,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;1.6 Simple Reply - recurring with overrides in master, invalid ones in reply&quot;,
</span><span class="lines">@@ -1590,6 +1595,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;2.1 X-CALENDARSERVER-RESET-PARTSTAT Reply - non recurring&quot;,
</span><span class="lines">@@ -1636,6 +1642,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;2.2 X-CALENDARSERVER-RESET-PARTSTAT Reply - non recurring&quot;,
</span><span class="lines">@@ -1682,6 +1689,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;2.3 X-CALENDARSERVER-RESET-PARTSTAT Reply - non recurring&quot;,
</span><span class="lines">@@ -1728,6 +1736,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;2.4 X-CALENDARSERVER-RESET-PARTSTAT Reply - non recurring&quot;,
</span><span class="lines">@@ -1774,6 +1783,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                None,
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;3.1 Simple VPOLL Reply - response added&quot;,
</span><span class="lines">@@ -1874,6 +1884,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                ('mailto:user2@example.com', set([(&quot;&quot;, True, False,)])),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;3.2 Simple VPOLL Reply - response changed&quot;,
</span><span class="lines">@@ -1978,6 +1989,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                ('mailto:user2@example.com', set([(&quot;&quot;, True, False,)])),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;3.3 Simple VPOLL Reply - response added and changed&quot;,
</span><span class="lines">@@ -2090,6 +2102,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                ('mailto:user2@example.com', set([(&quot;&quot;, True, False,)])),
</ins><span class="cx">             ),
</span><span class="cx">             (
</span><span class="cx">                 &quot;3.4 Simple VPOLL Reply - response one changed&quot;,
</span><span class="lines">@@ -2202,21 +2215,475 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> &quot;&quot;&quot;,
</span><span class="cx">                 True,
</span><ins>+                ('mailto:user2@example.com', set([(&quot;&quot;, True, False,)])),
</ins><span class="cx">             ),
</span><ins>+            (
+                &quot;3.5 Simple VPOLL Reply - no changes&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: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,
+                ('mailto:user2@example.com', set([])),
+            ),
</ins><span class="cx">         )
</span><span class="cx"> 
</span><del>-        for title, calendar_txt, itip_txt, changed_txt, expected in data:
</del><ins>+        for title, calendar_txt, itip_txt, changed_txt, expected, processed in data:
</ins><span class="cx">             calendar = Component.fromString(calendar_txt)
</span><span class="cx">             itip = Component.fromString(itip_txt)
</span><span class="cx">             if expected:
</span><span class="cx">                 changed = Component.fromString(changed_txt)
</span><span class="cx"> 
</span><del>-            result, _ignore = iTipProcessing.processReply(itip, calendar)
</del><ins>+            result, result_processed = iTipProcessing.processReply(itip, calendar)
</ins><span class="cx">             self.assertEqual(result, expected, msg=&quot;Result mismatch: %s&quot; % (title,))
</span><span class="cx">             if expected:
</span><span class="cx">                 self.assertEqual(changed, calendar, msg=&quot;Calendar mismatch: %s&quot; % (title,))
</span><ins>+            if processed is not None:
+                self.assertEqual(result_processed, processed, msg=&quot;Process mismatch: %s&quot; % (title,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def test_processPollStatus(self):
+        &quot;&quot;&quot;
+        Test iTIPProcessing.processPollStatus
+        &quot;&quot;&quot;
+
+        data = (
+            (
+                &quot;3.1 Simple VPOLL - 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=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user1@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user3@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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:POLLSTATUS
+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:user1@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user3@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+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=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user1@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user3@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:0
+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;3.2 Simple VPOLL - recipient response not 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=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user1@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user3@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
+&quot;&quot;&quot;,
+                &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+METHOD:POLLSTATUS
+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:user1@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+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:0
+END:VOTE
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user3@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+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=User 01:mailto:user1@example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user1@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:100
+END:VOTE
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2@example.com
+END:VVOTER
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user3@example.com
+BEGIN:VOTE
+POLL-ITEM-ID:1
+RESPONSE:100
+END:VOTE
+BEGIN:VOTE
+POLL-ITEM-ID:2
+RESPONSE:0
+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 title, calendar_txt, itip_txt, changed_txt in data:
+            calendar = Component.fromString(calendar_txt)
+            itip = Component.fromString(itip_txt)
+
+            result = iTipProcessing.processPollStatus(itip, calendar, &quot;mailto:user2@example.com&quot;)
+            self.assertEqual(normalize_iCalStr(result), normalize_iCalStr(changed_txt), msg=&quot;Calendar mismatch: %s&quot; % (title,))
+
+
</ins><span class="cx">     def test_update_attendee_partstat(self):
</span><span class="cx"> 
</span><span class="cx">         data = (
</span></span></pre>
</div>
</div>

</body>
</html>