[CalendarServer-changes] [2740] CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Jul 21 09:37:06 PDT 2008


Revision: 2740
          http://trac.macosforge.org/projects/calendarserver/changeset/2740
Author:   cdaboo at apple.com
Date:     2008-07-21 09:37:06 -0700 (Mon, 21 Jul 2008)
Log Message:
-----------
Attendee's implicit PUT behavior. Basically working, but some complicated checks still to do.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/ical.py
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/itip.py
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/implicit.py
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/scheduler.py
    CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/test/test_icalendar.py

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/ical.py	2008-07-21 16:34:35 UTC (rev 2739)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/ical.py	2008-07-21 16:37:06 UTC (rev 2740)
@@ -29,26 +29,17 @@
     "parse_duration",
 ]
 
-import datetime
-import cStringIO as StringIO
-
-from vobject import newFromBehavior, readComponents
-from vobject.base import Component as vComponent
-from vobject.base import ContentLine as vContentLine
-from vobject.base import ParseError as vParseError
-from vobject.icalendar import TimezoneComponent
-from vobject.icalendar import dateTimeToString
-from vobject.icalendar import deltaToOffset
-from vobject.icalendar import getTransition
-from vobject.icalendar import stringToDate, stringToDateTime, stringToDurations
-from vobject.icalendar import utc
-
+from twisted.web2.dav.util import allDataFromStream
 from twisted.web2.stream import IStream
-from twisted.web2.dav.util import allDataFromStream
-
 from twistedcaldav.dateops import compareDateTime, normalizeToUTC, timeRangesOverlap
 from twistedcaldav.instance import InstanceList
 from twistedcaldav.log import Logger
+from vobject import newFromBehavior, readComponents
+from vobject.base import Component as vComponent, ContentLine as vContentLine, ParseError as vParseError
+from vobject.icalendar import TimezoneComponent, dateTimeToString, deltaToOffset, getTransition, stringToDate, stringToDateTime, stringToDurations, utc
+import cStringIO as StringIO
+import datetime
+import heapq
 
 log = Logger()
 
@@ -1200,6 +1191,47 @@
             for exdate in exdates:
                 master_component.addProperty(Property("EXDATE", (exdate,)))
         
+    def removeAllButOneAttendee(self, attendee):
+        """
+        Remove all ATTENDEE properties except for the one specified.
+        """
+
+        assert self.name() == "VCALENDAR", "Not a calendar: %r" % (self,)
+
+        if self.name() == "VCALENDAR":
+            for component in self.subcomponents():
+                if component.name() == "VTIMEZONE":
+                    continue
+                [component.removeProperty(p) for p in tuple(component.properties("ATTENDEE")) if p.value() != attendee]
+            
+    def removeAlarms(self):
+        """
+        Remove all Alarms components
+        """
+
+        if self.name() == "VCALENDAR":
+            for component in self.subcomponents():
+                if component.name() == "VTIMEZONE":
+                    continue
+                component.removeAlarms()
+        else:
+            for component in tuple(self.subcomponents()):
+                if component.name() == "VALARM":
+                    self.removeComponent(component)
+                
+    def removeUnwantedProperties(self, keep_properties):
+        """
+        Remove all properties that do not match the provided set.
+        """
+
+        assert self.name() == "VCALENDAR", "Not a calendar: %r" % (self,)
+
+        if self.name() == "VCALENDAR":
+            for component in self.subcomponents():
+                if component.name() == "VTIMEZONE":
+                    continue
+                [component.removeProperty(p) for p in tuple(component.properties()) if p.name() not in keep_properties]
+            
 ##
 # Dates and date-times
 ##
@@ -1364,9 +1396,8 @@
 
 #
 # This function is from "Python Cookbook, 2d Ed., by Alex Martelli, Anna
-# Martelli Ravenscroft, and Davis Ascher (O'Reilly Media, 2005) 0-596-00797-3."
+# Martelli Ravenscroft, and David Ascher (O'Reilly Media, 2005) 0-596-00797-3."
 #
-import heapq
 def merge(*iterables):
     """
     Merge sorted iterables into one sorted iterable.

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/itip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/itip.py	2008-07-21 16:34:35 UTC (rev 2739)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/itip.py	2008-07-21 16:37:06 UTC (rev 2740)
@@ -956,4 +956,31 @@
         # Now filter out components that do not contain every attendee
         itip.attendeesView(attendees)
         
+        # No alarms
+        itip.removeAlarms()
+
         return itip
+
+    @staticmethod
+    def generateAttendeeReply(original, attendee):
+
+        # Start with a copy of the original as we may have to modify bits of it
+        itip = original.duplicate()
+        itip.addProperty(Property("METHOD", "REPLY"))
+        
+        # Remove all attendees except the one we want
+        itip.removeAllButOneAttendee(attendee)
+        
+        # No alarms
+        itip.removeAlarms()
+
+        # Remove all but essential properties
+        itip.removeUnwantedProperties((
+            "UID",
+            "RECURRENCE-ID",
+            "SEQUENCE",
+            "DTSTAMP",
+            "ORGANIZER",
+            "ATTENDEE",
+        ))
+        return itip

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/implicit.py	2008-07-21 16:34:35 UTC (rev 2739)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/implicit.py	2008-07-21 16:37:06 UTC (rev 2740)
@@ -82,7 +82,7 @@
         # Get the ATTENDEEs
         self.attendeesByInstance = self.calendar.getAttendeesByInstance()
         self.attendees = set()
-        for attendee in self.attendeesByInstance:
+        for attendee, _ignore in self.attendeesByInstance:
             self.attendees.add(attendee)
     
     def isOrganizerScheduling(self):
@@ -115,6 +115,7 @@
         for attendee in self.attendees:
             attendeePrincipal = self.resource.principalForCalendarUserAddress(attendee)
             if attendeePrincipal and attendeePrincipal.principalURL() == str(self.calendar_owner):
+                self.attendee = attendee
                 self.attendeePrincipal = attendeePrincipal
                 return True
         
@@ -140,7 +141,7 @@
             self.oldcalendar = None
             self.cancelledAttendees = ()   
             
-        yield self.scheduleAttendees()
+        yield self.scheduleWithAttendees()
 
     def isChangeSignificant(self):
         
@@ -161,7 +162,7 @@
         self.cancelledAttendees = mappedOld.difference(mappedNew)
 
     @inlineCallbacks
-    def scheduleAttendees(self):
+    def scheduleWithAttendees(self):
         
         # First process cancelled attendees
         yield self.processCancels()
@@ -233,5 +234,53 @@
             # TODO: need to figure out how to process the response for a REQUEST
             returnValue(response)
             
+    @inlineCallbacks
     def doImplicitAttendee(self):
-        pass
+
+        # Get the ORGANIZER's current copy of the calendar object
+        self.orgcalendar = self.getOrganizersCopy()
+        
+        # Determine whether the current change is allowed
+        if not self.isAttendeeChangeSignificant():
+            return
+            
+        yield self.scheduleWithOrganizer()
+
+    def getOrganizersCopy(self):
+        """
+        Get the Organizer's copy of the event being processed.
+        
+        NB it is possible that the Organizer is not hosted on this server
+        so the result here will be None. In that case we have to trust that
+        the attendee does the right thing about changing the details in the event.
+        """
+        
+        # TODO: extract UID and ORGANIZER, find match in ORGANIZER's calendars.
+
+        return None
+    
+    def isAttendeeChangeSignificant(self):
+        """
+        Check whether the change is significant (PARTSTAT) or allowed
+        (attendee can only change their property, alarms, TRANSP, and
+        instances. Raise an exception if it is not allowed.
+        """
+        
+        # TODO: all of the above.
+        return True
+
+    @inlineCallbacks
+    def scheduleWithOrganizer(self):
+
+        itipmsg = iTipGenerator.generateAttendeeReply(self.calendar, self.attendee)
+
+        # Send scheduling message
+
+        # This is a local CALDAV scheduling operation.
+        scheduler = CalDAVScheduler(self.request, self.resource)
+
+        # Do the PUT processing
+        response = (yield scheduler.doSchedulingViaPUT(self.attendee, (self.organizer,), itipmsg))
+        
+        # TODO: need to figure out how to process the response for a REQUEST
+        returnValue(response)

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/scheduler.py	2008-07-21 16:34:35 UTC (rev 2739)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/scheduling/scheduler.py	2008-07-21 16:37:06 UTC (rev 2740)
@@ -493,8 +493,7 @@
         # Attendee's Outbox MUST be the request URI
         attendeePrincipal = self.resource.principalForCalendarUserAddress(attendee)
         if attendeePrincipal:
-            aoutboxURL = attendeePrincipal.scheduleOutboxURL()
-            if aoutboxURL is None or aoutboxURL != self.request.uri:
+            if self.doingPOST and attendeePrincipal.scheduleOutboxURL() != self.request.uri:
                 log.err("ATTENDEE in calendar data does not match owner of Outbox: %s" % (self.calendar,))
                 raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "attendee-allowed")))
         else:

Modified: CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/test/test_icalendar.py	2008-07-21 16:34:35 UTC (rev 2739)
+++ CalendarServer/branches/users/cdaboo/implicit-2660/twistedcaldav/test/test_icalendar.py	2008-07-21 16:37:06 UTC (rev 2740)
@@ -737,3 +737,392 @@
             component = Component.fromString(original)
             component.attendeesView(attendees)
             self.assertEqual(filtered, str(component).replace("\r", ""))
+
+    def test_all_but_one_attendee(self):
+        
+        data = (
+            # One component, no attendees
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                "mailto:user02 at example.com",
+            ),
+
+            # One component, one attendee - removed
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2
+DTSTART:20071114T000000Z
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                "mailto:user1 at example.com",
+            ),
+
+            # One component, one attendee - left
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                "mailto:user2 at example.com",
+            ),
+
+            # One component, two attendees - none left
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-4
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-4
+DTSTART:20071114T000000Z
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                "mailto:user1 at example.com",
+            ),
+
+            # One component, two attendees - one left
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-5
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ATTENDEE:mailto:user3 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-5
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                "mailto:user2 at example.com",
+            ),
+
+        )
+        
+        for original, result, attendee in data:
+            component = Component.fromString(original)
+            component.removeAllButOneAttendee(attendee)
+            self.assertEqual(result, str(component).replace("\r", ""))
+
+    def test_remove_unwanted_properties(self):
+        
+        data = (
+            # One component
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REPLY
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+SUMMARY:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REPLY
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                ("UID", "RECURRENCE-ID", "SEQUENCE", "DTSTAMP", "ORGANIZER", "ATTENDEE",),
+            ),
+
+            # Multiple components
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REPLY
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+METHOD:REPLY
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                ("UID", "RECURRENCE-ID", "SEQUENCE", "DTSTAMP", "ORGANIZER", "ATTENDEE",),
+            ),
+
+        )
+        
+        for original, result, keep_properties in data:
+            component = Component.fromString(original)
+            component.removeUnwantedProperties(keep_properties)
+            self.assertEqual(result, str(component).replace("\r", ""))
+
+    def test_remove_alarms(self):
+        
+        data = (
+            # One component, no alarms
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+
+            # One component, one alarm
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2
+DTSTART:20071114T000000Z
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-2
+DTSTART:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+
+            # Multiple components, one alarm
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+
+            # Multiple components, multiple alarms
+            (
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""",
+                """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//PYVOBJECT//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-3
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+RRULE:FREQ=YEARLY
+END:VEVENT
+BEGIN:VEVENT
+UID:12345-67890
+RECURRENCE-ID:20081114T000000Z
+DTSTART:20071114T010000Z
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""",
+            ),
+        )
+        
+        for original, result in data:
+            component = Component.fromString(original)
+            component.removeAlarms()
+            self.assertEqual(result, str(component).replace("\r", ""))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080721/c8f63a70/attachment-0001.html 


More information about the calendarserver-changes mailing list