[CalendarServer-changes] [14302] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed Jan 14 07:42:38 PST 2015
Revision: 14302
http://trac.calendarserver.org//changeset/14302
Author: cdaboo at apple.com
Date: 2015-01-14 07:42:38 -0800 (Wed, 14 Jan 2015)
Log Message:
-----------
Update to latest VPOLL spec.
Modified Paths:
--------------
CalendarServer/trunk/contrib/webpoll/Makefile
CalendarServer/trunk/contrib/webpoll/webapp/index.html
CalendarServer/trunk/contrib/webpoll/webapp/js/caldav.js
CalendarServer/trunk/requirements-dev.txt
CalendarServer/trunk/requirements-stable.txt
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/simpleresource.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py
CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py
Property Changed:
----------------
CalendarServer/trunk/contrib/webpoll/webapp/css/
CalendarServer/trunk/contrib/webpoll/webapp/js/
Modified: CalendarServer/trunk/contrib/webpoll/Makefile
===================================================================
--- 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)
@@ -15,22 +15,19 @@
##
webpoll:
- 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
+ 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
- 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
+ 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
clean:
- 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
+ rm -rf webapp/js/3rdparty
+ rm -rf webapp/css/3rdparty
Property changes on: CalendarServer/trunk/contrib/webpoll/webapp/css
___________________________________________________________________
Added: svn:ignore
+ 3rdparty
Modified: CalendarServer/trunk/contrib/webpoll/webapp/index.html
===================================================================
--- 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)
@@ -3,13 +3,19 @@
<head>
<meta charset="utf-8">
<title>WebPoll</title>
- <link href="css/cupertino/jquery-ui.css" rel="stylesheet">
- <link href="css/datetimepicker.css" rel="stylesheet">
+
+ <!-- 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 -->
<link href="css/webpoll.css" rel="stylesheet">
- <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>
+
<script src="js/utils.js"></script>
<script src="js/jcal.js"></script>
<script src="js/caldav.js"></script>
Property changes on: CalendarServer/trunk/contrib/webpoll/webapp/js
___________________________________________________________________
Added: svn:ignore
+ 3rdparty
Modified: CalendarServer/trunk/contrib/webpoll/webapp/js/caldav.js
===================================================================
--- 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)
@@ -772,34 +772,45 @@
CalendarComponent.prototype.voter_responses = function() {
var voter_results = {}
- $.each(this.data.properties("voter"), function(index, voter) {
- voter_results[voter[3]] = parseInt(voter[1]["response"]);
+ 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;
+ }
+ });
});
return voter_results;
}
// Change active user's response to this event
CalendarComponent.prototype.changeVoterResponse = function(response) {
+ var matches_vvoter = $.grep(this.parent.data.components("vvoter"), function(vvoter, index) {
+ return gSession.currentPrincipal.matchingAddress(vvoter.getPropertyValue("voter"));
+ });
+ var pollitemid = this.pollitemid();
if (response !== null) {
- var matches = $.grep(this.data.properties("voter"), function(voter, index) {
- return gSession.currentPrincipal.matchingAddress(voter[3]);
+ var matches_vote = $.grep(matches_vvoter[0].components("vote"), function(vote, index) {
+ return vote.getPropertyValue("poll-item-id") == pollitemid;
});
- if (matches.length == 1) {
- new CalendarUser(matches[0], this).response(response.toString());
+ if (matches_vote.length == 1) {
+ matches_vote[0].getProperty("response")[3] = response;
} else {
- this.data.newProperty(
- "voter",
- gSession.currentPrincipal.defaultAddress(),
- { "response" : response.toString() },
- "cal-address"
- );
- this.changed(true);
+ var vote = matches_vvoter[0].newComponent("vote");
+ vote.newProperty("response", response, {}, "integer");
+ vote.newProperty("poll-item-id", pollitemid, {}, "integer");
}
} else {
- this.data.removePropertiesMatchingValue(function(propdata) {
- return propdata[0] == "voter" && gSession.currentPrincipal.matchingAddress(propdata[3]);
+ $.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;
+ }
});
}
+ this.changed(true);
}
// A container class for VCALENDAR objects
@@ -849,7 +860,8 @@
{ "cn" : gSession.currentPrincipal.cn },
"cal-address"
);
- vpoll.newProperty(
+ var vvoter = vpoll.newComponent("vvoter");
+ vvoter.newProperty(
"voter",
gSession.currentPrincipal.defaultAddress(),
{
@@ -895,43 +907,55 @@
// Add a new VEVENT to the VPOLL
CalendarPoll.prototype.addEvent = function(dtstart, dtend) {
this.changed(true);
+ var poll_item_id = this.data.components("vevent").length;
var vevent = this.data.newComponent("vevent", true);
vevent.newProperty("dtstart", jcaldate.jsDateTojCal(dtstart), {}, "date-time");
vevent.newProperty("dtend", jcaldate.jsDateTojCal(dtend), {}, "date-time");
vevent.newProperty("summary", this.summary());
- vevent.newProperty("poll-item-id", (this.data.components("vevent").length).toString());
- vevent.newProperty(
- "voter",
- this.organizer(),
- {"response" : "80"},
- "cal-address"
- );
+ 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");
return new CalendarEvent(vevent, this);
}
// Get an array of voters in the VPOLL
CalendarPoll.prototype.voters = function() {
var this_vpoll = this;
- return $.map(this.data.properties("voter"), function(voter) {
- return new CalendarUser(voter, this_vpoll);
+ return $.map(this.data.components("vvoter"), function(vvoter) {
+ return new CalendarUser(vvoter.getProperty("voter"), this_vpoll);
});
}
// Add a voter to the VPOLL
CalendarPoll.prototype.addVoter = function() {
this.changed(true);
- return new CalendarUser(this.data.newProperty("voter", "", {}, "cal-address"), this);
+ var vvoter = this.data.newComponent("vvoter");
+ return new CalendarUser(vvoter.newProperty("voter", "", {}, "cal-address"), this);
}
// Mark current user as accepted
CalendarPoll.prototype.acceptInvite = function() {
if (!this.isOwned()) {
- 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"];
+ $.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"];
+ }
})
}
}
@@ -962,7 +986,8 @@
vevent.copyProperty("dtstart", this.data);
vevent.copyProperty("dtend", this.data);
vevent.copyProperty("organizer", vpoll.data);
- $.each(vpoll.data.properties("voter"), function(index, voter) {
+ $.each(vpoll.data.components("vvoter"), function(index, vvoter) {
+ var voter = vvoter.getProperty("voter");
var attendee = vevent.newProperty(
"attendee",
voter[3],
Modified: CalendarServer/trunk/requirements-dev.txt
===================================================================
--- 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)
@@ -8,4 +8,4 @@
q
tl.eggdeps
--editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@13420#egg=CalDAVClientLibrary
---editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@14244#egg=CalDAVTester
+--editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@14300#egg=CalDAVTester
Modified: CalendarServer/trunk/requirements-stable.txt
===================================================================
--- 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)
@@ -68,7 +68,7 @@
-e svn+http://svn.calendarserver.org/repository/calendarserver/PyKerberos/trunk@13420#egg=kerberos
- -e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@14245#egg=pycalendar
+ -e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@14301#egg=pycalendar
python-dateutil==1.5 # Note: v2.0+ is for Python 3
pytz==2014.10
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- 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)
@@ -2296,6 +2296,25 @@
return ()
+ 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
+
+
def getOrganizerProperty(self):
"""
Get the organizer value. Works on either a VCALENDAR or on a component.
@@ -2349,28 +2368,44 @@
def recipientPropertyName(self):
- return "VOTER" if self.name() == "VPOLL" else "ATTENDEE"
+ return "VOTER" if self.name() in ("VPOLL", "VVOTER",) else "ATTENDEE"
- def getAttendees(self):
+ def getRecipientProperties(self):
"""
- Get the attendee value. Works on either a VCALENDAR or on a component.
+ Get the attendee properties. Works on either a VCALENDAR or on a component.
- @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
+ @return: a C{list} of the the Attendee properties
"""
# Extract appropriate sub-component if this is a VCALENDAR
if self.name() == "VCALENDAR":
for component in self.subcomponents(ignore=True):
- return component.getAttendees()
+ return component.getRecipientProperties()
else:
# Find the property values
- return [p.value() for p in self.properties(self.recipientPropertyName())]
+ 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()))
return None
+ 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()]
+
+
def getAttendeesByInstance(self, makeUnique=False, onlyScheduleAgentServer=False):
"""
Get the attendee values for each instance. Optionally remove duplicates.
@@ -2392,7 +2427,7 @@
result = ()
attendees = set()
rid = self.getRecurrenceIDUTC()
- for attendee in tuple(self.properties(self.recipientPropertyName())):
+ for attendee in tuple(self.getRecipientProperties()):
if onlyScheduleAgentServer:
if attendee.hasParameter("SCHEDULE-AGENT"):
@@ -2450,7 +2485,7 @@
return attendee
else:
# Find the primary subcomponent
- for attendee in self.properties(self.recipientPropertyName()):
+ for attendee in self.getRecipientProperties():
if normalizeCUAddr(attendee.value()) in test:
return attendee
@@ -2491,7 +2526,7 @@
yield attendee
else:
# Find the primary subcomponent
- for attendee in self.properties(self.recipientPropertyName()):
+ for attendee in self.getRecipientProperties():
yield attendee
@@ -2845,6 +2880,33 @@
master_component.addProperty(Property("EXDATE", [exdate, ]))
+ 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
+
+
def filterComponents(self, rids):
# If master is in rids do nothing
@@ -2873,7 +2935,15 @@
assert self.name() == "VCALENDAR", "Not a calendar: {0!r}".format(self,)
for component in self.subcomponents(ignore=True):
- [component.removeProperty(p) for p in tuple(component.properties(component.recipientPropertyName())) if p.value().lower() != attendee.lower()]
+ 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)
def removeAllButTheseAttendees(self, attendees):
@@ -2886,7 +2956,15 @@
attendees = set([attendee.lower() for attendee in attendees])
for component in self.subcomponents(ignore=True):
- [component.removeProperty(p) for p in tuple(component.properties(component.recipientPropertyName())) if p.value().lower() not in attendees]
+ 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)
def hasAlarm(self):
@@ -3059,6 +3137,9 @@
for prop in props:
for param in params:
prop.removeParameter(param)
+ if self.name() == "VPOLL":
+ for component in self.subcomponents(ignore=True):
+ component.removePropertyParameters(property, params)
def removePropertyParametersByValue(self, property, paramvalues):
@@ -3074,6 +3155,9 @@
for prop in props:
for param, value in paramvalues:
prop.removeParameterValue(param, value)
+ if self.name() == "VPOLL":
+ for component in self.subcomponents(ignore=True):
+ component.removePropertyParametersByValue(property, paramvalues)
def getITIPInfo(self):
Modified: CalendarServer/trunk/twistedcaldav/simpleresource.py
===================================================================
--- 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)
@@ -32,7 +32,6 @@
from twisted.internet.defer import succeed
-from twistedcaldav.config import config
from twistedcaldav.resource import CalDAVResource
from txdav.xml import element as davxml
@@ -105,7 +104,7 @@
def renderHTTP(self, request):
- return http.RedirectResponse(request.unparseURL(host=config.ServerHostName, **self._kwargs))
+ return http.RedirectResponse(request.unparseURL(host=request.host, **self._kwargs))
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/icaldiff.py
===================================================================
--- 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)
@@ -593,36 +593,39 @@
changed = False
- # 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,))
+ # Get the matching VVOTER component in each VPOLL
+ serverVoter = serverComponent.voterComponentForVoter(self.attendee)
+ clientVoter = clientComponent.voterComponentForVoter(self.attendee)
- # 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
+ # 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))
else:
- 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
+ server_vote.removeProperty("RESPONSE")
+ server_vote.replaceProperty(Property("LAST-MODIFIED", DateTime.getNowUTC()))
+ changed = True
return changed
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- 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)
@@ -671,6 +671,8 @@
self.coerceOrganizerScheduleAgent()
+ partstatProcessing = self.calendar.mainType() != "VPOLL"
+
# Check for a delete
if self.action == "remove":
@@ -692,8 +694,10 @@
self.oldcalendar = (yield self.resource.componentForUser())
self.oldAttendeesByInstance = self.oldcalendar.getAttendeesByInstance(True, onlyScheduleAgentServer=True)
self.oldInstances = set(self.oldcalendar.getComponentInstances())
- self.coerceAttendeesPartstatOnModify()
+ if partstatProcessing:
+ self.coerceAttendeesPartstatOnModify()
+
# Don't allow any SEQUENCE to decrease
if self.oldcalendar and (not queued or not config.Scheduling.Options.WorkQueues.Enabled):
self.calendar.sequenceInSync(self.oldcalendar)
@@ -737,10 +741,11 @@
# the PARTSTAT to NEEDS-ACTION.
# The organizer is automatically ACCEPTED to the event.
continue
- if attendee.hasParameter("PARTSTAT"):
- attendee.setParameter("PARTSTAT", "NEEDS-ACTION")
- seq = comp.propertyValue("SEQUENCE", 0)
- attendee.setParameter("X-CALENDARSERVER-RESET-PARTSTAT", str(seq))
+ if partstatProcessing:
+ if attendee.hasParameter("PARTSTAT"):
+ attendee.setParameter("PARTSTAT", "NEEDS-ACTION")
+ seq = comp.propertyValue("SEQUENCE", 0)
+ attendee.setParameter("X-CALENDARSERVER-RESET-PARTSTAT", str(seq))
# Look for changes to a specific attendee within an instance
for rid, attendees in needs_action_changed_rids.items():
@@ -750,10 +755,11 @@
if comp is not None:
self.calendar.addComponent(comp)
- for attendee in comp.getAllAttendeeProperties():
- if attendee.value() in attendees:
- seq = comp.propertyValue("SEQUENCE", 0)
- attendee.setParameter("X-CALENDARSERVER-RESET-PARTSTAT", str(seq))
+ 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))
else:
log.debug("Implicit - organizer '{organizer}' is splitting UID: '{uid}'", organizer=self.organizer, uid=self.uid)
@@ -768,7 +774,8 @@
elif self.action == "create":
if self.split_details is None:
log.debug("Implicit - organizer '{organizer}' is creating UID: '{uid}'", organizer=self.organizer, uid=self.uid)
- self.coerceAttendeesPartstatOnCreate()
+ if partstatProcessing:
+ self.coerceAttendeesPartstatOnCreate()
# We need to handle the case where an organizer "restores" a previously delete event that has a sequence
# lower than the one used in the cancel that attendees may still have. In this case what we need to do
@@ -784,14 +791,15 @@
log.debug("Implicit - organizer '{organizer}' is creating a split UID: '{uid}'", organizer=self.organizer, uid=self.uid)
# Always set RSVP=TRUE for any NEEDS-ACTION
- 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")
+ 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")
# If processing a queue item, actually execute the scheduling operations, else queue it.
# Note a split is always a queued execution, so we do not need to re-queue
@@ -1229,6 +1237,9 @@
aggregated.setdefault(attendee, []).append(rid)
count = 0
+ recipientProperties = collections.defaultdict(list)
+ for p in self.calendar.getAllAttendeeProperties():
+ recipientProperties[p.value()].append(p)
for attendee, rids in aggregated.iteritems():
# Don't send message back to the ORGANIZER
@@ -1261,12 +1272,8 @@
if queued:
# Always make it look like scheduling succeeded when queuing
- self.calendar.setParameterToValueForPropertyWithValue(
- "SCHEDULE-STATUS",
- iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
- "ATTENDEE",
- attendee,
- )
+ for p in recipientProperties[attendee]:
+ p.setParameter("SCHEDULE-STATUS", iTIPRequestStatus.MESSAGE_DELIVERED_CODE)
else:
# Add split details if needed
if self.split_details is not None:
@@ -1299,6 +1306,9 @@
# Do one per attendee
count = 0
+ recipientProperties = collections.defaultdict(list)
+ for p in self.calendar.getAllAttendeeProperties():
+ recipientProperties[p.value()].append(p)
for attendee in self.attendees:
# Don't send message back to the ORGANIZER
@@ -1327,10 +1337,8 @@
# Do not schedule with groups - ever
if attendeeAddress.hosted() and attendeeAddress.getCUType() == "GROUP":
# Set SCHEDULE-STATUS to something appropriate
- self.calendar.setParametersForPropertyWithValue(
- {"SCHEDULE-STATUS": iTIPRequestStatus.REQUEST_FORWARDED_CODE if config.GroupAttendees.Enabled else iTIPRequestStatus.NO_USER_SUPPORT_CODE},
- "ATTENDEE", attendee,
- )
+ for p in recipientProperties[attendee]:
+ p.setParameter("SCHEDULE-STATUS", iTIPRequestStatus.REQUEST_FORWARDED_CODE if config.GroupAttendees.Enabled else iTIPRequestStatus.NO_USER_SUPPORT_CODE)
continue
itipmsg = iTipGenerator.generateAttendeeRequest(self.calendar, (attendee,), self.changed_rids)
@@ -1340,12 +1348,8 @@
if queued:
# Always make it look like scheduling succeeded when queuing
- self.calendar.setParameterToValueForPropertyWithValue(
- "SCHEDULE-STATUS",
- iTIPRequestStatus.MESSAGE_DELIVERED_CODE,
- "ATTENDEE",
- attendee,
- )
+ for p in recipientProperties[attendee]:
+ p.setParameter("SCHEDULE-STATUS", iTIPRequestStatus.MESSAGE_DELIVERED_CODE)
else:
# Add split details if needed
if self.split_details is not None:
@@ -1411,20 +1415,19 @@
self.queuedResponses.append(response)
else:
# Map each recipient in the response to a status code
+ recipients = collections.defaultdict(list)
+ for p in self.calendar.getAllAttendeeProperties() if is_organizer else self.calendar.getOrganizerProperties():
+ recipients[p.value()].append(p)
+
responses = {}
- propname = self.calendar.mainComponent().recipientPropertyName() if is_organizer else "ORGANIZER"
for item in response.responses:
recipient = str(item.recipient.children[0])
status = str(item.reqstatus)
responses[recipient] = status
# Now apply to each ATTENDEE/ORGANIZER in the original data
- self.calendar.setParameterToValueForPropertyWithValue(
- "SCHEDULE-STATUS",
- status.split(";")[0],
- propname,
- recipient,
- )
+ for p in recipients[recipient]:
+ p.setParameter("SCHEDULE-STATUS", status.split(";")[0])
@inlineCallbacks
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/itip.py
===================================================================
--- 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)
@@ -485,7 +485,7 @@
reqstatus = "2.0"
# Get attendee in reply_component - there MUST be only one
- attendees = tuple(reply_component.properties(reply_component.recipientPropertyName()))
+ attendees = tuple(reply_component.getRecipientProperties())
if len(attendees) != 1:
log.error("There must be one and only one ATTENDEE property in a REPLY\n%s" % (str(reply_component),))
return None, False, False
@@ -500,17 +500,18 @@
# Only process the change for this component if it was made after the last partstat reset
if existing_attendee and reply_sequence >= existing_reset_sequence:
- oldpartstat = existing_attendee.parameterValue("PARTSTAT", "NEEDS-ACTION")
- existing_attendee.setParameter("PARTSTAT", partstat)
- existing_attendee.setParameter("SCHEDULE-STATUS", reqstatus)
- partstat_changed = (oldpartstat != partstat)
+ 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)
- # Always delete RSVP on PARTSTAT change
- if partstat_changed:
- try:
- existing_attendee.removeParameter("RSVP")
- except KeyError:
- pass
+ # Always delete RSVP on PARTSTAT change
+ if partstat_changed:
+ try:
+ existing_attendee.removeParameter("RSVP")
+ except KeyError:
+ pass
# Handle attendee comments
if config.Scheduling.CalDAV.get("EnablePrivateComments", True):
@@ -583,7 +584,8 @@
@staticmethod
def updateVPOLLDataFromReply(reply_component, organizer_component, attendee):
"""
- Update VPOLL sub-components with voter's response.
+ 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.
@param reply_component: component to copy from
@type reply_component: L{Component}
@@ -593,29 +595,47 @@
@type attendee: L{Property}
"""
- responses = {}
- for prop in reply_component.properties("POLL-ITEM-ID"):
- responses[prop.value()] = prop
+ # 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"
- 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(),))
+ # Get the matching VVOTER component in each VPOLL
+ replyVoter = reply_component.voterComponentForVoter(attendee.value())
+ organizerVoter = organizer_component.voterComponentForVoter(attendee.value())
- # 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
+ if replyVoter is None:
+ return
- # 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"))
+ 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()
+ # Add new ones
+ for vote in set(replyMap.keys()) - set(organizerMap.keys()):
+ organizerVoter.addComponent(replyMap[vote].duplicate())
+ # 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
+
+
@staticmethod
def transferItems(from_calendar, to_component, needs_action_rids, reschedule, master_details, remove_matched=False):
"""
@@ -1068,22 +1088,18 @@
@staticmethod
def generateVPOLLReply(vpoll, attendee):
"""
- Generate the proper poll response in a reply for each component being voted on.
+ Generate the proper poll response in a reply by removing all sub-components
+ except fore the VVOTER matching the attendee (voter) replying.
@param vpoll: the VPOLL component to process
@type vpoll: L{Component}
- @param attendee: calendar user address of attendee replying
+ @param attendee: calendar user address of attendee (voter) replying
@type attendee: C{str}
"""
for component in tuple(vpoll.subcomponents(ignore=True)):
- 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)
+ if component.name() != "VVOTER" or component.getVoterProperty((attendee,)) is None:
+ vpoll.removeComponent(component)
@staticmethod
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_icaldiff.py
===================================================================
--- 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)
@@ -6615,3 +6615,740 @@
strcal2 = str(cal2)
strchanged = str(Component.fromString(changed_calendar))
self.assertEqual(strchanged, strcal2, msg="%s mismatch:\n%s" % (description, "\n".join(unified_diff(strchanged.split("\n"), strcal2.split("\n")))))
+
+
+ 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+""",
+ "mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2 at example.com
+DTSTAMP:20150113T152404Z
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+""",
+ "mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+""",
+ "mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+""",
+ "mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2 at example.com
+DTSTAMP:20150113T152404Z
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+""",
+ "mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+""",
+ "mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at 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]),))
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/test/test_itip.py
===================================================================
--- 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)
@@ -1775,6 +1775,434 @@
""",
True,
),
+ (
+ "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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at example.com
+END:VVOTER
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;SCHEDULE-STATUS=2.0:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;SCHEDULE-STATUS=2.0:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;SCHEDULE-STATUS=2.0:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;RSVP=TRUE:mailto:user2 at 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 at example.com
+POLL-MODE:BASIC
+POLL-PROPERTIES:DTSTART,DTEND
+SUMMARY:New Poll
+BEGIN:VVOTER
+DTSTAMP:20150113T152404Z
+VOTER;SCHEDULE-STATUS=2.0:mailto:user2 at 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 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:1
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080602T120000Z
+DTEND:20080602T130000Z
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user2 at example.com
+POLL-ITEM-ID:2
+END:VEVENT
+END:VPOLL
+END:VCALENDAR
+""",
+ True,
+ ),
)
for title, calendar_txt, itip_txt, changed_txt, expected in data:
Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/work.py
===================================================================
--- 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)
@@ -32,6 +32,7 @@
from txdav.common.datastore.sql_tables import schema, \
scheduleActionToSQL, scheduleActionFromSQL
+import collections
import datetime
import hashlib
import traceback
@@ -262,18 +263,19 @@
# Map each recipient in the response to a status code
changed = False
- propname = calendar.mainComponent().recipientPropertyName() if is_organizer else "ORGANIZER"
+ recipients = collections.defaultdict(list)
+ for p in calendar.getAllAttendeeProperties() if is_organizer else calendar.getOrganizerProperties():
+ recipients[p.value()].append(p)
+
for recipient, statusCode in response:
# Now apply to each ATTENDEE/ORGANIZER in the original data only if not 1.2
if statusCode != iTIPRequestStatus.MESSAGE_DELIVERED_CODE:
- calendar.setParameterToValueForPropertyWithValue(
- "SCHEDULE-STATUS",
- statusCode,
- propname,
- recipient,
- )
- changed = True
+ # Now apply to each ATTENDEE/ORGANIZER in the original data
+ for p in recipients[recipient]:
+ p.setParameter("SCHEDULE-STATUS", statusCode)
+ changed = True
+
return changed
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150114/67d8444a/attachment-0001.html>
More information about the calendarserver-changes
mailing list