[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