[CalendarServer-changes] [11740] CalendarServer/branches/users/cdaboo/json
source_changes at macosforge.org
source_changes at macosforge.org
Thu Sep 19 20:40:01 PDT 2013
Revision: 11740
http://trac.calendarserver.org//changeset/11740
Author: cdaboo at apple.com
Date: 2013-09-19 20:40:01 -0700 (Thu, 19 Sep 2013)
Log Message:
-----------
Basic VPOLL scheduling support.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/json/twistedcaldav/ical.py
CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/icaldiff.py
CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/imip/inbound.py
CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/imip/test/test_inbound.py
CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/implicit.py
CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/itip.py
Modified: CalendarServer/branches/users/cdaboo/json/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/json/twistedcaldav/ical.py 2013-09-19 20:54:14 UTC (rev 11739)
+++ CalendarServer/branches/users/cdaboo/json/twistedcaldav/ical.py 2013-09-20 03:40:01 UTC (rev 11740)
@@ -133,12 +133,21 @@
"LAST-MODIFIED": (None, {"VALUE": "DATE-TIME"}),
"SEQUENCE": (0, {"VALUE": "INTEGER"}),
"REQUEST-STATUS": (None, {"VALUE": "TEXT"}),
+
+ "VOTER": (None, {
+ "VALUE": "CAL-ADDRESS",
+ "CUTYPE": "INDIVIDUAL",
+ "ROLE": "REQ-PARTICIPANT",
+ "RSVP": "FALSE",
+ "SCHEDULE-AGENT": "SERVER",
+ }),
}
# transformations to apply to property values
normalizePropsValue = {
"ATTENDEE": normalizeCUAddr,
"ORGANIZER": normalizeCUAddr,
+ "VOTER": normalizeCUAddr,
}
ignoredComponents = ("VTIMEZONE", PERUSER_COMPONENT,)
@@ -2172,6 +2181,10 @@
return is_server
+ def recipientPropertyName(self):
+ return "VOTER" if self.name() == "VPOLL" else "ATTENDEE"
+
+
def getAttendees(self):
"""
Get the attendee value. Works on either a VCALENDAR or on a component.
@@ -2187,7 +2200,7 @@
return component.getAttendees()
else:
# Find the property values
- return [p.value() for p in self.properties("ATTENDEE")]
+ return [p.value() for p in self.properties(self.recipientPropertyName())]
return None
@@ -2214,7 +2227,7 @@
result = ()
attendees = set()
rid = self.getRecurrenceIDUTC()
- for attendee in tuple(self.properties("ATTENDEE")):
+ for attendee in tuple(self.properties(self.recipientPropertyName())):
if onlyScheduleAgentServer:
if attendee.hasParameter("SCHEDULE-AGENT"):
@@ -2230,6 +2243,27 @@
return result
+ def getVoterProperty(self, match):
+ """
+ Get the voters matching a value.
+
+ @param match: a C{list} of calendar user address strings to try and match.
+ @return: the matching Voter property, or None
+ """
+
+ # Need to normalize http/https cu addresses
+ test = set()
+ for item in match:
+ test.add(normalizeCUAddr(item))
+
+ # Find the primary subcomponent
+ for voter in self.properties("VOTER"):
+ if normalizeCUAddr(voter.value()) in test:
+ return voter
+
+ return None
+
+
def getAttendeeProperty(self, match):
"""
Get the attendees matching a value. Works on either a VCALENDAR or on a component.
@@ -2252,7 +2286,7 @@
return attendee
else:
# Find the primary subcomponent
- for attendee in self.properties("ATTENDEE"):
+ for attendee in self.properties(self.recipientPropertyName()):
if normalizeCUAddr(attendee.value()) in test:
return attendee
@@ -2295,7 +2329,7 @@
yield attendee
else:
# Find the primary subcomponent
- for attendee in self.properties("ATTENDEE"):
+ for attendee in self.properties(self.recipientPropertyName()):
yield attendee
@@ -2676,7 +2710,7 @@
for component in self.subcomponents():
if component.name() in ignoredComponents:
continue
- [component.removeProperty(p) for p in tuple(component.properties("ATTENDEE")) if p.value().lower() != attendee.lower()]
+ [component.removeProperty(p) for p in tuple(component.properties(component.recipientPropertyName())) if p.value().lower() != attendee.lower()]
def removeAllButTheseAttendees(self, attendees):
@@ -2691,7 +2725,7 @@
for component in self.subcomponents():
if component.name() in ignoredComponents:
continue
- [component.removeProperty(p) for p in tuple(component.properties("ATTENDEE")) if p.value().lower() not in attendees]
+ [component.removeProperty(p) for p in tuple(component.properties(component.recipientPropertyName())) if p.value().lower() not in attendees]
def hasAlarm(self):
Modified: CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/icaldiff.py 2013-09-19 20:54:14 UTC (rev 11739)
+++ CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/icaldiff.py 2013-09-20 03:40:01 UTC (rev 11740)
@@ -516,6 +516,10 @@
if comp.name() == "VALARM":
serverComponent.addComponent(comp)
+ # VPOLL
+ if serverComponent.name() == "VPOLL":
+ replyNeeded = self._transferVPOLLData(serverComponent, clientComponent)
+
return True, replyNeeded
@@ -556,6 +560,44 @@
return True
+ def _transferVPOLLData(self, serverComponent, clientComponent):
+
+ 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,))
+
+ # 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
+ 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
+
+ return changed
+
+
def _checkInvalidChanges(self, serverComponent, clientComponent, declines):
# Properties we care about: DTSTART, DTEND, DURATION, RRULE, RDATE, EXDATE
@@ -585,7 +627,7 @@
def _getNormalizedDateTimeProperties(self, component):
# Basic time properties
- if component.name() in ("VEVENT", "VJOURNAL",):
+ if component.name() in ("VEVENT", "VJOURNAL", "VPOLL"):
dtstart = component.getProperty("DTSTART")
dtend = component.getProperty("DTEND")
duration = component.getProperty("DURATION")
@@ -612,6 +654,9 @@
newdue = component.getProperty("DUE")
if newdue is not None:
newdue = newdue.value().duplicate().adjustToUTC()
+ else:
+ timeRange = Period()
+ newdue = None
# Recurrence rules - we need to normalize the order of the value parts
newrrules = set()
@@ -734,6 +779,7 @@
comp.normalizePropertyValueLists("EXDATE")
comp.removePropertyParameters("ORGANIZER", ("SCHEDULE-STATUS",))
comp.removePropertyParameters("ATTENDEE", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
+ comp.removePropertyParameters("VOTER", ("SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
comp.removeAlarms()
comp.normalizeAll()
comp.normalizeAttachments()
Modified: CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/imip/inbound.py
===================================================================
--- CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/imip/inbound.py 2013-09-19 20:54:14 UTC (rev 11739)
+++ CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/imip/inbound.py 2013-09-20 03:40:01 UTC (rev 11740)
@@ -143,6 +143,7 @@
yield scheduleNextMailPoll(self.store, seconds)
+
def shouldDeleteAllMail(serverHostName, inboundServer, username):
"""
Given the hostname of the calendar server, the hostname of the pop/imap
@@ -165,6 +166,7 @@
)
+
@inlineCallbacks
def scheduleNextMailPoll(store, seconds):
txn = store.newTransaction()
Modified: CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/imip/test/test_inbound.py
===================================================================
--- CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/imip/test/test_inbound.py 2013-09-19 20:54:14 UTC (rev 11739)
+++ CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/imip/test/test_inbound.py 2013-09-20 03:40:01 UTC (rev 11740)
@@ -436,13 +436,13 @@
self.assertEquals(self.flagDeletedResult, "xyzzy")
+
class StubFactory(object):
def __init__(self, actionTaken, deleteAllMail):
self.actionTaken = actionTaken
self.deleteAllMail = deleteAllMail
+
def handleMessage(self, messageData):
return succeed(self.actionTaken)
-
-
Modified: CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/implicit.py 2013-09-19 20:54:14 UTC (rev 11739)
+++ CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/implicit.py 2013-09-20 03:40:01 UTC (rev 11740)
@@ -1045,6 +1045,7 @@
# Map each recipient in the response to a status code
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)
@@ -1054,7 +1055,7 @@
self.calendar.setParameterToValueForPropertyWithValue(
"SCHEDULE-STATUS",
status.split(";")[0],
- "ATTENDEE" if is_organizer else "ORGANIZER",
+ propname,
recipient)
Modified: CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/itip.py 2013-09-19 20:54:14 UTC (rev 11739)
+++ CalendarServer/branches/users/cdaboo/json/txdav/caldav/datastore/scheduling/itip.py 2013-09-20 03:40:01 UTC (rev 11740)
@@ -402,9 +402,14 @@
@staticmethod
def updateAttendeeData(from_component, to_component):
"""
+ Called when processing a REPLY only.
+
Copy the PARTSTAT of the Attendee in the from_component to the matching ATTENDEE
in the to_component. Ignore if no match found. Also update the private comments.
+ For VPOLL we need to copy POLL-ITEM-ID response values into the actual matching
+ polled sub-components as VOTER properties.
+
@param from_component: component to copy from
@type from_component: L{Component}
@param to_component: component to copy to
@@ -423,7 +428,7 @@
reqstatus = "2.0"
# Get attendee in from_component - there MUST be only one
- attendees = tuple(from_component.properties("ATTENDEE"))
+ attendees = tuple(from_component.properties(from_component.recipientPropertyName()))
if len(attendees) != 1:
log.error("There must be one and only one ATTENDEE property in a REPLY\n%s" % (str(from_component),))
return None, False, False
@@ -513,10 +518,53 @@
private_comment_changed = True
+ # Do VPOLL transfer
+ if from_component.name() == "VPOLL":
+ # TODO: figure out how to report changes back
+ iTipProcessing.updateVPOLLData(from_component, to_component, attendee)
+
return attendee.value(), partstat_changed, private_comment_changed
@staticmethod
+ def updateVPOLLData(from_component, to_component, attendee):
+ """
+ Update VPOLL sub-components with voter's response.
+
+ @param from_component: component to copy from
+ @type from_component: L{Component}
+ @param to_component: component to copy to
+ @type to_component: L{Component}
+ @param attendee: attendee being processed
+ @type attendee: L{Property}
+ """
+
+ responses = {}
+ for prop in from_component.properties("POLL-ITEM-ID"):
+ responses[prop.value()] = prop
+
+ for component in to_component.subcomponents():
+ if component.name() in ignoredComponents:
+ continue
+ poll_item_id = component.propertyValue("POLL-ITEM-ID")
+ if poll_item_id is None:
+ continue
+ voter = component.getVoterProperty((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
+
+ # 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"))
+
+
+ @staticmethod
def transferItems(from_calendar, to_component, master_valarms, private_comments, transps, completeds, organizer_schedule_status, attendee_dtstamp, other_props, recipient, remove_matched=False):
"""
Transfer properties from a calendar to a component by first trying to match the component in the original calendar and
@@ -884,6 +932,7 @@
"EXDATE",
"ORGANIZER",
"ATTENDEE",
+ "VOTER",
"X-CALENDARSERVER-PRIVATE-COMMENT",
"SUMMARY",
"LOCATION",
@@ -903,10 +952,38 @@
# Strip out unwanted bits
iTipGenerator.prepareSchedulingMessage(itip, reply=True)
+ # Handle VPOLL behavior
+ for component in itip.subcomponents():
+ if component.name() == "VPOLL":
+ iTipGenerator.generateVPOLLReply(component, attendee)
+
return itip
@staticmethod
+ def generateVPOLLReply(vpoll, attendee):
+ """
+ Generate the proper poll response in a reply for each component being voted on.
+
+ @param vpoll: the VPOLL component to process
+ @type vpoll: L{Component}
+ @param attendee: calendar user address of attendee replying
+ @type attendee: C{str}
+ """
+
+ for component in tuple(vpoll.subcomponents()):
+ if component.name() in ignoredComponents:
+ continue
+ 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)
+
+
+ @staticmethod
def prepareSchedulingMessage(itip, reply=False):
"""
Remove properties and parameters that should not be sent in an iTIP message
@@ -932,6 +1009,7 @@
# Property Parameters
itip.removePropertyParameters("ATTENDEE", ("SCHEDULE-AGENT", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND", "X-CALENDARSERVER-DTSTAMP",))
+ itip.removePropertyParameters("VOTER", ("SCHEDULE-AGENT", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND", "X-CALENDARSERVER-DTSTAMP",))
itip.removePropertyParameters("ORGANIZER", ("SCHEDULE-AGENT", "SCHEDULE-STATUS", "SCHEDULE-FORCE-SEND",))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130919/fde0e4fd/attachment-0001.html>
More information about the calendarserver-changes
mailing list