<!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):
+ """
+ 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}
+ """
+
+ # 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() == "VVOTER" and component.propertyValue("VOTER") != recipient:
+ calendar_master.removeComponent(component)
+
+ # Add each VVOTER in the iTip message
+ for component in itip_master.subcomponents():
+ if component.name() == "VVOTER" and component.propertyValue("VOTER") != recipient:
+ calendar_master.addComponent(component.duplicate())
+
+ return calendar
+
+
+ @staticmethod
</ins><span class="cx"> def processReply(itip_message, calendar):
</span><span class="cx"> """
</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() == "VPOLL":
</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"> """
</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("REQUEST-STATUS"))
</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()) & set(organizerMap.keys()):
</span><ins>+ if organizerMap[vote].propertyValue("RESPONSE") != replyMap[vote].propertyValue("RESPONSE"):
+ 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"> """
</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"> """
</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 ("REQUEST", "ADD", "CANCEL")
</del><ins>+ return self.method in ("REQUEST", "ADD", "CANCEL", "POLLSTATUS")
</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 == "ADD":
</span><span class="cx"> # TODO: implement ADD
</span><span class="cx"> result = (False, False, False, None)
</span><ins>+ elif self.method == "POLLSTATUS":
+ 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("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:REQUEST, UID: '%s' - ignoring" % (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("ImplicitProcessing - originator '%s' to recipient '%s' ignoring METHOD:CANCEL, UID: '%s' - attendee has no copy" % (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("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:CANCEL, UID: '%s' - ignoring" % (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):
+ """
+ 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.
+ """
+ # If there is no existing copy, then we must fail
+ if self.new_resource:
+ log.debug("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:POLLSTATUS, UID: '%s' - attendee has no copy" % (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("ImplicitProcessing - originator '%s' to recipient '%s' processing METHOD:POLLSTATUS, UID: '%s' - updating poll" % (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"> """
</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() == "VPOLL":
</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"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "1.2 Simple Reply - recurring no overrides",
</span><span class="lines">@@ -1335,6 +1336,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "1.3 Simple Reply - recurring with missing master",
</span><span class="lines">@@ -1389,6 +1391,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "1.4 Simple Reply - recurring with overrides in master but not reply",
</span><span class="lines">@@ -1453,6 +1456,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "1.5 Simple Reply - recurring with overrides in master invalid in reply",
</span><span class="lines">@@ -1517,6 +1521,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "1.6 Simple Reply - recurring with overrides in master, invalid ones in reply",
</span><span class="lines">@@ -1590,6 +1595,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "2.1 X-CALENDARSERVER-RESET-PARTSTAT Reply - non recurring",
</span><span class="lines">@@ -1636,6 +1642,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "2.2 X-CALENDARSERVER-RESET-PARTSTAT Reply - non recurring",
</span><span class="lines">@@ -1682,6 +1689,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "2.3 X-CALENDARSERVER-RESET-PARTSTAT Reply - non recurring",
</span><span class="lines">@@ -1728,6 +1736,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "2.4 X-CALENDARSERVER-RESET-PARTSTAT Reply - non recurring",
</span><span class="lines">@@ -1774,6 +1783,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ None,
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "3.1 Simple VPOLL Reply - response added",
</span><span class="lines">@@ -1874,6 +1884,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ ('mailto:user2@example.com', set([("", True, False,)])),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "3.2 Simple VPOLL Reply - response changed",
</span><span class="lines">@@ -1978,6 +1989,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ ('mailto:user2@example.com', set([("", True, False,)])),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "3.3 Simple VPOLL Reply - response added and changed",
</span><span class="lines">@@ -2090,6 +2102,7 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ ('mailto:user2@example.com', set([("", True, False,)])),
</ins><span class="cx"> ),
</span><span class="cx"> (
</span><span class="cx"> "3.4 Simple VPOLL Reply - response one changed",
</span><span class="lines">@@ -2202,21 +2215,475 @@
</span><span class="cx"> END:VCALENDAR
</span><span class="cx"> """,
</span><span class="cx"> True,
</span><ins>+ ('mailto:user2@example.com', set([("", True, False,)])),
</ins><span class="cx"> ),
</span><ins>+ (
+ "3.5 Simple VPOLL Reply - no changes",
+ """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: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,
+ ('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="Result mismatch: %s" % (title,))
</span><span class="cx"> if expected:
</span><span class="cx"> self.assertEqual(changed, calendar, msg="Calendar mismatch: %s" % (title,))
</span><ins>+ if processed is not None:
+ self.assertEqual(result_processed, processed, msg="Process mismatch: %s" % (title,))
</ins><span class="cx">
</span><span class="cx">
</span><ins>+ def test_processPollStatus(self):
+ """
+ Test iTIPProcessing.processPollStatus
+ """
+
+ data = (
+ (
+ "3.1 Simple VPOLL - 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: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
+""",
+ """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
+""",
+ """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
+""",
+ ),
+ (
+ "3.2 Simple VPOLL - recipient response not 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: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
+""",
+ """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
+""",
+ """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
+""",
+ ),
+ )
+
+ 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, "mailto:user2@example.com")
+ self.assertEqual(normalize_iCalStr(result), normalize_iCalStr(changed_txt), msg="Calendar mismatch: %s" % (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>