[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